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}