Detailed changes
@@ -11,7 +11,7 @@ use settings::Settings;
use smallvec::SmallVec;
use std::{cmp::Reverse, fmt::Write, sync::Arc};
use util::ResultExt;
-use workspace::{ItemHandle, StatusItemView, Workspace};
+use workspace::{item::ItemHandle, StatusItemView, Workspace};
actions!(lsp_status, [ShowErrorMessage]);
@@ -4,7 +4,10 @@ use gpui::{
use itertools::Itertools;
use search::ProjectSearchView;
use settings::Settings;
-use workspace::{ItemEvent, ItemHandle, ToolbarItemLocation, ToolbarItemView};
+use workspace::{
+ item::{ItemEvent, ItemHandle},
+ ToolbarItemLocation, ToolbarItemView,
+};
pub enum Event {
UpdateLocation,
@@ -52,7 +52,7 @@ use std::{
use theme::ThemeRegistry;
use unindent::Unindent as _;
use util::post_inc;
-use workspace::{shared_screen::SharedScreen, Item, SplitDirection, ToggleFollow, Workspace};
+use workspace::{shared_screen::SharedScreen, item::Item, SplitDirection, ToggleFollow, Workspace};
#[ctor::ctor]
fn init_logger() {
@@ -29,7 +29,10 @@ use std::{
sync::Arc,
};
use util::TryFutureExt;
-use workspace::{ItemHandle as _, ItemNavHistory, Workspace};
+use workspace::{
+ item::{Item, ItemEvent, ItemHandle},
+ ItemNavHistory, Workspace,
+};
actions!(diagnostics, [Deploy]);
@@ -503,7 +506,7 @@ impl ProjectDiagnosticsEditor {
}
}
-impl workspace::Item for ProjectDiagnosticsEditor {
+impl Item for ProjectDiagnosticsEditor {
fn tab_content(
&self,
_detail: Option<usize>,
@@ -571,7 +574,7 @@ impl workspace::Item for ProjectDiagnosticsEditor {
unreachable!()
}
- fn to_item_events(event: &Self::Event) -> Vec<workspace::ItemEvent> {
+ fn to_item_events(event: &Self::Event) -> Vec<ItemEvent> {
Editor::to_item_events(event)
}
@@ -7,7 +7,7 @@ use gpui::{
use language::Diagnostic;
use project::Project;
use settings::Settings;
-use workspace::StatusItemView;
+use workspace::{item::ItemHandle, StatusItemView};
pub struct DiagnosticIndicator {
summary: project::DiagnosticSummary,
@@ -219,7 +219,7 @@ impl View for DiagnosticIndicator {
impl StatusItemView for DiagnosticIndicator {
fn set_active_pane_item(
&mut self,
- active_pane_item: Option<&dyn workspace::ItemHandle>,
+ active_pane_item: Option<&dyn ItemHandle>,
cx: &mut ViewContext<Self>,
) {
if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
@@ -22,7 +22,10 @@ use util::{
assert_set_eq,
test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
};
-use workspace::{FollowableItem, ItemHandle, NavigationEntry, Pane};
+use workspace::{
+ item::{FollowableItem, ItemHandle},
+ NavigationEntry, Pane,
+};
#[gpui::test]
fn test_edit_events(cx: &mut MutableAppContext) {
@@ -475,7 +478,7 @@ fn test_clone(cx: &mut gpui::MutableAppContext) {
fn test_navigation_history(cx: &mut gpui::MutableAppContext) {
cx.set_global(Settings::test(cx));
cx.set_global(DragAndDrop::<Workspace>::default());
- use workspace::Item;
+ use workspace::item::Item;
let (_, pane) = cx.add_window(Default::default(), |cx| Pane::new(None, cx));
let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
@@ -24,9 +24,9 @@ use std::{
use text::Selection;
use util::TryFutureExt;
use workspace::{
+ item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
- FollowableItem, Item, ItemEvent, ItemHandle, ItemNavHistory, ProjectItem, StatusItemView,
- ToolbarItemLocation,
+ ItemNavHistory, StatusItemView, ToolbarItemLocation,
};
pub const MAX_TAB_TITLE_LEN: usize = 24;
@@ -490,7 +490,7 @@ impl Item for Editor {
Task::ready(Ok(()))
}
- fn to_item_events(event: &Self::Event) -> Vec<workspace::ItemEvent> {
+ fn to_item_events(event: &Self::Event) -> Vec<ItemEvent> {
let mut result = Vec::new();
match event {
Event::Closed => result.push(ItemEvent::CloseItem),
@@ -14,8 +14,9 @@ use serde::Deserialize;
use settings::Settings;
use std::{any::Any, sync::Arc};
use workspace::{
+ item::ItemHandle,
searchable::{Direction, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle},
- ItemHandle, Pane, ToolbarItemLocation, ToolbarItemView,
+ Pane, ToolbarItemLocation, ToolbarItemView,
};
#[derive(Clone, Deserialize, PartialEq)]
@@ -24,9 +24,9 @@ use std::{
};
use util::ResultExt as _;
use workspace::{
+ item::{Item, ItemEvent, ItemHandle},
searchable::{Direction, SearchableItem, SearchableItemHandle},
- Item, ItemEvent, ItemHandle, ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView,
- Workspace,
+ ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace,
};
actions!(project_search, [SearchInNew, ToggleFocus]);
@@ -893,7 +893,7 @@ impl View for ProjectSearchBar {
impl ToolbarItemView for ProjectSearchBar {
fn set_active_pane_item(
&mut self,
- active_pane_item: Option<&dyn workspace::ItemHandle>,
+ active_pane_item: Option<&dyn ItemHandle>,
cx: &mut ViewContext<Self>,
) -> ToolbarItemLocation {
cx.notify();
@@ -9,7 +9,10 @@ use gpui::{
};
use util::truncate_and_trailoff;
use workspace::searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle};
-use workspace::{Item, ItemEvent, ToolbarItemLocation, Workspace};
+use workspace::{
+ item::{Item, ItemEvent},
+ ToolbarItemLocation, Workspace,
+};
use project::{LocalWorktree, Project, ProjectPath};
use settings::{AlternateScroll, Settings, WorkingDirectory};
@@ -12,7 +12,10 @@ use project::{Project, ProjectEntryId, ProjectPath};
use settings::Settings;
use smallvec::SmallVec;
use theme::{ColorScheme, Layer, Style, StyleSet};
-use workspace::{Item, Workspace};
+use workspace::{
+ item::{Item, ItemEvent},
+ Workspace,
+};
actions!(theme, [DeployThemeTestbench]);
@@ -351,7 +354,7 @@ impl Item for ThemeTestbench {
gpui::Task::ready(Ok(()))
}
- fn to_item_events(_: &Self::Event) -> Vec<workspace::ItemEvent> {
+ fn to_item_events(_: &Self::Event) -> Vec<ItemEvent> {
Vec::new()
}
}
@@ -98,14 +98,14 @@ pub fn icon_for_dock_anchor(anchor: DockAnchor) -> &'static str {
}
impl DockPosition {
- fn is_visible(&self) -> bool {
+ pub fn is_visible(&self) -> bool {
match self {
DockPosition::Shown(_) => true,
DockPosition::Hidden(_) => false,
}
}
- fn anchor(&self) -> DockAnchor {
+ pub fn anchor(&self) -> DockAnchor {
match self {
DockPosition::Shown(anchor) | DockPosition::Hidden(anchor) => *anchor,
}
@@ -137,9 +137,15 @@ pub struct Dock {
}
impl Dock {
- pub fn new(default_item_factory: DefaultItemFactory, cx: &mut ViewContext<Workspace>) -> Self {
- let anchor = cx.global::<Settings>().default_dock_anchor;
- let pane = cx.add_view(|cx| Pane::new(Some(anchor), cx));
+ pub fn new(
+ default_item_factory: DefaultItemFactory,
+ position: Option<DockPosition>,
+ cx: &mut ViewContext<Workspace>,
+ ) -> Self {
+ let position = position
+ .unwrap_or_else(|| DockPosition::Hidden(cx.global::<Settings>().default_dock_anchor));
+
+ let pane = cx.add_view(|cx| Pane::new(Some(position.anchor()), cx));
pane.update(cx, |pane, cx| {
pane.set_active(false, cx);
});
@@ -152,7 +158,7 @@ impl Dock {
Self {
pane,
panel_sizes: Default::default(),
- position: DockPosition::Hidden(anchor),
+ position,
default_item_factory,
}
}
@@ -454,7 +460,7 @@ mod tests {
use settings::Settings;
use super::*;
- use crate::{sidebar::Sidebar, tests::TestItem, ItemHandle, Workspace};
+ use crate::{item::test::TestItem, sidebar::Sidebar, ItemHandle, Workspace};
pub fn default_item_factory(
_workspace: &mut Workspace,
@@ -0,0 +1,876 @@
+use std::{
+ any::{Any, TypeId},
+ borrow::Cow,
+ cell::RefCell,
+ fmt,
+ path::PathBuf,
+ rc::Rc,
+ sync::atomic::{AtomicBool, Ordering},
+ time::Duration,
+};
+
+use anyhow::Result;
+use client::proto;
+use gpui::{
+ AnyViewHandle, AppContext, ElementBox, ModelHandle, MutableAppContext, Task, View, ViewContext,
+ ViewHandle, WeakViewHandle,
+};
+use project::{Project, ProjectEntryId, ProjectPath};
+use settings::{Autosave, Settings};
+use smallvec::SmallVec;
+use theme::Theme;
+use util::ResultExt;
+
+use crate::{
+ pane,
+ persistence::model::{ItemId, WorkspaceId},
+ searchable::SearchableItemHandle,
+ DelayedDebouncedEditAction, FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation,
+ Workspace,
+};
+
+#[derive(Eq, PartialEq, Hash)]
+pub enum ItemEvent {
+ CloseItem,
+ UpdateTab,
+ UpdateBreadcrumbs,
+ Edit,
+}
+
+pub trait Item: View {
+ fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
+ fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
+ fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
+ false
+ }
+ fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
+ None
+ }
+ fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
+ -> ElementBox;
+ fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
+ fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
+ fn is_singleton(&self, cx: &AppContext) -> bool;
+ fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>);
+ fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
+ where
+ Self: Sized,
+ {
+ None
+ }
+ fn is_dirty(&self, _: &AppContext) -> bool {
+ false
+ }
+ fn has_conflict(&self, _: &AppContext) -> bool {
+ false
+ }
+ fn can_save(&self, cx: &AppContext) -> bool;
+ fn save(
+ &mut self,
+ project: ModelHandle<Project>,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Result<()>>;
+ fn save_as(
+ &mut self,
+ project: ModelHandle<Project>,
+ abs_path: PathBuf,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Result<()>>;
+ fn reload(
+ &mut self,
+ project: ModelHandle<Project>,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Result<()>>;
+ fn git_diff_recalc(
+ &mut self,
+ _project: ModelHandle<Project>,
+ _cx: &mut ViewContext<Self>,
+ ) -> Task<Result<()>> {
+ Task::ready(Ok(()))
+ }
+ fn to_item_events(event: &Self::Event) -> Vec<ItemEvent>;
+ fn should_close_item_on_event(_: &Self::Event) -> bool {
+ false
+ }
+ fn should_update_tab_on_event(_: &Self::Event) -> bool {
+ false
+ }
+ fn is_edit_event(_: &Self::Event) -> bool {
+ false
+ }
+ fn act_as_type(
+ &self,
+ type_id: TypeId,
+ self_handle: &ViewHandle<Self>,
+ _: &AppContext,
+ ) -> Option<AnyViewHandle> {
+ if TypeId::of::<Self>() == type_id {
+ Some(self_handle.into())
+ } else {
+ None
+ }
+ }
+ fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
+ None
+ }
+
+ fn breadcrumb_location(&self) -> ToolbarItemLocation {
+ ToolbarItemLocation::Hidden
+ }
+ fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<ElementBox>> {
+ None
+ }
+ fn serialized_item_kind() -> Option<&'static str>;
+ fn deserialize(
+ workspace_id: WorkspaceId,
+ item_id: ItemId,
+ cx: &mut ViewContext<Self>,
+ ) -> Result<Self>;
+}
+
+pub trait ItemHandle: 'static + fmt::Debug {
+ fn subscribe_to_item_events(
+ &self,
+ cx: &mut MutableAppContext,
+ handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
+ ) -> gpui::Subscription;
+ fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
+ fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
+ -> ElementBox;
+ fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
+ fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
+ fn is_singleton(&self, cx: &AppContext) -> bool;
+ fn boxed_clone(&self) -> Box<dyn ItemHandle>;
+ fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>>;
+ fn added_to_pane(
+ &self,
+ workspace: &mut Workspace,
+ pane: ViewHandle<Pane>,
+ cx: &mut ViewContext<Workspace>,
+ );
+ fn deactivated(&self, cx: &mut MutableAppContext);
+ fn workspace_deactivated(&self, cx: &mut MutableAppContext);
+ fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool;
+ fn id(&self) -> usize;
+ fn window_id(&self) -> usize;
+ fn to_any(&self) -> AnyViewHandle;
+ fn is_dirty(&self, cx: &AppContext) -> bool;
+ fn has_conflict(&self, cx: &AppContext) -> bool;
+ fn can_save(&self, cx: &AppContext) -> bool;
+ fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>>;
+ fn save_as(
+ &self,
+ project: ModelHandle<Project>,
+ abs_path: PathBuf,
+ cx: &mut MutableAppContext,
+ ) -> Task<Result<()>>;
+ fn reload(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext)
+ -> Task<Result<()>>;
+ fn git_diff_recalc(
+ &self,
+ project: ModelHandle<Project>,
+ cx: &mut MutableAppContext,
+ ) -> Task<Result<()>>;
+ fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle>;
+ fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
+ fn on_release(
+ &self,
+ cx: &mut MutableAppContext,
+ callback: Box<dyn FnOnce(&mut MutableAppContext)>,
+ ) -> gpui::Subscription;
+ fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
+ fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
+ fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>>;
+}
+
+pub trait WeakItemHandle {
+ fn id(&self) -> usize;
+ fn window_id(&self) -> usize;
+ fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
+}
+
+impl dyn ItemHandle {
+ pub fn downcast<T: View>(&self) -> Option<ViewHandle<T>> {
+ self.to_any().downcast()
+ }
+
+ pub fn act_as<T: View>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
+ self.act_as_type(TypeId::of::<T>(), cx)
+ .and_then(|t| t.downcast())
+ }
+}
+
+impl<T: Item> ItemHandle for ViewHandle<T> {
+ fn subscribe_to_item_events(
+ &self,
+ cx: &mut MutableAppContext,
+ handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
+ ) -> gpui::Subscription {
+ cx.subscribe(self, move |_, event, cx| {
+ for item_event in T::to_item_events(event) {
+ handler(item_event, cx)
+ }
+ })
+ }
+
+ fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
+ self.read(cx).tab_description(detail, cx)
+ }
+
+ fn tab_content(
+ &self,
+ detail: Option<usize>,
+ style: &theme::Tab,
+ cx: &AppContext,
+ ) -> ElementBox {
+ self.read(cx).tab_content(detail, style, cx)
+ }
+
+ fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
+ self.read(cx).project_path(cx)
+ }
+
+ fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
+ self.read(cx).project_entry_ids(cx)
+ }
+
+ fn is_singleton(&self, cx: &AppContext) -> bool {
+ self.read(cx).is_singleton(cx)
+ }
+
+ fn boxed_clone(&self) -> Box<dyn ItemHandle> {
+ Box::new(self.clone())
+ }
+
+ fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>> {
+ self.update(cx, |item, cx| {
+ cx.add_option_view(|cx| item.clone_on_split(cx))
+ })
+ .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
+ }
+
+ fn added_to_pane(
+ &self,
+ workspace: &mut Workspace,
+ pane: ViewHandle<Pane>,
+ cx: &mut ViewContext<Workspace>,
+ ) {
+ let history = pane.read(cx).nav_history_for_item(self);
+ self.update(cx, |this, cx| this.set_nav_history(history, cx));
+
+ if let Some(followed_item) = self.to_followable_item_handle(cx) {
+ if let Some(message) = followed_item.to_state_proto(cx) {
+ workspace.update_followers(
+ proto::update_followers::Variant::CreateView(proto::View {
+ id: followed_item.id() as u64,
+ variant: Some(message),
+ leader_id: workspace.leader_for_pane(&pane).map(|id| id.0),
+ }),
+ cx,
+ );
+ }
+ }
+
+ if workspace
+ .panes_by_item
+ .insert(self.id(), pane.downgrade())
+ .is_none()
+ {
+ let mut pending_autosave = DelayedDebouncedEditAction::new();
+ let mut pending_git_update = DelayedDebouncedEditAction::new();
+ let pending_update = Rc::new(RefCell::new(None));
+ let pending_update_scheduled = Rc::new(AtomicBool::new(false));
+
+ let mut event_subscription =
+ Some(cx.subscribe(self, move |workspace, item, event, cx| {
+ let pane = if let Some(pane) = workspace
+ .panes_by_item
+ .get(&item.id())
+ .and_then(|pane| pane.upgrade(cx))
+ {
+ pane
+ } else {
+ log::error!("unexpected item event after pane was dropped");
+ return;
+ };
+
+ if let Some(item) = item.to_followable_item_handle(cx) {
+ let leader_id = workspace.leader_for_pane(&pane);
+
+ if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
+ workspace.unfollow(&pane, cx);
+ }
+
+ if item.add_event_to_update_proto(
+ event,
+ &mut *pending_update.borrow_mut(),
+ cx,
+ ) && !pending_update_scheduled.load(Ordering::SeqCst)
+ {
+ pending_update_scheduled.store(true, Ordering::SeqCst);
+ cx.after_window_update({
+ let pending_update = pending_update.clone();
+ let pending_update_scheduled = pending_update_scheduled.clone();
+ move |this, cx| {
+ pending_update_scheduled.store(false, Ordering::SeqCst);
+ this.update_followers(
+ proto::update_followers::Variant::UpdateView(
+ proto::UpdateView {
+ id: item.id() as u64,
+ variant: pending_update.borrow_mut().take(),
+ leader_id: leader_id.map(|id| id.0),
+ },
+ ),
+ cx,
+ );
+ }
+ });
+ }
+ }
+
+ for item_event in T::to_item_events(event).into_iter() {
+ match item_event {
+ ItemEvent::CloseItem => {
+ Pane::close_item(workspace, pane, item.id(), cx)
+ .detach_and_log_err(cx);
+ return;
+ }
+
+ ItemEvent::UpdateTab => {
+ pane.update(cx, |_, cx| {
+ cx.emit(pane::Event::ChangeItemTitle);
+ cx.notify();
+ });
+ }
+
+ ItemEvent::Edit => {
+ if let Autosave::AfterDelay { milliseconds } =
+ cx.global::<Settings>().autosave
+ {
+ let delay = Duration::from_millis(milliseconds);
+ let item = item.clone();
+ pending_autosave.fire_new(
+ delay,
+ workspace,
+ cx,
+ |project, mut cx| async move {
+ cx.update(|cx| Pane::autosave_item(&item, project, cx))
+ .await
+ .log_err();
+ },
+ );
+ }
+
+ let settings = cx.global::<Settings>();
+ let debounce_delay = settings.git_overrides.gutter_debounce;
+
+ let item = item.clone();
+
+ if let Some(delay) = debounce_delay {
+ const MIN_GIT_DELAY: u64 = 50;
+
+ let delay = delay.max(MIN_GIT_DELAY);
+ let duration = Duration::from_millis(delay);
+
+ pending_git_update.fire_new(
+ duration,
+ workspace,
+ cx,
+ |project, mut cx| async move {
+ cx.update(|cx| item.git_diff_recalc(project, cx))
+ .await
+ .log_err();
+ },
+ );
+ } else {
+ let project = workspace.project().downgrade();
+ cx.spawn_weak(|_, mut cx| async move {
+ if let Some(project) = project.upgrade(&cx) {
+ cx.update(|cx| item.git_diff_recalc(project, cx))
+ .await
+ .log_err();
+ }
+ })
+ .detach();
+ }
+ }
+
+ _ => {}
+ }
+ }
+ }));
+
+ cx.observe_focus(self, move |workspace, item, focused, cx| {
+ if !focused && cx.global::<Settings>().autosave == Autosave::OnFocusChange {
+ Pane::autosave_item(&item, workspace.project.clone(), cx)
+ .detach_and_log_err(cx);
+ }
+ })
+ .detach();
+
+ let item_id = self.id();
+ cx.observe_release(self, move |workspace, _, _| {
+ workspace.panes_by_item.remove(&item_id);
+ event_subscription.take();
+ })
+ .detach();
+ }
+ }
+
+ fn deactivated(&self, cx: &mut MutableAppContext) {
+ self.update(cx, |this, cx| this.deactivated(cx));
+ }
+
+ fn workspace_deactivated(&self, cx: &mut MutableAppContext) {
+ self.update(cx, |this, cx| this.workspace_deactivated(cx));
+ }
+
+ fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool {
+ self.update(cx, |this, cx| this.navigate(data, cx))
+ }
+
+ fn id(&self) -> usize {
+ self.id()
+ }
+
+ fn window_id(&self) -> usize {
+ self.window_id()
+ }
+
+ fn to_any(&self) -> AnyViewHandle {
+ self.into()
+ }
+
+ fn is_dirty(&self, cx: &AppContext) -> bool {
+ self.read(cx).is_dirty(cx)
+ }
+
+ fn has_conflict(&self, cx: &AppContext) -> bool {
+ self.read(cx).has_conflict(cx)
+ }
+
+ fn can_save(&self, cx: &AppContext) -> bool {
+ self.read(cx).can_save(cx)
+ }
+
+ fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>> {
+ self.update(cx, |item, cx| item.save(project, cx))
+ }
+
+ fn save_as(
+ &self,
+ project: ModelHandle<Project>,
+ abs_path: PathBuf,
+ cx: &mut MutableAppContext,
+ ) -> Task<anyhow::Result<()>> {
+ self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
+ }
+
+ fn reload(
+ &self,
+ project: ModelHandle<Project>,
+ cx: &mut MutableAppContext,
+ ) -> Task<Result<()>> {
+ self.update(cx, |item, cx| item.reload(project, cx))
+ }
+
+ fn git_diff_recalc(
+ &self,
+ project: ModelHandle<Project>,
+ cx: &mut MutableAppContext,
+ ) -> Task<Result<()>> {
+ self.update(cx, |item, cx| item.git_diff_recalc(project, cx))
+ }
+
+ fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle> {
+ self.read(cx).act_as_type(type_id, self, cx)
+ }
+
+ fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
+ if cx.has_global::<FollowableItemBuilders>() {
+ let builders = cx.global::<FollowableItemBuilders>();
+ let item = self.to_any();
+ Some(builders.get(&item.view_type())?.1(item))
+ } else {
+ None
+ }
+ }
+
+ fn on_release(
+ &self,
+ cx: &mut MutableAppContext,
+ callback: Box<dyn FnOnce(&mut MutableAppContext)>,
+ ) -> gpui::Subscription {
+ cx.observe_release(self, move |_, cx| callback(cx))
+ }
+
+ fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
+ self.read(cx).as_searchable(self)
+ }
+
+ fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation {
+ self.read(cx).breadcrumb_location()
+ }
+
+ fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
+ self.read(cx).breadcrumbs(theme, cx)
+ }
+}
+
+impl From<Box<dyn ItemHandle>> for AnyViewHandle {
+ fn from(val: Box<dyn ItemHandle>) -> Self {
+ val.to_any()
+ }
+}
+
+impl From<&Box<dyn ItemHandle>> for AnyViewHandle {
+ fn from(val: &Box<dyn ItemHandle>) -> Self {
+ val.to_any()
+ }
+}
+
+impl Clone for Box<dyn ItemHandle> {
+ fn clone(&self) -> Box<dyn ItemHandle> {
+ self.boxed_clone()
+ }
+}
+
+impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
+ fn id(&self) -> usize {
+ self.id()
+ }
+
+ fn window_id(&self) -> usize {
+ self.window_id()
+ }
+
+ fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
+ self.upgrade(cx).map(|v| Box::new(v) as Box<dyn ItemHandle>)
+ }
+}
+
+pub trait ProjectItem: Item {
+ type Item: project::Item;
+
+ fn for_project_item(
+ project: ModelHandle<Project>,
+ item: ModelHandle<Self::Item>,
+ cx: &mut ViewContext<Self>,
+ ) -> Self;
+}
+
+pub trait FollowableItem: Item {
+ fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
+ fn from_state_proto(
+ pane: ViewHandle<Pane>,
+ project: ModelHandle<Project>,
+ state: &mut Option<proto::view::Variant>,
+ cx: &mut MutableAppContext,
+ ) -> Option<Task<Result<ViewHandle<Self>>>>;
+ fn add_event_to_update_proto(
+ &self,
+ event: &Self::Event,
+ update: &mut Option<proto::update_view::Variant>,
+ cx: &AppContext,
+ ) -> bool;
+ fn apply_update_proto(
+ &mut self,
+ message: proto::update_view::Variant,
+ cx: &mut ViewContext<Self>,
+ ) -> Result<()>;
+
+ fn set_leader_replica_id(&mut self, leader_replica_id: Option<u16>, cx: &mut ViewContext<Self>);
+ fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
+}
+
+pub trait FollowableItemHandle: ItemHandle {
+ fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext);
+ fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
+ fn add_event_to_update_proto(
+ &self,
+ event: &dyn Any,
+ update: &mut Option<proto::update_view::Variant>,
+ cx: &AppContext,
+ ) -> bool;
+ fn apply_update_proto(
+ &self,
+ message: proto::update_view::Variant,
+ cx: &mut MutableAppContext,
+ ) -> Result<()>;
+ fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
+}
+
+impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
+ fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext) {
+ self.update(cx, |this, cx| {
+ this.set_leader_replica_id(leader_replica_id, cx)
+ })
+ }
+
+ fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
+ self.read(cx).to_state_proto(cx)
+ }
+
+ fn add_event_to_update_proto(
+ &self,
+ event: &dyn Any,
+ update: &mut Option<proto::update_view::Variant>,
+ cx: &AppContext,
+ ) -> bool {
+ if let Some(event) = event.downcast_ref() {
+ self.read(cx).add_event_to_update_proto(event, update, cx)
+ } else {
+ false
+ }
+ }
+
+ fn apply_update_proto(
+ &self,
+ message: proto::update_view::Variant,
+ cx: &mut MutableAppContext,
+ ) -> Result<()> {
+ self.update(cx, |this, cx| this.apply_update_proto(message, cx))
+ }
+
+ fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool {
+ if let Some(event) = event.downcast_ref() {
+ T::should_unfollow_on_event(event, cx)
+ } else {
+ false
+ }
+ }
+}
+
+#[cfg(test)]
+pub(crate) mod test {
+ use std::{any::Any, borrow::Cow, cell::Cell};
+
+ use anyhow::anyhow;
+ use gpui::{
+ elements::Empty, AppContext, Element, ElementBox, Entity, ModelHandle, RenderContext, Task,
+ View, ViewContext,
+ };
+ use project::{Project, ProjectEntryId, ProjectPath};
+ use smallvec::SmallVec;
+
+ use crate::{sidebar::SidebarItem, ItemNavHistory};
+
+ use super::{Item, ItemEvent};
+
+ pub struct TestItem {
+ pub state: String,
+ pub label: String,
+ pub save_count: usize,
+ pub save_as_count: usize,
+ pub reload_count: usize,
+ pub is_dirty: bool,
+ pub is_singleton: bool,
+ pub has_conflict: bool,
+ pub project_entry_ids: Vec<ProjectEntryId>,
+ pub project_path: Option<ProjectPath>,
+ pub nav_history: Option<ItemNavHistory>,
+ pub tab_descriptions: Option<Vec<&'static str>>,
+ pub tab_detail: Cell<Option<usize>>,
+ }
+
+ pub enum TestItemEvent {
+ Edit,
+ }
+
+ impl Clone for TestItem {
+ fn clone(&self) -> Self {
+ Self {
+ state: self.state.clone(),
+ label: self.label.clone(),
+ save_count: self.save_count,
+ save_as_count: self.save_as_count,
+ reload_count: self.reload_count,
+ is_dirty: self.is_dirty,
+ is_singleton: self.is_singleton,
+ has_conflict: self.has_conflict,
+ project_entry_ids: self.project_entry_ids.clone(),
+ project_path: self.project_path.clone(),
+ nav_history: None,
+ tab_descriptions: None,
+ tab_detail: Default::default(),
+ }
+ }
+ }
+
+ impl TestItem {
+ pub fn new() -> Self {
+ Self {
+ state: String::new(),
+ label: String::new(),
+ save_count: 0,
+ save_as_count: 0,
+ reload_count: 0,
+ is_dirty: false,
+ has_conflict: false,
+ project_entry_ids: Vec::new(),
+ project_path: None,
+ is_singleton: true,
+ nav_history: None,
+ tab_descriptions: None,
+ tab_detail: Default::default(),
+ }
+ }
+
+ pub fn with_label(mut self, state: &str) -> Self {
+ self.label = state.to_string();
+ self
+ }
+
+ pub fn with_singleton(mut self, singleton: bool) -> Self {
+ self.is_singleton = singleton;
+ self
+ }
+
+ pub fn with_project_entry_ids(mut self, project_entry_ids: &[u64]) -> Self {
+ self.project_entry_ids.extend(
+ project_entry_ids
+ .iter()
+ .copied()
+ .map(ProjectEntryId::from_proto),
+ );
+ self
+ }
+
+ pub fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
+ self.push_to_nav_history(cx);
+ self.state = state;
+ }
+
+ fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
+ if let Some(history) = &mut self.nav_history {
+ history.push(Some(Box::new(self.state.clone())), cx);
+ }
+ }
+ }
+
+ impl Entity for TestItem {
+ type Event = TestItemEvent;
+ }
+
+ impl View for TestItem {
+ fn ui_name() -> &'static str {
+ "TestItem"
+ }
+
+ fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
+ Empty::new().boxed()
+ }
+ }
+
+ impl Item for TestItem {
+ fn tab_description<'a>(&'a self, detail: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
+ self.tab_descriptions.as_ref().and_then(|descriptions| {
+ let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
+ Some(description.into())
+ })
+ }
+
+ fn tab_content(&self, detail: Option<usize>, _: &theme::Tab, _: &AppContext) -> ElementBox {
+ self.tab_detail.set(detail);
+ Empty::new().boxed()
+ }
+
+ fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
+ self.project_path.clone()
+ }
+
+ fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
+ self.project_entry_ids.iter().copied().collect()
+ }
+
+ fn is_singleton(&self, _: &AppContext) -> bool {
+ self.is_singleton
+ }
+
+ fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
+ self.nav_history = Some(history);
+ }
+
+ fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
+ let state = *state.downcast::<String>().unwrap_or_default();
+ if state != self.state {
+ self.state = state;
+ true
+ } else {
+ false
+ }
+ }
+
+ fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
+ self.push_to_nav_history(cx);
+ }
+
+ fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
+ where
+ Self: Sized,
+ {
+ Some(self.clone())
+ }
+
+ fn is_dirty(&self, _: &AppContext) -> bool {
+ self.is_dirty
+ }
+
+ fn has_conflict(&self, _: &AppContext) -> bool {
+ self.has_conflict
+ }
+
+ fn can_save(&self, _: &AppContext) -> bool {
+ !self.project_entry_ids.is_empty()
+ }
+
+ fn save(
+ &mut self,
+ _: ModelHandle<Project>,
+ _: &mut ViewContext<Self>,
+ ) -> Task<anyhow::Result<()>> {
+ self.save_count += 1;
+ self.is_dirty = false;
+ Task::ready(Ok(()))
+ }
+
+ fn save_as(
+ &mut self,
+ _: ModelHandle<Project>,
+ _: std::path::PathBuf,
+ _: &mut ViewContext<Self>,
+ ) -> Task<anyhow::Result<()>> {
+ self.save_as_count += 1;
+ self.is_dirty = false;
+ Task::ready(Ok(()))
+ }
+
+ fn reload(
+ &mut self,
+ _: ModelHandle<Project>,
+ _: &mut ViewContext<Self>,
+ ) -> Task<anyhow::Result<()>> {
+ self.reload_count += 1;
+ self.is_dirty = false;
+ Task::ready(Ok(()))
+ }
+
+ fn to_item_events(_: &Self::Event) -> Vec<ItemEvent> {
+ vec![ItemEvent::UpdateTab, ItemEvent::Edit]
+ }
+
+ fn serialized_item_kind() -> Option<&'static str> {
+ None
+ }
+
+ fn deserialize(
+ workspace_id: crate::persistence::model::WorkspaceId,
+ item_id: crate::persistence::model::ItemId,
+ cx: &mut ViewContext<Self>,
+ ) -> anyhow::Result<Self> {
+ Err(anyhow!("Cannot deserialize test item"))
+ }
+ }
+
+ impl SidebarItem for TestItem {}
+}
@@ -3,8 +3,9 @@ mod dragged_item_receiver;
use super::{ItemHandle, SplitDirection};
use crate::{
dock::{icon_for_dock_anchor, AnchorDockBottom, AnchorDockRight, ExpandDock, HideDock},
+ item::WeakItemHandle,
toolbar::Toolbar,
- Item, NewFile, NewSearch, NewTerminal, WeakItemHandle, Workspace,
+ Item, NewFile, NewSearch, NewTerminal, Workspace,
};
use anyhow::Result;
use collections::{HashMap, HashSet, VecDeque};
@@ -1634,7 +1635,7 @@ mod tests {
use std::sync::Arc;
use super::*;
- use crate::tests::TestItem;
+ use crate::item::test::TestItem;
use gpui::{executor::Deterministic, TestAppContext};
use project::FakeFs;
@@ -4,6 +4,7 @@ pub mod model;
use std::ops::Deref;
use std::path::{Path, PathBuf};
+use std::sync::Arc;
use anyhow::{bail, Context, Result};
use db::open_file_db;
@@ -52,7 +53,9 @@ pub(crate) const WORKSPACES_MIGRATION: Migration = Migration::new(
parent_group_id INTEGER, -- NULL indicates that this is a root node
position INTEGER, -- NULL indicates that this is a root node
axis TEXT NOT NULL, -- Enum: 'Vertical' / 'Horizontal'
- FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE,
+ FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE,
FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
) STRICT;
@@ -61,7 +64,9 @@ pub(crate) const WORKSPACES_MIGRATION: Migration = Migration::new(
workspace_id BLOB NOT NULL,
parent_group_id INTEGER, -- NULL, this is a dock pane
position INTEGER, -- NULL, this is a dock pane
- FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE,
+ FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE,
FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
) STRICT;
@@ -71,8 +76,11 @@ pub(crate) const WORKSPACES_MIGRATION: Migration = Migration::new(
pane_id INTEGER NOT NULL,
kind TEXT NOT NULL,
position INTEGER NOT NULL,
- FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE
- FOREIGN KEY(pane_id) REFERENCES panes(pane_id) ON DELETE CASCADE
+ FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE,
+ FOREIGN KEY(pane_id) REFERENCES panes(pane_id)
+ ON DELETE CASCADE,
PRIMARY KEY(item_id, workspace_id)
) STRICT;
"}],
@@ -96,15 +104,15 @@ impl WorkspaceDb {
// Note that we re-assign the workspace_id here in case it's empty
// and we've grabbed the most recent workspace
- let (workspace_id, dock_anchor, dock_visible) = iife!({
+ let (workspace_id, dock_position) = iife!({
if worktree_roots.len() == 0 {
self.select_row(indoc! {"
- SELECT workspace_id, dock_anchor, dock_visible
+ SELECT workspace_id, dock_visible, dock_anchor
FROM workspaces
ORDER BY timestamp DESC LIMIT 1"})?()?
} else {
self.select_row_bound(indoc! {"
- SELECT workspace_id, dock_anchor, dock_visible
+ SELECT workspace_id, dock_visible, dock_anchor
FROM workspaces
WHERE workspace_id = ?"})?(&workspace_id)?
}
@@ -122,8 +130,7 @@ impl WorkspaceDb {
.get_center_pane_group(&workspace_id)
.context("Getting center group")
.log_err()?,
- dock_anchor,
- dock_visible,
+ dock_position,
})
}
@@ -150,8 +157,8 @@ impl WorkspaceDb {
self.exec_bound("DELETE FROM workspaces WHERE workspace_id = ?;")?(&workspace_id)?;
self.exec_bound(
- "INSERT INTO workspaces(workspace_id, dock_anchor, dock_visible) VALUES (?, ?, ?)",
- )?((&workspace_id, workspace.dock_anchor, workspace.dock_visible))?;
+ "INSERT INTO workspaces(workspace_id, dock_visible, dock_anchor) VALUES (?, ?, ?)",
+ )?((&workspace_id, workspace.dock_position))?;
// Save center pane group and dock pane
self.save_pane_group(&workspace_id, &workspace.center_group, None)?;
@@ -172,7 +179,7 @@ impl WorkspaceDb {
}
/// Returns the previous workspace ids sorted by last modified along with their opened worktree roots
- pub fn recent_workspaces(&self, limit: usize) -> Vec<Vec<PathBuf>> {
+ pub fn recent_workspaces(&self, limit: usize) -> Vec<Arc<Vec<PathBuf>>> {
iife!({
// TODO, upgrade anyhow: https://docs.rs/anyhow/1.0.66/anyhow/fn.Ok.html
Ok::<_, anyhow::Error>(
@@ -181,7 +188,7 @@ impl WorkspaceDb {
)?(limit)?
.into_iter()
.map(|id| id.paths())
- .collect::<Vec<Vec<PathBuf>>>(),
+ .collect::<Vec<Arc<Vec<PathBuf>>>>(),
)
})
.log_err()
@@ -339,22 +346,19 @@ mod tests {
let db = WorkspaceDb(open_memory_db("test_basic_functionality"));
let workspace_1 = SerializedWorkspace {
- dock_anchor: DockAnchor::Bottom,
- dock_visible: true,
+ dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom),
center_group: Default::default(),
dock_pane: Default::default(),
};
let workspace_2 = SerializedWorkspace {
- dock_anchor: DockAnchor::Expanded,
- dock_visible: false,
+ dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded),
center_group: Default::default(),
dock_pane: Default::default(),
};
let workspace_3 = SerializedWorkspace {
- dock_anchor: DockAnchor::Right,
- dock_visible: true,
+ dock_position: crate::dock::DockPosition::Shown(DockAnchor::Right),
center_group: Default::default(),
dock_pane: Default::default(),
};
@@ -414,8 +418,7 @@ mod tests {
center_group: &SerializedPaneGroup,
) -> SerializedWorkspace {
SerializedWorkspace {
- dock_anchor: DockAnchor::Right,
- dock_visible: false,
+ dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Right),
center_group: center_group.clone(),
dock_pane,
}
@@ -6,18 +6,21 @@ use std::{
use anyhow::{bail, Result};
use gpui::Axis;
+
use settings::DockAnchor;
use sqlez::{
bindable::{Bind, Column},
statement::Statement,
};
+use crate::dock::DockPosition;
+
#[derive(Debug, Clone, PartialEq, Eq)]
-pub(crate) struct WorkspaceId(Vec<PathBuf>);
+pub(crate) struct WorkspaceId(Arc<Vec<PathBuf>>);
impl WorkspaceId {
- pub fn paths(self) -> Vec<PathBuf> {
- self.0
+ pub fn paths(self) -> Arc<Vec<PathBuf>> {
+ self.0.clone()
}
}
@@ -28,7 +31,7 @@ impl<P: AsRef<Path>, T: IntoIterator<Item = P>> From<T> for WorkspaceId {
.map(|p| p.as_ref().to_path_buf())
.collect::<Vec<_>>();
roots.sort();
- Self(roots)
+ Self(Arc::new(roots))
}
}
@@ -49,8 +52,7 @@ impl Column for WorkspaceId {
#[derive(Debug, PartialEq, Eq)]
pub struct SerializedWorkspace {
- pub dock_anchor: DockAnchor,
- pub dock_visible: bool,
+ pub dock_position: DockPosition,
pub center_group: SerializedPaneGroup,
pub dock_pane: SerializedPane,
}
@@ -152,12 +154,31 @@ impl SerializedItem {
}
}
+impl Bind for DockPosition {
+ fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
+ let next_index = statement.bind(self.is_visible(), start_index)?;
+ statement.bind(self.anchor(), next_index)
+ }
+}
+
+impl Column for DockPosition {
+ fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
+ let (visible, next_index) = bool::column(statement, start_index)?;
+ let (dock_anchor, next_index) = DockAnchor::column(statement, next_index)?;
+ let position = if visible {
+ DockPosition::Shown(dock_anchor)
+ } else {
+ DockPosition::Hidden(dock_anchor)
+ };
+ Ok((position, next_index))
+ }
+}
+
#[cfg(test)]
mod tests {
+ use settings::DockAnchor;
use sqlez::connection::Connection;
- use crate::persistence::model::DockAnchor;
-
use super::WorkspaceId;
#[test]
@@ -6,7 +6,7 @@ use gpui::{
};
use project::search::SearchQuery;
-use crate::{Item, ItemHandle, WeakItemHandle};
+use crate::{item::WeakItemHandle, Item, ItemHandle};
#[derive(Debug)]
pub enum SearchEvent {
@@ -1,4 +1,8 @@
-use crate::{Item, ItemNavHistory};
+use crate::{
+ item::ItemEvent,
+ persistence::model::{ItemId, WorkspaceId},
+ Item, ItemNavHistory,
+};
use anyhow::{anyhow, Result};
use call::participant::{Frame, RemoteVideoTrack};
use client::{PeerId, User};
@@ -176,9 +180,21 @@ impl Item for SharedScreen {
Task::ready(Err(anyhow!("Item::reload called on SharedScreen")))
}
- fn to_item_events(event: &Self::Event) -> Vec<crate::ItemEvent> {
+ fn to_item_events(event: &Self::Event) -> Vec<ItemEvent> {
match event {
- Event::Close => vec![crate::ItemEvent::CloseItem],
+ Event::Close => vec![ItemEvent::CloseItem],
}
}
+
+ fn serialized_item_kind() -> Option<&'static str> {
+ None
+ }
+
+ fn deserialize(
+ workspace_id: WorkspaceId,
+ item_id: ItemId,
+ cx: &mut ViewContext<Self>,
+ ) -> Result<Self> {
+ Err(anyhow!("SharedScreen can not be deserialized"))
+ }
}
@@ -3,6 +3,7 @@
/// This may cause issues when you're trying to write tests that use workspace focus to add items at
/// specific locations.
pub mod dock;
+pub mod item;
pub mod pane;
pub mod pane_group;
mod persistence;
@@ -12,7 +13,15 @@ pub mod sidebar;
mod status_bar;
mod toolbar;
-use crate::persistence::model::SerializedWorkspace;
+use std::{
+ any::TypeId,
+ borrow::Cow,
+ future::Future,
+ path::{Path, PathBuf},
+ sync::Arc,
+ time::Duration,
+};
+
use anyhow::{anyhow, Context, Result};
use call::ActiveCall;
use client::{proto, Client, PeerId, TypedEnvelope, UserStore};
@@ -30,56 +39,25 @@ use gpui::{
MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View,
ViewContext, ViewHandle, WeakViewHandle,
};
+use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
use language::LanguageRegistry;
use log::{error, warn};
pub use pane::*;
pub use pane_group::*;
+use persistence::model::{ItemId, WorkspaceId};
use postage::prelude::Stream;
use project::{Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId};
-use searchable::SearchableItemHandle;
use serde::Deserialize;
use settings::{Autosave, DockAnchor, Settings};
use shared_screen::SharedScreen;
use sidebar::{Sidebar, SidebarButtons, SidebarSide, ToggleSidebarItem};
-use smallvec::SmallVec;
use status_bar::StatusBar;
pub use status_bar::StatusItemView;
-use std::{
- any::{Any, TypeId},
- borrow::Cow,
- cell::RefCell,
- fmt,
- future::Future,
- path::{Path, PathBuf},
- rc::Rc,
- sync::{
- atomic::{AtomicBool, Ordering::SeqCst},
- Arc,
- },
- time::Duration,
-};
use theme::{Theme, ThemeRegistry};
pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
use util::ResultExt;
-type ProjectItemBuilders = HashMap<
- TypeId,
- fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
->;
-
-type FollowableItemBuilder = fn(
- ViewHandle<Pane>,
- ModelHandle<Project>,
- &mut Option<proto::view::Variant>,
- &mut MutableAppContext,
-) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
-type FollowableItemBuilders = HashMap<
- TypeId,
- (
- FollowableItemBuilder,
- fn(AnyViewHandle) -> Box<dyn FollowableItemHandle>,
- ),
->;
+use crate::persistence::model::SerializedWorkspace;
#[derive(Clone, PartialEq)]
pub struct RemoveWorktreeFromProject(pub WorktreeId);
@@ -316,6 +294,10 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
client.add_view_message_handler(Workspace::handle_update_followers);
}
+type ProjectItemBuilders = HashMap<
+ TypeId,
+ fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
+>;
pub fn register_project_item<I: ProjectItem>(cx: &mut MutableAppContext) {
cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
@@ -325,6 +307,19 @@ pub fn register_project_item<I: ProjectItem>(cx: &mut MutableAppContext) {
});
}
+type FollowableItemBuilder = fn(
+ ViewHandle<Pane>,
+ ModelHandle<Project>,
+ &mut Option<proto::view::Variant>,
+ &mut MutableAppContext,
+) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
+type FollowableItemBuilders = HashMap<
+ TypeId,
+ (
+ FollowableItemBuilder,
+ fn(AnyViewHandle) -> Box<dyn FollowableItemHandle>,
+ ),
+>;
pub fn register_followable_item<I: FollowableItem>(cx: &mut MutableAppContext) {
cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
builders.insert(
@@ -342,6 +337,26 @@ pub fn register_followable_item<I: FollowableItem>(cx: &mut MutableAppContext) {
});
}
+type SerializableItemBuilders = HashMap<
+ &'static str,
+ fn(WorkspaceId, ItemId, &mut ViewContext<Pane>) -> Option<Box<dyn ItemHandle>>,
+>;
+pub fn register_deserializable_item<I: Item>(cx: &mut MutableAppContext) {
+ cx.update_default_global(|deserializers: &mut SerializableItemBuilders, _| {
+ if let Some(serialized_item_kind) = I::serialized_item_kind() {
+ deserializers.insert(serialized_item_kind, |workspace_id, item_id, cx| {
+ if let Some(v) =
+ cx.add_option_view(|cx| I::deserialize(workspace_id, item_id, cx).log_err())
+ {
+ Some(Box::new(v))
+ } else {
+ None
+ }
+ });
+ }
+ });
+}
+
pub struct AppState {
pub languages: Arc<LanguageRegistry>,
pub themes: Arc<ThemeRegistry>,
@@ -354,189 +369,34 @@ pub struct AppState {
pub default_item_factory: DefaultItemFactory,
}
-#[derive(Eq, PartialEq, Hash)]
-pub enum ItemEvent {
- CloseItem,
- UpdateTab,
- UpdateBreadcrumbs,
- Edit,
-}
-
-pub trait Item: View {
- fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
- fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
- fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
- false
- }
- fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
- None
- }
- fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
- -> ElementBox;
- fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
- fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
- fn is_singleton(&self, cx: &AppContext) -> bool;
- fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>);
- fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
- where
- Self: Sized,
- {
- None
- }
- fn is_dirty(&self, _: &AppContext) -> bool {
- false
- }
- fn has_conflict(&self, _: &AppContext) -> bool {
- false
- }
- fn can_save(&self, cx: &AppContext) -> bool;
- fn save(
- &mut self,
- project: ModelHandle<Project>,
- cx: &mut ViewContext<Self>,
- ) -> Task<Result<()>>;
- fn save_as(
- &mut self,
- project: ModelHandle<Project>,
- abs_path: PathBuf,
- cx: &mut ViewContext<Self>,
- ) -> Task<Result<()>>;
- fn reload(
- &mut self,
- project: ModelHandle<Project>,
- cx: &mut ViewContext<Self>,
- ) -> Task<Result<()>>;
- fn git_diff_recalc(
- &mut self,
- _project: ModelHandle<Project>,
- _cx: &mut ViewContext<Self>,
- ) -> Task<Result<()>> {
- Task::ready(Ok(()))
- }
- fn to_item_events(event: &Self::Event) -> Vec<ItemEvent>;
- fn should_close_item_on_event(_: &Self::Event) -> bool {
- false
- }
- fn should_update_tab_on_event(_: &Self::Event) -> bool {
- false
- }
- fn is_edit_event(_: &Self::Event) -> bool {
- false
- }
- fn act_as_type(
- &self,
- type_id: TypeId,
- self_handle: &ViewHandle<Self>,
- _: &AppContext,
- ) -> Option<AnyViewHandle> {
- if TypeId::of::<Self>() == type_id {
- Some(self_handle.into())
- } else {
- None
- }
- }
- fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
- None
- }
-
- fn breadcrumb_location(&self) -> ToolbarItemLocation {
- ToolbarItemLocation::Hidden
- }
- fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<ElementBox>> {
- None
- }
-}
-
-pub trait ProjectItem: Item {
- type Item: project::Item;
-
- fn for_project_item(
- project: ModelHandle<Project>,
- item: ModelHandle<Self::Item>,
- cx: &mut ViewContext<Self>,
- ) -> Self;
-}
-
-pub trait FollowableItem: Item {
- fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
- fn from_state_proto(
- pane: ViewHandle<Pane>,
- project: ModelHandle<Project>,
- state: &mut Option<proto::view::Variant>,
- cx: &mut MutableAppContext,
- ) -> Option<Task<Result<ViewHandle<Self>>>>;
- fn add_event_to_update_proto(
- &self,
- event: &Self::Event,
- update: &mut Option<proto::update_view::Variant>,
- cx: &AppContext,
- ) -> bool;
- fn apply_update_proto(
- &mut self,
- message: proto::update_view::Variant,
- cx: &mut ViewContext<Self>,
- ) -> Result<()>;
-
- fn set_leader_replica_id(&mut self, leader_replica_id: Option<u16>, cx: &mut ViewContext<Self>);
- fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
-}
+impl AppState {
+ #[cfg(any(test, feature = "test-support"))]
+ pub fn test(cx: &mut MutableAppContext) -> Arc<Self> {
+ use fs::HomeDir;
-pub trait FollowableItemHandle: ItemHandle {
- fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext);
- fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
- fn add_event_to_update_proto(
- &self,
- event: &dyn Any,
- update: &mut Option<proto::update_view::Variant>,
- cx: &AppContext,
- ) -> bool;
- fn apply_update_proto(
- &self,
- message: proto::update_view::Variant,
- cx: &mut MutableAppContext,
- ) -> Result<()>;
- fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
-}
+ cx.set_global(HomeDir(Path::new("/tmp/").to_path_buf()));
+ let settings = Settings::test(cx);
+ cx.set_global(settings);
-impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
- fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext) {
- self.update(cx, |this, cx| {
- this.set_leader_replica_id(leader_replica_id, cx)
+ let fs = fs::FakeFs::new(cx.background().clone());
+ let languages = Arc::new(LanguageRegistry::test());
+ let http_client = client::test::FakeHttpClient::with_404_response();
+ let client = Client::new(http_client.clone(), cx);
+ let project_store = cx.add_model(|_| ProjectStore::new());
+ let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
+ let themes = ThemeRegistry::new((), cx.font_cache().clone());
+ Arc::new(Self {
+ client,
+ themes,
+ fs,
+ languages,
+ user_store,
+ project_store,
+ initialize_workspace: |_, _, _| {},
+ build_window_options: Default::default,
+ default_item_factory: |_, _| unimplemented!(),
})
}
-
- fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
- self.read(cx).to_state_proto(cx)
- }
-
- fn add_event_to_update_proto(
- &self,
- event: &dyn Any,
- update: &mut Option<proto::update_view::Variant>,
- cx: &AppContext,
- ) -> bool {
- if let Some(event) = event.downcast_ref() {
- self.read(cx).add_event_to_update_proto(event, update, cx)
- } else {
- false
- }
- }
-
- fn apply_update_proto(
- &self,
- message: proto::update_view::Variant,
- cx: &mut MutableAppContext,
- ) -> Result<()> {
- self.update(cx, |this, cx| this.apply_update_proto(message, cx))
- }
-
- fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool {
- if let Some(event) = event.downcast_ref() {
- T::should_unfollow_on_event(event, cx)
- } else {
- false
- }
- }
}
struct DelayedDebouncedEditAction {
@@ -580,7 +440,7 @@ impl DelayedDebouncedEditAction {
futures::select_biased! {
_ = receiver => return,
- _ = timer => {}
+ _ = timer => {}
}
if let Some(project) = project.upgrade(&cx) {
@@ -590,427 +450,6 @@ impl DelayedDebouncedEditAction {
}
}
-pub trait ItemHandle: 'static + fmt::Debug {
- fn subscribe_to_item_events(
- &self,
- cx: &mut MutableAppContext,
- handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
- ) -> gpui::Subscription;
- fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
- fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
- -> ElementBox;
- fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
- fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
- fn is_singleton(&self, cx: &AppContext) -> bool;
- fn boxed_clone(&self) -> Box<dyn ItemHandle>;
- fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>>;
- fn added_to_pane(
- &self,
- workspace: &mut Workspace,
- pane: ViewHandle<Pane>,
- cx: &mut ViewContext<Workspace>,
- );
- fn deactivated(&self, cx: &mut MutableAppContext);
- fn workspace_deactivated(&self, cx: &mut MutableAppContext);
- fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool;
- fn id(&self) -> usize;
- fn window_id(&self) -> usize;
- fn to_any(&self) -> AnyViewHandle;
- fn is_dirty(&self, cx: &AppContext) -> bool;
- fn has_conflict(&self, cx: &AppContext) -> bool;
- fn can_save(&self, cx: &AppContext) -> bool;
- fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>>;
- fn save_as(
- &self,
- project: ModelHandle<Project>,
- abs_path: PathBuf,
- cx: &mut MutableAppContext,
- ) -> Task<Result<()>>;
- fn reload(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext)
- -> Task<Result<()>>;
- fn git_diff_recalc(
- &self,
- project: ModelHandle<Project>,
- cx: &mut MutableAppContext,
- ) -> Task<Result<()>>;
- fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle>;
- fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
- fn on_release(
- &self,
- cx: &mut MutableAppContext,
- callback: Box<dyn FnOnce(&mut MutableAppContext)>,
- ) -> gpui::Subscription;
- fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
- fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
- fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>>;
-}
-
-pub trait WeakItemHandle {
- fn id(&self) -> usize;
- fn window_id(&self) -> usize;
- fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
-}
-
-impl dyn ItemHandle {
- pub fn downcast<T: View>(&self) -> Option<ViewHandle<T>> {
- self.to_any().downcast()
- }
-
- pub fn act_as<T: View>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
- self.act_as_type(TypeId::of::<T>(), cx)
- .and_then(|t| t.downcast())
- }
-}
-
-impl<T: Item> ItemHandle for ViewHandle<T> {
- fn subscribe_to_item_events(
- &self,
- cx: &mut MutableAppContext,
- handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
- ) -> gpui::Subscription {
- cx.subscribe(self, move |_, event, cx| {
- for item_event in T::to_item_events(event) {
- handler(item_event, cx)
- }
- })
- }
-
- fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
- self.read(cx).tab_description(detail, cx)
- }
-
- fn tab_content(
- &self,
- detail: Option<usize>,
- style: &theme::Tab,
- cx: &AppContext,
- ) -> ElementBox {
- self.read(cx).tab_content(detail, style, cx)
- }
-
- fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
- self.read(cx).project_path(cx)
- }
-
- fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
- self.read(cx).project_entry_ids(cx)
- }
-
- fn is_singleton(&self, cx: &AppContext) -> bool {
- self.read(cx).is_singleton(cx)
- }
-
- fn boxed_clone(&self) -> Box<dyn ItemHandle> {
- Box::new(self.clone())
- }
-
- fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>> {
- self.update(cx, |item, cx| {
- cx.add_option_view(|cx| item.clone_on_split(cx))
- })
- .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
- }
-
- fn added_to_pane(
- &self,
- workspace: &mut Workspace,
- pane: ViewHandle<Pane>,
- cx: &mut ViewContext<Workspace>,
- ) {
- let history = pane.read(cx).nav_history_for_item(self);
- self.update(cx, |this, cx| this.set_nav_history(history, cx));
-
- if let Some(followed_item) = self.to_followable_item_handle(cx) {
- if let Some(message) = followed_item.to_state_proto(cx) {
- workspace.update_followers(
- proto::update_followers::Variant::CreateView(proto::View {
- id: followed_item.id() as u64,
- variant: Some(message),
- leader_id: workspace.leader_for_pane(&pane).map(|id| id.0),
- }),
- cx,
- );
- }
- }
-
- if workspace
- .panes_by_item
- .insert(self.id(), pane.downgrade())
- .is_none()
- {
- let mut pending_autosave = DelayedDebouncedEditAction::new();
- let mut pending_git_update = DelayedDebouncedEditAction::new();
- let pending_update = Rc::new(RefCell::new(None));
- let pending_update_scheduled = Rc::new(AtomicBool::new(false));
-
- let mut event_subscription =
- Some(cx.subscribe(self, move |workspace, item, event, cx| {
- let pane = if let Some(pane) = workspace
- .panes_by_item
- .get(&item.id())
- .and_then(|pane| pane.upgrade(cx))
- {
- pane
- } else {
- log::error!("unexpected item event after pane was dropped");
- return;
- };
-
- if let Some(item) = item.to_followable_item_handle(cx) {
- let leader_id = workspace.leader_for_pane(&pane);
-
- if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
- workspace.unfollow(&pane, cx);
- }
-
- if item.add_event_to_update_proto(
- event,
- &mut *pending_update.borrow_mut(),
- cx,
- ) && !pending_update_scheduled.load(SeqCst)
- {
- pending_update_scheduled.store(true, SeqCst);
- cx.after_window_update({
- let pending_update = pending_update.clone();
- let pending_update_scheduled = pending_update_scheduled.clone();
- move |this, cx| {
- pending_update_scheduled.store(false, SeqCst);
- this.update_followers(
- proto::update_followers::Variant::UpdateView(
- proto::UpdateView {
- id: item.id() as u64,
- variant: pending_update.borrow_mut().take(),
- leader_id: leader_id.map(|id| id.0),
- },
- ),
- cx,
- );
- }
- });
- }
- }
-
- for item_event in T::to_item_events(event).into_iter() {
- match item_event {
- ItemEvent::CloseItem => {
- Pane::close_item(workspace, pane, item.id(), cx)
- .detach_and_log_err(cx);
- return;
- }
-
- ItemEvent::UpdateTab => {
- pane.update(cx, |_, cx| {
- cx.emit(pane::Event::ChangeItemTitle);
- cx.notify();
- });
- }
-
- ItemEvent::Edit => {
- if let Autosave::AfterDelay { milliseconds } =
- cx.global::<Settings>().autosave
- {
- let delay = Duration::from_millis(milliseconds);
- let item = item.clone();
- pending_autosave.fire_new(
- delay,
- workspace,
- cx,
- |project, mut cx| async move {
- cx.update(|cx| Pane::autosave_item(&item, project, cx))
- .await
- .log_err();
- },
- );
- }
-
- let settings = cx.global::<Settings>();
- let debounce_delay = settings.git_overrides.gutter_debounce;
-
- let item = item.clone();
-
- if let Some(delay) = debounce_delay {
- const MIN_GIT_DELAY: u64 = 50;
-
- let delay = delay.max(MIN_GIT_DELAY);
- let duration = Duration::from_millis(delay);
-
- pending_git_update.fire_new(
- duration,
- workspace,
- cx,
- |project, mut cx| async move {
- cx.update(|cx| item.git_diff_recalc(project, cx))
- .await
- .log_err();
- },
- );
- } else {
- let project = workspace.project().downgrade();
- cx.spawn_weak(|_, mut cx| async move {
- if let Some(project) = project.upgrade(&cx) {
- cx.update(|cx| item.git_diff_recalc(project, cx))
- .await
- .log_err();
- }
- })
- .detach();
- }
- }
-
- _ => {}
- }
- }
- }));
-
- cx.observe_focus(self, move |workspace, item, focused, cx| {
- if !focused && cx.global::<Settings>().autosave == Autosave::OnFocusChange {
- Pane::autosave_item(&item, workspace.project.clone(), cx)
- .detach_and_log_err(cx);
- }
- })
- .detach();
-
- let item_id = self.id();
- cx.observe_release(self, move |workspace, _, _| {
- workspace.panes_by_item.remove(&item_id);
- event_subscription.take();
- })
- .detach();
- }
- }
-
- fn deactivated(&self, cx: &mut MutableAppContext) {
- self.update(cx, |this, cx| this.deactivated(cx));
- }
-
- fn workspace_deactivated(&self, cx: &mut MutableAppContext) {
- self.update(cx, |this, cx| this.workspace_deactivated(cx));
- }
-
- fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool {
- self.update(cx, |this, cx| this.navigate(data, cx))
- }
-
- fn id(&self) -> usize {
- self.id()
- }
-
- fn window_id(&self) -> usize {
- self.window_id()
- }
-
- fn to_any(&self) -> AnyViewHandle {
- self.into()
- }
-
- fn is_dirty(&self, cx: &AppContext) -> bool {
- self.read(cx).is_dirty(cx)
- }
-
- fn has_conflict(&self, cx: &AppContext) -> bool {
- self.read(cx).has_conflict(cx)
- }
-
- fn can_save(&self, cx: &AppContext) -> bool {
- self.read(cx).can_save(cx)
- }
-
- fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>> {
- self.update(cx, |item, cx| item.save(project, cx))
- }
-
- fn save_as(
- &self,
- project: ModelHandle<Project>,
- abs_path: PathBuf,
- cx: &mut MutableAppContext,
- ) -> Task<anyhow::Result<()>> {
- self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
- }
-
- fn reload(
- &self,
- project: ModelHandle<Project>,
- cx: &mut MutableAppContext,
- ) -> Task<Result<()>> {
- self.update(cx, |item, cx| item.reload(project, cx))
- }
-
- fn git_diff_recalc(
- &self,
- project: ModelHandle<Project>,
- cx: &mut MutableAppContext,
- ) -> Task<Result<()>> {
- self.update(cx, |item, cx| item.git_diff_recalc(project, cx))
- }
-
- fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle> {
- self.read(cx).act_as_type(type_id, self, cx)
- }
-
- fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
- if cx.has_global::<FollowableItemBuilders>() {
- let builders = cx.global::<FollowableItemBuilders>();
- let item = self.to_any();
- Some(builders.get(&item.view_type())?.1(item))
- } else {
- None
- }
- }
-
- fn on_release(
- &self,
- cx: &mut MutableAppContext,
- callback: Box<dyn FnOnce(&mut MutableAppContext)>,
- ) -> gpui::Subscription {
- cx.observe_release(self, move |_, cx| callback(cx))
- }
-
- fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
- self.read(cx).as_searchable(self)
- }
-
- fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation {
- self.read(cx).breadcrumb_location()
- }
-
- fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
- self.read(cx).breadcrumbs(theme, cx)
- }
-}
-
-impl From<Box<dyn ItemHandle>> for AnyViewHandle {
- fn from(val: Box<dyn ItemHandle>) -> Self {
- val.to_any()
- }
-}
-
-impl From<&Box<dyn ItemHandle>> for AnyViewHandle {
- fn from(val: &Box<dyn ItemHandle>) -> Self {
- val.to_any()
- }
-}
-
-impl Clone for Box<dyn ItemHandle> {
- fn clone(&self) -> Box<dyn ItemHandle> {
- self.boxed_clone()
- }
-}
-
-impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
- fn id(&self) -> usize {
- self.id()
- }
-
- fn window_id(&self) -> usize {
- self.window_id()
- }
-
- fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
- self.upgrade(cx).map(|v| Box::new(v) as Box<dyn ItemHandle>)
- }
-}
-
pub trait Notification: View {
fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool;
}
@@ -1036,34 +475,23 @@ impl From<&dyn NotificationHandle> for AnyViewHandle {
}
}
-impl AppState {
- #[cfg(any(test, feature = "test-support"))]
- pub fn test(cx: &mut MutableAppContext) -> Arc<Self> {
- use fs::HomeDir;
+#[derive(Default)]
+struct LeaderState {
+ followers: HashSet<PeerId>,
+}
- cx.set_global(HomeDir(Path::new("/tmp/").to_path_buf()));
- let settings = Settings::test(cx);
- cx.set_global(settings);
+type FollowerStatesByLeader = HashMap<PeerId, HashMap<ViewHandle<Pane>, FollowerState>>;
- let fs = fs::FakeFs::new(cx.background().clone());
- let languages = Arc::new(LanguageRegistry::test());
- let http_client = client::test::FakeHttpClient::with_404_response();
- let client = Client::new(http_client.clone(), cx);
- let project_store = cx.add_model(|_| ProjectStore::new());
- let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
- let themes = ThemeRegistry::new((), cx.font_cache().clone());
- Arc::new(Self {
- client,
- themes,
- fs,
- languages,
- user_store,
- project_store,
- initialize_workspace: |_, _, _| {},
- build_window_options: Default::default,
- default_item_factory: |_, _| unimplemented!(),
- })
- }
+#[derive(Default)]
+struct FollowerState {
+ active_view_id: Option<u64>,
+ items_by_leader_view_id: HashMap<u64, FollowerItem>,
+}
+
+#[derive(Debug)]
+enum FollowerItem {
+ Loading(Vec<proto::update_view::Variant>),
+ Loaded(Box<dyn FollowableItemHandle>),
}
pub enum Event {
@@ -1074,7 +502,6 @@ pub enum Event {
pub struct Workspace {
weak_self: WeakViewHandle<Self>,
- // _db_id: WorkspaceId,
client: Arc<Client>,
user_store: ModelHandle<client::UserStore>,
remote_entity_subscription: Option<client::Subscription>,
@@ -1100,28 +527,9 @@ pub struct Workspace {
_observe_current_user: Task<()>,
}
-#[derive(Default)]
-struct LeaderState {
- followers: HashSet<PeerId>,
-}
-
-type FollowerStatesByLeader = HashMap<PeerId, HashMap<ViewHandle<Pane>, FollowerState>>;
-
-#[derive(Default)]
-struct FollowerState {
- active_view_id: Option<u64>,
- items_by_leader_view_id: HashMap<u64, FollowerItem>,
-}
-
-#[derive(Debug)]
-enum FollowerItem {
- Loading(Vec<proto::update_view::Variant>),
- Loaded(Box<dyn FollowableItemHandle>),
-}
-
impl Workspace {
pub fn new(
- _serialized_workspace: Option<SerializedWorkspace>,
+ serialized_workspace: Option<SerializedWorkspace>,
project: ModelHandle<Project>,
dock_default_factory: DefaultItemFactory,
cx: &mut ViewContext<Self>,
@@ -1160,6 +568,20 @@ impl Workspace {
.detach();
cx.focus(¢er_pane);
cx.emit(Event::PaneAdded(center_pane.clone()));
+ let dock = Dock::new(
+ dock_default_factory,
+ serialized_workspace
+ .as_ref()
+ .map(|ws| ws.dock_position)
+ .clone(),
+ cx,
+ );
+ let dock_pane = dock.pane().clone();
+
+ if let Some(serialized_workspace) = serialized_workspace {
+
+ // Fill them in?
+ }
let fs = project.read(cx).fs().clone();
let user_store = project.read(cx).user_store();
@@ -1186,9 +608,6 @@ impl Workspace {
cx.emit_global(WorkspaceCreated(weak_handle.clone()));
- let dock = Dock::new(dock_default_factory, cx);
- let dock_pane = dock.pane().clone();
-
let left_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Left));
let right_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Right));
let left_sidebar_buttons = cx.add_view(|cx| SidebarButtons::new(left_sidebar.clone(), cx));
@@ -1218,7 +637,6 @@ impl Workspace {
let mut this = Workspace {
modal: None,
weak_self: weak_handle,
- // _db_id: serialized_workspace.workspace_id,
center: PaneGroup::new(center_pane.clone()),
dock,
// When removing an item, the last element remaining in this array
@@ -3086,13 +2504,13 @@ fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) -> Task<()> {
#[cfg(test)]
mod tests {
- use std::cell::Cell;
+ use std::{cell::RefCell, rc::Rc};
- use crate::sidebar::SidebarItem;
+ use crate::item::test::{TestItem, TestItemEvent};
use super::*;
use fs::FakeFs;
- use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
+ use gpui::{executor::Deterministic, TestAppContext, ViewContext};
use project::{Project, ProjectEntryId};
use serde_json::json;
@@ -3697,209 +3115,4 @@ mod tests {
assert!(pane.can_navigate_forward());
});
}
-
- pub struct TestItem {
- state: String,
- pub label: String,
- save_count: usize,
- save_as_count: usize,
- reload_count: usize,
- is_dirty: bool,
- is_singleton: bool,
- has_conflict: bool,
- project_entry_ids: Vec<ProjectEntryId>,
- project_path: Option<ProjectPath>,
- nav_history: Option<ItemNavHistory>,
- tab_descriptions: Option<Vec<&'static str>>,
- tab_detail: Cell<Option<usize>>,
- }
-
- pub enum TestItemEvent {
- Edit,
- }
-
- impl Clone for TestItem {
- fn clone(&self) -> Self {
- Self {
- state: self.state.clone(),
- label: self.label.clone(),
- save_count: self.save_count,
- save_as_count: self.save_as_count,
- reload_count: self.reload_count,
- is_dirty: self.is_dirty,
- is_singleton: self.is_singleton,
- has_conflict: self.has_conflict,
- project_entry_ids: self.project_entry_ids.clone(),
- project_path: self.project_path.clone(),
- nav_history: None,
- tab_descriptions: None,
- tab_detail: Default::default(),
- }
- }
- }
-
- impl TestItem {
- pub fn new() -> Self {
- Self {
- state: String::new(),
- label: String::new(),
- save_count: 0,
- save_as_count: 0,
- reload_count: 0,
- is_dirty: false,
- has_conflict: false,
- project_entry_ids: Vec::new(),
- project_path: None,
- is_singleton: true,
- nav_history: None,
- tab_descriptions: None,
- tab_detail: Default::default(),
- }
- }
-
- pub fn with_label(mut self, state: &str) -> Self {
- self.label = state.to_string();
- self
- }
-
- pub fn with_singleton(mut self, singleton: bool) -> Self {
- self.is_singleton = singleton;
- self
- }
-
- pub fn with_project_entry_ids(mut self, project_entry_ids: &[u64]) -> Self {
- self.project_entry_ids.extend(
- project_entry_ids
- .iter()
- .copied()
- .map(ProjectEntryId::from_proto),
- );
- self
- }
-
- fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
- self.push_to_nav_history(cx);
- self.state = state;
- }
-
- fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
- if let Some(history) = &mut self.nav_history {
- history.push(Some(Box::new(self.state.clone())), cx);
- }
- }
- }
-
- impl Entity for TestItem {
- type Event = TestItemEvent;
- }
-
- impl View for TestItem {
- fn ui_name() -> &'static str {
- "TestItem"
- }
-
- fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
- Empty::new().boxed()
- }
- }
-
- impl Item for TestItem {
- fn tab_description<'a>(&'a self, detail: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
- self.tab_descriptions.as_ref().and_then(|descriptions| {
- let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
- Some(description.into())
- })
- }
-
- fn tab_content(&self, detail: Option<usize>, _: &theme::Tab, _: &AppContext) -> ElementBox {
- self.tab_detail.set(detail);
- Empty::new().boxed()
- }
-
- fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
- self.project_path.clone()
- }
-
- fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
- self.project_entry_ids.iter().copied().collect()
- }
-
- fn is_singleton(&self, _: &AppContext) -> bool {
- self.is_singleton
- }
-
- fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
- self.nav_history = Some(history);
- }
-
- fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
- let state = *state.downcast::<String>().unwrap_or_default();
- if state != self.state {
- self.state = state;
- true
- } else {
- false
- }
- }
-
- fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
- self.push_to_nav_history(cx);
- }
-
- fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
- where
- Self: Sized,
- {
- Some(self.clone())
- }
-
- fn is_dirty(&self, _: &AppContext) -> bool {
- self.is_dirty
- }
-
- fn has_conflict(&self, _: &AppContext) -> bool {
- self.has_conflict
- }
-
- fn can_save(&self, _: &AppContext) -> bool {
- !self.project_entry_ids.is_empty()
- }
-
- fn save(
- &mut self,
- _: ModelHandle<Project>,
- _: &mut ViewContext<Self>,
- ) -> Task<anyhow::Result<()>> {
- self.save_count += 1;
- self.is_dirty = false;
- Task::ready(Ok(()))
- }
-
- fn save_as(
- &mut self,
- _: ModelHandle<Project>,
- _: std::path::PathBuf,
- _: &mut ViewContext<Self>,
- ) -> Task<anyhow::Result<()>> {
- self.save_as_count += 1;
- self.is_dirty = false;
- Task::ready(Ok(()))
- }
-
- fn reload(
- &mut self,
- _: ModelHandle<Project>,
- _: &mut ViewContext<Self>,
- ) -> Task<anyhow::Result<()>> {
- self.reload_count += 1;
- self.is_dirty = false;
- Task::ready(Ok(()))
- }
-
- fn to_item_events(_: &Self::Event) -> Vec<ItemEvent> {
- vec![ItemEvent::UpdateTab, ItemEvent::Edit]
- }
- }
-
- impl SidebarItem for TestItem {}
}
@@ -5,7 +5,7 @@ use gpui::{
Element, Entity, MouseButton, RenderContext, View,
};
use settings::Settings;
-use workspace::StatusItemView;
+use workspace::{item::ItemHandle, StatusItemView};
pub const NEW_ISSUE_URL: &str = "https://github.com/zed-industries/feedback/issues/new/choose";
@@ -43,7 +43,7 @@ impl View for FeedbackLink {
impl StatusItemView for FeedbackLink {
fn set_active_pane_item(
&mut self,
- _: Option<&dyn workspace::ItemHandle>,
+ _: Option<&dyn ItemHandle>,
_: &mut gpui::ViewContext<Self>,
) {
}
@@ -38,7 +38,7 @@ use fs::RealFs;
use settings::watched_json::{watch_keymap_file, watch_settings_file, WatchedJsonFile};
use theme::ThemeRegistry;
use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
-use workspace::{self, AppState, ItemHandle, NewFile, OpenPaths, Workspace};
+use workspace::{self, item::ItemHandle, AppState, NewFile, OpenPaths, Workspace};
use zed::{self, build_window_options, initialize_workspace, languages, menus};
fn main() {
@@ -625,7 +625,8 @@ mod tests {
};
use theme::ThemeRegistry;
use workspace::{
- open_paths, pane, Item, ItemHandle, NewFile, Pane, SplitDirection, WorkspaceHandle,
+ item::{Item, ItemHandle},
+ open_paths, pane, NewFile, Pane, SplitDirection, WorkspaceHandle,
};
#[gpui::test]