This commit is contained in:
Kay Simmons 2022-11-14 17:31:12 -08:00 committed by Mikayla Maki
parent 4798161118
commit c1f7902309
22 changed files with 1114 additions and 961 deletions

View File

@ -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]);

View File

@ -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,

View File

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

View File

@ -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)
}

View File

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

View File

@ -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);

View File

@ -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),

View File

@ -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)]

View File

@ -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();

View File

@ -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};

View File

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

View File

@ -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,

View File

@ -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 {}
}

View File

@ -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;

View File

@ -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,
}

View File

@ -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]

View File

@ -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 {

View File

@ -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"))
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -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]