Checkpoint

Antonio Scandurra created

Change summary

crates/workspace2/src/item.rs       |  20 
crates/workspace2/src/pane.rs       | 498 +++++++++++++++---------------
crates/workspace2/src/searchable.rs |   2 
crates/workspace2/src/workspace2.rs | 117 +++---
crates/zed2/src/main.rs             |   9 
crates/zed2/src/zed2.rs             |  14 
6 files changed, 330 insertions(+), 330 deletions(-)

Detailed changes

crates/workspace2/src/item.rs 🔗

@@ -12,8 +12,8 @@ use client2::{
     Client,
 };
 use gpui2::{
-    AnyElement, AnyView, AppContext, EventEmitter, HighlightStyle, Model, Pixels, Point, Render,
-    SharedString, Task, View, ViewContext, WeakView, WindowContext, WindowHandle,
+    AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, HighlightStyle, Model, Pixels,
+    Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext, WindowHandle,
 };
 use parking_lot::Mutex;
 use project2::{Project, ProjectEntryId, ProjectPath};
@@ -21,7 +21,6 @@ use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use settings2::Settings;
 use smallvec::SmallVec;
-use theme2::ThemeVariant;
 use std::{
     any::{Any, TypeId},
     ops::Range,
@@ -32,6 +31,7 @@ use std::{
     },
     time::Duration,
 };
+use theme2::ThemeVariant;
 
 #[derive(Deserialize)]
 pub struct ItemSettings {
@@ -237,7 +237,7 @@ pub trait ItemHandle: 'static + Send {
     fn deactivated(&self, cx: &mut WindowContext);
     fn workspace_deactivated(&self, cx: &mut WindowContext);
     fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
-    fn id(&self) -> usize;
+    fn id(&self) -> EntityId;
     fn to_any(&self) -> AnyView;
     fn is_dirty(&self, cx: &AppContext) -> bool;
     fn has_conflict(&self, cx: &AppContext) -> bool;
@@ -266,7 +266,7 @@ pub trait ItemHandle: 'static + Send {
 }
 
 pub trait WeakItemHandle: Send + Sync {
-    fn id(&self) -> usize;
+    fn id(&self) -> EntityId;
     fn upgrade(&self) -> Option<Box<dyn ItemHandle>>;
 }
 
@@ -518,8 +518,8 @@ impl<T: Item> ItemHandle for View<T> {
         self.update(cx, |this, cx| this.navigate(data, cx))
     }
 
