headless_project.rs

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