From 1919a826f9f2aeb3fdc3179e056bed2de5ad5c90 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 May 2023 15:02:26 -0600 Subject: [PATCH 01/61] Remove dock from workspace --- assets/keymaps/atom.json | 4 - assets/keymaps/default.json | 26 - assets/keymaps/jetbrains.json | 9 +- assets/keymaps/sublime_text.json | 7 - assets/keymaps/textmate.json | 4 - assets/settings/default.json | 10 - crates/collab/src/tests.rs | 1 - crates/settings/src/settings.rs | 49 +- crates/terminal_view/src/terminal_button.rs | 7 +- crates/theme/src/theme.rs | 10 - crates/workspace/src/dock.rs | 816 ------------------ .../workspace/src/dock/toggle_dock_button.rs | 126 --- crates/workspace/src/pane.rs | 90 +- crates/workspace/src/persistence.rs | 176 +--- crates/workspace/src/persistence/model.rs | 76 +- crates/workspace/src/workspace.rs | 137 +-- crates/zed/src/main.rs | 4 +- crates/zed/src/zed.rs | 1 - 18 files changed, 92 insertions(+), 1461 deletions(-) delete mode 100644 crates/workspace/src/dock.rs delete mode 100644 crates/workspace/src/dock/toggle_dock_button.rs diff --git a/assets/keymaps/atom.json b/assets/keymaps/atom.json index 99aae2638f..a1e8cf6d9d 100644 --- a/assets/keymaps/atom.json +++ b/assets/keymaps/atom.json @@ -62,9 +62,5 @@ "ctrl-f": "project_panel::ExpandSelectedEntry", "ctrl-shift-c": "project_panel::CopyPath" } - }, - { - "context": "Dock", - "bindings": {} } ] diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index b89940a751..6e8b519313 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -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": { diff --git a/assets/keymaps/jetbrains.json b/assets/keymaps/jetbrains.json index 59e069e7f7..0dafe58d32 100644 --- a/assets/keymaps/jetbrains.json +++ b/assets/keymaps/jetbrains.json @@ -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" } } ] diff --git a/assets/keymaps/sublime_text.json b/assets/keymaps/sublime_text.json index 373f3f4d3a..7a285ee824 100644 --- a/assets/keymaps/sublime_text.json +++ b/assets/keymaps/sublime_text.json @@ -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" - } } ] diff --git a/assets/keymaps/textmate.json b/assets/keymaps/textmate.json index 1abcaa376c..738b6603ff 100644 --- a/assets/keymaps/textmate.json +++ b/assets/keymaps/textmate.json @@ -83,9 +83,5 @@ { "context": "ProjectPanel", "bindings": {} - }, - { - "context": "Dock", - "bindings": {} } ] diff --git a/assets/settings/default.json b/assets/settings/default.json index 20d07acae0..58fb28bc62 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -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, diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index 64768d1a47..aeb26231d4 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -195,7 +195,6 @@ impl TestServer { fs: fs.clone(), build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _| unimplemented!(), - dock_default_item_factory: |_, _| None, background_actions: || &[], }); diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 7ba52fcb5e..fde7f516a7 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -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 { - 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, #[serde(default)] pub autosave: Option, - #[serde(default)] - pub default_dock_anchor: Option, #[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), diff --git a/crates/terminal_view/src/terminal_button.rs b/crates/terminal_view/src/terminal_button.rs index a92f7285b5..7b0da416ff 100644 --- a/crates/terminal_view/src/terminal_button.rs +++ b/crates/terminal_view/src/terminal_button.rs @@ -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::( 0, "Show Terminal".into(), - Some(Box::new(FocusDock)), + None, // TODO! We need a new action here. theme.tooltip.clone(), cx, ), diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 0bd23a0b87..8c77295748 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -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, } -#[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)] diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs deleted file mode 100644 index 7efcb7f9d3..0000000000 --- a/crates/workspace/src/dock.rs +++ /dev/null @@ -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| { - Dock::move_dock(workspace, DockAnchor::Right, true, cx); - }, - ); - cx.add_action( - |workspace: &mut Workspace, _: &AnchorDockBottom, cx: &mut ViewContext| { - Dock::move_dock(workspace, DockAnchor::Bottom, true, cx) - }, - ); - cx.add_action( - |workspace: &mut Workspace, _: &ExpandDock, cx: &mut ViewContext| { - Dock::move_dock(workspace, DockAnchor::Expanded, true, cx) - }, - ); - cx.add_action( - |workspace: &mut Workspace, _: &AddTabToDock, cx: &mut ViewContext| { - 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| { - 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) -> Option>; - -pub struct Dock { - position: DockPosition, - panel_sizes: HashMap, - pane: ViewHandle, - default_item_factory: DockDefaultItemFactory, -} - -impl Dock { - pub fn new( - default_item_factory: DockDefaultItemFactory, - background_actions: BackgroundActions, - cx: &mut ViewContext, - ) -> Self { - let position = DockPosition::Hidden(cx.global::().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 { - &self.pane - } - - pub fn visible_pane(&self) -> Option<&ViewHandle> { - 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.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) { - Self::set_dock_position(workspace, workspace.dock.position.hide(), true, cx); - } - - pub fn show(workspace: &mut Workspace, focus: bool, cx: &mut ViewContext) { - 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, - ) { - 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) { - Self::set_dock_position(workspace, workspace.dock.position.show(), true, cx); - } - - pub fn hide_dock(workspace: &mut Workspace, _: &HideDock, cx: &mut ViewContext) { - 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, - ) { - Self::set_dock_position(workspace, DockPosition::Shown(new_anchor), focus, cx); - } - - pub fn render( - &self, - theme: &Theme, - anchor: DockAnchor, - cx: &mut ViewContext, - ) -> Option> { - 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::( - 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::::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::::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, - ) -> Option> { - 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::(cx); - }); - - let serialized_workspace = SerializedWorkspace { - id: 0, - location: Vec::::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, - } - - 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(&self, read: F) -> T - where - F: FnOnce(&Workspace, &ViewContext) -> T, - { - self.workspace.read_with(self.cx, read) - } - - pub fn update_workspace(&mut self, update: F) -> T - where - F: FnOnce(&mut Workspace, &mut ViewContext) -> T, - { - self.workspace.update(self.cx, update) - } - - pub fn sidebar(&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 { - 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 { - 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(&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> { - self.dock_pane(|pane, cx| { - pane.items() - .map(|item| { - item.act_as::(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>(&self, window_id: usize, f: F) -> T { - BorrowWindowContext::read_with(self.cx, window_id, f) - } - - fn update T>(&mut self, window_id: usize, f: F) -> T { - BorrowWindowContext::update(self.cx, window_id, f) - } - } -} diff --git a/crates/workspace/src/dock/toggle_dock_button.rs b/crates/workspace/src/dock/toggle_dock_button.rs deleted file mode 100644 index 1fda55b783..0000000000 --- a/crates/workspace/src/dock/toggle_dock_button.rs +++ /dev/null @@ -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, -} - -impl ToggleDockButton { - pub fn new(workspace: ViewHandle, cx: &mut ViewContext) -> 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) -> AnyElement { - 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::().theme.clone(); - - let button = MouseEventHandler::::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::( - 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::( - 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, - ) { - //Not applicable - } -} diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 96320a4baf..1aed1c011e 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -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, tab_bar_context_menu: TabBarContextMenu, tab_context_menu: ViewHandle, - docked: Option, _background_actions: BackgroundActions, workspace: WeakViewHandle, 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, - docked: Option, background_actions: BackgroundActions, cx: &mut ViewContext, ) -> 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, cx: &mut ViewContext) { - self.docked = docked; - cx.notify(); - } - pub fn nav_history_for_item(&self, item: &ViewHandle) -> 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.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.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"); - } } } diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index d5d79c3ddd..3b7d0ecb87 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -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>, - // dock_visible: bool, - // dock_anchor: DockAnchor, // 'Bottom' / 'Right' / 'Expanded' - // dock_pane: Option, // PaneId + // dock_visible: bool, // Deprecated + // dock_anchor: DockAnchor, // Deprecated + // dock_pane: Option, // 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, Option, ) = 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 { - 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 { 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>( 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(), ¢er_pane); + let workspace = default_workspace(&["/tmp"], ¢er_pane); db.save_workspace(workspace.clone()).await; @@ -939,7 +843,7 @@ mod tests { let id = &["/tmp"]; - let mut workspace = default_workspace(id, Default::default(), ¢er_pane); + let mut workspace = default_workspace(id, ¢er_pane); db.save_workspace(workspace.clone()).await; diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index e838d22f0d..3a348d6105 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -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, pub display: Option, @@ -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 { - 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 + // )) + // ); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a57cea526b..84d1832df7 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -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, 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, Option, &dyn Platform) -> WindowOptions<'static>, pub initialize_workspace: fn(&mut Workspace, &Arc, &mut ViewContext), - 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), ContactRequestedJoin(u64), } @@ -457,7 +450,6 @@ pub struct Workspace { last_active_center_pane: Option>, status_bar: ViewHandle, titlebar_item: Option, - dock: Dock, notifications: Vec<(TypeId, usize, Box)>, project: ModelHandle, 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(¢er_pane, Self::handle_pane_event).detach(); cx.focus(¢er_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(¢er_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::().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, ) -> Task, 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, ) { - 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, ) -> Option> { - 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, ) -> Option>> { 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 { - self.dock.pane() - } - - fn dock_active(&self) -> bool { - &self.active_pane == self.dock.pane() - } - fn project_remote_id_changed(&mut self, remote_id: Option, cx: &mut ViewContext) { 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() diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f498078b52..ff969a8c75 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -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), ] diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index f687237bd2..46443eb77d 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -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)); From 03f8c1206a3c1f0d9abcbb8dd2745694f32b90f7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 May 2023 16:04:36 -0600 Subject: [PATCH 02/61] Rename Sidebar to Dock --- assets/keymaps/atom.json | 4 +- assets/keymaps/default.json | 2 +- assets/keymaps/jetbrains.json | 2 +- assets/keymaps/sublime_text.json | 2 +- assets/keymaps/textmate.json | 2 +- crates/copilot_button/src/copilot_button.rs | 2 +- crates/feedback/src/deploy_feedback_button.rs | 2 +- crates/project_panel/src/project_panel.rs | 2 +- crates/terminal_view/src/terminal_button.rs | 2 +- crates/theme/src/theme.rs | 12 +- crates/welcome/src/welcome.rs | 4 +- crates/workspace/src/{sidebar.rs => dock.rs} | 94 +++++++-------- crates/workspace/src/item.rs | 4 +- crates/workspace/src/workspace.rs | 114 +++++++++--------- crates/zed/src/menus.rs | 2 +- crates/zed/src/zed.rs | 12 +- styles/src/styleTree/statusBar.ts | 2 +- styles/src/styleTree/workspace.ts | 15 +-- 18 files changed, 133 insertions(+), 146 deletions(-) rename crates/workspace/src/{sidebar.rs => dock.rs} (81%) diff --git a/assets/keymaps/atom.json b/assets/keymaps/atom.json index a1e8cf6d9d..634aed322a 100644 --- a/assets/keymaps/atom.json +++ b/assets/keymaps/atom.json @@ -39,8 +39,8 @@ { "context": "Workspace", "bindings": { - "cmd-\\": "workspace::ToggleLeftSidebar", - "cmd-k cmd-b": "workspace::ToggleLeftSidebar", + "cmd-\\": "workspace::ToggleLeftDock", + "cmd-k cmd-b": "workspace::ToggleLeftDock", "cmd-t": "file_finder::Toggle", "cmd-shift-r": "project_symbols::Toggle" } diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 6e8b519313..cc36f79e9f 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -349,7 +349,7 @@ "workspace::ActivatePane", 8 ], - "cmd-b": "workspace::ToggleLeftSidebar", + "cmd-b": "workspace::ToggleLeftDock", "cmd-shift-f": "workspace::NewSearch", "cmd-k cmd-t": "theme_selector::Toggle", "cmd-k cmd-s": "zed::OpenKeymap", diff --git a/assets/keymaps/jetbrains.json b/assets/keymaps/jetbrains.json index 0dafe58d32..69a583e1e9 100644 --- a/assets/keymaps/jetbrains.json +++ b/assets/keymaps/jetbrains.json @@ -65,7 +65,7 @@ "bindings": { "cmd-shift-a": "command_palette::Toggle", "cmd-alt-o": "project_symbols::Toggle", - "cmd-1": "workspace::ToggleLeftSidebar", + "cmd-1": "workspace::ToggleLeftDock", "cmd-6": "diagnostics::Deploy" } } diff --git a/assets/keymaps/sublime_text.json b/assets/keymaps/sublime_text.json index 7a285ee824..2d32b77d58 100644 --- a/assets/keymaps/sublime_text.json +++ b/assets/keymaps/sublime_text.json @@ -45,7 +45,7 @@ { "context": "Workspace", "bindings": { - "cmd-k cmd-b": "workspace::ToggleLeftSidebar", + "cmd-k cmd-b": "workspace::ToggleLeftDock", "cmd-t": "file_finder::Toggle", "shift-cmd-r": "project_symbols::Toggle", // Currently busted: https://github.com/zed-industries/feedback/issues/898 diff --git a/assets/keymaps/textmate.json b/assets/keymaps/textmate.json index 738b6603ff..06be727429 100644 --- a/assets/keymaps/textmate.json +++ b/assets/keymaps/textmate.json @@ -68,7 +68,7 @@ { "context": "Workspace", "bindings": { - "cmd-alt-ctrl-d": "workspace::ToggleLeftSidebar", + "cmd-alt-ctrl-d": "workspace::ToggleLeftDock", "cmd-t": "file_finder::Toggle", "cmd-shift-t": "project_symbols::Toggle" } diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 4b0c9b494a..0d6bcc115c 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -64,7 +64,7 @@ impl View for CopilotButton { let style = theme .workspace .status_bar - .sidebar_buttons + .panel_buttons .item .style_for(state, active); diff --git a/crates/feedback/src/deploy_feedback_button.rs b/crates/feedback/src/deploy_feedback_button.rs index b464d00887..5fdb378eb6 100644 --- a/crates/feedback/src/deploy_feedback_button.rs +++ b/crates/feedback/src/deploy_feedback_button.rs @@ -40,7 +40,7 @@ impl View for DeployFeedbackButton { let style = &theme .workspace .status_bar - .sidebar_buttons + .panel_buttons .item .style_for(state, active); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 7602ff7db8..4a1d488eaf 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1327,7 +1327,7 @@ impl Entity for ProjectPanel { type Event = Event; } -impl workspace::sidebar::SidebarItem for ProjectPanel { +impl workspace::dock::DockItem for ProjectPanel { fn should_show_badge(&self, _: &AppContext) -> bool { false } diff --git a/crates/terminal_view/src/terminal_button.rs b/crates/terminal_view/src/terminal_button.rs index 7b0da416ff..cacd21c733 100644 --- a/crates/terminal_view/src/terminal_button.rs +++ b/crates/terminal_view/src/terminal_button.rs @@ -52,7 +52,7 @@ impl View for TerminalButton { let style = theme .workspace .status_bar - .sidebar_buttons + .panel_buttons .item .style_for(state, active); diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 8c77295748..c239e0ef40 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -61,7 +61,7 @@ pub struct Workspace { pub pane_divider: Border, pub leader_border_opacity: f32, pub leader_border_width: f32, - pub sidebar: Sidebar, + pub dock: Dock, pub status_bar: StatusBar, pub toolbar: Toolbar, pub breadcrumb_height: f32, @@ -335,16 +335,16 @@ pub struct StatusBar { pub auto_update_progress_message: TextStyle, pub auto_update_done_message: TextStyle, pub lsp_status: Interactive, - pub sidebar_buttons: StatusBarSidebarButtons, + pub panel_buttons: StatusBarPanelButtons, pub diagnostic_summary: Interactive, pub diagnostic_message: Interactive, } #[derive(Deserialize, Default)] -pub struct StatusBarSidebarButtons { +pub struct StatusBarPanelButtons { pub group_left: ContainerStyle, pub group_right: ContainerStyle, - pub item: Interactive, + pub item: Interactive, pub badge: ContainerStyle, } @@ -375,14 +375,14 @@ pub struct StatusBarLspStatus { } #[derive(Deserialize, Default)] -pub struct Sidebar { +pub struct Dock { pub initial_size: f32, #[serde(flatten)] pub container: ContainerStyle, } #[derive(Clone, Deserialize, Default)] -pub struct SidebarItem { +pub struct DockItem { #[serde(flatten)] pub container: ContainerStyle, pub icon_color: Color, diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index a3d91adc91..6adcf27bd1 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -10,7 +10,7 @@ use gpui::{ use settings::{settings_file::SettingsFile, Settings}; use workspace::{ - item::Item, open_new, sidebar::SidebarSide, AppState, PaneBackdrop, Welcome, Workspace, + item::Item, open_new, dock::DockPosition, AppState, PaneBackdrop, Welcome, Workspace, WorkspaceId, }; @@ -29,7 +29,7 @@ pub fn init(cx: &mut AppContext) { pub fn show_welcome_experience(app_state: &Arc, cx: &mut AppContext) { open_new(&app_state, cx, |workspace, cx| { - workspace.toggle_sidebar(SidebarSide::Left, cx); + workspace.toggle_dock(DockPosition::Left, cx); let welcome_page = cx.add_view(|cx| WelcomePage::new(workspace, cx)); workspace.add_item_to_center(Box::new(welcome_page.clone()), cx); cx.focus(&welcome_page); diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/dock.rs similarity index 81% rename from crates/workspace/src/sidebar.rs rename to crates/workspace/src/dock.rs index 6463ab7d24..88fc9576bd 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/dock.rs @@ -7,7 +7,7 @@ use serde::Deserialize; use settings::Settings; use std::rc::Rc; -pub trait SidebarItem: View { +pub trait DockItem: View { fn should_activate_item_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { false } @@ -19,16 +19,16 @@ pub trait SidebarItem: View { } } -pub trait SidebarItemHandle { +pub trait DockItemHandle { fn id(&self) -> usize; fn should_show_badge(&self, cx: &WindowContext) -> bool; fn is_focused(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; } -impl SidebarItemHandle for ViewHandle +impl DockItemHandle for ViewHandle where - T: SidebarItem, + T: DockItem, { fn id(&self) -> usize { self.id() @@ -47,26 +47,26 @@ where } } -impl From<&dyn SidebarItemHandle> for AnyViewHandle { - fn from(val: &dyn SidebarItemHandle) -> Self { +impl From<&dyn DockItemHandle> for AnyViewHandle { + fn from(val: &dyn DockItemHandle) -> Self { val.as_any().clone() } } -pub struct Sidebar { - sidebar_side: SidebarSide, +pub struct Dock { + position: DockPosition, items: Vec, is_open: bool, active_item_ix: usize, } #[derive(Clone, Copy, Debug, Deserialize, PartialEq)] -pub enum SidebarSide { +pub enum DockPosition { Left, Right, } -impl SidebarSide { +impl DockPosition { fn to_resizable_side(self) -> Side { match self { Self::Left => Side::Right, @@ -78,27 +78,27 @@ impl SidebarSide { struct Item { icon_path: &'static str, tooltip: String, - view: Rc, + view: Rc, _subscriptions: [Subscription; 2], } -pub struct SidebarButtons { - sidebar: ViewHandle, +pub struct PanelButtons { + dock: ViewHandle, workspace: WeakViewHandle, } #[derive(Clone, Debug, Deserialize, PartialEq)] -pub struct ToggleSidebarItem { - pub sidebar_side: SidebarSide, +pub struct ToggleDockItem { + pub dock_position: DockPosition, pub item_index: usize, } -impl_actions!(workspace, [ToggleSidebarItem]); +impl_actions!(workspace, [ToggleDockItem]); -impl Sidebar { - pub fn new(sidebar_side: SidebarSide) -> Self { +impl Dock { + pub fn new(position: DockPosition) -> Self { Self { - sidebar_side, + position, items: Default::default(), active_item_ix: 0, is_open: false, @@ -126,7 +126,7 @@ impl Sidebar { cx.notify(); } - pub fn add_item( + pub fn add_item( &mut self, icon_path: &'static str, tooltip: String, @@ -171,7 +171,7 @@ impl Sidebar { cx.notify(); } - pub fn active_item(&self) -> Option<&Rc> { + pub fn active_item(&self) -> Option<&Rc> { if self.is_open { self.items.get(self.active_item_ix).map(|item| &item.view) } else { @@ -180,25 +180,25 @@ impl Sidebar { } } -impl Entity for Sidebar { +impl Entity for Dock { type Event = (); } -impl View for Sidebar { +impl View for Dock { fn ui_name() -> &'static str { - "Sidebar" + "Dock" } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(active_item) = self.active_item() { enum ResizeHandleTag {} - let style = &cx.global::().theme.workspace.sidebar; + let style = &cx.global::().theme.workspace.dock; ChildView::new(active_item.as_any(), cx) .contained() .with_style(style.container) .with_resize_handle::( - self.sidebar_side as usize, - self.sidebar_side.to_resizable_side(), + self.position as usize, + self.position.to_resizable_side(), 4., style.initial_size, cx, @@ -210,43 +210,43 @@ impl View for Sidebar { } } -impl SidebarButtons { +impl PanelButtons { pub fn new( - sidebar: ViewHandle, + dock: ViewHandle, workspace: WeakViewHandle, cx: &mut ViewContext, ) -> Self { - cx.observe(&sidebar, |_, _, cx| cx.notify()).detach(); - Self { sidebar, workspace } + cx.observe(&dock, |_, _, cx| cx.notify()).detach(); + Self { dock, workspace } } } -impl Entity for SidebarButtons { +impl Entity for PanelButtons { type Event = (); } -impl View for SidebarButtons { +impl View for PanelButtons { fn ui_name() -> &'static str { - "SidebarToggleButton" + "DockToggleButton" } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { let theme = &cx.global::().theme; let tooltip_style = theme.tooltip.clone(); - let theme = &theme.workspace.status_bar.sidebar_buttons; - let sidebar = self.sidebar.read(cx); + let theme = &theme.workspace.status_bar.panel_buttons; + let dock = self.dock.read(cx); let item_style = theme.item.clone(); let badge_style = theme.badge; - let active_ix = sidebar.active_item_ix; - let is_open = sidebar.is_open; - let sidebar_side = sidebar.sidebar_side; - let group_style = match sidebar_side { - SidebarSide::Left => theme.group_left, - SidebarSide::Right => theme.group_right, + let active_ix = dock.active_item_ix; + let is_open = dock.is_open; + let dock_position = dock.position; + let group_style = match dock_position { + DockPosition::Left => theme.group_left, + DockPosition::Right => theme.group_right, }; #[allow(clippy::needless_collect)] - let items = sidebar + let items = dock .items .iter() .map(|item| (item.icon_path, item.tooltip.clone(), item.view.clone())) @@ -255,8 +255,8 @@ impl View for SidebarButtons { Flex::row() .with_children(items.into_iter().enumerate().map( |(ix, (icon_path, tooltip, item_view))| { - let action = ToggleSidebarItem { - sidebar_side, + let action = ToggleDockItem { + dock_position, item_index: ix, }; MouseEventHandler::::new(ix, cx, |state, cx| { @@ -291,7 +291,7 @@ impl View for SidebarButtons { let action = action.clone(); cx.window_context().defer(move |cx| { workspace.update(cx, |workspace, cx| { - workspace.toggle_sidebar_item(&action, cx) + workspace.toggle_panel(&action, cx) }); }); } @@ -312,7 +312,7 @@ impl View for SidebarButtons { } } -impl StatusItemView for SidebarButtons { +impl StatusItemView for PanelButtons { fn set_active_pane_item( &mut self, _: Option<&dyn crate::ItemHandle>, diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 43c4544611..49fd882b65 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -766,7 +766,7 @@ impl FollowableItemHandle for ViewHandle { #[cfg(test)] pub(crate) mod test { use super::{Item, ItemEvent}; - use crate::{sidebar::SidebarItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; + use crate::{dock::DockItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; use gpui::{ elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -1060,5 +1060,5 @@ pub(crate) mod test { } } - impl SidebarItem for TestItem {} + impl DockItem for TestItem {} } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 84d1832df7..4ab9e07526 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -9,7 +9,7 @@ pub mod pane_group; mod persistence; pub mod searchable; pub mod shared_screen; -pub mod sidebar; +pub mod dock; mod status_bar; mod toolbar; @@ -74,7 +74,7 @@ use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use serde::Deserialize; use settings::{Autosave, Settings}; use shared_screen::SharedScreen; -use sidebar::{Sidebar, SidebarButtons, SidebarSide, ToggleSidebarItem}; +use dock::{Dock, PanelButtons, DockPosition, ToggleDockItem}; use status_bar::StatusBar; pub use status_bar::StatusItemView; use theme::{Theme, ThemeRegistry}; @@ -114,7 +114,7 @@ actions!( ActivatePreviousPane, ActivateNextPane, FollowNextCollaborator, - ToggleLeftSidebar, + ToggleLeftDock, NewTerminal, NewSearch, Feedback, @@ -229,7 +229,7 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { workspace.save_active_item(true, cx).detach_and_log_err(cx); }, ); - cx.add_action(Workspace::toggle_sidebar_item); + cx.add_action(Workspace::toggle_panel); cx.add_action(Workspace::focus_center); cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| { workspace.activate_previous_pane(cx) @@ -237,8 +237,8 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| { workspace.activate_next_pane(cx) }); - cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftSidebar, cx| { - workspace.toggle_sidebar(SidebarSide::Left, cx); + cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| { + workspace.toggle_dock(DockPosition::Left, cx); }); cx.add_action(Workspace::activate_pane_at_index); @@ -442,8 +442,8 @@ pub struct Workspace { remote_entity_subscription: Option, modal: Option, center: PaneGroup, - left_sidebar: ViewHandle, - right_sidebar: ViewHandle, + left_dock: ViewHandle, + right_dock: ViewHandle, panes: Vec>, panes_by_item: HashMap>, active_pane: ViewHandle, @@ -562,16 +562,16 @@ impl Workspace { cx.emit_global(WorkspaceCreated(weak_handle.clone())); - let left_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Left)); - let right_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Right)); - let left_sidebar_buttons = - cx.add_view(|cx| SidebarButtons::new(left_sidebar.clone(), weak_handle.clone(), cx)); - let right_sidebar_buttons = - cx.add_view(|cx| SidebarButtons::new(right_sidebar.clone(), weak_handle.clone(), cx)); + let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left)); + let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right)); + let left_dock_buttons = + cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx)); + let right_dock_buttons = + cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); let status_bar = cx.add_view(|cx| { let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); - status_bar.add_left_item(left_sidebar_buttons, cx); - status_bar.add_right_item(right_sidebar_buttons, cx); + status_bar.add_left_item(left_dock_buttons, cx); + status_bar.add_right_item(right_dock_buttons, cx); status_bar }); @@ -621,8 +621,8 @@ impl Workspace { titlebar_item: None, notifications: Default::default(), remote_entity_subscription: None, - left_sidebar, - right_sidebar, + left_dock: left_dock, + right_dock: right_dock, project: project.clone(), leader_state: Default::default(), follower_states_by_leader: Default::default(), @@ -813,12 +813,12 @@ impl Workspace { self.weak_self.clone() } - pub fn left_sidebar(&self) -> &ViewHandle { - &self.left_sidebar + pub fn left_dock(&self) -> &ViewHandle { + &self.left_dock } - pub fn right_sidebar(&self) -> &ViewHandle { - &self.right_sidebar + pub fn right_dock(&self) -> &ViewHandle { + &self.right_dock } pub fn status_bar(&self) -> &ViewHandle { @@ -1306,14 +1306,14 @@ impl Workspace { } } - pub fn toggle_sidebar(&mut self, sidebar_side: SidebarSide, cx: &mut ViewContext) { - let sidebar = match sidebar_side { - SidebarSide::Left => &mut self.left_sidebar, - SidebarSide::Right => &mut self.right_sidebar, + pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { + let dock = match dock_side { + DockPosition::Left => &mut self.left_dock, + DockPosition::Right => &mut self.right_dock, }; - sidebar.update(cx, |sidebar, cx| { - let open = !sidebar.is_open(); - sidebar.set_open(open, cx); + dock.update(cx, |dock, cx| { + let open = !dock.is_open(); + dock.set_open(open, cx); }); self.serialize_workspace(cx); @@ -1322,19 +1322,19 @@ impl Workspace { cx.notify(); } - pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext) { - let sidebar = match action.sidebar_side { - SidebarSide::Left => &mut self.left_sidebar, - SidebarSide::Right => &mut self.right_sidebar, + pub fn toggle_panel(&mut self, action: &ToggleDockItem, cx: &mut ViewContext) { + let dock = match action.dock_position { + DockPosition::Left => &mut self.left_dock, + DockPosition::Right => &mut self.right_dock, }; - let active_item = sidebar.update(cx, move |sidebar, cx| { - if sidebar.is_open() && sidebar.active_item_ix() == action.item_index { - sidebar.set_open(false, cx); + let active_item = dock.update(cx, move |dock, cx| { + if dock.is_open() && dock.active_item_ix() == action.item_index { + dock.set_open(false, cx); None } else { - sidebar.set_open(true, cx); - sidebar.activate_item(action.item_index, cx); - sidebar.active_item().cloned() + dock.set_open(true, cx); + dock.activate_item(action.item_index, cx); + dock.active_item().cloned() } }); @@ -1353,20 +1353,20 @@ impl Workspace { cx.notify(); } - pub fn toggle_sidebar_item_focus( + pub fn toggle_panel_focus( &mut self, - sidebar_side: SidebarSide, - item_index: usize, + dock_position: DockPosition, + panel_index: usize, cx: &mut ViewContext, ) { - let sidebar = match sidebar_side { - SidebarSide::Left => &mut self.left_sidebar, - SidebarSide::Right => &mut self.right_sidebar, + let dock = match dock_position { + DockPosition::Left => &mut self.left_dock, + DockPosition::Right => &mut self.right_dock, }; - let active_item = sidebar.update(cx, |sidebar, cx| { - sidebar.set_open(true, cx); - sidebar.activate_item(item_index, cx); - sidebar.active_item().cloned() + let active_item = dock.update(cx, |dock, cx| { + dock.set_open(true, cx); + dock.activate_item(panel_index, cx); + dock.active_item().cloned() }); if let Some(active_item) = active_item { if active_item.is_focused(cx) { @@ -2452,7 +2452,7 @@ impl Workspace { id: self.database_id, location, center_group, - left_sidebar_open: self.left_sidebar.read(cx).is_open(), + left_sidebar_open: self.left_dock.read(cx).is_open(), bounds: Default::default(), display: Default::default(), }; @@ -2509,10 +2509,10 @@ impl Workspace { } } - if workspace.left_sidebar().read(cx).is_open() + if workspace.left_dock().read(cx).is_open() != serialized_workspace.left_sidebar_open { - workspace.toggle_sidebar(SidebarSide::Left, cx); + workspace.toggle_dock(DockPosition::Left, cx); } cx.notify(); @@ -2596,9 +2596,9 @@ impl View for Workspace { let project = self.project.clone(); Flex::row() .with_children( - if self.left_sidebar.read(cx).active_item().is_some() { + if self.left_dock.read(cx).active_item().is_some() { Some( - ChildView::new(&self.left_sidebar, cx) + ChildView::new(&self.left_dock, cx) .constrained() .dynamically(|constraint, _, cx| { SizeConstraint::new( @@ -2633,9 +2633,9 @@ impl View for Workspace { .flex(1., true), ) .with_children( - if self.right_sidebar.read(cx).active_item().is_some() { + if self.right_dock.read(cx).active_item().is_some() { Some( - ChildView::new(&self.right_sidebar, cx) + ChildView::new(&self.right_dock, cx) .constrained() .dynamically(|constraint, _, cx| { SizeConstraint::new( @@ -2803,7 +2803,7 @@ pub fn open_paths( workspace.update(&mut cx, |workspace, cx| { if contains_directory { - workspace.toggle_sidebar(SidebarSide::Left, cx); + workspace.toggle_dock(DockPosition::Left, cx); } })?; diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 741b61f323..a98147c51b 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -89,7 +89,7 @@ pub fn menus() -> Vec> { MenuItem::action("Zoom Out", super::DecreaseBufferFontSize), MenuItem::action("Reset Zoom", super::ResetBufferFontSize), MenuItem::separator(), - MenuItem::action("Toggle Left Sidebar", workspace::ToggleLeftSidebar), + MenuItem::action("Toggle Left Dock", workspace::ToggleLeftDock), MenuItem::submenu(Menu { name: "Editor Layout", items: vec![ diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 46443eb77d..b3968e45fc 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -36,7 +36,7 @@ use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; pub use workspace; use workspace::{ - create_and_open_local_file, open_new, sidebar::SidebarSide, AppState, NewFile, NewWindow, + create_and_open_local_file, open_new, dock::DockPosition, AppState, NewFile, NewWindow, Workspace, }; @@ -242,7 +242,7 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { |workspace: &mut Workspace, _: &project_panel::ToggleFocus, cx: &mut ViewContext| { - workspace.toggle_sidebar_item_focus(SidebarSide::Left, 0, cx); + workspace.toggle_panel_focus(DockPosition::Left, 0, cx); }, ); cx.add_global_action({ @@ -312,8 +312,8 @@ pub fn initialize_workspace( workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); let project_panel = ProjectPanel::new(workspace, cx); - workspace.left_sidebar().update(cx, |sidebar, cx| { - sidebar.add_item( + workspace.left_dock().update(cx, |dock, cx| { + dock.add_item( "icons/folder_tree_16.svg", "Project Panel".to_string(), project_panel, @@ -658,7 +658,7 @@ mod tests { .unwrap(); workspace_1.update(cx, |workspace, cx| { assert_eq!(workspace.worktrees(cx).count(), 2); - assert!(workspace.left_sidebar().read(cx).is_open()); + assert!(workspace.left_dock().read(cx).is_open()); assert!(workspace.active_pane().is_focused(cx)); }); @@ -701,7 +701,7 @@ mod tests { .collect::>(), &[Path::new("/root/c").into(), Path::new("/root/d").into()] ); - assert!(workspace.left_sidebar().read(cx).is_open()); + assert!(workspace.left_dock().read(cx).is_open()); assert!(workspace.active_pane().is_focused(cx)); }); } diff --git a/styles/src/styleTree/statusBar.ts b/styles/src/styleTree/statusBar.ts index c55160c336..8a711992ec 100644 --- a/styles/src/styleTree/statusBar.ts +++ b/styles/src/styleTree/statusBar.ts @@ -93,7 +93,7 @@ export default function statusBar(colorScheme: ColorScheme) { }, }, }, - sidebarButtons: { + panelButtons: { groupLeft: {}, groupRight: {}, item: { diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 9d0c4de9f7..fe359b0501 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -118,7 +118,7 @@ export default function workspace(colorScheme: ColorScheme) { }, cursor: "Arrow", }, - sidebar: { + dock: { initialSize: 240, border: border(layer, { left: true, right: true }), }, @@ -310,19 +310,6 @@ export default function workspace(colorScheme: ColorScheme) { width: 400, margin: { right: 10, bottom: 10 }, }, - dock: { - initialSizeRight: 640, - initialSizeBottom: 304, - wash_color: withOpacity(background(colorScheme.highest), 0.5), - panel: { - border: border(colorScheme.middle), - }, - maximized: { - margin: 32, - border: border(colorScheme.highest, { overlay: true }), - shadow: colorScheme.modalShadow, - }, - }, dropTargetOverlayColor: withOpacity(foreground(layer, "variant"), 0.5), } } From 1ddbda509535fc9e17bdabcbfe204537de472a2c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 May 2023 16:13:36 -0600 Subject: [PATCH 03/61] Rename DockItem to Panel --- crates/copilot_button/src/copilot_button.rs | 2 +- crates/feedback/src/deploy_feedback_button.rs | 2 +- crates/project_panel/src/project_panel.rs | 2 +- crates/terminal_view/src/terminal_button.rs | 2 +- crates/theme/src/theme.rs | 4 +-- crates/workspace/src/dock.rs | 27 ++++++++++--------- crates/workspace/src/item.rs | 4 +-- crates/workspace/src/workspace.rs | 4 +-- styles/src/styleTree/statusBar.ts | 2 +- 9 files changed, 25 insertions(+), 24 deletions(-) diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 0d6bcc115c..884d1358f7 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -65,7 +65,7 @@ impl View for CopilotButton { .workspace .status_bar .panel_buttons - .item + .button .style_for(state, active); Flex::row() diff --git a/crates/feedback/src/deploy_feedback_button.rs b/crates/feedback/src/deploy_feedback_button.rs index 5fdb378eb6..fc90b38750 100644 --- a/crates/feedback/src/deploy_feedback_button.rs +++ b/crates/feedback/src/deploy_feedback_button.rs @@ -41,7 +41,7 @@ impl View for DeployFeedbackButton { .workspace .status_bar .panel_buttons - .item + .button .style_for(state, active); Svg::new("icons/feedback_16.svg") diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 4a1d488eaf..35ab314f3a 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1327,7 +1327,7 @@ impl Entity for ProjectPanel { type Event = Event; } -impl workspace::dock::DockItem for ProjectPanel { +impl workspace::dock::Panel for ProjectPanel { fn should_show_badge(&self, _: &AppContext) -> bool { false } diff --git a/crates/terminal_view/src/terminal_button.rs b/crates/terminal_view/src/terminal_button.rs index cacd21c733..0747f86d2d 100644 --- a/crates/terminal_view/src/terminal_button.rs +++ b/crates/terminal_view/src/terminal_button.rs @@ -53,7 +53,7 @@ impl View for TerminalButton { .workspace .status_bar .panel_buttons - .item + .button .style_for(state, active); Flex::row() diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index c239e0ef40..bc6d639447 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -344,7 +344,7 @@ pub struct StatusBar { pub struct StatusBarPanelButtons { pub group_left: ContainerStyle, pub group_right: ContainerStyle, - pub item: Interactive, + pub button: Interactive, pub badge: ContainerStyle, } @@ -382,7 +382,7 @@ pub struct Dock { } #[derive(Clone, Deserialize, Default)] -pub struct DockItem { +pub struct PanelButton { #[serde(flatten)] pub container: ContainerStyle, pub icon_color: Color, diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 88fc9576bd..432ca646c8 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -7,7 +7,7 @@ use serde::Deserialize; use settings::Settings; use std::rc::Rc; -pub trait DockItem: View { +pub trait Panel: View { fn should_activate_item_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { false } @@ -19,16 +19,17 @@ pub trait DockItem: View { } } -pub trait DockItemHandle { +pub trait PanelHandle { + fn id(&self) -> usize; fn should_show_badge(&self, cx: &WindowContext) -> bool; fn is_focused(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; } -impl DockItemHandle for ViewHandle +impl PanelHandle for ViewHandle where - T: DockItem, + T: Panel, { fn id(&self) -> usize { self.id() @@ -47,8 +48,8 @@ where } } -impl From<&dyn DockItemHandle> for AnyViewHandle { - fn from(val: &dyn DockItemHandle) -> Self { +impl From<&dyn PanelHandle> for AnyViewHandle { + fn from(val: &dyn PanelHandle) -> Self { val.as_any().clone() } } @@ -78,7 +79,7 @@ impl DockPosition { struct Item { icon_path: &'static str, tooltip: String, - view: Rc, + view: Rc, _subscriptions: [Subscription; 2], } @@ -88,12 +89,12 @@ pub struct PanelButtons { } #[derive(Clone, Debug, Deserialize, PartialEq)] -pub struct ToggleDockItem { +pub struct TogglePanel { pub dock_position: DockPosition, pub item_index: usize, } -impl_actions!(workspace, [ToggleDockItem]); +impl_actions!(workspace, [TogglePanel]); impl Dock { pub fn new(position: DockPosition) -> Self { @@ -126,7 +127,7 @@ impl Dock { cx.notify(); } - pub fn add_item( + pub fn add_item( &mut self, icon_path: &'static str, tooltip: String, @@ -171,7 +172,7 @@ impl Dock { cx.notify(); } - pub fn active_item(&self) -> Option<&Rc> { + pub fn active_item(&self) -> Option<&Rc> { if self.is_open { self.items.get(self.active_item_ix).map(|item| &item.view) } else { @@ -235,7 +236,7 @@ impl View for PanelButtons { let tooltip_style = theme.tooltip.clone(); let theme = &theme.workspace.status_bar.panel_buttons; let dock = self.dock.read(cx); - let item_style = theme.item.clone(); + let item_style = theme.button.clone(); let badge_style = theme.badge; let active_ix = dock.active_item_ix; let is_open = dock.is_open; @@ -255,7 +256,7 @@ impl View for PanelButtons { Flex::row() .with_children(items.into_iter().enumerate().map( |(ix, (icon_path, tooltip, item_view))| { - let action = ToggleDockItem { + let action = TogglePanel { dock_position, item_index: ix, }; diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 49fd882b65..e7571c1bf6 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -766,7 +766,7 @@ impl FollowableItemHandle for ViewHandle { #[cfg(test)] pub(crate) mod test { use super::{Item, ItemEvent}; - use crate::{dock::DockItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; + use crate::{dock::Panel, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; use gpui::{ elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -1060,5 +1060,5 @@ pub(crate) mod test { } } - impl DockItem for TestItem {} + impl Panel for TestItem {} } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 4ab9e07526..f1bcb3928f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -74,7 +74,7 @@ use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use serde::Deserialize; use settings::{Autosave, Settings}; use shared_screen::SharedScreen; -use dock::{Dock, PanelButtons, DockPosition, ToggleDockItem}; +use dock::{Dock, PanelButtons, DockPosition, TogglePanel}; use status_bar::StatusBar; pub use status_bar::StatusItemView; use theme::{Theme, ThemeRegistry}; @@ -1322,7 +1322,7 @@ impl Workspace { cx.notify(); } - pub fn toggle_panel(&mut self, action: &ToggleDockItem, cx: &mut ViewContext) { + pub fn toggle_panel(&mut self, action: &TogglePanel, cx: &mut ViewContext) { let dock = match action.dock_position { DockPosition::Left => &mut self.left_dock, DockPosition::Right => &mut self.right_dock, diff --git a/styles/src/styleTree/statusBar.ts b/styles/src/styleTree/statusBar.ts index 8a711992ec..614b6ab34d 100644 --- a/styles/src/styleTree/statusBar.ts +++ b/styles/src/styleTree/statusBar.ts @@ -96,7 +96,7 @@ export default function statusBar(colorScheme: ColorScheme) { panelButtons: { groupLeft: {}, groupRight: {}, - item: { + button: { ...statusContainer, iconSize: 16, iconColor: foreground(layer, "variant"), From 6ddc610fa21905f366d5032a381e2f857a400754 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 8 May 2023 18:31:08 +0200 Subject: [PATCH 04/61] WIP: Start on `TerminalPanel` --- crates/terminal_view/src/terminal_panel.rs | 76 +++++++++++++++++++ crates/terminal_view/src/terminal_view.rs | 1 + crates/theme/src/theme.rs | 1 + crates/workspace/src/dock.rs | 4 +- crates/workspace/src/pane.rs | 9 +-- crates/workspace/src/workspace.rs | 87 +++++++++++++--------- crates/zed/src/zed.rs | 18 +++-- styles/src/styleTree/statusBar.ts | 1 + 8 files changed, 149 insertions(+), 48 deletions(-) create mode 100644 crates/terminal_view/src/terminal_panel.rs diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs new file mode 100644 index 0000000000..19b34209a3 --- /dev/null +++ b/crates/terminal_view/src/terminal_panel.rs @@ -0,0 +1,76 @@ +use gpui::{elements::*, Entity, ModelHandle, View, ViewContext, ViewHandle, WeakViewHandle}; +use project::Project; +use settings::{Settings, WorkingDirectory}; +use util::ResultExt; +use workspace::{dock::Panel, Pane, Workspace}; + +use crate::TerminalView; + +pub struct TerminalPanel { + project: ModelHandle, + pane: ViewHandle, + workspace: WeakViewHandle, +} + +impl TerminalPanel { + pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + Self { + project: workspace.project().clone(), + pane: cx.add_view(|cx| { + Pane::new( + workspace.weak_handle(), + workspace.app_state().background_actions, + cx, + ) + }), + workspace: workspace.weak_handle(), + } + } +} + +impl Entity for TerminalPanel { + type Event = (); +} + +impl View for TerminalPanel { + fn ui_name() -> &'static str { + "TerminalPanel" + } + + fn render(&mut self, cx: &mut ViewContext) -> gpui::AnyElement { + ChildView::new(&self.pane, cx).into_any() + } + + fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { + if self.pane.read(cx).items_len() == 0 { + if let Some(workspace) = self.workspace.upgrade(cx) { + let working_directory_strategy = cx + .global::() + .terminal_overrides + .working_directory + .clone() + .unwrap_or(WorkingDirectory::CurrentProjectDirectory); + let working_directory = crate::get_working_directory( + workspace.read(cx), + cx, + working_directory_strategy, + ); + let window_id = cx.window_id(); + if let Some(terminal) = self.project.update(cx, |project, cx| { + project + .create_terminal(working_directory, window_id, cx) + .log_err() + }) { + workspace.update(cx, |workspace, cx| { + let terminal = Box::new(cx.add_view(|cx| { + TerminalView::new(terminal, workspace.database_id(), cx) + })); + Pane::add_item(workspace, &self.pane, terminal, true, true, None, cx); + }); + } + } + } + } +} + +impl Panel for TerminalPanel {} diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index dfb2334dc5..e3a5153ef3 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1,6 +1,7 @@ mod persistence; pub mod terminal_button; pub mod terminal_element; +pub mod terminal_panel; use context_menu::{ContextMenu, ContextMenuItem}; use dirs::home_dir; diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index bc6d639447..67d07a3998 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -343,6 +343,7 @@ pub struct StatusBar { #[derive(Deserialize, Default)] pub struct StatusBarPanelButtons { pub group_left: ContainerStyle, + pub group_bottom: ContainerStyle, pub group_right: ContainerStyle, pub button: Interactive, pub badge: ContainerStyle, diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 432ca646c8..70708837e6 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -20,7 +20,6 @@ pub trait Panel: View { } pub trait PanelHandle { - fn id(&self) -> usize; fn should_show_badge(&self, cx: &WindowContext) -> bool; fn is_focused(&self, cx: &WindowContext) -> bool; @@ -64,6 +63,7 @@ pub struct Dock { #[derive(Clone, Copy, Debug, Deserialize, PartialEq)] pub enum DockPosition { Left, + Bottom, Right, } @@ -71,6 +71,7 @@ impl DockPosition { fn to_resizable_side(self) -> Side { match self { Self::Left => Side::Right, + Self::Bottom => Side::Bottom, Self::Right => Side::Left, } } @@ -243,6 +244,7 @@ impl View for PanelButtons { let dock_position = dock.position; let group_style = match dock_position { DockPosition::Left => theme.group_left, + DockPosition::Bottom => theme.group_bottom, DockPosition::Right => theme.group_right, }; diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 1aed1c011e..2597b4af81 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2,9 +2,7 @@ mod dragged_item_receiver; use super::{ItemHandle, SplitDirection}; use crate::{ - item::WeakItemHandle, - toolbar::Toolbar, - Item, NewFile, NewSearch, NewTerminal, Workspace, + item::WeakItemHandle, toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, Workspace, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -485,7 +483,7 @@ impl Pane { } } - pub(crate) fn add_item( + pub fn add_item( workspace: &mut Workspace, pane: &ViewHandle, item: Box, @@ -1594,7 +1592,8 @@ impl Pane { "icons/split_12.svg", cx, |pane, cx| pane.deploy_split_menu(cx), - self.tab_bar_context_menu.handle_if_kind(TabBarContextMenuKind::Split), + self.tab_bar_context_menu + .handle_if_kind(TabBarContextMenuKind::Split), )) .contained() .with_style(theme.workspace.tab_bar.pane_button_container) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f1bcb3928f..ee02a97439 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1,3 +1,4 @@ +pub mod dock; /// NOTE: Focus only 'takes' after an update has flushed_effects. /// /// This may cause issues when you're trying to write tests that use workspace focus to add items at @@ -9,7 +10,6 @@ pub mod pane_group; mod persistence; pub mod searchable; pub mod shared_screen; -pub mod dock; mod status_bar; mod toolbar; @@ -60,6 +60,7 @@ use crate::{ notifications::simple_message_notification::MessageNotification, persistence::model::{SerializedPane, SerializedPaneGroup, SerializedWorkspace}, }; +use dock::{Dock, DockPosition, PanelButtons, TogglePanel}; use lazy_static::lazy_static; use notifications::{NotificationHandle, NotifyResultExt}; pub use pane::*; @@ -74,7 +75,6 @@ use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use serde::Deserialize; use settings::{Autosave, Settings}; use shared_screen::SharedScreen; -use dock::{Dock, PanelButtons, DockPosition, TogglePanel}; use status_bar::StatusBar; pub use status_bar::StatusItemView; use theme::{Theme, ThemeRegistry}; @@ -443,6 +443,7 @@ pub struct Workspace { modal: Option, center: PaneGroup, left_dock: ViewHandle, + bottom_dock: ViewHandle, right_dock: ViewHandle, panes: Vec>, panes_by_item: HashMap>, @@ -526,8 +527,8 @@ impl Workspace { let weak_handle = cx.weak_handle(); - let center_pane = cx - .add_view(|cx| Pane::new(weak_handle.clone(), app_state.background_actions, cx)); + let center_pane = + cx.add_view(|cx| Pane::new(weak_handle.clone(), app_state.background_actions, cx)); cx.subscribe(¢er_pane, Self::handle_pane_event).detach(); cx.focus(¢er_pane); cx.emit(Event::PaneAdded(center_pane.clone())); @@ -563,14 +564,18 @@ impl Workspace { cx.emit_global(WorkspaceCreated(weak_handle.clone())); let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left)); + let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom)); let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right)); let left_dock_buttons = cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx)); + let bottom_dock_buttons = + cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx)); let right_dock_buttons = cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); let status_bar = cx.add_view(|cx| { let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); status_bar.add_left_item(left_dock_buttons, cx); + status_bar.add_right_item(bottom_dock_buttons, cx); status_bar.add_right_item(right_dock_buttons, cx); status_bar }); @@ -621,8 +626,9 @@ impl Workspace { titlebar_item: None, notifications: Default::default(), remote_entity_subscription: None, - left_dock: left_dock, - right_dock: right_dock, + left_dock, + bottom_dock, + right_dock, project: project.clone(), leader_state: Default::default(), follower_states_by_leader: Default::default(), @@ -817,6 +823,10 @@ impl Workspace { &self.left_dock } + pub fn bottom_dock(&self) -> &ViewHandle { + &self.bottom_dock + } + pub fn right_dock(&self) -> &ViewHandle { &self.right_dock } @@ -1309,6 +1319,7 @@ impl Workspace { pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { let dock = match dock_side { DockPosition::Left => &mut self.left_dock, + DockPosition::Bottom => &mut self.bottom_dock, DockPosition::Right => &mut self.right_dock, }; dock.update(cx, |dock, cx| { @@ -1325,6 +1336,7 @@ impl Workspace { pub fn toggle_panel(&mut self, action: &TogglePanel, cx: &mut ViewContext) { let dock = match action.dock_position { DockPosition::Left => &mut self.left_dock, + DockPosition::Bottom => &mut self.bottom_dock, DockPosition::Right => &mut self.right_dock, }; let active_item = dock.update(cx, move |dock, cx| { @@ -1361,6 +1373,7 @@ impl Workspace { ) { let dock = match dock_position { DockPosition::Left => &mut self.left_dock, + DockPosition::Bottom => &mut self.bottom_dock, DockPosition::Right => &mut self.right_dock, }; let active_item = dock.update(cx, |dock, cx| { @@ -1387,13 +1400,8 @@ impl Workspace { } fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { - let pane = cx.add_view(|cx| { - Pane::new( - self.weak_handle(), - self.app_state.background_actions, - cx, - ) - }); + let pane = + cx.add_view(|cx| Pane::new(self.weak_handle(), self.app_state.background_actions, cx)); cx.subscribe(&pane, Self::handle_pane_event).detach(); self.panes.push(pane.clone()); cx.focus(&pane); @@ -1560,7 +1568,7 @@ impl Workspace { status_bar.set_active_pane(&self.active_pane, cx); }); self.active_item_path_changed(cx); - self.last_active_center_pane = Some(pane.downgrade()); + self.last_active_center_pane = Some(pane.downgrade()); cx.notify(); } @@ -2470,13 +2478,12 @@ impl Workspace { cx: &mut AppContext, ) { cx.spawn(|mut cx| async move { - let (project, old_center_pane) = - workspace.read_with(&cx, |workspace, _| { - ( - workspace.project().clone(), - workspace.last_active_center_pane.clone(), - ) - })?; + let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| { + ( + workspace.project().clone(), + workspace.last_active_center_pane.clone(), + ) + })?; // Traverse the splits tree and add to things let center_group = serialized_workspace @@ -2615,22 +2622,28 @@ impl View for Workspace { }, ) .with_child( - FlexItem::new( - Flex::column() - .with_child( - FlexItem::new(self.center.render( - &project, - &theme, - &self.follower_states_by_leader, - self.active_call(), - self.active_pane(), - &self.app_state, - cx, - )) - .flex(1., true), - ) - ) - .flex(1., true), + Flex::column() + .with_child( + FlexItem::new(self.center.render( + &project, + &theme, + &self.follower_states_by_leader, + self.active_call(), + self.active_pane(), + &self.app_state, + cx, + )) + .flex(1., true), + ) + .with_children( + if self.bottom_dock.read(cx).active_item().is_some() + { + Some(ChildView::new(&self.bottom_dock, cx)) + } else { + None + }, + ) + .flex(1., true), ) .with_children( if self.right_dock.read(cx).active_item().is_some() { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index b3968e45fc..52b0ad0898 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -31,12 +31,12 @@ use serde::Deserialize; use serde_json::to_string_pretty; use settings::{Settings, DEFAULT_SETTINGS_ASSET_PATH}; use std::{borrow::Cow, str, sync::Arc}; -use terminal_view::terminal_button::TerminalButton; +use terminal_view::terminal_panel::TerminalPanel; use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; pub use workspace; use workspace::{ - create_and_open_local_file, open_new, dock::DockPosition, AppState, NewFile, NewWindow, + create_and_open_local_file, dock::DockPosition, open_new, AppState, NewFile, NewWindow, Workspace, }; @@ -318,10 +318,19 @@ pub fn initialize_workspace( "Project Panel".to_string(), project_panel, cx, - ) + ); + }); + + let terminal_panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); + workspace.bottom_dock().update(cx, |dock, cx| { + dock.add_item( + "icons/terminal_12.svg", + "Terminals".to_string(), + terminal_panel, + cx, + ); }); - let toggle_terminal = cx.add_view(|cx| TerminalButton::new(workspace_handle.clone(), cx)); let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx)); let diagnostic_summary = cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); @@ -335,7 +344,6 @@ pub fn initialize_workspace( workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(activity_indicator, cx); - status_bar.add_right_item(toggle_terminal, cx); status_bar.add_right_item(feedback_button, cx); status_bar.add_right_item(copilot, cx); status_bar.add_right_item(active_buffer_language, cx); diff --git a/styles/src/styleTree/statusBar.ts b/styles/src/styleTree/statusBar.ts index 614b6ab34d..eca537c150 100644 --- a/styles/src/styleTree/statusBar.ts +++ b/styles/src/styleTree/statusBar.ts @@ -95,6 +95,7 @@ export default function statusBar(colorScheme: ColorScheme) { }, panelButtons: { groupLeft: {}, + groupBottom: {}, groupRight: {}, button: { ...statusContainer, From 916612caf1fd12aaa9e782b61f36bb3f2579cc73 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 09:56:22 +0200 Subject: [PATCH 05/61] Prevent dragging items that aren't terminals to the terminal panel --- crates/terminal_view/src/terminal_panel.rs | 15 ++- crates/workspace/src/pane.rs | 36 ++++--- .../src/pane/dragged_item_receiver.rs | 101 ++++++++++-------- 3 files changed, 88 insertions(+), 64 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 19b34209a3..ddc53537b7 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -2,7 +2,7 @@ use gpui::{elements::*, Entity, ModelHandle, View, ViewContext, ViewHandle, Weak use project::Project; use settings::{Settings, WorkingDirectory}; use util::ResultExt; -use workspace::{dock::Panel, Pane, Workspace}; +use workspace::{dock::Panel, DraggedItem, Pane, Workspace}; use crate::TerminalView; @@ -17,11 +17,20 @@ impl TerminalPanel { Self { project: workspace.project().clone(), pane: cx.add_view(|cx| { - Pane::new( + let window_id = cx.window_id(); + let mut pane = Pane::new( workspace.weak_handle(), workspace.app_state().background_actions, cx, - ) + ); + pane.on_can_drop(move |drag_and_drop, cx| { + drag_and_drop + .currently_dragged::(window_id) + .map_or(false, |(_, item)| { + item.handle.act_as::(cx).is_some() + }) + }); + pane }), workspace: workspace.weak_handle(), } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 2597b4af81..fc3fda8e6a 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -7,8 +7,8 @@ use crate::{ use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; use context_menu::{ContextMenu, ContextMenuItem}; -use drag_and_drop::Draggable; -pub use dragged_item_receiver::{dragged_item_receiver, handle_dropped_item}; +use drag_and_drop::{DragAndDrop, Draggable}; +use dragged_item_receiver::dragged_item_receiver; use futures::StreamExt; use gpui::{ actions, @@ -148,6 +148,7 @@ pub struct Pane { _background_actions: BackgroundActions, workspace: WeakViewHandle, has_focus: bool, + can_drop: Rc, &WindowContext) -> bool>, } pub struct ItemNavHistory { @@ -185,9 +186,9 @@ pub struct NavigationEntry { pub data: Option>, } -struct DraggedItem { - item: Box, - pane: WeakViewHandle, +pub struct DraggedItem { + pub handle: Box, + pub pane: WeakViewHandle, } pub enum ReorderBehavior { @@ -253,6 +254,7 @@ impl Pane { _background_actions: background_actions, workspace, has_focus: false, + can_drop: Rc::new(|_, _| true), } } @@ -273,6 +275,13 @@ impl Pane { self.has_focus } + pub fn on_can_drop(&mut self, can_drop: F) + where + F: 'static + Fn(&DragAndDrop, &WindowContext) -> bool, + { + self.can_drop = Rc::new(can_drop); + } + pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { ItemNavHistory { history: self.nav_history.clone(), @@ -1293,7 +1302,7 @@ impl Pane { row.add_child({ enum TabDragReceiver {} let mut receiver = - dragged_item_receiver::(ix, ix, true, None, cx, { + dragged_item_receiver::(self, ix, ix, true, None, cx, { let item = item.clone(); let pane = pane.clone(); let detail = detail.clone(); @@ -1372,7 +1381,7 @@ impl Pane { receiver.as_draggable( DraggedItem { - item, + handle: item, pane: pane.clone(), }, { @@ -1382,7 +1391,7 @@ impl Pane { move |dragged_item: &DraggedItem, cx: &mut ViewContext| { let tab_style = &theme.workspace.tab_bar.dragged_tab; Self::render_dragged_tab( - &dragged_item.item, + &dragged_item.handle, dragged_item.pane.clone(), false, detail, @@ -1402,7 +1411,7 @@ impl Pane { let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false); enum Filler {} row.add_child( - dragged_item_receiver::(0, filler_index, true, None, cx, |_, _| { + dragged_item_receiver::(self, 0, filler_index, true, None, cx, |_, _| { Empty::new() .contained() .with_style(filler_style.container) @@ -1601,11 +1610,7 @@ impl Pane { .into_any() } - fn render_blank_pane( - &mut self, - theme: &Theme, - _cx: &mut ViewContext, - ) -> AnyElement { + fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext) -> AnyElement { let background = theme.workspace.background; Empty::new() .contained() @@ -1668,6 +1673,7 @@ impl View for Pane { .with_child({ enum PaneContentTabDropTarget {} dragged_item_receiver::( + self, 0, self.active_item_index + 1, false, @@ -1696,7 +1702,7 @@ impl View for Pane { enum EmptyPane {} let theme = cx.global::().theme.clone(); - dragged_item_receiver::(0, 0, false, None, cx, |_, cx| { + dragged_item_receiver::(self, 0, 0, false, None, cx, |_, cx| { self.render_blank_pane(&theme, cx) }) .on_down(MouseButton::Left, |_, _, cx| { diff --git a/crates/workspace/src/pane/dragged_item_receiver.rs b/crates/workspace/src/pane/dragged_item_receiver.rs index 961205b9ee..003cf1e4ef 100644 --- a/crates/workspace/src/pane/dragged_item_receiver.rs +++ b/crates/workspace/src/pane/dragged_item_receiver.rs @@ -15,6 +15,7 @@ use crate::{Pane, SplitDirection, Workspace}; use super::DraggedItem; pub fn dragged_item_receiver( + pane: &Pane, region_id: usize, drop_index: usize, allow_same_pane: bool, @@ -27,22 +28,24 @@ where D: Element, F: FnOnce(&mut MouseState, &mut ViewContext) -> D, { - MouseEventHandler::::above(region_id, cx, |state, cx| { + let drag_and_drop = cx.global::>(); + let drag_position = if (pane.can_drop)(drag_and_drop, cx) { + drag_and_drop + .currently_dragged::(cx.window_id()) + .map(|(drag_position, _)| drag_position) + .or_else(|| { + drag_and_drop + .currently_dragged::(cx.window_id()) + .map(|(drag_position, _)| drag_position) + }) + } else { + None + }; + + let mut handler = MouseEventHandler::::above(region_id, cx, |state, cx| { // Observing hovered will cause a render when the mouse enters regardless // of if mouse position was accessed before - let drag_position = if state.hovered() { - cx.global::>() - .currently_dragged::(cx.window_id()) - .map(|(drag_position, _)| drag_position) - .or_else(|| { - cx.global::>() - .currently_dragged::(cx.window_id()) - .map(|(drag_position, _)| drag_position) - }) - } else { - None - }; - + let drag_position = if state.hovered() { drag_position } else { None }; Stack::new() .with_child(render_child(state, cx)) .with_children(drag_position.map(|drag_position| { @@ -67,38 +70,44 @@ where } }) })) - }) - .on_up(MouseButton::Left, { - move |event, pane, cx| { - let workspace = pane.workspace.clone(); - let pane = cx.weak_handle(); - handle_dropped_item( - event, - workspace, - &pane, - drop_index, - allow_same_pane, - split_margin, - cx, - ); - cx.notify(); - } - }) - .on_move(|_, _, cx| { - let drag_and_drop = cx.global::>(); + }); - if drag_and_drop - .currently_dragged::(cx.window_id()) - .is_some() - || drag_and_drop - .currently_dragged::(cx.window_id()) - .is_some() - { - cx.notify(); - } else { - cx.propagate_event(); - } - }) + if drag_position.is_some() { + handler = handler + .on_up(MouseButton::Left, { + move |event, pane, cx| { + let workspace = pane.workspace.clone(); + let pane = cx.weak_handle(); + handle_dropped_item( + event, + workspace, + &pane, + drop_index, + allow_same_pane, + split_margin, + cx, + ); + cx.notify(); + } + }) + .on_move(|_, _, cx| { + let drag_and_drop = cx.global::>(); + + if drag_and_drop + .currently_dragged::(cx.window_id()) + .is_some() + || drag_and_drop + .currently_dragged::(cx.window_id()) + .is_some() + { + cx.notify(); + } else { + cx.propagate_event(); + } + }) + } + + handler } pub fn handle_dropped_item( @@ -118,7 +127,7 @@ pub fn handle_dropped_item( let action = if let Some((_, dragged_item)) = drag_and_drop.currently_dragged::(cx.window_id()) { - Action::Move(dragged_item.pane.clone(), dragged_item.item.id()) + Action::Move(dragged_item.pane.clone(), dragged_item.handle.id()) } else if let Some((_, project_entry)) = drag_and_drop.currently_dragged::(cx.window_id()) { From 506f978c41b100c5355730a0c40b07210f25782a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 10:17:35 +0200 Subject: [PATCH 06/61] Automatically close terminal dock when the last terminal was closed --- crates/terminal_view/src/terminal_panel.rs | 68 +++++++++++++++------- crates/workspace/src/dock.rs | 15 ++++- crates/workspace/src/workspace.rs | 29 ++++++++- 3 files changed, 85 insertions(+), 27 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index ddc53537b7..7d715019c3 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1,44 +1,66 @@ -use gpui::{elements::*, Entity, ModelHandle, View, ViewContext, ViewHandle, WeakViewHandle}; +use crate::TerminalView; +use gpui::{ + elements::*, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle, + WeakViewHandle, +}; use project::Project; use settings::{Settings, WorkingDirectory}; use util::ResultExt; -use workspace::{dock::Panel, DraggedItem, Pane, Workspace}; +use workspace::{dock::Panel, pane, DraggedItem, Pane, Workspace}; -use crate::TerminalView; +pub enum Event { + Close, +} pub struct TerminalPanel { project: ModelHandle, pane: ViewHandle, workspace: WeakViewHandle, + _subscription: Subscription, } impl TerminalPanel { pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + let pane = cx.add_view(|cx| { + let window_id = cx.window_id(); + let mut pane = Pane::new( + workspace.weak_handle(), + workspace.app_state().background_actions, + cx, + ); + pane.on_can_drop(move |drag_and_drop, cx| { + drag_and_drop + .currently_dragged::(window_id) + .map_or(false, |(_, item)| { + item.handle.act_as::(cx).is_some() + }) + }); + pane + }); + let subscription = cx.subscribe(&pane, Self::handle_pane_event); Self { project: workspace.project().clone(), - pane: cx.add_view(|cx| { - let window_id = cx.window_id(); - let mut pane = Pane::new( - workspace.weak_handle(), - workspace.app_state().background_actions, - cx, - ); - pane.on_can_drop(move |drag_and_drop, cx| { - drag_and_drop - .currently_dragged::(window_id) - .map_or(false, |(_, item)| { - item.handle.act_as::(cx).is_some() - }) - }); - pane - }), + pane, workspace: workspace.weak_handle(), + _subscription: subscription, + } + } + + fn handle_pane_event( + &mut self, + _pane: ViewHandle, + event: &pane::Event, + cx: &mut ViewContext, + ) { + match event { + pane::Event::Remove => cx.emit(Event::Close), + _ => {} } } } impl Entity for TerminalPanel { - type Event = (); + type Event = Event; } impl View for TerminalPanel { @@ -82,4 +104,8 @@ impl View for TerminalPanel { } } -impl Panel for TerminalPanel {} +impl Panel for TerminalPanel { + fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { + matches!(event, Event::Close) + } +} diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 70708837e6..3acda44297 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -8,7 +8,10 @@ use settings::Settings; use std::rc::Rc; pub trait Panel: View { - fn should_activate_item_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + false + } + fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { false } fn should_show_badge(&self, _: &AppContext) -> bool { @@ -53,6 +56,10 @@ impl From<&dyn PanelHandle> for AnyViewHandle { } } +pub enum Event { + Close, +} + pub struct Dock { position: DockPosition, items: Vec, @@ -138,7 +145,7 @@ impl Dock { let subscriptions = [ cx.observe(&view, |_, _, cx| cx.notify()), cx.subscribe(&view, |this, view, event, cx| { - if view.read(cx).should_activate_item_on_event(event, cx) { + if view.read(cx).should_activate_on_event(event, cx) { if let Some(ix) = this .items .iter() @@ -146,6 +153,8 @@ impl Dock { { this.activate_item(ix, cx); } + } else if view.read(cx).should_close_on_event(event, cx) { + cx.emit(Event::Close); } }), ]; @@ -183,7 +192,7 @@ impl Dock { } impl Entity for Dock { - type Event = (); + type Event = Event; } impl View for Dock { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ee02a97439..e79f071267 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -461,7 +461,7 @@ pub struct Workspace { leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: WorkspaceId, app_state: Arc, - _window_subscriptions: [Subscription; 3], + _subscriptions: Vec, _apply_leader_updates: Task>, _observe_current_user: Task>, } @@ -592,7 +592,7 @@ impl Workspace { active_call = Some((call, subscriptions)); } - let subscriptions = [ + let subscriptions = vec![ cx.observe_fullscreen(|_, _, cx| cx.notify()), cx.observe_window_activation(Self::on_window_activation_changed), cx.observe_window_bounds(move |_, mut bounds, display, cx| { @@ -612,6 +612,9 @@ impl Workspace { .spawn(DB.set_window_bounds(workspace_id, bounds, display)) .detach_and_log_err(cx); }), + Self::register_dock(&left_dock, cx), + Self::register_dock(&bottom_dock, cx), + Self::register_dock(&right_dock, cx), ]; let mut this = Workspace { @@ -640,7 +643,7 @@ impl Workspace { _observe_current_user, _apply_leader_updates, leader_updates_tx, - _window_subscriptions: subscriptions, + _subscriptions: subscriptions, }; this.project_remote_id_changed(project.read(cx).remote_id(), cx); cx.defer(|this, cx| this.update_window_title(cx)); @@ -1316,6 +1319,26 @@ impl Workspace { } } + fn register_dock(dock: &ViewHandle, cx: &mut ViewContext) -> Subscription { + cx.subscribe(dock, Self::handle_dock_event) + } + + fn handle_dock_event( + &mut self, + dock: ViewHandle, + event: &dock::Event, + cx: &mut ViewContext, + ) { + match event { + dock::Event::Close => { + dock.update(cx, |dock, cx| dock.set_open(false, cx)); + self.serialize_workspace(cx); + cx.focus_self(); + cx.notify(); + } + } + } + pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { let dock = match dock_side { DockPosition::Left => &mut self.left_dock, From 02066afb0e801f8226f8a155682bf60706286e79 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 11:45:39 +0200 Subject: [PATCH 07/61] Don't pass `&mut Workspace` when closing items in a `Pane` This allows closing items via actions even in the `TerminalPanel` where the `Pane` is not directly owned by a `Workspace`. --- crates/workspace/src/item.rs | 2 +- crates/workspace/src/pane.rs | 268 ++++++++---------------------- crates/workspace/src/workspace.rs | 43 ++--- crates/zed/src/zed.rs | 56 +++---- 4 files changed, 110 insertions(+), 259 deletions(-) diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index e7571c1bf6..0ae9e4f0ea 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -437,7 +437,7 @@ impl ItemHandle for ViewHandle { for item_event in T::to_item_events(event).into_iter() { match item_event { ItemEvent::CloseItem => { - Pane::close_item_by_id(workspace, pane, item.id(), cx) + pane.update(cx, |pane, cx| pane.close_item_by_id(item.id(), cx)) .detach_and_log_err(cx); return; } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index fc3fda8e6a..c02c5d7ef4 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -681,187 +681,108 @@ impl Pane { } pub fn close_active_item( - workspace: &mut Workspace, + &mut self, _: &CloseActiveItem, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - let pane = pane_handle.read(cx); - - if pane.items.is_empty() { + if self.items.is_empty() { return None; } - let active_item_id = pane.items[pane.active_item_index].id(); - - let task = Self::close_item_by_id(workspace, pane_handle, active_item_id, cx); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + let active_item_id = self.items[self.active_item_index].id(); + Some(self.close_item_by_id(active_item_id, cx)) } pub fn close_item_by_id( - workspace: &mut Workspace, - pane: ViewHandle, + &mut self, item_id_to_close: usize, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Task> { - Self::close_items(workspace, pane, cx, move |view_id| { - view_id == item_id_to_close - }) + self.close_items(cx, move |view_id| view_id == item_id_to_close) } pub fn close_inactive_items( - workspace: &mut Workspace, + &mut self, _: &CloseInactiveItems, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - let pane = pane_handle.read(cx); - let active_item_id = pane.items[pane.active_item_index].id(); - - let task = Self::close_items(workspace, pane_handle, cx, move |item_id| { - item_id != active_item_id - }); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + let active_item_id = self.items[self.active_item_index].id(); + Some(self.close_items(cx, move |item_id| item_id != active_item_id)) } pub fn close_clean_items( - workspace: &mut Workspace, + &mut self, _: &CloseCleanItems, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - let pane = pane_handle.read(cx); - - let item_ids: Vec<_> = pane + let item_ids: Vec<_> = self .items() .filter(|item| !item.is_dirty(cx)) .map(|item| item.id()) .collect(); - - let task = Self::close_items(workspace, pane_handle, cx, move |item_id| { - item_ids.contains(&item_id) - }); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + Some(self.close_items(cx, move |item_id| item_ids.contains(&item_id))) } pub fn close_items_to_the_left( - workspace: &mut Workspace, + &mut self, _: &CloseItemsToTheLeft, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - let pane = pane_handle.read(cx); - let active_item_id = pane.items[pane.active_item_index].id(); - - let task = Self::close_items_to_the_left_by_id(workspace, pane_handle, active_item_id, cx); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + let active_item_id = self.items[self.active_item_index].id(); + Some(self.close_items_to_the_left_by_id(active_item_id, cx)) } pub fn close_items_to_the_left_by_id( - workspace: &mut Workspace, - pane: ViewHandle, + &mut self, item_id: usize, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Task> { - let item_ids: Vec<_> = pane - .read(cx) + let item_ids: Vec<_> = self .items() .take_while(|item| item.id() != item_id) .map(|item| item.id()) .collect(); - - let task = Self::close_items(workspace, pane, cx, move |item_id| { - item_ids.contains(&item_id) - }); - - cx.foreground().spawn(async move { - task.await?; - Ok(()) - }) + self.close_items(cx, move |item_id| item_ids.contains(&item_id)) } pub fn close_items_to_the_right( - workspace: &mut Workspace, + &mut self, _: &CloseItemsToTheRight, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - let pane = pane_handle.read(cx); - let active_item_id = pane.items[pane.active_item_index].id(); - - let task = Self::close_items_to_the_right_by_id(workspace, pane_handle, active_item_id, cx); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + let active_item_id = self.items[self.active_item_index].id(); + Some(self.close_items_to_the_right_by_id(active_item_id, cx)) } pub fn close_items_to_the_right_by_id( - workspace: &mut Workspace, - pane: ViewHandle, + &mut self, item_id: usize, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Task> { - let item_ids: Vec<_> = pane - .read(cx) + let item_ids: Vec<_> = self .items() .rev() .take_while(|item| item.id() != item_id) .map(|item| item.id()) .collect(); - - let task = Self::close_items(workspace, pane, cx, move |item_id| { - item_ids.contains(&item_id) - }); - - cx.foreground().spawn(async move { - task.await?; - Ok(()) - }) + self.close_items(cx, move |item_id| item_ids.contains(&item_id)) } pub fn close_all_items( - workspace: &mut Workspace, + &mut self, _: &CloseAllItems, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - - let task = Self::close_items(workspace, pane_handle, cx, move |_| true); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + Some(self.close_items(cx, move |_| true)) } pub fn close_items( - workspace: &mut Workspace, - pane: ViewHandle, - cx: &mut ViewContext, + &mut self, + cx: &mut ViewContext, should_close: impl 'static + Fn(usize) -> bool, ) -> Task> { - let project = workspace.project().clone(); - // Find the items to close. let mut items_to_close = Vec::new(); - for item in &pane.read(cx).items { + for item in &self.items { if should_close(item.id()) { items_to_close.push(item.boxed_clone()); } @@ -873,8 +794,8 @@ impl Pane { // of what content they would be saving. items_to_close.sort_by_key(|item| !item.is_singleton(cx)); - let pane = pane.downgrade(); - cx.spawn(|workspace, mut cx| async move { + let workspace = self.workspace.clone(); + cx.spawn(|pane, mut cx| async move { let mut saved_project_items_ids = HashSet::default(); for item in items_to_close.clone() { // Find the item's current index and its set of project item models. Avoid @@ -892,7 +813,7 @@ impl Pane { // Check if this view has any project items that are not open anywhere else // in the workspace, AND that the user has not already been prompted to save. // If there are any such project entries, prompt the user to save this item. - workspace.read_with(&cx, |workspace, cx| { + let project = workspace.read_with(&cx, |workspace, cx| { for item in workspace.items(cx) { if !items_to_close .iter() @@ -902,6 +823,7 @@ impl Pane { project_item_ids.retain(|id| !other_project_item_ids.contains(id)); } } + workspace.project().clone() })?; let should_save = project_item_ids .iter() @@ -1200,14 +1122,11 @@ impl Pane { // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command. vec![ ContextMenuItem::handler("Close Inactive Item", { - let workspace = self.workspace.clone(); let pane = target_pane.clone(); move |cx| { - if let Some((workspace, pane)) = - workspace.upgrade(cx).zip(pane.upgrade(cx)) - { - workspace.update(cx, |workspace, cx| { - Self::close_item_by_id(workspace, pane, target_item_id, cx) + if let Some(pane) = pane.upgrade(cx) { + pane.update(cx, |pane, cx| { + pane.close_item_by_id(target_item_id, cx) .detach_and_log_err(cx); }) } @@ -1216,39 +1135,23 @@ impl Pane { ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), ContextMenuItem::action("Close Clean Items", CloseCleanItems), ContextMenuItem::handler("Close Items To The Left", { - let workspace = self.workspace.clone(); let pane = target_pane.clone(); move |cx| { - if let Some((workspace, pane)) = - workspace.upgrade(cx).zip(pane.upgrade(cx)) - { - workspace.update(cx, |workspace, cx| { - Self::close_items_to_the_left_by_id( - workspace, - pane, - target_item_id, - cx, - ) - .detach_and_log_err(cx); + if let Some(pane) = pane.upgrade(cx) { + pane.update(cx, |pane, cx| { + pane.close_items_to_the_left_by_id(target_item_id, cx) + .detach_and_log_err(cx); }) } } }), ContextMenuItem::handler("Close Items To The Right", { - let workspace = self.workspace.clone(); let pane = target_pane.clone(); move |cx| { - if let Some((workspace, pane)) = - workspace.upgrade(cx).zip(pane.upgrade(cx)) - { - workspace.update(cx, |workspace, cx| { - Self::close_items_to_the_right_by_id( - workspace, - pane, - target_item_id, - cx, - ) - .detach_and_log_err(cx); + if let Some(pane) = pane.upgrade(cx) { + pane.update(cx, |pane, cx| { + pane.close_items_to_the_right_by_id(target_item_id, cx) + .detach_and_log_err(cx); }) } } @@ -1336,20 +1239,7 @@ impl Pane { .on_click(MouseButton::Middle, { let item_id = item.id(); move |_, pane, cx| { - let workspace = pane.workspace.clone(); - let pane = cx.weak_handle(); - cx.window_context().defer(move |cx| { - if let Some((workspace, pane)) = - workspace.upgrade(cx).zip(pane.upgrade(cx)) - { - workspace.update(cx, |workspace, cx| { - Self::close_item_by_id( - workspace, pane, item_id, cx, - ) - .detach_and_log_err(cx); - }); - } - }); + pane.close_item_by_id(item_id, cx).detach_and_log_err(cx); } }) .on_down( @@ -1556,12 +1446,9 @@ impl Pane { let pane = pane.clone(); cx.window_context().defer(move |cx| { if let Some(pane) = pane.upgrade(cx) { - if let Some(workspace) = pane.read(cx).workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - Self::close_item_by_id(workspace, pane, item_id, cx) - .detach_and_log_err(cx); - }); - } + pane.update(cx, |pane, cx| { + pane.close_item_by_id(item_id, cx).detach_and_log_err(cx); + }); } }); } @@ -2028,9 +1915,10 @@ mod tests { let project = Project::test(fs, None, cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - workspace.update(cx, |workspace, cx| { - assert!(Pane::close_active_item(workspace, &CloseActiveItem, cx).is_none()) + pane.update(cx, |pane, cx| { + assert!(pane.close_active_item(&CloseActiveItem, cx).is_none()) }); } @@ -2327,30 +2215,22 @@ mod tests { add_labeled_item(&workspace, &pane, "1", false, cx); assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_active_item(workspace, &CloseActiveItem, cx); - }); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, ["A", "B*", "C", "D"], cx); pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx)); assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_active_item(workspace, &CloseActiveItem, cx); - }); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, ["A", "B*", "C"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_active_item(workspace, &CloseActiveItem, cx); - }); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, ["A", "C*"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_active_item(workspace, &CloseActiveItem, cx); - }); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, ["A*"], cx); } @@ -2366,8 +2246,8 @@ mod tests { set_labeled_items(&workspace, &pane, ["A", "B", "C*", "D", "E"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_inactive_items(workspace, &CloseInactiveItems, cx); + pane.update(cx, |pane, cx| { + pane.close_inactive_items(&CloseInactiveItems, cx) }); deterministic.run_until_parked(); @@ -2390,9 +2270,7 @@ mod tests { add_labeled_item(&workspace, &pane, "E", false, cx); assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_clean_items(workspace, &CloseCleanItems, cx); - }); + pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, ["A^", "C*^"], cx); @@ -2412,8 +2290,8 @@ mod tests { set_labeled_items(&workspace, &pane, ["A", "B", "C*", "D", "E"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_items_to_the_left(workspace, &CloseItemsToTheLeft, cx); + pane.update(cx, |pane, cx| { + pane.close_items_to_the_left(&CloseItemsToTheLeft, cx) }); deterministic.run_until_parked(); @@ -2434,8 +2312,8 @@ mod tests { set_labeled_items(&workspace, &pane, ["A", "B", "C*", "D", "E"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_items_to_the_right(workspace, &CloseItemsToTheRight, cx); + pane.update(cx, |pane, cx| { + pane.close_items_to_the_right(&CloseItemsToTheRight, cx) }); deterministic.run_until_parked(); @@ -2456,9 +2334,7 @@ mod tests { add_labeled_item(&workspace, &pane, "C", false, cx); assert_item_labels(&pane, ["A", "B", "C*"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_all_items(workspace, &CloseAllItems, cx); - }); + pane.update(cx, |pane, cx| pane.close_all_items(&CloseAllItems, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, [], cx); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e79f071267..59a8e97f3d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3125,6 +3125,7 @@ mod tests { let project = Project::test(fs, ["root1".as_ref()], cx).await; let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let worktree_id = project.read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() }); @@ -3167,12 +3168,11 @@ mod tests { }); // Close the active item - workspace - .update(cx, |workspace, cx| { - Pane::close_active_item(workspace, &Default::default(), cx).unwrap() - }) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_active_item(&Default::default(), cx).unwrap() + }) + .await + .unwrap(); assert_eq!( cx.current_window_title(window_id).as_deref(), Some("one.txt — root1") @@ -3281,18 +3281,13 @@ mod tests { workspace.active_pane().clone() }); - let close_items = workspace.update(cx, |workspace, cx| { - pane.update(cx, |pane, cx| { - pane.activate_item(1, true, true, cx); - assert_eq!(pane.active_item().unwrap().id(), item2.id()); - }); - + let close_items = pane.update(cx, |pane, cx| { + pane.activate_item(1, true, true, cx); + assert_eq!(pane.active_item().unwrap().id(), item2.id()); let item1_id = item1.id(); let item3_id = item3.id(); let item4_id = item4.id(); - Pane::close_items(workspace, pane.clone(), cx, move |id| { - [item1_id, item3_id, item4_id].contains(&id) - }) + pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id)) }); cx.foreground().run_until_parked(); @@ -3426,10 +3421,7 @@ mod tests { // once for project entry 0, and once for project entry 2. After those two // prompts, the task should complete. - let close = workspace.update(cx, |workspace, cx| { - Pane::close_items(workspace, left_pane.clone(), cx, |_| true) - }); - + let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true)); cx.foreground().run_until_parked(); left_pane.read_with(cx, |pane, cx| { assert_eq!( @@ -3464,6 +3456,7 @@ mod tests { let project = Project::test(fs, [], cx).await; let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let item = cx.add_view(window_id, |cx| { TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) @@ -3536,11 +3529,7 @@ mod tests { item.is_dirty = true; }); - workspace - .update(cx, |workspace, cx| { - let pane = workspace.active_pane().clone(); - Pane::close_items(workspace, pane, cx, move |id| id == item_id) - }) + pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id)) .await .unwrap(); assert!(!cx.has_pending_prompt(window_id)); @@ -3561,10 +3550,8 @@ mod tests { item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); // Ensure autosave is prevented for deleted files also when closing the buffer. - let _close_items = workspace.update(cx, |workspace, cx| { - let pane = workspace.active_pane().clone(); - Pane::close_items(workspace, pane, cx, move |id| id == item_id) - }); + let _close_items = + pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id)); deterministic.run_until_parked(); assert!(cx.has_pending_prompt(window_id)); item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 52b0ad0898..662638301d 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -734,6 +734,7 @@ mod tests { .unwrap() .downcast::() .unwrap(); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let editor = workspace.read_with(cx, |workspace, cx| { workspace .active_item(cx) @@ -756,9 +757,9 @@ mod tests { assert!(cx.is_window_edited(workspace.window_id())); // Closing the item restores the window's edited state. - let close = workspace.update(cx, |workspace, cx| { + let close = pane.update(cx, |pane, cx| { drop(editor); - Pane::close_active_item(workspace, &Default::default(), cx).unwrap() + pane.close_active_item(&Default::default(), cx).unwrap() }); executor.run_until_parked(); cx.simulate_prompt_answer(workspace.window_id(), 1); @@ -1384,6 +1385,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let entries = cx.read(|cx| workspace.file_project_paths(cx)); let file1 = entries[0].clone(); @@ -1501,14 +1503,13 @@ mod tests { // Go forward to an item that has been closed, ensuring it gets re-opened at the same // location. - workspace - .update(cx, |workspace, cx| { - let editor3_id = editor3.id(); - drop(editor3); - Pane::close_item_by_id(workspace, workspace.active_pane().clone(), editor3_id, cx) - }) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + let editor3_id = editor3.id(); + drop(editor3); + pane.close_item_by_id(editor3_id, cx) + }) + .await + .unwrap(); workspace .update(cx, |w, cx| Pane::go_forward(w, None, cx)) .await @@ -1537,14 +1538,13 @@ mod tests { ); // Go back to an item that has been closed and removed from disk, ensuring it gets skipped. - workspace - .update(cx, |workspace, cx| { - let editor2_id = editor2.id(); - drop(editor2); - Pane::close_item_by_id(workspace, workspace.active_pane().clone(), editor2_id, cx) - }) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + let editor2_id = editor2.id(); + drop(editor2); + pane.close_item_by_id(editor2_id, cx) + }) + .await + .unwrap(); app_state .fs .remove_file(Path::new("/root/a/file2"), Default::default()) @@ -1693,34 +1693,22 @@ mod tests { assert_eq!(active_path(&workspace, cx), Some(file4.clone())); // Close all the pane items in some arbitrary order. - workspace - .update(cx, |workspace, cx| { - Pane::close_item_by_id(workspace, pane.clone(), file1_item_id, cx) - }) + pane.update(cx, |pane, cx| pane.close_item_by_id(file1_item_id, cx)) .await .unwrap(); assert_eq!(active_path(&workspace, cx), Some(file4.clone())); - workspace - .update(cx, |workspace, cx| { - Pane::close_item_by_id(workspace, pane.clone(), file4_item_id, cx) - }) + pane.update(cx, |pane, cx| pane.close_item_by_id(file4_item_id, cx)) .await .unwrap(); assert_eq!(active_path(&workspace, cx), Some(file3.clone())); - workspace - .update(cx, |workspace, cx| { - Pane::close_item_by_id(workspace, pane.clone(), file2_item_id, cx) - }) + pane.update(cx, |pane, cx| pane.close_item_by_id(file2_item_id, cx)) .await .unwrap(); assert_eq!(active_path(&workspace, cx), Some(file3.clone())); - workspace - .update(cx, |workspace, cx| { - Pane::close_item_by_id(workspace, pane.clone(), file3_item_id, cx) - }) + pane.update(cx, |pane, cx| pane.close_item_by_id(file3_item_id, cx)) .await .unwrap(); assert_eq!(active_path(&workspace, cx), None); From e6be35c9a54d498e0c21cc2a54fc9f7076cc1e20 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 12:21:35 +0200 Subject: [PATCH 08/61] Show terminal count in panel button --- crates/project_panel/src/project_panel.rs | 6 +--- crates/terminal_view/src/terminal_panel.rs | 18 ++++++++++-- crates/theme/src/theme.rs | 1 - crates/workspace/src/dock.rs | 33 +++++++++++----------- 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 35ab314f3a..cecf6c114f 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1327,11 +1327,7 @@ impl Entity for ProjectPanel { type Event = Event; } -impl workspace::dock::Panel for ProjectPanel { - fn should_show_badge(&self, _: &AppContext) -> bool { - false - } -} +impl workspace::dock::Panel for ProjectPanel {} impl ClipboardEntry { fn is_cut(&self) -> bool { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 7d715019c3..ce70f08391 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -16,7 +16,7 @@ pub struct TerminalPanel { project: ModelHandle, pane: ViewHandle, workspace: WeakViewHandle, - _subscription: Subscription, + _subscriptions: Vec, } impl TerminalPanel { @@ -37,12 +37,15 @@ impl TerminalPanel { }); pane }); - let subscription = cx.subscribe(&pane, Self::handle_pane_event); + let subscriptions = vec![ + cx.observe(&pane, |_, _, cx| cx.notify()), + cx.subscribe(&pane, Self::handle_pane_event), + ]; Self { project: workspace.project().clone(), pane, workspace: workspace.weak_handle(), - _subscription: subscription, + _subscriptions: subscriptions, } } @@ -108,4 +111,13 @@ impl Panel for TerminalPanel { fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { matches!(event, Event::Close) } + + fn label(&self, cx: &AppContext) -> Option { + let count = self.pane.read(cx).items_len(); + if count == 0 { + None + } else { + Some(count.to_string()) + } + } } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 67d07a3998..2d4c520127 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -346,7 +346,6 @@ pub struct StatusBarPanelButtons { pub group_bottom: ContainerStyle, pub group_right: ContainerStyle, pub button: Interactive, - pub badge: ContainerStyle, } #[derive(Deserialize, Default)] diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 3acda44297..57d18fd368 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -14,8 +14,8 @@ pub trait Panel: View { fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { false } - fn should_show_badge(&self, _: &AppContext) -> bool { - false + fn label(&self, _: &AppContext) -> Option { + None } fn contains_focused_view(&self, _: &AppContext) -> bool { false @@ -24,7 +24,7 @@ pub trait Panel: View { pub trait PanelHandle { fn id(&self) -> usize; - fn should_show_badge(&self, cx: &WindowContext) -> bool; + fn label(&self, cx: &WindowContext) -> Option; fn is_focused(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; } @@ -37,8 +37,8 @@ where self.id() } - fn should_show_badge(&self, cx: &WindowContext) -> bool { - self.read(cx).should_show_badge(cx) + fn label(&self, cx: &WindowContext) -> Option { + self.read(cx).label(cx) } fn is_focused(&self, cx: &WindowContext) -> bool { @@ -247,7 +247,6 @@ impl View for PanelButtons { let theme = &theme.workspace.status_bar.panel_buttons; let dock = self.dock.read(cx); let item_style = theme.button.clone(); - let badge_style = theme.badge; let active_ix = dock.active_item_ix; let is_open = dock.is_open; let dock_position = dock.position; @@ -274,23 +273,25 @@ impl View for PanelButtons { MouseEventHandler::::new(ix, cx, |state, cx| { let is_active = is_open && ix == active_ix; let style = item_style.style_for(state, is_active); - Stack::new() - .with_child(Svg::new(icon_path).with_color(style.icon_color)) - .with_children(if !is_active && item_view.should_show_badge(cx) { + Flex::row() + .with_child( + Svg::new(icon_path) + .with_color(style.icon_color) + .constrained() + .with_width(style.icon_size) + .aligned(), + ) + .with_children(if let Some(label) = item_view.label(cx) { Some( - Empty::new() - .collapsed() + Label::new(label, style.label.text.clone()) .contained() - .with_style(badge_style) - .aligned() - .bottom() - .right(), + .with_style(style.label.container) + .aligned(), ) } else { None }) .constrained() - .with_width(style.icon_size) .with_height(style.icon_size) .contained() .with_style(style.container) From 26fe7a81f34f69ec993f993eb1523dbe2d8cb480 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 12:29:16 +0200 Subject: [PATCH 09/61] Prevent splits in the terminal panel --- crates/terminal_view/src/terminal_panel.rs | 1 + crates/workspace/src/pane.rs | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index ce70f08391..5b1a7d20df 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -28,6 +28,7 @@ impl TerminalPanel { workspace.app_state().background_actions, cx, ); + pane.set_can_split(false, cx); pane.on_can_drop(move |drag_and_drop, cx| { drag_and_drop .currently_dragged::(window_id) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index c02c5d7ef4..fd617e1685 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -149,6 +149,7 @@ pub struct Pane { workspace: WeakViewHandle, has_focus: bool, can_drop: Rc, &WindowContext) -> bool>, + can_split: bool, } pub struct ItemNavHistory { @@ -255,6 +256,7 @@ impl Pane { workspace, has_focus: false, can_drop: Rc::new(|_, _| true), + can_split: true, } } @@ -282,6 +284,11 @@ impl Pane { self.can_drop = Rc::new(can_drop); } + pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext) { + self.can_split = can_split; + cx.notify(); + } + pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { ItemNavHistory { history: self.nav_history.clone(), @@ -1563,8 +1570,8 @@ impl View for Pane { self, 0, self.active_item_index + 1, - false, - Some(100.), + !self.can_split, + if self.can_split { Some(100.) } else { None }, cx, { let toolbar = self.toolbar.clone(); From ad7f32d7d2c2fc9e3a1dba920f6796ce8402297f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 14:46:15 +0200 Subject: [PATCH 10/61] Fix bottom dock resizing --- crates/workspace/src/dock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 57d18fd368..2b23ceb7f3 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -78,7 +78,7 @@ impl DockPosition { fn to_resizable_side(self) -> Side { match self { Self::Left => Side::Right, - Self::Bottom => Side::Bottom, + Self::Bottom => Side::Top, Self::Right => Side::Left, } } From 634b699281a3ab66a690fa93cead04e78d94aa9c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 15:05:29 +0200 Subject: [PATCH 11/61] Allow customization of `Pane` tab bar buttons --- crates/terminal_view/src/terminal_panel.rs | 72 +++++++----- crates/workspace/src/pane.rs | 128 +++++++++++---------- 2 files changed, 114 insertions(+), 86 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 5b1a7d20df..90072ea652 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -21,6 +21,7 @@ pub struct TerminalPanel { impl TerminalPanel { pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + let this = cx.weak_handle(); let pane = cx.add_view(|cx| { let window_id = cx.window_id(); let mut pane = Pane::new( @@ -36,6 +37,23 @@ impl TerminalPanel { item.handle.act_as::(cx).is_some() }) }); + pane.set_render_tab_bar_buttons(cx, move |_, cx| { + let this = this.clone(); + Pane::render_tab_bar_button( + 0, + "icons/plus_12.svg", + cx, + move |_, cx| { + let this = this.clone(); + cx.window_context().defer(move |cx| { + if let Some(this) = this.upgrade(cx) { + this.update(cx, |this, cx| this.add_terminal(cx)); + } + }) + }, + None, + ) + }); pane }); let subscriptions = vec![ @@ -61,6 +79,33 @@ impl TerminalPanel { _ => {} } } + + fn add_terminal(&mut self, cx: &mut ViewContext) { + if let Some(workspace) = self.workspace.upgrade(cx) { + let working_directory_strategy = cx + .global::() + .terminal_overrides + .working_directory + .clone() + .unwrap_or(WorkingDirectory::CurrentProjectDirectory); + let working_directory = + crate::get_working_directory(workspace.read(cx), cx, working_directory_strategy); + let window_id = cx.window_id(); + if let Some(terminal) = self.project.update(cx, |project, cx| { + project + .create_terminal(working_directory, window_id, cx) + .log_err() + }) { + workspace.update(cx, |workspace, cx| { + let terminal = + Box::new(cx.add_view(|cx| { + TerminalView::new(terminal, workspace.database_id(), cx) + })); + Pane::add_item(workspace, &self.pane, terminal, true, true, None, cx); + }); + } + } + } } impl Entity for TerminalPanel { @@ -78,32 +123,7 @@ impl View for TerminalPanel { fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { if self.pane.read(cx).items_len() == 0 { - if let Some(workspace) = self.workspace.upgrade(cx) { - let working_directory_strategy = cx - .global::() - .terminal_overrides - .working_directory - .clone() - .unwrap_or(WorkingDirectory::CurrentProjectDirectory); - let working_directory = crate::get_working_directory( - workspace.read(cx), - cx, - working_directory_strategy, - ); - let window_id = cx.window_id(); - if let Some(terminal) = self.project.update(cx, |project, cx| { - project - .create_terminal(working_directory, window_id, cx) - .log_err() - }) { - workspace.update(cx, |workspace, cx| { - let terminal = Box::new(cx.add_view(|cx| { - TerminalView::new(terminal, workspace.database_id(), cx) - })); - Pane::add_item(workspace, &self.pane, terminal, true, true, None, cx); - }); - } - } + self.add_terminal(cx) } } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index fd617e1685..0523f082ea 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -150,6 +150,7 @@ pub struct Pane { has_focus: bool, can_drop: Rc, &WindowContext) -> bool>, can_split: bool, + render_tab_bar_buttons: Rc) -> AnyElement>, } pub struct ItemNavHistory { @@ -257,6 +258,27 @@ impl Pane { has_focus: false, can_drop: Rc::new(|_, _| true), can_split: true, + render_tab_bar_buttons: Rc::new(|pane, cx| { + Flex::row() + // New menu + .with_child(Self::render_tab_bar_button( + 0, + "icons/plus_12.svg", + cx, + |pane, cx| pane.deploy_new_menu(cx), + pane.tab_bar_context_menu + .handle_if_kind(TabBarContextMenuKind::New), + )) + .with_child(Self::render_tab_bar_button( + 2, + "icons/split_12.svg", + cx, + |pane, cx| pane.deploy_split_menu(cx), + pane.tab_bar_context_menu + .handle_if_kind(TabBarContextMenuKind::Split), + )) + .into_any() + }), } } @@ -289,6 +311,14 @@ impl Pane { cx.notify(); } + pub fn set_render_tab_bar_buttons(&mut self, cx: &mut ViewContext, render: F) + where + F: 'static + Fn(&mut Pane, &mut ViewContext) -> AnyElement, + { + self.render_tab_bar_buttons = Rc::new(render); + cx.notify(); + } + pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { ItemNavHistory { history: self.nav_history.clone(), @@ -1475,33 +1505,37 @@ impl Pane { .into_any() } - fn render_tab_bar_buttons( - &mut self, - theme: &Theme, - cx: &mut ViewContext, - ) -> AnyElement { - Flex::row() - // New menu - .with_child(render_tab_bar_button( - 0, - "icons/plus_12.svg", - cx, - |pane, cx| pane.deploy_new_menu(cx), - self.tab_bar_context_menu - .handle_if_kind(TabBarContextMenuKind::New), - )) - .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) + pub fn render_tab_bar_button)>( + index: usize, + icon: &'static str, + cx: &mut ViewContext, + on_click: F, + context_menu: Option>, + ) -> AnyElement { + enum TabBarButton {} + + Stack::new() + .with_child( + MouseEventHandler::::new(index, cx, |mouse_state, cx| { + let theme = &cx.global::().theme.workspace.tab_bar; + let style = theme.pane_button.style_for(mouse_state, false); + Svg::new(icon) + .with_color(style.color) + .constrained() + .with_width(style.icon_width) + .aligned() + .constrained() + .with_width(style.button_width) + .with_height(style.button_width) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx)), + ) + .with_children( + context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()), + ) .flex(1., false) - .into_any() + .into_any_named("tab bar button") } fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext) -> AnyElement { @@ -1554,7 +1588,14 @@ impl View for Pane { .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs")); if self.is_active { - tab_row.add_child(self.render_tab_bar_buttons(&theme, cx)) + let render_tab_bar_buttons = self.render_tab_bar_buttons.clone(); + tab_row.add_child( + (render_tab_bar_buttons)(self, cx) + .contained() + .with_style(theme.workspace.tab_bar.pane_button_container) + .flex(1., false) + .into_any(), + ) } stack.add_child(tab_row); @@ -1676,39 +1717,6 @@ impl View for Pane { } } -fn render_tab_bar_button)>( - index: usize, - icon: &'static str, - cx: &mut ViewContext, - on_click: F, - context_menu: Option>, -) -> AnyElement { - enum TabBarButton {} - - Stack::new() - .with_child( - MouseEventHandler::::new(index, cx, |mouse_state, cx| { - let theme = &cx.global::().theme.workspace.tab_bar; - let style = theme.pane_button.style_for(mouse_state, false); - Svg::new(icon) - .with_color(style.color) - .constrained() - .with_width(style.icon_width) - .aligned() - .constrained() - .with_width(style.button_width) - .with_height(style.button_width) - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx)), - ) - .with_children( - context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()), - ) - .flex(1., false) - .into_any_named("tab bar button") -} - impl ItemNavHistory { pub fn push(&self, data: Option, cx: &mut WindowContext) { self.history.borrow_mut().push(data, self.item.clone(), cx); From 641f5d1107b282127115d812368704b161748cca Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 17:38:18 +0200 Subject: [PATCH 12/61] Ensure `ctrl-`` works in the terminal panel --- crates/terminal_view/src/terminal_panel.rs | 12 +++++++++--- crates/terminal_view/src/terminal_view.rs | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 90072ea652..d963025f89 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -8,6 +8,10 @@ use settings::{Settings, WorkingDirectory}; use util::ResultExt; use workspace::{dock::Panel, pane, DraggedItem, Pane, Workspace}; +pub fn init(cx: &mut AppContext) { + cx.add_action(TerminalPanel::add_terminal); +} + pub enum Event { Close, } @@ -47,7 +51,9 @@ impl TerminalPanel { let this = this.clone(); cx.window_context().defer(move |cx| { if let Some(this) = this.upgrade(cx) { - this.update(cx, |this, cx| this.add_terminal(cx)); + this.update(cx, |this, cx| { + this.add_terminal(&Default::default(), cx); + }); } }) }, @@ -80,7 +86,7 @@ impl TerminalPanel { } } - fn add_terminal(&mut self, cx: &mut ViewContext) { + fn add_terminal(&mut self, _: &workspace::NewTerminal, cx: &mut ViewContext) { if let Some(workspace) = self.workspace.upgrade(cx) { let working_directory_strategy = cx .global::() @@ -123,7 +129,7 @@ impl View for TerminalPanel { fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { if self.pane.read(cx).items_len() == 0 { - self.add_terminal(cx) + self.add_terminal(&Default::default(), cx) } } } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index e3a5153ef3..b318b572b5 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -64,6 +64,7 @@ actions!( impl_actions!(terminal, [SendText, SendKeystroke]); pub fn init(cx: &mut AppContext) { + terminal_panel::init(cx); cx.add_action(TerminalView::deploy); register_deserializable_item::(cx); From 37d3ed5f5f6358d44d0546e24d709b8d3e804787 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 17:38:54 +0200 Subject: [PATCH 13/61] Focus new item if pane was focused when removing previous active item Previously, we were relying on the item getting blurred and the workspace receiving focus, which would in turn focus the active pane. This doesn't play well with docks because they aren't part of the workspace panes. --- crates/workspace/src/pane.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 0523f082ea..30e782c750 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -903,7 +903,8 @@ impl Pane { // to activating the item to the left .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1)); - self.activate_item(index_to_activate, activate_pane, activate_pane, cx); + let should_activate = activate_pane || self.has_focus; + self.activate_item(index_to_activate, should_activate, should_activate, cx); } let item = self.items.remove(item_index); From 45df09245b867a37475f405c0a7ee9e9a2222ac5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 18:06:59 +0200 Subject: [PATCH 14/61] Remove unused code Co-Authored-By: Nathan Sobo --- crates/workspace/src/dock.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 2b23ceb7f3..492f61c86f 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -17,9 +17,6 @@ pub trait Panel: View { fn label(&self, _: &AppContext) -> Option { None } - fn contains_focused_view(&self, _: &AppContext) -> bool { - false - } } pub trait PanelHandle { @@ -42,7 +39,7 @@ where } fn is_focused(&self, cx: &WindowContext) -> bool { - ViewHandle::is_focused(self, cx) || self.read(cx).contains_focused_view(cx) + ViewHandle::is_focused(self, cx) } fn as_any(&self) -> &AnyViewHandle { From 8f12489937ce86d997b1f6fc73783f7231995a79 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 18:57:25 +0200 Subject: [PATCH 15/61] WIP: Allow panels to be moved Co-Authored-By: Nathan Sobo --- crates/project_panel/src/project_panel.rs | 32 +++- crates/terminal_view/src/terminal_panel.rs | 2 +- crates/workspace/src/dock.rs | 161 +++++++++++---------- crates/workspace/src/item.rs | 30 +++- crates/workspace/src/workspace.rs | 11 +- crates/zed/src/zed.rs | 13 +- 6 files changed, 157 insertions(+), 92 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index cecf6c114f..f326d665d0 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -28,7 +28,7 @@ use std::{ }; use theme::ProjectPanelEntry; use unicase::UniCase; -use workspace::Workspace; +use workspace::{dock::DockPosition, Workspace}; const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX; @@ -1327,7 +1327,35 @@ impl Entity for ProjectPanel { type Event = Event; } -impl workspace::dock::Panel for ProjectPanel {} +impl workspace::dock::Panel for ProjectPanel { + fn position(&self, cx: &gpui::WindowContext) -> DockPosition { + todo!() + } + + fn position_is_valid(&self, position: DockPosition) -> bool { + matches!(position, DockPosition::Left | DockPosition::Right) + } + + fn icon_path(&self) -> &'static str { + "icons/folder_tree_16.svg" + } + + fn icon_tooltip(&self) -> String { + "Project Panel".into() + } + + fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + todo!() + } + + fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + false + } + + fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + false + } +} impl ClipboardEntry { fn is_cut(&self) -> bool { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index d963025f89..be3c3de9d0 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -139,7 +139,7 @@ impl Panel for TerminalPanel { matches!(event, Event::Close) } - fn label(&self, cx: &AppContext) -> Option { + fn icon_label(&self, cx: &AppContext) -> Option { let count = self.pane.read(cx).items_len(); if count == 0 { None diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 492f61c86f..a378e00ec3 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -8,20 +8,25 @@ use settings::Settings; use std::rc::Rc; pub trait Panel: View { - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - false - } - fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - false - } - fn label(&self, _: &AppContext) -> Option { + fn position(&self, cx: &WindowContext) -> DockPosition; + fn position_is_valid(&self, position: DockPosition) -> bool; + fn icon_path(&self) -> &'static str; + fn icon_tooltip(&self) -> String; + fn icon_label(&self, _: &AppContext) -> Option { None } + fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; + fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; + fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; } pub trait PanelHandle { fn id(&self) -> usize; - fn label(&self, cx: &WindowContext) -> Option; + fn position(&self, cx: &WindowContext) -> DockPosition; + fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; + fn icon_path(&self, cx: &WindowContext) -> &'static str; + fn icon_tooltip(&self, cx: &WindowContext) -> String; + fn icon_label(&self, cx: &WindowContext) -> Option; fn is_focused(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; } @@ -34,8 +39,24 @@ where self.id() } - fn label(&self, cx: &WindowContext) -> Option { - self.read(cx).label(cx) + fn position(&self, cx: &WindowContext) -> DockPosition { + self.read(cx).position(cx) + } + + fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool { + self.read(cx).position_is_valid(position) + } + + fn icon_path(&self, cx: &WindowContext) -> &'static str { + self.read(cx).icon_path() + } + + fn icon_tooltip(&self, cx: &WindowContext) -> String { + self.read(cx).icon_tooltip() + } + + fn icon_label(&self, cx: &WindowContext) -> Option { + self.read(cx).icon_label(cx) } fn is_focused(&self, cx: &WindowContext) -> bool { @@ -82,8 +103,6 @@ impl DockPosition { } struct Item { - icon_path: &'static str, - tooltip: String, view: Rc, _subscriptions: [Subscription; 2], } @@ -132,13 +151,7 @@ impl Dock { cx.notify(); } - pub fn add_item( - &mut self, - icon_path: &'static str, - tooltip: String, - view: ViewHandle, - cx: &mut ViewContext, - ) { + pub fn add_panel(&mut self, view: ViewHandle, cx: &mut ViewContext) { let subscriptions = [ cx.observe(&view, |_, _, cx| cx.notify()), cx.subscribe(&view, |this, view, event, cx| { @@ -157,8 +170,6 @@ impl Dock { ]; self.items.push(Item { - icon_path, - tooltip, view: Rc::new(view), _subscriptions: subscriptions, }); @@ -235,15 +246,15 @@ impl Entity for PanelButtons { impl View for PanelButtons { fn ui_name() -> &'static str { - "DockToggleButton" + "PanelButtons" } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { let theme = &cx.global::().theme; let tooltip_style = theme.tooltip.clone(); let theme = &theme.workspace.status_bar.panel_buttons; - let dock = self.dock.read(cx); let item_style = theme.button.clone(); + let dock = self.dock.read(cx); let active_ix = dock.active_item_ix; let is_open = dock.is_open; let dock_position = dock.position; @@ -253,69 +264,65 @@ impl View for PanelButtons { DockPosition::Right => theme.group_right, }; - #[allow(clippy::needless_collect)] let items = dock .items .iter() - .map(|item| (item.icon_path, item.tooltip.clone(), item.view.clone())) + .map(|item| item.view.clone()) .collect::>(); - Flex::row() - .with_children(items.into_iter().enumerate().map( - |(ix, (icon_path, tooltip, item_view))| { - let action = TogglePanel { - dock_position, - item_index: ix, - }; - MouseEventHandler::::new(ix, cx, |state, cx| { - let is_active = is_open && ix == active_ix; - let style = item_style.style_for(state, is_active); - Flex::row() - .with_child( - Svg::new(icon_path) - .with_color(style.icon_color) - .constrained() - .with_width(style.icon_size) + .with_children(items.into_iter().enumerate().map(|(ix, view)| { + let action = TogglePanel { + dock_position, + item_index: ix, + }; + MouseEventHandler::::new(ix, cx, |state, cx| { + let is_active = is_open && ix == active_ix; + let style = item_style.style_for(state, is_active); + Flex::row() + .with_child( + Svg::new(view.icon_path(cx)) + .with_color(style.icon_color) + .constrained() + .with_width(style.icon_size) + .aligned(), + ) + .with_children(if let Some(label) = view.icon_label(cx) { + Some( + Label::new(label, style.label.text.clone()) + .contained() + .with_style(style.label.container) .aligned(), ) - .with_children(if let Some(label) = item_view.label(cx) { - Some( - Label::new(label, style.label.text.clone()) - .contained() - .with_style(style.label.container) - .aligned(), - ) - } else { - None - }) - .constrained() - .with_height(style.icon_size) - .contained() - .with_style(style.container) - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, { - let action = action.clone(); - move |_, this, cx| { - if let Some(workspace) = this.workspace.upgrade(cx) { - let action = action.clone(); - cx.window_context().defer(move |cx| { - workspace.update(cx, |workspace, cx| { - workspace.toggle_panel(&action, cx) - }); + } else { + None + }) + .constrained() + .with_height(style.icon_size) + .contained() + .with_style(style.container) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, { + let action = action.clone(); + move |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + let action = action.clone(); + cx.window_context().defer(move |cx| { + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel(&action, cx) }); - } + }); } - }) - .with_tooltip::( - ix, - tooltip, - Some(Box::new(action)), - tooltip_style.clone(), - cx, - ) - }, - )) + } + }) + .with_tooltip::( + ix, + view.icon_tooltip(cx), + Some(Box::new(action)), + tooltip_style.clone(), + cx, + ) + })) .contained() .with_style(group_style) .into_any() diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 0ae9e4f0ea..ddee068d46 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -1060,5 +1060,33 @@ pub(crate) mod test { } } - impl Panel for TestItem {} + impl Panel for TestItem { + fn position(&self, cx: &gpui::WindowContext) -> crate::dock::DockPosition { + unimplemented!() + } + + fn position_is_valid(&self, position: crate::dock::DockPosition) -> bool { + unimplemented!() + } + + fn icon_path(&self) -> &'static str { + unimplemented!() + } + + fn icon_tooltip(&self) -> String { + unimplemented!() + } + + fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + unimplemented!() + } + + fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + unimplemented!() + } + + fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + unimplemented!() + } + } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 59a8e97f3d..d86b25b746 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -60,7 +60,7 @@ use crate::{ notifications::simple_message_notification::MessageNotification, persistence::model::{SerializedPane, SerializedPaneGroup, SerializedWorkspace}, }; -use dock::{Dock, DockPosition, PanelButtons, TogglePanel}; +use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle, TogglePanel}; use lazy_static::lazy_static; use notifications::{NotificationHandle, NotifyResultExt}; pub use pane::*; @@ -834,6 +834,15 @@ impl Workspace { &self.right_dock } + pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) { + let dock = match panel.position(cx) { + DockPosition::Left => &mut self.left_dock, + DockPosition::Bottom => &mut self.bottom_dock, + DockPosition::Right => &mut self.right_dock, + }; + dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); + } + pub fn status_bar(&self) -> &ViewHandle { &self.status_bar } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 662638301d..394661da41 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -311,19 +311,12 @@ pub fn initialize_workspace( cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); - let project_panel = ProjectPanel::new(workspace, cx); - workspace.left_dock().update(cx, |dock, cx| { - dock.add_item( - "icons/folder_tree_16.svg", - "Project Panel".to_string(), - project_panel, - cx, - ); - }); + let project_panel = cx.add_view(|cx| ProjectPanel::new(workspace, cx)); + workspace.add_panel(panel, cx); let terminal_panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); workspace.bottom_dock().update(cx, |dock, cx| { - dock.add_item( + dock.add_panel( "icons/terminal_12.svg", "Terminals".to_string(), terminal_panel, From 6645323f1bee4bfc09c6f1256f5e2b83d568fda1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 13:28:34 -0600 Subject: [PATCH 16/61] WIP --- crates/project_panel/src/project_panel.rs | 2 +- crates/settings/src/settings.rs | 50 +++++++++++++++++++++- crates/terminal_view/src/terminal_panel.rs | 30 +++++++++++-- crates/workspace/src/dock.rs | 10 +++++ crates/workspace/src/item.rs | 4 +- crates/zed/src/zed.rs | 13 ++---- 6 files changed, 92 insertions(+), 17 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index f326d665d0..a595ec6225 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1329,7 +1329,7 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { - todo!() + cx.global::().project_panel_overrides.dock.into() } fn position_is_valid(&self, position: DockPosition) -> bool { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index fde7f516a7..528812af87 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -44,6 +44,8 @@ pub struct Settings { pub show_call_status_icon: bool, pub vim_mode: bool, pub autosave: Autosave, + pub project_panel_defaults: ProjectPanelSettings, + pub project_panel_overrides: ProjectPanelSettings, pub editor_defaults: EditorSettings, pub editor_overrides: EditorSettings, pub git: GitSettings, @@ -129,6 +131,14 @@ impl TelemetrySettings { } } +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all="lowercase")] +pub enum DockPosition { + Left, + Right, + Bottom, +} + #[derive(Clone, Debug, Default)] pub struct CopilotSettings { pub disabled_globs: Vec, @@ -156,6 +166,19 @@ pub enum GitGutter { pub struct GitGutterConfig {} +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct ProjectPanelSettings { + pub dock: DockPosition +} + +impl Default for ProjectPanelSettings { + fn default() -> Self { + Self { + dock: DockPosition::Left + } + } +} + #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct EditorSettings { pub tab_size: Option, @@ -237,7 +260,7 @@ impl Default for HourFormat { } } -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct TerminalSettings { pub shell: Option, pub working_directory: Option, @@ -250,8 +273,29 @@ pub struct TerminalSettings { pub alternate_scroll: Option, pub option_as_meta: Option, pub copy_on_select: Option, + pub dock: DockPosition, } +impl Default for TerminalSettings { + fn default() -> Self { + Self { + shell:Default::default(), + working_directory:Default::default(), + font_size:Default::default(), + font_family:Default::default(), + line_height:Default::default(), + font_features:Default::default(), + env:Default::default(), + blinking:Default::default(), + alternate_scroll:Default::default(), + option_as_meta:Default::default(), + copy_on_select:Default::default(), + dock: DockPosition::Bottom, + } + } +} + + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] #[serde(rename_all = "snake_case")] pub enum TerminalLineHeight { @@ -457,6 +501,8 @@ impl Settings { show_call_status_icon: defaults.show_call_status_icon.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), + project_panel_defaults: Default::default(), + project_panel_overrides: Default::default(), editor_defaults: EditorSettings { tab_size: required(defaults.editor.tab_size), hard_tabs: required(defaults.editor.hard_tabs), @@ -750,6 +796,8 @@ impl Settings { show_call_status_icon: true, vim_mode: false, autosave: Autosave::Off, + project_panel_defaults: Default::default(), + project_panel_overrides: Default::default(), editor_defaults: EditorSettings { tab_size: Some(4.try_into().unwrap()), hard_tabs: Some(false), diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index be3c3de9d0..e9cdb5a1ea 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -6,7 +6,7 @@ use gpui::{ use project::Project; use settings::{Settings, WorkingDirectory}; use util::ResultExt; -use workspace::{dock::Panel, pane, DraggedItem, Pane, Workspace}; +use workspace::{dock::{Panel, DockPosition}, pane, DraggedItem, Pane, Workspace}; pub fn init(cx: &mut AppContext) { cx.add_action(TerminalPanel::add_terminal); @@ -135,8 +135,20 @@ impl View for TerminalPanel { } impl Panel for TerminalPanel { - fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { - matches!(event, Event::Close) + fn position(&self, cx: &gpui::WindowContext) -> DockPosition { + cx.global::().terminal_overrides.dock.into() + } + + fn position_is_valid(&self, _: DockPosition) -> bool { + true + } + + fn icon_path(&self) -> &'static str { + "icons/terminal_12.svg" + } + + fn icon_tooltip(&self) -> String { + "Terminals".to_string() } fn icon_label(&self, cx: &AppContext) -> Option { @@ -147,4 +159,16 @@ impl Panel for TerminalPanel { Some(count.to_string()) } } + + fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + todo!() + } + + fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + todo!() + } + + fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { + matches!(event, Event::Close) + } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index a378e00ec3..100b8e8661 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -92,6 +92,16 @@ pub enum DockPosition { Right, } +impl From for DockPosition { + fn from(value: settings::DockPosition) -> Self { + match value { + settings::DockPosition::Left => Self::Left, + settings::DockPosition::Bottom => Self::Bottom, + settings::DockPosition::Right => Self::Right, + } + } +} + impl DockPosition { fn to_resizable_side(self) -> Side { match self { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index ddee068d46..a4e034d741 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -1061,11 +1061,11 @@ pub(crate) mod test { } impl Panel for TestItem { - fn position(&self, cx: &gpui::WindowContext) -> crate::dock::DockPosition { + fn position(&self, _cx: &gpui::WindowContext) -> crate::dock::DockPosition { unimplemented!() } - fn position_is_valid(&self, position: crate::dock::DockPosition) -> bool { + fn position_is_valid(&self, _position: crate::dock::DockPosition) -> bool { unimplemented!() } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 394661da41..8a40a5d1d3 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -311,18 +311,11 @@ pub fn initialize_workspace( cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); - let project_panel = cx.add_view(|cx| ProjectPanel::new(workspace, cx)); - workspace.add_panel(panel, cx); + let project_panel = ProjectPanel::new(workspace, cx); + workspace.add_panel(project_panel, cx); let terminal_panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); - workspace.bottom_dock().update(cx, |dock, cx| { - dock.add_panel( - "icons/terminal_12.svg", - "Terminals".to_string(), - terminal_panel, - cx, - ); - }); + workspace.add_panel(terminal_panel, cx); let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx)); let diagnostic_summary = From 0d78266ddb15772d946922068d557dbfb7e7e5a4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 16:37:10 -0600 Subject: [PATCH 17/61] Replace todo with unimplemented to reduce distractions --- crates/editor/src/items.rs | 12 ++++++------ crates/gpui/src/elements/list.rs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index e971af943a..fc7c1cd968 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1222,27 +1222,27 @@ mod tests { } fn as_local(&self) -> Option<&dyn language::LocalFile> { - todo!() + unimplemented!() } fn mtime(&self) -> SystemTime { - todo!() + unimplemented!() } fn file_name<'a>(&'a self, _: &'a gpui::AppContext) -> &'a std::ffi::OsStr { - todo!() + unimplemented!() } fn is_deleted(&self) -> bool { - todo!() + unimplemented!() } fn as_any(&self) -> &dyn std::any::Any { - todo!() + unimplemented!() } fn to_proto(&self) -> rpc::proto::File { - todo!() + unimplemented!() } } } diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index ca73196c8b..1cf8cc986f 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -990,7 +990,7 @@ mod tests { _: &mut V, _: &mut ViewContext, ) { - todo!() + unimplemented!() } fn rect_for_text_range( @@ -1003,7 +1003,7 @@ mod tests { _: &V, _: &ViewContext, ) -> Option { - todo!() + unimplemented!() } fn debug(&self, _: RectF, _: &(), _: &(), _: &V, _: &ViewContext) -> serde_json::Value { From 6a7feb4c4c0fea7ef5ab9c01a5029f43930519ed Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 17:26:54 -0600 Subject: [PATCH 18/61] Allow the project panel to be docked right or left Co-Authored-By: Joseph Lyons --- assets/settings/default.json | 5 ++++ crates/project_panel/src/project_panel.rs | 20 +++++++++++-- crates/settings/src/settings.rs | 4 ++- crates/terminal_view/src/terminal_panel.rs | 2 +- crates/workspace/src/dock.rs | 35 ++++++++++++---------- crates/workspace/src/item.rs | 2 +- crates/workspace/src/workspace.rs | 22 ++++++++++++-- 7 files changed, 66 insertions(+), 24 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 58fb28bc62..5d85e751d4 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -107,6 +107,9 @@ // Automatically update Zed "auto_update": true, // Git gutter behavior configuration. + "project_panel": { + "dock": "left" + }, "git": { // Control whether the git gutter is shown. May take 2 values: // 1. Show the gutter @@ -149,6 +152,8 @@ // } // } "shell": "system", + // Where to dock terminals panel. Can be 'left', 'right', 'bottom'. + "dock": "bottom", // What working directory to use when launching the terminal. // May take 4 values: // 1. Use the current file's project directory. Will Fallback to the diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index a595ec6225..8ee701c3cc 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -135,12 +135,25 @@ pub enum Event { entry_id: ProjectEntryId, focus_opened_item: bool, }, + DockPositionChanged, } impl ProjectPanel { pub fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> ViewHandle { let project = workspace.project().clone(); let project_panel = cx.add_view(|cx: &mut ViewContext| { + // Update the dock position when the setting changes. + let mut old_dock_position = cx.global::().project_panel_overrides.dock; + dbg!(old_dock_position); + cx.observe_global::(move |_, cx| { + let new_dock_position = cx.global::().project_panel_overrides.dock; + dbg!(new_dock_position); + if new_dock_position != old_dock_position { + old_dock_position = new_dock_position; + cx.emit(Event::DockPositionChanged); + } + }).detach(); + cx.observe(&project, |this, _, cx| { this.update_visible_entries(None, cx); cx.notify(); @@ -242,7 +255,8 @@ impl ProjectPanel { } } } - } + }, + Event::DockPositionChanged => {}, } }) .detach(); @@ -1344,8 +1358,8 @@ impl workspace::dock::Panel for ProjectPanel { "Project Panel".into() } - fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - todo!() + fn should_change_position_on_event(event: &Self::Event) -> bool { + matches!(event, Event::DockPositionChanged) } fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 528812af87..0eb9eb9e5d 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -131,7 +131,7 @@ impl TelemetrySettings { } } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq)] #[serde(rename_all="lowercase")] pub enum DockPosition { Left, @@ -407,6 +407,7 @@ pub struct SettingsFileContent { pub autosave: Option, #[serde(flatten)] pub editor: EditorSettings, + pub project_panel: ProjectPanelSettings, #[serde(default)] pub journal: JournalSettings, #[serde(default)] @@ -609,6 +610,7 @@ impl Settings { } } self.editor_overrides = data.editor; + self.project_panel_overrides = data.project_panel; self.git_overrides = data.git.unwrap_or_default(); self.journal_overrides = data.journal; self.terminal_defaults.font_size = data.terminal.font_size; diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index e9cdb5a1ea..e2bf2c9e32 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -160,7 +160,7 @@ impl Panel for TerminalPanel { } } - fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_change_position_on_event(_: &Self::Event) -> bool { todo!() } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 100b8e8661..0e2d84e626 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -15,7 +15,7 @@ pub trait Panel: View { fn icon_label(&self, _: &AppContext) -> Option { None } - fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; + fn should_change_position_on_event(_: &Self::Event) -> bool; fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; } @@ -80,7 +80,7 @@ pub enum Event { pub struct Dock { position: DockPosition, - items: Vec, + panels: Vec, is_open: bool, active_item_ix: usize, } @@ -112,8 +112,8 @@ impl DockPosition { } } -struct Item { - view: Rc, +struct PanelEntry { + panel: Rc, _subscriptions: [Subscription; 2], } @@ -134,7 +134,7 @@ impl Dock { pub fn new(position: DockPosition) -> Self { Self { position, - items: Default::default(), + panels: Default::default(), active_item_ix: 0, is_open: false, } @@ -161,15 +161,15 @@ impl Dock { cx.notify(); } - pub fn add_panel(&mut self, view: ViewHandle, cx: &mut ViewContext) { + pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) { let subscriptions = [ - cx.observe(&view, |_, _, cx| cx.notify()), - cx.subscribe(&view, |this, view, event, cx| { + cx.observe(&panel, |_, _, cx| cx.notify()), + cx.subscribe(&panel, |this, view, event, cx| { if view.read(cx).should_activate_on_event(event, cx) { if let Some(ix) = this - .items + .panels .iter() - .position(|item| item.view.id() == view.id()) + .position(|item| item.panel.id() == view.id()) { this.activate_item(ix, cx); } @@ -179,13 +179,18 @@ impl Dock { }), ]; - self.items.push(Item { - view: Rc::new(view), + self.panels.push(PanelEntry { + panel: Rc::new(panel), _subscriptions: subscriptions, }); cx.notify() } + pub fn remove_panel(&mut self, panel: &ViewHandle, cx: &mut ViewContext) { + self.panels.retain(|entry| entry.panel.id() != panel.id()); + cx.notify(); + } + pub fn activate_item(&mut self, item_ix: usize, cx: &mut ViewContext) { self.active_item_ix = item_ix; cx.notify(); @@ -202,7 +207,7 @@ impl Dock { pub fn active_item(&self) -> Option<&Rc> { if self.is_open { - self.items.get(self.active_item_ix).map(|item| &item.view) + self.panels.get(self.active_item_ix).map(|item| &item.panel) } else { None } @@ -275,9 +280,9 @@ impl View for PanelButtons { }; let items = dock - .items + .panels .iter() - .map(|item| item.view.clone()) + .map(|item| item.panel.clone()) .collect::>(); Flex::row() .with_children(items.into_iter().enumerate().map(|(ix, view)| { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index a4e034d741..d2b9251a62 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -1077,7 +1077,7 @@ pub(crate) mod test { unimplemented!() } - fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_change_position_on_event(_: &Self::Event) -> bool { unimplemented!() } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index d86b25b746..1708555f50 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -836,10 +836,26 @@ impl Workspace { pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) { let dock = match panel.position(cx) { - DockPosition::Left => &mut self.left_dock, - DockPosition::Bottom => &mut self.bottom_dock, - DockPosition::Right => &mut self.right_dock, + DockPosition::Left => &self.left_dock, + DockPosition::Bottom => &self.bottom_dock, + DockPosition::Right => &self.right_dock, }; + + cx.subscribe(&panel, { + let mut dock = dock.clone(); + move |this, panel, event, cx| { + if T::should_change_position_on_event(event) { + dock.update(cx, |dock, cx| dock.remove_panel(&panel, cx)); + dock = match panel.read(cx).position(cx) { + DockPosition::Left => &this.left_dock, + DockPosition::Bottom => &this.bottom_dock, + DockPosition::Right => &this.right_dock, + }.clone(); + dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); + } + } + }).detach(); + dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); } From ba248244ec94724af99a41c9ec791ac437cb78e4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 17:36:49 -0600 Subject: [PATCH 19/61] Allow terminal to be docked left, bottom, or right Co-Authored-By: Joseph Lyons --- crates/project_panel/src/project_panel.rs | 2 -- crates/terminal_view/src/terminal_panel.rs | 16 +++++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 8ee701c3cc..c22e297a02 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -144,10 +144,8 @@ impl ProjectPanel { let project_panel = cx.add_view(|cx: &mut ViewContext| { // Update the dock position when the setting changes. let mut old_dock_position = cx.global::().project_panel_overrides.dock; - dbg!(old_dock_position); cx.observe_global::(move |_, cx| { let new_dock_position = cx.global::().project_panel_overrides.dock; - dbg!(new_dock_position); if new_dock_position != old_dock_position { old_dock_position = new_dock_position; cx.emit(Event::DockPositionChanged); diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index e2bf2c9e32..58deda9236 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -14,6 +14,7 @@ pub fn init(cx: &mut AppContext) { pub enum Event { Close, + DockPositionChanged, } pub struct TerminalPanel { @@ -25,6 +26,15 @@ pub struct TerminalPanel { impl TerminalPanel { pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + let mut old_dock_position = cx.global::().terminal_overrides.dock; + cx.observe_global::(move |_, cx| { + let new_dock_position = cx.global::().terminal_overrides.dock; + if new_dock_position != old_dock_position { + old_dock_position = new_dock_position; + cx.emit(Event::DockPositionChanged); + } + }).detach(); + let this = cx.weak_handle(); let pane = cx.add_view(|cx| { let window_id = cx.window_id(); @@ -160,12 +170,12 @@ impl Panel for TerminalPanel { } } - fn should_change_position_on_event(_: &Self::Event) -> bool { - todo!() + fn should_change_position_on_event(event: &Self::Event) -> bool { + matches!(event, Event::DockPositionChanged) } fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - todo!() + false } fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { From f554877ff47ccb11a5c9b4fb1971241ab3407f3a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 17:41:24 -0600 Subject: [PATCH 20/61] Render the bottom dock buttons before the right dock buttons --- crates/workspace/src/workspace.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1708555f50..bfc0d8228f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -575,8 +575,8 @@ impl Workspace { let status_bar = cx.add_view(|cx| { let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); status_bar.add_left_item(left_dock_buttons, cx); - status_bar.add_right_item(bottom_dock_buttons, cx); status_bar.add_right_item(right_dock_buttons, cx); + status_bar.add_right_item(bottom_dock_buttons, cx); status_bar }); From 9173916ab800959e0a7a46e463e4f1c801027e9d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 20:24:37 -0600 Subject: [PATCH 21/61] Remove unused terminal_button module --- crates/terminal_view/src/terminal_button.rs | 173 -------------------- crates/terminal_view/src/terminal_view.rs | 1 - 2 files changed, 174 deletions(-) delete mode 100644 crates/terminal_view/src/terminal_button.rs diff --git a/crates/terminal_view/src/terminal_button.rs b/crates/terminal_view/src/terminal_button.rs deleted file mode 100644 index 0747f86d2d..0000000000 --- a/crates/terminal_view/src/terminal_button.rs +++ /dev/null @@ -1,173 +0,0 @@ -use crate::TerminalView; -use context_menu::{ContextMenu, ContextMenuItem}; -use gpui::{ - elements::*, - platform::{CursorStyle, MouseButton}, - AnyElement, Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle, -}; -use settings::Settings; -use std::any::TypeId; -use workspace::{ - item::ItemHandle, - NewTerminal, StatusItemView, Workspace, -}; - -pub struct TerminalButton { - workspace: WeakViewHandle, - popup_menu: ViewHandle, -} - -impl Entity for TerminalButton { - type Event = (); -} - -impl View for TerminalButton { - fn ui_name() -> &'static str { - "TerminalButton" - } - - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let workspace = self.workspace.upgrade(cx); - let project = match workspace { - Some(workspace) => workspace.read(cx).project().read(cx), - None => return Empty::new().into_any(), - }; - - let focused_view = cx.focused_view_id(); - let active = focused_view - .map(|view_id| { - cx.view_type_id(cx.window_id(), view_id) == Some(TypeId::of::()) - }) - .unwrap_or(false); - - let has_terminals = !project.local_terminal_handles().is_empty(); - let terminal_count = project.local_terminal_handles().len() as i32; - let theme = cx.global::().theme.clone(); - - Stack::new() - .with_child( - MouseEventHandler::::new(0, cx, { - let theme = theme.clone(); - move |state, _cx| { - let style = theme - .workspace - .status_bar - .panel_buttons - .button - .style_for(state, active); - - Flex::row() - .with_child( - Svg::new("icons/terminal_12.svg") - .with_color(style.icon_color) - .constrained() - .with_width(style.icon_size) - .aligned() - .into_any_named("terminals-icon"), - ) - .with_children(has_terminals.then(|| { - Label::new(terminal_count.to_string(), style.label.text.clone()) - .contained() - .with_style(style.label.container) - .aligned() - })) - .constrained() - .with_height(style.icon_size) - .contained() - .with_style(style.container) - } - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, this, cx| { - if has_terminals { - this.deploy_terminal_menu(cx); - } else { - if !active { - if let Some(workspace) = this.workspace.upgrade(cx) { - workspace.update(cx, |_workspace, _cx| { - todo!() - }) - } - } - }; - }) - .with_tooltip::( - 0, - "Show Terminal".into(), - None, // TODO! We need a new action here. - theme.tooltip.clone(), - cx, - ), - ) - .with_child(ChildView::new(&self.popup_menu, cx).aligned().top().right()) - .into_any_named("terminal button") - } -} - -impl TerminalButton { - pub fn new(workspace: ViewHandle, cx: &mut ViewContext) -> Self { - let button_view_id = cx.view_id(); - cx.observe(&workspace, |_, _, cx| cx.notify()).detach(); - Self { - workspace: workspace.downgrade(), - popup_menu: cx.add_view(|cx| { - let mut menu = ContextMenu::new(button_view_id, cx); - menu.set_position_mode(OverlayPositionMode::Local); - menu - }), - } - } - - pub fn deploy_terminal_menu(&mut self, cx: &mut ViewContext) { - let mut menu_options = vec![ContextMenuItem::action("New Terminal", NewTerminal)]; - - if let Some(workspace) = self.workspace.upgrade(cx) { - let project = workspace.read(cx).project().read(cx); - let local_terminal_handles = project.local_terminal_handles(); - - if !local_terminal_handles.is_empty() { - menu_options.push(ContextMenuItem::Separator) - } - - for local_terminal_handle in local_terminal_handles { - if let Some(terminal) = local_terminal_handle.upgrade(cx) { - let workspace = self.workspace.clone(); - let local_terminal_handle = local_terminal_handle.clone(); - menu_options.push(ContextMenuItem::handler( - terminal.read(cx).title(), - move |cx| { - if let Some(workspace) = workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - let terminal = workspace - .items_of_type::(cx) - .find(|terminal| { - terminal.read(cx).model().downgrade() - == local_terminal_handle - }); - if let Some(terminal) = terminal { - workspace.activate_item(&terminal, cx); - } - }); - } - }, - )) - } - } - } - - self.popup_menu.update(cx, |menu, cx| { - menu.show( - Default::default(), - AnchorCorner::BottomRight, - menu_options, - cx, - ); - }); - } -} - -impl StatusItemView for TerminalButton { - fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, cx: &mut ViewContext) { - cx.notify(); - } -} diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index b318b572b5..2c33cc8f39 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1,5 +1,4 @@ mod persistence; -pub mod terminal_button; pub mod terminal_element; pub mod terminal_panel; From 6e3ce6332a2af20525dc928137237847dac212d7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 10:43:42 +0200 Subject: [PATCH 22/61] Fix panic when not specifying a `project_panel` setting --- crates/project_panel/src/project_panel.rs | 13 ++++--- crates/settings/src/settings.rs | 43 +++++++++-------------- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index c22e297a02..85694ec629 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -150,7 +150,8 @@ impl ProjectPanel { old_dock_position = new_dock_position; cx.emit(Event::DockPositionChanged); } - }).detach(); + }) + .detach(); cx.observe(&project, |this, _, cx| { this.update_visible_entries(None, cx); @@ -253,8 +254,8 @@ impl ProjectPanel { } } } - }, - Event::DockPositionChanged => {}, + } + Event::DockPositionChanged => {} } }) .detach(); @@ -1341,7 +1342,11 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { - cx.global::().project_panel_overrides.dock.into() + cx.global::() + .project_panel_overrides + .dock + .map(Into::into) + .unwrap_or(DockPosition::Left) } fn position_is_valid(&self, position: DockPosition) -> bool { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 0eb9eb9e5d..a49d84c6a8 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -132,7 +132,7 @@ impl TelemetrySettings { } #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq)] -#[serde(rename_all="lowercase")] +#[serde(rename_all = "lowercase")] pub enum DockPosition { Left, Right, @@ -166,17 +166,9 @@ pub enum GitGutter { pub struct GitGutterConfig {} -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct ProjectPanelSettings { - pub dock: DockPosition -} - -impl Default for ProjectPanelSettings { - fn default() -> Self { - Self { - dock: DockPosition::Left - } - } + pub dock: Option, } #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] @@ -279,23 +271,22 @@ pub struct TerminalSettings { impl Default for TerminalSettings { fn default() -> Self { Self { - shell:Default::default(), - working_directory:Default::default(), - font_size:Default::default(), - font_family:Default::default(), - line_height:Default::default(), - font_features:Default::default(), - env:Default::default(), - blinking:Default::default(), - alternate_scroll:Default::default(), - option_as_meta:Default::default(), - copy_on_select:Default::default(), + shell: Default::default(), + working_directory: Default::default(), + font_size: Default::default(), + font_family: Default::default(), + line_height: Default::default(), + font_features: Default::default(), + env: Default::default(), + blinking: Default::default(), + alternate_scroll: Default::default(), + option_as_meta: Default::default(), + copy_on_select: Default::default(), dock: DockPosition::Bottom, } } } - #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] #[serde(rename_all = "snake_case")] pub enum TerminalLineHeight { @@ -407,7 +398,7 @@ pub struct SettingsFileContent { pub autosave: Option, #[serde(flatten)] pub editor: EditorSettings, - pub project_panel: ProjectPanelSettings, + pub project_panel: Option, #[serde(default)] pub journal: JournalSettings, #[serde(default)] @@ -502,7 +493,7 @@ impl Settings { show_call_status_icon: defaults.show_call_status_icon.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), - project_panel_defaults: Default::default(), + project_panel_defaults: defaults.project_panel.unwrap(), project_panel_overrides: Default::default(), editor_defaults: EditorSettings { tab_size: required(defaults.editor.tab_size), @@ -610,7 +601,7 @@ impl Settings { } } self.editor_overrides = data.editor; - self.project_panel_overrides = data.project_panel; + self.project_panel_overrides = data.project_panel.unwrap_or_default(); self.git_overrides = data.git.unwrap_or_default(); self.journal_overrides = data.journal; self.terminal_defaults.font_size = data.terminal.font_size; From 8fa379bbc5c09c7e4e57b4aa0b59f0dfc35ca8d2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 11:06:37 +0200 Subject: [PATCH 23/61] Maintain panel visibility when changing its position --- crates/workspace/src/dock.rs | 18 ++++++++++++++++-- crates/workspace/src/workspace.rs | 23 +++++++++++++++++++---- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 0e2d84e626..3d5c4b6c38 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -187,8 +187,22 @@ impl Dock { } pub fn remove_panel(&mut self, panel: &ViewHandle, cx: &mut ViewContext) { - self.panels.retain(|entry| entry.panel.id() != panel.id()); - cx.notify(); + if let Some(panel_ix) = self + .panels + .iter() + .position(|item| item.panel.id() == panel.id()) + { + if panel_ix == self.active_item_ix { + self.active_item_ix = 0; + cx.emit(Event::Close); + } + self.panels.remove(panel_ix); + cx.notify(); + } + } + + pub fn panels_len(&self) -> usize { + self.panels.len() } pub fn activate_item(&mut self, item_ix: usize, cx: &mut ViewContext) { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index bfc0d8228f..d147557103 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -845,16 +845,31 @@ impl Workspace { let mut dock = dock.clone(); move |this, panel, event, cx| { if T::should_change_position_on_event(event) { - dock.update(cx, |dock, cx| dock.remove_panel(&panel, cx)); + let mut was_visible = false; + dock.update(cx, |dock, cx| { + was_visible = dock.is_open() + && dock + .active_item() + .map_or(false, |item| item.as_any().is::()); + dock.remove_panel(&panel, cx); + }); dock = match panel.read(cx).position(cx) { DockPosition::Left => &this.left_dock, DockPosition::Bottom => &this.bottom_dock, DockPosition::Right => &this.right_dock, - }.clone(); - dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); + } + .clone(); + dock.update(cx, |dock, cx| { + dock.add_panel(panel, cx); + if was_visible { + dock.set_open(true, cx); + dock.activate_item(dock.panels_len() - 1, cx); + } + }); } } - }).detach(); + }) + .detach(); dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); } From f28eee88b64a69ed436763c856105d29dc7705c0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 14:05:48 +0200 Subject: [PATCH 24/61] Add context menu to change panel position --- crates/project_panel/src/project_panel.rs | 44 +++-- crates/settings/src/settings.rs | 30 +--- crates/terminal_view/src/terminal_panel.rs | 24 ++- crates/workspace/src/dock.rs | 181 +++++++++++++++------ crates/workspace/src/item.rs | 8 + 5 files changed, 190 insertions(+), 97 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 85694ec629..4c75035c83 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -17,7 +17,7 @@ use gpui::{ }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; -use settings::Settings; +use settings::{settings_file::SettingsFile, Settings}; use std::{ cmp::Ordering, collections::{hash_map, HashMap}, @@ -28,7 +28,10 @@ use std::{ }; use theme::ProjectPanelEntry; use unicase::UniCase; -use workspace::{dock::DockPosition, Workspace}; +use workspace::{ + dock::{DockPosition, Panel}, + Workspace, +}; const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX; @@ -142,17 +145,6 @@ impl ProjectPanel { pub fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> ViewHandle { let project = workspace.project().clone(); let project_panel = cx.add_view(|cx: &mut ViewContext| { - // Update the dock position when the setting changes. - let mut old_dock_position = cx.global::().project_panel_overrides.dock; - cx.observe_global::(move |_, cx| { - let new_dock_position = cx.global::().project_panel_overrides.dock; - if new_dock_position != old_dock_position { - old_dock_position = new_dock_position; - cx.emit(Event::DockPositionChanged); - } - }) - .detach(); - cx.observe(&project, |this, _, cx| { this.update_visible_entries(None, cx); cx.notify(); @@ -224,6 +216,18 @@ impl ProjectPanel { workspace: workspace.weak_handle(), }; this.update_visible_entries(None, cx); + + // Update the dock position when the setting changes. + let mut old_dock_position = this.position(cx); + cx.observe_global::(move |this, cx| { + let new_dock_position = this.position(cx); + if new_dock_position != old_dock_position { + old_dock_position = new_dock_position; + cx.emit(Event::DockPositionChanged); + } + }) + .detach(); + this }); @@ -1342,17 +1346,25 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { - cx.global::() + let settings = cx.global::(); + settings .project_panel_overrides .dock - .map(Into::into) - .unwrap_or(DockPosition::Left) + .or(settings.project_panel_defaults.dock) + .unwrap() + .into() } fn position_is_valid(&self, position: DockPosition) -> bool { matches!(position, DockPosition::Left | DockPosition::Right) } + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { + SettingsFile::update(cx, move |settings| { + settings.project_panel.dock = Some(position.into()) + }) + } + fn icon_path(&self) -> &'static str { "icons/folder_tree_16.svg" } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index a49d84c6a8..3bd3786071 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -252,7 +252,7 @@ impl Default for HourFormat { } } -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct TerminalSettings { pub shell: Option, pub working_directory: Option, @@ -265,26 +265,7 @@ pub struct TerminalSettings { pub alternate_scroll: Option, pub option_as_meta: Option, pub copy_on_select: Option, - pub dock: DockPosition, -} - -impl Default for TerminalSettings { - fn default() -> Self { - Self { - shell: Default::default(), - working_directory: Default::default(), - font_size: Default::default(), - font_family: Default::default(), - line_height: Default::default(), - font_features: Default::default(), - env: Default::default(), - blinking: Default::default(), - alternate_scroll: Default::default(), - option_as_meta: Default::default(), - copy_on_select: Default::default(), - dock: DockPosition::Bottom, - } - } + pub dock: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] @@ -398,7 +379,8 @@ pub struct SettingsFileContent { pub autosave: Option, #[serde(flatten)] pub editor: EditorSettings, - pub project_panel: Option, + #[serde(default)] + pub project_panel: ProjectPanelSettings, #[serde(default)] pub journal: JournalSettings, #[serde(default)] @@ -493,7 +475,7 @@ impl Settings { show_call_status_icon: defaults.show_call_status_icon.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), - project_panel_defaults: defaults.project_panel.unwrap(), + project_panel_defaults: defaults.project_panel, project_panel_overrides: Default::default(), editor_defaults: EditorSettings { tab_size: required(defaults.editor.tab_size), @@ -601,7 +583,7 @@ impl Settings { } } self.editor_overrides = data.editor; - self.project_panel_overrides = data.project_panel.unwrap_or_default(); + self.project_panel_overrides = data.project_panel; self.git_overrides = data.git.unwrap_or_default(); self.journal_overrides = data.journal; self.terminal_defaults.font_size = data.terminal.font_size; diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 58deda9236..4e90a8fec4 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -4,9 +4,12 @@ use gpui::{ WeakViewHandle, }; use project::Project; -use settings::{Settings, WorkingDirectory}; +use settings::{settings_file::SettingsFile, Settings, WorkingDirectory}; use util::ResultExt; -use workspace::{dock::{Panel, DockPosition}, pane, DraggedItem, Pane, Workspace}; +use workspace::{ + dock::{DockPosition, Panel}, + pane, DraggedItem, Pane, Workspace, +}; pub fn init(cx: &mut AppContext) { cx.add_action(TerminalPanel::add_terminal); @@ -33,7 +36,8 @@ impl TerminalPanel { old_dock_position = new_dock_position; cx.emit(Event::DockPositionChanged); } - }).detach(); + }) + .detach(); let this = cx.weak_handle(); let pane = cx.add_view(|cx| { @@ -146,13 +150,25 @@ impl View for TerminalPanel { impl Panel for TerminalPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { - cx.global::().terminal_overrides.dock.into() + let settings = cx.global::(); + settings + .terminal_overrides + .dock + .or(settings.terminal_defaults.dock) + .unwrap() + .into() } fn position_is_valid(&self, _: DockPosition) -> bool { true } + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { + SettingsFile::update(cx, move |settings| { + settings.terminal.dock = Some(position.into()); + }); + } + fn icon_path(&self) -> &'static str { "icons/terminal_12.svg" } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 3d5c4b6c38..6f7f6b1c12 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,4 +1,5 @@ use crate::{StatusItemView, Workspace}; +use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, @@ -10,6 +11,7 @@ use std::rc::Rc; pub trait Panel: View { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; fn icon_label(&self, _: &AppContext) -> Option { @@ -24,6 +26,7 @@ pub trait PanelHandle { fn id(&self) -> usize; fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; + fn set_position(&self, position: DockPosition, cx: &mut WindowContext); fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -47,6 +50,10 @@ where self.read(cx).position_is_valid(position) } + fn set_position(&self, position: DockPosition, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.set_position(position, cx)) + } + fn icon_path(&self, cx: &WindowContext) -> &'static str { self.read(cx).icon_path() } @@ -102,7 +109,25 @@ impl From for DockPosition { } } +impl From for settings::DockPosition { + fn from(value: DockPosition) -> settings::DockPosition { + match value { + DockPosition::Left => settings::DockPosition::Left, + DockPosition::Bottom => settings::DockPosition::Bottom, + DockPosition::Right => settings::DockPosition::Right, + } + } +} + impl DockPosition { + fn to_label(&self) -> &'static str { + match self { + Self::Left => "left", + Self::Bottom => "bottom", + Self::Right => "right", + } + } + fn to_resizable_side(self) -> Side { match self { Self::Left => Side::Right, @@ -114,6 +139,7 @@ impl DockPosition { struct PanelEntry { panel: Rc, + context_menu: ViewHandle, _subscriptions: [Subscription; 2], } @@ -179,8 +205,14 @@ impl Dock { }), ]; + let dock_view_id = cx.view_id(); self.panels.push(PanelEntry { panel: Rc::new(panel), + context_menu: cx.add_view(|cx| { + let mut menu = ContextMenu::new(dock_view_id, cx); + menu.set_position_mode(OverlayPositionMode::Local); + menu + }), _subscriptions: subscriptions, }); cx.notify() @@ -292,66 +324,109 @@ impl View for PanelButtons { DockPosition::Bottom => theme.group_bottom, DockPosition::Right => theme.group_right, }; + let menu_corner = match dock_position { + DockPosition::Left => AnchorCorner::BottomLeft, + DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight, + }; let items = dock .panels .iter() - .map(|item| item.panel.clone()) + .map(|item| (item.panel.clone(), item.context_menu.clone())) .collect::>(); Flex::row() - .with_children(items.into_iter().enumerate().map(|(ix, view)| { - let action = TogglePanel { - dock_position, - item_index: ix, - }; - MouseEventHandler::::new(ix, cx, |state, cx| { - let is_active = is_open && ix == active_ix; - let style = item_style.style_for(state, is_active); - Flex::row() - .with_child( - Svg::new(view.icon_path(cx)) - .with_color(style.icon_color) - .constrained() - .with_width(style.icon_size) - .aligned(), - ) - .with_children(if let Some(label) = view.icon_label(cx) { - Some( - Label::new(label, style.label.text.clone()) - .contained() - .with_style(style.label.container) - .aligned(), + .with_children( + items + .into_iter() + .enumerate() + .map(|(ix, (view, context_menu))| { + let action = TogglePanel { + dock_position, + item_index: ix, + }; + + Stack::new() + .with_child( + MouseEventHandler::::new(ix, cx, |state, cx| { + let is_active = is_open && ix == active_ix; + let style = item_style.style_for(state, is_active); + Flex::row() + .with_child( + Svg::new(view.icon_path(cx)) + .with_color(style.icon_color) + .constrained() + .with_width(style.icon_size) + .aligned(), + ) + .with_children(if let Some(label) = view.icon_label(cx) { + Some( + Label::new(label, style.label.text.clone()) + .contained() + .with_style(style.label.container) + .aligned(), + ) + } else { + None + }) + .constrained() + .with_height(style.icon_size) + .contained() + .with_style(style.container) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, { + let action = action.clone(); + move |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + let action = action.clone(); + cx.window_context().defer(move |cx| { + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel(&action, cx) + }); + }); + } + } + }) + .on_click(MouseButton::Right, { + let view = view.clone(); + let menu = context_menu.clone(); + move |_, _, cx| { + const POSITIONS: [DockPosition; 3] = [ + DockPosition::Left, + DockPosition::Right, + DockPosition::Bottom, + ]; + + menu.update(cx, |menu, cx| { + let items = POSITIONS + .into_iter() + .filter(|position| { + *position != dock_position + && view.position_is_valid(*position, cx) + }) + .map(|position| { + let view = view.clone(); + ContextMenuItem::handler( + format!("Dock {}", position.to_label()), + move |cx| view.set_position(position, cx), + ) + }) + .collect(); + menu.show(Default::default(), menu_corner, items, cx); + }) + } + }) + .with_tooltip::( + ix, + view.icon_tooltip(cx), + Some(Box::new(action)), + tooltip_style.clone(), + cx, + ), ) - } else { - None - }) - .constrained() - .with_height(style.icon_size) - .contained() - .with_style(style.container) - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, { - let action = action.clone(); - move |_, this, cx| { - if let Some(workspace) = this.workspace.upgrade(cx) { - let action = action.clone(); - cx.window_context().defer(move |cx| { - workspace.update(cx, |workspace, cx| { - workspace.toggle_panel(&action, cx) - }); - }); - } - } - }) - .with_tooltip::( - ix, - view.icon_tooltip(cx), - Some(Box::new(action)), - tooltip_style.clone(), - cx, - ) - })) + .with_child(ChildView::new(&context_menu, cx)) + }), + ) .contained() .with_style(group_style) .into_any() diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index d2b9251a62..dfad53c3a3 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -1069,6 +1069,14 @@ pub(crate) mod test { unimplemented!() } + fn set_position( + &mut self, + _position: crate::dock::DockPosition, + _cx: &mut ViewContext, + ) { + unimplemented!() + } + fn icon_path(&self) -> &'static str { unimplemented!() } From 9b9d53fcf8247fea9fca3323c301dd5667a2bd20 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 15:40:46 +0200 Subject: [PATCH 25/61] Focus the root view if the previously-focused view isn't rendered --- crates/gpui/src/app.rs | 57 +++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index d5a7a2f3ab..488f55d0c6 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1451,27 +1451,13 @@ impl AppContext { self.views_metadata.remove(&(window_id, view_id)); let mut view = self.views.remove(&(window_id, view_id)).unwrap(); view.release(self); - let change_focus_to = self.windows.get_mut(&window_id).and_then(|window| { + if let Some(window) = self.windows.get_mut(&window_id) { window.parents.remove(&view_id); window .invalidation .get_or_insert_with(Default::default) .removed .push(view_id); - if window.focused_view_id == Some(view_id) { - Some(window.root_view().id()) - } else { - None - } - }); - - if let Some(view_id) = change_focus_to { - self.pending_effects - .push_back(Effect::Focus(FocusEffect::View { - window_id, - view_id: Some(view_id), - is_forced: false, - })); } self.pending_effects @@ -1710,6 +1696,25 @@ impl AppContext { cx.invalidate(invalidation, appearance); if cx.layout(refreshing).log_err().is_some() { updated_windows.insert(window_id); + + // When the previously-focused view isn't rendered and + // there isn't any pending focus, focus the root view. + if let Some(focused_view_id) = cx.focused_view_id() { + let root_view_id = cx.window.root_view().id(); + if focused_view_id != root_view_id + && !cx.window.parents.contains_key(&focused_view_id) + && !focus_effects.contains_key(&window_id) + { + focus_effects.insert( + window_id, + FocusEffect::View { + window_id, + view_id: Some(root_view_id), + is_forced: false, + }, + ); + } + } } } }); @@ -1886,9 +1891,27 @@ impl AppContext { fn handle_focus_effect(&mut self, effect: FocusEffect) { let window_id = effect.window_id(); self.update_window(window_id, |cx| { + // Ensure the newly-focused view has been rendered, otherwise focus + // the root view instead. let focused_id = match effect { - FocusEffect::View { view_id, .. } => view_id, - FocusEffect::ViewParent { view_id, .. } => cx.ancestors(view_id).skip(1).next(), + FocusEffect::View { view_id, .. } => { + if let Some(view_id) = view_id { + if cx.window.parents.contains_key(&view_id) { + Some(view_id) + } else { + Some(cx.root_view().id()) + } + } else { + None + } + } + FocusEffect::ViewParent { view_id, .. } => Some( + cx.window + .parents + .get(&view_id) + .copied() + .unwrap_or(cx.root_view().id()), + ), }; let focus_changed = cx.window.focused_view_id != focused_id; From e507eadb4b08d284d04a906ddd80517193104ceb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 15:43:44 +0200 Subject: [PATCH 26/61] Rely on root getting re-focused when panel isn't rendered This achieves two things: - When a panel like `TerminalPanel` closes its last tab, the focus is returned to the root view (the `Workspace`) - When a panel is moved from a dock to another, it will get rendered in another spot but the focus will be kept on the panel This also ensures the focus is not lost when a panel is moved from a dock to another because that view --- crates/workspace/src/dock.rs | 11 +++------- crates/workspace/src/workspace.rs | 35 +++++++++++-------------------- 2 files changed, 15 insertions(+), 31 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 6f7f6b1c12..0948140e8e 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -81,10 +81,6 @@ impl From<&dyn PanelHandle> for AnyViewHandle { } } -pub enum Event { - Close, -} - pub struct Dock { position: DockPosition, panels: Vec, @@ -182,7 +178,6 @@ impl Dock { } pub fn toggle_open(&mut self, cx: &mut ViewContext) { - if self.is_open {} self.is_open = !self.is_open; cx.notify(); } @@ -200,7 +195,7 @@ impl Dock { this.activate_item(ix, cx); } } else if view.read(cx).should_close_on_event(event, cx) { - cx.emit(Event::Close); + this.set_open(false, cx); } }), ]; @@ -226,7 +221,7 @@ impl Dock { { if panel_ix == self.active_item_ix { self.active_item_ix = 0; - cx.emit(Event::Close); + self.set_open(false, cx); } self.panels.remove(panel_ix); cx.notify(); @@ -261,7 +256,7 @@ impl Dock { } impl Entity for Dock { - type Event = Event; + type Event = (); } impl View for Dock { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index d147557103..497b28cff1 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -612,9 +612,18 @@ impl Workspace { .spawn(DB.set_window_bounds(workspace_id, bounds, display)) .detach_and_log_err(cx); }), - Self::register_dock(&left_dock, cx), - Self::register_dock(&bottom_dock, cx), - Self::register_dock(&right_dock, cx), + cx.observe(&left_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), + cx.observe(&bottom_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), + cx.observe(&right_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), ]; let mut this = Workspace { @@ -1359,26 +1368,6 @@ impl Workspace { } } - fn register_dock(dock: &ViewHandle, cx: &mut ViewContext) -> Subscription { - cx.subscribe(dock, Self::handle_dock_event) - } - - fn handle_dock_event( - &mut self, - dock: ViewHandle, - event: &dock::Event, - cx: &mut ViewContext, - ) { - match event { - dock::Event::Close => { - dock.update(cx, |dock, cx| dock.set_open(false, cx)); - self.serialize_workspace(cx); - cx.focus_self(); - cx.notify(); - } - } - } - pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { let dock = match dock_side { DockPosition::Left => &mut self.left_dock, From 0ccb4a50e6b952ccf2b25e506521fc6c028889f2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 16:52:10 +0200 Subject: [PATCH 27/61] Create more specific dock position settings associated with each panel --- crates/project_panel/src/project_panel.rs | 17 ++++++++++---- crates/settings/src/settings.rs | 27 ++++++++++++++-------- crates/terminal_view/src/terminal_panel.rs | 18 +++++++++++---- crates/workspace/src/dock.rs | 20 ---------------- 4 files changed, 44 insertions(+), 38 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 4c75035c83..14779f9b6f 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1347,12 +1347,15 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { let settings = cx.global::(); - settings + let dock = settings .project_panel_overrides .dock .or(settings.project_panel_defaults.dock) - .unwrap() - .into() + .unwrap(); + match dock { + settings::ProjectPanelDockPosition::Left => DockPosition::Left, + settings::ProjectPanelDockPosition::Right => DockPosition::Right, + } } fn position_is_valid(&self, position: DockPosition) -> bool { @@ -1361,7 +1364,13 @@ impl workspace::dock::Panel for ProjectPanel { fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { SettingsFile::update(cx, move |settings| { - settings.project_panel.dock = Some(position.into()) + let dock = match position { + DockPosition::Left | DockPosition::Bottom => { + settings::ProjectPanelDockPosition::Left + } + DockPosition::Right => settings::ProjectPanelDockPosition::Right, + }; + settings.project_panel.dock = Some(dock); }) } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 3bd3786071..67acd12939 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -131,14 +131,6 @@ impl TelemetrySettings { } } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum DockPosition { - Left, - Right, - Bottom, -} - #[derive(Clone, Debug, Default)] pub struct CopilotSettings { pub disabled_globs: Vec, @@ -168,7 +160,14 @@ pub struct GitGutterConfig {} #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct ProjectPanelSettings { - pub dock: Option, + pub dock: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum ProjectPanelDockPosition { + Left, + Right, } #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] @@ -265,7 +264,15 @@ pub struct TerminalSettings { pub alternate_scroll: Option, pub option_as_meta: Option, pub copy_on_select: Option, - pub dock: Option, + pub dock: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum TerminalDockPosition { + Left, + Bottom, + Right, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 4e90a8fec4..5d4099c446 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -4,7 +4,7 @@ use gpui::{ WeakViewHandle, }; use project::Project; -use settings::{settings_file::SettingsFile, Settings, WorkingDirectory}; +use settings::{settings_file::SettingsFile, Settings, TerminalDockPosition, WorkingDirectory}; use util::ResultExt; use workspace::{ dock::{DockPosition, Panel}, @@ -151,12 +151,17 @@ impl View for TerminalPanel { impl Panel for TerminalPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { let settings = cx.global::(); - settings + let dock = settings .terminal_overrides .dock .or(settings.terminal_defaults.dock) .unwrap() - .into() + .into(); + match dock { + settings::TerminalDockPosition::Left => DockPosition::Left, + settings::TerminalDockPosition::Bottom => DockPosition::Bottom, + settings::TerminalDockPosition::Right => DockPosition::Right, + } } fn position_is_valid(&self, _: DockPosition) -> bool { @@ -165,7 +170,12 @@ impl Panel for TerminalPanel { fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { SettingsFile::update(cx, move |settings| { - settings.terminal.dock = Some(position.into()); + let dock = match position { + DockPosition::Left => TerminalDockPosition::Left, + DockPosition::Bottom => TerminalDockPosition::Bottom, + DockPosition::Right => TerminalDockPosition::Right, + }; + settings.terminal.dock = Some(dock); }); } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 0948140e8e..7413209874 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -95,26 +95,6 @@ pub enum DockPosition { Right, } -impl From for DockPosition { - fn from(value: settings::DockPosition) -> Self { - match value { - settings::DockPosition::Left => Self::Left, - settings::DockPosition::Bottom => Self::Bottom, - settings::DockPosition::Right => Self::Right, - } - } -} - -impl From for settings::DockPosition { - fn from(value: DockPosition) -> settings::DockPosition { - match value { - DockPosition::Left => settings::DockPosition::Left, - DockPosition::Bottom => settings::DockPosition::Bottom, - DockPosition::Right => settings::DockPosition::Right, - } - } -} - impl DockPosition { fn to_label(&self) -> &'static str { match self { From cc21421ea86914e036ffef810dfa62bfc566c940 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 10 May 2023 09:54:20 -0600 Subject: [PATCH 28/61] Rename item to panel in various locations --- crates/workspace/src/dock.rs | 66 +++++++++++++++---------------- crates/workspace/src/workspace.rs | 20 +++++----- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 7413209874..82d8d40425 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -83,9 +83,9 @@ impl From<&dyn PanelHandle> for AnyViewHandle { pub struct Dock { position: DockPosition, - panels: Vec, + panel_entries: Vec, is_open: bool, - active_item_ix: usize, + active_panel_index: usize, } #[derive(Clone, Copy, Debug, Deserialize, PartialEq)] @@ -127,7 +127,7 @@ pub struct PanelButtons { #[derive(Clone, Debug, Deserialize, PartialEq)] pub struct TogglePanel { pub dock_position: DockPosition, - pub item_index: usize, + pub panel_index: usize, } impl_actions!(workspace, [TogglePanel]); @@ -136,8 +136,8 @@ impl Dock { pub fn new(position: DockPosition) -> Self { Self { position, - panels: Default::default(), - active_item_ix: 0, + panel_entries: Default::default(), + active_panel_index: 0, is_open: false, } } @@ -146,8 +146,8 @@ impl Dock { self.is_open } - pub fn active_item_ix(&self) -> usize { - self.active_item_ix + pub fn active_panel_index(&self) -> usize { + self.active_panel_index } pub fn set_open(&mut self, open: bool, cx: &mut ViewContext) { @@ -168,11 +168,11 @@ impl Dock { cx.subscribe(&panel, |this, view, event, cx| { if view.read(cx).should_activate_on_event(event, cx) { if let Some(ix) = this - .panels + .panel_entries .iter() - .position(|item| item.panel.id() == view.id()) + .position(|entry| entry.panel.id() == view.id()) { - this.activate_item(ix, cx); + this.activate_panel(ix, cx); } } else if view.read(cx).should_close_on_event(event, cx) { this.set_open(false, cx); @@ -181,7 +181,7 @@ impl Dock { ]; let dock_view_id = cx.view_id(); - self.panels.push(PanelEntry { + self.panel_entries.push(PanelEntry { panel: Rc::new(panel), context_menu: cx.add_view(|cx| { let mut menu = ContextMenu::new(dock_view_id, cx); @@ -195,40 +195,40 @@ impl Dock { pub fn remove_panel(&mut self, panel: &ViewHandle, cx: &mut ViewContext) { if let Some(panel_ix) = self - .panels + .panel_entries .iter() - .position(|item| item.panel.id() == panel.id()) + .position(|entry| entry.panel.id() == panel.id()) { - if panel_ix == self.active_item_ix { - self.active_item_ix = 0; + if panel_ix == self.active_panel_index { + self.active_panel_index = 0; self.set_open(false, cx); } - self.panels.remove(panel_ix); + self.panel_entries.remove(panel_ix); cx.notify(); } } pub fn panels_len(&self) -> usize { - self.panels.len() + self.panel_entries.len() } - pub fn activate_item(&mut self, item_ix: usize, cx: &mut ViewContext) { - self.active_item_ix = item_ix; + pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { + self.active_panel_index = panel_ix; cx.notify(); } - pub fn toggle_item(&mut self, item_ix: usize, cx: &mut ViewContext) { - if self.active_item_ix == item_ix { + pub fn toggle_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { + if self.active_panel_index == panel_ix { self.is_open = false; } else { - self.active_item_ix = item_ix; + self.active_panel_index = panel_ix; } cx.notify(); } - pub fn active_item(&self) -> Option<&Rc> { + pub fn active_panel(&self) -> Option<&Rc> { if self.is_open { - self.panels.get(self.active_item_ix).map(|item| &item.panel) + self.panel_entries.get(self.active_panel_index).map(|entry| &entry.panel) } else { None } @@ -245,10 +245,10 @@ impl View for Dock { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - if let Some(active_item) = self.active_item() { + if let Some(active_panel) = self.active_panel() { enum ResizeHandleTag {} let style = &cx.global::().theme.workspace.dock; - ChildView::new(active_item.as_any(), cx) + ChildView::new(active_panel.as_any(), cx) .contained() .with_style(style.container) .with_resize_handle::( @@ -289,9 +289,9 @@ impl View for PanelButtons { let theme = &cx.global::().theme; let tooltip_style = theme.tooltip.clone(); let theme = &theme.workspace.status_bar.panel_buttons; - let item_style = theme.button.clone(); + let button_style = theme.button.clone(); let dock = self.dock.read(cx); - let active_ix = dock.active_item_ix; + let active_ix = dock.active_panel_index; let is_open = dock.is_open; let dock_position = dock.position; let group_style = match dock_position { @@ -304,27 +304,27 @@ impl View for PanelButtons { DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight, }; - let items = dock - .panels + let panels = dock + .panel_entries .iter() .map(|item| (item.panel.clone(), item.context_menu.clone())) .collect::>(); Flex::row() .with_children( - items + panels .into_iter() .enumerate() .map(|(ix, (view, context_menu))| { let action = TogglePanel { dock_position, - item_index: ix, + panel_index: ix, }; Stack::new() .with_child( MouseEventHandler::::new(ix, cx, |state, cx| { let is_active = is_open && ix == active_ix; - let style = item_style.style_for(state, is_active); + let style = button_style.style_for(state, is_active); Flex::row() .with_child( Svg::new(view.icon_path(cx)) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 497b28cff1..cf89ed43c7 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -858,7 +858,7 @@ impl Workspace { dock.update(cx, |dock, cx| { was_visible = dock.is_open() && dock - .active_item() + .active_panel() .map_or(false, |item| item.as_any().is::()); dock.remove_panel(&panel, cx); }); @@ -872,7 +872,7 @@ impl Workspace { dock.add_panel(panel, cx); if was_visible { dock.set_open(true, cx); - dock.activate_item(dock.panels_len() - 1, cx); + dock.activate_panel(dock.panels_len() - 1, cx); } }); } @@ -1392,13 +1392,13 @@ impl Workspace { DockPosition::Right => &mut self.right_dock, }; let active_item = dock.update(cx, move |dock, cx| { - if dock.is_open() && dock.active_item_ix() == action.item_index { + if dock.is_open() && dock.active_panel_index() == action.panel_index { dock.set_open(false, cx); None } else { dock.set_open(true, cx); - dock.activate_item(action.item_index, cx); - dock.active_item().cloned() + dock.activate_panel(action.panel_index, cx); + dock.active_panel().cloned() } }); @@ -1430,8 +1430,8 @@ impl Workspace { }; let active_item = dock.update(cx, |dock, cx| { dock.set_open(true, cx); - dock.activate_item(panel_index, cx); - dock.active_item().cloned() + dock.activate_panel(panel_index, cx); + dock.active_panel().cloned() }); if let Some(active_item) = active_item { if active_item.is_focused(cx) { @@ -2655,7 +2655,7 @@ impl View for Workspace { let project = self.project.clone(); Flex::row() .with_children( - if self.left_dock.read(cx).active_item().is_some() { + if self.left_dock.read(cx).active_panel().is_some() { Some( ChildView::new(&self.left_dock, cx) .constrained() @@ -2688,7 +2688,7 @@ impl View for Workspace { .flex(1., true), ) .with_children( - if self.bottom_dock.read(cx).active_item().is_some() + if self.bottom_dock.read(cx).active_panel().is_some() { Some(ChildView::new(&self.bottom_dock, cx)) } else { @@ -2698,7 +2698,7 @@ impl View for Workspace { .flex(1., true), ) .with_children( - if self.right_dock.read(cx).active_item().is_some() { + if self.right_dock.read(cx).active_panel().is_some() { Some( ChildView::new(&self.right_dock, cx) .constrained() From 55496693166f9b17a4371a1497bcfeb01a91dbfd Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 10 May 2023 10:41:03 -0600 Subject: [PATCH 29/61] Test moving panels Co-Authored-By: Julia Risley --- crates/welcome/src/welcome.rs | 2 +- crates/workspace/src/dock.rs | 71 +++++++++++++++- crates/workspace/src/persistence.rs | 6 +- crates/workspace/src/persistence/model.rs | 4 +- crates/workspace/src/workspace.rs | 98 ++++++++++++++++++++++- crates/zed/src/main.rs | 3 +- 6 files changed, 169 insertions(+), 15 deletions(-) diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 6adcf27bd1..d78b230dd9 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -10,7 +10,7 @@ use gpui::{ use settings::{settings_file::SettingsFile, Settings}; use workspace::{ - item::Item, open_new, dock::DockPosition, AppState, PaneBackdrop, Welcome, Workspace, + dock::DockPosition, item::Item, open_new, AppState, PaneBackdrop, Welcome, Workspace, WorkspaceId, }; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 82d8d40425..5320a4c652 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -202,6 +202,8 @@ impl Dock { if panel_ix == self.active_panel_index { self.active_panel_index = 0; self.set_open(false, cx); + } else if panel_ix < self.active_panel_index { + self.active_panel_index -= 1; } self.panel_entries.remove(panel_ix); cx.notify(); @@ -228,7 +230,9 @@ impl Dock { pub fn active_panel(&self) -> Option<&Rc> { if self.is_open { - self.panel_entries.get(self.active_panel_index).map(|entry| &entry.panel) + self.panel_entries + .get(self.active_panel_index) + .map(|entry| &entry.panel) } else { None } @@ -416,3 +420,68 @@ impl StatusItemView for PanelButtons { ) { } } + +#[cfg(test)] +pub(crate) mod test { + use super::*; + use gpui::Entity; + + pub enum TestPanelEvent { + PositionChanged, + Activated, + Closed, + } + + pub struct TestPanel { + pub position: DockPosition, + } + + impl Entity for TestPanel { + type Event = TestPanelEvent; + } + + impl View for TestPanel { + fn ui_name() -> &'static str { + "TestPanel" + } + + fn render(&mut self, _: &mut ViewContext<'_, '_, Self>) -> AnyElement { + Empty::new().into_any() + } + } + + impl Panel for TestPanel { + fn position(&self, _: &gpui::WindowContext) -> super::DockPosition { + self.position + } + + fn position_is_valid(&self, _: super::DockPosition) -> bool { + true + } + + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { + self.position = position; + cx.emit(TestPanelEvent::PositionChanged); + } + + fn icon_path(&self) -> &'static str { + "icons/test_panel.svg" + } + + fn icon_tooltip(&self) -> String { + "Test Panel".into() + } + + fn should_change_position_on_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::PositionChanged) + } + + fn should_activate_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool { + matches!(event, TestPanelEvent::Activated) + } + + fn should_close_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool { + matches!(event, TestPanelEvent::Closed) + } + } +} diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 3b7d0ecb87..2ea99581d2 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -214,10 +214,7 @@ impl WorkspaceDb { workspace_location = ?2, left_sidebar_open = ?3, timestamp = CURRENT_TIMESTAMP - ))?(( - workspace.id, - &workspace.location, - )) + ))?((workspace.id, &workspace.location)) .context("Updating workspace")?; // Save center pane group @@ -454,7 +451,6 @@ impl WorkspaceDb { #[cfg(test)] mod tests { - use db::open_test_db; use super::*; diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index f946ebf9de..19da782fb6 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -1,6 +1,4 @@ -use crate::{ - ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, -}; +use crate::{ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId}; use anyhow::{anyhow, Context, Result}; use async_recursion::async_recursion; use db::sqlez::{ diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index cf89ed43c7..791d0d0a44 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -859,7 +859,7 @@ impl Workspace { was_visible = dock.is_open() && dock .active_panel() - .map_or(false, |item| item.as_any().is::()); + .map_or(false, |active_panel| active_panel.id() == panel.id()); dock.remove_panel(&panel, cx); }); dock = match panel.read(cx).position(cx) { @@ -2688,7 +2688,11 @@ impl View for Workspace { .flex(1., true), ) .with_children( - if self.bottom_dock.read(cx).active_panel().is_some() + if self + .bottom_dock + .read(cx) + .active_panel() + .is_some() { Some(ChildView::new(&self.bottom_dock, cx)) } else { @@ -3074,7 +3078,10 @@ fn parse_pixel_position_env_var(value: &str) -> Option { mod tests { use std::{cell::RefCell, rc::Rc}; - use crate::item::test::{TestItem, TestItemEvent, TestProjectItem}; + use crate::{ + dock::test::TestPanel, + item::test::{TestItem, TestItemEvent, TestProjectItem}, + }; use super::*; use fs::FakeFs; @@ -3644,4 +3651,89 @@ mod tests { assert!(pane.can_navigate_forward()); }); } + + #[gpui::test] + async fn test_panels(deterministic: Arc, cx: &mut gpui::TestAppContext) { + deterministic.forbid_parking(); + Settings::test_async(cx); + let fs = FakeFs::new(cx.background()); + + let project = Project::test(fs, [], cx).await; + let (_window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + + let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { + // Add panel_1 on the left, panel_2 on the right. + let panel_1 = cx.add_view(|_| TestPanel { + position: DockPosition::Left, + }); + workspace.add_panel(panel_1.clone(), cx); + workspace + .left_dock() + .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); + let panel_2 = cx.add_view(|_| TestPanel { + position: DockPosition::Right, + }); + workspace.add_panel(panel_2.clone(), cx); + workspace + .right_dock() + .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + + assert_eq!( + workspace.left_dock().read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + assert_eq!( + workspace.right_dock().read(cx).active_panel().unwrap().id(), + panel_2.id() + ); + + (panel_1, panel_2) + }); + + // Move panel_1 to the right + panel_1.update(cx, |panel_1, cx| { + panel_1.set_position(DockPosition::Right, cx) + }); + + workspace.update(cx, |workspace, cx| { + // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right. + // Since it was the only panel on the left, the left dock should now be closed. + assert!(!workspace.left_dock().read(cx).is_open()); + assert!(workspace.left_dock().read(cx).active_panel().is_none()); + assert_eq!( + workspace.right_dock().read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + + // Now we move panel_2 to the left + panel_2.set_position(DockPosition::Left, cx); + }); + + workspace.update(cx, |workspace, cx| { + // Since panel_2 was not visible on the right, we don't open the left dock. + assert!(!workspace.left_dock().read(cx).is_open()); + // And the right dock is unaffected in it's displaying of panel_1 + assert!(workspace.right_dock().read(cx).is_open()); + assert_eq!( + workspace.right_dock().read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + }); + + // Move panel_1 back to the left + panel_1.update(cx, |panel_1, cx| { + panel_1.set_position(DockPosition::Left, cx) + }); + + workspace.update(cx, |workspace, cx| { + // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. + assert!(workspace.left_dock().read(cx).is_open()); + assert_eq!( + workspace.left_dock().read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + // And right the dock should be closed as it no longer has any panels. + assert!(!workspace.right_dock().read(cx).is_open()); + }); + } } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index ff969a8c75..362b714a92 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -52,8 +52,7 @@ use staff_mode::StaffMode; use theme::ThemeRegistry; use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use workspace::{ - item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, - Workspace, + item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, Workspace, }; use zed::{self, build_window_options, initialize_workspace, languages, menus}; From 214354b4da5aa7ac3595dde4cd9d374b66c38f34 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 11 May 2023 23:25:05 -0600 Subject: [PATCH 30/61] Make panels independently resizable --- assets/settings/default.json | 3 +- crates/gpui/src/elements.rs | 20 ++-- crates/gpui/src/elements/resizable.rs | 124 +++++++++------------ crates/project_panel/src/project_panel.rs | 13 ++- crates/settings/src/settings.rs | 31 ++++-- crates/terminal_view/src/terminal_panel.rs | 8 ++ crates/workspace/src/dock.rs | 50 +++++++-- crates/workspace/src/item.rs | 40 +------ 8 files changed, 135 insertions(+), 154 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 5d85e751d4..d1a6499655 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -108,7 +108,8 @@ "auto_update": true, // Git gutter behavior configuration. "project_panel": { - "dock": "left" + "dock": "left", + "default_width": 240 }, "git": { // Control whether the git gutter is shown. May take 2 values: diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index e2c4af143c..3caa91a3b8 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -187,25 +187,21 @@ pub trait Element: 'static { Tooltip::new::(id, text, action, style, self.into_any(), cx) } - fn with_resize_handle( + fn resizable( self, - element_id: usize, - side: Side, - handle_size: f32, - initial_size: f32, - cx: &mut ViewContext, + side: HandleSide, + size: f32, + on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext), ) -> Resizable where Self: 'static + Sized, { - Resizable::new::( + Resizable::new( self.into_any(), - element_id, side, - handle_size, - initial_size, - cx, - ) + size, + on_resize + ) } } diff --git a/crates/gpui/src/elements/resizable.rs b/crates/gpui/src/elements/resizable.rs index 0e78cc07fb..a3d95893f4 100644 --- a/crates/gpui/src/elements/resizable.rs +++ b/crates/gpui/src/elements/resizable.rs @@ -1,4 +1,4 @@ -use std::{cell::Cell, rc::Rc}; +use std::{cell::RefCell, rc::Rc}; use pathfinder_geometry::vector::{vec2f, Vector2F}; use serde_json::json; @@ -7,25 +7,23 @@ use crate::{ geometry::rect::RectF, platform::{CursorStyle, MouseButton}, scene::MouseDrag, - AnyElement, Axis, Element, ElementStateHandle, LayoutContext, MouseRegion, SceneBuilder, View, - ViewContext, + AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, View, + ViewContext, SizeConstraint, }; -use super::{ConstrainedBox, Hook}; - #[derive(Copy, Clone, Debug)] -pub enum Side { +pub enum HandleSide { Top, Bottom, Left, Right, } -impl Side { +impl HandleSide { fn axis(&self) -> Axis { match self { - Side::Left | Side::Right => Axis::Horizontal, - Side::Top | Side::Bottom => Axis::Vertical, + HandleSide::Left | HandleSide::Right => Axis::Horizontal, + HandleSide::Top | HandleSide::Bottom => Axis::Vertical, } } @@ -33,8 +31,8 @@ impl Side { /// then top-to-bottom fn before_content(self) -> bool { match self { - Side::Left | Side::Top => true, - Side::Right | Side::Bottom => false, + HandleSide::Left | HandleSide::Top => true, + HandleSide::Right | HandleSide::Bottom => false, } } @@ -55,14 +53,14 @@ impl Side { fn of_rect(&self, bounds: RectF, handle_size: f32) -> RectF { match self { - Side::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)), - Side::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())), - Side::Bottom => { + HandleSide::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)), + HandleSide::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())), + HandleSide::Bottom => { let mut origin = bounds.lower_left(); origin.set_y(origin.y() - handle_size); RectF::new(origin, vec2f(bounds.width(), handle_size)) } - Side::Right => { + HandleSide::Right => { let mut origin = bounds.upper_right(); origin.set_x(origin.x() - handle_size); RectF::new(origin, vec2f(handle_size, bounds.height())) @@ -71,69 +69,44 @@ impl Side { } } -struct ResizeHandleState { - actual_dimension: Cell, - custom_dimension: Cell, +pub struct Resizable { + child: AnyElement, + handle_side: HandleSide, + handle_size: f32, + on_resize: Rc)>> } -pub struct Resizable { - side: Side, - handle_size: f32, - child: AnyElement, - state: Rc, - _state_handle: ElementStateHandle>, -} +const DEFAULT_HANDLE_SIZE: f32 = 4.0; impl Resizable { - pub fn new( + pub fn new( child: AnyElement, - element_id: usize, - side: Side, - handle_size: f32, - initial_size: f32, - cx: &mut ViewContext, + handle_side: HandleSide, + size: f32, + on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext) ) -> Self { - let state_handle = cx.element_state::>( - element_id, - Rc::new(ResizeHandleState { - actual_dimension: Cell::new(initial_size), - custom_dimension: Cell::new(initial_size), - }), - ); - - let state = state_handle.read(cx).clone(); - - let child = Hook::new({ - let constrained = ConstrainedBox::new(child); - match side.axis() { - Axis::Horizontal => constrained.with_max_width(state.custom_dimension.get()), - Axis::Vertical => constrained.with_max_height(state.custom_dimension.get()), - } - }) - .on_after_layout({ - let state = state.clone(); - move |size, _| { - state.actual_dimension.set(side.relevant_component(size)); - } - }) + let child = match handle_side.axis() { + Axis::Horizontal => child.constrained().with_max_width(size), + Axis::Vertical => child.constrained().with_max_height(size), + } .into_any(); Self { - side, child, - handle_size, - state, - _state_handle: state_handle, + handle_side, + handle_size: DEFAULT_HANDLE_SIZE, + on_resize: Rc::new(RefCell::new(on_resize)), } } - pub fn current_size(&self) -> f32 { - self.state.actual_dimension.get() + pub fn with_handle_size(mut self, handle_size: f32) -> Self { + self.handle_size = handle_size; + self } } impl Element for Resizable { - type LayoutState = (); + type LayoutState = SizeConstraint; type PaintState = (); fn layout( @@ -142,7 +115,7 @@ impl Element for Resizable { view: &mut V, cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { - (self.child.layout(constraint, view, cx), ()) + (self.child.layout(constraint, view, cx), constraint) } fn paint( @@ -150,34 +123,37 @@ impl Element for Resizable { scene: &mut SceneBuilder, bounds: pathfinder_geometry::rect::RectF, visible_bounds: pathfinder_geometry::rect::RectF, - _child_size: &mut Self::LayoutState, + constraint: &mut SizeConstraint, view: &mut V, cx: &mut ViewContext, ) -> Self::PaintState { scene.push_stacking_context(None, None); - let handle_region = self.side.of_rect(bounds, self.handle_size); + let handle_region = self.handle_side.of_rect(bounds, self.handle_size); enum ResizeHandle {} scene.push_mouse_region( - MouseRegion::new::(cx.view_id(), self.side as usize, handle_region) + MouseRegion::new::(cx.view_id(), self.handle_side as usize, handle_region) .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere .on_drag(MouseButton::Left, { - let state = self.state.clone(); - let side = self.side; - move |e, _: &mut V, cx| { - let prev_width = state.actual_dimension.get(); - state - .custom_dimension - .set(0f32.max(prev_width + side.compute_delta(e)).round()); - cx.notify(); + let bounds = bounds.clone(); + let side = self.handle_side; + let prev_size = side.relevant_component(bounds.size()); + let min_size = side.relevant_component(constraint.min); + let max_size = side.relevant_component(constraint.max); + let on_resize = self.on_resize.clone(); + move |event, view: &mut V, cx| { + let new_size = min_size.max(prev_size + side.compute_delta(event)).min(max_size).round(); + if new_size != prev_size { + on_resize.borrow_mut()(view, new_size, cx); + } } }), ); scene.push_cursor_region(crate::CursorRegion { bounds: handle_region, - style: match self.side.axis() { + style: match self.handle_side.axis() { Axis::Horizontal => CursorStyle::ResizeLeftRight, Axis::Vertical => CursorStyle::ResizeUpDown, }, diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 14779f9b6f..ac0df343ae 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1347,12 +1347,9 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { let settings = cx.global::(); - let dock = settings - .project_panel_overrides - .dock - .or(settings.project_panel_defaults.dock) - .unwrap(); - match dock { + match settings + .project_panel + .dock { settings::ProjectPanelDockPosition::Left => DockPosition::Left, settings::ProjectPanelDockPosition::Right => DockPosition::Right, } @@ -1374,6 +1371,10 @@ impl workspace::dock::Panel for ProjectPanel { }) } + fn default_size(&self, cx: &gpui::WindowContext) -> f32 { + cx.global::().project_panel.default_width + } + fn icon_path(&self) -> &'static str { "icons/folder_tree_16.svg" } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 67acd12939..df03d4c1f6 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -44,8 +44,7 @@ pub struct Settings { pub show_call_status_icon: bool, pub vim_mode: bool, pub autosave: Autosave, - pub project_panel_defaults: ProjectPanelSettings, - pub project_panel_overrides: ProjectPanelSettings, + pub project_panel: ProjectPanelSettings, pub editor_defaults: EditorSettings, pub editor_overrides: EditorSettings, pub git: GitSettings, @@ -158,9 +157,15 @@ pub enum GitGutter { pub struct GitGutterConfig {} -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct ProjectPanelSettings { + pub dock: ProjectPanelDockPosition, + pub default_width: f32, +} +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct ProjectPanelSettingsContent { pub dock: Option, + pub default_width: Option, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)] @@ -253,6 +258,8 @@ impl Default for HourFormat { #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct TerminalSettings { + pub default_width: Option, + pub default_height: Option, pub shell: Option, pub working_directory: Option, pub font_size: Option, @@ -387,7 +394,7 @@ pub struct SettingsFileContent { #[serde(flatten)] pub editor: EditorSettings, #[serde(default)] - pub project_panel: ProjectPanelSettings, + pub project_panel: ProjectPanelSettingsContent, #[serde(default)] pub journal: JournalSettings, #[serde(default)] @@ -423,7 +430,6 @@ pub struct Features { } #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] pub struct FeaturesContent { pub copilot: Option, } @@ -482,8 +488,10 @@ impl Settings { show_call_status_icon: defaults.show_call_status_icon.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), - project_panel_defaults: defaults.project_panel, - project_panel_overrides: Default::default(), + project_panel: ProjectPanelSettings { + dock: defaults.project_panel.dock.unwrap(), + default_width: defaults.project_panel.default_width.unwrap(), + }, editor_defaults: EditorSettings { tab_size: required(defaults.editor.tab_size), hard_tabs: required(defaults.editor.hard_tabs), @@ -590,7 +598,8 @@ impl Settings { } } self.editor_overrides = data.editor; - self.project_panel_overrides = data.project_panel; + merge(&mut self.project_panel.dock, data.project_panel.dock); + merge(&mut self.project_panel.default_width, data.project_panel.default_width); self.git_overrides = data.git.unwrap_or_default(); self.journal_overrides = data.journal; self.terminal_defaults.font_size = data.terminal.font_size; @@ -778,8 +787,10 @@ impl Settings { show_call_status_icon: true, vim_mode: false, autosave: Autosave::Off, - project_panel_defaults: Default::default(), - project_panel_overrides: Default::default(), + project_panel: ProjectPanelSettings { + dock: ProjectPanelDockPosition::Left, + default_width: 240., + }, editor_defaults: EditorSettings { tab_size: Some(4.try_into().unwrap()), hard_tabs: Some(false), diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 5d4099c446..995c8be50b 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -179,6 +179,14 @@ impl Panel for TerminalPanel { }); } + fn default_size(&self, cx: &gpui::WindowContext) -> f32 { + let settings = &cx.global::().terminal_overrides; + match self.position(cx) { + DockPosition::Left | DockPosition::Right => settings.default_width.unwrap_or(640.), + DockPosition::Bottom => settings.default_height.unwrap_or(320.), + } + } + fn icon_path(&self) -> &'static str { "icons/terminal_12.svg" } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 5320a4c652..6ce1e4f691 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -12,6 +12,7 @@ pub trait Panel: View { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); + fn default_size(&self, cx: &WindowContext) -> f32; fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; fn icon_label(&self, _: &AppContext) -> Option { @@ -27,6 +28,7 @@ pub trait PanelHandle { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; fn set_position(&self, position: DockPosition, cx: &mut WindowContext); + fn default_size(&self, cx: &WindowContext) -> f32; fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -54,6 +56,10 @@ where self.update(cx, |this, cx| this.set_position(position, cx)) } + fn default_size(&self, cx: &WindowContext) -> f32 { + self.read(cx).default_size(cx) + } + fn icon_path(&self, cx: &WindowContext) -> &'static str { self.read(cx).icon_path() } @@ -104,17 +110,18 @@ impl DockPosition { } } - fn to_resizable_side(self) -> Side { + fn to_resize_handle_side(self) -> HandleSide { match self { - Self::Left => Side::Right, - Self::Bottom => Side::Top, - Self::Right => Side::Left, + Self::Left => HandleSide::Right, + Self::Bottom => HandleSide::Top, + Self::Right => HandleSide::Left, } } } struct PanelEntry { panel: Rc, + size: f32, context_menu: ViewHandle, _subscriptions: [Subscription; 2], } @@ -181,8 +188,10 @@ impl Dock { ]; let dock_view_id = cx.view_id(); + let size = panel.default_size(cx); self.panel_entries.push(PanelEntry { panel: Rc::new(panel), + size, context_menu: cx.add_view(|cx| { let mut menu = ContextMenu::new(dock_view_id, cx); menu.set_position_mode(OverlayPositionMode::Local); @@ -237,6 +246,23 @@ impl Dock { None } } + + pub fn active_panel_size(&self) -> Option { + if self.is_open { + self.panel_entries + .get(self.active_panel_index) + .map(|entry| entry.size) + } else { + None + } + } + + pub fn resize_active_panel(&mut self, size: f32, cx: &mut ViewContext) { + if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) { + entry.size = size; + cx.notify(); + } + } } impl Entity for Dock { @@ -250,18 +276,14 @@ impl View for Dock { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(active_panel) = self.active_panel() { - enum ResizeHandleTag {} + let size = self.active_panel_size().unwrap(); let style = &cx.global::().theme.workspace.dock; ChildView::new(active_panel.as_any(), cx) .contained() .with_style(style.container) - .with_resize_handle::( - self.position as usize, - self.position.to_resizable_side(), - 4., - style.initial_size, - cx, - ) + .resizable(self.position.to_resize_handle_side(), size, |dock: &mut Self, size, cx| { + dock.resize_active_panel(size, cx); + }) .into_any() } else { Empty::new().into_any() @@ -464,6 +486,10 @@ pub(crate) mod test { cx.emit(TestPanelEvent::PositionChanged); } + fn default_size(&self, _: &WindowContext) -> f32 { + 300. + } + fn icon_path(&self) -> &'static str { "icons/test_panel.svg" } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index dfad53c3a3..2adbff51fe 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -766,7 +766,7 @@ impl FollowableItemHandle for ViewHandle { #[cfg(test)] pub(crate) mod test { use super::{Item, ItemEvent}; - use crate::{dock::Panel, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; + use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; use gpui::{ elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -1059,42 +1059,4 @@ pub(crate) mod test { Task::Ready(Some(anyhow::Ok(view))) } } - - impl Panel for TestItem { - fn position(&self, _cx: &gpui::WindowContext) -> crate::dock::DockPosition { - unimplemented!() - } - - fn position_is_valid(&self, _position: crate::dock::DockPosition) -> bool { - unimplemented!() - } - - fn set_position( - &mut self, - _position: crate::dock::DockPosition, - _cx: &mut ViewContext, - ) { - unimplemented!() - } - - fn icon_path(&self) -> &'static str { - unimplemented!() - } - - fn icon_tooltip(&self) -> String { - unimplemented!() - } - - fn should_change_position_on_event(_: &Self::Event) -> bool { - unimplemented!() - } - - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - unimplemented!() - } - - fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - unimplemented!() - } - } } From bd795d7607f7ee03e67babc70049d2f7cfd61319 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 12 May 2023 15:15:11 -0600 Subject: [PATCH 31/61] Preserve panel size when re-docking between left and right --- crates/gpui/src/elements.rs | 7 +--- crates/gpui/src/elements/resizable.rs | 45 +++++++++++--------- crates/project_panel/src/project_panel.rs | 8 ++-- crates/settings/src/settings.rs | 5 ++- crates/workspace/src/dock.rs | 42 ++++++++++++++++--- crates/workspace/src/workspace.rs | 51 ++++++++++++++++++++--- 6 files changed, 117 insertions(+), 41 deletions(-) diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index 3caa91a3b8..f593615ae7 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -196,12 +196,7 @@ pub trait Element: 'static { where Self: 'static + Sized, { - Resizable::new( - self.into_any(), - side, - size, - on_resize - ) + Resizable::new(self.into_any(), side, size, on_resize) } } diff --git a/crates/gpui/src/elements/resizable.rs b/crates/gpui/src/elements/resizable.rs index a3d95893f4..da4b3473b3 100644 --- a/crates/gpui/src/elements/resizable.rs +++ b/crates/gpui/src/elements/resizable.rs @@ -7,8 +7,8 @@ use crate::{ geometry::rect::RectF, platform::{CursorStyle, MouseButton}, scene::MouseDrag, - AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, View, - ViewContext, SizeConstraint, + AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View, + ViewContext, }; #[derive(Copy, Clone, Debug)] @@ -73,7 +73,7 @@ pub struct Resizable { child: AnyElement, handle_side: HandleSide, handle_size: f32, - on_resize: Rc)>> + on_resize: Rc)>>, } const DEFAULT_HANDLE_SIZE: f32 = 4.0; @@ -83,7 +83,7 @@ impl Resizable { child: AnyElement, handle_side: HandleSide, size: f32, - on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext) + on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext), ) -> Self { let child = match handle_side.axis() { Axis::Horizontal => child.constrained().with_max_width(size), @@ -133,22 +133,29 @@ impl Element for Resizable { enum ResizeHandle {} scene.push_mouse_region( - MouseRegion::new::(cx.view_id(), self.handle_side as usize, handle_region) - .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere - .on_drag(MouseButton::Left, { - let bounds = bounds.clone(); - let side = self.handle_side; - let prev_size = side.relevant_component(bounds.size()); - let min_size = side.relevant_component(constraint.min); - let max_size = side.relevant_component(constraint.max); - let on_resize = self.on_resize.clone(); - move |event, view: &mut V, cx| { - let new_size = min_size.max(prev_size + side.compute_delta(event)).min(max_size).round(); - if new_size != prev_size { - on_resize.borrow_mut()(view, new_size, cx); - } + MouseRegion::new::( + cx.view_id(), + self.handle_side as usize, + handle_region, + ) + .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere + .on_drag(MouseButton::Left, { + let bounds = bounds.clone(); + let side = self.handle_side; + let prev_size = side.relevant_component(bounds.size()); + let min_size = side.relevant_component(constraint.min); + let max_size = side.relevant_component(constraint.max); + let on_resize = self.on_resize.clone(); + move |event, view: &mut V, cx| { + let new_size = min_size + .max(prev_size + side.compute_delta(event)) + .min(max_size) + .round(); + if new_size != prev_size { + on_resize.borrow_mut()(view, new_size, cx); } - }), + } + }), ); scene.push_cursor_region(crate::CursorRegion { diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index ac0df343ae..c76d2481ef 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -12,8 +12,8 @@ use gpui::{ geometry::vector::Vector2F, keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, - AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext, - ViewHandle, WeakViewHandle, + AnyElement, AppContext, Axis, ClipboardItem, Element, Entity, ModelHandle, Task, View, + ViewContext, ViewHandle, WeakViewHandle, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; @@ -1347,9 +1347,7 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { let settings = cx.global::(); - match settings - .project_panel - .dock { + match settings.project_panel.dock { settings::ProjectPanelDockPosition::Left => DockPosition::Left, settings::ProjectPanelDockPosition::Right => DockPosition::Right, } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index df03d4c1f6..618ccb89ac 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -599,7 +599,10 @@ impl Settings { } self.editor_overrides = data.editor; merge(&mut self.project_panel.dock, data.project_panel.dock); - merge(&mut self.project_panel.default_width, data.project_panel.default_width); + merge( + &mut self.project_panel.default_width, + data.project_panel.default_width, + ); self.git_overrides = data.git.unwrap_or_default(); self.journal_overrides = data.journal; self.terminal_defaults.font_size = data.terminal.font_size; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 6ce1e4f691..cafe8bd5dc 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -2,7 +2,8 @@ use crate::{StatusItemView, Workspace}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, - AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, + AppContext, Axis, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, + WindowContext, }; use serde::Deserialize; use settings::Settings; @@ -117,6 +118,13 @@ impl DockPosition { Self::Right => HandleSide::Left, } } + + pub fn axis(&self) -> Axis { + match self { + Self::Left | Self::Right => Axis::Horizontal, + Self::Bottom => Axis::Vertical, + } + } } struct PanelEntry { @@ -247,6 +255,23 @@ impl Dock { } } + pub fn panel_size(&self, panel: &dyn PanelHandle) -> Option { + self.panel_entries + .iter() + .find(|entry| entry.panel.id() == panel.id()) + .map(|entry| entry.size) + } + + pub fn resize_panel(&mut self, panel: &dyn PanelHandle, size: f32) { + let entry = self + .panel_entries + .iter_mut() + .find(|entry| entry.panel.id() == panel.id()); + if let Some(entry) = entry { + entry.size = size; + } + } + pub fn active_panel_size(&self) -> Option { if self.is_open { self.panel_entries @@ -281,9 +306,13 @@ impl View for Dock { ChildView::new(active_panel.as_any(), cx) .contained() .with_style(style.container) - .resizable(self.position.to_resize_handle_side(), size, |dock: &mut Self, size, cx| { - dock.resize_active_panel(size, cx); - }) + .resizable( + self.position.to_resize_handle_side(), + size, + |dock: &mut Self, size, cx| { + dock.resize_active_panel(size, cx); + }, + ) .into_any() } else { Empty::new().into_any() @@ -487,7 +516,10 @@ pub(crate) mod test { } fn default_size(&self, _: &WindowContext) -> f32 { - 300. + match self.position.axis() { + Axis::Horizontal => 300., + Axis::Vertical => 200., + } } fn icon_path(&self) -> &'static str { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 791d0d0a44..7bfe180d50 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -852,10 +852,18 @@ impl Workspace { cx.subscribe(&panel, { let mut dock = dock.clone(); + let mut prev_position = panel.position(cx); move |this, panel, event, cx| { if T::should_change_position_on_event(event) { + let new_position = panel.read(cx).position(cx); let mut was_visible = false; + let mut size = None; dock.update(cx, |dock, cx| { + if new_position.axis() == prev_position.axis() { + size = dock.panel_size(&panel); + } + prev_position = new_position; + was_visible = dock.is_open() && dock .active_panel() @@ -869,7 +877,11 @@ impl Workspace { } .clone(); dock.update(cx, |dock, cx| { - dock.add_panel(panel, cx); + dock.add_panel(panel.clone(), cx); + if let Some(size) = size { + dock.resize_panel(&panel, size); + } + if was_visible { dock.set_open(true, cx); dock.activate_panel(dock.panels_len() - 1, cx); @@ -3678,10 +3690,17 @@ mod tests { .right_dock() .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + let left_dock = workspace.left_dock(); assert_eq!( - workspace.left_dock().read(cx).active_panel().unwrap().id(), + left_dock.read(cx).active_panel().unwrap().id(), panel_1.id() ); + assert_eq!( + left_dock.read(cx).active_panel_size().unwrap(), + panel_1.default_size(cx) + ); + + left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx)); assert_eq!( workspace.right_dock().read(cx).active_panel().unwrap().id(), panel_2.id() @@ -3700,10 +3719,12 @@ mod tests { // Since it was the only panel on the left, the left dock should now be closed. assert!(!workspace.left_dock().read(cx).is_open()); assert!(workspace.left_dock().read(cx).active_panel().is_none()); + let right_dock = workspace.right_dock(); assert_eq!( - workspace.right_dock().read(cx).active_panel().unwrap().id(), + right_dock.read(cx).active_panel().unwrap().id(), panel_1.id() ); + assert_eq!(right_dock.read(cx).active_panel_size().unwrap(), 1337.); // Now we move panel_2 to the left panel_2.set_position(DockPosition::Left, cx); @@ -3727,13 +3748,33 @@ mod tests { workspace.update(cx, |workspace, cx| { // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. - assert!(workspace.left_dock().read(cx).is_open()); + let left_dock = workspace.left_dock(); + assert!(left_dock.read(cx).is_open()); assert_eq!( - workspace.left_dock().read(cx).active_panel().unwrap().id(), + left_dock.read(cx).active_panel().unwrap().id(), panel_1.id() ); + assert_eq!(left_dock.read(cx).active_panel_size().unwrap(), 1337.); // And right the dock should be closed as it no longer has any panels. assert!(!workspace.right_dock().read(cx).is_open()); + + // Now we move panel_1 to the bottom + panel_1.set_position(DockPosition::Bottom, cx); + }); + + workspace.update(cx, |workspace, cx| { + // Since panel_1 was visible on the left, we close the left dock. + assert!(!workspace.left_dock().read(cx).is_open()); + // The bottom dock is sized based on the panel's default size, + // since the panel orientation changed from vertical to horizontal. + assert_eq!( + workspace + .bottom_dock() + .read(cx) + .active_panel_size() + .unwrap(), + panel_1.default_size(cx), + ); }); } } From 6c608538428820ddd3df182438bb03720498dcdd Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 12 May 2023 15:44:09 -0600 Subject: [PATCH 32/61] Don't close panel on event unless active; add tests --- crates/project_panel/src/project_panel.rs | 4 +- crates/workspace/src/dock.rs | 12 ++++-- crates/workspace/src/workspace.rs | 48 ++++++++++++++++++++--- 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index c76d2481ef..e271c9970b 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -12,8 +12,8 @@ use gpui::{ geometry::vector::Vector2F, keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, - AnyElement, AppContext, Axis, ClipboardItem, Element, Entity, ModelHandle, Task, View, - ViewContext, ViewHandle, WeakViewHandle, + AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext, + ViewHandle, WeakViewHandle, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index cafe8bd5dc..86c2cc3929 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -180,16 +180,20 @@ impl Dock { pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) { let subscriptions = [ cx.observe(&panel, |_, _, cx| cx.notify()), - cx.subscribe(&panel, |this, view, event, cx| { - if view.read(cx).should_activate_on_event(event, cx) { + cx.subscribe(&panel, |this, panel, event, cx| { + if panel.read(cx).should_activate_on_event(event, cx) { if let Some(ix) = this .panel_entries .iter() - .position(|entry| entry.panel.id() == view.id()) + .position(|entry| entry.panel.id() == panel.id()) { + this.set_open(true, cx); this.activate_panel(ix, cx); + cx.focus(&panel); } - } else if view.read(cx).should_close_on_event(event, cx) { + } else if panel.read(cx).should_close_on_event(event, cx) + && this.active_panel().map_or(false, |p| p.id() == panel.id()) + { this.set_open(false, cx); } }), diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 7bfe180d50..ad717c362b 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3091,7 +3091,7 @@ mod tests { use std::{cell::RefCell, rc::Rc}; use crate::{ - dock::test::TestPanel, + dock::test::{TestPanel, TestPanelEvent}, item::test::{TestItem, TestItemEvent, TestProjectItem}, }; @@ -3767,14 +3767,50 @@ mod tests { assert!(!workspace.left_dock().read(cx).is_open()); // The bottom dock is sized based on the panel's default size, // since the panel orientation changed from vertical to horizontal. + let bottom_dock = workspace.bottom_dock(); assert_eq!( - workspace - .bottom_dock() - .read(cx) - .active_panel_size() - .unwrap(), + bottom_dock.read(cx).active_panel_size().unwrap(), panel_1.default_size(cx), ); + // Close bottom dock and move panel_1 back to the left. + bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); + panel_1.set_position(DockPosition::Left, cx); + }); + + // Emit activated event on panel 1 + panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated)); + + // Now the left dock is open and panel_1 is active and focused. + workspace.read_with(cx, |workspace, cx| { + let left_dock = workspace.left_dock(); + assert!(left_dock.read(cx).is_open()); + assert_eq!( + left_dock.read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + assert!(panel_1.is_focused(cx)); + }); + + // Emit closed event on panel 2, which is not active + panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed)); + + // Wo don't close the left dock, because panel_2 wasn't the active panel + workspace.read_with(cx, |workspace, cx| { + let left_dock = workspace.left_dock(); + assert!(left_dock.read(cx).is_open()); + assert_eq!( + left_dock.read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + }); + + // Emit closed event on panel 1, which is active + panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed)); + + // Now the left dock is closed, because panel_1 was the active panel + workspace.read_with(cx, |workspace, cx| { + let left_dock = workspace.left_dock(); + assert!(!left_dock.read(cx).is_open()); }); } } From ba50b35de691f692c9624f11491d2f78d9a31ef0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 13 May 2023 14:34:09 -0600 Subject: [PATCH 33/61] wip --- assets/keymaps/default.json | 1037 +++++++++-------- crates/gpui/src/app.rs | 6 + crates/gpui/src/elements.rs | 14 +- .../gpui/src/elements/mouse_event_handler.rs | 21 +- crates/theme/src/theme.rs | 2 + crates/workspace/src/pane.rs | 23 + crates/workspace/src/pane_group.rs | 8 +- crates/workspace/src/workspace.rs | 39 +- styles/src/styleTree/workspace.ts | 9 + 9 files changed, 634 insertions(+), 525 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 2e4fa62e8e..768e0c6ed1 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -1,534 +1,535 @@ [ - // Standard macOS bindings - { - "bindings": { - "up": "menu::SelectPrev", - "pageup": "menu::SelectFirst", - "shift-pageup": "menu::SelectFirst", - "ctrl-p": "menu::SelectPrev", - "down": "menu::SelectNext", - "pagedown": "menu::SelectLast", - "shift-pagedown": "menu::SelectFirst", - "ctrl-n": "menu::SelectNext", - "cmd-up": "menu::SelectFirst", - "cmd-down": "menu::SelectLast", - "enter": "menu::Confirm", - "escape": "menu::Cancel", - "ctrl-c": "menu::Cancel", - "cmd-{": "pane::ActivatePrevItem", - "cmd-}": "pane::ActivateNextItem", - "alt-cmd-left": "pane::ActivatePrevItem", - "alt-cmd-right": "pane::ActivateNextItem", - "cmd-w": "pane::CloseActiveItem", - "alt-cmd-t": "pane::CloseInactiveItems", - "cmd-k u": "pane::CloseCleanItems", - "cmd-k cmd-w": "pane::CloseAllItems", - "cmd-shift-w": "workspace::CloseWindow", - "cmd-s": "workspace::Save", - "cmd-shift-s": "workspace::SaveAs", - "cmd-=": "zed::IncreaseBufferFontSize", - "cmd--": "zed::DecreaseBufferFontSize", - "cmd-0": "zed::ResetBufferFontSize", - "cmd-,": "zed::OpenSettings", - "cmd-q": "zed::Quit", - "cmd-h": "zed::Hide", - "alt-cmd-h": "zed::HideOthers", - "cmd-m": "zed::Minimize", - "ctrl-cmd-f": "zed::ToggleFullScreen", - "cmd-n": "workspace::NewFile", - "cmd-shift-n": "workspace::NewWindow", - "cmd-o": "workspace::Open", - "alt-cmd-o": "projects::OpenRecent", - "ctrl-`": "workspace::NewTerminal" - } - }, - { - "context": "Editor", - "bindings": { - "escape": "editor::Cancel", - "backspace": "editor::Backspace", - "shift-backspace": "editor::Backspace", - "ctrl-h": "editor::Backspace", - "delete": "editor::Delete", - "ctrl-d": "editor::Delete", - "tab": "editor::Tab", - "shift-tab": "editor::TabPrev", - "ctrl-k": "editor::CutToEndOfLine", - "ctrl-t": "editor::Transpose", - "cmd-backspace": "editor::DeleteToBeginningOfLine", - "cmd-delete": "editor::DeleteToEndOfLine", - "alt-backspace": "editor::DeleteToPreviousWordStart", - "alt-delete": "editor::DeleteToNextWordEnd", - "alt-h": "editor::DeleteToPreviousWordStart", - "alt-d": "editor::DeleteToNextWordEnd", - "cmd-x": "editor::Cut", - "cmd-c": "editor::Copy", - "cmd-v": "editor::Paste", - "cmd-z": "editor::Undo", - "cmd-shift-z": "editor::Redo", - "up": "editor::MoveUp", - "pageup": "editor::PageUp", - "shift-pageup": "editor::MovePageUp", - "home": "editor::MoveToBeginningOfLine", - "down": "editor::MoveDown", - "pagedown": "editor::PageDown", - "shift-pagedown": "editor::MovePageDown", - "end": "editor::MoveToEndOfLine", - "left": "editor::MoveLeft", - "right": "editor::MoveRight", - "ctrl-p": "editor::MoveUp", - "ctrl-n": "editor::MoveDown", - "ctrl-b": "editor::MoveLeft", - "ctrl-f": "editor::MoveRight", - "ctrl-l": "editor::NextScreen", - "alt-left": "editor::MoveToPreviousWordStart", - "alt-b": "editor::MoveToPreviousWordStart", - "alt-right": "editor::MoveToNextWordEnd", - "alt-f": "editor::MoveToNextWordEnd", - "cmd-left": "editor::MoveToBeginningOfLine", - "ctrl-a": "editor::MoveToBeginningOfLine", - "cmd-right": "editor::MoveToEndOfLine", - "ctrl-e": "editor::MoveToEndOfLine", - "cmd-up": "editor::MoveToBeginning", - "cmd-down": "editor::MoveToEnd", - "shift-up": "editor::SelectUp", - "ctrl-shift-p": "editor::SelectUp", - "shift-down": "editor::SelectDown", - "ctrl-shift-n": "editor::SelectDown", - "shift-left": "editor::SelectLeft", - "ctrl-shift-b": "editor::SelectLeft", - "shift-right": "editor::SelectRight", - "ctrl-shift-f": "editor::SelectRight", - "alt-shift-left": "editor::SelectToPreviousWordStart", - "alt-shift-b": "editor::SelectToPreviousWordStart", - "alt-shift-right": "editor::SelectToNextWordEnd", - "alt-shift-f": "editor::SelectToNextWordEnd", - "cmd-shift-up": "editor::SelectToBeginning", - "cmd-shift-down": "editor::SelectToEnd", - "cmd-a": "editor::SelectAll", - "cmd-l": "editor::SelectLine", - "cmd-shift-i": "editor::Format", - "cmd-shift-left": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true + // Standard macOS bindings + { + "bindings": { + "up": "menu::SelectPrev", + "pageup": "menu::SelectFirst", + "shift-pageup": "menu::SelectFirst", + "ctrl-p": "menu::SelectPrev", + "down": "menu::SelectNext", + "pagedown": "menu::SelectLast", + "shift-pagedown": "menu::SelectFirst", + "ctrl-n": "menu::SelectNext", + "cmd-up": "menu::SelectFirst", + "cmd-down": "menu::SelectLast", + "enter": "menu::Confirm", + "escape": "menu::Cancel", + "ctrl-c": "menu::Cancel", + "cmd-{": "pane::ActivatePrevItem", + "cmd-}": "pane::ActivateNextItem", + "alt-cmd-left": "pane::ActivatePrevItem", + "alt-cmd-right": "pane::ActivateNextItem", + "cmd-w": "pane::CloseActiveItem", + "alt-cmd-t": "pane::CloseInactiveItems", + "cmd-k u": "pane::CloseCleanItems", + "cmd-k cmd-w": "pane::CloseAllItems", + "cmd-shift-w": "workspace::CloseWindow", + "cmd-s": "workspace::Save", + "cmd-shift-s": "workspace::SaveAs", + "cmd-=": "zed::IncreaseBufferFontSize", + "cmd--": "zed::DecreaseBufferFontSize", + "cmd-0": "zed::ResetBufferFontSize", + "cmd-,": "zed::OpenSettings", + "cmd-q": "zed::Quit", + "cmd-h": "zed::Hide", + "alt-cmd-h": "zed::HideOthers", + "cmd-m": "zed::Minimize", + "ctrl-cmd-f": "zed::ToggleFullScreen", + "cmd-n": "workspace::NewFile", + "cmd-shift-n": "workspace::NewWindow", + "cmd-o": "workspace::Open", + "alt-cmd-o": "projects::OpenRecent", + "ctrl-`": "workspace::NewTerminal" } - ], - "shift-home": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true + }, + { + "context": "Editor", + "bindings": { + "escape": "editor::Cancel", + "backspace": "editor::Backspace", + "shift-backspace": "editor::Backspace", + "ctrl-h": "editor::Backspace", + "delete": "editor::Delete", + "ctrl-d": "editor::Delete", + "tab": "editor::Tab", + "shift-tab": "editor::TabPrev", + "ctrl-k": "editor::CutToEndOfLine", + "ctrl-t": "editor::Transpose", + "cmd-backspace": "editor::DeleteToBeginningOfLine", + "cmd-delete": "editor::DeleteToEndOfLine", + "alt-backspace": "editor::DeleteToPreviousWordStart", + "alt-delete": "editor::DeleteToNextWordEnd", + "alt-h": "editor::DeleteToPreviousWordStart", + "alt-d": "editor::DeleteToNextWordEnd", + "cmd-x": "editor::Cut", + "cmd-c": "editor::Copy", + "cmd-v": "editor::Paste", + "cmd-z": "editor::Undo", + "cmd-shift-z": "editor::Redo", + "up": "editor::MoveUp", + "pageup": "editor::PageUp", + "shift-pageup": "editor::MovePageUp", + "home": "editor::MoveToBeginningOfLine", + "down": "editor::MoveDown", + "pagedown": "editor::PageDown", + "shift-pagedown": "editor::MovePageDown", + "end": "editor::MoveToEndOfLine", + "left": "editor::MoveLeft", + "right": "editor::MoveRight", + "ctrl-p": "editor::MoveUp", + "ctrl-n": "editor::MoveDown", + "ctrl-b": "editor::MoveLeft", + "ctrl-f": "editor::MoveRight", + "ctrl-l": "editor::NextScreen", + "alt-left": "editor::MoveToPreviousWordStart", + "alt-b": "editor::MoveToPreviousWordStart", + "alt-right": "editor::MoveToNextWordEnd", + "alt-f": "editor::MoveToNextWordEnd", + "cmd-left": "editor::MoveToBeginningOfLine", + "ctrl-a": "editor::MoveToBeginningOfLine", + "cmd-right": "editor::MoveToEndOfLine", + "ctrl-e": "editor::MoveToEndOfLine", + "cmd-up": "editor::MoveToBeginning", + "cmd-down": "editor::MoveToEnd", + "shift-up": "editor::SelectUp", + "ctrl-shift-p": "editor::SelectUp", + "shift-down": "editor::SelectDown", + "ctrl-shift-n": "editor::SelectDown", + "shift-left": "editor::SelectLeft", + "ctrl-shift-b": "editor::SelectLeft", + "shift-right": "editor::SelectRight", + "ctrl-shift-f": "editor::SelectRight", + "alt-shift-left": "editor::SelectToPreviousWordStart", + "alt-shift-b": "editor::SelectToPreviousWordStart", + "alt-shift-right": "editor::SelectToNextWordEnd", + "alt-shift-f": "editor::SelectToNextWordEnd", + "cmd-shift-up": "editor::SelectToBeginning", + "cmd-shift-down": "editor::SelectToEnd", + "cmd-a": "editor::SelectAll", + "cmd-l": "editor::SelectLine", + "cmd-shift-i": "editor::Format", + "cmd-shift-left": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true + } + ], + "shift-home": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true + } + ], + "ctrl-shift-a": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true + } + ], + "cmd-shift-right": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true + } + ], + "shift-end": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true + } + ], + "ctrl-shift-e": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true + } + ], + "ctrl-v": [ + "editor::MovePageDown", + { + "center_cursor": true + } + ], + "alt-v": [ + "editor::MovePageUp", + { + "center_cursor": true + } + ], + "ctrl-cmd-space": "editor::ShowCharacterPalette" } - ], - "ctrl-shift-a": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true + }, + { + "context": "Editor && mode == full", + "bindings": { + "enter": "editor::Newline", + "cmd-shift-enter": "editor::NewlineAbove", + "cmd-enter": "editor::NewlineBelow", + "alt-z": "editor::ToggleSoftWrap", + "cmd-f": [ + "buffer_search::Deploy", + { + "focus": true + } + ], + "cmd-e": [ + "buffer_search::Deploy", + { + "focus": false + } + ], + "alt-\\": "copilot::Suggest", + "alt-]": "copilot::NextSuggestion", + "alt-[": "copilot::PreviousSuggestion" } - ], - "cmd-shift-right": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true + }, + { + "context": "Editor && mode == auto_height", + "bindings": { + "alt-enter": "editor::Newline", + "cmd-alt-enter": "editor::NewlineBelow" } - ], - "shift-end": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true + }, + { + "context": "BufferSearchBar > Editor", + "bindings": { + "escape": "buffer_search::Dismiss", + "tab": "buffer_search::FocusEditor", + "enter": "search::SelectNextMatch", + "shift-enter": "search::SelectPrevMatch" } - ], - "ctrl-shift-e": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true + }, + { + "context": "ProjectSearchBar > Editor", + "bindings": { + "escape": "project_search::ToggleFocus" } - ], - "ctrl-v": [ - "editor::MovePageDown", - { - "center_cursor": true + }, + { + "context": "ProjectSearchView > Editor", + "bindings": { + "escape": "project_search::ToggleFocus" } - ], - "alt-v": [ - "editor::MovePageUp", - { - "center_cursor": true + }, + { + "context": "Pane", + "bindings": { + "cmd-f": "project_search::ToggleFocus", + "cmd-g": "search::SelectNextMatch", + "cmd-shift-g": "search::SelectPrevMatch", + "alt-cmd-c": "search::ToggleCaseSensitive", + "alt-cmd-w": "search::ToggleWholeWord", + "alt-cmd-r": "search::ToggleRegex", + "shift-escape": "pane::ToggleZoom" } - ], - "ctrl-cmd-space": "editor::ShowCharacterPalette" - } - }, - { - "context": "Editor && mode == full", - "bindings": { - "enter": "editor::Newline", - "cmd-shift-enter": "editor::NewlineAbove", - "cmd-enter": "editor::NewlineBelow", - "alt-z": "editor::ToggleSoftWrap", - "cmd-f": [ - "buffer_search::Deploy", - { - "focus": true + }, + // Bindings from VS Code + { + "context": "Editor", + "bindings": { + "cmd-[": "editor::Outdent", + "cmd-]": "editor::Indent", + "cmd-alt-up": "editor::AddSelectionAbove", + "cmd-ctrl-p": "editor::AddSelectionAbove", + "cmd-alt-down": "editor::AddSelectionBelow", + "cmd-ctrl-n": "editor::AddSelectionBelow", + "cmd-d": [ + "editor::SelectNext", + { + "replace_newest": false + } + ], + "cmd-k cmd-d": [ + "editor::SelectNext", + { + "replace_newest": true + } + ], + "cmd-k cmd-i": "editor::Hover", + "cmd-/": [ + "editor::ToggleComments", + { + "advance_downwards": false + } + ], + "alt-up": "editor::SelectLargerSyntaxNode", + "alt-down": "editor::SelectSmallerSyntaxNode", + "cmd-u": "editor::UndoSelection", + "cmd-shift-u": "editor::RedoSelection", + "f8": "editor::GoToDiagnostic", + "shift-f8": "editor::GoToPrevDiagnostic", + "f2": "editor::Rename", + "f12": "editor::GoToDefinition", + "cmd-f12": "editor::GoToTypeDefinition", + "alt-shift-f12": "editor::FindAllReferences", + "ctrl-m": "editor::MoveToEnclosingBracket", + "alt-cmd-[": "editor::Fold", + "alt-cmd-]": "editor::UnfoldLines", + "ctrl-space": "editor::ShowCompletions", + "cmd-.": "editor::ToggleCodeActions", + "alt-cmd-r": "editor::RevealInFinder" } - ], - "cmd-e": [ - "buffer_search::Deploy", - { - "focus": false + }, + { + "context": "Editor && mode == full", + "bindings": { + "cmd-shift-o": "outline::Toggle", + "ctrl-g": "go_to_line::Toggle" } - ], - "alt-\\": "copilot::Suggest", - "alt-]": "copilot::NextSuggestion", - "alt-[": "copilot::PreviousSuggestion" - } - }, - { - "context": "Editor && mode == auto_height", - "bindings": { - "alt-enter": "editor::Newline", - "cmd-alt-enter": "editor::NewlineBelow" - } - }, - { - "context": "BufferSearchBar > Editor", - "bindings": { - "escape": "buffer_search::Dismiss", - "tab": "buffer_search::FocusEditor", - "enter": "search::SelectNextMatch", - "shift-enter": "search::SelectPrevMatch" - } - }, - { - "context": "ProjectSearchBar > Editor", - "bindings": { - "escape": "project_search::ToggleFocus" - } - }, - { - "context": "ProjectSearchView > Editor", - "bindings": { - "escape": "project_search::ToggleFocus" - } - }, - { - "context": "Pane", - "bindings": { - "cmd-f": "project_search::ToggleFocus", - "cmd-g": "search::SelectNextMatch", - "cmd-shift-g": "search::SelectPrevMatch", - "alt-cmd-c": "search::ToggleCaseSensitive", - "alt-cmd-w": "search::ToggleWholeWord", - "alt-cmd-r": "search::ToggleRegex" - } - }, - // Bindings from VS Code - { - "context": "Editor", - "bindings": { - "cmd-[": "editor::Outdent", - "cmd-]": "editor::Indent", - "cmd-alt-up": "editor::AddSelectionAbove", - "cmd-ctrl-p": "editor::AddSelectionAbove", - "cmd-alt-down": "editor::AddSelectionBelow", - "cmd-ctrl-n": "editor::AddSelectionBelow", - "cmd-d": [ - "editor::SelectNext", - { - "replace_newest": false + }, + { + "context": "Pane", + "bindings": { + "ctrl-1": [ + "pane::ActivateItem", + 0 + ], + "ctrl-2": [ + "pane::ActivateItem", + 1 + ], + "ctrl-3": [ + "pane::ActivateItem", + 2 + ], + "ctrl-4": [ + "pane::ActivateItem", + 3 + ], + "ctrl-5": [ + "pane::ActivateItem", + 4 + ], + "ctrl-6": [ + "pane::ActivateItem", + 5 + ], + "ctrl-7": [ + "pane::ActivateItem", + 6 + ], + "ctrl-8": [ + "pane::ActivateItem", + 7 + ], + "ctrl-9": [ + "pane::ActivateItem", + 8 + ], + "ctrl-0": "pane::ActivateLastItem", + "ctrl--": "pane::GoBack", + "ctrl-_": "pane::GoForward", + "cmd-shift-t": "pane::ReopenClosedItem", + "cmd-shift-f": "project_search::ToggleFocus" } - ], - "cmd-k cmd-d": [ - "editor::SelectNext", - { - "replace_newest": true + }, + { + "context": "Workspace", + "bindings": { + "cmd-1": [ + "workspace::ActivatePane", + 0 + ], + "cmd-2": [ + "workspace::ActivatePane", + 1 + ], + "cmd-3": [ + "workspace::ActivatePane", + 2 + ], + "cmd-4": [ + "workspace::ActivatePane", + 3 + ], + "cmd-5": [ + "workspace::ActivatePane", + 4 + ], + "cmd-6": [ + "workspace::ActivatePane", + 5 + ], + "cmd-7": [ + "workspace::ActivatePane", + 6 + ], + "cmd-8": [ + "workspace::ActivatePane", + 7 + ], + "cmd-9": [ + "workspace::ActivatePane", + 8 + ], + "cmd-b": "workspace::ToggleLeftDock", + "cmd-shift-f": "workspace::NewSearch", + "cmd-k cmd-t": "theme_selector::Toggle", + "cmd-k cmd-s": "zed::OpenKeymap", + "cmd-t": "project_symbols::Toggle", + "cmd-p": "file_finder::Toggle", + "cmd-shift-p": "command_palette::Toggle", + "cmd-shift-m": "diagnostics::Deploy", + "cmd-shift-e": "project_panel::ToggleFocus", + "cmd-alt-s": "workspace::SaveAll", + "cmd-k m": "language_selector::Toggle" } - ], - "cmd-k cmd-i": "editor::Hover", - "cmd-/": [ - "editor::ToggleComments", - { - "advance_downwards": false + }, + // Bindings from Sublime Text + { + "context": "Editor", + "bindings": { + "ctrl-shift-k": "editor::DeleteLine", + "cmd-shift-d": "editor::DuplicateLine", + "cmd-shift-l": "editor::SplitSelectionIntoLines", + "ctrl-cmd-up": "editor::MoveLineUp", + "ctrl-cmd-down": "editor::MoveLineDown", + "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart", + "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", + "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", + "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", + "ctrl-alt-left": "editor::MoveToPreviousSubwordStart", + "ctrl-alt-b": "editor::MoveToPreviousSubwordStart", + "ctrl-alt-right": "editor::MoveToNextSubwordEnd", + "ctrl-alt-f": "editor::MoveToNextSubwordEnd", + "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart", + "ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart", + "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd", + "ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd" + } + }, + { + "bindings": { + "cmd-k cmd-left": "workspace::ActivatePreviousPane", + "cmd-k cmd-right": "workspace::ActivateNextPane" + } + }, + // Bindings from Atom + { + "context": "Pane", + "bindings": { + "cmd-k up": "pane::SplitUp", + "cmd-k down": "pane::SplitDown", + "cmd-k left": "pane::SplitLeft", + "cmd-k right": "pane::SplitRight" + } + }, + // Bindings that should be unified with bindings for more general actions + { + "context": "Editor && renaming", + "bindings": { + "enter": "editor::ConfirmRename" + } + }, + { + "context": "Editor && showing_completions", + "bindings": { + "enter": "editor::ConfirmCompletion", + "tab": "editor::ConfirmCompletion" + } + }, + { + "context": "Editor && showing_code_actions", + "bindings": { + "enter": "editor::ConfirmCodeAction" + } + }, + // Custom bindings + { + "bindings": { + "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator", + "cmd-shift-c": "collab::ToggleContactsMenu", + "cmd-alt-i": "zed::DebugElements" + } + }, + { + "context": "Editor", + "bindings": { + "alt-enter": "editor::OpenExcerpts", + "cmd-f8": "editor::GoToHunk", + "cmd-shift-f8": "editor::GoToPrevHunk" + } + }, + { + "context": "ProjectSearchBar", + "bindings": { + "cmd-enter": "project_search::SearchInNew" + } + }, + { + "context": "ProjectPanel", + "bindings": { + "left": "project_panel::CollapseSelectedEntry", + "right": "project_panel::ExpandSelectedEntry", + "cmd-x": "project_panel::Cut", + "cmd-c": "project_panel::Copy", + "cmd-v": "project_panel::Paste", + "cmd-alt-c": "project_panel::CopyPath", + "alt-cmd-shift-c": "project_panel::CopyRelativePath", + "f2": "project_panel::Rename", + "backspace": "project_panel::Delete", + "alt-cmd-r": "project_panel::RevealInFinder" + } + }, + { + "context": "Terminal", + "bindings": { + "ctrl-cmd-space": "terminal::ShowCharacterPalette", + "cmd-c": "terminal::Copy", + "cmd-v": "terminal::Paste", + "cmd-k": "terminal::Clear", + // Some nice conveniences + "cmd-backspace": [ + "terminal::SendText", + "\u0015" + ], + "cmd-right": [ + "terminal::SendText", + "\u0005" + ], + "cmd-left": [ + "terminal::SendText", + "\u0001" + ], + // Terminal.app compatability + "alt-left": [ + "terminal::SendText", + "\u001bb" + ], + "alt-right": [ + "terminal::SendText", + "\u001bf" + ], + // There are conflicting bindings for these keys in the global context. + // these bindings override them, remove at your own risk: + "up": [ + "terminal::SendKeystroke", + "up" + ], + "pageup": [ + "terminal::SendKeystroke", + "pageup" + ], + "down": [ + "terminal::SendKeystroke", + "down" + ], + "pagedown": [ + "terminal::SendKeystroke", + "pagedown" + ], + "escape": [ + "terminal::SendKeystroke", + "escape" + ], + "enter": [ + "terminal::SendKeystroke", + "enter" + ], + "ctrl-c": [ + "terminal::SendKeystroke", + "ctrl-c" + ] } - ], - "alt-up": "editor::SelectLargerSyntaxNode", - "alt-down": "editor::SelectSmallerSyntaxNode", - "cmd-u": "editor::UndoSelection", - "cmd-shift-u": "editor::RedoSelection", - "f8": "editor::GoToDiagnostic", - "shift-f8": "editor::GoToPrevDiagnostic", - "f2": "editor::Rename", - "f12": "editor::GoToDefinition", - "cmd-f12": "editor::GoToTypeDefinition", - "alt-shift-f12": "editor::FindAllReferences", - "ctrl-m": "editor::MoveToEnclosingBracket", - "alt-cmd-[": "editor::Fold", - "alt-cmd-]": "editor::UnfoldLines", - "ctrl-space": "editor::ShowCompletions", - "cmd-.": "editor::ToggleCodeActions", - "alt-cmd-r": "editor::RevealInFinder" } - }, - { - "context": "Editor && mode == full", - "bindings": { - "cmd-shift-o": "outline::Toggle", - "ctrl-g": "go_to_line::Toggle" - } - }, - { - "context": "Pane", - "bindings": { - "ctrl-1": [ - "pane::ActivateItem", - 0 - ], - "ctrl-2": [ - "pane::ActivateItem", - 1 - ], - "ctrl-3": [ - "pane::ActivateItem", - 2 - ], - "ctrl-4": [ - "pane::ActivateItem", - 3 - ], - "ctrl-5": [ - "pane::ActivateItem", - 4 - ], - "ctrl-6": [ - "pane::ActivateItem", - 5 - ], - "ctrl-7": [ - "pane::ActivateItem", - 6 - ], - "ctrl-8": [ - "pane::ActivateItem", - 7 - ], - "ctrl-9": [ - "pane::ActivateItem", - 8 - ], - "ctrl-0": "pane::ActivateLastItem", - "ctrl--": "pane::GoBack", - "ctrl-_": "pane::GoForward", - "cmd-shift-t": "pane::ReopenClosedItem", - "cmd-shift-f": "project_search::ToggleFocus" - } - }, - { - "context": "Workspace", - "bindings": { - "cmd-1": [ - "workspace::ActivatePane", - 0 - ], - "cmd-2": [ - "workspace::ActivatePane", - 1 - ], - "cmd-3": [ - "workspace::ActivatePane", - 2 - ], - "cmd-4": [ - "workspace::ActivatePane", - 3 - ], - "cmd-5": [ - "workspace::ActivatePane", - 4 - ], - "cmd-6": [ - "workspace::ActivatePane", - 5 - ], - "cmd-7": [ - "workspace::ActivatePane", - 6 - ], - "cmd-8": [ - "workspace::ActivatePane", - 7 - ], - "cmd-9": [ - "workspace::ActivatePane", - 8 - ], - "cmd-b": "workspace::ToggleLeftDock", - "cmd-shift-f": "workspace::NewSearch", - "cmd-k cmd-t": "theme_selector::Toggle", - "cmd-k cmd-s": "zed::OpenKeymap", - "cmd-t": "project_symbols::Toggle", - "cmd-p": "file_finder::Toggle", - "cmd-shift-p": "command_palette::Toggle", - "cmd-shift-m": "diagnostics::Deploy", - "cmd-shift-e": "project_panel::ToggleFocus", - "cmd-alt-s": "workspace::SaveAll", - "cmd-k m": "language_selector::Toggle" - } - }, - // Bindings from Sublime Text - { - "context": "Editor", - "bindings": { - "ctrl-shift-k": "editor::DeleteLine", - "cmd-shift-d": "editor::DuplicateLine", - "cmd-shift-l": "editor::SplitSelectionIntoLines", - "ctrl-cmd-up": "editor::MoveLineUp", - "ctrl-cmd-down": "editor::MoveLineDown", - "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart", - "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", - "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", - "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", - "ctrl-alt-left": "editor::MoveToPreviousSubwordStart", - "ctrl-alt-b": "editor::MoveToPreviousSubwordStart", - "ctrl-alt-right": "editor::MoveToNextSubwordEnd", - "ctrl-alt-f": "editor::MoveToNextSubwordEnd", - "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart", - "ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart", - "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd", - "ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd" - } - }, - { - "bindings": { - "cmd-k cmd-left": "workspace::ActivatePreviousPane", - "cmd-k cmd-right": "workspace::ActivateNextPane" - } - }, - // Bindings from Atom - { - "context": "Pane", - "bindings": { - "cmd-k up": "pane::SplitUp", - "cmd-k down": "pane::SplitDown", - "cmd-k left": "pane::SplitLeft", - "cmd-k right": "pane::SplitRight" - } - }, - // Bindings that should be unified with bindings for more general actions - { - "context": "Editor && renaming", - "bindings": { - "enter": "editor::ConfirmRename" - } - }, - { - "context": "Editor && showing_completions", - "bindings": { - "enter": "editor::ConfirmCompletion", - "tab": "editor::ConfirmCompletion" - } - }, - { - "context": "Editor && showing_code_actions", - "bindings": { - "enter": "editor::ConfirmCodeAction" - } - }, - // Custom bindings - { - "bindings": { - "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator", - "cmd-shift-c": "collab::ToggleContactsMenu", - "cmd-alt-i": "zed::DebugElements" - } - }, - { - "context": "Editor", - "bindings": { - "alt-enter": "editor::OpenExcerpts", - "cmd-f8": "editor::GoToHunk", - "cmd-shift-f8": "editor::GoToPrevHunk" - } - }, - { - "context": "ProjectSearchBar", - "bindings": { - "cmd-enter": "project_search::SearchInNew" - } - }, - { - "context": "ProjectPanel", - "bindings": { - "left": "project_panel::CollapseSelectedEntry", - "right": "project_panel::ExpandSelectedEntry", - "cmd-x": "project_panel::Cut", - "cmd-c": "project_panel::Copy", - "cmd-v": "project_panel::Paste", - "cmd-alt-c": "project_panel::CopyPath", - "alt-cmd-shift-c": "project_panel::CopyRelativePath", - "f2": "project_panel::Rename", - "backspace": "project_panel::Delete", - "alt-cmd-r": "project_panel::RevealInFinder" - } - }, - { - "context": "Terminal", - "bindings": { - "ctrl-cmd-space": "terminal::ShowCharacterPalette", - "cmd-c": "terminal::Copy", - "cmd-v": "terminal::Paste", - "cmd-k": "terminal::Clear", - // Some nice conveniences - "cmd-backspace": [ - "terminal::SendText", - "\u0015" - ], - "cmd-right": [ - "terminal::SendText", - "\u0005" - ], - "cmd-left": [ - "terminal::SendText", - "\u0001" - ], - // Terminal.app compatability - "alt-left": [ - "terminal::SendText", - "\u001bb" - ], - "alt-right": [ - "terminal::SendText", - "\u001bf" - ], - // There are conflicting bindings for these keys in the global context. - // these bindings override them, remove at your own risk: - "up": [ - "terminal::SendKeystroke", - "up" - ], - "pageup": [ - "terminal::SendKeystroke", - "pageup" - ], - "down": [ - "terminal::SendKeystroke", - "down" - ], - "pagedown": [ - "terminal::SendKeystroke", - "pagedown" - ], - "escape": [ - "terminal::SendKeystroke", - "escape" - ], - "enter": [ - "terminal::SendKeystroke", - "enter" - ], - "ctrl-c": [ - "terminal::SendKeystroke", - "ctrl-c" - ] - } - } ] diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 488f55d0c6..65418dd997 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3816,6 +3816,12 @@ impl PartialEq for ViewHandle { } } +impl PartialEq for ViewHandle { + fn eq(&self, other: &AnyViewHandle) -> bool { + self.window_id == other.window_id && self.view_id == other.view_id + } +} + impl PartialEq> for ViewHandle { fn eq(&self, other: &WeakViewHandle) -> bool { self.window_id == other.window_id && self.view_id == other.view_id diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index f593615ae7..40b6a50816 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -33,8 +33,11 @@ use crate::{ rect::RectF, vector::{vec2f, Vector2F}, }, - json, Action, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle, - WindowContext, + json, + platform::MouseButton, + scene::MouseDown, + Action, EventContext, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, + WeakViewHandle, WindowContext, }; use anyhow::{anyhow, Result}; use collections::HashMap; @@ -198,6 +201,13 @@ pub trait Element: 'static { { Resizable::new(self.into_any(), side, size, on_resize) } + + fn mouse(self, region_id: usize) -> MouseEventHandler + where + Self: Sized, + { + MouseEventHandler::for_child(self.into_any(), region_id) + } } trait AnyElementState { diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index ed624922d5..6f2762db66 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -32,10 +32,25 @@ pub struct MouseEventHandler { /// Element which provides a render_child callback with a MouseState and paints a mouse /// region under (or above) it for easy mouse event handling. impl MouseEventHandler { - pub fn new(region_id: usize, cx: &mut ViewContext, render_child: F) -> Self + pub fn for_child(child: impl Element, region_id: usize) -> Self { + Self { + child: child.into_any(), + region_id, + cursor_style: None, + handlers: Default::default(), + notify_on_hover: false, + notify_on_click: false, + hoverable: false, + above: false, + padding: Default::default(), + _tag: PhantomData, + } + } + + pub fn new(region_id: usize, cx: &mut ViewContext, render_child: F) -> Self where - D: Element, - F: FnOnce(&mut MouseState, &mut ViewContext) -> D, + E: Element, + F: FnOnce(&mut MouseState, &mut ViewContext) -> E, { let mut mouse_state = cx.mouse_state::(region_id); let child = render_child(&mut mouse_state, cx).into_any(); diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 29bb51be68..50f4607d66 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -68,6 +68,8 @@ pub struct Workspace { pub breadcrumbs: Interactive, pub disconnected_overlay: ContainedText, pub modal: ContainerStyle, + pub zoomed_foreground: ContainerStyle, + pub zoomed_background: ContainerStyle, pub notification: ContainerStyle, pub notifications: Notifications, pub joining_project_avatar: ImageStyle, diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 30e782c750..a216f6181c 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -69,6 +69,7 @@ actions!( SplitUp, SplitRight, SplitDown, + ToggleZoom, ] ); @@ -91,6 +92,7 @@ const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; pub type BackgroundActions = fn() -> &'static [(&'static str, &'static dyn Action)]; pub fn init(cx: &mut AppContext) { + cx.add_action(Pane::toggle_zoom); cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| { pane.activate_item(action.0, true, true, cx); }); @@ -132,12 +134,15 @@ pub enum Event { Split(SplitDirection), ChangeItemTitle, Focus, + ZoomIn, + ZoomOut, } pub struct Pane { items: Vec>, activation_history: Vec, is_active: bool, + zoomed: bool, active_item_index: usize, last_focused_view_by_item: HashMap, autoscroll: bool, @@ -236,6 +241,7 @@ impl Pane { items: Vec::new(), activation_history: Vec::new(), is_active: true, + zoomed: false, active_item_index: 0, last_focused_view_by_item: Default::default(), autoscroll: false, @@ -655,6 +661,14 @@ impl Pane { self.items.iter().position(|i| i.id() == item.id()) } + pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { + if self.zoomed { + cx.emit(Event::ZoomOut); + } else { + cx.emit(Event::ZoomIn); + } + } + pub fn activate_item( &mut self, index: usize, @@ -1546,6 +1560,15 @@ impl Pane { .with_background_color(background) .into_any() } + + pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { + self.zoomed = zoomed; + cx.notify(); + } + + pub fn is_zoomed(&self) -> bool { + self.zoomed + } } impl Entity for Pane { diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 55032b4bc1..a1b35c1d65 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -142,6 +142,12 @@ impl Member { match self { Member::Pane(pane) => { + let pane_element = if pane.read(cx).is_zoomed() { + Empty::new().into_any() + } else { + ChildView::new(pane, cx).into_any() + }; + let leader = follower_states .iter() .find_map(|(leader_id, follower_states)| { @@ -258,7 +264,7 @@ impl Member { }; Stack::new() - .with_child(ChildView::new(pane, cx).contained().with_border(border)) + .with_child(pane_element.contained().with_border(border)) .with_children(leader_status_box) .into_any() } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ad717c362b..f4ff8d5c91 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -441,6 +441,7 @@ pub struct Workspace { weak_self: WeakViewHandle, remote_entity_subscription: Option, modal: Option, + zoomed: Option, center: PaneGroup, left_dock: ViewHandle, bottom_dock: ViewHandle, @@ -627,8 +628,9 @@ impl Workspace { ]; let mut this = Workspace { - modal: None, weak_self: weak_handle.clone(), + modal: None, + zoomed: None, center: PaneGroup::new(center_pane.clone()), panes: vec![center_pane.clone()], panes_by_item: Default::default(), @@ -1303,6 +1305,16 @@ impl Workspace { } } + pub fn zoom_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext) { + self.zoomed = Some(view); + cx.notify(); + } + + pub fn zoom_out(&mut self, cx: &mut ViewContext) { + self.zoomed.take(); + cx.notify(); + } + pub fn items<'a>( &'a self, cx: &'a AppContext, @@ -1685,6 +1697,16 @@ impl Workspace { pane::Event::Focus => { self.handle_pane_focused(pane.clone(), cx); } + pane::Event::ZoomIn => { + pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); + self.zoom_in(pane.into_any(), cx); + } + pane::Event::ZoomOut => { + if self.zoomed.as_ref().map_or(false, |zoomed| *zoomed == pane) { + pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); + self.zoom_out(cx); + } + } } self.serialize_workspace(cx); @@ -2735,6 +2757,21 @@ impl View for Workspace { }) .with_child(Overlay::new( Stack::new() + .with_children(self.zoomed.as_ref().map(|zoomed| { + enum ZoomBackground {} + + ChildView::new(zoomed, cx) + .contained() + .with_style(theme.workspace.zoomed_foreground) + .aligned() + .contained() + .with_style(theme.workspace.zoomed_background) + .mouse::(0) + .capture_all() + .on_down(MouseButton::Left, |_, this: &mut Self, cx| { + this.zoom_out(cx); + }) + })) .with_children(self.modal.as_ref().map(|modal| { ChildView::new(modal, cx) .contained() diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index fe359b0501..501debc876 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -118,6 +118,15 @@ export default function workspace(colorScheme: ColorScheme) { }, cursor: "Arrow", }, + zoomedBackground: { + padding: 10, + cursor: "Arrow", + background: withOpacity(background(colorScheme.lowest), 0.5) + }, + zoomedForeground: { + shadow: colorScheme.modalShadow, + border: border(colorScheme.highest, { overlay: true }), + }, dock: { initialSize: 240, border: border(layer, { left: true, right: true }), From c03e470fe6e0ef750f784d757ea31ddf689c3d05 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 May 2023 17:10:30 +0200 Subject: [PATCH 34/61] Introduce `Panel::can_zoom` --- crates/project_panel/src/project_panel.rs | 4 ++++ crates/terminal_view/src/terminal_panel.rs | 10 +++++++--- crates/workspace/src/dock.rs | 18 +++++++++++++++--- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index e271c9970b..b1f0740df0 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1373,6 +1373,10 @@ impl workspace::dock::Panel for ProjectPanel { cx.global::().project_panel.default_width } + fn can_zoom(&self, _cx: &gpui::WindowContext) -> bool { + false + } + fn icon_path(&self) -> &'static str { "icons/folder_tree_16.svg" } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 995c8be50b..f3bfbdd730 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1,7 +1,7 @@ use crate::TerminalView; use gpui::{ elements::*, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle, - WeakViewHandle, + WeakViewHandle, WindowContext, }; use project::Project; use settings::{settings_file::SettingsFile, Settings, TerminalDockPosition, WorkingDirectory}; @@ -149,7 +149,7 @@ impl View for TerminalPanel { } impl Panel for TerminalPanel { - fn position(&self, cx: &gpui::WindowContext) -> DockPosition { + fn position(&self, cx: &WindowContext) -> DockPosition { let settings = cx.global::(); let dock = settings .terminal_overrides @@ -179,7 +179,7 @@ impl Panel for TerminalPanel { }); } - fn default_size(&self, cx: &gpui::WindowContext) -> f32 { + fn default_size(&self, cx: &WindowContext) -> f32 { let settings = &cx.global::().terminal_overrides; match self.position(cx) { DockPosition::Left | DockPosition::Right => settings.default_width.unwrap_or(640.), @@ -187,6 +187,10 @@ impl Panel for TerminalPanel { } } + fn can_zoom(&self, _: &WindowContext) -> bool { + true + } + fn icon_path(&self) -> &'static str { "icons/terminal_12.svg" } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 86c2cc3929..6d0b34dc27 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -9,11 +9,16 @@ use serde::Deserialize; use settings::Settings; use std::rc::Rc; +pub fn init(cx: &mut AppContext) { + cx.capture_action(Dock::toggle_zoom); +} + pub trait Panel: View { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); fn default_size(&self, cx: &WindowContext) -> f32; + fn can_zoom(&self, cx: &WindowContext) -> bool; fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; fn icon_label(&self, _: &AppContext) -> Option { @@ -30,6 +35,7 @@ pub trait PanelHandle { fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; fn set_position(&self, position: DockPosition, cx: &mut WindowContext); fn default_size(&self, cx: &WindowContext) -> f32; + fn can_zoom(&self, cx: &WindowContext) -> bool; fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -61,6 +67,10 @@ where self.read(cx).default_size(cx) } + fn can_zoom(&self, cx: &WindowContext) -> bool { + self.read(cx).can_zoom(cx) + } + fn icon_path(&self, cx: &WindowContext) -> &'static str { self.read(cx).icon_path() } @@ -313,9 +323,7 @@ impl View for Dock { .resizable( self.position.to_resize_handle_side(), size, - |dock: &mut Self, size, cx| { - dock.resize_active_panel(size, cx); - }, + |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), ) .into_any() } else { @@ -526,6 +534,10 @@ pub(crate) mod test { } } + fn can_zoom(&self, _cx: &WindowContext) -> bool { + unimplemented!() + } + fn icon_path(&self) -> &'static str { "icons/test_panel.svg" } From adf361b374b7a293881795aa24093bac8aae5751 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 16 May 2023 11:49:48 +0200 Subject: [PATCH 35/61] Implement zooming for panes and docks --- assets/keymaps/default.json | 2 +- crates/gpui/src/elements.rs | 7 +-- crates/workspace/src/dock.rs | 77 +++++++++++++++++++------- crates/workspace/src/pane.rs | 10 ++-- crates/workspace/src/workspace.rs | 89 +++++++++++++++++++------------ 5 files changed, 122 insertions(+), 63 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 768e0c6ed1..37c010f0f9 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -220,7 +220,7 @@ "alt-cmd-c": "search::ToggleCaseSensitive", "alt-cmd-w": "search::ToggleWholeWord", "alt-cmd-r": "search::ToggleRegex", - "shift-escape": "pane::ToggleZoom" + "shift-escape": "workspace::ToggleZoom" } }, // Bindings from VS Code diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index 40b6a50816..a566751fd5 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -33,11 +33,8 @@ use crate::{ rect::RectF, vector::{vec2f, Vector2F}, }, - json, - platform::MouseButton, - scene::MouseDown, - Action, EventContext, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, - WeakViewHandle, WindowContext, + json, Action, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle, + WindowContext, }; use anyhow::{anyhow, Result}; use collections::HashMap; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 6d0b34dc27..2e4303e70f 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,4 +1,4 @@ -use crate::{StatusItemView, Workspace}; +use crate::{StatusItemView, ToggleZoom, Workspace}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, @@ -10,7 +10,7 @@ use settings::Settings; use std::rc::Rc; pub fn init(cx: &mut AppContext) { - cx.capture_action(Dock::toggle_zoom); + cx.add_action(Dock::toggle_zoom); } pub trait Panel: View { @@ -98,6 +98,10 @@ impl From<&dyn PanelHandle> for AnyViewHandle { } } +pub enum Event { + ZoomIn, +} + pub struct Dock { position: DockPosition, panel_entries: Vec, @@ -141,6 +145,7 @@ struct PanelEntry { panel: Rc, size: f32, context_menu: ViewHandle, + zoomed: bool, _subscriptions: [Subscription; 2], } @@ -187,6 +192,25 @@ impl Dock { cx.notify(); } + pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { + for (ix, entry) in self.panel_entries.iter_mut().enumerate() { + if ix == self.active_panel_index && entry.panel.can_zoom(cx) { + entry.zoomed = zoomed; + } else { + entry.zoomed = false; + } + } + + cx.notify(); + } + + pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { + cx.propagate_action(); + if !self.active_entry().map_or(false, |entry| entry.zoomed) { + cx.emit(Event::ZoomIn); + } + } + pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) { let subscriptions = [ cx.observe(&panel, |_, _, cx| cx.notify()), @@ -214,6 +238,7 @@ impl Dock { self.panel_entries.push(PanelEntry { panel: Rc::new(panel), size, + zoomed: false, context_menu: cx.add_view(|cx| { let mut menu = ContextMenu::new(dock_view_id, cx); menu.set_position_mode(OverlayPositionMode::Local); @@ -260,10 +285,22 @@ impl Dock { } pub fn active_panel(&self) -> Option<&Rc> { + let entry = self.active_entry()?; + Some(&entry.panel) + } + + fn active_entry(&self) -> Option<&PanelEntry> { if self.is_open { - self.panel_entries - .get(self.active_panel_index) - .map(|entry| &entry.panel) + self.panel_entries.get(self.active_panel_index) + } else { + None + } + } + + pub fn zoomed_panel(&self) -> Option { + let entry = self.active_entry()?; + if entry.zoomed { + Some(entry.panel.as_any().clone()) } else { None } @@ -305,7 +342,7 @@ impl Dock { } impl Entity for Dock { - type Event = (); + type Event = Event; } impl View for Dock { @@ -314,18 +351,22 @@ impl View for Dock { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - if let Some(active_panel) = self.active_panel() { - let size = self.active_panel_size().unwrap(); - let style = &cx.global::().theme.workspace.dock; - ChildView::new(active_panel.as_any(), cx) - .contained() - .with_style(style.container) - .resizable( - self.position.to_resize_handle_side(), - size, - |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), - ) - .into_any() + if let Some(active_entry) = self.active_entry() { + if active_entry.zoomed { + Empty::new().into_any() + } else { + let size = self.active_panel_size().unwrap(); + let style = &cx.global::().theme.workspace.dock; + ChildView::new(active_entry.panel.as_any(), cx) + .contained() + .with_style(style.container) + .resizable( + self.position.to_resize_handle_side(), + size, + |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), + ) + .into_any() + } } else { Empty::new().into_any() } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index a216f6181c..6ccd1573bc 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2,7 +2,8 @@ mod dragged_item_receiver; use super::{ItemHandle, SplitDirection}; use crate::{ - item::WeakItemHandle, toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, Workspace, + item::WeakItemHandle, toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, ToggleZoom, + Workspace, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -69,7 +70,6 @@ actions!( SplitUp, SplitRight, SplitDown, - ToggleZoom, ] ); @@ -135,7 +135,6 @@ pub enum Event { ChangeItemTitle, Focus, ZoomIn, - ZoomOut, } pub struct Pane { @@ -662,9 +661,8 @@ impl Pane { } pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { - if self.zoomed { - cx.emit(Event::ZoomOut); - } else { + cx.propagate_action(); + if !self.zoomed { cx.emit(Event::ZoomIn); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f4ff8d5c91..af8c958db5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -119,7 +119,8 @@ actions!( NewSearch, Feedback, Restart, - Welcome + Welcome, + ToggleZoom, ] ); @@ -181,6 +182,7 @@ pub type WorkspaceId = i64; impl_actions!(workspace, [ActivatePane]); pub fn init(app_state: Arc, cx: &mut AppContext) { + dock::init(cx); pane::init(cx); notifications::init(cx); @@ -230,6 +232,7 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { }, ); cx.add_action(Workspace::toggle_panel); + cx.add_action(Workspace::toggle_zoom); cx.add_action(Workspace::focus_center); cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| { workspace.activate_previous_pane(cx) @@ -441,7 +444,6 @@ pub struct Workspace { weak_self: WeakViewHandle, remote_entity_subscription: Option, modal: Option, - zoomed: Option, center: PaneGroup, left_dock: ViewHandle, bottom_dock: ViewHandle, @@ -593,7 +595,7 @@ impl Workspace { active_call = Some((call, subscriptions)); } - let subscriptions = vec![ + let mut subscriptions = vec![ cx.observe_fullscreen(|_, _, cx| cx.notify()), cx.observe_window_activation(Self::on_window_activation_changed), cx.observe_window_bounds(move |_, mut bounds, display, cx| { @@ -613,24 +615,14 @@ impl Workspace { .spawn(DB.set_window_bounds(workspace_id, bounds, display)) .detach_and_log_err(cx); }), - cx.observe(&left_dock, |this, _, cx| { - this.serialize_workspace(cx); - cx.notify(); - }), - cx.observe(&bottom_dock, |this, _, cx| { - this.serialize_workspace(cx); - cx.notify(); - }), - cx.observe(&right_dock, |this, _, cx| { - this.serialize_workspace(cx); - cx.notify(); - }), ]; + subscriptions.extend(Self::register_dock(&left_dock, cx)); + subscriptions.extend(Self::register_dock(&bottom_dock, cx)); + subscriptions.extend(Self::register_dock(&right_dock, cx)); let mut this = Workspace { weak_self: weak_handle.clone(), modal: None, - zoomed: None, center: PaneGroup::new(center_pane.clone()), panes: vec![center_pane.clone()], panes_by_item: Default::default(), @@ -1305,14 +1297,16 @@ impl Workspace { } } - pub fn zoom_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext) { - self.zoomed = Some(view); - cx.notify(); - } - - pub fn zoom_out(&mut self, cx: &mut ViewContext) { - self.zoomed.take(); - cx.notify(); + fn zoomed(&self, cx: &AppContext) -> Option { + self.left_dock + .read(cx) + .zoomed_panel() + .or(self.bottom_dock.read(cx).zoomed_panel()) + .or(self.right_dock.read(cx).zoomed_panel()) + .or_else(|| { + let pane = self.panes.iter().find(|pane| pane.read(cx).is_zoomed())?; + Some(pane.clone().into_any()) + }) } pub fn items<'a>( @@ -1470,11 +1464,46 @@ impl Workspace { cx.notify(); } + fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { + // Any time the zoom is toggled we will zoom out all panes and docks. Then, + // the dock or pane that was zoomed will emit an event to zoom itself back in. + self.zoom_out(cx); + } + + fn zoom_out(&mut self, cx: &mut ViewContext) { + for pane in &self.panes { + pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); + } + + self.left_dock + .update(cx, |dock, cx| dock.set_zoomed(false, cx)); + self.bottom_dock + .update(cx, |dock, cx| dock.set_zoomed(false, cx)); + self.right_dock + .update(cx, |dock, cx| dock.set_zoomed(false, cx)); + + cx.notify(); + } + pub fn focus_center(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { cx.focus_self(); cx.notify(); } + fn register_dock(dock: &ViewHandle, cx: &mut ViewContext) -> [Subscription; 2] { + [ + cx.observe(dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), + cx.subscribe(dock, |_, dock, event, cx| { + dock.update(cx, |dock, cx| match event { + dock::Event::ZoomIn => dock.set_zoomed(true, cx), + }) + }), + ] + } + fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { let pane = cx.add_view(|cx| Pane::new(self.weak_handle(), self.app_state.background_actions, cx)); @@ -1699,13 +1728,7 @@ impl Workspace { } pane::Event::ZoomIn => { pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); - self.zoom_in(pane.into_any(), cx); - } - pane::Event::ZoomOut => { - if self.zoomed.as_ref().map_or(false, |zoomed| *zoomed == pane) { - pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); - self.zoom_out(cx); - } + cx.notify(); } } @@ -2757,10 +2780,10 @@ impl View for Workspace { }) .with_child(Overlay::new( Stack::new() - .with_children(self.zoomed.as_ref().map(|zoomed| { + .with_children(self.zoomed(cx).map(|zoomed| { enum ZoomBackground {} - ChildView::new(zoomed, cx) + ChildView::new(&zoomed, cx) .contained() .with_style(theme.workspace.zoomed_foreground) .aligned() From f87ae6032e6eb0d11fcf039b64aa9b6c729000b4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 16 May 2023 18:01:18 +0200 Subject: [PATCH 36/61] Don't rely on action propagation for zooming in and out Co-Authored-By: Antonio Scandurra --- crates/gpui/src/app.rs | 6 ++ crates/project_panel/src/project_panel.rs | 8 ++- crates/terminal_view/src/terminal_panel.rs | 16 +++++- crates/workspace/src/dock.rs | 67 +++++++++++++--------- crates/workspace/src/pane.rs | 6 +- crates/workspace/src/workspace.rs | 55 ++++++++---------- 6 files changed, 94 insertions(+), 64 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 65418dd997..9cc1ad908a 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3972,6 +3972,12 @@ impl Clone for AnyViewHandle { } } +impl PartialEq for AnyViewHandle { + fn eq(&self, other: &Self) -> bool { + self.window_id == other.window_id && self.view_id == other.view_id + } +} + impl PartialEq> for AnyViewHandle { fn eq(&self, other: &ViewHandle) -> bool { self.window_id == other.window_id && self.view_id == other.view_id diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index b1f0740df0..c448ac4e43 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1373,10 +1373,16 @@ impl workspace::dock::Panel for ProjectPanel { cx.global::().project_panel.default_width } - fn can_zoom(&self, _cx: &gpui::WindowContext) -> bool { + fn should_zoom_in_on_event(_: &Self::Event) -> bool { false } + fn should_zoom_out_on_event(_: &Self::Event) -> bool { + false + } + + fn set_zoomed(&mut self, _: bool, _: &mut ViewContext) {} + fn icon_path(&self) -> &'static str { "icons/folder_tree_16.svg" } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index f3bfbdd730..68783a5bc1 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -18,6 +18,8 @@ pub fn init(cx: &mut AppContext) { pub enum Event { Close, DockPositionChanged, + ZoomIn, + ZoomOut, } pub struct TerminalPanel { @@ -96,6 +98,8 @@ impl TerminalPanel { ) { match event { pane::Event::Remove => cx.emit(Event::Close), + pane::Event::ZoomIn => cx.emit(Event::ZoomIn), + pane::Event::ZoomOut => cx.emit(Event::ZoomOut), _ => {} } } @@ -187,8 +191,16 @@ impl Panel for TerminalPanel { } } - fn can_zoom(&self, _: &WindowContext) -> bool { - true + fn should_zoom_in_on_event(event: &Event) -> bool { + matches!(event, Event::ZoomIn) + } + + fn should_zoom_out_on_event(event: &Event) -> bool { + matches!(event, Event::ZoomOut) + } + + fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { + self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx)); } fn icon_path(&self) -> &'static str { diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 2e4303e70f..40e7288de0 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,4 +1,4 @@ -use crate::{StatusItemView, ToggleZoom, Workspace}; +use crate::{StatusItemView, Workspace}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, @@ -9,22 +9,20 @@ use serde::Deserialize; use settings::Settings; use std::rc::Rc; -pub fn init(cx: &mut AppContext) { - cx.add_action(Dock::toggle_zoom); -} - pub trait Panel: View { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); fn default_size(&self, cx: &WindowContext) -> f32; - fn can_zoom(&self, cx: &WindowContext) -> bool; fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; fn icon_label(&self, _: &AppContext) -> Option { None } fn should_change_position_on_event(_: &Self::Event) -> bool; + fn should_zoom_in_on_event(_: &Self::Event) -> bool; + fn should_zoom_out_on_event(_: &Self::Event) -> bool; + fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext); fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; } @@ -34,8 +32,8 @@ pub trait PanelHandle { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; fn set_position(&self, position: DockPosition, cx: &mut WindowContext); + fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext); fn default_size(&self, cx: &WindowContext) -> f32; - fn can_zoom(&self, cx: &WindowContext) -> bool; fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -67,8 +65,8 @@ where self.read(cx).default_size(cx) } - fn can_zoom(&self, cx: &WindowContext) -> bool { - self.read(cx).can_zoom(cx) + fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.set_zoomed(zoomed, cx)) } fn icon_path(&self, cx: &WindowContext) -> &'static str { @@ -98,10 +96,6 @@ impl From<&dyn PanelHandle> for AnyViewHandle { } } -pub enum Event { - ZoomIn, -} - pub struct Dock { position: DockPosition, panel_entries: Vec, @@ -192,22 +186,33 @@ impl Dock { cx.notify(); } - pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { - for (ix, entry) in self.panel_entries.iter_mut().enumerate() { - if ix == self.active_panel_index && entry.panel.can_zoom(cx) { - entry.zoomed = zoomed; - } else { + pub fn set_panel_zoomed( + &mut self, + panel: &AnyViewHandle, + zoomed: bool, + cx: &mut ViewContext, + ) { + for entry in &mut self.panel_entries { + if entry.panel.as_any() == panel { + if zoomed != entry.zoomed { + entry.zoomed = zoomed; + entry.panel.set_zoomed(zoomed, cx); + } + } else if entry.zoomed { entry.zoomed = false; + entry.panel.set_zoomed(false, cx); } } cx.notify(); } - pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { - cx.propagate_action(); - if !self.active_entry().map_or(false, |entry| entry.zoomed) { - cx.emit(Event::ZoomIn); + pub fn zoom_out(&mut self, cx: &mut ViewContext) { + for entry in &mut self.panel_entries { + if entry.zoomed { + entry.zoomed = false; + entry.panel.set_zoomed(false, cx); + } } } @@ -342,7 +347,7 @@ impl Dock { } impl Entity for Dock { - type Event = Event; + type Event = (); } impl View for Dock { @@ -568,6 +573,10 @@ pub(crate) mod test { cx.emit(TestPanelEvent::PositionChanged); } + fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext) { + unimplemented!() + } + fn default_size(&self, _: &WindowContext) -> f32 { match self.position.axis() { Axis::Horizontal => 300., @@ -575,10 +584,6 @@ pub(crate) mod test { } } - fn can_zoom(&self, _cx: &WindowContext) -> bool { - unimplemented!() - } - fn icon_path(&self) -> &'static str { "icons/test_panel.svg" } @@ -591,6 +596,14 @@ pub(crate) mod test { matches!(event, TestPanelEvent::PositionChanged) } + fn should_zoom_in_on_event(_: &Self::Event) -> bool { + false + } + + fn should_zoom_out_on_event(_: &Self::Event) -> bool { + false + } + fn should_activate_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool { matches!(event, TestPanelEvent::Activated) } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 6ccd1573bc..73738ab413 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -135,6 +135,7 @@ pub enum Event { ChangeItemTitle, Focus, ZoomIn, + ZoomOut, } pub struct Pane { @@ -661,8 +662,9 @@ impl Pane { } pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { - cx.propagate_action(); - if !self.zoomed { + if self.zoomed { + cx.emit(Event::ZoomOut); + } else { cx.emit(Event::ZoomIn); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index af8c958db5..06832e1385 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -182,7 +182,6 @@ pub type WorkspaceId = i64; impl_actions!(workspace, [ActivatePane]); pub fn init(app_state: Arc, cx: &mut AppContext) { - dock::init(cx); pane::init(cx); notifications::init(cx); @@ -232,7 +231,6 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { }, ); cx.add_action(Workspace::toggle_panel); - cx.add_action(Workspace::toggle_zoom); cx.add_action(Workspace::focus_center); cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| { workspace.activate_previous_pane(cx) @@ -595,7 +593,7 @@ impl Workspace { active_call = Some((call, subscriptions)); } - let mut subscriptions = vec![ + let subscriptions = vec![ cx.observe_fullscreen(|_, _, cx| cx.notify()), cx.observe_window_activation(Self::on_window_activation_changed), cx.observe_window_bounds(move |_, mut bounds, display, cx| { @@ -615,10 +613,19 @@ impl Workspace { .spawn(DB.set_window_bounds(workspace_id, bounds, display)) .detach_and_log_err(cx); }), + cx.observe(&left_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), + cx.observe(&bottom_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), + cx.observe(&right_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), ]; - subscriptions.extend(Self::register_dock(&left_dock, cx)); - subscriptions.extend(Self::register_dock(&bottom_dock, cx)); - subscriptions.extend(Self::register_dock(&right_dock, cx)); let mut this = Workspace { weak_self: weak_handle.clone(), @@ -881,6 +888,11 @@ impl Workspace { dock.activate_panel(dock.panels_len() - 1, cx); } }); + } else if T::should_zoom_in_on_event(event) { + this.zoom_out(cx); + dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx)); + } else if T::should_zoom_out_on_event(event) { + this.zoom_out(cx); } } }) @@ -1464,23 +1476,14 @@ impl Workspace { cx.notify(); } - fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { - // Any time the zoom is toggled we will zoom out all panes and docks. Then, - // the dock or pane that was zoomed will emit an event to zoom itself back in. - self.zoom_out(cx); - } - fn zoom_out(&mut self, cx: &mut ViewContext) { for pane in &self.panes { pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); } - self.left_dock - .update(cx, |dock, cx| dock.set_zoomed(false, cx)); - self.bottom_dock - .update(cx, |dock, cx| dock.set_zoomed(false, cx)); - self.right_dock - .update(cx, |dock, cx| dock.set_zoomed(false, cx)); + self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx)); + self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx)); + self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx)); cx.notify(); } @@ -1490,20 +1493,6 @@ impl Workspace { cx.notify(); } - fn register_dock(dock: &ViewHandle, cx: &mut ViewContext) -> [Subscription; 2] { - [ - cx.observe(dock, |this, _, cx| { - this.serialize_workspace(cx); - cx.notify(); - }), - cx.subscribe(dock, |_, dock, event, cx| { - dock.update(cx, |dock, cx| match event { - dock::Event::ZoomIn => dock.set_zoomed(true, cx), - }) - }), - ] - } - fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { let pane = cx.add_view(|cx| Pane::new(self.weak_handle(), self.app_state.background_actions, cx)); @@ -1727,9 +1716,11 @@ impl Workspace { self.handle_pane_focused(pane.clone(), cx); } pane::Event::ZoomIn => { + self.zoom_out(cx); pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); cx.notify(); } + pane::Event::ZoomOut => self.zoom_out(cx), } self.serialize_workspace(cx); From 981129ef8ece66868ae17172542e82a837bded5c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 May 2023 15:06:58 +0200 Subject: [PATCH 37/61] Show a panel/pane as zoomed only if it's the active item in workspace --- crates/gpui/src/app.rs | 2 +- crates/project_panel/src/project_panel.rs | 34 ++++- crates/terminal_view/src/terminal_panel.rs | 20 ++- crates/workspace/src/dock.rs | 87 ++++++++----- crates/workspace/src/pane.rs | 8 +- crates/workspace/src/pane_group.rs | 10 +- crates/workspace/src/workspace.rs | 143 ++++++++++++--------- 7 files changed, 195 insertions(+), 109 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 9cc1ad908a..5cb2e8d931 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -4224,7 +4224,7 @@ impl Hash for WeakViewHandle { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct AnyWeakViewHandle { window_id: usize, view_id: usize, diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index c448ac4e43..63263b23c7 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -13,7 +13,7 @@ use gpui::{ keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext, - ViewHandle, WeakViewHandle, + ViewHandle, WeakViewHandle, WindowContext, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; @@ -48,6 +48,7 @@ pub struct ProjectPanel { context_menu: ViewHandle, dragged_entry_destination: Option>, workspace: WeakViewHandle, + has_focus: bool, } #[derive(Copy, Clone)] @@ -139,6 +140,7 @@ pub enum Event { focus_opened_item: bool, }, DockPositionChanged, + Focus, } impl ProjectPanel { @@ -214,6 +216,7 @@ impl ProjectPanel { context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)), dragged_entry_destination: None, workspace: workspace.weak_handle(), + has_focus: false, }; this.update_visible_entries(None, cx); @@ -259,7 +262,7 @@ impl ProjectPanel { } } } - Event::DockPositionChanged => {} + _ => {} } }) .detach(); @@ -1338,6 +1341,17 @@ impl View for ProjectPanel { Self::reset_to_default_keymap_context(keymap); keymap.add_identifier("menu"); } + + fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { + if !self.has_focus { + self.has_focus = true; + cx.emit(Event::Focus); + } + } + + fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext) { + self.has_focus = false; + } } impl Entity for ProjectPanel { @@ -1345,7 +1359,7 @@ impl Entity for ProjectPanel { } impl workspace::dock::Panel for ProjectPanel { - fn position(&self, cx: &gpui::WindowContext) -> DockPosition { + fn position(&self, cx: &WindowContext) -> DockPosition { let settings = cx.global::(); match settings.project_panel.dock { settings::ProjectPanelDockPosition::Left => DockPosition::Left, @@ -1369,7 +1383,7 @@ impl workspace::dock::Panel for ProjectPanel { }) } - fn default_size(&self, cx: &gpui::WindowContext) -> f32 { + fn default_size(&self, cx: &WindowContext) -> f32 { cx.global::().project_panel.default_width } @@ -1395,13 +1409,21 @@ impl workspace::dock::Panel for ProjectPanel { matches!(event, Event::DockPositionChanged) } - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_activate_on_event(_: &Self::Event) -> bool { false } - fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_close_on_event(_: &Self::Event) -> bool { false } + + fn has_focus(&self, _: &WindowContext) -> bool { + self.has_focus + } + + fn is_focus_event(event: &Self::Event) -> bool { + matches!(event, Event::Focus) + } } impl ClipboardEntry { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 68783a5bc1..25302d7df5 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -20,6 +20,7 @@ pub enum Event { DockPositionChanged, ZoomIn, ZoomOut, + Focus, } pub struct TerminalPanel { @@ -100,6 +101,7 @@ impl TerminalPanel { pane::Event::Remove => cx.emit(Event::Close), pane::Event::ZoomIn => cx.emit(Event::ZoomIn), pane::Event::ZoomOut => cx.emit(Event::ZoomOut), + pane::Event::Focus => cx.emit(Event::Focus), _ => {} } } @@ -149,6 +151,10 @@ impl View for TerminalPanel { if self.pane.read(cx).items_len() == 0 { self.add_terminal(&Default::default(), cx) } + + if cx.is_self_focused() { + cx.focus(&self.pane); + } } } @@ -211,7 +217,7 @@ impl Panel for TerminalPanel { "Terminals".to_string() } - fn icon_label(&self, cx: &AppContext) -> Option { + fn icon_label(&self, cx: &WindowContext) -> Option { let count = self.pane.read(cx).items_len(); if count == 0 { None @@ -224,11 +230,19 @@ impl Panel for TerminalPanel { matches!(event, Event::DockPositionChanged) } - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_activate_on_event(_: &Self::Event) -> bool { false } - fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { + fn should_close_on_event(event: &Event) -> bool { matches!(event, Event::Close) } + + fn has_focus(&self, cx: &WindowContext) -> bool { + self.pane.read(cx).has_focus() + } + + fn is_focus_event(event: &Self::Event) -> bool { + matches!(event, Event::Focus) + } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 40e7288de0..edd6a5959f 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,9 +1,8 @@ use crate::{StatusItemView, Workspace}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ - elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, - AppContext, Axis, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, - WindowContext, + elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, Axis, + Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use serde::Deserialize; use settings::Settings; @@ -16,15 +15,17 @@ pub trait Panel: View { fn default_size(&self, cx: &WindowContext) -> f32; fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; - fn icon_label(&self, _: &AppContext) -> Option { + fn icon_label(&self, _: &WindowContext) -> Option { None } fn should_change_position_on_event(_: &Self::Event) -> bool; fn should_zoom_in_on_event(_: &Self::Event) -> bool; fn should_zoom_out_on_event(_: &Self::Event) -> bool; fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext); - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; - fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; + fn should_activate_on_event(_: &Self::Event) -> bool; + fn should_close_on_event(_: &Self::Event) -> bool; + fn has_focus(&self, cx: &WindowContext) -> bool; + fn is_focus_event(_: &Self::Event) -> bool; } pub trait PanelHandle { @@ -37,7 +38,7 @@ pub trait PanelHandle { fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; - fn is_focused(&self, cx: &WindowContext) -> bool; + fn has_focus(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; } @@ -81,8 +82,8 @@ where self.read(cx).icon_label(cx) } - fn is_focused(&self, cx: &WindowContext) -> bool { - ViewHandle::is_focused(self, cx) + fn has_focus(&self, cx: &WindowContext) -> bool { + self.read(cx).has_focus(cx) } fn as_any(&self) -> &AnyViewHandle { @@ -170,6 +171,11 @@ impl Dock { self.is_open } + pub fn has_focus(&self, cx: &WindowContext) -> bool { + self.active_panel() + .map_or(false, |panel| panel.has_focus(cx)) + } + pub fn active_panel_index(&self) -> usize { self.active_panel_index } @@ -220,7 +226,7 @@ impl Dock { let subscriptions = [ cx.observe(&panel, |_, _, cx| cx.notify()), cx.subscribe(&panel, |this, panel, event, cx| { - if panel.read(cx).should_activate_on_event(event, cx) { + if T::should_activate_on_event(event) { if let Some(ix) = this .panel_entries .iter() @@ -230,7 +236,7 @@ impl Dock { this.activate_panel(ix, cx); cx.focus(&panel); } - } else if panel.read(cx).should_close_on_event(event, cx) + } else if T::should_close_on_event(event) && this.active_panel().map_or(false, |p| p.id() == panel.id()) { this.set_open(false, cx); @@ -302,10 +308,10 @@ impl Dock { } } - pub fn zoomed_panel(&self) -> Option { + pub fn zoomed_panel(&self) -> Option> { let entry = self.active_entry()?; if entry.zoomed { - Some(entry.panel.as_any().clone()) + Some(entry.panel.clone()) } else { None } @@ -344,6 +350,24 @@ impl Dock { cx.notify(); } } + + pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement { + if let Some(active_entry) = self.active_entry() { + let style = &cx.global::().theme.workspace.dock; + Empty::new() + .into_any() + .contained() + .with_style(style.container) + .resizable( + self.position.to_resize_handle_side(), + active_entry.size, + |_, _, _| {}, + ) + .into_any() + } else { + Empty::new().into_any() + } + } } impl Entity for Dock { @@ -357,21 +381,16 @@ impl View for Dock { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(active_entry) = self.active_entry() { - if active_entry.zoomed { - Empty::new().into_any() - } else { - let size = self.active_panel_size().unwrap(); - let style = &cx.global::().theme.workspace.dock; - ChildView::new(active_entry.panel.as_any(), cx) - .contained() - .with_style(style.container) - .resizable( - self.position.to_resize_handle_side(), - size, - |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), - ) - .into_any() - } + let style = &cx.global::().theme.workspace.dock; + ChildView::new(active_entry.panel.as_any(), cx) + .contained() + .with_style(style.container) + .resizable( + self.position.to_resize_handle_side(), + active_entry.size, + |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), + ) + .into_any() } else { Empty::new().into_any() } @@ -604,12 +623,20 @@ pub(crate) mod test { false } - fn should_activate_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool { + fn should_activate_on_event(event: &Self::Event) -> bool { matches!(event, TestPanelEvent::Activated) } - fn should_close_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool { + fn should_close_on_event(event: &Self::Event) -> bool { matches!(event, TestPanelEvent::Closed) } + + fn has_focus(&self, _cx: &WindowContext) -> bool { + unimplemented!() + } + + fn is_focus_event(_: &Self::Event) -> bool { + unimplemented!() + } } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 73738ab413..681a7eda27 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1699,7 +1699,11 @@ impl View for Pane { } fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { - self.has_focus = true; + if !self.has_focus { + self.has_focus = true; + cx.emit(Event::Focus); + } + self.toolbar.update(cx, |toolbar, cx| { toolbar.pane_focus_update(true, cx); }); @@ -1725,8 +1729,6 @@ impl View for Pane { .insert(active_item.id(), focused.downgrade()); } } - - cx.emit(Event::Focus); } fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index a1b35c1d65..5f6d46aa46 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -7,7 +7,7 @@ use gpui::{ elements::*, geometry::{rect::RectF, vector::Vector2F}, platform::{CursorStyle, MouseButton}, - Axis, Border, ModelHandle, ViewContext, ViewHandle, + AnyViewHandle, Axis, Border, ModelHandle, ViewContext, ViewHandle, }; use project::Project; use serde::Deserialize; @@ -72,6 +72,7 @@ impl PaneGroup { follower_states: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, active_pane: &ViewHandle, + zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { @@ -81,6 +82,7 @@ impl PaneGroup { follower_states, active_call, active_pane, + zoomed, app_state, cx, ) @@ -135,6 +137,7 @@ impl Member { follower_states: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, active_pane: &ViewHandle, + zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { @@ -142,7 +145,7 @@ impl Member { match self { Member::Pane(pane) => { - let pane_element = if pane.read(cx).is_zoomed() { + let pane_element = if Some(&**pane) == zoomed { Empty::new().into_any() } else { ChildView::new(pane, cx).into_any() @@ -274,6 +277,7 @@ impl Member { follower_states, active_call, active_pane, + zoomed, app_state, cx, ), @@ -378,6 +382,7 @@ impl PaneAxis { follower_state: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, active_pane: &ViewHandle, + zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { @@ -395,6 +400,7 @@ impl PaneAxis { follower_state, active_call, active_pane, + zoomed, app_state, cx, ); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 06832e1385..0fd961a768 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -893,6 +893,8 @@ impl Workspace { dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx)); } else if T::should_zoom_out_on_event(event) { this.zoom_out(cx); + } else if T::is_focus_event(event) { + cx.notify(); } } }) @@ -1309,16 +1311,42 @@ impl Workspace { } } - fn zoomed(&self, cx: &AppContext) -> Option { - self.left_dock - .read(cx) - .zoomed_panel() - .or(self.bottom_dock.read(cx).zoomed_panel()) - .or(self.right_dock.read(cx).zoomed_panel()) - .or_else(|| { - let pane = self.panes.iter().find(|pane| pane.read(cx).is_zoomed())?; - Some(pane.clone().into_any()) - }) + fn zoomed(&self, cx: &WindowContext) -> Option { + self.zoomed_panel_for_dock(DockPosition::Left, cx) + .or_else(|| self.zoomed_panel_for_dock(DockPosition::Bottom, cx)) + .or_else(|| self.zoomed_panel_for_dock(DockPosition::Right, cx)) + .or_else(|| self.zoomed_pane(cx)) + } + + fn zoomed_panel_for_dock( + &self, + position: DockPosition, + cx: &WindowContext, + ) -> Option { + let (dock, other_docks) = match position { + DockPosition::Left => (&self.left_dock, [&self.bottom_dock, &self.right_dock]), + DockPosition::Bottom => (&self.bottom_dock, [&self.left_dock, &self.right_dock]), + DockPosition::Right => (&self.right_dock, [&self.left_dock, &self.bottom_dock]), + }; + + let zoomed_panel = dock.read(&cx).zoomed_panel()?; + if other_docks.iter().all(|dock| !dock.read(cx).has_focus(cx)) + && !self.active_pane.read(cx).has_focus() + { + Some(zoomed_panel.as_any().clone()) + } else { + None + } + } + + fn zoomed_pane(&self, cx: &WindowContext) -> Option { + let active_pane = self.active_pane.read(cx); + let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock]; + if active_pane.is_zoomed() && docks.iter().all(|dock| !dock.read(cx).has_focus(cx)) { + Some(self.active_pane.clone().into_any()) + } else { + None + } } pub fn items<'a>( @@ -1433,7 +1461,7 @@ impl Workspace { }); if let Some(active_item) = active_item { - if active_item.is_focused(cx) { + if active_item.has_focus(cx) { cx.focus_self(); } else { cx.focus(active_item.as_any()); @@ -1464,7 +1492,7 @@ impl Workspace { dock.active_panel().cloned() }); if let Some(active_item) = active_item { - if active_item.is_focused(cx) { + if active_item.has_focus(cx) { cx.focus_self(); } else { cx.focus(active_item.as_any()); @@ -1663,7 +1691,6 @@ impl Workspace { }); self.active_item_path_changed(cx); self.last_active_center_pane = Some(pane.downgrade()); - cx.notify(); } self.update_followers( @@ -1677,6 +1704,8 @@ impl Workspace { }), cx, ); + + cx.notify(); } fn handle_pane_event( @@ -1716,9 +1745,11 @@ impl Workspace { self.handle_pane_focused(pane.clone(), cx); } pane::Event::ZoomIn => { - self.zoom_out(cx); - pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); - cx.notify(); + if pane == self.active_pane { + self.zoom_out(cx); + pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); + cx.notify(); + } } pane::Event::ZoomOut => self.zoom_out(cx), } @@ -2646,6 +2677,33 @@ impl Workspace { }); Self::new(None, 0, project, app_state, cx) } + + fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option> { + let dock = match position { + DockPosition::Left => &self.left_dock, + DockPosition::Right => &self.right_dock, + DockPosition::Bottom => &self.bottom_dock, + }; + let active_panel = dock.read(cx).active_panel()?; + let element = if Some(active_panel.as_any()) == self.zoomed(cx).as_ref() { + dock.read(cx).render_placeholder(cx) + } else { + ChildView::new(dock, cx).into_any() + }; + + Some( + element + .constrained() + .dynamically(move |constraint, _, cx| match position { + DockPosition::Left | DockPosition::Right => SizeConstraint::new( + Vector2F::new(20., constraint.min.y()), + Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()), + ), + _ => constraint, + }) + .into_any(), + ) + } } fn notify_if_database_failed(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { @@ -2702,25 +2760,7 @@ impl View for Workspace { .with_child({ let project = self.project.clone(); Flex::row() - .with_children( - if self.left_dock.read(cx).active_panel().is_some() { - Some( - ChildView::new(&self.left_dock, cx) - .constrained() - .dynamically(|constraint, _, cx| { - SizeConstraint::new( - Vector2F::new(20., constraint.min.y()), - Vector2F::new( - cx.window_size().x() * 0.8, - constraint.max.y(), - ), - ) - }), - ) - } else { - None - }, - ) + .with_children(self.render_dock(DockPosition::Left, cx)) .with_child( Flex::column() .with_child( @@ -2730,44 +2770,18 @@ impl View for Workspace { &self.follower_states_by_leader, self.active_call(), self.active_pane(), + self.zoomed(cx).as_ref(), &self.app_state, cx, )) .flex(1., true), ) .with_children( - if self - .bottom_dock - .read(cx) - .active_panel() - .is_some() - { - Some(ChildView::new(&self.bottom_dock, cx)) - } else { - None - }, + self.render_dock(DockPosition::Bottom, cx), ) .flex(1., true), ) - .with_children( - if self.right_dock.read(cx).active_panel().is_some() { - Some( - ChildView::new(&self.right_dock, cx) - .constrained() - .dynamically(|constraint, _, cx| { - SizeConstraint::new( - Vector2F::new(20., constraint.min.y()), - Vector2F::new( - cx.window_size().x() * 0.8, - constraint.max.y(), - ), - ) - }), - ) - } else { - None - }, - ) + .with_children(self.render_dock(DockPosition::Right, cx)) }) .with_child(Overlay::new( Stack::new() @@ -2810,6 +2824,7 @@ impl View for Workspace { if cx.is_self_focused() { cx.focus(&self.active_pane); } + cx.notify(); } } From 747fbfadeb994e2d52bc42f36cc3baa557d735f0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 May 2023 17:12:12 +0200 Subject: [PATCH 38/61] Notify old/new ancestors of the focused view when they change --- crates/gpui/src/app.rs | 48 ++++++++++++++++++++++++++++++++--- crates/gpui/src/app/window.rs | 7 ++--- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 5cb2e8d931..26963fa2dd 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1694,12 +1694,54 @@ impl AppContext { if let Some(invalidation) = invalidation { let appearance = cx.window.platform_window.appearance(); cx.invalidate(invalidation, appearance); - if cx.layout(refreshing).log_err().is_some() { + if let Some(old_parents) = cx.layout(refreshing).log_err() { updated_windows.insert(window_id); - // When the previously-focused view isn't rendered and - // there isn't any pending focus, focus the root view. if let Some(focused_view_id) = cx.focused_view_id() { + let old_ancestors = std::iter::successors( + Some(focused_view_id), + |&view_id| old_parents.get(&view_id).copied(), + ) + .collect::>(); + let new_ancestors = + cx.ancestors(focused_view_id).collect::>(); + + // Notify the old ancestors of the focused view when they don't contain it anymore. + for old_ancestor in old_ancestors.iter().copied() { + if !new_ancestors.contains(&old_ancestor) { + if let Some(mut view) = + cx.views.remove(&(window_id, old_ancestor)) + { + view.focus_out( + focused_view_id, + cx, + old_ancestor, + ); + cx.views + .insert((window_id, old_ancestor), view); + } + } + } + + // Notify the new ancestors of the focused view if they contain it now. + for new_ancestor in new_ancestors.iter().copied() { + if !old_ancestors.contains(&new_ancestor) { + if let Some(mut view) = + cx.views.remove(&(window_id, new_ancestor)) + { + view.focus_in( + focused_view_id, + cx, + new_ancestor, + ); + cx.views + .insert((window_id, new_ancestor), view); + } + } + } + + // When the previously-focused view isn't rendered and + // there isn't any pending focus, focus the root view. let root_view_id = cx.window.root_view().id(); if focused_view_id != root_view_id && !cx.window.parents.contains_key(&focused_view_id) diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index bd9bd6d2db..24a3686563 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -29,6 +29,7 @@ use sqlez::{ }; use std::{ any::TypeId, + mem, ops::{Deref, DerefMut, Range}, }; use util::ResultExt; @@ -890,7 +891,7 @@ impl<'a> WindowContext<'a> { Ok(element) } - pub(crate) fn layout(&mut self, refreshing: bool) -> Result<()> { + pub(crate) fn layout(&mut self, refreshing: bool) -> Result> { let window_size = self.window.platform_window.content_size(); let root_view_id = self.window.root_view().id(); let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap(); @@ -923,11 +924,11 @@ impl<'a> WindowContext<'a> { } } - self.window.parents = new_parents; + let old_parents = mem::replace(&mut self.window.parents, new_parents); self.window .rendered_views .insert(root_view_id, rendered_root); - Ok(()) + Ok(old_parents) } pub(crate) fn paint(&mut self) -> Result { From f09744454699e0160d32c8a40ccca61e878480ea Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 May 2023 17:35:10 +0200 Subject: [PATCH 39/61] Rebind `ctrl-`` to toggle terminal panel focus Also, add `ctrl-~` to create new terminals. Co-Authored-By: Mikayla Maki --- assets/keymaps/default.json | 3 +- crates/terminal_view/src/terminal_panel.rs | 6 ++- crates/workspace/src/dock.rs | 6 +++ crates/workspace/src/workspace.rs | 45 ++++++++++------------ crates/zed/src/zed.rs | 16 +++++--- 5 files changed, 42 insertions(+), 34 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 37c010f0f9..246b699558 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -39,7 +39,8 @@ "cmd-shift-n": "workspace::NewWindow", "cmd-o": "workspace::Open", "alt-cmd-o": "projects::OpenRecent", - "ctrl-`": "workspace::NewTerminal" + "ctrl-~": "workspace::NewTerminal", + "ctrl-`": "terminal_panel::ToggleFocus" } }, { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 25302d7df5..4e90121d59 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1,7 +1,7 @@ use crate::TerminalView; use gpui::{ - elements::*, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle, - WeakViewHandle, WindowContext, + actions, elements::*, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, + ViewHandle, WeakViewHandle, WindowContext, }; use project::Project; use settings::{settings_file::SettingsFile, Settings, TerminalDockPosition, WorkingDirectory}; @@ -11,6 +11,8 @@ use workspace::{ pane, DraggedItem, Pane, Workspace, }; +actions!(terminal_panel, [ToggleFocus]); + pub fn init(cx: &mut AppContext) { cx.add_action(TerminalPanel::add_terminal); } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index edd6a5959f..a387f05e65 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -176,6 +176,12 @@ impl Dock { .map_or(false, |panel| panel.has_focus(cx)) } + pub fn panel_index(&self) -> Option { + self.panel_entries + .iter() + .position(|entry| entry.panel.as_any().is::()) + } + pub fn active_panel_index(&self) -> usize { self.active_panel_index } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 0fd961a768..278ea9d879 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -116,6 +116,7 @@ actions!( FollowNextCollaborator, ToggleLeftDock, NewTerminal, + ToggleTerminalFocus, NewSearch, Feedback, Restart, @@ -1475,33 +1476,27 @@ impl Workspace { cx.notify(); } - pub fn toggle_panel_focus( - &mut self, - dock_position: DockPosition, - panel_index: usize, - cx: &mut ViewContext, - ) { - let dock = match dock_position { - DockPosition::Left => &mut self.left_dock, - DockPosition::Bottom => &mut self.bottom_dock, - DockPosition::Right => &mut self.right_dock, - }; - let active_item = dock.update(cx, |dock, cx| { - dock.set_open(true, cx); - dock.activate_panel(panel_index, cx); - dock.active_panel().cloned() - }); - if let Some(active_item) = active_item { - if active_item.has_focus(cx) { - cx.focus_self(); - } else { - cx.focus(active_item.as_any()); + pub fn toggle_panel_focus(&mut self, cx: &mut ViewContext) { + for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { + if let Some(panel_index) = dock.read(cx).panel_index::() { + let active_item = dock.update(cx, |dock, cx| { + dock.set_open(true, cx); + dock.activate_panel(panel_index, cx); + dock.active_panel().cloned() + }); + if let Some(active_item) = active_item { + if active_item.has_focus(cx) { + cx.focus_self(); + } else { + cx.focus(active_item.as_any()); + } + } + + self.serialize_workspace(cx); + cx.notify(); + break; } } - - self.serialize_workspace(cx); - - cx.notify(); } fn zoom_out(&mut self, cx: &mut ViewContext) { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 8a40a5d1d3..fb7da693fc 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -31,14 +31,11 @@ use serde::Deserialize; use serde_json::to_string_pretty; use settings::{Settings, DEFAULT_SETTINGS_ASSET_PATH}; use std::{borrow::Cow, str, sync::Arc}; -use terminal_view::terminal_panel::TerminalPanel; +use terminal_view::terminal_panel::{self, TerminalPanel}; use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; pub use workspace; -use workspace::{ - create_and_open_local_file, dock::DockPosition, open_new, AppState, NewFile, NewWindow, - Workspace, -}; +use workspace::{create_and_open_local_file, open_new, AppState, NewFile, NewWindow, Workspace}; #[derive(Deserialize, Clone, PartialEq)] pub struct OpenBrowser { @@ -242,7 +239,14 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { |workspace: &mut Workspace, _: &project_panel::ToggleFocus, cx: &mut ViewContext| { - workspace.toggle_panel_focus(DockPosition::Left, 0, cx); + workspace.toggle_panel_focus::(cx); + }, + ); + cx.add_action( + |workspace: &mut Workspace, + _: &terminal_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace.toggle_panel_focus::(cx); }, ); cx.add_global_action({ From 05fb0519241490856d62d6ce783dde88b44975ee Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 May 2023 17:51:11 +0200 Subject: [PATCH 40/61] Store whether a panel is zoomed in the panel itself Co-Authored-By: Mikayla Maki --- crates/project_panel/src/project_panel.rs | 4 ++++ crates/terminal_view/src/terminal_panel.rs | 4 ++++ crates/workspace/src/dock.rs | 25 +++++++++++++--------- crates/workspace/src/workspace.rs | 2 +- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 63263b23c7..a89d544074 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1395,6 +1395,10 @@ impl workspace::dock::Panel for ProjectPanel { false } + fn is_zoomed(&self, _: &WindowContext) -> bool { + false + } + fn set_zoomed(&mut self, _: bool, _: &mut ViewContext) {} fn icon_path(&self) -> &'static str { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 4e90121d59..4926a8775e 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -207,6 +207,10 @@ impl Panel for TerminalPanel { matches!(event, Event::ZoomOut) } + fn is_zoomed(&self, cx: &WindowContext) -> bool { + self.pane.read(cx).is_zoomed() + } + fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx)); } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index a387f05e65..5c501f64ad 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -21,6 +21,7 @@ pub trait Panel: View { fn should_change_position_on_event(_: &Self::Event) -> bool; fn should_zoom_in_on_event(_: &Self::Event) -> bool; fn should_zoom_out_on_event(_: &Self::Event) -> bool; + fn is_zoomed(&self, cx: &WindowContext) -> bool; fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext); fn should_activate_on_event(_: &Self::Event) -> bool; fn should_close_on_event(_: &Self::Event) -> bool; @@ -33,6 +34,7 @@ pub trait PanelHandle { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; fn set_position(&self, position: DockPosition, cx: &mut WindowContext); + fn is_zoomed(&self, cx: &WindowContext) -> bool; fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext); fn default_size(&self, cx: &WindowContext) -> f32; fn icon_path(&self, cx: &WindowContext) -> &'static str; @@ -66,6 +68,10 @@ where self.read(cx).default_size(cx) } + fn is_zoomed(&self, cx: &WindowContext) -> bool { + self.read(cx).is_zoomed(cx) + } + fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext) { self.update(cx, |this, cx| this.set_zoomed(zoomed, cx)) } @@ -140,7 +146,6 @@ struct PanelEntry { panel: Rc, size: f32, context_menu: ViewHandle, - zoomed: bool, _subscriptions: [Subscription; 2], } @@ -206,12 +211,10 @@ impl Dock { ) { for entry in &mut self.panel_entries { if entry.panel.as_any() == panel { - if zoomed != entry.zoomed { - entry.zoomed = zoomed; + if zoomed != entry.panel.is_zoomed(cx) { entry.panel.set_zoomed(zoomed, cx); } - } else if entry.zoomed { - entry.zoomed = false; + } else if entry.panel.is_zoomed(cx) { entry.panel.set_zoomed(false, cx); } } @@ -221,8 +224,7 @@ impl Dock { pub fn zoom_out(&mut self, cx: &mut ViewContext) { for entry in &mut self.panel_entries { - if entry.zoomed { - entry.zoomed = false; + if entry.panel.is_zoomed(cx) { entry.panel.set_zoomed(false, cx); } } @@ -255,7 +257,6 @@ impl Dock { self.panel_entries.push(PanelEntry { panel: Rc::new(panel), size, - zoomed: false, context_menu: cx.add_view(|cx| { let mut menu = ContextMenu::new(dock_view_id, cx); menu.set_position_mode(OverlayPositionMode::Local); @@ -314,9 +315,9 @@ impl Dock { } } - pub fn zoomed_panel(&self) -> Option> { + pub fn zoomed_panel(&self, cx: &WindowContext) -> Option> { let entry = self.active_entry()?; - if entry.zoomed { + if entry.panel.is_zoomed(cx) { Some(entry.panel.clone()) } else { None @@ -598,6 +599,10 @@ pub(crate) mod test { cx.emit(TestPanelEvent::PositionChanged); } + fn is_zoomed(&self, _: &WindowContext) -> bool { + unimplemented!() + } + fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext) { unimplemented!() } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 278ea9d879..e9fc2866aa 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1330,7 +1330,7 @@ impl Workspace { DockPosition::Right => (&self.right_dock, [&self.left_dock, &self.bottom_dock]), }; - let zoomed_panel = dock.read(&cx).zoomed_panel()?; + let zoomed_panel = dock.read(&cx).zoomed_panel(cx)?; if other_docks.iter().all(|dock| !dock.read(cx).has_focus(cx)) && !self.active_pane.read(cx).has_focus() { From 489841761704c4b3c5976c8eadc9730e405f4d8d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 May 2023 18:21:35 +0200 Subject: [PATCH 41/61] Drop foreign key constraint from workspaces.dock_pane to panes table Co-Authored-By: Nathan Sobo Co-Authored-By: Mikayla Maki --- crates/workspace/src/persistence.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 2ea99581d2..3621a4e0d9 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -130,6 +130,27 @@ define_connection! { ALTER TABLE workspaces ADD COLUMN window_width REAL; ALTER TABLE workspaces ADD COLUMN window_height REAL; ALTER TABLE workspaces ADD COLUMN display BLOB; + ), + // Drop foreign key constraint from workspaces.dock_pane to panes table. + sql!( + CREATE TABLE workspaces_2( + workspace_id INTEGER PRIMARY KEY, + workspace_location BLOB UNIQUE, + 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, + window_state TEXT, + window_x REAL, + window_y REAL, + window_width REAL, + window_height REAL, + display BLOB + ) STRICT; + INSERT INTO workspaces_2 SELECT * FROM workspaces; + DROP TABLE workspaces; + ALTER TABLE workspaces_2 RENAME TO workspaces; )]; } From 89d8bb1425be55627b038b05a7357946d3ddc9d5 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 17 May 2023 17:34:20 -0700 Subject: [PATCH 42/61] WIP: Add persistence to new docks --- crates/gpui/src/platform.rs | 8 +-- crates/sqlez/src/bindable.rs | 2 +- crates/sqlez/src/statement.rs | 6 +- crates/sqlez/src/typed_statements.rs | 6 +- crates/workspace/src/persistence.rs | 52 +++++++++++++++-- crates/workspace/src/persistence/model.rs | 65 +++++++++++++++++++++- crates/workspace/src/workspace.rs | 68 +++++++++++++++++++++-- 7 files changed, 181 insertions(+), 26 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index ec301939f7..8fbcda02a3 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -222,21 +222,21 @@ impl Bind for WindowBounds { fn bind(&self, statement: &Statement, start_index: i32) -> Result { let (region, next_index) = match self { WindowBounds::Fullscreen => { - let next_index = statement.bind("Fullscreen", start_index)?; + let next_index = statement.bind(&"Fullscreen", start_index)?; (None, next_index) } WindowBounds::Maximized => { - let next_index = statement.bind("Maximized", start_index)?; + let next_index = statement.bind(&"Maximized", start_index)?; (None, next_index) } WindowBounds::Fixed(region) => { - let next_index = statement.bind("Fixed", start_index)?; + let next_index = statement.bind(&"Fixed", start_index)?; (Some(*region), next_index) } }; statement.bind( - region.map(|region| { + ®ion.map(|region| { ( region.min_x(), region.min_y(), diff --git a/crates/sqlez/src/bindable.rs b/crates/sqlez/src/bindable.rs index 86d69afe5f..4c874c4585 100644 --- a/crates/sqlez/src/bindable.rs +++ b/crates/sqlez/src/bindable.rs @@ -27,7 +27,7 @@ impl StaticColumnCount for bool {} impl Bind for bool { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement - .bind(self.then_some(1).unwrap_or(0), start_index) + .bind(&self.then_some(1).unwrap_or(0), start_index) .with_context(|| format!("Failed to bind bool at index {start_index}")) } } diff --git a/crates/sqlez/src/statement.rs b/crates/sqlez/src/statement.rs index 69d5685ba0..de0ad626a5 100644 --- a/crates/sqlez/src/statement.rs +++ b/crates/sqlez/src/statement.rs @@ -236,7 +236,7 @@ impl<'a> Statement<'a> { Ok(str::from_utf8(slice)?) } - pub fn bind(&self, value: T, index: i32) -> Result { + pub fn bind(&self, value: &T, index: i32) -> Result { debug_assert!(index > 0); Ok(value.bind(self, index)?) } @@ -258,7 +258,7 @@ impl<'a> Statement<'a> { } } - pub fn with_bindings(&mut self, bindings: impl Bind) -> Result<&mut Self> { + pub fn with_bindings(&mut self, bindings: &impl Bind) -> Result<&mut Self> { self.bind(bindings, 1)?; Ok(self) } @@ -464,7 +464,7 @@ mod test { connection .exec(indoc! {" CREATE TABLE texts ( - text TEXT + text TEXT )"}) .unwrap()() .unwrap(); diff --git a/crates/sqlez/src/typed_statements.rs b/crates/sqlez/src/typed_statements.rs index 488ee27c0c..d7f25cde51 100644 --- a/crates/sqlez/src/typed_statements.rs +++ b/crates/sqlez/src/typed_statements.rs @@ -29,7 +29,7 @@ impl Connection { query: &str, ) -> Result Result<()>> { let mut statement = Statement::prepare(self, query)?; - Ok(move |bindings| statement.with_bindings(bindings)?.exec()) + Ok(move |bindings| statement.with_bindings(&bindings)?.exec()) } /// Prepare a statement which has no bindings and returns a `Vec`. @@ -55,7 +55,7 @@ impl Connection { query: &str, ) -> Result Result>> { let mut statement = Statement::prepare(self, query)?; - Ok(move |bindings| statement.with_bindings(bindings)?.rows::()) + Ok(move |bindings| statement.with_bindings(&bindings)?.rows::()) } /// Prepare a statement that selects a single row from the database. @@ -87,7 +87,7 @@ impl Connection { let mut statement = Statement::prepare(self, query)?; Ok(move |bindings| { statement - .with_bindings(bindings) + .with_bindings(&bindings) .context("Bindings failed")? .maybe_row::() .context("Maybe row failed") diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 3621a4e0d9..f5d8c56a46 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -18,6 +18,9 @@ use model::{ WorkspaceLocation, }; +use self::model::DockStructure; + + define_connection! { // Current schema shape using pseudo-rust syntax: // @@ -151,6 +154,15 @@ define_connection! { INSERT INTO workspaces_2 SELECT * FROM workspaces; DROP TABLE workspaces; ALTER TABLE workspaces_2 RENAME TO workspaces; + ), + // Add panels related information + sql!( + ALTER TABLE workspaces ADD COLUMN left_dock_visible INTEGER; //bool + ALTER TABLE workspaces ADD COLUMN left_dock_size REAL; + ALTER TABLE workspaces ADD COLUMN right_dock_visible INTEGER; //bool + ALTER TABLE workspaces ADD COLUMN right_dock_size REAL; + ALTER TABLE workspaces ADD COLUMN bottom_dock_visible INTEGER; //bool + ALTER TABLE workspaces ADD COLUMN bottom_dock_size REAL; )]; } @@ -166,12 +178,13 @@ 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, bounds, display): ( + let (workspace_id, workspace_location, left_sidebar_open, bounds, display, docks): ( WorkspaceId, WorkspaceLocation, bool, Option, Option, + DockStructure ) = self .select_row_bound(sql! { SELECT @@ -183,7 +196,13 @@ impl WorkspaceDb { window_y, window_width, window_height, - display + display, + left_dock_visible, + left_dock_size, + right_dock_visible, + right_dock_size, + bottom_dock_visible, + bottom_dock_size FROM workspaces WHERE workspace_location = ? }) @@ -202,6 +221,7 @@ impl WorkspaceDb { left_sidebar_open, bounds, display, + docks }) } @@ -227,15 +247,27 @@ impl WorkspaceDb { workspace_id, workspace_location, left_sidebar_open, + left_dock_visible, + left_dock_size, + right_dock_visible, + right_dock_size, + bottom_dock_visible, + bottom_dock_size, timestamp ) - VALUES (?1, ?2, ?3, CURRENT_TIMESTAMP) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, CURRENT_TIMESTAMP) ON CONFLICT DO UPDATE SET workspace_location = ?2, left_sidebar_open = ?3, + left_dock_visible = ?4, + left_dock_size = ?5, + right_dock_visible = ?6, + right_dock_size = ?7, + bottom_dock_visible = ?8, + bottom_dock_size = ?9, timestamp = CURRENT_TIMESTAMP - ))?((workspace.id, &workspace.location)) + ))?((workspace.id, &workspace.location, workspace.left_sidebar_open, workspace.docks)) .context("Updating workspace")?; // Save center pane group @@ -549,19 +581,22 @@ mod tests { let mut workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), - center_group: Default::default(), left_sidebar_open: true, + center_group: Default::default(), bounds: Default::default(), display: Default::default(), + docks: Default::default() }; let mut _workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), - center_group: Default::default(), left_sidebar_open: false, + center_group: Default::default(), bounds: Default::default(), display: Default::default(), + docks: Default::default() + }; db.save_workspace(workspace_1.clone()).await; @@ -659,6 +694,7 @@ mod tests { left_sidebar_open: true, bounds: Default::default(), display: Default::default(), + docks: Default::default() }; db.save_workspace(workspace.clone()).await; @@ -687,6 +723,7 @@ mod tests { left_sidebar_open: true, bounds: Default::default(), display: Default::default(), + docks: Default::default() }; let mut workspace_2 = SerializedWorkspace { @@ -696,6 +733,7 @@ mod tests { left_sidebar_open: false, bounds: Default::default(), display: Default::default(), + docks: Default::default() }; db.save_workspace(workspace_1.clone()).await; @@ -732,6 +770,7 @@ mod tests { left_sidebar_open: false, bounds: Default::default(), display: Default::default(), + docks: Default::default() }; db.save_workspace(workspace_3.clone()).await; @@ -765,6 +804,7 @@ mod tests { left_sidebar_open: true, bounds: Default::default(), display: Default::default(), + docks: Default::default() } } diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index 19da782fb6..9d997fcfad 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -63,6 +63,65 @@ pub struct SerializedWorkspace { pub left_sidebar_open: bool, pub bounds: Option, pub display: Option, + pub docks: DockStructure, +} + +#[derive(Debug, PartialEq, Clone, Default)] +pub struct DockStructure { + pub(crate) left: DockData, + pub(crate) right: DockData, + pub(crate) bottom: DockData, +} + +impl Column for DockStructure { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (left, next_index) = DockData::column(statement, start_index)?; + let (right, next_index) = DockData::column(statement, next_index)?; + let (bottom, next_index) = DockData::column(statement, next_index)?; + Ok(( + DockStructure { + left, + right, + bottom, + }, + next_index, + )) + } +} + +impl Bind for DockStructure { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + let next_index = statement.bind(&self.left, start_index)?; + let next_index = statement.bind(&self.right, next_index)?; + statement.bind(&self.bottom, next_index) + } +} + +#[derive(Debug, PartialEq, Clone, Default)] +pub struct DockData { + pub(crate) visible: bool, + pub(crate) size: Option, +} + +impl Column for DockData { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (visible, next_index) = Option::::column(statement, start_index)?; + let (size, next_index) = Option::::column(statement, next_index)?; + Ok(( + DockData { + visible: visible.unwrap_or(false), + size, + }, + next_index, + )) + } +} + +impl Bind for DockData { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + let next_index = statement.bind(&self.visible, start_index)?; + statement.bind(&self.size, next_index) + } } #[derive(Debug, PartialEq, Eq, Clone)] @@ -251,9 +310,9 @@ impl StaticColumnCount for SerializedItem { } impl Bind for &SerializedItem { fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let next_index = statement.bind(self.kind.clone(), start_index)?; - let next_index = statement.bind(self.item_id, next_index)?; - statement.bind(self.active, next_index) + let next_index = statement.bind(&self.kind, start_index)?; + let next_index = statement.bind(&self.item_id, next_index)?; + statement.bind(&self.active, next_index) } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e9fc2866aa..6cb9f66edb 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -58,7 +58,9 @@ use std::{ use crate::{ notifications::simple_message_notification::MessageNotification, - persistence::model::{SerializedPane, SerializedPaneGroup, SerializedWorkspace}, + persistence::model::{ + DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace, + }, }; use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle, TogglePanel}; use lazy_static::lazy_static; @@ -2575,12 +2577,51 @@ impl Workspace { } } + fn build_serialized_docks(this: &Workspace, cx: &AppContext) -> DockStructure { + let left_dock = this.left_dock.read(cx); + let left_visible = left_dock.is_open(); + let left_size = left_dock + .active_panel() + .map(|panel| left_dock.panel_size(panel.as_ref())) + .flatten(); + + let right_dock = this.right_dock.read(cx); + let right_visible = right_dock.is_open(); + let right_size = right_dock + .active_panel() + .map(|panel| right_dock.panel_size(panel.as_ref())) + .flatten(); + + let bottom_dock = this.bottom_dock.read(cx); + let bottom_visible = bottom_dock.is_open(); + let bottom_size = bottom_dock + .active_panel() + .map(|panel| bottom_dock.panel_size(panel.as_ref())) + .flatten(); + + DockStructure { + left: DockData { + visible: left_visible, + size: left_size, + }, + right: DockData { + visible: right_visible, + size: right_size, + }, + bottom: DockData { + visible: bottom_visible, + size: bottom_size, + }, + } + } + if let Some(location) = self.location(cx) { // Load bearing special case: // - with_local_workspace() relies on this to not have other stuff open // when you open your log if !location.paths().is_empty() { let center_group = build_serialized_pane_group(&self.center.root, cx); + let docks = build_serialized_docks(self, cx); let serialized_workspace = SerializedWorkspace { id: self.database_id, @@ -2589,6 +2630,7 @@ impl Workspace { left_sidebar_open: self.left_dock.read(cx).is_open(), bounds: Default::default(), display: Default::default(), + docks, }; cx.background() @@ -2642,11 +2684,25 @@ impl Workspace { } } - if workspace.left_dock().read(cx).is_open() - != serialized_workspace.left_sidebar_open - { - workspace.toggle_dock(DockPosition::Left, cx); - } + let docks = serialized_workspace.docks; + workspace.left_dock.update(cx, |dock, cx| { + dock.set_open(docks.left.visible, cx); + if let Some(size) = docks.left.size { + dock.resize_active_panel(size, cx); + } + }); + workspace.right_dock.update(cx, |dock, cx| { + dock.set_open(docks.right.visible, cx); + if let Some(size) = docks.right.size { + dock.resize_active_panel(size, cx); + } + }); + workspace.bottom_dock.update(cx, |dock, cx| { + dock.set_open(docks.bottom.visible, cx); + if let Some(size) = docks.bottom.size { + dock.resize_active_panel(size, cx); + } + }); cx.notify(); })?; From f2ad17dbc0392c8b49c0054bd281da360feeccd2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 May 2023 15:35:46 +0200 Subject: [PATCH 43/61] WIP --- crates/workspace/src/persistence.rs | 68 +++++++++-------------- crates/workspace/src/persistence/model.rs | 9 ++- crates/workspace/src/workspace.rs | 52 +++++++++-------- 3 files changed, 56 insertions(+), 73 deletions(-) diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index f5d8c56a46..9cf13af681 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -20,7 +20,6 @@ use model::{ use self::model::DockStructure; - define_connection! { // Current schema shape using pseudo-rust syntax: // @@ -158,11 +157,11 @@ define_connection! { // Add panels related information sql!( ALTER TABLE workspaces ADD COLUMN left_dock_visible INTEGER; //bool - ALTER TABLE workspaces ADD COLUMN left_dock_size REAL; + ALTER TABLE workspaces ADD COLUMN left_dock_active_panel TEXT; ALTER TABLE workspaces ADD COLUMN right_dock_visible INTEGER; //bool - ALTER TABLE workspaces ADD COLUMN right_dock_size REAL; + ALTER TABLE workspaces ADD COLUMN right_dock_active_panel TEXT; ALTER TABLE workspaces ADD COLUMN bottom_dock_visible INTEGER; //bool - ALTER TABLE workspaces ADD COLUMN bottom_dock_size REAL; + ALTER TABLE workspaces ADD COLUMN bottom_dock_active_panel TEXT; )]; } @@ -178,19 +177,17 @@ 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, bounds, display, docks): ( + let (workspace_id, workspace_location, bounds, display, docks): ( WorkspaceId, WorkspaceLocation, - bool, Option, Option, - DockStructure + DockStructure, ) = self .select_row_bound(sql! { SELECT workspace_id, workspace_location, - left_sidebar_open, window_state, window_x, window_y, @@ -198,11 +195,11 @@ impl WorkspaceDb { window_height, display, left_dock_visible, - left_dock_size, + left_dock_active_panel, right_dock_visible, - right_dock_size, + right_dock_active_panel, bottom_dock_visible, - bottom_dock_size + bottom_dock_active_panel FROM workspaces WHERE workspace_location = ? }) @@ -218,10 +215,9 @@ impl WorkspaceDb { .get_center_pane_group(workspace_id) .context("Getting center group") .log_err()?, - left_sidebar_open, bounds, display, - docks + docks, }) } @@ -246,28 +242,26 @@ impl WorkspaceDb { INSERT INTO workspaces( workspace_id, workspace_location, - left_sidebar_open, left_dock_visible, - left_dock_size, + left_dock_active_panel, right_dock_visible, - right_dock_size, + right_dock_active_panel, bottom_dock_visible, - bottom_dock_size, + bottom_dock_active_panel, timestamp ) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, CURRENT_TIMESTAMP) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, CURRENT_TIMESTAMP) ON CONFLICT DO UPDATE SET workspace_location = ?2, - left_sidebar_open = ?3, - left_dock_visible = ?4, - left_dock_size = ?5, - right_dock_visible = ?6, - right_dock_size = ?7, - bottom_dock_visible = ?8, - bottom_dock_size = ?9, + left_dock_visible = ?3, + left_dock_active_panel = ?4, + right_dock_visible = ?5, + right_dock_active_panel = ?6, + bottom_dock_visible = ?7, + bottom_dock_active_panel = ?8, timestamp = CURRENT_TIMESTAMP - ))?((workspace.id, &workspace.location, workspace.left_sidebar_open, workspace.docks)) + ))?((workspace.id, &workspace.location, workspace.docks)) .context("Updating workspace")?; // Save center pane group @@ -581,22 +575,19 @@ mod tests { let mut workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), - left_sidebar_open: true, center_group: Default::default(), bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), }; let mut _workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), - left_sidebar_open: false, center_group: Default::default(), bounds: Default::default(), display: Default::default(), - docks: Default::default() - + docks: Default::default(), }; db.save_workspace(workspace_1.clone()).await; @@ -691,10 +682,9 @@ mod tests { id: 5, location: (["/tmp", "/tmp2"]).into(), center_group, - left_sidebar_open: true, bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), }; db.save_workspace(workspace.clone()).await; @@ -720,20 +710,18 @@ mod tests { id: 1, location: (["/tmp", "/tmp2"]).into(), center_group: Default::default(), - left_sidebar_open: true, bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), }; let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), center_group: Default::default(), - left_sidebar_open: false, bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), }; db.save_workspace(workspace_1.clone()).await; @@ -767,10 +755,9 @@ mod tests { id: 3, location: (&["/tmp", "/tmp2"]).into(), center_group: Default::default(), - left_sidebar_open: false, bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), }; db.save_workspace(workspace_3.clone()).await; @@ -801,10 +788,9 @@ mod tests { id: 4, location: workspace_id.into(), center_group: center_group.clone(), - left_sidebar_open: true, bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), } } diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index 9d997fcfad..bc10c931bb 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -60,7 +60,6 @@ pub struct SerializedWorkspace { pub id: WorkspaceId, pub location: WorkspaceLocation, pub center_group: SerializedPaneGroup, - pub left_sidebar_open: bool, pub bounds: Option, pub display: Option, pub docks: DockStructure, @@ -100,17 +99,17 @@ impl Bind for DockStructure { #[derive(Debug, PartialEq, Clone, Default)] pub struct DockData { pub(crate) visible: bool, - pub(crate) size: Option, + pub(crate) active_panel: Option, } impl Column for DockData { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { let (visible, next_index) = Option::::column(statement, start_index)?; - let (size, next_index) = Option::::column(statement, next_index)?; + let (active_panel, next_index) = Option::::column(statement, next_index)?; Ok(( DockData { visible: visible.unwrap_or(false), - size, + active_panel, }, next_index, )) @@ -120,7 +119,7 @@ impl Column for DockData { impl Bind for DockData { fn bind(&self, statement: &Statement, start_index: i32) -> Result { let next_index = statement.bind(&self.visible, start_index)?; - statement.bind(&self.size, next_index) + statement.bind(&self.active_panel, next_index) } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6cb9f66edb..30a2a2f4e3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2580,39 +2580,45 @@ impl Workspace { fn build_serialized_docks(this: &Workspace, cx: &AppContext) -> DockStructure { let left_dock = this.left_dock.read(cx); let left_visible = left_dock.is_open(); - let left_size = left_dock - .active_panel() - .map(|panel| left_dock.panel_size(panel.as_ref())) - .flatten(); + let left_active_panel = left_dock.active_panel().and_then(|panel| { + Some( + cx.view_ui_name(panel.as_any().window_id(), panel.id())? + .to_string(), + ) + }); let right_dock = this.right_dock.read(cx); let right_visible = right_dock.is_open(); - let right_size = right_dock - .active_panel() - .map(|panel| right_dock.panel_size(panel.as_ref())) - .flatten(); + let right_active_panel = right_dock.active_panel().and_then(|panel| { + Some( + cx.view_ui_name(panel.as_any().window_id(), panel.id())? + .to_string(), + ) + }); let bottom_dock = this.bottom_dock.read(cx); let bottom_visible = bottom_dock.is_open(); - let bottom_size = bottom_dock - .active_panel() - .map(|panel| bottom_dock.panel_size(panel.as_ref())) - .flatten(); + let bottom_active_panel = bottom_dock.active_panel().and_then(|panel| { + Some( + cx.view_ui_name(panel.as_any().window_id(), panel.id())? + .to_string(), + ) + }); - DockStructure { + dbg!(DockStructure { left: DockData { visible: left_visible, - size: left_size, + active_panel: left_active_panel, }, right: DockData { visible: right_visible, - size: right_size, + active_panel: right_active_panel, }, bottom: DockData { visible: bottom_visible, - size: bottom_size, + active_panel: bottom_active_panel, }, - } + }) } if let Some(location) = self.location(cx) { @@ -2627,7 +2633,6 @@ impl Workspace { id: self.database_id, location, center_group, - left_sidebar_open: self.left_dock.read(cx).is_open(), bounds: Default::default(), display: Default::default(), docks, @@ -2686,22 +2691,15 @@ impl Workspace { let docks = serialized_workspace.docks; workspace.left_dock.update(cx, |dock, cx| { + dbg!(docks.left.visible); dock.set_open(docks.left.visible, cx); - if let Some(size) = docks.left.size { - dock.resize_active_panel(size, cx); - } + dbg!(dock.is_open()); }); workspace.right_dock.update(cx, |dock, cx| { dock.set_open(docks.right.visible, cx); - if let Some(size) = docks.right.size { - dock.resize_active_panel(size, cx); - } }); workspace.bottom_dock.update(cx, |dock, cx| { dock.set_open(docks.bottom.visible, cx); - if let Some(size) = docks.bottom.size { - dock.resize_active_panel(size, cx); - } }); cx.notify(); From 3d6b7283647ec2444b6d7fc647e4e9400fcc6172 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 May 2023 14:18:11 +0200 Subject: [PATCH 44/61] Activate the correct panel when deserializing workspace --- crates/workspace/src/dock.rs | 14 +++++++++++--- crates/workspace/src/workspace.rs | 23 ++++++++++++++++++----- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 5c501f64ad..e115e1d573 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,8 +1,9 @@ use crate::{StatusItemView, Workspace}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ - elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, Axis, - Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, + elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, + AppContext, Axis, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, + WindowContext, }; use serde::Deserialize; use settings::Settings; @@ -181,12 +182,19 @@ impl Dock { .map_or(false, |panel| panel.has_focus(cx)) } - pub fn panel_index(&self) -> Option { + pub fn panel_index_for_type(&self) -> Option { self.panel_entries .iter() .position(|entry| entry.panel.as_any().is::()) } + pub fn panel_index_for_ui_name(&self, ui_name: &str, cx: &AppContext) -> Option { + self.panel_entries.iter().position(|entry| { + let panel = entry.panel.as_any(); + cx.view_ui_name(panel.window_id(), panel.id()) == Some(ui_name) + }) + } + pub fn active_panel_index(&self) -> usize { self.active_panel_index } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 30a2a2f4e3..01ed898de4 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1480,7 +1480,7 @@ impl Workspace { pub fn toggle_panel_focus(&mut self, cx: &mut ViewContext) { for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { - if let Some(panel_index) = dock.read(cx).panel_index::() { + if let Some(panel_index) = dock.read(cx).panel_index_for_type::() { let active_item = dock.update(cx, |dock, cx| { dock.set_open(true, cx); dock.activate_panel(panel_index, cx); @@ -2605,7 +2605,7 @@ impl Workspace { ) }); - dbg!(DockStructure { + DockStructure { left: DockData { visible: left_visible, active_panel: left_active_panel, @@ -2618,7 +2618,7 @@ impl Workspace { visible: bottom_visible, active_panel: bottom_active_panel, }, - }) + } } if let Some(location) = self.location(cx) { @@ -2691,15 +2691,28 @@ impl Workspace { let docks = serialized_workspace.docks; workspace.left_dock.update(cx, |dock, cx| { - dbg!(docks.left.visible); dock.set_open(docks.left.visible, cx); - dbg!(dock.is_open()); + if let Some(active_panel) = docks.left.active_panel { + if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { + dock.activate_panel(ix, cx); + } + } }); workspace.right_dock.update(cx, |dock, cx| { dock.set_open(docks.right.visible, cx); + if let Some(active_panel) = docks.right.active_panel { + if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { + dock.activate_panel(ix, cx); + } + } }); workspace.bottom_dock.update(cx, |dock, cx| { dock.set_open(docks.bottom.visible, cx); + if let Some(active_panel) = docks.bottom.active_panel { + if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { + dock.activate_panel(ix, cx); + } + } }); cx.notify(); From 924ec961ffa1a41b7f7e86829b501e0a64f32d4f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 May 2023 15:27:18 +0200 Subject: [PATCH 45/61] Toggle project panel when opening new workspace in a dock-agnostic way --- crates/collab/src/tests.rs | 2 +- crates/workspace/src/workspace.rs | 44 +++++++++++-------------------- crates/zed/src/zed.rs | 20 +++++++++++++- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index aeb26231d4..cbc4b3bd45 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -194,7 +194,7 @@ impl TestServer { themes: ThemeRegistry::new((), cx.font_cache()), fs: fs.clone(), build_window_options: |_, _, _| Default::default(), - initialize_workspace: |_, _, _| unimplemented!(), + initialize_workspace: |_, _, _, _| unimplemented!(), background_actions: || &[], }); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 01ed898de4..b26acf9b4d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -361,7 +361,7 @@ pub struct AppState { pub fs: Arc, pub build_window_options: fn(Option, Option, &dyn Platform) -> WindowOptions<'static>, - pub initialize_workspace: fn(&mut Workspace, &Arc, &mut ViewContext), + pub initialize_workspace: fn(&mut Workspace, bool, &Arc, &mut ViewContext), pub background_actions: BackgroundActions, } @@ -383,7 +383,7 @@ impl AppState { fs, languages, user_store, - initialize_workspace: |_, _, _| {}, + initialize_workspace: |_, _, _, _| {}, build_window_options: |_, _, _| Default::default(), background_actions: || &[], }) @@ -733,6 +733,7 @@ impl Workspace { let build_workspace = |cx: &mut ViewContext, serialized_workspace: Option| { + let was_deserialized = serialized_workspace.is_some(); let mut workspace = Workspace::new( serialized_workspace, workspace_id, @@ -740,7 +741,12 @@ impl Workspace { app_state.clone(), cx, ); - (app_state.initialize_workspace)(&mut workspace, &app_state, cx); + (app_state.initialize_workspace)( + &mut workspace, + was_deserialized, + &app_state, + cx, + ); workspace }; @@ -2734,7 +2740,7 @@ impl Workspace { user_store: project.read(cx).user_store(), fs: project.read(cx).fs().clone(), build_window_options: |_, _, _| Default::default(), - initialize_workspace: |_, _, _| {}, + initialize_workspace: |_, _, _, _| {}, background_actions: || &[], }); Self::new(None, 0, project, app_state, cx) @@ -2998,28 +3004,11 @@ pub fn open_paths( .await, )) } else { - let contains_directory = - futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path))) - .await - .contains(&false); - - cx.update(|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; - - workspace.update(&mut cx, |workspace, cx| { - if contains_directory { - workspace.toggle_dock(DockPosition::Left, cx); - } - })?; - - anyhow::Ok((workspace, items)) + Ok(cx + .update(|cx| { + Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx) }) - }) - .await + .await) } }) } @@ -3109,9 +3098,8 @@ pub fn join_remote_project( let (_, workspace) = cx.add_window( (app_state.build_window_options)(None, None, cx.platform().as_ref()), |cx| { - let mut workspace = - Workspace::new(Default::default(), 0, project, app_state.clone(), cx); - (app_state.initialize_workspace)(&mut workspace, &app_state, cx); + let mut workspace = Workspace::new(None, 0, project, app_state.clone(), cx); + (app_state.initialize_workspace)(&mut workspace, false, &app_state, cx); workspace }, ); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index fb7da693fc..35a430f63f 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -35,7 +35,10 @@ use terminal_view::terminal_panel::{self, TerminalPanel}; use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; pub use workspace; -use workspace::{create_and_open_local_file, open_new, AppState, NewFile, NewWindow, Workspace}; +use workspace::{ + create_and_open_local_file, dock::PanelHandle, open_new, AppState, NewFile, NewWindow, + Workspace, +}; #[derive(Deserialize, Clone, PartialEq)] pub struct OpenBrowser { @@ -279,6 +282,7 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { pub fn initialize_workspace( workspace: &mut Workspace, + was_deserialized: bool, app_state: &Arc, cx: &mut ViewContext, ) { @@ -316,7 +320,21 @@ pub fn initialize_workspace( workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); let project_panel = ProjectPanel::new(workspace, cx); + let project_panel_position = project_panel.position(cx); workspace.add_panel(project_panel, cx); + if !was_deserialized + && workspace + .project() + .read(cx) + .visible_worktrees(cx) + .any(|tree| { + tree.read(cx) + .root_entry() + .map_or(false, |entry| entry.is_dir()) + }) + { + workspace.toggle_dock(project_panel_position, cx); + } let terminal_panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); workspace.add_panel(terminal_panel, cx); From e49281699c690897e7c96d8e7c6253b51259c717 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 May 2023 16:07:47 +0200 Subject: [PATCH 46/61] Add new terminal when the terminal panel is activated, and not on focus --- crates/project_panel/src/project_panel.rs | 2 + crates/terminal_view/src/terminal_panel.rs | 61 ++++++++++++---------- crates/workspace/src/dock.rs | 33 ++++++++---- 3 files changed, 58 insertions(+), 38 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index a89d544074..3476a6a155 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1401,6 +1401,8 @@ impl workspace::dock::Panel for ProjectPanel { fn set_zoomed(&mut self, _: bool, _: &mut ViewContext) {} + fn set_active(&mut self, _: bool, _: &mut ViewContext) {} + fn icon_path(&self) -> &'static str { "icons/folder_tree_16.svg" } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 4926a8775e..4652b58072 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1,9 +1,8 @@ use crate::TerminalView; use gpui::{ - actions, elements::*, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, - ViewHandle, WeakViewHandle, WindowContext, + actions, anyhow, elements::*, AppContext, Entity, Subscription, View, ViewContext, ViewHandle, + WeakViewHandle, WindowContext, }; -use project::Project; use settings::{settings_file::SettingsFile, Settings, TerminalDockPosition, WorkingDirectory}; use util::ResultExt; use workspace::{ @@ -26,7 +25,6 @@ pub enum Event { } pub struct TerminalPanel { - project: ModelHandle, pane: ViewHandle, workspace: WeakViewHandle, _subscriptions: Vec, @@ -86,7 +84,6 @@ impl TerminalPanel { cx.subscribe(&pane, Self::handle_pane_event), ]; Self { - project: workspace.project().clone(), pane, workspace: workspace.weak_handle(), _subscriptions: subscriptions, @@ -109,30 +106,34 @@ impl TerminalPanel { } fn add_terminal(&mut self, _: &workspace::NewTerminal, cx: &mut ViewContext) { - if let Some(workspace) = self.workspace.upgrade(cx) { - let working_directory_strategy = cx - .global::() - .terminal_overrides - .working_directory - .clone() - .unwrap_or(WorkingDirectory::CurrentProjectDirectory); - let working_directory = - crate::get_working_directory(workspace.read(cx), cx, working_directory_strategy); - let window_id = cx.window_id(); - if let Some(terminal) = self.project.update(cx, |project, cx| { - project - .create_terminal(working_directory, window_id, cx) - .log_err() - }) { - workspace.update(cx, |workspace, cx| { + let workspace = self.workspace.clone(); + cx.spawn(|this, mut cx| async move { + let pane = this.read_with(&cx, |this, _| this.pane.clone())?; + workspace.update(&mut cx, |workspace, cx| { + let working_directory_strategy = cx + .global::() + .terminal_overrides + .working_directory + .clone() + .unwrap_or(WorkingDirectory::CurrentProjectDirectory); + let working_directory = + crate::get_working_directory(workspace, cx, working_directory_strategy); + let window_id = cx.window_id(); + if let Some(terminal) = workspace.project().update(cx, |project, cx| { + project + .create_terminal(working_directory, window_id, cx) + .log_err() + }) { let terminal = Box::new(cx.add_view(|cx| { TerminalView::new(terminal, workspace.database_id(), cx) })); - Pane::add_item(workspace, &self.pane, terminal, true, true, None, cx); - }); - } - } + Pane::add_item(workspace, &pane, terminal, true, true, None, cx); + } + })?; + anyhow::Ok(()) + }) + .detach_and_log_err(cx); } } @@ -150,10 +151,6 @@ impl View for TerminalPanel { } fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { - if self.pane.read(cx).items_len() == 0 { - self.add_terminal(&Default::default(), cx) - } - if cx.is_self_focused() { cx.focus(&self.pane); } @@ -215,6 +212,12 @@ impl Panel for TerminalPanel { self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx)); } + fn set_active(&mut self, active: bool, cx: &mut ViewContext) { + if active && self.pane.read(cx).items_len() == 0 { + self.add_terminal(&Default::default(), cx) + } + } + fn icon_path(&self) -> &'static str { "icons/terminal_12.svg" } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index e115e1d573..83bb54d3be 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -24,6 +24,7 @@ pub trait Panel: View { fn should_zoom_out_on_event(_: &Self::Event) -> bool; fn is_zoomed(&self, cx: &WindowContext) -> bool; fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext); + fn set_active(&mut self, active: bool, cx: &mut ViewContext); fn should_activate_on_event(_: &Self::Event) -> bool; fn should_close_on_event(_: &Self::Event) -> bool; fn has_focus(&self, cx: &WindowContext) -> bool; @@ -37,6 +38,7 @@ pub trait PanelHandle { fn set_position(&self, position: DockPosition, cx: &mut WindowContext); fn is_zoomed(&self, cx: &WindowContext) -> bool; fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext); + fn set_active(&self, active: bool, cx: &mut WindowContext); fn default_size(&self, cx: &WindowContext) -> f32; fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; @@ -77,6 +79,10 @@ where self.update(cx, |this, cx| this.set_zoomed(zoomed, cx)) } + fn set_active(&self, active: bool, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.set_active(active, cx)) + } + fn icon_path(&self, cx: &WindowContext) -> &'static str { self.read(cx).icon_path() } @@ -202,12 +208,16 @@ impl Dock { pub fn set_open(&mut self, open: bool, cx: &mut ViewContext) { if open != self.is_open { self.is_open = open; + if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + active_panel.panel.set_active(open, cx); + } + cx.notify(); } } pub fn toggle_open(&mut self, cx: &mut ViewContext) { - self.is_open = !self.is_open; + self.set_open(!self.is_open, cx); cx.notify(); } @@ -297,17 +307,18 @@ impl Dock { } pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { - self.active_panel_index = panel_ix; - cx.notify(); - } + if panel_ix != self.active_panel_index { + if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + active_panel.panel.set_active(false, cx); + } - pub fn toggle_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { - if self.active_panel_index == panel_ix { - self.is_open = false; - } else { self.active_panel_index = panel_ix; + if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + active_panel.panel.set_active(true, cx); + } + + cx.notify(); } - cx.notify(); } pub fn active_panel(&self) -> Option<&Rc> { @@ -615,6 +626,10 @@ pub(crate) mod test { unimplemented!() } + fn set_active(&mut self, _active: bool, _cx: &mut ViewContext) { + unimplemented!() + } + fn default_size(&self, _: &WindowContext) -> f32 { match self.position.axis() { Axis::Horizontal => 300., From 5ff49bde317ba4cacc827fb8f91458b9f02dd6c2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 May 2023 19:26:32 +0200 Subject: [PATCH 47/61] Serialize and deserialize `TerminalPanel` --- crates/terminal_view/src/terminal_panel.rs | 115 +++++++++++++- crates/workspace/src/workspace.rs | 69 ++++---- crates/zed/src/zed.rs | 173 +++++++++++---------- 3 files changed, 245 insertions(+), 112 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 4652b58072..1f4096880c 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1,15 +1,20 @@ use crate::TerminalView; +use db::kvp::KEY_VALUE_STORE; use gpui::{ - actions, anyhow, elements::*, AppContext, Entity, Subscription, View, ViewContext, ViewHandle, - WeakViewHandle, WindowContext, + actions, anyhow::Result, elements::*, serde_json, AppContext, AsyncAppContext, Entity, + Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; +use serde::{Deserialize, Serialize}; use settings::{settings_file::SettingsFile, Settings, TerminalDockPosition, WorkingDirectory}; -use util::ResultExt; +use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel}, + item::Item, pane, DraggedItem, Pane, Workspace, }; +const TERMINAL_PANEL_KEY: &'static str = "TerminalPanel"; + actions!(terminal_panel, [ToggleFocus]); pub fn init(cx: &mut AppContext) { @@ -27,6 +32,7 @@ pub enum Event { pub struct TerminalPanel { pane: ViewHandle, workspace: WeakViewHandle, + pending_serialization: Task>, _subscriptions: Vec, } @@ -86,10 +92,79 @@ impl TerminalPanel { Self { pane, workspace: workspace.weak_handle(), + pending_serialization: Task::ready(None), _subscriptions: subscriptions, } } + pub fn load( + workspace: WeakViewHandle, + cx: AsyncAppContext, + ) -> Task>> { + cx.spawn(|mut cx| async move { + let serialized_panel = if let Some(panel) = cx + .background() + .spawn(async move { KEY_VALUE_STORE.read_kvp(TERMINAL_PANEL_KEY) }) + .await? + { + Some(serde_json::from_str::(&panel)?) + } else { + None + }; + let (panel, pane, items) = workspace.update(&mut cx, |workspace, cx| { + let panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); + let items = if let Some(serialized_panel) = serialized_panel.as_ref() { + panel.update(cx, |panel, cx| { + panel.pane.update(cx, |_, cx| { + serialized_panel + .items + .iter() + .map(|item_id| { + TerminalView::deserialize( + workspace.project().clone(), + workspace.weak_handle(), + workspace.database_id(), + *item_id, + cx, + ) + }) + .collect::>() + }) + }) + } else { + Default::default() + }; + let pane = panel.read(cx).pane.clone(); + (panel, pane, items) + })?; + + let items = futures::future::join_all(items).await; + workspace.update(&mut cx, |workspace, cx| { + let active_item_id = serialized_panel + .as_ref() + .and_then(|panel| panel.active_item_id); + let mut active_ix = None; + for item in items { + if let Some(item) = item.log_err() { + let item_id = item.id(); + Pane::add_item(workspace, &pane, Box::new(item), false, false, None, cx); + if Some(item_id) == active_item_id { + active_ix = Some(pane.read(cx).items_len() - 1); + } + } + } + + if let Some(active_ix) = active_ix { + pane.update(cx, |pane, cx| { + pane.activate_item(active_ix, false, false, cx) + }); + } + })?; + + Ok(panel) + }) + } + fn handle_pane_event( &mut self, _pane: ViewHandle, @@ -97,6 +172,8 @@ impl TerminalPanel { cx: &mut ViewContext, ) { match event { + pane::Event::ActivateItem { .. } => self.serialize(cx), + pane::Event::RemoveItem { .. } => self.serialize(cx), pane::Event::Remove => cx.emit(Event::Close), pane::Event::ZoomIn => cx.emit(Event::ZoomIn), pane::Event::ZoomOut => cx.emit(Event::ZoomOut), @@ -131,10 +208,36 @@ impl TerminalPanel { Pane::add_item(workspace, &pane, terminal, true, true, None, cx); } })?; + this.update(&mut cx, |this, cx| this.serialize(cx))?; anyhow::Ok(()) }) .detach_and_log_err(cx); } + + fn serialize(&mut self, cx: &mut ViewContext) { + let items = self + .pane + .read(cx) + .items() + .map(|item| item.id()) + .collect::>(); + let active_item_id = self.pane.read(cx).active_item().map(|item| item.id()); + self.pending_serialization = cx.background().spawn( + async move { + KEY_VALUE_STORE + .write_kvp( + TERMINAL_PANEL_KEY.into(), + serde_json::to_string(&SerializedTerminalPanel { + items, + active_item_id, + })?, + ) + .await?; + anyhow::Ok(()) + } + .log_err(), + ); + } } impl Entity for TerminalPanel { @@ -255,3 +358,9 @@ impl Panel for TerminalPanel { matches!(event, Event::Focus) } } + +#[derive(Serialize, Deserialize)] +struct SerializedTerminalPanel { + items: Vec, + active_item_id: Option, +} diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index b26acf9b4d..5dc7c066f7 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -361,7 +361,8 @@ pub struct AppState { pub fs: Arc, pub build_window_options: fn(Option, Option, &dyn Platform) -> WindowOptions<'static>, - pub initialize_workspace: fn(&mut Workspace, bool, &Arc, &mut ViewContext), + pub initialize_workspace: + fn(WeakViewHandle, bool, Arc, AsyncAppContext) -> Task>, pub background_actions: BackgroundActions, } @@ -383,7 +384,7 @@ impl AppState { fs, languages, user_store, - initialize_workspace: |_, _, _, _| {}, + initialize_workspace: |_, _, _, _| Task::ready(Ok(())), build_window_options: |_, _, _| Default::default(), background_actions: || &[], }) @@ -730,31 +731,19 @@ impl Workspace { )) }); - let build_workspace = - |cx: &mut ViewContext, - serialized_workspace: Option| { - let was_deserialized = serialized_workspace.is_some(); - let mut workspace = Workspace::new( - serialized_workspace, - workspace_id, - project_handle.clone(), - app_state.clone(), - cx, - ); - (app_state.initialize_workspace)( - &mut workspace, - was_deserialized, - &app_state, - cx, - ); - workspace - }; + let was_deserialized = serialized_workspace.is_some(); let workspace = requesting_window_id .and_then(|window_id| { cx.update(|cx| { cx.replace_root_view(window_id, |cx| { - build_workspace(cx, serialized_workspace.take()) + Workspace::new( + serialized_workspace.take(), + workspace_id, + project_handle.clone(), + app_state.clone(), + cx, + ) }) }) }) @@ -794,11 +783,28 @@ impl Workspace { // 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), + |cx| { + Workspace::new( + serialized_workspace, + workspace_id, + project_handle.clone(), + app_state.clone(), + cx, + ) + }, ) .1 }); + (app_state.initialize_workspace)( + workspace.downgrade(), + was_deserialized, + app_state.clone(), + cx.clone(), + ) + .await + .log_err(); + let workspace = workspace.downgrade(); notify_if_database_failed(&workspace, &mut cx); @@ -2740,7 +2746,7 @@ impl Workspace { user_store: project.read(cx).user_store(), fs: project.read(cx).fs().clone(), build_window_options: |_, _, _| Default::default(), - initialize_workspace: |_, _, _, _| {}, + initialize_workspace: |_, _, _, _| Task::ready(Ok(())), background_actions: || &[], }); Self::new(None, 0, project, app_state, cx) @@ -3097,12 +3103,17 @@ pub fn join_remote_project( let (_, workspace) = cx.add_window( (app_state.build_window_options)(None, None, cx.platform().as_ref()), - |cx| { - let mut workspace = Workspace::new(None, 0, project, app_state.clone(), cx); - (app_state.initialize_workspace)(&mut workspace, false, &app_state, cx); - workspace - }, + |cx| Workspace::new(None, 0, project, app_state.clone(), cx), ); + (app_state.initialize_workspace)( + workspace.downgrade(), + false, + app_state.clone(), + cx.clone(), + ) + .await + .log_err(); + workspace.downgrade() }; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 35a430f63f..2608ea1ec4 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -18,10 +18,11 @@ use feedback::{ use futures::StreamExt; use gpui::{ actions, + anyhow::{self, Result}, geometry::vector::vec2f, impl_actions, platform::{Platform, PromptLevel, TitlebarOptions, WindowBounds, WindowKind, WindowOptions}, - AppContext, ViewContext, + AppContext, AsyncAppContext, Task, ViewContext, WeakViewHandle, }; pub use lsp; pub use project; @@ -281,93 +282,105 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { } pub fn initialize_workspace( - workspace: &mut Workspace, + workspace_handle: WeakViewHandle, was_deserialized: bool, - app_state: &Arc, - cx: &mut ViewContext, -) { - let workspace_handle = cx.handle(); - cx.subscribe(&workspace_handle, { - move |workspace, _, event, cx| { - if let workspace::Event::PaneAdded(pane) = event { - pane.update(cx, |pane, cx| { - pane.toolbar().update(cx, |toolbar, cx| { - let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace)); - toolbar.add_item(breadcrumbs, cx); - let buffer_search_bar = cx.add_view(BufferSearchBar::new); - toolbar.add_item(buffer_search_bar, cx); - let project_search_bar = cx.add_view(|_| ProjectSearchBar::new()); - toolbar.add_item(project_search_bar, cx); - let submit_feedback_button = cx.add_view(|_| SubmitFeedbackButton::new()); - toolbar.add_item(submit_feedback_button, cx); - let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new()); - toolbar.add_item(feedback_info_text, cx); - let lsp_log_item = cx.add_view(|_| { - lsp_log::LspLogToolbarItemView::new(workspace.project().clone()) + app_state: Arc, + cx: AsyncAppContext, +) -> Task> { + cx.spawn(|mut cx| async move { + workspace_handle.update(&mut cx, |workspace, cx| { + let workspace_handle = cx.handle(); + cx.subscribe(&workspace_handle, { + move |workspace, _, event, cx| { + if let workspace::Event::PaneAdded(pane) = event { + pane.update(cx, |pane, cx| { + pane.toolbar().update(cx, |toolbar, cx| { + let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace)); + toolbar.add_item(breadcrumbs, cx); + let buffer_search_bar = cx.add_view(BufferSearchBar::new); + toolbar.add_item(buffer_search_bar, cx); + let project_search_bar = cx.add_view(|_| ProjectSearchBar::new()); + toolbar.add_item(project_search_bar, cx); + let submit_feedback_button = + cx.add_view(|_| SubmitFeedbackButton::new()); + toolbar.add_item(submit_feedback_button, cx); + let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new()); + toolbar.add_item(feedback_info_text, cx); + let lsp_log_item = cx.add_view(|_| { + lsp_log::LspLogToolbarItemView::new(workspace.project().clone()) + }); + toolbar.add_item(lsp_log_item, cx); + }) }); - toolbar.add_item(lsp_log_item, cx); - }) - }); - } - } - }) - .detach(); - - cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone())); - - let collab_titlebar_item = - cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); - workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); - - let project_panel = ProjectPanel::new(workspace, cx); - let project_panel_position = project_panel.position(cx); - workspace.add_panel(project_panel, cx); - if !was_deserialized - && workspace - .project() - .read(cx) - .visible_worktrees(cx) - .any(|tree| { - tree.read(cx) - .root_entry() - .map_or(false, |entry| entry.is_dir()) + } + } }) - { - workspace.toggle_dock(project_panel_position, cx); - } + .detach(); - let terminal_panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); - workspace.add_panel(terminal_panel, cx); + cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone())); - let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx)); - let diagnostic_summary = - cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); - let activity_indicator = - activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx); - let active_buffer_language = - cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace)); - let feedback_button = - cx.add_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)); - let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); - workspace.status_bar().update(cx, |status_bar, cx| { - status_bar.add_left_item(diagnostic_summary, cx); - status_bar.add_left_item(activity_indicator, cx); - status_bar.add_right_item(feedback_button, cx); - status_bar.add_right_item(copilot, cx); - status_bar.add_right_item(active_buffer_language, cx); - status_bar.add_right_item(cursor_position, cx); - }); + let collab_titlebar_item = + cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); + workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); - auto_update::notify_of_any_new_update(cx.weak_handle(), cx); + let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx)); + let diagnostic_summary = + cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); + let activity_indicator = activity_indicator::ActivityIndicator::new( + workspace, + app_state.languages.clone(), + cx, + ); + let active_buffer_language = + cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace)); + let feedback_button = cx.add_view(|_| { + feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace) + }); + let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); + workspace.status_bar().update(cx, |status_bar, cx| { + status_bar.add_left_item(diagnostic_summary, cx); + status_bar.add_left_item(activity_indicator, cx); + status_bar.add_right_item(feedback_button, cx); + status_bar.add_right_item(copilot, cx); + status_bar.add_right_item(active_buffer_language, cx); + status_bar.add_right_item(cursor_position, cx); + }); - vim::observe_keystrokes(cx); + auto_update::notify_of_any_new_update(cx.weak_handle(), cx); - cx.on_window_should_close(|workspace, cx| { - if let Some(task) = workspace.close(&Default::default(), cx) { - task.detach_and_log_err(cx); - } - false - }); + vim::observe_keystrokes(cx); + + cx.on_window_should_close(|workspace, cx| { + if let Some(task) = workspace.close(&Default::default(), cx) { + task.detach_and_log_err(cx); + } + false + }); + + let project_panel = ProjectPanel::new(workspace, cx); + let project_panel_position = project_panel.position(cx); + workspace.add_panel(project_panel, cx); + if !was_deserialized + && workspace + .project() + .read(cx) + .visible_worktrees(cx) + .any(|tree| { + tree.read(cx) + .root_entry() + .map_or(false, |entry| entry.is_dir()) + }) + { + workspace.toggle_dock(project_panel_position, cx); + } + })?; + + let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone()).await?; + workspace_handle.update(&mut cx, |workspace, cx| { + workspace.add_panel(terminal_panel, cx) + })?; + Ok(()) + }) } pub fn build_window_options( From 5a8fb18c20363da73179efc4e105648e7300a57e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 May 2023 19:35:00 +0200 Subject: [PATCH 48/61] Show workspace only after initializing it --- crates/collab_ui/src/incoming_call_notification.rs | 1 + crates/collab_ui/src/project_shared_notification.rs | 1 + crates/copilot/src/sign_in.rs | 1 + crates/gpui/src/platform.rs | 2 ++ crates/gpui/src/platform/mac/window.rs | 2 +- crates/workspace/src/workspace.rs | 2 ++ crates/zed/src/zed.rs | 3 ++- 7 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index 35484b3309..d8cf01d20c 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -42,6 +42,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { titlebar: None, center: false, focus: false, + show: true, kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index 8a41368276..fac2588e0b 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -36,6 +36,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { titlebar: None, center: false, focus: false, + show: true, kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index da3c96956e..18211e4276 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -74,6 +74,7 @@ fn create_copilot_auth_window( titlebar: None, center: true, focus: true, + show: true, kind: WindowKind::Normal, is_movable: true, screen: None, diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 8fbcda02a3..9b4fd7ca51 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -173,6 +173,7 @@ pub struct WindowOptions<'a> { pub titlebar: Option>, pub center: bool, pub focus: bool, + pub show: bool, pub kind: WindowKind, pub is_movable: bool, pub screen: Option>, @@ -376,6 +377,7 @@ impl<'a> Default for WindowOptions<'a> { }), center: false, focus: true, + show: true, kind: WindowKind::Normal, is_movable: true, screen: None, diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index bcff08d005..2d1de6df75 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -614,7 +614,7 @@ impl Window { } if options.focus { native_window.makeKeyAndOrderFront_(nil); - } else { + } else if options.show { native_window.orderFront_(nil); } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5dc7c066f7..dfb8560843 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -805,6 +805,8 @@ impl Workspace { .await .log_err(); + cx.update_window(workspace.window_id(), |cx| cx.activate_window()); + let workspace = workspace.downgrade(); notify_if_database_failed(&workspace, &mut cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 2608ea1ec4..77a3bf350f 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -398,7 +398,8 @@ pub fn build_window_options( traffic_light_position: Some(vec2f(8., 8.)), }), center: false, - focus: true, + focus: false, + show: false, kind: WindowKind::Normal, is_movable: true, bounds, From 2098ac2c7732159328a8b6df426726e12ef94447 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 10:41:48 +0200 Subject: [PATCH 49/61] Fix formatting for keymap.json --- assets/keymaps/default.json | 1066 +++++++++++++++++------------------ 1 file changed, 533 insertions(+), 533 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 246b699558..1fca19a83f 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -1,536 +1,536 @@ [ - // Standard macOS bindings - { - "bindings": { - "up": "menu::SelectPrev", - "pageup": "menu::SelectFirst", - "shift-pageup": "menu::SelectFirst", - "ctrl-p": "menu::SelectPrev", - "down": "menu::SelectNext", - "pagedown": "menu::SelectLast", - "shift-pagedown": "menu::SelectFirst", - "ctrl-n": "menu::SelectNext", - "cmd-up": "menu::SelectFirst", - "cmd-down": "menu::SelectLast", - "enter": "menu::Confirm", - "escape": "menu::Cancel", - "ctrl-c": "menu::Cancel", - "cmd-{": "pane::ActivatePrevItem", - "cmd-}": "pane::ActivateNextItem", - "alt-cmd-left": "pane::ActivatePrevItem", - "alt-cmd-right": "pane::ActivateNextItem", - "cmd-w": "pane::CloseActiveItem", - "alt-cmd-t": "pane::CloseInactiveItems", - "cmd-k u": "pane::CloseCleanItems", - "cmd-k cmd-w": "pane::CloseAllItems", - "cmd-shift-w": "workspace::CloseWindow", - "cmd-s": "workspace::Save", - "cmd-shift-s": "workspace::SaveAs", - "cmd-=": "zed::IncreaseBufferFontSize", - "cmd--": "zed::DecreaseBufferFontSize", - "cmd-0": "zed::ResetBufferFontSize", - "cmd-,": "zed::OpenSettings", - "cmd-q": "zed::Quit", - "cmd-h": "zed::Hide", - "alt-cmd-h": "zed::HideOthers", - "cmd-m": "zed::Minimize", - "ctrl-cmd-f": "zed::ToggleFullScreen", - "cmd-n": "workspace::NewFile", - "cmd-shift-n": "workspace::NewWindow", - "cmd-o": "workspace::Open", - "alt-cmd-o": "projects::OpenRecent", - "ctrl-~": "workspace::NewTerminal", - "ctrl-`": "terminal_panel::ToggleFocus" - } - }, - { - "context": "Editor", - "bindings": { - "escape": "editor::Cancel", - "backspace": "editor::Backspace", - "shift-backspace": "editor::Backspace", - "ctrl-h": "editor::Backspace", - "delete": "editor::Delete", - "ctrl-d": "editor::Delete", - "tab": "editor::Tab", - "shift-tab": "editor::TabPrev", - "ctrl-k": "editor::CutToEndOfLine", - "ctrl-t": "editor::Transpose", - "cmd-backspace": "editor::DeleteToBeginningOfLine", - "cmd-delete": "editor::DeleteToEndOfLine", - "alt-backspace": "editor::DeleteToPreviousWordStart", - "alt-delete": "editor::DeleteToNextWordEnd", - "alt-h": "editor::DeleteToPreviousWordStart", - "alt-d": "editor::DeleteToNextWordEnd", - "cmd-x": "editor::Cut", - "cmd-c": "editor::Copy", - "cmd-v": "editor::Paste", - "cmd-z": "editor::Undo", - "cmd-shift-z": "editor::Redo", - "up": "editor::MoveUp", - "pageup": "editor::PageUp", - "shift-pageup": "editor::MovePageUp", - "home": "editor::MoveToBeginningOfLine", - "down": "editor::MoveDown", - "pagedown": "editor::PageDown", - "shift-pagedown": "editor::MovePageDown", - "end": "editor::MoveToEndOfLine", - "left": "editor::MoveLeft", - "right": "editor::MoveRight", - "ctrl-p": "editor::MoveUp", - "ctrl-n": "editor::MoveDown", - "ctrl-b": "editor::MoveLeft", - "ctrl-f": "editor::MoveRight", - "ctrl-l": "editor::NextScreen", - "alt-left": "editor::MoveToPreviousWordStart", - "alt-b": "editor::MoveToPreviousWordStart", - "alt-right": "editor::MoveToNextWordEnd", - "alt-f": "editor::MoveToNextWordEnd", - "cmd-left": "editor::MoveToBeginningOfLine", - "ctrl-a": "editor::MoveToBeginningOfLine", - "cmd-right": "editor::MoveToEndOfLine", - "ctrl-e": "editor::MoveToEndOfLine", - "cmd-up": "editor::MoveToBeginning", - "cmd-down": "editor::MoveToEnd", - "shift-up": "editor::SelectUp", - "ctrl-shift-p": "editor::SelectUp", - "shift-down": "editor::SelectDown", - "ctrl-shift-n": "editor::SelectDown", - "shift-left": "editor::SelectLeft", - "ctrl-shift-b": "editor::SelectLeft", - "shift-right": "editor::SelectRight", - "ctrl-shift-f": "editor::SelectRight", - "alt-shift-left": "editor::SelectToPreviousWordStart", - "alt-shift-b": "editor::SelectToPreviousWordStart", - "alt-shift-right": "editor::SelectToNextWordEnd", - "alt-shift-f": "editor::SelectToNextWordEnd", - "cmd-shift-up": "editor::SelectToBeginning", - "cmd-shift-down": "editor::SelectToEnd", - "cmd-a": "editor::SelectAll", - "cmd-l": "editor::SelectLine", - "cmd-shift-i": "editor::Format", - "cmd-shift-left": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true - } - ], - "shift-home": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true - } - ], - "ctrl-shift-a": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true - } - ], - "cmd-shift-right": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true - } - ], - "shift-end": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true - } - ], - "ctrl-shift-e": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true - } - ], - "ctrl-v": [ - "editor::MovePageDown", - { - "center_cursor": true - } - ], - "alt-v": [ - "editor::MovePageUp", - { - "center_cursor": true - } - ], - "ctrl-cmd-space": "editor::ShowCharacterPalette" - } - }, - { - "context": "Editor && mode == full", - "bindings": { - "enter": "editor::Newline", - "cmd-shift-enter": "editor::NewlineAbove", - "cmd-enter": "editor::NewlineBelow", - "alt-z": "editor::ToggleSoftWrap", - "cmd-f": [ - "buffer_search::Deploy", - { - "focus": true - } - ], - "cmd-e": [ - "buffer_search::Deploy", - { - "focus": false - } - ], - "alt-\\": "copilot::Suggest", - "alt-]": "copilot::NextSuggestion", - "alt-[": "copilot::PreviousSuggestion" - } - }, - { - "context": "Editor && mode == auto_height", - "bindings": { - "alt-enter": "editor::Newline", - "cmd-alt-enter": "editor::NewlineBelow" - } - }, - { - "context": "BufferSearchBar > Editor", - "bindings": { - "escape": "buffer_search::Dismiss", - "tab": "buffer_search::FocusEditor", - "enter": "search::SelectNextMatch", - "shift-enter": "search::SelectPrevMatch" - } - }, - { - "context": "ProjectSearchBar > Editor", - "bindings": { - "escape": "project_search::ToggleFocus" - } - }, - { - "context": "ProjectSearchView > Editor", - "bindings": { - "escape": "project_search::ToggleFocus" - } - }, - { - "context": "Pane", - "bindings": { - "cmd-f": "project_search::ToggleFocus", - "cmd-g": "search::SelectNextMatch", - "cmd-shift-g": "search::SelectPrevMatch", - "alt-cmd-c": "search::ToggleCaseSensitive", - "alt-cmd-w": "search::ToggleWholeWord", - "alt-cmd-r": "search::ToggleRegex", - "shift-escape": "workspace::ToggleZoom" - } - }, - // Bindings from VS Code - { - "context": "Editor", - "bindings": { - "cmd-[": "editor::Outdent", - "cmd-]": "editor::Indent", - "cmd-alt-up": "editor::AddSelectionAbove", - "cmd-ctrl-p": "editor::AddSelectionAbove", - "cmd-alt-down": "editor::AddSelectionBelow", - "cmd-ctrl-n": "editor::AddSelectionBelow", - "cmd-d": [ - "editor::SelectNext", - { - "replace_newest": false - } - ], - "cmd-k cmd-d": [ - "editor::SelectNext", - { - "replace_newest": true - } - ], - "cmd-k cmd-i": "editor::Hover", - "cmd-/": [ - "editor::ToggleComments", - { - "advance_downwards": false - } - ], - "alt-up": "editor::SelectLargerSyntaxNode", - "alt-down": "editor::SelectSmallerSyntaxNode", - "cmd-u": "editor::UndoSelection", - "cmd-shift-u": "editor::RedoSelection", - "f8": "editor::GoToDiagnostic", - "shift-f8": "editor::GoToPrevDiagnostic", - "f2": "editor::Rename", - "f12": "editor::GoToDefinition", - "cmd-f12": "editor::GoToTypeDefinition", - "alt-shift-f12": "editor::FindAllReferences", - "ctrl-m": "editor::MoveToEnclosingBracket", - "alt-cmd-[": "editor::Fold", - "alt-cmd-]": "editor::UnfoldLines", - "ctrl-space": "editor::ShowCompletions", - "cmd-.": "editor::ToggleCodeActions", - "alt-cmd-r": "editor::RevealInFinder" - } - }, - { - "context": "Editor && mode == full", - "bindings": { - "cmd-shift-o": "outline::Toggle", - "ctrl-g": "go_to_line::Toggle" - } - }, - { - "context": "Pane", - "bindings": { - "ctrl-1": [ - "pane::ActivateItem", - 0 - ], - "ctrl-2": [ - "pane::ActivateItem", - 1 - ], - "ctrl-3": [ - "pane::ActivateItem", - 2 - ], - "ctrl-4": [ - "pane::ActivateItem", - 3 - ], - "ctrl-5": [ - "pane::ActivateItem", - 4 - ], - "ctrl-6": [ - "pane::ActivateItem", - 5 - ], - "ctrl-7": [ - "pane::ActivateItem", - 6 - ], - "ctrl-8": [ - "pane::ActivateItem", - 7 - ], - "ctrl-9": [ - "pane::ActivateItem", - 8 - ], - "ctrl-0": "pane::ActivateLastItem", - "ctrl--": "pane::GoBack", - "ctrl-_": "pane::GoForward", - "cmd-shift-t": "pane::ReopenClosedItem", - "cmd-shift-f": "project_search::ToggleFocus" - } - }, - { - "context": "Workspace", - "bindings": { - "cmd-1": [ - "workspace::ActivatePane", - 0 - ], - "cmd-2": [ - "workspace::ActivatePane", - 1 - ], - "cmd-3": [ - "workspace::ActivatePane", - 2 - ], - "cmd-4": [ - "workspace::ActivatePane", - 3 - ], - "cmd-5": [ - "workspace::ActivatePane", - 4 - ], - "cmd-6": [ - "workspace::ActivatePane", - 5 - ], - "cmd-7": [ - "workspace::ActivatePane", - 6 - ], - "cmd-8": [ - "workspace::ActivatePane", - 7 - ], - "cmd-9": [ - "workspace::ActivatePane", - 8 - ], - "cmd-b": "workspace::ToggleLeftDock", - "cmd-shift-f": "workspace::NewSearch", - "cmd-k cmd-t": "theme_selector::Toggle", - "cmd-k cmd-s": "zed::OpenKeymap", - "cmd-t": "project_symbols::Toggle", - "cmd-p": "file_finder::Toggle", - "cmd-shift-p": "command_palette::Toggle", - "cmd-shift-m": "diagnostics::Deploy", - "cmd-shift-e": "project_panel::ToggleFocus", - "cmd-alt-s": "workspace::SaveAll", - "cmd-k m": "language_selector::Toggle" - } - }, - // Bindings from Sublime Text - { - "context": "Editor", - "bindings": { - "ctrl-shift-k": "editor::DeleteLine", - "cmd-shift-d": "editor::DuplicateLine", - "cmd-shift-l": "editor::SplitSelectionIntoLines", - "ctrl-cmd-up": "editor::MoveLineUp", - "ctrl-cmd-down": "editor::MoveLineDown", - "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart", - "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", - "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", - "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", - "ctrl-alt-left": "editor::MoveToPreviousSubwordStart", - "ctrl-alt-b": "editor::MoveToPreviousSubwordStart", - "ctrl-alt-right": "editor::MoveToNextSubwordEnd", - "ctrl-alt-f": "editor::MoveToNextSubwordEnd", - "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart", - "ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart", - "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd", - "ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd" - } - }, - { - "bindings": { - "cmd-k cmd-left": "workspace::ActivatePreviousPane", - "cmd-k cmd-right": "workspace::ActivateNextPane" - } - }, - // Bindings from Atom - { - "context": "Pane", - "bindings": { - "cmd-k up": "pane::SplitUp", - "cmd-k down": "pane::SplitDown", - "cmd-k left": "pane::SplitLeft", - "cmd-k right": "pane::SplitRight" - } - }, - // Bindings that should be unified with bindings for more general actions - { - "context": "Editor && renaming", - "bindings": { - "enter": "editor::ConfirmRename" - } - }, - { - "context": "Editor && showing_completions", - "bindings": { - "enter": "editor::ConfirmCompletion", - "tab": "editor::ConfirmCompletion" - } - }, - { - "context": "Editor && showing_code_actions", - "bindings": { - "enter": "editor::ConfirmCodeAction" - } - }, - // Custom bindings - { - "bindings": { - "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator", - "cmd-shift-c": "collab::ToggleContactsMenu", - "cmd-alt-i": "zed::DebugElements" - } - }, - { - "context": "Editor", - "bindings": { - "alt-enter": "editor::OpenExcerpts", - "cmd-f8": "editor::GoToHunk", - "cmd-shift-f8": "editor::GoToPrevHunk" - } - }, - { - "context": "ProjectSearchBar", - "bindings": { - "cmd-enter": "project_search::SearchInNew" - } - }, - { - "context": "ProjectPanel", - "bindings": { - "left": "project_panel::CollapseSelectedEntry", - "right": "project_panel::ExpandSelectedEntry", - "cmd-x": "project_panel::Cut", - "cmd-c": "project_panel::Copy", - "cmd-v": "project_panel::Paste", - "cmd-alt-c": "project_panel::CopyPath", - "alt-cmd-shift-c": "project_panel::CopyRelativePath", - "f2": "project_panel::Rename", - "backspace": "project_panel::Delete", - "alt-cmd-r": "project_panel::RevealInFinder" - } - }, - { - "context": "Terminal", - "bindings": { - "ctrl-cmd-space": "terminal::ShowCharacterPalette", - "cmd-c": "terminal::Copy", - "cmd-v": "terminal::Paste", - "cmd-k": "terminal::Clear", - // Some nice conveniences - "cmd-backspace": [ - "terminal::SendText", - "\u0015" - ], - "cmd-right": [ - "terminal::SendText", - "\u0005" - ], - "cmd-left": [ - "terminal::SendText", - "\u0001" - ], - // Terminal.app compatability - "alt-left": [ - "terminal::SendText", - "\u001bb" - ], - "alt-right": [ - "terminal::SendText", - "\u001bf" - ], - // There are conflicting bindings for these keys in the global context. - // these bindings override them, remove at your own risk: - "up": [ - "terminal::SendKeystroke", - "up" - ], - "pageup": [ - "terminal::SendKeystroke", - "pageup" - ], - "down": [ - "terminal::SendKeystroke", - "down" - ], - "pagedown": [ - "terminal::SendKeystroke", - "pagedown" - ], - "escape": [ - "terminal::SendKeystroke", - "escape" - ], - "enter": [ - "terminal::SendKeystroke", - "enter" - ], - "ctrl-c": [ - "terminal::SendKeystroke", - "ctrl-c" - ] - } + // Standard macOS bindings + { + "bindings": { + "up": "menu::SelectPrev", + "pageup": "menu::SelectFirst", + "shift-pageup": "menu::SelectFirst", + "ctrl-p": "menu::SelectPrev", + "down": "menu::SelectNext", + "pagedown": "menu::SelectLast", + "shift-pagedown": "menu::SelectFirst", + "ctrl-n": "menu::SelectNext", + "cmd-up": "menu::SelectFirst", + "cmd-down": "menu::SelectLast", + "enter": "menu::Confirm", + "escape": "menu::Cancel", + "ctrl-c": "menu::Cancel", + "cmd-{": "pane::ActivatePrevItem", + "cmd-}": "pane::ActivateNextItem", + "alt-cmd-left": "pane::ActivatePrevItem", + "alt-cmd-right": "pane::ActivateNextItem", + "cmd-w": "pane::CloseActiveItem", + "alt-cmd-t": "pane::CloseInactiveItems", + "cmd-k u": "pane::CloseCleanItems", + "cmd-k cmd-w": "pane::CloseAllItems", + "cmd-shift-w": "workspace::CloseWindow", + "cmd-s": "workspace::Save", + "cmd-shift-s": "workspace::SaveAs", + "cmd-=": "zed::IncreaseBufferFontSize", + "cmd--": "zed::DecreaseBufferFontSize", + "cmd-0": "zed::ResetBufferFontSize", + "cmd-,": "zed::OpenSettings", + "cmd-q": "zed::Quit", + "cmd-h": "zed::Hide", + "alt-cmd-h": "zed::HideOthers", + "cmd-m": "zed::Minimize", + "ctrl-cmd-f": "zed::ToggleFullScreen", + "cmd-n": "workspace::NewFile", + "cmd-shift-n": "workspace::NewWindow", + "cmd-o": "workspace::Open", + "alt-cmd-o": "projects::OpenRecent", + "ctrl-~": "workspace::NewTerminal", + "ctrl-`": "terminal_panel::ToggleFocus" } + }, + { + "context": "Editor", + "bindings": { + "escape": "editor::Cancel", + "backspace": "editor::Backspace", + "shift-backspace": "editor::Backspace", + "ctrl-h": "editor::Backspace", + "delete": "editor::Delete", + "ctrl-d": "editor::Delete", + "tab": "editor::Tab", + "shift-tab": "editor::TabPrev", + "ctrl-k": "editor::CutToEndOfLine", + "ctrl-t": "editor::Transpose", + "cmd-backspace": "editor::DeleteToBeginningOfLine", + "cmd-delete": "editor::DeleteToEndOfLine", + "alt-backspace": "editor::DeleteToPreviousWordStart", + "alt-delete": "editor::DeleteToNextWordEnd", + "alt-h": "editor::DeleteToPreviousWordStart", + "alt-d": "editor::DeleteToNextWordEnd", + "cmd-x": "editor::Cut", + "cmd-c": "editor::Copy", + "cmd-v": "editor::Paste", + "cmd-z": "editor::Undo", + "cmd-shift-z": "editor::Redo", + "up": "editor::MoveUp", + "pageup": "editor::PageUp", + "shift-pageup": "editor::MovePageUp", + "home": "editor::MoveToBeginningOfLine", + "down": "editor::MoveDown", + "pagedown": "editor::PageDown", + "shift-pagedown": "editor::MovePageDown", + "end": "editor::MoveToEndOfLine", + "left": "editor::MoveLeft", + "right": "editor::MoveRight", + "ctrl-p": "editor::MoveUp", + "ctrl-n": "editor::MoveDown", + "ctrl-b": "editor::MoveLeft", + "ctrl-f": "editor::MoveRight", + "ctrl-l": "editor::NextScreen", + "alt-left": "editor::MoveToPreviousWordStart", + "alt-b": "editor::MoveToPreviousWordStart", + "alt-right": "editor::MoveToNextWordEnd", + "alt-f": "editor::MoveToNextWordEnd", + "cmd-left": "editor::MoveToBeginningOfLine", + "ctrl-a": "editor::MoveToBeginningOfLine", + "cmd-right": "editor::MoveToEndOfLine", + "ctrl-e": "editor::MoveToEndOfLine", + "cmd-up": "editor::MoveToBeginning", + "cmd-down": "editor::MoveToEnd", + "shift-up": "editor::SelectUp", + "ctrl-shift-p": "editor::SelectUp", + "shift-down": "editor::SelectDown", + "ctrl-shift-n": "editor::SelectDown", + "shift-left": "editor::SelectLeft", + "ctrl-shift-b": "editor::SelectLeft", + "shift-right": "editor::SelectRight", + "ctrl-shift-f": "editor::SelectRight", + "alt-shift-left": "editor::SelectToPreviousWordStart", + "alt-shift-b": "editor::SelectToPreviousWordStart", + "alt-shift-right": "editor::SelectToNextWordEnd", + "alt-shift-f": "editor::SelectToNextWordEnd", + "cmd-shift-up": "editor::SelectToBeginning", + "cmd-shift-down": "editor::SelectToEnd", + "cmd-a": "editor::SelectAll", + "cmd-l": "editor::SelectLine", + "cmd-shift-i": "editor::Format", + "cmd-shift-left": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true + } + ], + "shift-home": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true + } + ], + "ctrl-shift-a": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true + } + ], + "cmd-shift-right": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true + } + ], + "shift-end": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true + } + ], + "ctrl-shift-e": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true + } + ], + "ctrl-v": [ + "editor::MovePageDown", + { + "center_cursor": true + } + ], + "alt-v": [ + "editor::MovePageUp", + { + "center_cursor": true + } + ], + "ctrl-cmd-space": "editor::ShowCharacterPalette" + } + }, + { + "context": "Editor && mode == full", + "bindings": { + "enter": "editor::Newline", + "cmd-shift-enter": "editor::NewlineAbove", + "cmd-enter": "editor::NewlineBelow", + "alt-z": "editor::ToggleSoftWrap", + "cmd-f": [ + "buffer_search::Deploy", + { + "focus": true + } + ], + "cmd-e": [ + "buffer_search::Deploy", + { + "focus": false + } + ], + "alt-\\": "copilot::Suggest", + "alt-]": "copilot::NextSuggestion", + "alt-[": "copilot::PreviousSuggestion" + } + }, + { + "context": "Editor && mode == auto_height", + "bindings": { + "alt-enter": "editor::Newline", + "cmd-alt-enter": "editor::NewlineBelow" + } + }, + { + "context": "BufferSearchBar > Editor", + "bindings": { + "escape": "buffer_search::Dismiss", + "tab": "buffer_search::FocusEditor", + "enter": "search::SelectNextMatch", + "shift-enter": "search::SelectPrevMatch" + } + }, + { + "context": "ProjectSearchBar > Editor", + "bindings": { + "escape": "project_search::ToggleFocus" + } + }, + { + "context": "ProjectSearchView > Editor", + "bindings": { + "escape": "project_search::ToggleFocus" + } + }, + { + "context": "Pane", + "bindings": { + "cmd-f": "project_search::ToggleFocus", + "cmd-g": "search::SelectNextMatch", + "cmd-shift-g": "search::SelectPrevMatch", + "alt-cmd-c": "search::ToggleCaseSensitive", + "alt-cmd-w": "search::ToggleWholeWord", + "alt-cmd-r": "search::ToggleRegex", + "shift-escape": "workspace::ToggleZoom" + } + }, + // Bindings from VS Code + { + "context": "Editor", + "bindings": { + "cmd-[": "editor::Outdent", + "cmd-]": "editor::Indent", + "cmd-alt-up": "editor::AddSelectionAbove", + "cmd-ctrl-p": "editor::AddSelectionAbove", + "cmd-alt-down": "editor::AddSelectionBelow", + "cmd-ctrl-n": "editor::AddSelectionBelow", + "cmd-d": [ + "editor::SelectNext", + { + "replace_newest": false + } + ], + "cmd-k cmd-d": [ + "editor::SelectNext", + { + "replace_newest": true + } + ], + "cmd-k cmd-i": "editor::Hover", + "cmd-/": [ + "editor::ToggleComments", + { + "advance_downwards": false + } + ], + "alt-up": "editor::SelectLargerSyntaxNode", + "alt-down": "editor::SelectSmallerSyntaxNode", + "cmd-u": "editor::UndoSelection", + "cmd-shift-u": "editor::RedoSelection", + "f8": "editor::GoToDiagnostic", + "shift-f8": "editor::GoToPrevDiagnostic", + "f2": "editor::Rename", + "f12": "editor::GoToDefinition", + "cmd-f12": "editor::GoToTypeDefinition", + "alt-shift-f12": "editor::FindAllReferences", + "ctrl-m": "editor::MoveToEnclosingBracket", + "alt-cmd-[": "editor::Fold", + "alt-cmd-]": "editor::UnfoldLines", + "ctrl-space": "editor::ShowCompletions", + "cmd-.": "editor::ToggleCodeActions", + "alt-cmd-r": "editor::RevealInFinder" + } + }, + { + "context": "Editor && mode == full", + "bindings": { + "cmd-shift-o": "outline::Toggle", + "ctrl-g": "go_to_line::Toggle" + } + }, + { + "context": "Pane", + "bindings": { + "ctrl-1": [ + "pane::ActivateItem", + 0 + ], + "ctrl-2": [ + "pane::ActivateItem", + 1 + ], + "ctrl-3": [ + "pane::ActivateItem", + 2 + ], + "ctrl-4": [ + "pane::ActivateItem", + 3 + ], + "ctrl-5": [ + "pane::ActivateItem", + 4 + ], + "ctrl-6": [ + "pane::ActivateItem", + 5 + ], + "ctrl-7": [ + "pane::ActivateItem", + 6 + ], + "ctrl-8": [ + "pane::ActivateItem", + 7 + ], + "ctrl-9": [ + "pane::ActivateItem", + 8 + ], + "ctrl-0": "pane::ActivateLastItem", + "ctrl--": "pane::GoBack", + "ctrl-_": "pane::GoForward", + "cmd-shift-t": "pane::ReopenClosedItem", + "cmd-shift-f": "project_search::ToggleFocus" + } + }, + { + "context": "Workspace", + "bindings": { + "cmd-1": [ + "workspace::ActivatePane", + 0 + ], + "cmd-2": [ + "workspace::ActivatePane", + 1 + ], + "cmd-3": [ + "workspace::ActivatePane", + 2 + ], + "cmd-4": [ + "workspace::ActivatePane", + 3 + ], + "cmd-5": [ + "workspace::ActivatePane", + 4 + ], + "cmd-6": [ + "workspace::ActivatePane", + 5 + ], + "cmd-7": [ + "workspace::ActivatePane", + 6 + ], + "cmd-8": [ + "workspace::ActivatePane", + 7 + ], + "cmd-9": [ + "workspace::ActivatePane", + 8 + ], + "cmd-b": "workspace::ToggleLeftDock", + "cmd-shift-f": "workspace::NewSearch", + "cmd-k cmd-t": "theme_selector::Toggle", + "cmd-k cmd-s": "zed::OpenKeymap", + "cmd-t": "project_symbols::Toggle", + "cmd-p": "file_finder::Toggle", + "cmd-shift-p": "command_palette::Toggle", + "cmd-shift-m": "diagnostics::Deploy", + "cmd-shift-e": "project_panel::ToggleFocus", + "cmd-alt-s": "workspace::SaveAll", + "cmd-k m": "language_selector::Toggle" + } + }, + // Bindings from Sublime Text + { + "context": "Editor", + "bindings": { + "ctrl-shift-k": "editor::DeleteLine", + "cmd-shift-d": "editor::DuplicateLine", + "cmd-shift-l": "editor::SplitSelectionIntoLines", + "ctrl-cmd-up": "editor::MoveLineUp", + "ctrl-cmd-down": "editor::MoveLineDown", + "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart", + "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", + "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", + "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", + "ctrl-alt-left": "editor::MoveToPreviousSubwordStart", + "ctrl-alt-b": "editor::MoveToPreviousSubwordStart", + "ctrl-alt-right": "editor::MoveToNextSubwordEnd", + "ctrl-alt-f": "editor::MoveToNextSubwordEnd", + "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart", + "ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart", + "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd", + "ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd" + } + }, + { + "bindings": { + "cmd-k cmd-left": "workspace::ActivatePreviousPane", + "cmd-k cmd-right": "workspace::ActivateNextPane" + } + }, + // Bindings from Atom + { + "context": "Pane", + "bindings": { + "cmd-k up": "pane::SplitUp", + "cmd-k down": "pane::SplitDown", + "cmd-k left": "pane::SplitLeft", + "cmd-k right": "pane::SplitRight" + } + }, + // Bindings that should be unified with bindings for more general actions + { + "context": "Editor && renaming", + "bindings": { + "enter": "editor::ConfirmRename" + } + }, + { + "context": "Editor && showing_completions", + "bindings": { + "enter": "editor::ConfirmCompletion", + "tab": "editor::ConfirmCompletion" + } + }, + { + "context": "Editor && showing_code_actions", + "bindings": { + "enter": "editor::ConfirmCodeAction" + } + }, + // Custom bindings + { + "bindings": { + "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator", + "cmd-shift-c": "collab::ToggleContactsMenu", + "cmd-alt-i": "zed::DebugElements" + } + }, + { + "context": "Editor", + "bindings": { + "alt-enter": "editor::OpenExcerpts", + "cmd-f8": "editor::GoToHunk", + "cmd-shift-f8": "editor::GoToPrevHunk" + } + }, + { + "context": "ProjectSearchBar", + "bindings": { + "cmd-enter": "project_search::SearchInNew" + } + }, + { + "context": "ProjectPanel", + "bindings": { + "left": "project_panel::CollapseSelectedEntry", + "right": "project_panel::ExpandSelectedEntry", + "cmd-x": "project_panel::Cut", + "cmd-c": "project_panel::Copy", + "cmd-v": "project_panel::Paste", + "cmd-alt-c": "project_panel::CopyPath", + "alt-cmd-shift-c": "project_panel::CopyRelativePath", + "f2": "project_panel::Rename", + "backspace": "project_panel::Delete", + "alt-cmd-r": "project_panel::RevealInFinder" + } + }, + { + "context": "Terminal", + "bindings": { + "ctrl-cmd-space": "terminal::ShowCharacterPalette", + "cmd-c": "terminal::Copy", + "cmd-v": "terminal::Paste", + "cmd-k": "terminal::Clear", + // Some nice conveniences + "cmd-backspace": [ + "terminal::SendText", + "\u0015" + ], + "cmd-right": [ + "terminal::SendText", + "\u0005" + ], + "cmd-left": [ + "terminal::SendText", + "\u0001" + ], + // Terminal.app compatability + "alt-left": [ + "terminal::SendText", + "\u001bb" + ], + "alt-right": [ + "terminal::SendText", + "\u001bf" + ], + // There are conflicting bindings for these keys in the global context. + // these bindings override them, remove at your own risk: + "up": [ + "terminal::SendKeystroke", + "up" + ], + "pageup": [ + "terminal::SendKeystroke", + "pageup" + ], + "down": [ + "terminal::SendKeystroke", + "down" + ], + "pagedown": [ + "terminal::SendKeystroke", + "pagedown" + ], + "escape": [ + "terminal::SendKeystroke", + "escape" + ], + "enter": [ + "terminal::SendKeystroke", + "enter" + ], + "ctrl-c": [ + "terminal::SendKeystroke", + "ctrl-c" + ] + } + } ] From 10e947cb5f8948817036d6302c32dd9a0a043d41 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 15:55:44 +0200 Subject: [PATCH 50/61] Persist project and terminal panel sizes --- Cargo.lock | 1 + crates/project_panel/Cargo.toml | 2 + crates/project_panel/src/project_panel.rs | 77 ++++++++++++++++++++-- crates/terminal/src/terminal.rs | 1 + crates/terminal_view/src/terminal_panel.rs | 36 ++++++++-- crates/workspace/src/dock.rs | 47 +++++++------ crates/workspace/src/workspace.rs | 20 ++---- crates/zed/src/zed.rs | 9 +-- 8 files changed, 139 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2aea69e90b..b8b57e8b8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4888,6 +4888,7 @@ version = "0.1.0" dependencies = [ "client", "context_menu", + "db", "drag_and_drop", "editor", "futures 0.3.28", diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml index 8cd5a06675..aab5b8a41c 100644 --- a/crates/project_panel/Cargo.toml +++ b/crates/project_panel/Cargo.toml @@ -10,6 +10,7 @@ doctest = false [dependencies] context_menu = { path = "../context_menu" } +db = { path = "../db" } drag_and_drop = { path = "../drag_and_drop" } editor = { path = "../editor" } gpui = { path = "../gpui" } @@ -23,6 +24,7 @@ postage.workspace = true futures.workspace = true schemars.workspace = true serde.workspace = true +serde_json.workspace = true unicase = "2.6" [dev-dependencies] diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 761d803084..010a305f16 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1,10 +1,11 @@ use context_menu::{ContextMenu, ContextMenuItem}; +use db::kvp::KEY_VALUE_STORE; use drag_and_drop::{DragAndDrop, Draggable}; use editor::{Cancel, Editor}; use futures::stream::StreamExt; use gpui::{ actions, - anyhow::{anyhow, Result}, + anyhow::{self, anyhow, Result}, elements::{ AnchorCorner, ChildView, ComponentHost, ContainerStyle, Empty, Flex, MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, @@ -12,8 +13,8 @@ use gpui::{ geometry::vector::Vector2F, keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, - AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext, - ViewHandle, WeakViewHandle, WindowContext, + AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, ModelHandle, Task, + View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{ @@ -33,11 +34,13 @@ use std::{ }; use theme::{ui::FileName, ProjectPanelEntry}; use unicase::UniCase; +use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel}, Workspace, }; +const PROJECT_PANEL_KEY: &'static str = "ProjectPanel"; const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX; #[derive(Deserialize)] @@ -67,6 +70,7 @@ pub struct ProjectPanelSettingsContent { } #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] pub enum ProjectPanelDockPosition { Left, Right, @@ -87,6 +91,8 @@ pub struct ProjectPanel { dragged_entry_destination: Option>, workspace: WeakViewHandle, has_focus: bool, + width: Option, + pending_serialization: Task>, } #[derive(Copy, Clone)] @@ -183,8 +189,13 @@ pub enum Event { Focus, } +#[derive(Serialize, Deserialize)] +struct SerializedProjectPanel { + width: Option, +} + impl ProjectPanel { - pub fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> ViewHandle { + fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> ViewHandle { let project = workspace.project().clone(); let project_panel = cx.add_view(|cx: &mut ViewContext| { cx.observe(&project, |this, _, cx| { @@ -258,6 +269,8 @@ impl ProjectPanel { dragged_entry_destination: None, workspace: workspace.weak_handle(), has_focus: false, + width: None, + pending_serialization: Task::ready(None), }; this.update_visible_entries(None, cx); @@ -311,6 +324,51 @@ impl ProjectPanel { project_panel } + pub fn load( + workspace: WeakViewHandle, + cx: AsyncAppContext, + ) -> Task>> { + cx.spawn(|mut cx| async move { + let serialized_panel = if let Some(panel) = cx + .background() + .spawn(async move { KEY_VALUE_STORE.read_kvp(PROJECT_PANEL_KEY) }) + .await + .log_err() + .flatten() + { + Some(serde_json::from_str::(&panel)?) + } else { + None + }; + workspace.update(&mut cx, |workspace, cx| { + let panel = ProjectPanel::new(workspace, cx); + if let Some(serialized_panel) = serialized_panel { + panel.update(cx, |panel, cx| { + panel.width = serialized_panel.width; + cx.notify(); + }); + } + panel + }) + }) + } + + fn serialize(&mut self, cx: &mut ViewContext) { + let width = self.width; + self.pending_serialization = cx.background().spawn( + async move { + KEY_VALUE_STORE + .write_kvp( + PROJECT_PANEL_KEY.into(), + serde_json::to_string(&SerializedProjectPanel { width })?, + ) + .await?; + anyhow::Ok(()) + } + .log_err(), + ); + } + fn deploy_context_menu( &mut self, position: Vector2F, @@ -1435,8 +1493,15 @@ impl workspace::dock::Panel for ProjectPanel { ); } - fn default_size(&self, cx: &WindowContext) -> f32 { - settings::get::(cx).default_width + fn size(&self, cx: &WindowContext) -> f32 { + self.width + .unwrap_or_else(|| settings::get::(cx).default_width) + } + + fn set_size(&mut self, size: f32, cx: &mut ViewContext) { + self.width = Some(size); + self.serialize(cx); + cx.notify(); } fn should_zoom_in_on_event(_: &Self::Event) -> bool { diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 9cf3f52924..576719526d 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -120,6 +120,7 @@ pub fn init(cx: &mut AppContext) { } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] pub enum TerminalDockPosition { Left, Bottom, diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index afd564fb56..9c8073eada 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -37,12 +37,14 @@ pub struct TerminalPanel { pane: ViewHandle, fs: Arc, workspace: WeakViewHandle, + width: Option, + height: Option, pending_serialization: Task>, _subscriptions: Vec, } impl TerminalPanel { - pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { let weak_self = cx.weak_handle(); let pane = cx.add_view(|cx| { let window_id = cx.window_id(); @@ -90,6 +92,8 @@ impl TerminalPanel { fs: workspace.app_state().fs.clone(), workspace: workspace.weak_handle(), pending_serialization: Task::ready(None), + width: None, + height: None, _subscriptions: subscriptions, }; let mut old_dock_position = this.position(cx); @@ -112,7 +116,9 @@ impl TerminalPanel { let serialized_panel = if let Some(panel) = cx .background() .spawn(async move { KEY_VALUE_STORE.read_kvp(TERMINAL_PANEL_KEY) }) - .await? + .await + .log_err() + .flatten() { Some(serde_json::from_str::(&panel)?) } else { @@ -122,6 +128,9 @@ impl TerminalPanel { let panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); let items = if let Some(serialized_panel) = serialized_panel.as_ref() { panel.update(cx, |panel, cx| { + cx.notify(); + panel.height = serialized_panel.height; + panel.width = serialized_panel.width; panel.pane.update(cx, |_, cx| { serialized_panel .items @@ -226,6 +235,8 @@ impl TerminalPanel { .map(|item| item.id()) .collect::>(); let active_item_id = self.pane.read(cx).active_item().map(|item| item.id()); + let height = self.height; + let width = self.width; self.pending_serialization = cx.background().spawn( async move { KEY_VALUE_STORE @@ -234,6 +245,8 @@ impl TerminalPanel { serde_json::to_string(&SerializedTerminalPanel { items, active_item_id, + height, + width, })?, ) .await?; @@ -288,14 +301,25 @@ impl Panel for TerminalPanel { }); } - fn default_size(&self, cx: &WindowContext) -> f32 { + fn size(&self, cx: &WindowContext) -> f32 { let settings = settings::get::(cx); match self.position(cx) { - DockPosition::Left | DockPosition::Right => settings.default_width, - DockPosition::Bottom => settings.default_height, + DockPosition::Left | DockPosition::Right => { + self.width.unwrap_or_else(|| settings.default_width) + } + DockPosition::Bottom => self.height.unwrap_or_else(|| settings.default_height), } } + fn set_size(&mut self, size: f32, cx: &mut ViewContext) { + match self.position(cx) { + DockPosition::Left | DockPosition::Right => self.width = Some(size), + DockPosition::Bottom => self.height = Some(size), + } + self.serialize(cx); + cx.notify(); + } + fn should_zoom_in_on_event(event: &Event) -> bool { matches!(event, Event::ZoomIn) } @@ -360,4 +384,6 @@ impl Panel for TerminalPanel { struct SerializedTerminalPanel { items: Vec, active_item_id: Option, + width: Option, + height: Option, } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index c327c4ded8..c4693b3403 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -13,7 +13,8 @@ pub trait Panel: View { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); - fn default_size(&self, cx: &WindowContext) -> f32; + fn size(&self, cx: &WindowContext) -> f32; + fn set_size(&mut self, size: f32, cx: &mut ViewContext); fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; fn icon_label(&self, _: &WindowContext) -> Option { @@ -39,7 +40,8 @@ pub trait PanelHandle { fn is_zoomed(&self, cx: &WindowContext) -> bool; fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext); fn set_active(&self, active: bool, cx: &mut WindowContext); - fn default_size(&self, cx: &WindowContext) -> f32; + fn size(&self, cx: &WindowContext) -> f32; + fn set_size(&self, size: f32, cx: &mut WindowContext); fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -67,8 +69,12 @@ where self.update(cx, |this, cx| this.set_position(position, cx)) } - fn default_size(&self, cx: &WindowContext) -> f32 { - self.read(cx).default_size(cx) + fn size(&self, cx: &WindowContext) -> f32 { + self.read(cx).size(cx) + } + + fn set_size(&self, size: f32, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.set_size(size, cx)) } fn is_zoomed(&self, cx: &WindowContext) -> bool { @@ -151,7 +157,6 @@ impl DockPosition { struct PanelEntry { panel: Rc, - size: f32, context_menu: ViewHandle, _subscriptions: [Subscription; 2], } @@ -271,10 +276,8 @@ impl Dock { ]; let dock_view_id = cx.view_id(); - let size = panel.default_size(cx); self.panel_entries.push(PanelEntry { panel: Rc::new(panel), - size, context_menu: cx.add_view(|cx| { let mut menu = ContextMenu::new(dock_view_id, cx); menu.set_position_mode(OverlayPositionMode::Local); @@ -343,28 +346,18 @@ impl Dock { } } - pub fn panel_size(&self, panel: &dyn PanelHandle) -> Option { + pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option { self.panel_entries .iter() .find(|entry| entry.panel.id() == panel.id()) - .map(|entry| entry.size) + .map(|entry| entry.panel.size(cx)) } - pub fn resize_panel(&mut self, panel: &dyn PanelHandle, size: f32) { - let entry = self - .panel_entries - .iter_mut() - .find(|entry| entry.panel.id() == panel.id()); - if let Some(entry) = entry { - entry.size = size; - } - } - - pub fn active_panel_size(&self) -> Option { + pub fn active_panel_size(&self, cx: &WindowContext) -> Option { if self.is_open { self.panel_entries .get(self.active_panel_index) - .map(|entry| entry.size) + .map(|entry| entry.panel.size(cx)) } else { None } @@ -372,7 +365,7 @@ impl Dock { pub fn resize_active_panel(&mut self, size: f32, cx: &mut ViewContext) { if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) { - entry.size = size; + entry.panel.set_size(size, cx); cx.notify(); } } @@ -386,7 +379,7 @@ impl Dock { .with_style(style.container) .resizable( self.position.to_resize_handle_side(), - active_entry.size, + active_entry.panel.size(cx), |_, _, _| {}, ) .into_any() @@ -413,7 +406,7 @@ impl View for Dock { .with_style(style.container) .resizable( self.position.to_resize_handle_side(), - active_entry.size, + active_entry.panel.size(cx), |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), ) .into_any() @@ -630,13 +623,17 @@ pub(crate) mod test { unimplemented!() } - fn default_size(&self, _: &WindowContext) -> f32 { + fn size(&self, _: &WindowContext) -> f32 { match self.position.axis() { Axis::Horizontal => 300., Axis::Vertical => 200., } } + fn set_size(&mut self, _: f32, _: &mut ViewContext) { + unimplemented!() + } + fn icon_path(&self) -> &'static str { "icons/test_panel.svg" } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 78900eb002..6c7d61fef9 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -853,11 +853,7 @@ impl Workspace { if T::should_change_position_on_event(event) { let new_position = panel.read(cx).position(cx); let mut was_visible = false; - let mut size = None; dock.update(cx, |dock, cx| { - if new_position.axis() == prev_position.axis() { - size = dock.panel_size(&panel); - } prev_position = new_position; was_visible = dock.is_open() @@ -874,10 +870,6 @@ impl Workspace { .clone(); dock.update(cx, |dock, cx| { dock.add_panel(panel.clone(), cx); - if let Some(size) = size { - dock.resize_panel(&panel, size); - } - if was_visible { dock.set_open(true, cx); dock.activate_panel(dock.panels_len() - 1, cx); @@ -3961,8 +3953,8 @@ mod tests { panel_1.id() ); assert_eq!( - left_dock.read(cx).active_panel_size().unwrap(), - panel_1.default_size(cx) + left_dock.read(cx).active_panel_size(cx).unwrap(), + panel_1.size(cx) ); left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx)); @@ -3989,7 +3981,7 @@ mod tests { right_dock.read(cx).active_panel().unwrap().id(), panel_1.id() ); - assert_eq!(right_dock.read(cx).active_panel_size().unwrap(), 1337.); + assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); // Now we move panel_2 to the left panel_2.set_position(DockPosition::Left, cx); @@ -4019,7 +4011,7 @@ mod tests { left_dock.read(cx).active_panel().unwrap().id(), panel_1.id() ); - assert_eq!(left_dock.read(cx).active_panel_size().unwrap(), 1337.); + assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); // And right the dock should be closed as it no longer has any panels. assert!(!workspace.right_dock().read(cx).is_open()); @@ -4034,8 +4026,8 @@ mod tests { // since the panel orientation changed from vertical to horizontal. let bottom_dock = workspace.bottom_dock(); assert_eq!( - bottom_dock.read(cx).active_panel_size().unwrap(), - panel_1.default_size(cx), + bottom_dock.read(cx).active_panel_size(cx).unwrap(), + panel_1.size(cx), ); // Close bottom dock and move panel_1 back to the left. bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 0c4b63081c..be0c566bb1 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -335,8 +335,12 @@ pub fn initialize_workspace( } false }); + })?; - let project_panel = ProjectPanel::new(workspace, cx); + let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone()); + let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone()); + let (project_panel, terminal_panel) = futures::try_join!(project_panel, terminal_panel)?; + workspace_handle.update(&mut cx, |workspace, cx| { let project_panel_position = project_panel.position(cx); workspace.add_panel(project_panel, cx); if !was_deserialized @@ -352,10 +356,7 @@ pub fn initialize_workspace( { workspace.toggle_dock(project_panel_position, cx); } - })?; - let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone()).await?; - workspace_handle.update(&mut cx, |workspace, cx| { workspace.add_panel(terminal_panel, cx) })?; Ok(()) From 185a624b99a2cd5cd73bab32d2a43973b8c21339 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 16:18:53 +0200 Subject: [PATCH 51/61] Fix some project panel tests --- crates/project_panel/src/project_panel.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 010a305f16..9159e8fa98 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -2243,6 +2243,7 @@ mod tests { theme::init((), cx); language::init(cx); editor::init_settings(cx); + crate::init(cx); workspace::init_settings(cx); }); } @@ -2255,6 +2256,7 @@ mod tests { language::init(cx); editor::init(cx); pane::init(cx); + crate::init(cx); workspace::init(app_state.clone(), cx); }); } From 4aa2858b2b69e22b9769df48db5bec1427fade58 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 18:05:08 +0200 Subject: [PATCH 52/61] Transfer focus to root view only if previously-focused view was dropped --- crates/gpui/src/app.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index bc89503459..0fafe76942 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1749,11 +1749,11 @@ impl AppContext { } } - // When the previously-focused view isn't rendered and + // When the previously-focused view has been dropped and // there isn't any pending focus, focus the root view. let root_view_id = cx.window.root_view().id(); if focused_view_id != root_view_id - && !cx.window.parents.contains_key(&focused_view_id) + && !cx.views.contains_key(&(window_id, focused_view_id)) && !focus_effects.contains_key(&window_id) { focus_effects.insert( @@ -1942,12 +1942,12 @@ impl AppContext { fn handle_focus_effect(&mut self, effect: FocusEffect) { let window_id = effect.window_id(); self.update_window(window_id, |cx| { - // Ensure the newly-focused view has been rendered, otherwise focus + // Ensure the newly-focused view still exists, otherwise focus // the root view instead. let focused_id = match effect { FocusEffect::View { view_id, .. } => { if let Some(view_id) = view_id { - if cx.window.parents.contains_key(&view_id) { + if cx.views.contains_key(&(window_id, view_id)) { Some(view_id) } else { Some(cx.root_view().id()) From deaf60005c258d282159b263a34a07c85ddde528 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 18:21:12 +0200 Subject: [PATCH 53/61] Fix more tests --- crates/zed/src/zed.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index be0c566bb1..4c4137b7f3 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1396,7 +1396,7 @@ mod tests { cx.foreground().run_until_parked(); workspace.read_with(cx, |workspace, _| { - assert_eq!(workspace.panes().len(), 2); //Center pane + Dock pane + assert_eq!(workspace.panes().len(), 1); assert_eq!(workspace.active_pane(), &pane_1); }); @@ -1406,7 +1406,7 @@ mod tests { cx.foreground().run_until_parked(); workspace.read_with(cx, |workspace, cx| { - assert_eq!(workspace.panes().len(), 2); + assert_eq!(workspace.panes().len(), 1); assert!(workspace.active_item(cx).is_none()); }); @@ -2088,6 +2088,8 @@ mod tests { language::init(cx); editor::init(cx); pane::init(cx); + project_panel::init(cx); + terminal_view::init(cx); app_state }) } From 5b7e852903c3b1e9b4c42505ef44f9d47187e16a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 18:28:30 +0200 Subject: [PATCH 54/61] Await closing of items in tests --- crates/workspace/src/pane.rs | 59 ++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 5f698d89a5..aae900c861 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2343,23 +2343,31 @@ mod tests { add_labeled_item(&workspace, &pane, "1", false, cx); assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "B*", "C", "D"], cx); pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx)); assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "B*", "C"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "C*"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A*"], cx); } @@ -2376,9 +2384,10 @@ mod tests { pane.update(cx, |pane, cx| { pane.close_inactive_items(&CloseInactiveItems, cx) - }); - - deterministic.run_until_parked(); + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["C*"], cx); } @@ -2398,9 +2407,10 @@ mod tests { add_labeled_item(&workspace, &pane, "E", false, cx); assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx); - pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)); - - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A^", "C*^"], cx); } @@ -2420,9 +2430,10 @@ mod tests { pane.update(cx, |pane, cx| { pane.close_items_to_the_left(&CloseItemsToTheLeft, cx) - }); - - deterministic.run_until_parked(); + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["C*", "D", "E"], cx); } @@ -2442,9 +2453,10 @@ mod tests { pane.update(cx, |pane, cx| { pane.close_items_to_the_right(&CloseItemsToTheRight, cx) - }); - - deterministic.run_until_parked(); + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "B", "C*"], cx); } @@ -2462,9 +2474,10 @@ mod tests { add_labeled_item(&workspace, &pane, "C", false, cx); assert_item_labels(&pane, ["A", "B", "C*"], cx); - pane.update(cx, |pane, cx| pane.close_all_items(&CloseAllItems, cx)); - - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_all_items(&CloseAllItems, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, [], cx); } From 75a0742c90028ae3e0128714ecea27b658a37280 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 18:32:28 +0200 Subject: [PATCH 55/61] Uncomment persistence test --- crates/workspace/src/persistence.rs | 37 ++++++++++++----------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index fbefd3984e..76b66c5329 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -579,7 +579,7 @@ mod tests { docks: Default::default(), }; - let mut _workspace_2 = SerializedWorkspace { + let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), center_group: Default::default(), @@ -597,7 +597,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 (?, ?))) @@ -609,28 +609,21 @@ mod tests { workspace_1.location = (["/tmp", "/tmp3"]).into(); db.save_workspace(workspace_1.clone()).await; db.save_workspace(workspace_1).await; + 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] From 3ca95678f1698c42699847c6d5034a35b5401713 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 18:57:14 +0200 Subject: [PATCH 56/61] Avoid leaking docks when adding panels --- crates/workspace/src/workspace.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6c7d61fef9..bd50251cf1 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -477,7 +477,7 @@ pub struct Workspace { leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: WorkspaceId, app_state: Arc, - _subscriptions: Vec, + subscriptions: Vec, _apply_leader_updates: Task>, _observe_current_user: Task>, pane_history_timestamp: Arc, @@ -683,7 +683,7 @@ impl Workspace { _observe_current_user, _apply_leader_updates, leader_updates_tx, - _subscriptions: subscriptions, + subscriptions, pane_history_timestamp, }; this.project_remote_id_changed(project.read(cx).remote_id(), cx); @@ -846,7 +846,7 @@ impl Workspace { DockPosition::Right => &self.right_dock, }; - cx.subscribe(&panel, { + self.subscriptions.push(cx.subscribe(&panel, { let mut dock = dock.clone(); let mut prev_position = panel.position(cx); move |this, panel, event, cx| { @@ -884,8 +884,7 @@ impl Workspace { cx.notify(); } } - }) - .detach(); + })); dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); } From 048498e39be19fdef8e196b67c7b97ee88c65c25 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 19:30:31 +0200 Subject: [PATCH 57/61] Test zooming panels --- crates/workspace/src/dock.rs | 60 +++++++++++++++++++++---------- crates/workspace/src/workspace.rs | 32 +++++++++++++---- 2 files changed, 68 insertions(+), 24 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index c4693b3403..8f44419ddc 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -577,10 +577,29 @@ pub(crate) mod test { PositionChanged, Activated, Closed, + ZoomIn, + ZoomOut, + Focus, } pub struct TestPanel { pub position: DockPosition, + pub zoomed: bool, + pub active: bool, + pub has_focus: bool, + pub size: f32, + } + + impl TestPanel { + pub fn new(position: DockPosition) -> Self { + Self { + position, + zoomed: false, + active: false, + has_focus: false, + size: 300., + } + } } impl Entity for TestPanel { @@ -595,6 +614,14 @@ pub(crate) mod test { fn render(&mut self, _: &mut ViewContext<'_, '_, Self>) -> AnyElement { Empty::new().into_any() } + + fn focus_in(&mut self, _: AnyViewHandle, _: &mut ViewContext) { + self.has_focus = true; + } + + fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext) { + self.has_focus = false; + } } impl Panel for TestPanel { @@ -612,26 +639,23 @@ pub(crate) mod test { } fn is_zoomed(&self, _: &WindowContext) -> bool { - unimplemented!() + self.zoomed } - fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext) { - unimplemented!() + fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext) { + self.zoomed = zoomed; } - fn set_active(&mut self, _active: bool, _cx: &mut ViewContext) { - unimplemented!() + fn set_active(&mut self, active: bool, _cx: &mut ViewContext) { + self.active = active; } fn size(&self, _: &WindowContext) -> f32 { - match self.position.axis() { - Axis::Horizontal => 300., - Axis::Vertical => 200., - } + self.size } - fn set_size(&mut self, _: f32, _: &mut ViewContext) { - unimplemented!() + fn set_size(&mut self, size: f32, _: &mut ViewContext) { + self.size = size; } fn icon_path(&self) -> &'static str { @@ -646,12 +670,12 @@ pub(crate) mod test { matches!(event, TestPanelEvent::PositionChanged) } - fn should_zoom_in_on_event(_: &Self::Event) -> bool { - false + fn should_zoom_in_on_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::ZoomIn) } - fn should_zoom_out_on_event(_: &Self::Event) -> bool { - false + fn should_zoom_out_on_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::ZoomOut) } fn should_activate_on_event(event: &Self::Event) -> bool { @@ -663,11 +687,11 @@ pub(crate) mod test { } fn has_focus(&self, _cx: &WindowContext) -> bool { - unimplemented!() + self.has_focus } - fn is_focus_event(_: &Self::Event) -> bool { - unimplemented!() + fn is_focus_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::Focus) } } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index bd50251cf1..495989c1be 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3931,16 +3931,12 @@ mod tests { let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { // Add panel_1 on the left, panel_2 on the right. - let panel_1 = cx.add_view(|_| TestPanel { - position: DockPosition::Left, - }); + let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left)); workspace.add_panel(panel_1.clone(), cx); workspace .left_dock() .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); - let panel_2 = cx.add_view(|_| TestPanel { - position: DockPosition::Right, - }); + let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right)); workspace.add_panel(panel_2.clone(), cx); workspace .right_dock() @@ -4060,6 +4056,30 @@ mod tests { ); }); + // Emitting a ZoomIn event shows the panel as zoomed. + panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn)); + workspace.read_with(cx, |workspace, cx| { + assert_eq!(workspace.zoomed(cx), Some(panel_1.clone().into_any())); + }); + + // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed. + workspace.update(cx, |_, cx| cx.focus_self()); + workspace.read_with(cx, |workspace, cx| { + assert_eq!(workspace.zoomed(cx), None); + }); + + // When focus is transferred back to the panel, it is zoomed again. + panel_1.update(cx, |_, cx| cx.focus_self()); + workspace.read_with(cx, |workspace, cx| { + assert_eq!(workspace.zoomed(cx), Some(panel_1.clone().into_any())); + }); + + // Emitting a ZoomOut event unzooms the panel. + panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut)); + workspace.read_with(cx, |workspace, cx| { + assert_eq!(workspace.zoomed(cx), None); + }); + // Emit closed event on panel 1, which is active panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed)); From 6f39d49b184a55d344d6d6920abea74197d5aa1b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 19:33:11 +0200 Subject: [PATCH 58/61] Fix warnings --- crates/workspace/src/dock.rs | 3 ++- crates/workspace/src/pane.rs | 8 ++++---- crates/workspace/src/persistence.rs | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 8f44419ddc..7e6868f9b0 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -615,8 +615,9 @@ pub(crate) mod test { Empty::new().into_any() } - fn focus_in(&mut self, _: AnyViewHandle, _: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { self.has_focus = true; + cx.emit(TestPanelEvent::Focus); } fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext) { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index aae900c861..eb3dd12b33 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2325,7 +2325,7 @@ mod tests { } #[gpui::test] - async fn test_remove_item_ordering(deterministic: Arc, cx: &mut TestAppContext) { + async fn test_remove_item_ordering(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); @@ -2372,7 +2372,7 @@ mod tests { } #[gpui::test] - async fn test_close_inactive_items(deterministic: Arc, cx: &mut TestAppContext) { + async fn test_close_inactive_items(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); @@ -2392,7 +2392,7 @@ mod tests { } #[gpui::test] - async fn test_close_clean_items(deterministic: Arc, cx: &mut TestAppContext) { + async fn test_close_clean_items(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); @@ -2461,7 +2461,7 @@ mod tests { } #[gpui::test] - async fn test_close_all_items(deterministic: Arc, cx: &mut TestAppContext) { + async fn test_close_all_items(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 76b66c5329..d27818d202 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -579,7 +579,7 @@ mod tests { docks: Default::default(), }; - let mut workspace_2 = SerializedWorkspace { + let workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), center_group: Default::default(), From 33f6c56b1465e5628d791f2b2f74307157064c47 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 19:36:01 +0200 Subject: [PATCH 59/61] Fix more warnings --- crates/workspace/src/pane.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index eb3dd12b33..e07bd0c9cf 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2415,10 +2415,7 @@ mod tests { } #[gpui::test] - async fn test_close_items_to_the_left( - deterministic: Arc, - cx: &mut TestAppContext, - ) { + async fn test_close_items_to_the_left(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); @@ -2438,10 +2435,7 @@ mod tests { } #[gpui::test] - async fn test_close_items_to_the_right( - deterministic: Arc, - cx: &mut TestAppContext, - ) { + async fn test_close_items_to_the_right(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); From f22067b236bdb0bebb3c58b0790ec78b0d4a549b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 19:38:57 +0200 Subject: [PATCH 60/61] Remove unused imports --- crates/workspace/src/pane.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index e07bd0c9cf..e1079f201b 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2028,11 +2028,9 @@ impl Element for PaneBackdrop { #[cfg(test)] mod tests { - use std::sync::Arc; - use super::*; use crate::item::test::{TestItem, TestProjectItem}; - use gpui::{executor::Deterministic, TestAppContext}; + use gpui::TestAppContext; use project::FakeFs; use settings::SettingsStore; From 19b817e48aa372329b0dde3597651d9a760ea3b0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 23 May 2023 09:02:45 +0200 Subject: [PATCH 61/61] Improve styling of bottom and right docks --- crates/theme/src/theme.rs | 6 +++--- crates/workspace/src/dock.rs | 17 +++++++++++++---- styles/src/styleTree/workspace.ts | 11 +++++++++-- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 31a9f5cc03..b1c9e9c215 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -402,9 +402,9 @@ pub struct StatusBarLspStatus { #[derive(Deserialize, Default)] pub struct Dock { - pub initial_size: f32, - #[serde(flatten)] - pub container: ContainerStyle, + pub left: ContainerStyle, + pub bottom: ContainerStyle, + pub right: ContainerStyle, } #[derive(Clone, Deserialize, Default)] diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 7e6868f9b0..6ca78cd935 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -372,11 +372,10 @@ impl Dock { pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement { if let Some(active_entry) = self.active_entry() { - let style = &settings::get::(cx).theme.workspace.dock; Empty::new() .into_any() .contained() - .with_style(style.container) + .with_style(self.style(cx)) .resizable( self.position.to_resize_handle_side(), active_entry.panel.size(cx), @@ -387,6 +386,16 @@ impl Dock { Empty::new().into_any() } } + + fn style(&self, cx: &WindowContext) -> ContainerStyle { + let theme = &settings::get::(cx).theme; + let style = match self.position { + DockPosition::Left => theme.workspace.dock.left, + DockPosition::Bottom => theme.workspace.dock.bottom, + DockPosition::Right => theme.workspace.dock.right, + }; + style + } } impl Entity for Dock { @@ -400,10 +409,10 @@ impl View for Dock { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(active_entry) = self.active_entry() { - let style = &settings::get::(cx).theme.workspace.dock; + let style = self.style(cx); ChildView::new(active_entry.panel.as_any(), cx) .contained() - .with_style(style.container) + .with_style(style) .resizable( self.position.to_resize_handle_side(), active_entry.panel.size(cx), diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 501debc876..737d225784 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -128,8 +128,15 @@ export default function workspace(colorScheme: ColorScheme) { border: border(colorScheme.highest, { overlay: true }), }, dock: { - initialSize: 240, - border: border(layer, { left: true, right: true }), + left: { + border: border(layer, { right: true }), + }, + bottom: { + border: border(layer, { top: true }), + }, + right: { + border: border(layer, { left: true }), + } }, paneDivider: { color: borderColor(layer),