Remove dock from workspace

This commit is contained in:
Nathan Sobo 2023-05-05 15:02:26 -06:00 committed by Antonio Scandurra
parent 2d7cfb8c7c
commit 1919a826f9
18 changed files with 92 additions and 1461 deletions

View file

@ -62,9 +62,5 @@
"ctrl-f": "project_panel::ExpandSelectedEntry",
"ctrl-shift-c": "project_panel::CopyPath"
}
},
{
"context": "Dock",
"bindings": {}
}
]

View file

@ -443,32 +443,6 @@
"cmd-enter": "project_search::SearchInNew"
}
},
{
"context": "Workspace",
"bindings": {
"shift-escape": "dock::FocusDock"
}
},
{
"bindings": {
"cmd-shift-k cmd-shift-right": "dock::AnchorDockRight",
"cmd-shift-k cmd-shift-down": "dock::AnchorDockBottom",
"cmd-shift-k cmd-shift-up": "dock::ExpandDock"
}
},
{
"context": "Pane",
"bindings": {
"cmd-escape": "dock::AddTabToDock"
}
},
{
"context": "Pane && docked",
"bindings": {
"shift-escape": "dock::HideDock",
"cmd-escape": "dock::RemoveTabFromDock"
}
},
{
"context": "ProjectPanel",
"bindings": {

View file

@ -66,14 +66,7 @@
"cmd-shift-a": "command_palette::Toggle",
"cmd-alt-o": "project_symbols::Toggle",
"cmd-1": "workspace::ToggleLeftSidebar",
"cmd-6": "diagnostics::Deploy",
"alt-f12": "dock::FocusDock"
}
},
{
"context": "Dock",
"bindings": {
"alt-f12": "dock::HideDock"
"cmd-6": "diagnostics::Deploy"
}
}
]

View file

@ -45,18 +45,11 @@
{
"context": "Workspace",
"bindings": {
"ctrl-`": "dock::FocusDock",
"cmd-k cmd-b": "workspace::ToggleLeftSidebar",
"cmd-t": "file_finder::Toggle",
"shift-cmd-r": "project_symbols::Toggle",
// Currently busted: https://github.com/zed-industries/feedback/issues/898
"ctrl-0": "project_panel::ToggleFocus"
}
},
{
"context": "Dock",
"bindings": {
"ctrl-`": "dock::HideDock"
}
}
]

View file

@ -83,9 +83,5 @@
{
"context": "ProjectPanel",
"bindings": {}
},
{
"context": "Dock",
"bindings": {}
}
]

View file

@ -59,16 +59,6 @@
// 4. Save when idle for a certain amount of time:
// "autosave": { "after_delay": {"milliseconds": 500} },
"autosave": "off",
// Where to place the dock by default. This setting can take three
// values:
//
// 1. Position the dock attached to the bottom of the workspace
// "default_dock_anchor": "bottom"
// 2. Position the dock to the right of the workspace like a side panel
// "default_dock_anchor": "right"
// 3. Position the dock full screen over the entire workspace"
// "default_dock_anchor": "expanded"
"default_dock_anchor": "bottom",
// Whether or not to remove any trailing whitespace from lines of a buffer
// before saving it.
"remove_trailing_whitespace_on_save": true,

View file

@ -195,7 +195,6 @@ impl TestServer {
fs: fs.clone(),
build_window_options: |_, _, _| Default::default(),
initialize_workspace: |_, _, _| unimplemented!(),
dock_default_item_factory: |_, _| None,
background_actions: || &[],
});

View file

