Merge branch 'main' into joseph/z-226-add-terminal-popup-menu

This commit is contained in:
Petros Amoiridis 2023-03-11 16:42:42 +02:00
commit bccc34c61a
No known key found for this signature in database
159 changed files with 5447 additions and 1711 deletions

View file

@ -13,7 +13,7 @@ use gpui::{
use settings::{DockAnchor, Settings};
use theme::Theme;
use crate::{sidebar::SidebarSide, ItemHandle, Pane, Workspace};
use crate::{sidebar::SidebarSide, BackgroundActions, ItemHandle, Pane, Workspace};
pub use toggle_dock_button::ToggleDockButton;
#[derive(PartialEq, Clone, Deserialize)]
@ -39,20 +39,24 @@ impl_internal_actions!(dock, [MoveDock, AddDefaultItemToDock]);
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Dock::focus_dock);
cx.add_action(Dock::hide_dock);
cx.add_action(Dock::move_dock);
cx.add_action(
|workspace: &mut Workspace, &MoveDock(dock_anchor), cx: &mut ViewContext<Workspace>| {
Dock::move_dock(workspace, dock_anchor, true, cx);
},
);
cx.add_action(
|workspace: &mut Workspace, _: &AnchorDockRight, cx: &mut ViewContext<Workspace>| {
Dock::move_dock(workspace, &MoveDock(DockAnchor::Right), cx)
Dock::move_dock(workspace, DockAnchor::Right, true, cx);
},
);
cx.add_action(
|workspace: &mut Workspace, _: &AnchorDockBottom, cx: &mut ViewContext<Workspace>| {
Dock::move_dock(workspace, &MoveDock(DockAnchor::Bottom), cx)
Dock::move_dock(workspace, DockAnchor::Bottom, true, cx)
},
);
cx.add_action(
|workspace: &mut Workspace, _: &ExpandDock, cx: &mut ViewContext<Workspace>| {
Dock::move_dock(workspace, &MoveDock(DockAnchor::Expanded), cx)
Dock::move_dock(workspace, DockAnchor::Expanded, true, cx)
},
);
cx.add_action(
@ -177,12 +181,21 @@ pub struct Dock {
impl Dock {
pub fn new(
workspace_id: usize,
default_item_factory: DockDefaultItemFactory,
background_actions: BackgroundActions,
cx: &mut ViewContext<Workspace>,
) -> Self {
let position = DockPosition::Hidden(cx.global::<Settings>().default_dock_anchor);
let pane = cx.add_view(|cx| Pane::new(Some(position.anchor()), cx));
let pane = cx.add_view(|cx| {
Pane::new(
workspace_id,
Some(position.anchor()),
background_actions,
cx,
)
});
pane.update(cx, |pane, cx| {
pane.set_active(false, cx);
});
@ -215,6 +228,7 @@ impl Dock {
pub(crate) fn set_dock_position(
workspace: &mut Workspace,
new_position: DockPosition,
focus: bool,
cx: &mut ViewContext<Workspace>,
) {
workspace.dock.position = new_position;
@ -235,19 +249,23 @@ impl Dock {
let pane = workspace.dock.pane.clone();
if pane.read(cx).items().next().is_none() {
if let Some(item_to_add) = (workspace.dock.default_item_factory)(workspace, cx) {
Pane::add_item(workspace, &pane, item_to_add, true, true, None, cx);
Pane::add_item(workspace, &pane, item_to_add, focus, focus, None, cx);
} else {
workspace.dock.position = workspace.dock.position.hide();
}
} else {
cx.focus(pane);
if focus {
cx.focus(pane);
}
}
} else if let Some(last_active_center_pane) = workspace
.last_active_center_pane
.as_ref()
.and_then(|pane| pane.upgrade(cx))
{
cx.focus(last_active_center_pane);
if focus {
cx.focus(last_active_center_pane);
}
}
cx.emit(crate::Event::DockAnchorChanged);
workspace.serialize_workspace(cx);
@ -255,11 +273,11 @@ impl Dock {
}
pub fn hide(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
Self::set_dock_position(workspace, workspace.dock.position.hide(), cx);
Self::set_dock_position(workspace, workspace.dock.position.hide(), true, cx);
}
pub fn show(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
Self::set_dock_position(workspace, workspace.dock.position.show(), cx);
pub fn show(workspace: &mut Workspace, focus: bool, cx: &mut ViewContext<Workspace>) {
Self::set_dock_position(workspace, workspace.dock.position.show(), focus, cx);
}
pub fn hide_on_sidebar_shown(
@ -275,19 +293,20 @@ impl Dock {
}
fn focus_dock(workspace: &mut Workspace, _: &FocusDock, cx: &mut ViewContext<Workspace>) {
Self::set_dock_position(workspace, workspace.dock.position.show(), cx);
Self::set_dock_position(workspace, workspace.dock.position.show(), true, cx);
}
fn hide_dock(workspace: &mut Workspace, _: &HideDock, cx: &mut ViewContext<Workspace>) {
Self::set_dock_position(workspace, workspace.dock.position.hide(), cx);
Self::set_dock_position(workspace, workspace.dock.position.hide(), true, cx);
}
fn move_dock(
pub fn move_dock(
workspace: &mut Workspace,
&MoveDock(new_anchor): &MoveDock,
new_anchor: DockAnchor,
focus: bool,
cx: &mut ViewContext<Workspace>,
) {
Self::set_dock_position(workspace, DockPosition::Shown(new_anchor), cx);
Self::set_dock_position(workspace, DockPosition::Shown(new_anchor), focus, cx);
}
pub fn render(
@ -482,6 +501,7 @@ mod tests {
0,
project.clone(),
default_item_factory,
|| &[],
cx,
)
});
@ -610,7 +630,14 @@ mod tests {
cx.update(|cx| init(cx));
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, default_item_factory, cx)
Workspace::new(
Default::default(),
0,
project,
default_item_factory,
|| &[],
cx,
)
});
workspace.update(cx, |workspace, cx| {

View file

@ -42,6 +42,7 @@ impl View for ToggleDockButton {
let workspace = workspace.unwrap();
let dock_position = workspace.read(cx).dock.position;
let dock_pane = workspace.read(cx.app).dock_pane().clone();
let theme = cx.global::<Settings>().theme.clone();
@ -67,7 +68,6 @@ impl View for ToggleDockButton {
})
.with_cursor_style(CursorStyle::PointingHand)
.on_up(MouseButton::Left, move |event, cx| {
let dock_pane = workspace.read(cx.app).dock_pane();
let drop_index = dock_pane.read(cx.app).items_len() + 1;
handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx);
});

View file

@ -151,6 +151,9 @@ pub trait Item: View {
"deserialize() must be implemented if serialized_item_kind() returns Some(_)"
)
}
fn show_toolbar(&self) -> bool {
true
}
}
pub trait ItemHandle: 'static + fmt::Debug {
@ -213,6 +216,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>>;
fn serialized_item_kind(&self) -> Option<&'static str>;
fn show_toolbar(&self, cx: &AppContext) -> bool;
}
pub trait WeakItemHandle {
@ -591,6 +595,10 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
fn serialized_item_kind(&self) -> Option<&'static str> {
T::serialized_item_kind()
}
fn show_toolbar(&self, cx: &AppContext) -> bool {
self.read(cx).show_toolbar()
}
}
impl From<Box<dyn ItemHandle>> for AnyViewHandle {

View file

@ -122,6 +122,8 @@ impl Workspace {
pub mod simple_message_notification {
use std::borrow::Cow;
use gpui::{
actions,
elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text},
@ -153,9 +155,9 @@ pub mod simple_message_notification {
}
pub struct MessageNotification {
message: String,
message: Cow<'static, str>,
click_action: Option<Box<dyn Action>>,
click_message: Option<String>,
click_message: Option<Cow<'static, str>>,
}
pub enum MessageNotificationEvent {
@ -167,23 +169,23 @@ pub mod simple_message_notification {
}
impl MessageNotification {
pub fn new_message<S: AsRef<str>>(message: S) -> MessageNotification {
pub fn new_message<S: Into<Cow<'static, str>>>(message: S) -> MessageNotification {
Self {
message: message.as_ref().to_string(),
message: message.into(),
click_action: None,
click_message: None,
}
}
pub fn new<S1: AsRef<str>, A: Action, S2: AsRef<str>>(
pub fn new<S1: Into<Cow<'static, str>>, A: Action, S2: Into<Cow<'static, str>>>(
message: S1,
click_action: A,
click_message: S2,
) -> Self {
Self {
message: message.as_ref().to_string(),
message: message.into(),
click_action: Some(Box::new(click_action) as Box<dyn Action>),
click_message: Some(click_message.as_ref().to_string()),
click_message: Some(click_message.into()),
}
}
@ -210,6 +212,8 @@ pub mod simple_message_notification {
let click_message = self.click_message.as_ref().map(|message| message.clone());
let message = self.message.clone();
let has_click_action = click_action.is_some();
MouseEventHandler::<MessageNotificationTag>::new(0, cx, |state, cx| {
Flex::column()
.with_child(
@ -243,6 +247,7 @@ pub mod simple_message_notification {
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(CancelMessageNotification)
})
.with_cursor_style(CursorStyle::PointingHand)
.aligned()
.constrained()
.with_height(
@ -272,12 +277,19 @@ pub mod simple_message_notification {
.contained()
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
// Since we're not using a proper overlay, we have to capture these extra events
.on_down(MouseButton::Left, |_, _| {})
.on_up(MouseButton::Left, |_, _| {})
.on_click(MouseButton::Left, move |_, cx| {
if let Some(click_action) = click_action.as_ref() {
cx.dispatch_any_action(click_action.boxed_clone())
}
})
.with_cursor_style(if has_click_action {
CursorStyle::PointingHand
} else {
CursorStyle::Arrow
})
.boxed()
}
}

View file

@ -24,8 +24,8 @@ use gpui::{
keymap_matcher::KeymapContext,
platform::{CursorStyle, NavigationDirection},
Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
ModelHandle, MouseButton, MutableAppContext, PromptLevel, Quad, RenderContext, Task, View,
ViewContext, ViewHandle, WeakViewHandle,
ModelHandle, MouseButton, MouseRegion, MutableAppContext, PromptLevel, Quad, RenderContext,
Task, View, ViewContext, ViewHandle, WeakViewHandle,
};
use project::{Project, ProjectEntryId, ProjectPath};
use serde::Deserialize;
@ -110,6 +110,8 @@ impl_internal_actions!(
const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
pub type BackgroundActions = fn() -> &'static [(&'static str, &'static dyn Action)];
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
pane.activate_item(action.0, true, true, cx);
@ -215,6 +217,8 @@ pub struct Pane {
toolbar: ViewHandle<Toolbar>,
tab_bar_context_menu: ViewHandle<ContextMenu>,
docked: Option<DockAnchor>,
_background_actions: BackgroundActions,
_workspace_id: usize,
}
pub struct ItemNavHistory {
@ -271,7 +275,12 @@ enum ItemType {
}
impl Pane {
pub fn new(docked: Option<DockAnchor>, cx: &mut ViewContext<Self>) -> Self {
pub fn new(
workspace_id: usize,
docked: Option<DockAnchor>,
background_actions: BackgroundActions,
cx: &mut ViewContext<Self>,
) -> Self {
let handle = cx.weak_handle();
let context_menu = cx.add_view(ContextMenu::new);
Self {
@ -292,6 +301,8 @@ impl Pane {
toolbar: cx.add_view(|_| Toolbar::new(handle)),
tab_bar_context_menu: context_menu,
docked,
_background_actions: background_actions,
_workspace_id: workspace_id,
}
}
@ -1415,6 +1426,14 @@ impl Pane {
.flex(1., false)
.boxed()
}
fn render_blank_pane(&mut self, theme: &Theme, _cx: &mut RenderContext<Self>) -> ElementBox {
let background = theme.workspace.background;
Empty::new()
.contained()
.with_background_color(background)
.boxed()
}
}
impl Entity for Pane {
@ -1485,11 +1504,12 @@ impl View for Pane {
cx,
{
let toolbar = self.toolbar.clone();
let toolbar_hidden = toolbar.read(cx).hidden();
move |_, cx| {
Flex::column()
.with_child(
ChildView::new(&toolbar, cx).expanded().boxed(),
)
.with_children((!toolbar_hidden).then(|| {
ChildView::new(&toolbar, cx).expanded().boxed()
}))
.with_child(
ChildView::new(active_item, cx)
.flex(1., true)
@ -1507,11 +1527,8 @@ impl View for Pane {
enum EmptyPane {}
let theme = cx.global::<Settings>().theme.clone();
dragged_item_receiver::<EmptyPane, _>(0, 0, false, None, cx, |_, _| {
Empty::new()
.contained()
.with_background_color(theme.workspace.background)
.boxed()
dragged_item_receiver::<EmptyPane, _>(0, 0, false, None, cx, |_, cx| {
self.render_blank_pane(&theme, cx)
})
.on_down(MouseButton::Left, |_, cx| {
cx.focus_parent_view();
@ -1705,6 +1722,93 @@ impl NavHistory {
}
}
pub struct PaneBackdrop {
child_view: usize,
child: ElementBox,
}
impl PaneBackdrop {
pub fn new(pane_item_view: usize, child: ElementBox) -> Self {
PaneBackdrop {
child,
child_view: pane_item_view,
}
}
}
impl Element for PaneBackdrop {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
constraint: gpui::SizeConstraint,
cx: &mut gpui::LayoutContext,
) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, cx);
(size, ())
}
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
cx: &mut gpui::PaintContext,
) -> Self::PaintState {
let background = cx.global::<Settings>().theme.editor.background;
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
cx.scene.push_quad(gpui::Quad {
bounds: RectF::new(bounds.origin(), bounds.size()),
background: Some(background),
..Default::default()
});
let child_view_id = self.child_view;
cx.scene.push_mouse_region(
MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
gpui::MouseButton::Left,
move |_, cx| {
let window_id = cx.window_id;
cx.focus(window_id, Some(child_view_id))
},
),
);
cx.paint_layer(Some(bounds), |cx| {
self.child.paint(bounds.origin(), visible_bounds, cx)
})
}
fn rect_for_text_range(
&self,
range_utf16: std::ops::Range<usize>,
_bounds: RectF,
_visible_bounds: RectF,
_layout: &Self::LayoutState,
_paint: &Self::PaintState,
cx: &gpui::MeasurementContext,
) -> Option<RectF> {
self.child.rect_for_text_range(range_utf16, cx)
}
fn debug(
&self,
_bounds: RectF,
_layout: &Self::LayoutState,
_paint: &Self::PaintState,
cx: &gpui::DebugContext,
) -> serde_json::Value {
gpui::json::json!({
"type": "Pane Back Drop",
"view": self.child_view,
"child": self.child.debug(cx),
})
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
@ -1721,9 +1825,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
let (_, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// 1. Add with a destination index
@ -1811,9 +1913,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
let (_, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// 1. Add with a destination index
@ -1889,9 +1989,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
let (_, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// singleton view
@ -2000,8 +2098,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
let (_, workspace) =
cx.add_window(|cx| Workspace::new(None, 0, project, |_, _| unimplemented!(), cx));
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
add_labled_item(&workspace, &pane, "A", cx);

View file

@ -42,6 +42,7 @@ pub enum ToolbarItemLocation {
pub struct Toolbar {
active_pane_item: Option<Box<dyn ItemHandle>>,
hidden: bool,
pane: WeakViewHandle<Pane>,
items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
}
@ -211,6 +212,7 @@ impl Toolbar {
active_pane_item: None,
pane,
items: Default::default(),
hidden: false,
}
}
@ -243,6 +245,12 @@ impl Toolbar {
cx: &mut ViewContext<Self>,
) {
self.active_pane_item = pane_item.map(|item| item.boxed_clone());
self.hidden = self
.active_pane_item
.as_ref()
.map(|item| !item.show_toolbar(cx))
.unwrap_or(false);
for (toolbar_item, current_location) in self.items.iter_mut() {
let new_location = toolbar_item.set_active_pane_item(pane_item, cx);
if new_location != *current_location {
@ -257,6 +265,10 @@ impl Toolbar {
.iter()
.find_map(|(item, _)| item.to_any().downcast())
}
pub fn hidden(&self) -> bool {
self.hidden
}
}
impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {

View file

@ -16,7 +16,7 @@ mod toolbar;
pub use smallvec;
use anyhow::{anyhow, Result};
use anyhow::{anyhow, Context, Result};
use call::ActiveCall;
use client::{
proto::{self, PeerId},
@ -43,7 +43,8 @@ use gpui::{
platform::{CursorStyle, WindowOptions},
AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
MouseButton, MutableAppContext, PathPromptOptions, Platform, PromptLevel, RenderContext,
SizeConstraint, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds,
SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
WindowBounds,
};
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
use language::LanguageRegistry;
@ -63,7 +64,7 @@ use crate::{
};
use lazy_static::lazy_static;
use log::{error, warn};
use notifications::NotificationHandle;
use notifications::{NotificationHandle, NotifyResultExt};
pub use pane::*;
pub use pane_group::*;
use persistence::{model::SerializedItem, DB};
@ -116,7 +117,8 @@ actions!(
NewTerminal,
NewSearch,
Feedback,
Restart
Restart,
Welcome
]
);
@ -185,21 +187,66 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
dock::init(cx);
notifications::init(cx);
cx.add_global_action(open);
cx.add_global_action(|_: &Open, cx: &mut MutableAppContext| {
let mut paths = cx.prompt_for_paths(PathPromptOptions {
files: true,
directories: true,
multiple: true,
});
cx.spawn(|mut cx| async move {
if let Some(paths) = paths.recv().await.flatten() {
cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));
}
})
.detach();
});
cx.add_action(|_, _: &Open, cx: &mut ViewContext<Workspace>| {
let mut paths = cx.prompt_for_paths(PathPromptOptions {
files: true,
directories: true,
multiple: true,
});
let handle = cx.handle().downgrade();
cx.spawn(|_, mut cx| async move {
if let Some(paths) = paths.recv().await.flatten() {
cx.update(|cx| {
cx.dispatch_action_at(handle.window_id(), handle.id(), OpenPaths { paths })
})
}
})
.detach();
});
cx.add_global_action({
let app_state = Arc::downgrade(&app_state);
move |action: &OpenPaths, cx: &mut MutableAppContext| {
if let Some(app_state) = app_state.upgrade() {
open_paths(&action.paths, &app_state, cx).detach();
open_paths(&action.paths, &app_state, None, cx).detach();
}
}
});
cx.add_global_action({
cx.add_async_action({
let app_state = Arc::downgrade(&app_state);
move |_: &NewFile, cx: &mut MutableAppContext| {
if let Some(app_state) = app_state.upgrade() {
open_new(&app_state, cx).detach();
move |workspace, action: &OpenPaths, cx: &mut ViewContext<Workspace>| {
if !workspace.project().read(cx).is_local() {
cx.propagate_action();
return None;
}
let app_state = app_state.upgrade()?;
let window_id = cx.window_id();
let action = action.clone();
let close = workspace.prepare_to_close(false, cx);
Some(cx.spawn_weak(|_, mut cx| async move {
let can_close = close.await?;
if can_close {
cx.update(|cx| open_paths(&action.paths, &app_state, Some(window_id), cx))
.await;
}
Ok(())
}))
}
});
@ -207,7 +254,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
let app_state = Arc::downgrade(&app_state);
move |_: &NewWindow, cx: &mut MutableAppContext| {
if let Some(app_state) = app_state.upgrade() {
open_new(&app_state, cx).detach();
open_new(&app_state, cx, |_, cx| cx.dispatch_action(NewFile)).detach();
}
}
});
@ -273,6 +320,31 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
},
);
cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
cx.spawn(|workspace, mut cx| async move {
let err = install_cli::install_cli(&cx)
.await
.context("Failed to create CLI symlink");
cx.update(|cx| {
workspace.update(cx, |workspace, cx| {
if matches!(err, Err(_)) {
err.notify_err(workspace, cx);
} else {
workspace.show_notification(1, cx, |cx| {
cx.add_view(|_| {
MessageNotification::new_message(
"Successfully installed the `zed` binary",
)
})
});
}
})
})
})
.detach();
});
let client = &app_state.client;
client.add_view_request_handler(Workspace::handle_follow);
client.add_view_message_handler(Workspace::handle_unfollow);
@ -358,6 +430,7 @@ pub struct AppState {
fn(Option<WindowBounds>, Option<uuid::Uuid>, &dyn Platform) -> WindowOptions<'static>,
pub initialize_workspace: fn(&mut Workspace, &Arc<AppState>, &mut ViewContext<Workspace>),
pub dock_default_item_factory: DockDefaultItemFactory,
pub background_actions: BackgroundActions,
}
impl AppState {
@ -380,7 +453,8 @@ impl AppState {
user_store,
initialize_workspace: |_, _, _| {},
build_window_options: |_, _, _| Default::default(),
dock_default_item_factory: |_, _| unimplemented!(),
dock_default_item_factory: |_, _| None,
background_actions: || &[],
})
}
}
@ -468,6 +542,8 @@ pub struct Workspace {
active_call: Option<(ModelHandle<ActiveCall>, Vec<gpui::Subscription>)>,
leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
database_id: WorkspaceId,
background_actions: BackgroundActions,
_window_subscriptions: [Subscription; 3],
_apply_leader_updates: Task<Result<()>>,
_observe_current_user: Task<()>,
}
@ -497,12 +573,9 @@ impl Workspace {
workspace_id: WorkspaceId,
project: ModelHandle<Project>,
dock_default_factory: DockDefaultItemFactory,
background_actions: BackgroundActions,
cx: &mut ViewContext<Self>,
) -> Self {
cx.observe_fullscreen(|_, _, cx| cx.notify()).detach();
cx.observe_window_activation(Self::on_window_activation_changed)
.detach();
cx.observe(&project, |_, _, cx| cx.notify()).detach();
cx.subscribe(&project, move |this, _, event, cx| {
match event {
@ -531,7 +604,10 @@ impl Workspace {
})
.detach();
let center_pane = cx.add_view(|cx| Pane::new(None, cx));
let weak_handle = cx.weak_handle();
let center_pane =
cx.add_view(|cx| Pane::new(weak_handle.id(), None, background_actions, cx));
let pane_id = center_pane.id();
cx.subscribe(&center_pane, move |this, _, event, cx| {
this.handle_pane_event(pane_id, event, cx)
@ -539,7 +615,12 @@ impl Workspace {
.detach();
cx.focus(&center_pane);
cx.emit(Event::PaneAdded(center_pane.clone()));
let dock = Dock::new(dock_default_factory, cx);
let dock = Dock::new(
weak_handle.id(),
dock_default_factory,
background_actions,
cx,
);
let dock_pane = dock.pane().clone();
let fs = project.read(cx).fs().clone();
@ -562,7 +643,6 @@ impl Workspace {
}
});
let handle = cx.handle();
let weak_handle = cx.weak_handle();
// All leader updates are enqueued and then processed in a single task, so
// that each asynchronous operation can be run in order.
@ -607,6 +687,28 @@ impl Workspace {
active_call = Some((call, subscriptions));
}
let subscriptions = [
cx.observe_fullscreen(|_, _, cx| cx.notify()),
cx.observe_window_activation(Self::on_window_activation_changed),
cx.observe_window_bounds(move |_, mut bounds, display, cx| {
// Transform fixed bounds to be stored in terms of the containing display
if let WindowBounds::Fixed(mut window_bounds) = bounds {
if let Some(screen) = cx.platform().screen_by_id(display) {
let screen_bounds = screen.bounds();
window_bounds
.set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
window_bounds
.set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
bounds = WindowBounds::Fixed(window_bounds);
}
}
cx.background()
.spawn(DB.set_window_bounds(workspace_id, bounds, display))
.detach_and_log_err(cx);
}),
];
let mut this = Workspace {
modal: None,
weak_self: weak_handle.clone(),
@ -635,9 +737,11 @@ impl Workspace {
window_edited: false,
active_call,
database_id: workspace_id,
background_actions,
_observe_current_user,
_apply_leader_updates,
leader_updates_tx,
_window_subscriptions: subscriptions,
};
this.project_remote_id_changed(project.read(cx).remote_id(), cx);
cx.defer(|this, cx| this.update_window_title(cx));
@ -646,6 +750,10 @@ impl Workspace {
cx.defer(move |_, cx| {
Self::load_from_serialized_workspace(weak_handle, serialized_workspace, cx)
});
} else {
if cx.global::<Settings>().default_dock_anchor != DockAnchor::Expanded {
Dock::show(&mut this, false, cx);
}
}
this
@ -654,6 +762,7 @@ impl Workspace {
fn new_local(
abs_paths: Vec<PathBuf>,
app_state: Arc<AppState>,
requesting_window_id: Option<usize>,
cx: &mut MutableAppContext,
) -> Task<(
ViewHandle<Workspace>,
@ -709,73 +818,65 @@ impl Workspace {
))
});
let (bounds, display) = if let Some(bounds) = window_bounds_override {
(Some(bounds), None)
} else {
serialized_workspace
.as_ref()
.and_then(|serialized_workspace| {
let display = serialized_workspace.display?;
let mut bounds = serialized_workspace.bounds?;
// Stored bounds are relative to the containing display.
// So convert back to global coordinates if that screen still exists
if let WindowBounds::Fixed(mut window_bounds) = bounds {
if let Some(screen) = cx.platform().screen_by_id(display) {
let screen_bounds = screen.bounds();
window_bounds.set_origin_x(
window_bounds.origin_x() + screen_bounds.origin_x(),
);
window_bounds.set_origin_y(
window_bounds.origin_y() + screen_bounds.origin_y(),
);
bounds = WindowBounds::Fixed(window_bounds);
} else {
// Screen no longer exists. Return none here.
return None;
}
}
Some((bounds, display))
})
.unzip()
};
// Use the serialized workspace to construct the new window
let (_, workspace) = cx.add_window(
(app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
|cx| {
let build_workspace =
|cx: &mut ViewContext<Workspace>,
serialized_workspace: Option<SerializedWorkspace>| {
let mut workspace = Workspace::new(
serialized_workspace,
workspace_id,
project_handle,
app_state.dock_default_item_factory,
app_state.background_actions,
cx,
);
(app_state.initialize_workspace)(&mut workspace, &app_state, cx);
cx.observe_window_bounds(move |_, mut bounds, display, cx| {
// Transform fixed bounds to be stored in terms of the containing display
if let WindowBounds::Fixed(mut window_bounds) = bounds {
if let Some(screen) = cx.platform().screen_by_id(display) {
let screen_bounds = screen.bounds();
window_bounds.set_origin_x(
window_bounds.origin_x() - screen_bounds.origin_x(),
);
window_bounds.set_origin_y(
window_bounds.origin_y() - screen_bounds.origin_y(),
);
bounds = WindowBounds::Fixed(window_bounds);
}
}
cx.background()
.spawn(DB.set_window_bounds(workspace_id, bounds, display))
.detach_and_log_err(cx);
})
.detach();
workspace
},
);
};
let workspace = if let Some(window_id) = requesting_window_id {
cx.update(|cx| {
cx.replace_root_view(window_id, |cx| build_workspace(cx, serialized_workspace))
})
} else {
let (bounds, display) = if let Some(bounds) = window_bounds_override {
(Some(bounds), None)
} else {
serialized_workspace
.as_ref()
.and_then(|serialized_workspace| {
let display = serialized_workspace.display?;
let mut bounds = serialized_workspace.bounds?;
// Stored bounds are relative to the containing display.
// So convert back to global coordinates if that screen still exists
if let WindowBounds::Fixed(mut window_bounds) = bounds {
if let Some(screen) = cx.platform().screen_by_id(display) {
let screen_bounds = screen.bounds();
window_bounds.set_origin_x(
window_bounds.origin_x() + screen_bounds.origin_x(),
);
window_bounds.set_origin_y(
window_bounds.origin_y() + screen_bounds.origin_y(),
);
bounds = WindowBounds::Fixed(window_bounds);
} else {
// Screen no longer exists. Return none here.
return None;
}
}
Some((bounds, display))
})
.unzip()
};
// Use the serialized workspace to construct the new window
cx.add_window(
(app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
|cx| build_workspace(cx, serialized_workspace),
)
.1
};
notify_if_database_failed(&workspace, &mut cx);
@ -871,7 +972,7 @@ impl Workspace {
if self.project.read(cx).is_local() {
Task::Ready(Some(callback(self, cx)))
} else {
let task = Self::new_local(Vec::new(), app_state.clone(), cx);
let task = Self::new_local(Vec::new(), app_state.clone(), None, cx);
cx.spawn(|_vh, mut cx| async move {
let (workspace, _) = task.await;
workspace.update(&mut cx, callback)
@ -1340,7 +1441,8 @@ impl Workspace {
}
fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
let pane = cx.add_view(|cx| Pane::new(None, cx));
let pane =
cx.add_view(|cx| Pane::new(self.weak_handle().id(), None, self.background_actions, cx));
let pane_id = pane.id();
cx.subscribe(&pane, move |this, _, event, cx| {
this.handle_pane_event(pane_id, event, cx)
@ -1352,6 +1454,23 @@ impl Workspace {
pane
}
pub fn add_item_to_center(
&mut self,
item: Box<dyn ItemHandle>,
cx: &mut ViewContext<Self>,
) -> bool {
if let Some(center_pane) = self.last_active_center_pane.clone() {
if let Some(center_pane) = center_pane.upgrade(cx) {
Pane::add_item(self, &center_pane, item, true, true, None, cx);
true
} else {
false
}
} else {
false
}
}
pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
let active_pane = self.active_pane().clone();
Pane::add_item(self, &active_pane, item, true, true, None, cx);
@ -1509,7 +1628,7 @@ impl Workspace {
self.active_item_path_changed(cx);
if &pane == self.dock_pane() {
Dock::show(self, cx);
Dock::show(self, true, cx);
} else {
self.last_active_center_pane = Some(pane.downgrade());
if self.dock.is_anchored_at(DockAnchor::Expanded) {
@ -2522,7 +2641,12 @@ impl Workspace {
// the focus the dock generates start generating alternating
// focus due to the deferred execution each triggering each other
cx.after_window_update(move |workspace, cx| {
Dock::set_dock_position(workspace, serialized_workspace.dock_position, cx);
Dock::set_dock_position(
workspace,
serialized_workspace.dock_position,
true,
cx,
);
});
cx.notify();
@ -2534,6 +2658,11 @@ impl Workspace {
})
.detach();
}
#[cfg(any(test, feature = "test-support"))]
pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
Self::new(None, 0, project, |_, _| None, || &[], cx)
}
}
fn notify_if_database_failed(workspace: &ViewHandle<Workspace>, cx: &mut AsyncAppContext) {
@ -2765,20 +2894,6 @@ impl std::fmt::Debug for OpenPaths {
}
}
fn open(_: &Open, cx: &mut MutableAppContext) {
let mut paths = cx.prompt_for_paths(PathPromptOptions {
files: true,
directories: true,
multiple: true,
});
cx.spawn(|mut cx| async move {
if let Some(paths) = paths.recv().await.flatten() {
cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));
}
})
.detach();
}
pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
pub fn activate_workspace_for_project(
@ -2805,6 +2920,7 @@ pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
pub fn open_paths(
abs_paths: &[PathBuf],
app_state: &Arc<AppState>,
requesting_window_id: Option<usize>,
cx: &mut MutableAppContext,
) -> Task<(
ViewHandle<Workspace>,
@ -2835,7 +2951,8 @@ pub fn open_paths(
.contains(&false);
cx.update(|cx| {
let task = Workspace::new_local(abs_paths, app_state.clone(), cx);
let task =
Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx);
cx.spawn(|mut cx| async move {
let (workspace, items) = task.await;
@ -2854,14 +2971,18 @@ pub fn open_paths(
})
}
pub fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) -> Task<()> {
let task = Workspace::new_local(Vec::new(), app_state.clone(), cx);
pub fn open_new(
app_state: &Arc<AppState>,
cx: &mut MutableAppContext,
init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
) -> Task<()> {
let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
cx.spawn(|mut cx| async move {
let (workspace, opened_paths) = task.await;
workspace.update(&mut cx, |_, cx| {
workspace.update(&mut cx, |workspace, cx| {
if opened_paths.is_empty() {
cx.dispatch_action(NewFile);
init(workspace, cx)
}
})
})
@ -2882,17 +3003,10 @@ mod tests {
use super::*;
use fs::FakeFs;
use gpui::{executor::Deterministic, TestAppContext, ViewContext};
use gpui::{executor::Deterministic, TestAppContext};
use project::{Project, ProjectEntryId};
use serde_json::json;
pub fn default_item_factory(
_workspace: &mut Workspace,
_cx: &mut ViewContext<Workspace>,
) -> Option<Box<dyn ItemHandle>> {
unimplemented!()
}
#[gpui::test]
async fn test_tab_disambiguation(cx: &mut TestAppContext) {
cx.foreground().forbid_parking();
@ -2905,7 +3019,8 @@ mod tests {
Default::default(),
0,
project.clone(),
default_item_factory,
|_, _| None,
|| &[],
cx,
)
});
@ -2977,7 +3092,8 @@ mod tests {
Default::default(),
0,
project.clone(),
default_item_factory,
|_, _| None,
|| &[],
cx,
)
});
@ -3077,7 +3193,8 @@ mod tests {
Default::default(),
0,
project.clone(),
default_item_factory,
|_, _| None,
|| &[],
cx,
)
});
@ -3116,7 +3233,7 @@ mod tests {
let project = Project::test(fs, None, cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, default_item_factory, cx)
Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
});
let item1 = cx.add_view(&workspace, |cx| {
@ -3225,7 +3342,7 @@ mod tests {
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, default_item_factory, cx)
Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
});
// Create several workspace items with single project entries, and two
@ -3334,7 +3451,7 @@ mod tests {
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, default_item_factory, cx)
Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
});
let item = cx.add_view(&workspace, |cx| {
@ -3453,7 +3570,7 @@ mod tests {
let project = Project::test(fs, [], cx).await;
let (_, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, default_item_factory, cx)
Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
});
let item = cx.add_view(&workspace, |cx| {