@@ -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(),
})
})
}
@@ -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)]
@@ -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| {