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}