@ -2,7 +2,7 @@ mod keymap_file;
pub mod settings_file;
pub mod watched_json;
use anyhow::{bail, Result};
use anyhow::Result;
use gpui::{
font_cache::{FamilyId, FontCache},
fonts, AssetSource,
@ -15,10 +15,6 @@ use schemars::{
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::Value;
use sqlez::{
bindable::{Bind, Column, StaticColumnCount},
statement::Statement,
};
use std::{
borrow::Cow, collections::HashMap, num::NonZeroU32, ops::Range, path::Path, str, sync::Arc,
};
@ -48,7 +44,6 @@ pub struct Settings {
pub show_call_status_icon: bool,
pub vim_mode: bool,
pub autosave: Autosave,
pub default_dock_anchor: DockAnchor,
pub editor_defaults: EditorSettings,
pub editor_overrides: EditorSettings,
pub git: GitSettings,
@ -340,43 +335,6 @@ impl TerminalSettings {
}
}
#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum DockAnchor {
#[default]
Bottom,
Right,
Expanded,
}
impl StaticColumnCount for DockAnchor {}
impl Bind for DockAnchor {
fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
match self {
DockAnchor::Bottom => "Bottom",
DockAnchor::Right => "Right",
DockAnchor::Expanded => "Expanded",
}
.bind(statement, start_index)
}
}
impl Column for DockAnchor {
fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
String::column(statement, start_index).and_then(|(anchor_text, next_index)| {
Ok((
match anchor_text.as_ref() {
"Bottom" => DockAnchor::Bottom,
"Right" => DockAnchor::Right,
"Expanded" => DockAnchor::Expanded,
_ => bail!("Stored dock anchor is incorrect"),
},
next_index,
))
})
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct SettingsFileContent {
#[serde(default)]
@ -403,8 +361,6 @@ pub struct SettingsFileContent {
pub vim_mode: Option<bool>,
#[serde(default)]
pub autosave: Option<Autosave>,
#[serde(default)]
pub default_dock_anchor: Option<DockAnchor>,
#[serde(flatten)]
pub editor: EditorSettings,
#[serde(default)]
@ -501,7 +457,6 @@ impl Settings {
show_call_status_icon: defaults.show_call_status_icon.unwrap(),
vim_mode: defaults.vim_mode.unwrap(),
autosave: defaults.autosave.unwrap(),
default_dock_anchor: defaults.default_dock_anchor.unwrap(),
editor_defaults: EditorSettings {
tab_size: required(defaults.editor.tab_size),
hard_tabs: required(defaults.editor.hard_tabs),
@ -596,7 +551,6 @@ impl Settings {
);
merge(&mut self.vim_mode, data.vim_mode);
merge(&mut self.autosave, data.autosave);
merge(&mut self.default_dock_anchor, data.default_dock_anchor);
merge(&mut self.base_keymap, data.base_keymap);
merge(&mut self.features.copilot, data.features.copilot);
@ -796,7 +750,6 @@ impl Settings {
show_call_status_icon: true,
vim_mode: false,
autosave: Autosave::Off,
default_dock_anchor: DockAnchor::Bottom,
editor_defaults: EditorSettings {
tab_size: Some(4.try_into().unwrap()),
hard_tabs: Some(false),

View file

@ -8,7 +8,6 @@ use gpui::{
use settings::Settings;
use std::any::TypeId;
use workspace::{
dock::{Dock, FocusDock},
item::ItemHandle,
NewTerminal, StatusItemView, Workspace,
};
@ -85,8 +84,8 @@ impl View for TerminalButton {
} else {
if !active {
if let Some(workspace) = this.workspace.upgrade(cx) {
workspace.update(cx, |workspace, cx| {
Dock::focus_dock(workspace, &Default::default(), cx)
workspace.update(cx, |_workspace, _cx| {
todo!()
})
}
}
@ -95,7 +94,7 @@ impl View for TerminalButton {
.with_tooltip::<Self>(
0,
"Show Terminal".into(),
Some(Box::new(FocusDock)),
None, // TODO! We need a new action here.
theme.tooltip.clone(),
cx,
),

View file

@ -73,7 +73,6 @@ pub struct Workspace {
pub joining_project_avatar: ImageStyle,
pub joining_project_message: ContainedText,
pub external_location_message: ContainedText,
pub dock: Dock,
pub drop_target_overlay_color: Color,
}
@ -296,15 +295,6 @@ pub struct Toolbar {
pub nav_button: Interactive<IconButton>,
}
#[derive(Clone, Deserialize, Default)]
pub struct Dock {
pub initial_size_right: f32,
pub initial_size_bottom: f32,
pub wash_color: Color,
pub panel: ContainerStyle,
pub maximized: ContainerStyle,
}
#[derive(Clone, Deserialize, Default)]
pub struct Notifications {
#[serde(flatten)]

View file

@ -1,816 +0,0 @@
mod toggle_dock_button;
use collections::HashMap;
use gpui::{
actions,
elements::{ChildView, Empty, MouseEventHandler, ParentElement, Side, Stack},
geometry::vector::Vector2F,
platform::{CursorStyle, MouseButton},
AnyElement, AppContext, Border, Element, SizeConstraint, ViewContext, ViewHandle,
};
use settings::{DockAnchor, Settings};
use theme::Theme;
use crate::{sidebar::SidebarSide, BackgroundActions, ItemHandle, Pane, Workspace};
pub use toggle_dock_button::ToggleDockButton;
actions!(
dock,
[
FocusDock,
HideDock,
AnchorDockRight,
AnchorDockBottom,
ExpandDock,
AddTabToDock,
RemoveTabFromDock,
]
);
pub fn init(cx: &mut AppContext) {
cx.add_action(Dock::focus_dock);
cx.add_action(Dock::hide_dock);
cx.add_action(
|workspace: &mut Workspace, _: &AnchorDockRight, cx: &mut ViewContext<Workspace>| {
Dock::move_dock(workspace, DockAnchor::Right, true, cx);
},
);
cx.add_action(
|workspace: &mut Workspace, _: &AnchorDockBottom, cx: &mut ViewContext<Workspace>| {
Dock::move_dock(workspace, DockAnchor::Bottom, true, cx)
},
);
cx.add_action(
|workspace: &mut Workspace, _: &ExpandDock, cx: &mut ViewContext<Workspace>| {
Dock::move_dock(workspace, DockAnchor::Expanded, true, cx)
},
);
cx.add_action(
|workspace: &mut Workspace, _: &AddTabToDock, cx: &mut ViewContext<Workspace>| {
if let Some(active_item) = workspace.active_item(cx) {
let item_id = active_item.id();
let from = workspace.active_pane();
let to = workspace.dock_pane();
if from.id() == to.id() {
return;
}
let destination_index = to.read(cx).items_len() + 1;
Pane::move_item(
workspace,
from.clone(),
to.clone(),
item_id,
destination_index,
cx,
);
}
},
);
cx.add_action(
|workspace: &mut Workspace, _: &RemoveTabFromDock, cx: &mut ViewContext<Workspace>| {
if let Some(active_item) = workspace.active_item(cx) {
let item_id = active_item.id();
let from = workspace.dock_pane();
let to = workspace
.last_active_center_pane
.as_ref()
.and_then(|pane| pane.upgrade(cx))
.unwrap_or_else(|| {
workspace
.panes
.first()
.expect("There must be a pane")
.clone()
});
if from.id() == to.id() {
return;
}
let destination_index = to.read(cx).items_len() + 1;
Pane::move_item(
workspace,
from.clone(),
to.clone(),
item_id,
destination_index,
cx,
);
}
},
);
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum DockPosition {
Shown(DockAnchor),
Hidden(DockAnchor),
}
impl Default for DockPosition {
fn default() -> Self {
DockPosition::Hidden(Default::default())
}
}
pub fn icon_for_dock_anchor(anchor: DockAnchor) -> &'static str {
match anchor {
DockAnchor::Right => "icons/dock_right_12.svg",
DockAnchor::Bottom => "icons/dock_bottom_12.svg",
DockAnchor::Expanded => "icons/dock_modal_12.svg",
}
}
impl DockPosition {
pub fn is_visible(&self) -> bool {
match self {
DockPosition::Shown(_) => true,
DockPosition::Hidden(_) => false,
}
}
pub fn anchor(&self) -> DockAnchor {
match self {
DockPosition::Shown(anchor) | DockPosition::Hidden(anchor) => *anchor,
}
}
fn hide(self) -> Self {
match self {
DockPosition::Shown(anchor) => DockPosition::Hidden(anchor),
DockPosition::Hidden(_) => self,
}
}
fn show(self) -> Self {
match self {
DockPosition::Hidden(anchor) => DockPosition::Shown(anchor),
DockPosition::Shown(_) => self,
}
}
}
pub type DockDefaultItemFactory =
fn(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Option<Box<dyn ItemHandle>>;
pub struct Dock {
position: DockPosition,
panel_sizes: HashMap<DockAnchor, f32>,
pane: ViewHandle<Pane>,
default_item_factory: DockDefaultItemFactory,
}
impl Dock {
pub fn new(
default_item_factory: DockDefaultItemFactory,
background_actions: BackgroundActions,
cx: &mut ViewContext<Workspace>,
) -> Self {
let position = DockPosition::Hidden(cx.global::<Settings>().default_dock_anchor);
let workspace = cx.weak_handle();
let pane =
cx.add_view(|cx| Pane::new(workspace, Some(position.anchor()), background_actions, cx));
pane.update(cx, |pane, cx| {
pane.set_active(false, cx);
});
cx.subscribe(&pane, Workspace::handle_pane_event).detach();
Self {
pane,
panel_sizes: Default::default(),
position,
default_item_factory,
}
}
pub fn pane(&self) -> &ViewHandle<Pane> {
&self.pane
}
pub fn visible_pane(&self) -> Option<&ViewHandle<Pane>> {
self.position.is_visible().then(|| self.pane())
}
pub fn is_anchored_at(&self, anchor: DockAnchor) -> bool {
self.position.is_visible() && self.position.anchor() == anchor
}
pub(crate) fn set_dock_position(
workspace: &mut Workspace,
new_position: DockPosition,
focus: bool,
cx: &mut ViewContext<Workspace>,
) {
workspace.dock.position = new_position;
// Tell the pane about the new anchor position
workspace.dock.pane.update(cx, |pane, cx| {
pane.set_docked(Some(new_position.anchor()), cx)
});
if workspace.dock.position.is_visible() {
// Close the right sidebar if the dock is on the right side and the right sidebar is open
if workspace.dock.position.anchor() == DockAnchor::Right {
if workspace.right_sidebar().read(cx).is_open() {
workspace.toggle_sidebar(SidebarSide::Right, cx);
}
}
// Ensure that the pane has at least one item or construct a default item to put in it
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, focus, focus, None, cx);
} else {
workspace.dock.position = workspace.dock.position.hide();
}
} else {
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))
{
if focus {
cx.focus(&last_active_center_pane);
}
}
cx.emit(crate::Event::DockAnchorChanged);
workspace.serialize_workspace(cx);
cx.notify();
}
pub fn hide(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
Self::set_dock_position(workspace, workspace.dock.position.hide(), true, 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(
workspace: &mut Workspace,
sidebar_side: SidebarSide,
cx: &mut ViewContext<Workspace>,
) {
if (sidebar_side == SidebarSide::Right && workspace.dock.is_anchored_at(DockAnchor::Right))
|| workspace.dock.is_anchored_at(DockAnchor::Expanded)
{
Self::hide(workspace, cx);
}
}
pub fn focus_dock(workspace: &mut Workspace, _: &FocusDock, cx: &mut ViewContext<Workspace>) {
Self::set_dock_position(workspace, workspace.dock.position.show(), true, cx);
}
pub fn hide_dock(workspace: &mut Workspace, _: &HideDock, cx: &mut ViewContext<Workspace>) {
Self::set_dock_position(workspace, workspace.dock.position.hide(), true, cx);
}
pub fn move_dock(
workspace: &mut Workspace,
new_anchor: DockAnchor,
focus: bool,
cx: &mut ViewContext<Workspace>,
) {
Self::set_dock_position(workspace, DockPosition::Shown(new_anchor), focus, cx);
}
pub fn render(
&self,
theme: &Theme,
anchor: DockAnchor,
cx: &mut ViewContext<Workspace>,
) -> Option<AnyElement<Workspace>> {
let style = &theme.workspace.dock;
self.position
.is_visible()
.then(|| self.position.anchor())
.filter(|current_anchor| *current_anchor == anchor)
.map(|anchor| match anchor {
DockAnchor::Bottom | DockAnchor::Right => {
let mut panel_style = style.panel.clone();
let (resize_side, initial_size) = if anchor == DockAnchor::Bottom {
panel_style.border = Border {
top: true,
bottom: false,
left: false,
right: false,
..panel_style.border
};
(Side::Top, style.initial_size_bottom)
} else {
panel_style.border = Border {
top: false,
bottom: false,
left: true,
right: false,
..panel_style.border
};
(Side::Left, style.initial_size_right)
};
enum DockResizeHandle {}
let resizable = ChildView::new(&self.pane, cx)
.contained()
.with_style(panel_style)
.with_resize_handle::<DockResizeHandle>(
resize_side as usize,
resize_side,
4.,
self.panel_sizes
.get(&anchor)
.copied()
.unwrap_or(initial_size),
cx,
);
let size = resizable.current_size();
cx.defer(move |workspace, _| {
workspace.dock.panel_sizes.insert(anchor, size);
});
if anchor == DockAnchor::Right {
resizable.constrained().dynamically(|constraint, _, cx| {
SizeConstraint::new(
Vector2F::new(20., constraint.min.y()),
Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
)
})
} else {
resizable.constrained().dynamically(|constraint, _, cx| {
SizeConstraint::new(
Vector2F::new(constraint.min.x(), 50.),
Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
)
})
}
.into_any()
}
DockAnchor::Expanded => {
enum ExpandedDockWash {}
enum ExpandedDockPane {}
Stack::new()
.with_child(
// Render wash under the dock which when clicked hides it
MouseEventHandler::<ExpandedDockWash, _>::new(0, cx, |_, _| {
Empty::new()
.contained()
.with_background_color(style.wash_color)
})
.capture_all()
.on_down(MouseButton::Left, |_, workspace, cx| {
Dock::hide_dock(workspace, &Default::default(), cx)
})
.with_cursor_style(CursorStyle::Arrow),
)
.with_child(
MouseEventHandler::<ExpandedDockPane, _>::new(0, cx, |_state, cx| {
ChildView::new(&self.pane, cx)
})
// Make sure all events directly under the dock pane
// are captured
.capture_all()
.contained()
.with_style(style.maximized),
)
.into_any()
}
})
}
pub fn position(&self) -> DockPosition {
self.position
}
}
#[cfg(test)]
mod tests {
use std::{
ops::{Deref, DerefMut},
path::PathBuf,
sync::Arc,
};
use gpui::{AppContext, BorrowWindowContext, TestAppContext, ViewContext, WindowContext};
use project::{FakeFs, Project};
use settings::Settings;
use theme::ThemeRegistry;
use super::*;
use crate::{
dock,
item::{self, test::TestItem},
persistence::model::{
SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
},
register_deserializable_item,
sidebar::Sidebar,
AppState, ItemHandle, Workspace,
};
pub fn default_item_factory(
_workspace: &mut Workspace,
cx: &mut ViewContext<Workspace>,
) -> Option<Box<dyn ItemHandle>> {
Some(Box::new(cx.add_view(|_| TestItem::new())))
}
#[gpui::test]
async fn test_dock_workspace_infinite_loop(cx: &mut TestAppContext) {
cx.foreground().forbid_parking();
Settings::test_async(cx);
cx.update(|cx| {
register_deserializable_item::<item::test::TestItem>(cx);
});
let serialized_workspace = SerializedWorkspace {
id: 0,
location: Vec::<PathBuf>::new().into(),
dock_position: dock::DockPosition::Shown(DockAnchor::Expanded),
center_group: SerializedPaneGroup::Pane(SerializedPane {
active: false,
children: vec![],
}),
dock_pane: SerializedPane {
active: true,
children: vec![SerializedItem {
active: true,
item_id: 0,
kind: "TestItem".into(),
}],
},
left_sidebar_open: false,
bounds: Default::default(),
display: Default::default(),
};
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (_, _workspace) = cx.add_window(|cx| {
Workspace::new(
Some(serialized_workspace),
0,
project.clone(),
Arc::new(AppState {
languages: project.read(cx).languages().clone(),
themes: ThemeRegistry::new((), cx.font_cache().clone()),
client: project.read(cx).client(),
user_store: project.read(cx).user_store(),
fs: project.read(cx).fs().clone(),
build_window_options: |_, _, _| Default::default(),
initialize_workspace: |_, _, _| {},
dock_default_item_factory: default_item_factory,
background_actions: || &[],
}),
cx,
)
});
cx.foreground().run_until_parked();
//Should terminate
}
#[gpui::test]
async fn test_dock_hides_when_pane_empty(cx: &mut TestAppContext) {
let mut cx = DockTestContext::new(cx).await;
// Closing the last item in the dock hides the dock
cx.move_dock(DockAnchor::Right);
let old_items = cx.dock_items();
assert!(!old_items.is_empty());
cx.close_dock_items().await;
cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Right));
// Reopening the dock adds a new item
cx.move_dock(DockAnchor::Right);
let new_items = cx.dock_items();
assert!(!new_items.is_empty());
assert!(new_items
.into_iter()
.all(|new_item| !old_items.contains(&new_item)));
}
#[gpui::test]
async fn test_dock_panel_collisions(cx: &mut TestAppContext) {
let mut cx = DockTestContext::new(cx).await;
// Dock closes when expanded for either panel
cx.move_dock(DockAnchor::Expanded);
cx.open_sidebar(SidebarSide::Left);
cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Expanded));
cx.close_sidebar(SidebarSide::Left);
cx.move_dock(DockAnchor::Expanded);
cx.open_sidebar(SidebarSide::Right);
cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Expanded));
// Dock closes in the right position if the right sidebar is opened
cx.move_dock(DockAnchor::Right);
cx.open_sidebar(SidebarSide::Left);
cx.assert_dock_position(DockPosition::Shown(DockAnchor::Right));
cx.open_sidebar(SidebarSide::Right);
cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Right));
cx.close_sidebar(SidebarSide::Right);
// Dock in bottom position ignores sidebars
cx.move_dock(DockAnchor::Bottom);
cx.open_sidebar(SidebarSide::Left);
cx.open_sidebar(SidebarSide::Right);
cx.assert_dock_position(DockPosition::Shown(DockAnchor::Bottom));
// Opening the dock in the right position closes the right sidebar
cx.move_dock(DockAnchor::Right);
cx.assert_sidebar_closed(SidebarSide::Right);
}
#[gpui::test]
async fn test_focusing_panes_shows_and_hides_dock(cx: &mut TestAppContext) {
let mut cx = DockTestContext::new(cx).await;
// Focusing an item not in the dock when expanded hides the dock
let center_item = cx.add_item_to_center_pane();
cx.move_dock(DockAnchor::Expanded);
let dock_item = cx
.dock_items()
.get(0)
.cloned()
.expect("Dock should have an item at this point");
center_item.update(&mut cx, |_, cx| cx.focus_self());
cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Expanded));
// Focusing an item not in the dock when not expanded, leaves the dock open but inactive
cx.move_dock(DockAnchor::Right);
center_item.update(&mut cx, |_, cx| cx.focus_self());
cx.assert_dock_position(DockPosition::Shown(DockAnchor::Right));
cx.assert_dock_pane_inactive();
cx.assert_workspace_pane_active();
// Focusing an item in the dock activates it's pane
dock_item.update(&mut cx, |_, cx| cx.focus_self());
cx.assert_dock_position(DockPosition::Shown(DockAnchor::Right));
cx.assert_dock_pane_active();
cx.assert_workspace_pane_inactive();
}
#[gpui::test]
async fn test_toggle_dock_focus(cx: &mut TestAppContext) {
let mut cx = DockTestContext::new(cx).await;
cx.move_dock(DockAnchor::Right);
cx.assert_dock_pane_active();
cx.hide_dock();
cx.move_dock(DockAnchor::Right);
cx.assert_dock_pane_active();
}
#[gpui::test]
async fn test_activate_next_and_prev_pane(cx: &mut TestAppContext) {
let mut cx = DockTestContext::new(cx).await;
cx.move_dock(DockAnchor::Right);
cx.assert_dock_pane_active();
cx.update_workspace(|workspace, cx| workspace.activate_next_pane(cx));
cx.assert_dock_pane_active();
cx.update_workspace(|workspace, cx| workspace.activate_previous_pane(cx));
cx.assert_dock_pane_active();
}
struct DockTestContext<'a> {
pub cx: &'a mut TestAppContext,
pub window_id: usize,
pub workspace: ViewHandle<Workspace>,
}
impl<'a> DockTestContext<'a> {
pub async fn new(cx: &'a mut TestAppContext) -> DockTestContext<'a> {
Settings::test_async(cx);
let fs = FakeFs::new(cx.background());
cx.update(|cx| init(cx));
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
Workspace::new(
None,
0,
project.clone(),
Arc::new(AppState {
languages: project.read(cx).languages().clone(),
themes: ThemeRegistry::new((), cx.font_cache().clone()),
client: project.read(cx).client(),
user_store: project.read(cx).user_store(),
fs: project.read(cx).fs().clone(),
build_window_options: |_, _, _| Default::default(),
initialize_workspace: |_, _, _| {},
dock_default_item_factory: default_item_factory,
background_actions: || &[],
}),
cx,
)
});
workspace.update(cx, |workspace, cx| {
let left_panel = cx.add_view(|_| TestItem::new());
workspace.left_sidebar().update(cx, |sidebar, cx| {
sidebar.add_item(
"icons/folder_tree_16.svg",
"Left Test Panel".to_string(),
left_panel.clone(),
cx,
);
});
let right_panel = cx.add_view(|_| TestItem::new());
workspace.right_sidebar().update(cx, |sidebar, cx| {
sidebar.add_item(
"icons/folder_tree_16.svg",
"Right Test Panel".to_string(),
right_panel.clone(),
cx,
);
});
});
Self {
cx,
window_id,
workspace,
}
}
pub fn workspace<F, T>(&self, read: F) -> T
where
F: FnOnce(&Workspace, &ViewContext<Workspace>) -> T,
{
self.workspace.read_with(self.cx, read)
}
pub fn update_workspace<F, T>(&mut self, update: F) -> T
where
F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
{
self.workspace.update(self.cx, update)
}
pub fn sidebar<F, T>(&self, sidebar_side: SidebarSide, read: F) -> T
where
F: FnOnce(&Sidebar, &AppContext) -> T,
{
self.workspace(|workspace, cx| {
let sidebar = match sidebar_side {
SidebarSide::Left => workspace.left_sidebar(),
SidebarSide::Right => workspace.right_sidebar(),
}
.read(cx);
read(sidebar, cx)
})
}
pub fn center_pane_handle(&self) -> ViewHandle<Pane> {
self.workspace(|workspace, cx| {
workspace
.last_active_center_pane
.clone()
.and_then(|pane| pane.upgrade(cx))
.unwrap_or_else(|| workspace.center.panes()[0].clone())
})
}
pub fn add_item_to_center_pane(&mut self) -> ViewHandle<TestItem> {
self.update_workspace(|workspace, cx| {
let item = cx.add_view(|_| TestItem::new());
let pane = workspace
.last_active_center_pane
.clone()
.and_then(|pane| pane.upgrade(cx))
.unwrap_or_else(|| workspace.center.panes()[0].clone());
Pane::add_item(
workspace,
&pane,
Box::new(item.clone()),
true,
true,
None,
cx,
);
item
})
}
pub fn dock_pane<F, T>(&self, read: F) -> T
where
F: FnOnce(&Pane, &AppContext) -> T,
{
self.workspace(|workspace, cx| {
let dock_pane = workspace.dock_pane().read(cx);
read(dock_pane, cx)
})
}
pub fn move_dock(&mut self, anchor: DockAnchor) {
self.update_workspace(|workspace, cx| Dock::move_dock(workspace, anchor, true, cx));
}
pub fn hide_dock(&mut self) {
self.cx.dispatch_action(self.window_id, HideDock);
}
pub fn open_sidebar(&mut self, sidebar_side: SidebarSide) {
if !self.sidebar(sidebar_side, |sidebar, _| sidebar.is_open()) {
self.update_workspace(|workspace, cx| workspace.toggle_sidebar(sidebar_side, cx));
}
}
pub fn close_sidebar(&mut self, sidebar_side: SidebarSide) {
if self.sidebar(sidebar_side, |sidebar, _| sidebar.is_open()) {
self.update_workspace(|workspace, cx| workspace.toggle_sidebar(sidebar_side, cx));
}
}
pub fn dock_items(&self) -> Vec<ViewHandle<TestItem>> {
self.dock_pane(|pane, cx| {
pane.items()
.map(|item| {
item.act_as::<TestItem>(cx)
.expect("Dock Test Context uses TestItems in the dock")
})
.collect()
})
}
pub async fn close_dock_items(&mut self) {
self.update_workspace(|workspace, cx| {
Pane::close_items(workspace, workspace.dock_pane().clone(), cx, |_| true)
})
.await
.expect("Could not close dock items")
}
pub fn assert_dock_position(&self, expected_position: DockPosition) {
self.workspace(|workspace, _| assert_eq!(workspace.dock.position, expected_position));
}
pub fn assert_sidebar_closed(&self, sidebar_side: SidebarSide) {
assert!(!self.sidebar(sidebar_side, |sidebar, _| sidebar.is_open()));
}
pub fn assert_workspace_pane_active(&self) {
assert!(self
.center_pane_handle()
.read_with(self.cx, |pane, _| pane.is_active()));
}
pub fn assert_workspace_pane_inactive(&self) {
assert!(!self
.center_pane_handle()
.read_with(self.cx, |pane, _| pane.is_active()));
}
pub fn assert_dock_pane_active(&self) {
assert!(self.dock_pane(|pane, _| pane.is_active()))
}
pub fn assert_dock_pane_inactive(&self) {
assert!(!self.dock_pane(|pane, _| pane.is_active()))
}
}
impl<'a> Deref for DockTestContext<'a> {
type Target = gpui::TestAppContext;
fn deref(&self) -> &Self::Target {
self.cx
}
}
impl<'a> DerefMut for DockTestContext<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.cx
}
}
impl BorrowWindowContext for DockTestContext<'_> {
fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
BorrowWindowContext::read_with(self.cx, window_id, f)
}
fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
BorrowWindowContext::update(self.cx, window_id, f)
}
}
}

