1use super::{ItemView, ItemViewHandle};
2use crate::{
3 editor::Buffer,
4 settings::Settings,
5 time::ReplicaId,
6 watch,
7 worktree::{Worktree, WorktreeHandle as _},
8};
9use anyhow::anyhow;
10use gpui::{AppContext, Entity, Handle, ModelContext, ModelHandle, MutableAppContext, ViewContext};
11use smol::prelude::*;
12use std::{
13 collections::{HashMap, HashSet},
14 fmt::Debug,
15 path::{Path, PathBuf},
16 pin::Pin,
17 sync::Arc,
18};
19
20pub trait Item
21where
22 Self: Sized,
23{
24 type View: ItemView;
25 fn build_view(
26 handle: ModelHandle<Self>,
27 settings: watch::Receiver<Settings>,
28 ctx: &mut ViewContext<Self::View>,
29 ) -> Self::View;
30}
31
32pub trait ItemHandle: Debug + Send + Sync {
33 fn add_view(
34 &self,
35 window_id: usize,
36 settings: watch::Receiver<Settings>,
37 app: &mut MutableAppContext,
38 ) -> Box<dyn ItemViewHandle>;
39 fn id(&self) -> usize;
40 fn boxed_clone(&self) -> Box<dyn ItemHandle>;
41}
42
43impl<T: 'static + Item> ItemHandle for ModelHandle<T> {
44 fn add_view(
45 &self,
46 window_id: usize,
47 settings: watch::Receiver<Settings>,
48 app: &mut MutableAppContext,
49 ) -> Box<dyn ItemViewHandle> {
50 Box::new(app.add_view(window_id, |ctx| T::build_view(self.clone(), settings, ctx)))
51 }
52
53 fn id(&self) -> usize {
54 Handle::id(self)
55 }
56
57 fn boxed_clone(&self) -> Box<dyn ItemHandle> {
58 Box::new(self.clone())
59 }
60}
61
62impl Clone for Box<dyn ItemHandle> {
63 fn clone(&self) -> Self {
64 self.boxed_clone()
65 }
66}
67
68pub type OpenResult = Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>;
69
70#[derive(Clone)]
71enum OpenedItem {
72 Loading(watch::Receiver<Option<OpenResult>>),
73 Loaded(Box<dyn ItemHandle>),
74}
75
76pub struct Workspace {
77 replica_id: ReplicaId,
78 worktrees: HashSet<ModelHandle<Worktree>>,
79 items: HashMap<(usize, u64), OpenedItem>,
80}
81
82impl Workspace {
83 pub fn new(paths: Vec<PathBuf>, ctx: &mut ModelContext<Self>) -> Self {
84 let mut workspace = Self {
85 replica_id: 0,
86 worktrees: HashSet::new(),
87 items: HashMap::new(),
88 };
89 workspace.open_paths(&paths, ctx);
90 workspace
91 }
92
93 pub fn worktrees(&self) -> &HashSet<ModelHandle<Worktree>> {
94 &self.worktrees
95 }
96
97 pub fn contains_paths(&self, paths: &[PathBuf], app: &AppContext) -> bool {
98 paths.iter().all(|path| self.contains_path(&path, app))
99 }
100
101 pub fn contains_path(&self, path: &Path, app: &AppContext) -> bool {
102 self.worktrees
103 .iter()
104 .any(|worktree| worktree.read(app).contains_path(path))
105 }
106
107 pub fn open_paths(&mut self, paths: &[PathBuf], ctx: &mut ModelContext<Self>) {
108 for path in paths.iter().cloned() {
109 self.open_path(path, ctx);
110 }
111 }
112
113 pub fn open_path<'a>(&'a mut self, path: PathBuf, ctx: &mut ModelContext<Self>) {
114 for tree in self.worktrees.iter() {
115 if tree.read(ctx).contains_path(&path) {
116 return;
117 }
118 }
119
120 let worktree = ctx.add_model(|ctx| Worktree::new(ctx.model_id(), path, Some(ctx)));
121 ctx.observe(&worktree, Self::on_worktree_updated);
122 self.worktrees.insert(worktree);
123 ctx.notify();
124 }
125
126 pub fn open_entry(
127 &mut self,
128 entry: (usize, u64),
129 ctx: &mut ModelContext<'_, Self>,
130 ) -> anyhow::Result<Pin<Box<dyn Future<Output = OpenResult> + Send>>> {
131 if let Some(item) = self.items.get(&entry).cloned() {
132 return Ok(async move {
133 match item {
134 OpenedItem::Loaded(handle) => {
135 return Ok(handle);
136 }
137 OpenedItem::Loading(rx) => loop {
138 rx.updated().await;
139
140 if let Some(result) = smol::block_on(rx.read()).clone() {
141 return result;
142 }
143 },
144 }
145 }
146 .boxed());
147 }
148
149 let worktree = self
150 .worktrees
151 .get(&entry.0)
152 .cloned()
153 .ok_or(anyhow!("worktree {} does not exist", entry.0,))?;
154
155 let replica_id = self.replica_id;
156 let file = worktree.file(entry.1, ctx.as_ref())?;
157 let history = file.load_history(ctx.as_ref());
158 let buffer = async move { Ok(Buffer::from_history(replica_id, file, history.await?)) };
159
160 let (mut tx, rx) = watch::channel(None);
161 self.items.insert(entry, OpenedItem::Loading(rx));
162 ctx.spawn(
163 buffer,
164 move |me, buffer: anyhow::Result<Buffer>, ctx| match buffer {
165 Ok(buffer) => {
166 let handle = Box::new(ctx.add_model(|_| buffer)) as Box<dyn ItemHandle>;
167 me.items.insert(entry, OpenedItem::Loaded(handle.clone()));
168 ctx.spawn(
169 async move {
170 tx.update(|value| *value = Some(Ok(handle))).await;
171 },
172 |_, _, _| {},
173 )
174 .detach();
175 }
176 Err(error) => {
177 ctx.spawn(
178 async move {
179 tx.update(|value| *value = Some(Err(Arc::new(error)))).await;
180 },
181 |_, _, _| {},
182 )
183 .detach();
184 }
185 },
186 )
187 .detach();
188
189 self.open_entry(entry, ctx)
190 }
191
192 fn on_worktree_updated(&mut self, _: ModelHandle<Worktree>, ctx: &mut ModelContext<Self>) {
193 ctx.notify();
194 }
195}
196
197impl Entity for Workspace {
198 type Event = ();
199}
200
201#[cfg(test)]
202pub trait WorkspaceHandle {
203 fn file_entries(&self, app: &AppContext) -> Vec<(usize, u64)>;
204}
205
206#[cfg(test)]
207impl WorkspaceHandle for ModelHandle<Workspace> {
208 fn file_entries(&self, app: &AppContext) -> Vec<(usize, u64)> {
209 self.read(app)
210 .worktrees()
211 .iter()
212 .flat_map(|tree| {
213 let tree_id = tree.id();
214 tree.read(app)
215 .files()
216 .map(move |file| (tree_id, file.entry_id))
217 })
218 .collect::<Vec<_>>()
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use crate::test::temp_tree;
226 use gpui::App;
227 use serde_json::json;
228
229 #[test]
230 fn test_open_entry() {
231 App::test_async((), |mut app| async move {
232 let dir = temp_tree(json!({
233 "a": {
234 "aa": "aa contents",
235 "ab": "ab contents",
236 },
237 }));
238
239 let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx));
240 app.finish_pending_tasks().await; // Open and populate worktree.
241
242 // Get the first file entry.
243 let tree = app.read(|ctx| workspace.read(ctx).worktrees.iter().next().unwrap().clone());
244 let entry_id = app.read(|ctx| tree.read(ctx).files().next().unwrap().entry_id);
245 let entry = (tree.id(), entry_id);
246
247 // Open the same entry twice before it finishes loading.
248 let (future_1, future_2) = workspace.update(&mut app, |w, app| {
249 (
250 w.open_entry(entry, app).unwrap(),
251 w.open_entry(entry, app).unwrap(),
252 )
253 });
254
255 let handle_1 = future_1.await.unwrap();
256 let handle_2 = future_2.await.unwrap();
257 assert_eq!(handle_1.id(), handle_2.id());
258
259 // Open the same entry again now that it has loaded
260 let handle_3 = workspace
261 .update(&mut app, |w, app| w.open_entry(entry, app).unwrap())
262 .await
263 .unwrap();
264
265 assert_eq!(handle_3.id(), handle_1.id());
266 })
267 }
268}