-    fn id(&self) -> usize {
-        self.id()
+    fn id(&self) -> EntityId {
+        self.entity_id()
     }
 
     fn to_any(&self) -> AnyView {
@@ -621,8 +621,8 @@ impl Clone for Box<dyn ItemHandle> {
 }
 
 impl<T: Item> WeakItemHandle for WeakView<T> {
-    fn id(&self) -> usize {
-        self.id()
+    fn id(&self) -> EntityId {
+        self.entity_id()
     }
 
     fn upgrade(&self) -> Option<Box<dyn ItemHandle>> {
@@ -695,7 +695,7 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
         self.read(cx).remote_id().or_else(|| {
             client.peer_id().map(|creator| ViewId {
                 creator,
-                id: self.id() as u64,
+                id: self.id().as_u64(),
             })
         })
     }

crates/workspace2/src/pane.rs 🔗

@@ -3,26 +3,29 @@
 use crate::{
     item::{Item, ItemHandle, WeakItemHandle},
     toolbar::Toolbar,
+    workspace_settings::{AutosaveSetting, WorkspaceSettings},
     SplitDirection, Workspace,
 };
 use anyhow::Result;
-use collections::{HashMap, VecDeque};
+use collections::{HashMap, HashSet, VecDeque};
 use gpui2::{
-    AppContext, EventEmitter, Model, Task, View, ViewContext, VisualContext, WeakView,
-    WindowContext,
+    AppContext, AsyncWindowContext, EntityId, EventEmitter, Model, PromptLevel, Task, View,
+    ViewContext, VisualContext, WeakView, WindowContext,
 };
 use parking_lot::Mutex;
 use project2::{Project, ProjectEntryId, ProjectPath};
 use serde::Deserialize;
+use settings2::Settings;
 use std::{
     any::Any,
     cmp, fmt, mem,
-    path::PathBuf,
+    path::{Path, PathBuf},
     sync::{
         atomic::{AtomicUsize, Ordering},
         Arc,
     },
 };
+use util::truncate_and_remove_front;
 
 #[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
 #[serde(rename_all = "camelCase")]
@@ -132,7 +135,7 @@ pub enum Event {
     AddItem { item: Box<dyn ItemHandle> },
     ActivateItem { local: bool },
     Remove,
-    RemoveItem { item_id: usize },
+    RemoveItem { item_id: EntityId },
     Split(SplitDirection),
     ChangeItemTitle,
     Focus,
@@ -167,7 +170,7 @@ impl fmt::Debug for Event {
 
 pub struct Pane {
     items: Vec<Box<dyn ItemHandle>>,
-    activation_history: Vec<usize>,
+    activation_history: Vec<EntityId>,
     zoomed: bool,
     active_item_index: usize,
     //     last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
@@ -176,7 +179,7 @@ pub struct Pane {
     toolbar: View<Toolbar>,
     //     tab_bar_context_menu: TabBarContextMenu,
     //     tab_context_menu: ViewHandle<ContextMenu>,
-    //     workspace: WeakView<Workspace>,
+    workspace: WeakView<Workspace>,
     project: Model<Project>,
     has_focus: bool,
     //     can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
@@ -197,7 +200,7 @@ struct NavHistoryState {
     backward_stack: VecDeque<NavigationEntry>,
     forward_stack: VecDeque<NavigationEntry>,
     closed_stack: VecDeque<NavigationEntry>,
-    paths_by_item: HashMap<usize, (ProjectPath, Option<PathBuf>)>,
+    paths_by_item: HashMap<EntityId, (ProjectPath, Option<PathBuf>)>,
     pane: WeakView<Pane>,
     next_timestamp: Arc<AtomicUsize>,
 }
@@ -346,7 +349,7 @@ impl Pane {
             //     handle: context_menu,
             // },
             // tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)),
-            // workspace,
+            workspace,
             project,
             has_focus: false,
             // can_drop: Rc::new(|_, _| true),
@@ -748,12 +751,11 @@ impl Pane {
 
     pub fn close_item_by_id(
         &mut self,
-        item_id_to_close: usize,
+        item_id_to_close: EntityId,
         save_intent: SaveIntent,
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<()>> {
-        // self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
-        todo!()
+        self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
     }
 
     // pub fn close_inactive_items(
@@ -857,142 +859,142 @@ impl Pane {
     //     )
     // }
 
-    //     pub(super) fn file_names_for_prompt(
-    //         items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
-    //         all_dirty_items: usize,
-    //         cx: &AppContext,
-    //     ) -> String {
-    //         /// Quantity of item paths displayed in prompt prior to cutoff..
-    //         const FILE_NAMES_CUTOFF_POINT: usize = 10;
-    //         let mut file_names: Vec<_> = items
-    //             .filter_map(|item| {
-    //                 item.project_path(cx).and_then(|project_path| {
-    //                     project_path
-    //                         .path
-    //                         .file_name()
-    //                         .and_then(|name| name.to_str().map(ToOwned::to_owned))
-    //                 })
-    //             })
-    //             .take(FILE_NAMES_CUTOFF_POINT)
-    //             .collect();
-    //         let should_display_followup_text =
-    //             all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items;
-    //         if should_display_followup_text {
-    //             let not_shown_files = all_dirty_items - file_names.len();
-    //             if not_shown_files == 1 {
-    //                 file_names.push(".. 1 file not shown".into());
-    //             } else {
-    //                 file_names.push(format!(".. {} files not shown", not_shown_files).into());
-    //             }
-    //         }
-    //         let file_names = file_names.join("\n");
-    //         format!(
-    //             "Do you want to save changes to the following {} files?\n{file_names}",
-    //             all_dirty_items
-    //         )
-    //     }
+    pub(super) fn file_names_for_prompt(
+        items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
+        all_dirty_items: usize,
+        cx: &AppContext,
+    ) -> String {
+        /// Quantity of item paths displayed in prompt prior to cutoff..
+        const FILE_NAMES_CUTOFF_POINT: usize = 10;
+        let mut file_names: Vec<_> = items
+            .filter_map(|item| {
+                item.project_path(cx).and_then(|project_path| {
+                    project_path
+                        .path
+                        .file_name()
+                        .and_then(|name| name.to_str().map(ToOwned::to_owned))
+                })
+            })
+            .take(FILE_NAMES_CUTOFF_POINT)
+            .collect();
+        let should_display_followup_text =
+            all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items;
+        if should_display_followup_text {
+            let not_shown_files = all_dirty_items - file_names.len();
+            if not_shown_files == 1 {
+                file_names.push(".. 1 file not shown".into());
+            } else {
+                file_names.push(format!(".. {} files not shown", not_shown_files).into());
+            }
+        }
+        let file_names = file_names.join("\n");
+        format!(
+            "Do you want to save changes to the following {} files?\n{file_names}",
+            all_dirty_items
+        )
+    }
 
-    //     pub fn close_items(
-    //         &mut self,
-    //         cx: &mut ViewContext<Pane>,
-    //         mut save_intent: SaveIntent,
-    //         should_close: impl 'static + Fn(usize) -> bool,
-    //     ) -> Task<Result<()>> {
-    //         // Find the items to close.
-    //         let mut items_to_close = Vec::new();
-    //         let mut dirty_items = Vec::new();
-    //         for item in &self.items {
-    //             if should_close(item.id()) {
-    //                 items_to_close.push(item.boxed_clone());
-    //                 if item.is_dirty(cx) {
-    //                     dirty_items.push(item.boxed_clone());
-    //                 }
-    //             }
-    //         }
+    pub fn close_items(
+        &mut self,
+        cx: &mut ViewContext<Pane>,
+        mut save_intent: SaveIntent,
+        should_close: impl 'static + Fn(EntityId) -> bool,
+    ) -> Task<Result<()>> {
+        // Find the items to close.
+        let mut items_to_close = Vec::new();
+        let mut dirty_items = Vec::new();
+        for item in &self.items {
+            if should_close(item.id()) {
+                items_to_close.push(item.boxed_clone());
+                if item.is_dirty(cx) {
+                    dirty_items.push(item.boxed_clone());
+                }
+            }
+        }
 
-    //         // If a buffer is open both in a singleton editor and in a multibuffer, make sure
-    //         // to focus the singleton buffer when prompting to save that buffer, as opposed
-    //         // to focusing the multibuffer, because this gives the user a more clear idea
-    //         // of what content they would be saving.
-    //         items_to_close.sort_by_key(|item| !item.is_singleton(cx));
-
-    //         let workspace = self.workspace.clone();
-    //         cx.spawn(|pane, mut cx| async move {
-    //             if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
-    //                 let mut answer = pane.update(&mut cx, |_, cx| {
-    //                     let prompt =
-    //                         Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
-    //                     cx.prompt(
-    //                         PromptLevel::Warning,
-    //                         &prompt,
-    //                         &["Save all", "Discard all", "Cancel"],
-    //                     )
-    //                 })?;
-    //                 match answer.next().await {
-    //                     Some(0) => save_intent = SaveIntent::SaveAll,
-    //                     Some(1) => save_intent = SaveIntent::Skip,
-    //                     _ => {}
-    //                 }
-    //             }
-    //             let mut saved_project_items_ids = HashSet::default();
-    //             for item in items_to_close.clone() {
-    //                 // Find the item's current index and its set of project item models. Avoid
-    //                 // storing these in advance, in case they have changed since this task
-    //                 // was started.
-    //                 let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| {
-    //                     (pane.index_for_item(&*item), item.project_item_model_ids(cx))
-    //                 })?;
-    //                 let item_ix = if let Some(ix) = item_ix {
-    //                     ix
-    //                 } else {
-    //                     continue;
-    //                 };
-
-    //                 // Check if this view has any project items that are not open anywhere else
-    //                 // in the workspace, AND that the user has not already been prompted to save.
-    //                 // If there are any such project entries, prompt the user to save this item.
-    //                 let project = workspace.read_with(&cx, |workspace, cx| {
-    //                     for item in workspace.items(cx) {
-    //                         if !items_to_close
-    //                             .iter()
-    //                             .any(|item_to_close| item_to_close.id() == item.id())
-    //                         {
-    //                             let other_project_item_ids = item.project_item_model_ids(cx);
-    //                             project_item_ids.retain(|id| !other_project_item_ids.contains(id));
-    //                         }
-    //                     }
-    //                     workspace.project().clone()
-    //                 })?;
-    //                 let should_save = project_item_ids
-    //                     .iter()
-    //                     .any(|id| saved_project_items_ids.insert(*id));
-
-    //                 if should_save
-    //                     && !Self::save_item(
-    //                         project.clone(),
-    //                         &pane,
-    //                         item_ix,
-    //                         &*item,
-    //                         save_intent,
-    //                         &mut cx,
-    //                     )
-    //                     .await?
-    //                 {
-    //                     break;
-    //                 }
+        // If a buffer is open both in a singleton editor and in a multibuffer, make sure
+        // to focus the singleton buffer when prompting to save that buffer, as opposed
+        // to focusing the multibuffer, because this gives the user a more clear idea
+        // of what content they would be saving.
+        items_to_close.sort_by_key(|item| !item.is_singleton(cx));
+
+        let workspace = self.workspace.clone();
+        cx.spawn(|pane, mut cx| async move {
+            if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
+                let answer = pane.update(&mut cx, |_, cx| {
+                    let prompt =
+                        Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
+                    cx.prompt(
+                        PromptLevel::Warning,
+                        &prompt,
+                        &["Save all", "Discard all", "Cancel"],
+                    )
+                })?;
+                match answer.await {
+                    Ok(0) => save_intent = SaveIntent::SaveAll,
+                    Ok(1) => save_intent = SaveIntent::Skip,
+                    _ => {}
+                }
+            }
+            let mut saved_project_items_ids = HashSet::default();
+            for item in items_to_close.clone() {
+                // Find the item's current index and its set of project item models. Avoid
+                // storing these in advance, in case they have changed since this task
+                // was started.
+                let (item_ix, mut project_item_ids) = pane.update(&mut cx, |pane, cx| {
+                    (pane.index_for_item(&*item), item.project_item_model_ids(cx))
+                })?;
+                let item_ix = if let Some(ix) = item_ix {
+                    ix
+                } else {
+                    continue;
+                };
+
+                // Check if this view has any project items that are not open anywhere else
+                // in the workspace, AND that the user has not already been prompted to save.
+                // If there are any such project entries, prompt the user to save this item.
+                let project = workspace.update(&mut cx, |workspace, cx| {
+                    for item in workspace.items(cx) {
+                        if !items_to_close
+                            .iter()
+                            .any(|item_to_close| item_to_close.id() == item.id())
+                        {
+                            let other_project_item_ids = item.project_item_model_ids(cx);
+                            project_item_ids.retain(|id| !other_project_item_ids.contains(id));
+                        }
+                    }
+                    workspace.project().clone()
+                })?;
+                let should_save = project_item_ids
+                    .iter()
+                    .any(|id| saved_project_items_ids.insert(*id));
+
+                if should_save
+                    && !Self::save_item(
+                        project.clone(),
+                        &pane,
+                        item_ix,
+                        &*item,
+                        save_intent,
+                        &mut cx,
+                    )
+                    .await?
+                {
+                    break;
+                }
 
-    //                 // Remove the item from the pane.
-    //                 pane.update(&mut cx, |pane, cx| {
-    //                     if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
-    //                         pane.remove_item(item_ix, false, cx);
-    //                     }
-    //                 })?;
-    //             }
+                // Remove the item from the pane.
+                pane.update(&mut cx, |pane, cx| {
+                    if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
+                        pane.remove_item(item_ix, false, cx);
+                    }
+                })?;
+            }
 
-    //             pane.update(&mut cx, |_, cx| cx.notify())?;
-    //             Ok(())
-    //         })
-    //     }
+            pane.update(&mut cx, |_, cx| cx.notify())?;
+            Ok(())
+        })
+    }
 
     pub fn remove_item(
         &mut self,
@@ -1062,106 +1064,106 @@ impl Pane {
         cx.notify();
     }
 
-    //     pub async fn save_item(
-    //         project: Model<Project>,
-    //         pane: &WeakView<Pane>,
-    //         item_ix: usize,
-    //         item: &dyn ItemHandle,
-    //         save_intent: SaveIntent,
-    //         cx: &mut AsyncAppContext,
-    //     ) -> Result<bool> {
-    //         const CONFLICT_MESSAGE: &str =
-    //             "This file has changed on disk since you started editing it. Do you want to overwrite it?";
-
-    //         if save_intent == SaveIntent::Skip {
-    //             return Ok(true);
-    //         }
+    pub async fn save_item(
+        project: Model<Project>,
+        pane: &WeakView<Pane>,
+        item_ix: usize,
+        item: &dyn ItemHandle,
+        save_intent: SaveIntent,
+        cx: &mut AsyncWindowContext,
+    ) -> Result<bool> {
+        const CONFLICT_MESSAGE: &str =
+                "This file has changed on disk since you started editing it. Do you want to overwrite it?";
 
-    //         let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.read(|cx| {
-    //             (
-    //                 item.has_conflict(cx),
-    //                 item.is_dirty(cx),
-    //                 item.can_save(cx),
-    //                 item.is_singleton(cx),
-    //             )
-    //         });
+        if save_intent == SaveIntent::Skip {
+            return Ok(true);
+        }
 
-    //         // when saving a single buffer, we ignore whether or not it's dirty.
-    //         if save_intent == SaveIntent::Save {
-    //             is_dirty = true;
-    //         }
+        let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.update(|_, cx| {
+            (
+                item.has_conflict(cx),
+                item.is_dirty(cx),
+                item.can_save(cx),
+                item.is_singleton(cx),
+            )
+        })?;
 
-    //         if save_intent == SaveIntent::SaveAs {
-    //             is_dirty = true;
-    //             has_conflict = false;
-    //             can_save = false;
-    //         }
+        // when saving a single buffer, we ignore whether or not it's dirty.
+        if save_intent == SaveIntent::Save {
+            is_dirty = true;
+        }
 
-    //         if save_intent == SaveIntent::Overwrite {
-    //             has_conflict = false;
-    //         }
+        if save_intent == SaveIntent::SaveAs {
+            is_dirty = true;
+            has_conflict = false;
+            can_save = false;
+        }
 
-    //         if has_conflict && can_save {
-    //             let mut answer = pane.update(cx, |pane, cx| {
-    //                 pane.activate_item(item_ix, true, true, cx);
-    //                 cx.prompt(
-    //                     PromptLevel::Warning,
-    //                     CONFLICT_MESSAGE,
-    //                     &["Overwrite", "Discard", "Cancel"],
-    //                 )
-    //             })?;
-    //             match answer.next().await {
-    //                 Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
-    //                 Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
-    //                 _ => return Ok(false),
-    //             }
-    //         } else if is_dirty && (can_save || can_save_as) {
-    //             if save_intent == SaveIntent::Close {
-    //                 let will_autosave = cx.read(|cx| {
-    //                     matches!(
-    //                         settings::get::<WorkspaceSettings>(cx).autosave,
-    //                         AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
-    //                     ) && Self::can_autosave_item(&*item, cx)
-    //                 });
-    //                 if !will_autosave {
-    //                     let mut answer = pane.update(cx, |pane, cx| {
-    //                         pane.activate_item(item_ix, true, true, cx);
-    //                         let prompt = dirty_message_for(item.project_path(cx));
-    //                         cx.prompt(
-    //                             PromptLevel::Warning,
-    //                             &prompt,
-    //                             &["Save", "Don't Save", "Cancel"],
-    //                         )
-    //                     })?;
-    //                     match answer.next().await {
-    //                         Some(0) => {}
-    //                         Some(1) => return Ok(true), // Don't save his file
-    //                         _ => return Ok(false),      // Cancel
-    //                     }
-    //                 }
-    //             }
+        if save_intent == SaveIntent::Overwrite {
+            has_conflict = false;
+        }
 
-    //             if can_save {
-    //                 pane.update(cx, |_, cx| item.save(project, cx))?.await?;
-    //             } else if can_save_as {
-    //                 let start_abs_path = project
-    //                     .read_with(cx, |project, cx| {
-    //                         let worktree = project.visible_worktrees(cx).next()?;
-    //                         Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
-    //                     })
-    //                     .unwrap_or_else(|| Path::new("").into());
-
-    //                 let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path));
-    //                 if let Some(abs_path) = abs_path.next().await.flatten() {
-    //                     pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
-    //                         .await?;
-    //                 } else {
-    //                     return Ok(false);
-    //                 }
-    //             }
-    //         }
-    //         Ok(true)
-    //     }
+        if has_conflict && can_save {
+            let answer = pane.update(cx, |pane, cx| {
+                pane.activate_item(item_ix, true, true, cx);
+                cx.prompt(
+                    PromptLevel::Warning,
+                    CONFLICT_MESSAGE,
+                    &["Overwrite", "Discard", "Cancel"],
+                )
+            })?;
+            match answer.await {
+                Ok(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
+                Ok(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
+                _ => return Ok(false),
+            }
+        } else if is_dirty && (can_save || can_save_as) {
+            if save_intent == SaveIntent::Close {
+                let will_autosave = cx.update(|_, cx| {
+                    matches!(
+                        WorkspaceSettings::get_global(cx).autosave,
+                        AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
+                    ) && Self::can_autosave_item(&*item, cx)
+                })?;
+                if !will_autosave {
+                    let answer = pane.update(cx, |pane, cx| {
+                        pane.activate_item(item_ix, true, true, cx);
+                        let prompt = dirty_message_for(item.project_path(cx));
+                        cx.prompt(
+                            PromptLevel::Warning,
+                            &prompt,
+                            &["Save", "Don't Save", "Cancel"],
+                        )
+                    })?;
+                    match answer.await {
+                        Ok(0) => {}
+                        Ok(1) => return Ok(true), // Don't save this file
+                        _ => return Ok(false),    // Cancel
+                    }
+                }
+            }
+
+            if can_save {
+                pane.update(cx, |_, cx| item.save(project, cx))?.await?;
+            } else if can_save_as {
+                let start_abs_path = project
+                    .update(cx, |project, cx| {
+                        let worktree = project.visible_worktrees(cx).next()?;
+                        Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
+                    })?
+                    .unwrap_or_else(|| Path::new("").into());
+
+                let abs_path = cx.update(|_, cx| cx.prompt_for_new_path(&start_abs_path))?;
+                if let Some(abs_path) = abs_path.await.ok().flatten() {
+                    pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
+                        .await?;
+                } else {
+                    return Ok(false);
+                }
+            }
+        }
+        Ok(true)
+    }
 
     fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
         let is_deleted = item.project_entry_ids(cx).is_empty();
@@ -2093,7 +2095,7 @@ impl NavHistory {
         state.did_update(cx);
     }
 
-    pub fn remove_item(&mut self, item_id: usize) {
+    pub fn remove_item(&mut self, item_id: EntityId) {
         let mut state = self.0.lock();
         state.paths_by_item.remove(&item_id);
         state
@@ -2107,7 +2109,7 @@ impl NavHistory {
             .retain(|entry| entry.item.id() != item_id);
     }
 
-    pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option<PathBuf>)> {
+    pub fn path_for_item(&self, item_id: EntityId) -> Option<(ProjectPath, Option<PathBuf>)> {
         self.0.lock().paths_by_item.get(&item_id).cloned()
     }
 }
@@ -2214,14 +2216,14 @@ impl NavHistoryState {
 //     }
 // }
 
-// fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
-//     let path = buffer_path
-//         .as_ref()
-//         .and_then(|p| p.path.to_str())
-//         .unwrap_or(&"This buffer");
-//     let path = truncate_and_remove_front(path, 80);
-//     format!("{path} contains unsaved edits. Do you want to save it?")
-// }
+fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
+    let path = buffer_path
+        .as_ref()
+        .and_then(|p| p.path.to_str())
+        .unwrap_or(&"This buffer");
+    let path = truncate_and_remove_front(path, 80);
+    format!("{path} contains unsaved edits. Do you want to save it?")
+}
 
 // todo!("uncomment tests")
 // #[cfg(test)]

crates/workspace2/src/searchable.rs 🔗

@@ -200,7 +200,7 @@ impl<T: SearchableItem> SearchableItemHandle for View<T> {
         cx: &mut WindowContext,
     ) -> Task<Vec<Box<dyn Any + Send>>> {
         let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
-        cx.spawn_on_main(|cx| async {
+        cx.spawn(|cx| async {
             let matches = matches.await;
             matches
                 .into_iter()

crates/workspace2/src/workspace2.rs 🔗

@@ -29,12 +29,12 @@ use futures::{
 };
 use gpui2::{
     div, point, size, AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds,
-    Context, Div, EventEmitter, GlobalPixels, MainThread, Model, ModelContext, Point, Render, Size,
+    Div, EntityId, EventEmitter, GlobalPixels, Model, ModelContext, Point, Render, Size,
     Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext,
     WindowHandle, WindowOptions,
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
-use language2::{LanguageRegistry, LocalFile};
+use language2::LanguageRegistry;
 use lazy_static::lazy_static;
 use node_runtime::NodeRuntime;
 use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
@@ -386,7 +386,7 @@ pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
         (
             |pane, workspace, id, state, cx| {
                 I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
-                    cx.executor()
+                    cx.foreground_executor()
                         .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
                 })
             },
@@ -412,7 +412,8 @@ pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
                 Arc::from(serialized_item_kind),
                 |project, workspace, workspace_id, item_id, cx| {
                     let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
-                    cx.spawn_on_main(|_| async { Ok(Box::new(task.await?) as Box<_>) })
+                    cx.foreground_executor()
+                        .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
                 },
             );
         }
@@ -426,7 +427,7 @@ pub struct AppState {
     pub workspace_store: Model<WorkspaceStore>,
     pub fs: Arc<dyn fs2::Fs>,
     pub build_window_options:
-        fn(Option<WindowBounds>, Option<Uuid>, &mut MainThread<AppContext>) -> WindowOptions,
+        fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
     pub initialize_workspace: fn(
         WeakView<Workspace>,
         bool,
@@ -511,7 +512,7 @@ impl DelayedDebouncedEditAction {
 
         let previous_task = self.task.take();
         self.task = Some(cx.spawn(move |workspace, mut cx| async move {
-            let mut timer = cx.executor().timer(delay).fuse();
+            let mut timer = cx.background_executor().timer(delay).fuse();
             if let Some(previous_task) = previous_task {
                 previous_task.await;
             }
@@ -546,7 +547,7 @@ pub struct Workspace {
     bottom_dock: View<Dock>,
     right_dock: View<Dock>,
     panes: Vec<View<Pane>>,
-    panes_by_item: HashMap<usize, WeakView<Pane>>,
+    panes_by_item: HashMap<EntityId, WeakView<Pane>>,
     active_pane: View<Pane>,
     last_active_center_pane: Option<WeakView<Pane>>,
     //     last_active_view_id: Option<proto::ViewId>,
@@ -568,9 +569,6 @@ pub struct Workspace {
     pane_history_timestamp: Arc<AtomicUsize>,
 }
 
-trait AssertSend: Send {}
-impl AssertSend for WindowHandle<Workspace> {}
-
 // struct ActiveModal {
 //     view: Box<dyn ModalHandle>,
 //     previously_focused_view_id: Option<usize>,
@@ -795,7 +793,7 @@ impl Workspace {
         abs_paths: Vec<PathBuf>,
         app_state: Arc<AppState>,
         _requesting_window: Option<WindowHandle<Workspace>>,
-        cx: &mut MainThread<AppContext>,
+        cx: &mut AppContext,
     ) -> Task<
         anyhow::Result<(
             WindowHandle<Workspace>,
@@ -811,7 +809,7 @@ impl Workspace {
             cx,
         );
 
-        cx.spawn_on_main(|mut cx| async move {
+        cx.spawn(|mut cx| async move {
             let serialized_workspace: Option<SerializedWorkspace> = None; //persistence::DB.workspace_for_roots(&abs_paths.as_slice());
 
             let paths_to_open = Arc::new(abs_paths);
@@ -857,21 +855,25 @@ impl Workspace {
                     serialized_workspace
                         .as_ref()
                         .and_then(|serialized_workspace| {
-                            let display = serialized_workspace.display?;
+                            let serialized_display = serialized_workspace.display?;
                             let mut bounds = serialized_workspace.bounds?;
 
                             // Stored bounds are relative to the containing display.
                             // So convert back to global coordinates if that screen still exists
                             if let WindowBounds::Fixed(mut window_bounds) = bounds {
                                 let screen =
-                                    cx.update(|cx| cx.display_for_uuid(display)).ok()??;
+                                    cx.update(|cx|
+                                        cx.displays()
+                                            .into_iter()
+                                            .find(|display| display.uuid().ok() == Some(serialized_display))
+                                    ).ok()??;
                                 let screen_bounds = screen.bounds();
                                 window_bounds.origin.x += screen_bounds.origin.x;
                                 window_bounds.origin.y += screen_bounds.origin.y;
                                 bounds = WindowBounds::Fixed(window_bounds);
                             }
 
-                            Some((bounds, display))
+                            Some((bounds, serialized_display))
                         })
                         .unzip()
                 };
@@ -885,11 +887,12 @@ impl Workspace {
                     let workspace_id = workspace_id.clone();
                     let project_handle = project_handle.clone();
                     move |cx| {
-                    cx.build_view(|cx| {
-                        Workspace::new(workspace_id, project_handle, app_state, cx)
-                    })
-                }})?
-                 };
+                        cx.build_view(|cx| {
+                            Workspace::new(workspace_id, project_handle, app_state, cx)
+                        })
+                    }
+                })?
+            };
 
             // todo!() Ask how to do this
             let weak_view = window.update(&mut cx, |_, cx| cx.view().downgrade())?;
@@ -2123,7 +2126,7 @@ impl Workspace {
             let (project_entry_id, project_item) = project_item.await?;
             let build_item = cx.update(|_, cx| {
                 cx.default_global::<ProjectItemBuilders>()
-                    .get(&project_item.type_id())
+                    .get(&project_item.entity_type())
                     .ok_or_else(|| anyhow!("no item builder for project item"))
                     .cloned()
             })??;
@@ -3259,7 +3262,7 @@ impl Workspace {
                         .filter_map(|item_handle| {
                             Some(SerializedItem {
                                 kind: Arc::from(item_handle.serialized_item_kind()?),
-                                item_id: item_handle.id(),
+                                item_id: item_handle.id().as_u64() as usize,
                                 active: Some(item_handle.id()) == active_item_id,
                             })
                         })
@@ -3565,7 +3568,7 @@ impl Workspace {
     // }
 }
 
-fn window_bounds_env_override(cx: &MainThread<AsyncAppContext>) -> Option<WindowBounds> {
+fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
     let display_origin = cx
         .update(|cx| Some(cx.displays().first()?.bounds().origin))
         .ok()??;
@@ -3583,7 +3586,7 @@ fn open_items(
     _serialized_workspace: Option<SerializedWorkspace>,
     project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
     app_state: Arc<AppState>,
-    cx: &mut MainThread<ViewContext<'_, Workspace>>,
+    cx: &mut ViewContext<Workspace>,
 ) -> impl Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
     let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
 
@@ -4115,38 +4118,34 @@ impl ViewId {
 
 // pub struct WorkspaceCreated(pub WeakView<Workspace>);
 
-pub async fn activate_workspace_for_project(
-    cx: &mut AsyncAppContext,
+pub fn activate_workspace_for_project(
+    cx: &mut AppContext,
     predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
 ) -> Option<WindowHandle<Workspace>> {
-    cx.run_on_main(move |cx| {
-        for window in cx.windows() {
-            let Some(workspace) = window.downcast::<Workspace>() else {
-                continue;
-            };
+    for window in cx.windows() {
+        let Some(workspace) = window.downcast::<Workspace>() else {
+            continue;
+        };
 
-            let predicate = workspace
-                .update(cx, |workspace, cx| {
-                    let project = workspace.project.read(cx);
-                    if predicate(project, cx) {
-                        cx.activate_window();
-                        true
-                    } else {
-                        false
-                    }
-                })
-                .log_err()
-                .unwrap_or(false);
+        let predicate = workspace
+            .update(cx, |workspace, cx| {
+                let project = workspace.project.read(cx);
+                if predicate(project, cx) {
+                    cx.activate_window();
+                    true
+                } else {
+                    false
+                }
+            })
+            .log_err()
+            .unwrap_or(false);
 
-            if predicate {
-                return Some(workspace);
-            }
+        if predicate {
+            return Some(workspace);
         }
+    }
 
-        None
-    })
-    .ok()?
-    .await
+    None
 }
 
 pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
@@ -4349,14 +4348,12 @@ pub fn open_paths(
 > {
     let app_state = app_state.clone();
     let abs_paths = abs_paths.to_vec();
-    cx.spawn_on_main(move |mut cx| async move {
-        // Open paths in existing workspace if possible
-        let existing = activate_workspace_for_project(&mut cx, {
-            let abs_paths = abs_paths.clone();
-            move |project, cx| project.contains_paths(&abs_paths, cx)
-        })
-        .await;
-
+    // Open paths in existing workspace if possible
+    let existing = activate_workspace_for_project(cx, {
+        let abs_paths = abs_paths.clone();
+        move |project, cx| project.contains_paths(&abs_paths, cx)
+    });
+    cx.spawn(move |mut cx| async move {
         if let Some(existing) = existing {
             // // Ok((
             //     existing.clone(),
@@ -4377,11 +4374,11 @@ pub fn open_paths(
 
 pub fn open_new(
     app_state: &Arc<AppState>,
-    cx: &mut MainThread<AppContext>,
+    cx: &mut AppContext,
     init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
 ) -> Task<()> {
     let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
-    cx.spawn_on_main(|mut cx| async move {
+    cx.spawn(|mut cx| async move {
         if let Some((workspace, opened_paths)) = task.await.log_err() {
             workspace
                 .update(&mut cx, |workspace, cx| {

crates/zed2/src/main.rs 🔗

@@ -12,7 +12,7 @@ use client2::UserStore;
 use db2::kvp::KEY_VALUE_STORE;
 use fs2::RealFs;
 use futures::{channel::mpsc, SinkExt, StreamExt};
-use gpui2::{Action, App, AppContext, AsyncAppContext, Context, MainThread, SemanticVersion, Task};
+use gpui2::{Action, App, AppContext, AsyncAppContext, Context, SemanticVersion, Task};
 use isahc::{prelude::Configurable, Request};
 use language2::LanguageRegistry;
 use log::LevelFilter;
@@ -249,7 +249,7 @@ fn main() {
                 // .detach_and_log_err(cx)
             }
             Ok(None) | Err(_) => cx
-                .spawn_on_main({
+                .spawn({
                     let app_state = app_state.clone();
                     |cx| async move { restore_or_create_workspace(&app_state, cx).await }
                 })
@@ -320,10 +320,7 @@ async fn installation_id() -> Result<String> {
     }
 }
 
-async fn restore_or_create_workspace(
-    app_state: &Arc<AppState>,
-    mut cx: MainThread<AsyncAppContext>,
-) {
+async fn restore_or_create_workspace(app_state: &Arc<AppState>, mut cx: AsyncAppContext) {
     async_maybe!({
         if let Some(location) = workspace2::last_opened_workspace_paths().await {
             cx.update(|cx| workspace2::open_paths(location.paths().as_ref(), app_state, None, cx))?

crates/zed2/src/zed2.rs 🔗

@@ -6,8 +6,8 @@ mod open_listener;
 pub use assets::*;
 use collections::HashMap;
 use gpui2::{
-    point, px, AppContext, AsyncAppContext, AsyncWindowContext, MainThread, Point, Task,
-    TitlebarOptions, WeakView, WindowBounds, WindowHandle, WindowKind, WindowOptions,
+    point, px, AppContext, AsyncAppContext, AsyncWindowContext, Point, Task, TitlebarOptions,
+    WeakView, WindowBounds, WindowKind, WindowOptions,
 };
 pub use only_instance::*;
 pub use open_listener::*;
@@ -160,7 +160,7 @@ pub async fn handle_cli_connection(
                             }
 
                             if wait {
-                                let executor = cx.executor().clone();
+                                let executor = cx.background_executor().clone();
                                 let wait = async move {
                                     if paths.is_empty() {
                                         let (done_tx, done_rx) = oneshot::channel();
@@ -219,10 +219,14 @@ pub async fn handle_cli_connection(
 pub fn build_window_options(
     bounds: Option<WindowBounds>,
     display_uuid: Option<Uuid>,
-    cx: &mut MainThread<AppContext>,
+    cx: &mut AppContext,
 ) -> WindowOptions {
     let bounds = bounds.unwrap_or(WindowBounds::Maximized);
-    let display = display_uuid.and_then(|uuid| cx.display_for_uuid(uuid));
+    let display = display_uuid.and_then(|uuid| {
+        cx.displays()
+            .into_iter()
+            .find(|display| display.uuid().ok() == Some(uuid))
+    });
 
     WindowOptions {
         bounds,