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 = cx.new_model(|cx| BufferStore::new(worktree_store.clone(), true, cx));
 44        cx.subscribe(&buffer_store, Self::on_buffer_store_event)
 45            .detach();
 46
 47        session.add_request_handler(this.clone(), Self::handle_add_worktree);
 48        session.add_request_handler(this.clone(), Self::handle_open_buffer_by_path);
 49        session.add_request_handler(this.clone(), Self::handle_update_buffer);
 50        session.add_request_handler(this.clone(), Self::handle_save_buffer);
 51        session.add_request_handler(
 52            worktree_store.downgrade(),
 53            WorktreeStore::handle_create_project_entry,
 54        );
 55        session.add_request_handler(
 56            worktree_store.downgrade(),
 57            WorktreeStore::handle_rename_project_entry,
 58        );
 59        session.add_request_handler(
 60            worktree_store.downgrade(),
 61            WorktreeStore::handle_copy_project_entry,
 62        );
 63        session.add_request_handler(
 64            worktree_store.downgrade(),
 65            WorktreeStore::handle_delete_project_entry,
 66        );
 67        session.add_request_handler(
 68            worktree_store.downgrade(),
 69            WorktreeStore::handle_expand_project_entry,
 70        );
 71
 72        HeadlessProject {
 73            session: session.into(),
 74            fs,
 75            worktree_store,
 76            buffer_store,
 77            next_entry_id: Default::default(),
 78        }
 79    }
 80
 81    pub async fn handle_add_worktree(
 82        this: Model<Self>,
 83        message: TypedEnvelope<proto::AddWorktree>,
 84        mut cx: AsyncAppContext,
 85    ) -> Result<proto::AddWorktreeResponse> {
 86        let worktree = this
 87            .update(&mut cx.clone(), |this, _| {
 88                Worktree::local(
 89                    Path::new(&message.payload.path),
 90                    true,
 91                    this.fs.clone(),
 92                    this.next_entry_id.clone(),
 93                    &mut cx,
 94                )
 95            })?
 96            .await?;
 97
 98        this.update(&mut cx, |this, cx| {
 99            let session = this.session.clone();
100            this.worktree_store.update(cx, |worktree_store, cx| {
101                worktree_store.add(&worktree, cx);
102            });
103            worktree.update(cx, |worktree, cx| {
104                worktree.observe_updates(0, cx, move |update| {
105                    session.send(update).ok();
106                    futures::future::ready(true)
107                });
108                proto::AddWorktreeResponse {
109                    worktree_id: worktree.id().to_proto(),
110                }
111            })
112        })
113    }
114
115    pub async fn handle_update_buffer(
116        this: Model<Self>,
117        envelope: TypedEnvelope<proto::UpdateBuffer>,
118        mut cx: AsyncAppContext,
119    ) -> Result<proto::Ack> {
120        this.update(&mut cx, |this, cx| {
121            this.buffer_store.update(cx, |buffer_store, cx| {
122                buffer_store.handle_update_buffer(envelope, false, cx)
123            })
124        })?
125    }
126
127    pub async fn handle_save_buffer(
128        this: Model<Self>,
129        envelope: TypedEnvelope<proto::SaveBuffer>,
130        mut cx: AsyncAppContext,
131    ) -> Result<proto::BufferSaved> {
132        let buffer_store = this.update(&mut cx, |this, _| this.buffer_store.clone())?;
133        BufferStore::handle_save_buffer(buffer_store, PROJECT_ID, envelope, cx).await
134    }
135
136    pub async fn handle_open_buffer_by_path(
137        this: Model<Self>,
138        message: TypedEnvelope<proto::OpenBufferByPath>,
139        mut cx: AsyncAppContext,
140    ) -> Result<proto::OpenBufferResponse> {
141        let worktree_id = WorktreeId::from_proto(message.payload.worktree_id);
142        let (buffer_store, buffer, session) = this.update(&mut cx, |this, cx| {
143            let buffer_store = this.buffer_store.clone();
144            let buffer = this.buffer_store.update(cx, |buffer_store, cx| {
145                buffer_store.open_buffer(
146                    ProjectPath {
147                        worktree_id,
148                        path: PathBuf::from(message.payload.path).into(),
149                    },
150                    cx,
151                )
152            });
153            anyhow::Ok((buffer_store, buffer, this.session.clone()))
154        })??;
155
156        let buffer = buffer.await?;
157        let buffer_id = buffer.read_with(&cx, |b, _| b.remote_id())?;
158
159        cx.spawn(|mut cx| async move {
160            BufferStore::create_buffer_for_peer(
161                buffer_store,
162                PEER_ID,
163                buffer_id,
164                PROJECT_ID,
165                session,
166                &mut cx,
167            )
168            .await
169        })
170        .detach();
171
172        Ok(proto::OpenBufferResponse {
173            buffer_id: buffer_id.to_proto(),
174        })
175    }
176
177    pub fn on_buffer_store_event(
178        &mut self,
179        _: Model<BufferStore>,
180        event: &BufferStoreEvent,
181        cx: &mut ModelContext<Self>,
182    ) {
183        match event {
184            BufferStoreEvent::LocalBufferUpdated { buffer } => {
185                let buffer = buffer.read(cx);
186                let buffer_id = buffer.remote_id();
187                let Some(new_file) = buffer.file() else {
188                    return;
189                };
190                self.session
191                    .send(proto::UpdateBufferFile {
192                        project_id: 0,
193                        buffer_id: buffer_id.into(),
194                        file: Some(new_file.to_proto(cx)),
195                    })
196                    .log_err();
197            }
198            BufferStoreEvent::DiffBaseUpdated { buffer } => {
199                let buffer = buffer.read(cx);
200                let buffer_id = buffer.remote_id();
201                let diff_base = buffer.diff_base();
202                self.session
203                    .send(proto::UpdateDiffBase {
204                        project_id: 0,
205                        buffer_id: buffer_id.to_proto(),
206                        diff_base: diff_base.map(|b| b.to_string()),
207                    })
208                    .log_err();
209            }
210            _ => {}
211        }
212    }
213}