From 32dded551c7edf742ac9484f7ff84f4dce884a93 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 11:05:24 +0100 Subject: [PATCH] Checkpoint --- 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(-) diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index 313a54d7561e88a15b4c3ca9b445886a8439546f..ebc51942d09df4268aa9a5f50c28f01591841176 100644 --- a/crates/workspace2/src/item.rs +++ b/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, 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>; } @@ -518,8 +518,8 @@ impl ItemHandle for View { 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 { } impl WeakItemHandle for WeakView { - fn id(&self) -> usize { - self.id() + fn id(&self) -> EntityId { + self.entity_id() } fn upgrade(&self) -> Option> { @@ -695,7 +695,7 @@ impl FollowableItemHandle for View { self.read(cx).remote_id().or_else(|| { client.peer_id().map(|creator| ViewId { creator, - id: self.id() as u64, + id: self.id().as_u64(), }) }) } diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 7357b2e8c22642b7c2c8360044cfaf260f83b59b..af7056e00feb1b1359e78b20a55b5699a7d9f2b9 100644 --- a/crates/workspace2/src/pane.rs +++ b/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 }, 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>, - activation_history: Vec, + activation_history: Vec, zoomed: bool, active_item_index: usize, // last_focused_view_by_item: HashMap, @@ -176,7 +179,7 @@ pub struct Pane { toolbar: View, // tab_bar_context_menu: TabBarContextMenu, // tab_context_menu: ViewHandle, - // workspace: WeakView, + workspace: WeakView, project: Model, has_focus: bool, // can_drop: Rc, &WindowContext) -> bool>, @@ -197,7 +200,7 @@ struct NavHistoryState { backward_stack: VecDeque, forward_stack: VecDeque, closed_stack: VecDeque, - paths_by_item: HashMap)>, + paths_by_item: HashMap)>, pane: WeakView, next_timestamp: Arc, } @@ -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, ) -> Task> { - // 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>, - // 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>, + 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, - // mut save_intent: SaveIntent, - // should_close: impl 'static + Fn(usize) -> bool, - // ) -> Task> { - // // 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, + mut save_intent: SaveIntent, + should_close: impl 'static + Fn(EntityId) -> bool, + ) -> Task> { + // 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, - // pane: &WeakView, - // item_ix: usize, - // item: &dyn ItemHandle, - // save_intent: SaveIntent, - // cx: &mut AsyncAppContext, - // ) -> Result { - // 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, + pane: &WeakView, + item_ix: usize, + item: &dyn ItemHandle, + save_intent: SaveIntent, + cx: &mut AsyncWindowContext, + ) -> Result { + 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::(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)> { + pub fn path_for_item(&self, item_id: EntityId) -> Option<(ProjectPath, Option)> { self.0.lock().paths_by_item.get(&item_id).cloned() } } @@ -2214,14 +2216,14 @@ impl NavHistoryState { // } // } -// fn dirty_message_for(buffer_path: Option) -> 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) -> 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)] diff --git a/crates/workspace2/src/searchable.rs b/crates/workspace2/src/searchable.rs index ff132a8d80dfdaf5849c90d23363b49b4b41d0ab..3935423635cc153d45ae18a3a435d6a95adcd39e 100644 --- a/crates/workspace2/src/searchable.rs +++ b/crates/workspace2/src/searchable.rs @@ -200,7 +200,7 @@ impl SearchableItemHandle for View { cx: &mut WindowContext, ) -> Task>> { 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() diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index f813dd5c039d8284b93a0c9090555746099e4375..e268c7045becf936c586d37aab716e45f0c38850 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/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(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(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, pub fs: Arc, pub build_window_options: - fn(Option, Option, &mut MainThread) -> WindowOptions, + fn(Option, Option, &mut AppContext) -> WindowOptions, pub initialize_workspace: fn( WeakView, 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, right_dock: View, panes: Vec>, - panes_by_item: HashMap>, + panes_by_item: HashMap>, active_pane: View, last_active_center_pane: Option>, // last_active_view_id: Option, @@ -568,9 +569,6 @@ pub struct Workspace { pane_history_timestamp: Arc, } -trait AssertSend: Send {} -impl AssertSend for WindowHandle {} - // struct ActiveModal { // view: Box, // previously_focused_view_id: Option, @@ -795,7 +793,7 @@ impl Workspace { abs_paths: Vec, app_state: Arc, _requesting_window: Option>, - cx: &mut MainThread, + cx: &mut AppContext, ) -> Task< anyhow::Result<( WindowHandle, @@ -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 = 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::() - .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) -> Option { +fn window_bounds_env_override(cx: &AsyncAppContext) -> Option { let display_origin = cx .update(|cx| Some(cx.displays().first()?.bounds().origin)) .ok()??; @@ -3583,7 +3586,7 @@ fn open_items( _serialized_workspace: Option, project_paths_to_open: Vec<(PathBuf, Option)>, app_state: Arc, - cx: &mut MainThread>, + cx: &mut ViewContext, ) -> impl Future>>>>> { let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); @@ -4115,38 +4118,34 @@ impl ViewId { // pub struct WorkspaceCreated(pub WeakView); -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> { - cx.run_on_main(move |cx| { - for window in cx.windows() { - let Some(workspace) = window.downcast::() else { - continue; - }; + for window in cx.windows() { + let Some(workspace) = window.downcast::() 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 { @@ -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, - cx: &mut MainThread, + cx: &mut AppContext, init: impl FnOnce(&mut Workspace, &mut ViewContext) + '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| { diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index a10a6c1f70cc2d53adf93ebf33c5d3a431212733..2dff4f2eff7c36d4bca97571a6a654664b138189 100644 --- a/crates/zed2/src/main.rs +++ b/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 { } } -async fn restore_or_create_workspace( - app_state: &Arc, - mut cx: MainThread, -) { +async fn restore_or_create_workspace(app_state: &Arc, 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))? diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index d49bec8c56223c6c5f7093c635b599d2b0e5274d..e2d02fe853621b52cadcff931b81bd013573e7b1 100644 --- a/crates/zed2/src/zed2.rs +++ b/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, display_uuid: Option, - cx: &mut MainThread, + 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,