workspace.rs

  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}