View file

@ -1,126 +0,0 @@
use super::{icon_for_dock_anchor, Dock, FocusDock, HideDock};
use crate::{handle_dropped_item, StatusItemView, Workspace};
use gpui::{
elements::{Empty, MouseEventHandler, Svg},
platform::CursorStyle,
platform::MouseButton,
AnyElement, Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle,
};
use settings::Settings;
pub struct ToggleDockButton {
workspace: WeakViewHandle<Workspace>,
}
impl ToggleDockButton {
pub fn new(workspace: ViewHandle<Workspace>, cx: &mut ViewContext<Self>) -> Self {
// When dock moves, redraw so that the icon and toggle status matches.
cx.subscribe(&workspace, |_, _, _, cx| cx.notify()).detach();
Self {
workspace: workspace.downgrade(),
}
}
}
impl Entity for ToggleDockButton {
type Event = ();
}
impl View for ToggleDockButton {
fn ui_name() -> &'static str {
"Dock Toggle"
}
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> AnyElement<Self> {
let workspace = self.workspace.upgrade(cx);
if workspace.is_none() {
return Empty::new().into_any();
}
let workspace = workspace.unwrap();
let dock_position = workspace.read(cx).dock.position;
let dock_pane = workspace.read(cx).dock_pane().clone();
let theme = cx.global::<Settings>().theme.clone();
let button = MouseEventHandler::<Self, _>::new(0, cx, {
let theme = theme.clone();
move |state, _| {
let style = theme
.workspace
.status_bar
.sidebar_buttons
.item
.style_for(state, dock_position.is_visible());
Svg::new(icon_for_dock_anchor(dock_position.anchor()))
.with_color(style.icon_color)
.constrained()
.with_width(style.icon_size)
.with_height(style.icon_size)
.contained()
.with_style(style.container)
}
})
.with_cursor_style(CursorStyle::PointingHand)
.on_up(MouseButton::Left, move |event, this, cx| {
let drop_index = dock_pane.read(cx).items_len() + 1;
handle_dropped_item(
event,
this.workspace.clone(),
&dock_pane.downgrade(),
drop_index,
false,
None,
cx,
);
});
if dock_position.is_visible() {
button
.on_click(MouseButton::Left, |_, this, cx| {
if let Some(workspace) = this.workspace.upgrade(cx) {
workspace.update(cx, |workspace, cx| {
Dock::hide_dock(workspace, &Default::default(), cx)
})
}
})
.with_tooltip::<Self>(
0,
"Hide Dock".into(),
Some(Box::new(HideDock)),
theme.tooltip.clone(),
cx,
)
} else {
button
.on_click(MouseButton::Left, |_, this, cx| {
if let Some(workspace) = this.workspace.upgrade(cx) {
workspace.update(cx, |workspace, cx| {
Dock::focus_dock(workspace, &Default::default(), cx)
})
}
})
.with_tooltip::<Self>(
0,
"Focus Dock".into(),
Some(Box::new(FocusDock)),
theme.tooltip.clone(),
cx,
)
}
.into_any()
}
}
impl StatusItemView for ToggleDockButton {
fn set_active_pane_item(
&mut self,
_active_pane_item: Option<&dyn crate::ItemHandle>,
_cx: &mut ViewContext<Self>,
) {
//Not applicable
}
}

