1use anyhow::{Context as _, Result, anyhow};
  2use lsp::LanguageServerId;
  3
  4use extension::ExtensionHostProxy;
  5use extension_host::headless_host::HeadlessExtensionStore;
  6use fs::Fs;
  7use gpui::{App, AppContext as _, AsyncApp, Context, Entity, PromptLevel};
  8use http_client::HttpClient;
  9use language::{Buffer, BufferEvent, LanguageRegistry, proto::serialize_operation};
 10use node_runtime::NodeRuntime;
 11use project::{
 12    LspStore, LspStoreEvent, ManifestTree, PrettierStore, ProjectEnvironment, ProjectPath,
 13    ToolchainStore, WorktreeId,
 14    agent_server_store::AgentServerStore,
 15    buffer_store::{BufferStore, BufferStoreEvent},
 16    debugger::{breakpoint_store::BreakpointStore, dap_store::DapStore},
 17    git_store::GitStore,
 18    lsp_store::log_store::{self, GlobalLogStore, LanguageServerKind},
 19    project_settings::SettingsObserver,
 20    search::SearchQuery,
 21    task_store::TaskStore,
 22    worktree_store::WorktreeStore,
 23};
 24use rpc::{
 25    AnyProtoClient, TypedEnvelope,
 26    proto::{self, REMOTE_SERVER_PEER_ID, REMOTE_SERVER_PROJECT_ID},
 27};
 28
 29use settings::{Settings as _, initial_server_settings_content};
 30use smol::stream::StreamExt;
 31use std::{
 32    path::{Path, PathBuf},
 33    sync::{Arc, atomic::AtomicUsize},
 34};
 35use sysinfo::{ProcessRefreshKind, RefreshKind, System, UpdateKind};
 36use util::{ResultExt, paths::PathStyle, rel_path::RelPath};
 37use worktree::Worktree;
 38
 39pub struct HeadlessProject {
 40    pub fs: Arc<dyn Fs>,
 41    pub session: AnyProtoClient,
 42    pub worktree_store: Entity<WorktreeStore>,
 43    pub buffer_store: Entity<BufferStore>,
 44    pub lsp_store: Entity<LspStore>,
 45    pub task_store: Entity<TaskStore>,
 46    pub dap_store: Entity<DapStore>,
 47    pub agent_server_store: Entity<AgentServerStore>,
 48    pub settings_observer: Entity<SettingsObserver>,
 49    pub next_entry_id: Arc<AtomicUsize>,
 50    pub languages: Arc<LanguageRegistry>,
 51    pub extensions: Entity<HeadlessExtensionStore>,
 52    pub git_store: Entity<GitStore>,
 53    pub environment: Entity<ProjectEnvironment>,
 54    // Used mostly to keep alive the toolchain store for RPC handlers.
 55    // Local variant is used within LSP store, but that's a separate entity.
 56    pub _toolchain_store: Entity<ToolchainStore>,
 57}
 58
 59pub struct HeadlessAppState {
 60    pub session: AnyProtoClient,
 61    pub fs: Arc<dyn Fs>,
 62    pub http_client: Arc<dyn HttpClient>,
 63    pub node_runtime: NodeRuntime,
 64    pub languages: Arc<LanguageRegistry>,
 65    pub extension_host_proxy: Arc<ExtensionHostProxy>,
 66}
 67
 68impl HeadlessProject {
 69    pub fn init(cx: &mut App) {
 70        settings::init(cx);
 71        language::init(cx);
 72        project::Project::init_settings(cx);
 73        extension_host::ExtensionSettings::register(cx);
 74        log_store::init(true, cx);
 75    }
 76
 77    pub fn new(
 78        HeadlessAppState {
 79            session,
 80            fs,
 81            http_client,
 82            node_runtime,
 83            languages,
 84            extension_host_proxy: proxy,
 85        }: HeadlessAppState,
 86        cx: &mut Context<Self>,
 87    ) -> Self {
 88        debug_adapter_extension::init(proxy.clone(), cx);
 89        languages::init(languages.clone(), fs.clone(), node_runtime.clone(), cx);
 90
 91        let worktree_store = cx.new(|cx| {
 92            let mut store = WorktreeStore::local(true, fs.clone());
 93            store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
 94            store
 95        });
 96
 97        let environment =
 98            cx.new(|cx| ProjectEnvironment::new(None, worktree_store.downgrade(), None, cx));
 99        let manifest_tree = ManifestTree::new(worktree_store.clone(), cx);
100        let toolchain_store = cx.new(|cx| {
101            ToolchainStore::local(
102                languages.clone(),
103                worktree_store.clone(),
104                environment.clone(),
105                manifest_tree.clone(),
106                fs.clone(),
107                cx,
108            )
109        });
110
111        let buffer_store = cx.new(|cx| {
112            let mut buffer_store = BufferStore::local(worktree_store.clone(), cx);
113            buffer_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
114            buffer_store
115        });
116
117        let breakpoint_store =
118            cx.new(|_| BreakpointStore::local(worktree_store.clone(), buffer_store.clone()));
119
120        let dap_store = cx.new(|cx| {
121            let mut dap_store = DapStore::new_local(
122                http_client.clone(),
123                node_runtime.clone(),
124                fs.clone(),
125                environment.clone(),
126                toolchain_store.read(cx).as_language_toolchain_store(),
127                worktree_store.clone(),
128                breakpoint_store.clone(),
129                true,
130                cx,
131            );
132            dap_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
133            dap_store
134        });
135
136        let git_store = cx.new(|cx| {
137            let mut store = GitStore::local(
138                &worktree_store,
139                buffer_store.clone(),
140                environment.clone(),
141                fs.clone(),
142                cx,
143            );
144            store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
145            store
146        });
147
148        let prettier_store = cx.new(|cx| {
149            PrettierStore::new(
150                node_runtime.clone(),
151                fs.clone(),
152                languages.clone(),
153                worktree_store.clone(),
154                cx,
155            )
156        });
157
158        let task_store = cx.new(|cx| {
159            let mut task_store = TaskStore::local(
160                buffer_store.downgrade(),
161                worktree_store.clone(),
162                toolchain_store.read(cx).as_language_toolchain_store(),
163                environment.clone(),
164                cx,
165            );
166            task_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
167            task_store
168        });
169        let settings_observer = cx.new(|cx| {
170            let mut observer = SettingsObserver::new_local(
171                fs.clone(),
172                worktree_store.clone(),
173                task_store.clone(),
174                cx,
175            );
176            observer.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
177            observer
178        });
179
180        let lsp_store = cx.new(|cx| {
181            let mut lsp_store = LspStore::new_local(
182                buffer_store.clone(),
183                worktree_store.clone(),
184                prettier_store.clone(),
185                toolchain_store
186                    .read(cx)
187                    .as_local_store()
188                    .expect("Toolchain store to be local")
189                    .clone(),
190                environment.clone(),
191                manifest_tree,
192                languages.clone(),
193                http_client.clone(),
194                fs.clone(),
195                cx,
196            );
197            lsp_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
198            lsp_store
199        });
200
201        let agent_server_store = cx.new(|cx| {
202            let mut agent_server_store = AgentServerStore::local(
203                node_runtime.clone(),
204                fs.clone(),
205                environment.clone(),
206                http_client.clone(),
207                cx,
208            );
209            agent_server_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
210            agent_server_store
211        });
212
213        cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
214        language_extension::init(
215            language_extension::LspAccess::ViaLspStore(lsp_store.clone()),
216            proxy.clone(),
217            languages.clone(),
218        );
219
220        cx.subscribe(&buffer_store, |_this, _buffer_store, event, cx| {
221            if let BufferStoreEvent::BufferAdded(buffer) = event {
222                cx.subscribe(buffer, Self::on_buffer_event).detach();
223            }
224        })
225        .detach();
226
227        let extensions = HeadlessExtensionStore::new(
228            fs.clone(),
229            http_client.clone(),
230            paths::remote_extensions_dir().to_path_buf(),
231            proxy,
232            node_runtime,
233            cx,
234        );
235
236        // local_machine -> ssh handlers
237        session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &worktree_store);
238        session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &buffer_store);
239        session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &cx.entity());
240        session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &lsp_store);
241        session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &task_store);
242        session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &toolchain_store);
243        session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &dap_store);
244        session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &settings_observer);
245        session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &git_store);
246        session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &agent_server_store);
247
248        session.add_request_handler(cx.weak_entity(), Self::handle_list_remote_directory);
249        session.add_request_handler(cx.weak_entity(), Self::handle_get_path_metadata);
250        session.add_request_handler(cx.weak_entity(), Self::handle_shutdown_remote_server);
251        session.add_request_handler(cx.weak_entity(), Self::handle_ping);
252        session.add_request_handler(cx.weak_entity(), Self::handle_get_processes);
253
254        session.add_entity_request_handler(Self::handle_add_worktree);
255        session.add_request_handler(cx.weak_entity(), Self::handle_remove_worktree);
256
257        session.add_entity_request_handler(Self::handle_open_buffer_by_path);
258        session.add_entity_request_handler(Self::handle_open_new_buffer);
259        session.add_entity_request_handler(Self::handle_find_search_candidates);
260        session.add_entity_request_handler(Self::handle_open_server_settings);
261        session.add_entity_request_handler(Self::handle_get_directory_environment);
262        session.add_entity_message_handler(Self::handle_toggle_lsp_logs);
263
264        session.add_entity_request_handler(BufferStore::handle_update_buffer);
265        session.add_entity_message_handler(BufferStore::handle_close_buffer);
266
267        session.add_request_handler(
268            extensions.downgrade(),
269            HeadlessExtensionStore::handle_sync_extensions,
270        );
271        session.add_request_handler(
272            extensions.downgrade(),
273            HeadlessExtensionStore::handle_install_extension,
274        );
275
276        BufferStore::init(&session);
277        WorktreeStore::init(&session);
278        SettingsObserver::init(&session);
279        LspStore::init(&session);
280        TaskStore::init(Some(&session));
281        ToolchainStore::init(&session);
282        DapStore::init(&session, cx);
283        // todo(debugger): Re init breakpoint store when we set it up for collab
284        // BreakpointStore::init(&client);
285        GitStore::init(&session);
286        AgentServerStore::init_headless(&session);
287
288        HeadlessProject {
289            next_entry_id: Default::default(),
290            session,
291            settings_observer,
292            fs,
293            worktree_store,
294            buffer_store,
295            lsp_store,
296            task_store,
297            dap_store,
298            agent_server_store,
299            languages,
300            extensions,
301            git_store,
302            environment,
303            _toolchain_store: toolchain_store,
304        }
305    }
306
307    fn on_buffer_event(
308        &mut self,
309        buffer: Entity<Buffer>,
310        event: &BufferEvent,
311        cx: &mut Context<Self>,
312    ) {
313        if let BufferEvent::Operation {
314            operation,
315            is_local: true,
316        } = event
317        {
318            cx.background_spawn(self.session.request(proto::UpdateBuffer {
319                project_id: REMOTE_SERVER_PROJECT_ID,
320                buffer_id: buffer.read(cx).remote_id().to_proto(),
321                operations: vec![serialize_operation(operation)],
322            }))
323            .detach()
324        }
325    }
326
327    fn on_lsp_store_event(
328        &mut self,
329        lsp_store: Entity<LspStore>,
330        event: &LspStoreEvent,
331        cx: &mut Context<Self>,
332    ) {
333        match event {
334            LspStoreEvent::LanguageServerAdded(id, name, worktree_id) => {
335                let log_store = cx
336                    .try_global::<GlobalLogStore>()
337                    .map(|lsp_logs| lsp_logs.0.clone());
338                if let Some(log_store) = log_store {
339                    log_store.update(cx, |log_store, cx| {
340                        log_store.add_language_server(
341                            LanguageServerKind::LocalSsh {
342                                lsp_store: self.lsp_store.downgrade(),
343                            },
344                            *id,
345                            Some(name.clone()),
346                            *worktree_id,
347                            lsp_store.read(cx).language_server_for_id(*id),
348                            cx,
349                        );
350                    });
351                }
352            }
353            LspStoreEvent::LanguageServerRemoved(id) => {
354                let log_store = cx
355                    .try_global::<GlobalLogStore>()
356                    .map(|lsp_logs| lsp_logs.0.clone());
357                if let Some(log_store) = log_store {
358                    log_store.update(cx, |log_store, cx| {
359                        log_store.remove_language_server(*id, cx);
360                    });
361                }
362            }
363            LspStoreEvent::LanguageServerUpdate {
364                language_server_id,
365                name,
366                message,
367            } => {
368                self.session
369                    .send(proto::UpdateLanguageServer {
370                        project_id: REMOTE_SERVER_PROJECT_ID,
371                        server_name: name.as_ref().map(|name| name.to_string()),
372                        language_server_id: language_server_id.to_proto(),
373                        variant: Some(message.clone()),
374                    })
375                    .log_err();
376            }
377            LspStoreEvent::Notification(message) => {
378                self.session
379                    .send(proto::Toast {
380                        project_id: REMOTE_SERVER_PROJECT_ID,
381                        notification_id: "lsp".to_string(),
382                        message: message.clone(),
383                    })
384                    .log_err();
385            }
386            LspStoreEvent::LanguageServerPrompt(prompt) => {
387                let request = self.session.request(proto::LanguageServerPromptRequest {
388                    project_id: REMOTE_SERVER_PROJECT_ID,
389                    actions: prompt
390                        .actions
391                        .iter()
392                        .map(|action| action.title.to_string())
393                        .collect(),
394                    level: Some(prompt_to_proto(prompt)),
395                    lsp_name: prompt.lsp_name.clone(),
396                    message: prompt.message.clone(),
397                });
398                let prompt = prompt.clone();
399                cx.background_spawn(async move {
400                    let response = request.await?;
401                    if let Some(action_response) = response.action_response {
402                        prompt.respond(action_response as usize).await;
403                    }
404                    anyhow::Ok(())
405                })
406                .detach();
407            }
408            _ => {}
409        }
410    }
411
412    pub async fn handle_add_worktree(
413        this: Entity<Self>,
414        message: TypedEnvelope<proto::AddWorktree>,
415        mut cx: AsyncApp,
416    ) -> Result<proto::AddWorktreeResponse> {
417        use client::ErrorCodeExt;
418        let fs = this.read_with(&cx, |this, _| this.fs.clone())?;
419        let path = PathBuf::from(shellexpand::tilde(&message.payload.path).to_string());
420
421        let canonicalized = match fs.canonicalize(&path).await {
422            Ok(path) => path,
423            Err(e) => {
424                let mut parent = path
425                    .parent()
426                    .ok_or(e)
427                    .with_context(|| format!("{path:?} does not exist"))?;
428                if parent == Path::new("") {
429                    parent = util::paths::home_dir();
430                }
431                let parent = fs.canonicalize(parent).await.map_err(|_| {
432                    anyhow!(
433                        proto::ErrorCode::DevServerProjectPathDoesNotExist
434                            .with_tag("path", path.to_string_lossy().as_ref())
435                    )
436                })?;
437                parent.join(path.file_name().unwrap())
438            }
439        };
440
441        let worktree = this
442            .read_with(&cx.clone(), |this, _| {
443                Worktree::local(
444                    Arc::from(canonicalized.as_path()),
445                    message.payload.visible,
446                    this.fs.clone(),
447                    this.next_entry_id.clone(),
448                    &mut cx,
449                )
450            })?
451            .await?;
452
453        let response = this.read_with(&cx, |_, cx| {
454            let worktree = worktree.read(cx);
455            proto::AddWorktreeResponse {
456                worktree_id: worktree.id().to_proto(),
457                canonicalized_path: canonicalized.to_string_lossy().into_owned(),
458            }
459        })?;
460
461        // We spawn this asynchronously, so that we can send the response back
462        // *before* `worktree_store.add()` can send out UpdateProject requests
463        // to the client about the new worktree.
464        //
465        // That lets the client manage the reference/handles of the newly-added
466        // worktree, before getting interrupted by an UpdateProject request.
467        //
468        // This fixes the problem of the client sending the AddWorktree request,
469        // headless project sending out a project update, client receiving it
470        // and immediately dropping the reference of the new client, causing it
471        // to be dropped on the headless project, and the client only then
472        // receiving a response to AddWorktree.
473        cx.spawn(async move |cx| {
474            this.update(cx, |this, cx| {
475                this.worktree_store.update(cx, |worktree_store, cx| {
476                    worktree_store.add(&worktree, cx);
477                });
478            })
479            .log_err();
480        })
481        .detach();
482
483        Ok(response)
484    }
485
486    pub async fn handle_remove_worktree(
487        this: Entity<Self>,
488        envelope: TypedEnvelope<proto::RemoveWorktree>,
489        mut cx: AsyncApp,
490    ) -> Result<proto::Ack> {
491        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
492        this.update(&mut cx, |this, cx| {
493            this.worktree_store.update(cx, |worktree_store, cx| {
494                worktree_store.remove_worktree(worktree_id, cx);
495            });
496        })?;
497        Ok(proto::Ack {})
498    }
499
500    pub async fn handle_open_buffer_by_path(
501        this: Entity<Self>,
502        message: TypedEnvelope<proto::OpenBufferByPath>,
503        mut cx: AsyncApp,
504    ) -> Result<proto::OpenBufferResponse> {
505        let worktree_id = WorktreeId::from_proto(message.payload.worktree_id);
506        let path = RelPath::from_proto(&message.payload.path)?;
507        let (buffer_store, buffer) = this.update(&mut cx, |this, cx| {
508            let buffer_store = this.buffer_store.clone();
509            let buffer = this.buffer_store.update(cx, |buffer_store, cx| {
510                buffer_store.open_buffer(ProjectPath { worktree_id, path }, cx)
511            });
512            anyhow::Ok((buffer_store, buffer))
513        })??;
514
515        let buffer = buffer.await?;
516        let buffer_id = buffer.read_with(&cx, |b, _| b.remote_id())?;
517        buffer_store.update(&mut cx, |buffer_store, cx| {
518            buffer_store
519                .create_buffer_for_peer(&buffer, REMOTE_SERVER_PEER_ID, cx)
520                .detach_and_log_err(cx);
521        })?;
522
523        Ok(proto::OpenBufferResponse {
524            buffer_id: buffer_id.to_proto(),
525        })
526    }
527
528    pub async fn handle_open_new_buffer(
529        this: Entity<Self>,
530        _message: TypedEnvelope<proto::OpenNewBuffer>,
531        mut cx: AsyncApp,
532    ) -> Result<proto::OpenBufferResponse> {
533        let (buffer_store, buffer) = this.update(&mut cx, |this, cx| {
534            let buffer_store = this.buffer_store.clone();
535            let buffer = this
536                .buffer_store
537                .update(cx, |buffer_store, cx| buffer_store.create_buffer(true, cx));
538            anyhow::Ok((buffer_store, buffer))
539        })??;
540
541        let buffer = buffer.await?;
542        let buffer_id = buffer.read_with(&cx, |b, _| b.remote_id())?;
543        buffer_store.update(&mut cx, |buffer_store, cx| {
544            buffer_store
545                .create_buffer_for_peer(&buffer, REMOTE_SERVER_PEER_ID, cx)
546                .detach_and_log_err(cx);
547        })?;
548
549        Ok(proto::OpenBufferResponse {
550            buffer_id: buffer_id.to_proto(),
551        })
552    }
553
554    async fn handle_toggle_lsp_logs(
555        _: Entity<Self>,
556        envelope: TypedEnvelope<proto::ToggleLspLogs>,
557        mut cx: AsyncApp,
558    ) -> Result<()> {
559        let server_id = LanguageServerId::from_proto(envelope.payload.server_id);
560        let lsp_logs = cx
561            .update(|cx| {
562                cx.try_global::<GlobalLogStore>()
563                    .map(|lsp_logs| lsp_logs.0.clone())
564            })?
565            .context("lsp logs store is missing")?;
566
567        lsp_logs.update(&mut cx, |lsp_logs, _| {
568            // RPC logs are very noisy and we need to toggle it on the headless server too.
569            // The rest of the logs for the ssh project are very important to have toggled always,
570            // to e.g. send language server error logs to the client before anything is toggled.
571            if envelope.payload.enabled {
572                lsp_logs.enable_rpc_trace_for_language_server(server_id);
573            } else {
574                lsp_logs.disable_rpc_trace_for_language_server(server_id);
575            }
576        })?;
577        Ok(())
578    }
579
580    async fn handle_open_server_settings(
581        this: Entity<Self>,
582        _: TypedEnvelope<proto::OpenServerSettings>,
583        mut cx: AsyncApp,
584    ) -> Result<proto::OpenBufferResponse> {
585        let settings_path = paths::settings_file();
586        let (worktree, path) = this
587            .update(&mut cx, |this, cx| {
588                this.worktree_store.update(cx, |worktree_store, cx| {
589                    worktree_store.find_or_create_worktree(settings_path, false, cx)
590                })
591            })?
592            .await?;
593
594        let (buffer, buffer_store) = this.update(&mut cx, |this, cx| {
595            let buffer = this.buffer_store.update(cx, |buffer_store, cx| {
596                buffer_store.open_buffer(
597                    ProjectPath {
598                        worktree_id: worktree.read(cx).id(),
599                        path: path,
600                    },
601                    cx,
602                )
603            });
604
605            (buffer, this.buffer_store.clone())
606        })?;
607
608        let buffer = buffer.await?;
609
610        let buffer_id = cx.update(|cx| {
611            if buffer.read(cx).is_empty() {
612                buffer.update(cx, |buffer, cx| {
613                    buffer.edit([(0..0, initial_server_settings_content())], None, cx)
614                });
615            }
616
617            let buffer_id = buffer.read(cx).remote_id();
618
619            buffer_store.update(cx, |buffer_store, cx| {
620                buffer_store
621                    .create_buffer_for_peer(&buffer, REMOTE_SERVER_PEER_ID, cx)
622                    .detach_and_log_err(cx);
623            });
624
625            buffer_id
626        })?;
627
628        Ok(proto::OpenBufferResponse {
629            buffer_id: buffer_id.to_proto(),
630        })
631    }
632
633    async fn handle_find_search_candidates(
634        this: Entity<Self>,
635        envelope: TypedEnvelope<proto::FindSearchCandidates>,
636        mut cx: AsyncApp,
637    ) -> Result<proto::FindSearchCandidatesResponse> {
638        let message = envelope.payload;
639        let query = SearchQuery::from_proto(
640            message.query.context("missing query field")?,
641            PathStyle::local(),
642        )?;
643        let results = this.update(&mut cx, |this, cx| {
644            this.buffer_store.update(cx, |buffer_store, cx| {
645                buffer_store.find_search_candidates(&query, message.limit as _, this.fs.clone(), cx)
646            })
647        })?;
648
649        let mut response = proto::FindSearchCandidatesResponse {
650            buffer_ids: Vec::new(),
651        };
652
653        let buffer_store = this.read_with(&cx, |this, _| this.buffer_store.clone())?;
654
655        while let Ok(buffer) = results.recv().await {
656            let buffer_id = buffer.read_with(&cx, |this, _| this.remote_id())?;
657            response.buffer_ids.push(buffer_id.to_proto());
658            buffer_store
659                .update(&mut cx, |buffer_store, cx| {
660                    buffer_store.create_buffer_for_peer(&buffer, REMOTE_SERVER_PEER_ID, cx)
661                })?
662                .await?;
663        }
664
665        Ok(response)
666    }
667
668    async fn handle_list_remote_directory(
669        this: Entity<Self>,
670        envelope: TypedEnvelope<proto::ListRemoteDirectory>,
671        cx: AsyncApp,
672    ) -> Result<proto::ListRemoteDirectoryResponse> {
673        let fs = cx.read_entity(&this, |this, _| this.fs.clone())?;
674        let expanded = PathBuf::from(shellexpand::tilde(&envelope.payload.path).to_string());
675        let check_info = envelope
676            .payload
677            .config
678            .as_ref()
679            .is_some_and(|config| config.is_dir);
680
681        let mut entries = Vec::new();
682        let mut entry_info = Vec::new();
683        let mut response = fs.read_dir(&expanded).await?;
684        while let Some(path) = response.next().await {
685            let path = path?;
686            if let Some(file_name) = path.file_name() {
687                entries.push(file_name.to_string_lossy().into_owned());
688                if check_info {
689                    let is_dir = fs.is_dir(&path).await;
690                    entry_info.push(proto::EntryInfo { is_dir });
691                }
692            }
693        }
694        Ok(proto::ListRemoteDirectoryResponse {
695            entries,
696            entry_info,
697        })
698    }
699
700    async fn handle_get_path_metadata(
701        this: Entity<Self>,
702        envelope: TypedEnvelope<proto::GetPathMetadata>,
703        cx: AsyncApp,
704    ) -> Result<proto::GetPathMetadataResponse> {
705        let fs = cx.read_entity(&this, |this, _| this.fs.clone())?;
706        let expanded = PathBuf::from(shellexpand::tilde(&envelope.payload.path).to_string());
707
708        let metadata = fs.metadata(&expanded).await?;
709        let is_dir = metadata.map(|metadata| metadata.is_dir).unwrap_or(false);
710
711        Ok(proto::GetPathMetadataResponse {
712            exists: metadata.is_some(),
713            is_dir,
714            path: expanded.to_string_lossy().into_owned(),
715        })
716    }
717
718    async fn handle_shutdown_remote_server(
719        _this: Entity<Self>,
720        _envelope: TypedEnvelope<proto::ShutdownRemoteServer>,
721        cx: AsyncApp,
722    ) -> Result<proto::Ack> {
723        cx.spawn(async move |cx| {
724            cx.update(|cx| {
725                // TODO: This is a hack, because in a headless project, shutdown isn't executed
726                // when calling quit, but it should be.
727                cx.shutdown();
728                cx.quit();
729            })
730        })
731        .detach();
732
733        Ok(proto::Ack {})
734    }
735
736    pub async fn handle_ping(
737        _this: Entity<Self>,
738        _envelope: TypedEnvelope<proto::Ping>,
739        _cx: AsyncApp,
740    ) -> Result<proto::Ack> {
741        log::debug!("Received ping from client");
742        Ok(proto::Ack {})
743    }
744
745    async fn handle_get_processes(
746        _this: Entity<Self>,
747        _envelope: TypedEnvelope<proto::GetProcesses>,
748        _cx: AsyncApp,
749    ) -> Result<proto::GetProcessesResponse> {
750        let mut processes = Vec::new();
751        let refresh_kind = RefreshKind::nothing().with_processes(
752            ProcessRefreshKind::nothing()
753                .without_tasks()
754                .with_cmd(UpdateKind::Always),
755        );
756
757        for process in System::new_with_specifics(refresh_kind)
758            .processes()
759            .values()
760        {
761            let name = process.name().to_string_lossy().into_owned();
762            let command = process
763                .cmd()
764                .iter()
765                .map(|s| s.to_string_lossy().into_owned())
766                .collect::<Vec<_>>();
767
768            processes.push(proto::ProcessInfo {
769                pid: process.pid().as_u32(),
770                name,
771                command,
772            });
773        }
774
775        processes.sort_by_key(|p| p.name.clone());
776
777        Ok(proto::GetProcessesResponse { processes })
778    }
779
780    async fn handle_get_directory_environment(
781        this: Entity<Self>,
782        envelope: TypedEnvelope<proto::GetDirectoryEnvironment>,
783        mut cx: AsyncApp,
784    ) -> Result<proto::DirectoryEnvironment> {
785        let shell = task::shell_from_proto(envelope.payload.shell.context("missing shell")?)?;
786        let directory = PathBuf::from(envelope.payload.directory);
787        let environment = this
788            .update(&mut cx, |this, cx| {
789                this.environment.update(cx, |environment, cx| {
790                    environment.local_directory_environment(&shell, directory.into(), cx)
791                })
792            })?
793            .await
794            .context("failed to get directory environment")?
795            .into_iter()
796            .collect();
797        Ok(proto::DirectoryEnvironment { environment })
798    }
799}
800
801fn prompt_to_proto(
802    prompt: &project::LanguageServerPromptRequest,
803) -> proto::language_server_prompt_request::Level {
804    match prompt.level {
805        PromptLevel::Info => proto::language_server_prompt_request::Level::Info(
806            proto::language_server_prompt_request::Info {},
807        ),
808        PromptLevel::Warning => proto::language_server_prompt_request::Level::Warning(
809            proto::language_server_prompt_request::Warning {},
810        ),
811        PromptLevel::Critical => proto::language_server_prompt_request::Level::Critical(
812            proto::language_server_prompt_request::Critical {},
813        ),
814    }
815}