headless_project.rs

  1use anyhow::Result;
  2use fs::Fs;
  3use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext};
  4use project::{
  5    buffer_store::{BufferStore, BufferStoreEvent},
  6    worktree_store::WorktreeStore,
  7    ProjectPath, WorktreeId, WorktreeSettings,
  8};
  9use remote::SshSession;
 10use rpc::{
 11    proto::{self, AnyProtoClient, PeerId},
 12    TypedEnvelope,
 13};
 14use settings::{Settings as _, SettingsStore};
 15use std::{
 16    path::{Path, PathBuf},
 17    sync::{atomic::AtomicUsize, Arc},
 18};
 19use util::ResultExt as _;
 20use worktree::Worktree;
 21
 22const PEER_ID: PeerId = PeerId { owner_id: 0, id: 0 };
 23const PROJECT_ID: u64 = 0;
 24
 25pub struct HeadlessProject {
 26    pub fs: Arc<dyn Fs>,
 27    pub session: AnyProtoClient,
 28    pub worktree_store: Model<WorktreeStore>,
 29    pub buffer_store: Model<BufferStore>,
 30    pub next_entry_id: Arc<AtomicUsize>,
 31}
 32
 33impl HeadlessProject {
 34    pub fn init(cx: &mut AppContext) {
 35        cx.set_global(SettingsStore::new(cx));
 36        WorktreeSettings::register(cx);
 37    }
 38
 39    pub fn new(session: Arc<SshSession>, fs: Arc<dyn Fs>, cx: &mut ModelContext<Self>) -> Self {
 40        let this = cx.weak_model();
 41
 42        let worktree_store = cx.new_model(|_| WorktreeStore::new(true));
 43        let buffer_store =
 44            cx.new_model(|cx| BufferStore::new(worktree_store.clone(), Some(PROJECT_ID), cx));
 45        cx.subscribe(&buffer_store, Self::on_buffer_store_event)
 46            .detach();
 47
 48        session.add_request_handler(this.clone(), Self::handle_add_worktree);
 49        session.add_request_handler(this.clone(), Self::handle_open_buffer_by_path);
 50
 51        session.add_request_handler(buffer_store.downgrade(), BufferStore::handle_blame_buffer);
 52        session.add_request_handler(buffer_store.downgrade(), BufferStore::handle_update_buffer);
 53        session.add_request_handler(buffer_store.downgrade(), BufferStore::handle_save_buffer);
 54
 55        session.add_request_handler(
 56            worktree_store.downgrade(),
 57            WorktreeStore::handle_create_project_entry,
 58        );
 59        session.add_request_handler(
 60            worktree_store.downgrade(),
 61            WorktreeStore::handle_rename_project_entry,
 62        );
 63        session.add_request_handler(
 64            worktree_store.downgrade(),
 65            WorktreeStore::handle_copy_project_entry,
 66        );
 67        session.add_request_handler(
 68            worktree_store.downgrade(),
 69            WorktreeStore::handle_delete_project_entry,
 70        );
 71        session.add_request_handler(
 72            worktree_store.downgrade(),
 73            WorktreeStore::handle_expand_project_entry,
 74        );
 75
 76        HeadlessProject {
 77            session: session.into(),
 78            fs,
 79            worktree_store,
 80            buffer_store,
 81            next_entry_id: Default::default(),
 82        }
 83    }
 84
 85    pub async fn handle_add_worktree(
 86        this: Model<Self>,
 87        message: TypedEnvelope<proto::AddWorktree>,
 88        mut cx: AsyncAppContext,
 89    ) -> Result<proto::AddWorktreeResponse> {
 90        let worktree = this
 91            .update(&mut cx.clone(), |this, _| {
 92                Worktree::local(
 93                    Path::new(&message.payload.path),
 94                    true,
 95                    this.fs.clone(),
 96                    this.next_entry_id.clone(),
 97                    &mut cx,
 98                )
 99            })?
100            .await?;
101
102        this.update(&mut cx, |this, cx| {
103            let session = this.session.clone();
104            this.worktree_store.update(cx, |worktree_store, cx| {
105                worktree_store.add(&worktree, cx);
106            });
107            worktree.update(cx, |worktree, cx| {
108                worktree.observe_updates(0, cx, move |update| {
109                    session.send(update).ok();
110                    futures::future::ready(true)
111                });
112                proto::AddWorktreeResponse {
113                    worktree_id: worktree.id().to_proto(),
114                }
115            })
116        })
117    }
118
119    pub async fn handle_open_buffer_by_path(
120        this: Model<Self>,
121        message: TypedEnvelope<proto::OpenBufferByPath>,
122        mut cx: AsyncAppContext,
123    ) -> Result<proto::OpenBufferResponse> {
124        let worktree_id = WorktreeId::from_proto(message.payload.worktree_id);
125        let (buffer_store, buffer, session) = this.update(&mut cx, |this, cx| {
126            let buffer_store = this.buffer_store.clone();
127            let buffer = this.buffer_store.update(cx, |buffer_store, cx| {
128                buffer_store.open_buffer(
129                    ProjectPath {
130                        worktree_id,
131                        path: PathBuf::from(message.payload.path).into(),
132                    },
133                    cx,
134                )
135            });
136            anyhow::Ok((buffer_store, buffer, this.session.clone()))
137        })??;
138
139        let buffer = buffer.await?;
140        let buffer_id = buffer.read_with(&cx, |b, _| b.remote_id())?;
141
142        cx.spawn(|mut cx| async move {
143            BufferStore::create_buffer_for_peer(
144                buffer_store,
145                PEER_ID,
146                buffer_id,
147                PROJECT_ID,
148                session,
149                &mut cx,
150            )
151            .await
152        })
153        .detach();
154
155        Ok(proto::OpenBufferResponse {
156            buffer_id: buffer_id.to_proto(),
157        })
158    }
159
160    pub fn on_buffer_store_event(
161        &mut self,
162        _: Model<BufferStore>,
163        event: &BufferStoreEvent,
164        _: &mut ModelContext<Self>,
165    ) {
166        match event {
167            BufferStoreEvent::MessageToReplicas(message) => {
168                self.session
169                    .send_dynamic(message.as_ref().clone())
170                    .log_err();
171            }
172            _ => {}
173        }
174    }
175}