View file

@ -2,7 +2,6 @@ mod dragged_item_receiver;
use super::{ItemHandle, SplitDirection};
use crate::{
dock::{icon_for_dock_anchor, AnchorDockBottom, AnchorDockRight, Dock, ExpandDock},
item::WeakItemHandle,
toolbar::Toolbar,
Item, NewFile, NewSearch, NewTerminal, Workspace,
@ -29,7 +28,7 @@ use gpui::{
};
use project::{Project, ProjectEntryId, ProjectPath};
use serde::Deserialize;
use settings::{Autosave, DockAnchor, Settings};
use settings::{Autosave, Settings};
use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc};
use theme::Theme;
use util::ResultExt;
@ -148,7 +147,6 @@ pub struct Pane {
toolbar: ViewHandle<Toolbar>,
tab_bar_context_menu: TabBarContextMenu,
tab_context_menu: ViewHandle<ContextMenu>,
docked: Option<DockAnchor>,
_background_actions: BackgroundActions,
workspace: WeakViewHandle<Workspace>,
has_focus: bool,
@ -204,7 +202,6 @@ pub enum ReorderBehavior {
enum TabBarContextMenuKind {
New,
Split,
Dock,
}
struct TabBarContextMenu {
@ -224,7 +221,6 @@ impl TabBarContextMenu {
impl Pane {
pub fn new(
workspace: WeakViewHandle<Workspace>,
docked: Option<DockAnchor>,
background_actions: BackgroundActions,
cx: &mut ViewContext<Self>,
) -> Self {
@ -256,7 +252,6 @@ impl Pane {
handle: context_menu,
},
tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)),
docked,
_background_actions: background_actions,
workspace,
has_focus: false,
@ -280,11 +275,6 @@ impl Pane {
self.has_focus
}
pub fn set_docked(&mut self, docked: Option<DockAnchor>, cx: &mut ViewContext<Self>) {
self.docked = docked;
cx.notify();
}
pub fn nav_history_for_item<T: Item>(&self, item: &ViewHandle<T>) -> ItemNavHistory {
ItemNavHistory {
history: self.nav_history.clone(),
@ -1157,23 +1147,6 @@ impl Pane {
self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split;
}
fn deploy_dock_menu(&mut self, cx: &mut ViewContext<Self>) {
self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
menu.show(
Default::default(),
AnchorCorner::TopRight,
vec![
ContextMenuItem::action("Anchor Dock Right", AnchorDockRight),
ContextMenuItem::action("Anchor Dock Bottom", AnchorDockBottom),
ContextMenuItem::action("Expand Dock", ExpandDock),
],
cx,
);
});
self.tab_bar_context_menu.kind = TabBarContextMenuKind::Dock;
}
fn deploy_new_menu(&mut self, cx: &mut ViewContext<Self>) {
self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
menu.show(
@ -1616,51 +1589,13 @@ impl Pane {
self.tab_bar_context_menu
.handle_if_kind(TabBarContextMenuKind::New),
))
.with_child(
self.docked
.map(|anchor| {
// Add the dock menu button if this pane is a dock
let dock_icon = icon_for_dock_anchor(anchor);
render_tab_bar_button(
1,
dock_icon,
cx,
|pane, cx| pane.deploy_dock_menu(cx),
self.tab_bar_context_menu
.handle_if_kind(TabBarContextMenuKind::Dock),
)
})
.unwrap_or_else(|| {
// Add the split menu if this pane is not a dock
render_tab_bar_button(
2,
"icons/split_12.svg",
cx,
|pane, cx| pane.deploy_split_menu(cx),
self.tab_bar_context_menu
.handle_if_kind(TabBarContextMenuKind::Split),
)
}),
)
// Add the close dock button if this pane is a dock
.with_children(self.docked.map(|_| {
render_tab_bar_button(
3,
"icons/x_mark_8.svg",
cx,
|this, cx| {
if let Some(workspace) = this.workspace.upgrade(cx) {
cx.window_context().defer(move |cx| {
workspace.update(cx, |workspace, cx| {
Dock::hide_dock(workspace, &Default::default(), cx)
})
});
}
},
None,
)
}))
.with_child(render_tab_bar_button(
2,
"icons/split_12.svg",
cx,
|pane, cx| pane.deploy_split_menu(cx),
self.tab_bar_context_menu.handle_if_kind(TabBarContextMenuKind::Split),
))
.contained()
.with_style(theme.workspace.tab_bar.pane_button_container)
.flex(1., false)
@ -1737,11 +1672,7 @@ impl View for Pane {
0,
self.active_item_index + 1,
false,
if self.docked.is_some() {
None
} else {
Some(100.)
},
Some(100.),
cx,
{
let toolbar = self.toolbar.clone();
@ -1843,9 +1774,6 @@ impl View for Pane {
fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
Self::reset_to_default_keymap_context(keymap);
if self.docked.is_some() {
keymap.add_identifier("docked");
}
}
}

View file

@ -11,7 +11,6 @@ use gpui::{platform::WindowBounds, Axis};
use util::{unzip_option, ResultExt};
use uuid::Uuid;
use crate::dock::DockPosition;
use crate::WorkspaceId;
use model::{
@ -25,9 +24,9 @@ define_connection! {
// workspaces(
// workspace_id: usize, // Primary key for workspaces
// workspace_location: Bincode<Vec<PathBuf>>,
// dock_visible: bool,
// dock_anchor: DockAnchor, // 'Bottom' / 'Right' / 'Expanded'
// dock_pane: Option<usize>, // PaneId
// dock_visible: bool, // Deprecated
// dock_anchor: DockAnchor, // Deprecated
// dock_pane: Option<usize>, // Deprecated
// left_sidebar_open: boolean,
// timestamp: String, // UTC YYYY-MM-DD HH:MM:SS
// window_state: String, // WindowBounds Discriminant
@ -71,10 +70,10 @@ define_connection! {
CREATE TABLE workspaces(
workspace_id INTEGER PRIMARY KEY,
workspace_location BLOB UNIQUE,
dock_visible INTEGER, // Boolean
dock_anchor TEXT, // Enum: 'Bottom' / 'Right' / 'Expanded'
dock_pane INTEGER, // NULL indicates that we don't have a dock pane yet
left_sidebar_open INTEGER, //Boolean
dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed.
dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed.
dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed.
left_sidebar_open INTEGER, // Boolean
timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY(dock_pane) REFERENCES panes(pane_id)
) STRICT;
@ -146,11 +145,10 @@ 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, workspace_location, left_sidebar_open, dock_position, bounds, display): (
let (workspace_id, workspace_location, left_sidebar_open, bounds, display): (
WorkspaceId,
WorkspaceLocation,
bool,
DockPosition,
Option<WindowBounds>,
Option<Uuid>,
) = self
@ -159,8 +157,6 @@ impl WorkspaceDb {
workspace_id,
workspace_location,
left_sidebar_open,
dock_visible,
dock_anchor,
window_state,
window_x,
window_y,
@ -178,15 +174,10 @@ impl WorkspaceDb {
Some(SerializedWorkspace {
id: workspace_id,
location: workspace_location.clone(),
dock_pane: self
.get_dock_pane(workspace_id)
.context("Getting dock pane")
.log_err()?,
center_group: self
.get_center_pane_group(workspace_id)
.context("Getting center group")
.log_err()?,
dock_position,
left_sidebar_open,
bounds,
display,
@ -200,7 +191,6 @@ impl WorkspaceDb {
conn.with_savepoint("update_worktrees", || {
// Clear out panes and pane_groups
conn.exec_bound(sql!(
UPDATE workspaces SET dock_pane = NULL WHERE workspace_id = ?1;
DELETE FROM pane_groups WHERE workspace_id = ?1;
DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id)
.expect("Clearing old panes");
@ -216,41 +206,24 @@ impl WorkspaceDb {
workspace_id,
workspace_location,
left_sidebar_open,
dock_visible,
dock_anchor,
timestamp
)
VALUES (?1, ?2, ?3, ?4, ?5, CURRENT_TIMESTAMP)
VALUES (?1, ?2, ?3, CURRENT_TIMESTAMP)
ON CONFLICT DO
UPDATE SET
workspace_location = ?2,
left_sidebar_open = ?3,
dock_visible = ?4,
dock_anchor = ?5,
timestamp = CURRENT_TIMESTAMP
))?((
workspace.id,
&workspace.location,
workspace.left_sidebar_open,
workspace.dock_position,
))
.context("Updating workspace")?;
// Save center pane group and dock pane
// Save center pane group
Self::save_pane_group(conn, workspace.id, &workspace.center_group, None)
.context("save pane group in save workspace")?;
let dock_id = Self::save_pane(conn, workspace.id, &workspace.dock_pane, None, true)
.context("save pane in save workspace")?;
// Complete workspace initialization
conn.exec_bound(sql!(
UPDATE workspaces
SET dock_pane = ?
WHERE workspace_id = ?
))?((dock_id, workspace.id))
.context("Finishing initialization with dock pane")?;
Ok(())
})
.log_err();
@ -402,32 +375,17 @@ impl WorkspaceDb {
Ok(())
}
SerializedPaneGroup::Pane(pane) => {
Self::save_pane(conn, workspace_id, &pane, parent, false)?;
Self::save_pane(conn, workspace_id, &pane, parent)?;
Ok(())
}
}
}
fn get_dock_pane(&self, workspace_id: WorkspaceId) -> Result<SerializedPane> {
let (pane_id, active) = self.select_row_bound(sql!(
SELECT pane_id, active
FROM panes
WHERE pane_id = (SELECT dock_pane FROM workspaces WHERE workspace_id = ?)
))?(workspace_id)?
.context("No dock pane for workspace")?;
Ok(SerializedPane::new(
self.get_items(pane_id).context("Reading items")?,
active,
))
}
fn save_pane(
conn: &Connection,
workspace_id: WorkspaceId,
pane: &SerializedPane,
parent: Option<(GroupId, usize)>, // None indicates BOTH dock pane AND center_pane
dock: bool,
parent: Option<(GroupId, usize)>,
) -> Result<PaneId> {
let pane_id = conn.select_row_bound::<_, i64>(sql!(
INSERT INTO panes(workspace_id, active)
@ -436,13 +394,11 @@ impl WorkspaceDb {
))?((workspace_id, pane.active))?
.ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?;
if !dock {
let (parent_id, order) = unzip_option(parent);
conn.exec_bound(sql!(
INSERT INTO center_panes(pane_id, parent_group_id, position)
VALUES (?, ?, ?)
))?((pane_id, parent_id, order))?;
}
let (parent_id, order) = unzip_option(parent);
conn.exec_bound(sql!(
INSERT INTO center_panes(pane_id, parent_group_id, position)
VALUES (?, ?, ?)
))?((pane_id, parent_id, order))?;
Self::save_items(conn, workspace_id, pane_id, &pane.children).context("Saving items")?;
@ -498,10 +454,8 @@ impl WorkspaceDb {
#[cfg(test)]
mod tests {
use std::sync::Arc;
use db::open_test_db;
use settings::DockAnchor;
use super::*;
@ -578,20 +532,16 @@ mod tests {
let mut workspace_1 = SerializedWorkspace {
id: 1,
location: (["/tmp", "/tmp2"]).into(),
dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom),
center_group: Default::default(),
dock_pane: Default::default(),
left_sidebar_open: true,
bounds: Default::default(),
display: Default::default(),
};
let mut workspace_2 = SerializedWorkspace {
let mut _workspace_2 = SerializedWorkspace {
id: 2,
location: (["/tmp"]).into(),
dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded),
center_group: Default::default(),
dock_pane: Default::default(),
left_sidebar_open: false,
bounds: Default::default(),
display: Default::default(),
@ -606,7 +556,7 @@ mod tests {
})
.await;
db.save_workspace(workspace_2.clone()).await;
db.save_workspace(_workspace_2.clone()).await;
db.write(|conn| {
conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
@ -619,26 +569,27 @@ mod tests {
db.save_workspace(workspace_1.clone()).await;
db.save_workspace(workspace_1).await;
workspace_2.dock_pane.children.push(SerializedItem {
kind: Arc::from("Test"),
item_id: 10,
active: true,
});
db.save_workspace(workspace_2).await;
todo!();
// workspace_2.dock_pane.children.push(SerializedItem {
// kind: Arc::from("Test"),
// item_id: 10,
// active: true,
// });
// db.save_workspace(workspace_2).await;
let test_text_2 = db
.select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
.unwrap()(2)
.unwrap()
.unwrap();
assert_eq!(test_text_2, "test-text-2");
// let test_text_2 = db
// .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
// .unwrap()(2)
// .unwrap()
// .unwrap();
// assert_eq!(test_text_2, "test-text-2");
let test_text_1 = db
.select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
.unwrap()(1)
.unwrap()
.unwrap();
assert_eq!(test_text_1, "test-text-1");
// let test_text_1 = db
// .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
// .unwrap()(1)
// .unwrap()
// .unwrap();
// assert_eq!(test_text_1, "test-text-1");
}
#[gpui::test]
@ -647,16 +598,6 @@ mod tests {
let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await);
let dock_pane = crate::persistence::model::SerializedPane {
children: vec![
SerializedItem::new("Terminal", 1, false),
SerializedItem::new("Terminal", 2, false),
SerializedItem::new("Terminal", 3, true),
SerializedItem::new("Terminal", 4, false),
],
active: false,
};
// -----------------
// | 1,2 | 5,6 |
// | - - - | |
@ -697,9 +638,7 @@ mod tests {
let workspace = SerializedWorkspace {
id: 5,
location: (["/tmp", "/tmp2"]).into(),
dock_position: DockPosition::Shown(DockAnchor::Bottom),
center_group,
dock_pane,
left_sidebar_open: true,
bounds: Default::default(),
display: Default::default(),
@ -727,9 +666,7 @@ mod tests {
let workspace_1 = SerializedWorkspace {
id: 1,
location: (["/tmp", "/tmp2"]).into(),
dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom),
center_group: Default::default(),
dock_pane: Default::default(),
left_sidebar_open: true,
bounds: Default::default(),
display: Default::default(),
@ -738,9 +675,7 @@ mod tests {
let mut workspace_2 = SerializedWorkspace {
id: 2,
location: (["/tmp"]).into(),
dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded),
center_group: Default::default(),
dock_pane: Default::default(),
left_sidebar_open: false,
bounds: Default::default(),
display: Default::default(),
@ -776,9 +711,7 @@ mod tests {
let mut workspace_3 = SerializedWorkspace {
id: 3,
location: (&["/tmp", "/tmp2"]).into(),
dock_position: DockPosition::Shown(DockAnchor::Right),
center_group: Default::default(),
dock_pane: Default::default(),
left_sidebar_open: false,
bounds: Default::default(),
display: Default::default(),
@ -801,52 +734,23 @@ mod tests {
);
}
use crate::dock::DockPosition;
use crate::persistence::model::SerializedWorkspace;
use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup};
fn default_workspace<P: AsRef<Path>>(
workspace_id: &[P],
dock_pane: SerializedPane,
center_group: &SerializedPaneGroup,
) -> SerializedWorkspace {
SerializedWorkspace {
id: 4,
location: workspace_id.into(),
dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Right),
center_group: center_group.clone(),
dock_pane,
left_sidebar_open: true,
bounds: Default::default(),
display: Default::default(),
}
}
#[gpui::test]
async fn test_basic_dock_pane() {
env_logger::try_init().ok();
let db = WorkspaceDb(open_test_db("basic_dock_pane").await);
let dock_pane = crate::persistence::model::SerializedPane::new(
vec![
SerializedItem::new("Terminal", 1, false),
SerializedItem::new("Terminal", 4, false),
SerializedItem::new("Terminal", 2, false),
SerializedItem::new("Terminal", 3, true),
],
false,
);
let workspace = default_workspace(&["/tmp"], dock_pane, &Default::default());
db.save_workspace(workspace.clone()).await;
let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap();
assert_eq!(workspace.dock_pane, new_workspace.dock_pane);
}
#[gpui::test]
async fn test_simple_split() {
env_logger::try_init().ok();
@ -890,7 +794,7 @@ mod tests {
],
};
let workspace = default_workspace(&["/tmp"], Default::default(), &center_pane);
let workspace = default_workspace(&["/tmp"], &center_pane);
db.save_workspace(workspace.clone()).await;
@ -939,7 +843,7 @@ mod tests {
let id = &["/tmp"];
let mut workspace = default_workspace(id, Default::default(), &center_pane);
let mut workspace = default_workspace(id, &center_pane);
db.save_workspace(workspace.clone()).await;

View file

@ -1,5 +1,5 @@
use crate::{
dock::DockPosition, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId,
ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId,
};
use anyhow::{anyhow, Context, Result};
use async_recursion::async_recursion;
@ -11,7 +11,6 @@ use gpui::{
platform::WindowBounds, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WeakViewHandle,
};
use project::Project;
use settings::DockAnchor;
use std::{
path::{Path, PathBuf},
sync::Arc,
@ -62,9 +61,7 @@ impl Column for WorkspaceLocation {
pub struct SerializedWorkspace {
pub id: WorkspaceId,
pub location: WorkspaceLocation,
pub dock_position: DockPosition,
pub center_group: SerializedPaneGroup,
pub dock_pane: SerializedPane,
pub left_sidebar_open: bool,
pub bounds: Option<WindowBounds>,
pub display: Option<Uuid>,
@ -278,64 +275,39 @@ impl Column for SerializedItem {
}
}
impl StaticColumnCount for DockPosition {
fn column_count() -> usize {
2
}
}
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 db::sqlez::connection::Connection;
use settings::DockAnchor;
use super::WorkspaceLocation;
// use super::WorkspaceLocation;
#[test]
fn test_workspace_round_trips() {
let db = Connection::open_memory(Some("workspace_id_round_trips"));
let _db = Connection::open_memory(Some("workspace_id_round_trips"));
db.exec(indoc::indoc! {"
CREATE TABLE workspace_id_test(
workspace_id INTEGER,
dock_anchor TEXT
);"})
.unwrap()()
.unwrap();
todo!();
// db.exec(indoc::indoc! {"
// CREATE TABLE workspace_id_test(
// workspace_id INTEGER,
// dock_anchor TEXT
// );"})
// .unwrap()()
// .unwrap();
let workspace_id: WorkspaceLocation = WorkspaceLocation::from(&["\test2", "\test1"]);
// let workspace_id: WorkspaceLocation = WorkspaceLocation::from(&["\test2", "\test1"]);
db.exec_bound("INSERT INTO workspace_id_test(workspace_id, dock_anchor) VALUES (?,?)")
.unwrap()((&workspace_id, DockAnchor::Bottom))
.unwrap();
// db.exec_bound("INSERT INTO workspace_id_test(workspace_id, dock_anchor) VALUES (?,?)")
// .unwrap()((&workspace_id, DockAnchor::Bottom))
// .unwrap();
assert_eq!(
db.select_row("SELECT workspace_id, dock_anchor FROM workspace_id_test LIMIT 1")
.unwrap()()
.unwrap(),
Some((
WorkspaceLocation::from(&["\test1", "\test2"]),
DockAnchor::Bottom
))
);
// assert_eq!(
// db.select_row("SELECT workspace_id, dock_anchor FROM workspace_id_test LIMIT 1")
// .unwrap()()
// .unwrap(),
// Some((
// WorkspaceLocation::from(&["\test1", "\test2"]),
// DockAnchor::Bottom
// ))
// );
}
}

View file

@ -2,7 +2,6 @@
///
/// 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 notifications;
pub mod pane;
@ -22,7 +21,6 @@ use client::{
Client, TypedEnvelope, UserStore,
};
use collections::{hash_map, HashMap, HashSet};
use dock::{Dock, DockDefaultItemFactory, ToggleDockButton};
use drag_and_drop::DragAndDrop;
use futures::{
channel::{mpsc, oneshot},
@ -63,7 +61,6 @@ use crate::{
persistence::model::{SerializedPane, SerializedPaneGroup, SerializedWorkspace},
};
use lazy_static::lazy_static;
use log::warn;
use notifications::{NotificationHandle, NotifyResultExt};
pub use pane::*;
pub use pane_group::*;
@ -75,7 +72,7 @@ pub use persistence::{
use postage::prelude::Stream;
use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
use serde::Deserialize;
use settings::{Autosave, DockAnchor, Settings};
use settings::{Autosave, Settings};
use shared_screen::SharedScreen;
use sidebar::{Sidebar, SidebarButtons, SidebarSide, ToggleSidebarItem};
use status_bar::StatusBar;
@ -185,7 +182,6 @@ impl_actions!(workspace, [ActivatePane]);
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
pane::init(cx);
dock::init(cx);
notifications::init(cx);
cx.add_global_action({
@ -362,7 +358,6 @@ pub struct AppState {
pub build_window_options:
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,
}
@ -386,7 +381,6 @@ impl AppState {
user_store,
initialize_workspace: |_, _, _| {},
build_window_options: |_, _, _| Default::default(),
dock_default_item_factory: |_, _| None,
background_actions: || &[],
})
}
@ -439,7 +433,6 @@ impl DelayedDebouncedEditAction {
}
pub enum Event {
DockAnchorChanged,
PaneAdded(ViewHandle<Pane>),
ContactRequestedJoin(u64),
}
@ -457,7 +450,6 @@ pub struct Workspace {
last_active_center_pane: Option<WeakViewHandle<Pane>>,
status_bar: ViewHandle<StatusBar>,
titlebar_item: Option<AnyViewHandle>,
dock: Dock,
notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
project: ModelHandle<Project>,
leader_state: LeaderState,
@ -535,16 +527,10 @@ impl Workspace {
let weak_handle = cx.weak_handle();
let center_pane = cx
.add_view(|cx| Pane::new(weak_handle.clone(), None, app_state.background_actions, cx));
.add_view(|cx| Pane::new(weak_handle.clone(), app_state.background_actions, cx));
cx.subscribe(&center_pane, Self::handle_pane_event).detach();
cx.focus(&center_pane);
cx.emit(Event::PaneAdded(center_pane.clone()));
let dock = Dock::new(
app_state.dock_default_item_factory,
app_state.background_actions,
cx,
);
let dock_pane = dock.pane().clone();
let mut current_user = app_state.user_store.read(cx).watch_current_user();
let mut connection_status = app_state.client.status();
@ -559,7 +545,6 @@ impl Workspace {
}
anyhow::Ok(())
});
let handle = cx.handle();
// All leader updates are enqueued and then processed in a single task, so
// that each asynchronous operation can be run in order.
@ -581,14 +566,12 @@ impl Workspace {
let right_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Right));
let left_sidebar_buttons =
cx.add_view(|cx| SidebarButtons::new(left_sidebar.clone(), weak_handle.clone(), cx));
let toggle_dock = cx.add_view(|cx| ToggleDockButton::new(handle, cx));
let right_sidebar_buttons =
cx.add_view(|cx| SidebarButtons::new(right_sidebar.clone(), weak_handle.clone(), cx));
let status_bar = cx.add_view(|cx| {
let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
status_bar.add_left_item(left_sidebar_buttons, cx);
status_bar.add_right_item(right_sidebar_buttons, cx);
status_bar.add_right_item(toggle_dock, cx);
status_bar
});
@ -630,11 +613,7 @@ impl Workspace {
modal: None,
weak_self: weak_handle.clone(),
center: PaneGroup::new(center_pane.clone()),
dock,
// When removing an item, the last element remaining in this array
// is used to find where focus should fallback to. As such, the order
// of these two variables is important.
panes: vec![dock_pane.clone(), center_pane.clone()],
panes: vec![center_pane.clone()],
panes_by_item: Default::default(),
active_pane: center_pane.clone(),
last_active_center_pane: Some(center_pane.downgrade()),
@ -664,10 +643,6 @@ impl Workspace {
cx.defer(move |_, cx| {
Self::load_from_serialized_workspace(weak_handle, serialized_workspace, cx)
});
} else if project.read(cx).is_local() {
if cx.global::<Settings>().default_dock_anchor != DockAnchor::Expanded {
Dock::show(&mut this, false, cx);
}
}
this
@ -1336,16 +1311,11 @@ impl Workspace {
SidebarSide::Left => &mut self.left_sidebar,
SidebarSide::Right => &mut self.right_sidebar,
};
let open = sidebar.update(cx, |sidebar, cx| {
sidebar.update(cx, |sidebar, cx| {
let open = !sidebar.is_open();
sidebar.set_open(open, cx);
open
});
if open {
Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
}
self.serialize_workspace(cx);
cx.focus_self();
@ -1369,8 +1339,6 @@ impl Workspace {
});
if let Some(active_item) = active_item {
Dock::hide_on_sidebar_shown(self, action.sidebar_side, cx);
if active_item.is_focused(cx) {
cx.focus_self();
} else {
@ -1401,8 +1369,6 @@ impl Workspace {
sidebar.active_item().cloned()
});
if let Some(active_item) = active_item {
Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
if active_item.is_focused(cx) {
cx.focus_self();
} else {
@ -1424,7 +1390,6 @@ impl Workspace {
let pane = cx.add_view(|cx| {
Pane::new(
self.weak_handle(),
None,
self.app_state.background_actions,
cx,
)
@ -1466,16 +1431,12 @@ impl Workspace {
cx: &mut ViewContext<Self>,
) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
let pane = pane.unwrap_or_else(|| {
if !self.dock_active() {
self.active_pane().downgrade()
} else {
self.last_active_center_pane.clone().unwrap_or_else(|| {
self.panes
.first()
.expect("There must be an active pane")
.downgrade()
})
}
self.last_active_center_pane.clone().unwrap_or_else(|| {
self.panes
.first()
.expect("There must be an active pane")
.downgrade()
})
});
let task = self.load_path(path.into(), cx);
@ -1599,15 +1560,7 @@ impl Workspace {
status_bar.set_active_pane(&self.active_pane, cx);
});
self.active_item_path_changed(cx);
if &pane == self.dock_pane() {
Dock::show(self, true, cx);
} else {
self.last_active_center_pane = Some(pane.downgrade());
if self.dock.is_anchored_at(DockAnchor::Expanded) {
Dock::hide(self, cx);
}
}
cx.notify();
}
@ -1630,13 +1583,11 @@ impl Workspace {
event: &pane::Event,
cx: &mut ViewContext<Self>,
) {
let is_dock = &pane == self.dock.pane();
match event {
pane::Event::Split(direction) if !is_dock => {
pane::Event::Split(direction) => {
self.split_pane(pane, *direction, cx);
}
pane::Event::Remove if !is_dock => self.remove_pane(pane, cx),
pane::Event::Remove if is_dock => Dock::hide(self, cx),
pane::Event::Remove => self.remove_pane(pane, cx),
pane::Event::ActivateItem { local } => {
if *local {
self.unfollow(&pane, cx);
@ -1662,7 +1613,6 @@ impl Workspace {
pane::Event::Focus => {
self.handle_pane_focused(pane.clone(), cx);
}
_ => {}
}
self.serialize_workspace(cx);
@ -1674,11 +1624,6 @@ impl Workspace {
direction: SplitDirection,
cx: &mut ViewContext<Self>,
) -> Option<ViewHandle<Pane>> {
if &pane == self.dock_pane() {
warn!("Can't split dock pane.");
return None;
}
let item = pane.read(cx).active_item()?;
let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
let new_pane = self.add_pane(cx);
@ -1702,10 +1647,6 @@ impl Workspace {
) {
let Some(pane_to_split) = pane_to_split.upgrade(cx) else { return; };
let Some(from) = from.upgrade(cx) else { return; };
if &pane_to_split == self.dock_pane() {
warn!("Can't split dock pane.");
return;
}
let new_pane = self.add_pane(cx);
Pane::move_item(self, from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
@ -1723,11 +1664,6 @@ impl Workspace {
cx: &mut ViewContext<Self>,
) -> Option<Task<Result<()>>> {
let pane_to_split = pane_to_split.upgrade(cx)?;
if &pane_to_split == self.dock_pane() {
warn!("Can't split dock pane.");
return None;
}
let new_pane = self.add_pane(cx);
self.center
.split(&pane_to_split, &new_pane, split_direction)
@ -1764,14 +1700,6 @@ impl Workspace {
&self.active_pane
}
pub fn dock_pane(&self) -> &ViewHandle<Pane> {
self.dock.pane()
}
fn dock_active(&self) -> bool {
&self.active_pane == self.dock.pane()
}
fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
if let Some(remote_id) = remote_id {
self.remote_entity_subscription = Some(
@ -2518,14 +2446,11 @@ impl Workspace {
// - with_local_workspace() relies on this to not have other stuff open
// when you open your log
if !location.paths().is_empty() {
let dock_pane = serialize_pane_handle(self.dock.pane(), cx);
let center_group = build_serialized_pane_group(&self.center.root, cx);
let serialized_workspace = SerializedWorkspace {
id: self.database_id,
location,
dock_position: self.dock.position(),
dock_pane,
center_group,
left_sidebar_open: self.left_sidebar.read(cx).is_open(),
bounds: Default::default(),
@ -2545,26 +2470,14 @@ impl Workspace {
cx: &mut AppContext,
) {
cx.spawn(|mut cx| async move {
let (project, dock_pane_handle, old_center_pane) =
let (project, old_center_pane) =
workspace.read_with(&cx, |workspace, _| {
(
workspace.project().clone(),
workspace.dock_pane().downgrade(),
workspace.last_active_center_pane.clone(),
)
})?;
serialized_workspace
.dock_pane
.deserialize_to(
&project,
&dock_pane_handle,
serialized_workspace.id,
&workspace,
&mut cx,
)
.await?;
// Traverse the splits tree and add to things
let center_group = serialized_workspace
.center_group
@ -2602,18 +2515,6 @@ impl Workspace {
workspace.toggle_sidebar(SidebarSide::Left, cx);
}
// Note that without after_window, the focus_self() and
// 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,
true,
cx,
);
});
cx.notify();
})?;
@ -2634,7 +2535,6 @@ impl Workspace {
fs: project.read(cx).fs().clone(),
build_window_options: |_, _, _| Default::default(),
initialize_workspace: |_, _, _| {},
dock_default_item_factory: |_, _| None,
background_actions: || &[],
});
Self::new(None, 0, project, app_state, cx)
@ -2729,15 +2629,9 @@ impl View for Workspace {
))
.flex(1., true),
)
.with_children(self.dock.render(
&theme,
DockAnchor::Bottom,
cx,
)),
)
.flex(1., true),
)
.with_children(self.dock.render(&theme, DockAnchor::Right, cx))
.with_children(
if self.right_sidebar.read(cx).active_item().is_some() {
Some(
@ -2760,11 +2654,6 @@ impl View for Workspace {
})
.with_child(Overlay::new(
Stack::new()
.with_children(self.dock.render(
&theme,
DockAnchor::Expanded,
cx,
))
.with_children(self.modal.as_ref().map(|modal| {
ChildView::new(modal, cx)
.contained()

View file

@ -52,7 +52,7 @@ use staff_mode::StaffMode;
use theme::ThemeRegistry;
use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
use workspace::{
dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings,
item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings,
Workspace,
};
use zed::{self, build_window_options, initialize_workspace, languages, menus};
@ -205,7 +205,6 @@ fn main() {
fs,
build_window_options,
initialize_workspace,
dock_default_item_factory,
background_actions,
});
cx.set_global(Arc::downgrade(&app_state));
@ -776,7 +775,6 @@ pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] {
&[
("Go to file", &file_finder::Toggle),
("Open command palette", &command_palette::Toggle),
("Focus the dock", &FocusDock),
("Open recent projects", &recent_projects::OpenRecent),
("Change your settings", &OpenSettings),
]

View file

@ -306,7 +306,6 @@ pub fn initialize_workspace(
.detach();
cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone()));
cx.emit(workspace::Event::PaneAdded(workspace.dock_pane().clone()));
let collab_titlebar_item =
cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx));