headless_project.rs

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