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 | 180 +--- crates/workspace/src/persistence/model.rs | 84 +- crates/workspace/src/workspace.rs | 137 +-- crates/zed/src/main.rs | 4 +- crates/zed/src/zed.rs | 1 - 18 files changed, 98 insertions(+), 1467 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 99aae2638fb206e3caf648fc55c6075cb12154a6..a1e8cf6d9d189c02bc3a5b6cb1823563fa8739ca 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 b89940a751256d3fd461f7d7b6e818a8fa25c543..6e8b5193138415606210f6b34a4263f6d0fbafe9 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 59e069e7f779f56bd1789fdab54d4737102837fa..0dafe58d32112e11f212978a1f48b9d598b6aa02 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 373f3f4d3a7c0dd978b34ad535dff30b14671fbe..7a285ee824d0fe58699dab65fe149cea16295e5f 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 1abcaa376cdd62fe9d02f064000dc23127c873f2..738b6603ff8718dcc944dd9642133eb701c30491 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 20d07acae05fc26fcb2da87fb39e57cf3c1fcd18..58fb28bc62577b6219fbfb59475b76b08a9d9b0e 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 64768d1a4795679cfd54a8f7231d2c79488f01f7..aeb26231d4651909f535549f1837e5bb9989a03e 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 7ba52fcb5e52e64e1c82f6a0e9f465f1bf8ecc9b..fde7f516a7e6ea4f86ca1c7f82155c20a3ddce6c 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 a92f7285b51d4bad489af8ef929cb8a88ef91074..7b0da416ffcc865978fcaf292913d26c5f3603f7 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 0bd23a0b87598c02d41a2d5ae79aaaefa5ba2a14..8c77295748eb7bcad94740ece4dc1c1c8c350b4a 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 7efcb7f9d31dae6c2dc665bfcd6243843e3f443e..0000000000000000000000000000000000000000 --- 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 1fda55b7834967207a0f4463e548c8ad219bf84e..0000000000000000000000000000000000000000 --- 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 96320a4baf5c5460030e73efd8276f26c7c6f804..1aed1c011e7af69be58a1bff3045001657fc440a 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 d5d79c3ddd3368ed5960747908a6c88d4edd8094..3b7d0ecb8701c4b974781f9ef0bb9e8a123156d3 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; - - 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"); + 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_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 e838d22f0d746c7a717dbcac4804dcc3f593a00a..3a348d610562f15931203b335e3bb5e03387a622 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")); - - 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"]); - - 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 - )) - ); + let _db = Connection::open_memory(Some("workspace_id_round_trips")); + + 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"]); + + // 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 + // )) + // ); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a57cea526bd950c4be5c0bfc1f7d9a5eecb39cc2..84d1832df73bcaca91ac0b90d8f4bf5f0971163c 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 f498078b520f5fe497fb87f8a7f04a0ca4025460..ff969a8c7597d8accae7ceb93cffa0751012405e 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 f687237bd22dd599a3d313bf3d79bdfa0aa64399..46443eb77d110f0056802dd22c288f3c8500dc73 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 a1e8cf6d9d189c02bc3a5b6cb1823563fa8739ca..634aed322abde3333cef56f352a752d03e68005f 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 6e8b5193138415606210f6b34a4263f6d0fbafe9..cc36f79e9f1bcf6b82ead7c5f19dfdb33d31a94c 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 0dafe58d32112e11f212978a1f48b9d598b6aa02..69a583e1e96522f26b935a1aff2424bfdfeedc33 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 7a285ee824d0fe58699dab65fe149cea16295e5f..2d32b77d58a91b0a508a8da03754ce5eada9d64b 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 738b6603ff8718dcc944dd9642133eb701c30491..06be72742950a320ae483df70c37b2db8a4bcf02 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 4b0c9b494a1dc4f6a05455522f95e0301b88b794..0d6bcc115cb3d402d0d452c295d11c9be997c5b0 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 b464d0088707e73be0f71a1c2a333823ce76f13d..5fdb378eb628021c4657b8056a6d3cc36055586a 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 7602ff7db8fefa640a8b0763656140f8f2e28be5..4a1d488eaf6b62a6ca8c92100bef0288fcf5748e 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 7b0da416ffcc865978fcaf292913d26c5f3603f7..cacd21c73306a21714a640b5e6c1e86b38e11bb3 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 8c77295748eb7bcad94740ece4dc1c1c8c350b4a..c239e0ef40cad02ff419c3ecdf88303ea26df4ce 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 a3d91adc910f658ab2bfd3b9af063ae4bc81142b..6adcf27bd1aa18f6f96c1f2433861cdce8e38e09 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 6463ab7d24b9ea6e44ababa2c3768dc7d36b663a..88fc9576bdb6472696129ae6a3e606a4eab28976 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 43c4544611e9f174db5d8d2350501020eaa6d84e..49fd882b65d033cc2b73807ffdf4ff6aba057e9b 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 84d1832df73bcaca91ac0b90d8f4bf5f0971163c..4ab9e0752619ca1ef4ac71cd9405813df1a44d21 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 741b61f323d13cf3682a1c5c9460fb2f97a16661..a98147c51b09ff4e06b8583c68e1206c37e799c6 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 46443eb77d110f0056802dd22c288f3c8500dc73..b3968e45fcc1a5946a86ded208f9f20b78a8d4a8 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 c55160c336275c841b04258836d3ed47f04704ea..8a711992ec589ab652a8b3c4b3d308a4aaf70b10 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 9d0c4de9f78eef989a71ee33c1b9da3fcc8f8a50..fe359b0501b1549c22671e5513dcdf428ae71322 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 0d6bcc115cb3d402d0d452c295d11c9be997c5b0..884d1358f739b3d7a7189d5e7c2d341612032c71 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 5fdb378eb628021c4657b8056a6d3cc36055586a..fc90b38750efef6ea34790b5adb2fc996b9045eb 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 4a1d488eaf6b62a6ca8c92100bef0288fcf5748e..35ab314f3ada6892c7a047b57f649a6737c40a1b 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 cacd21c73306a21714a640b5e6c1e86b38e11bb3..0747f86d2d0c99f1d150e507bb3c3e872ca5a761 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 c239e0ef40cad02ff419c3ecdf88303ea26df4ce..bc6d63944739f40e3570fd07be7daf8af33a59a4 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 88fc9576bdb6472696129ae6a3e606a4eab28976..432ca646c8010674522fc3436ac755efe2f76fe0 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 49fd882b65d033cc2b73807ffdf4ff6aba057e9b..e7571c1bf6c8c5a028d20c92e0af9e772d0fa473 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 4ab9e0752619ca1ef4ac71cd9405813df1a44d21..f1bcb3928fb3418a4cacc829abd3c3dd2ae36d78 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 8a711992ec589ab652a8b3c4b3d308a4aaf70b10..614b6ab34d1e0f7c6775737d84c7aed75de70274 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 0000000000000000000000000000000000000000..19b34209a3e82fcf2c8bdcc78110a70b6baa357b --- /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 dfb2334dc554d6dabc1ea7b5ac0d71d6726daf86..e3a5153ef309fa4505576e2d202046c59eb3ae91 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 bc6d63944739f40e3570fd07be7daf8af33a59a4..67d07a3998d69366441ff698ebe0e506d847bb36 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 432ca646c8010674522fc3436ac755efe2f76fe0..70708837e692e5f59874d113a4adec4f4ec00474 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 1aed1c011e7af69be58a1bff3045001657fc440a..2597b4af811b09ab83bac94d562ea2f8bd2eb7c9 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 f1bcb3928fb3418a4cacc829abd3c3dd2ae36d78..ee02a97439a2823f000fa6d8ec1d3d102c011268 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 b3968e45fcc1a5946a86ded208f9f20b78a8d4a8..52b0ad08980ab2c90a53ddcac1ccf5ebdf9fd6a8 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 614b6ab34d1e0f7c6775737d84c7aed75de70274..eca537c150d566e7fe454d7f32b5a652cc5c6328 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 19b34209a3e82fcf2c8bdcc78110a70b6baa357b..ddc53537b75e5c575992cdd85c8477216c0995ea 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 2597b4af811b09ab83bac94d562ea2f8bd2eb7c9..fc3fda8e6a7fe1de348b49fbff37443d26f117ab 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 961205b9ee72e67a32e152bb18cbad58a7c7a64f..003cf1e4efc54ed7ae54526b5e62b03bef0515e1 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 ddc53537b75e5c575992cdd85c8477216c0995ea..7d715019c364f3c2c0644b3a02d533cdb551425d 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 70708837e692e5f59874d113a4adec4f4ec00474..3acda4429764429000b8525f9778995975d22ba2 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 ee02a97439a2823f000fa6d8ec1d3d102c011268..e79f071267e3f25f1c686605effac66c5053f419 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 e7571c1bf6c8c5a028d20c92e0af9e772d0fa473..0ae9e4f0eac92c50b98d9247fe1ee30e6dc459c0 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 fc3fda8e6a7fe1de348b49fbff37443d26f117ab..c02c5d7ef4bc719ca7d602a9b8fe2681f794bdcf 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 e79f071267e3f25f1c686605effac66c5053f419..59a8e97f3d29a892657282114e007d699093b8a0 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 52b0ad08980ab2c90a53ddcac1ccf5ebdf9fd6a8..662638301dce4bbb9b9cdae7fcb857b7e16842f4 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 35ab314f3ada6892c7a047b57f649a6737c40a1b..cecf6c114f27d567fd30564f91f05c37716a14bb 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 7d715019c364f3c2c0644b3a02d533cdb551425d..ce70f083911eb336cd9e41d9778398ffa3729e10 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 67d07a3998d69366441ff698ebe0e506d847bb36..2d4c5201270aa8ada60345b5da52ea302c6f3482 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 3acda4429764429000b8525f9778995975d22ba2..57d18fd3686a3e91d3c83d55cd7b1cca8ed71a5e 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 ce70f083911eb336cd9e41d9778398ffa3729e10..5b1a7d20dfff1c6beb5d28e382525dd9959e7b79 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 c02c5d7ef4bc719ca7d602a9b8fe2681f794bdcf..fd617e1685d06bd1b9d9628620e283d7625fc1dd 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 57d18fd3686a3e91d3c83d55cd7b1cca8ed71a5e..2b23ceb7f308bc6cc0cb83073757d161eeb5796f 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 5b1a7d20dfff1c6beb5d28e382525dd9959e7b79..90072ea6528cdf20ab56b74040ef6bde5b00f131 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 fd617e1685d06bd1b9d9628620e283d7625fc1dd..0523f082ea5a8d56014a5fe315b2263a66c966f7 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 90072ea6528cdf20ab56b74040ef6bde5b00f131..d963025f89bccd36efb29cff8f65da9e91b4a431 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 e3a5153ef309fa4505576e2d202046c59eb3ae91..b318b572b5635cbede7ccf6687958b1532f1dea5 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 0523f082ea5a8d56014a5fe315b2263a66c966f7..30e782c7501ee19d80a92931c3300fa4b9dc529c 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 2b23ceb7f308bc6cc0cb83073757d161eeb5796f..492f61c86fbdd37131668d1f9b3220e106964ca8 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 cecf6c114f27d567fd30564f91f05c37716a14bb..f326d665d00615052fb021cd2e588f3ccbc5dab3 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 d963025f89bccd36efb29cff8f65da9e91b4a431..be3c3de9d038ccb2b0f31f364f8c018e6443efb1 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 492f61c86fbdd37131668d1f9b3220e106964ca8..a378e00ec34399d40d66f73e7d74d59726f07671 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 0ae9e4f0eac92c50b98d9247fe1ee30e6dc459c0..ddee068d4645f712547047b8a5124c0f51c7ec10 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 59a8e97f3d29a892657282114e007d699093b8a0..d86b25b7463e3e704ce573e7e187bec139959088 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 662638301dce4bbb9b9cdae7fcb857b7e16842f4..394661da418cb97ad016bf16b0a5792503f68a42 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 f326d665d00615052fb021cd2e588f3ccbc5dab3..a595ec62255c6564600d3c402823a2652deb424d 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 fde7f516a7e6ea4f86ca1c7f82155c20a3ddce6c..528812af878febfbd8f2e184c42a5d0b73a028ff 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 be3c3de9d038ccb2b0f31f364f8c018e6443efb1..e9cdb5a1ea58431ec649b40ac112fce52b328b32 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 a378e00ec34399d40d66f73e7d74d59726f07671..100b8e8661e59338e30acb01a1009e9b73629eb7 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 ddee068d4645f712547047b8a5124c0f51c7ec10..a4e034d7412406f5a37543a02222ecaf2b9a994c 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 394661da418cb97ad016bf16b0a5792503f68a42..8a40a5d1d3a6e43313458d7cdc32c4b89130fe39 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 e971af943aba42c94b7050c1ef0fcd5baeca9bae..fc7c1cd9686ebbbf66d4915d5e4a8ed4de9429e4 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 ca73196c8bb04a1e3dba879c23e667ecba1b7e22..1cf8cc986f08afe5325b3fe8f756192969487e99 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 58fb28bc62577b6219fbfb59475b76b08a9d9b0e..5d85e751d482445c7bce29cda0ba08cb89e1fdfa 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 a595ec62255c6564600d3c402823a2652deb424d..8ee701c3cc56ae68267cb47bfa50d54ecb8a934c 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 528812af878febfbd8f2e184c42a5d0b73a028ff..0eb9eb9e5db0b1cb4d799531465d3b117ffcec3a 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 e9cdb5a1ea58431ec649b40ac112fce52b328b32..e2bf2c9e32d9c7239ddbf223709b09640e21e01a 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 100b8e8661e59338e30acb01a1009e9b73629eb7..0e2d84e626738bc2c7064c17aec77c22ad79001e 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 a4e034d7412406f5a37543a02222ecaf2b9a994c..d2b9251a6294349780368789c17232ad2ac2437d 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 d86b25b7463e3e704ce573e7e187bec139959088..1708555f50ce0e9aa78fdf091ac06f300f31697a 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 8ee701c3cc56ae68267cb47bfa50d54ecb8a934c..c22e297a020d40fc5669ffd6c3720b5b4b46d550 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 e2bf2c9e32d9c7239ddbf223709b09640e21e01a..58deda923684e055943d7c2e8256fa09b183c054 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 1708555f50ce0e9aa78fdf091ac06f300f31697a..bfc0d8228f1c80feef85e0180446b9a9e295c1ab 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 0747f86d2d0c99f1d150e507bb3c3e872ca5a761..0000000000000000000000000000000000000000 --- 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 b318b572b5635cbede7ccf6687958b1532f1dea5..2c33cc8f39d1c5e35da3b767148a0b963aa6c960 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 c22e297a020d40fc5669ffd6c3720b5b4b46d550..85694ec629eb943076f6c2381059457f4215c0ac 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 0eb9eb9e5db0b1cb4d799531465d3b117ffcec3a..a49d84c6a874fb0dce6acc9167a7e95849ca32d9 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 0e2d84e626738bc2c7064c17aec77c22ad79001e..3d5c4b6c38abf529a44a8856cb22a5f96e63b256 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 bfc0d8228f1c80feef85e0180446b9a9e295c1ab..d147557103e94550958dd7e55ea066dd47e5cd68 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 85694ec629eb943076f6c2381059457f4215c0ac..4c75035c839cdd776febb33691373d6ede568ea4 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 a49d84c6a874fb0dce6acc9167a7e95849ca32d9..3bd37860715c6c00d1a88954d99efb75065f6712 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 58deda923684e055943d7c2e8256fa09b183c054..4e90a8fec45ee757e0b321a3cb60724e6e126c95 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 3d5c4b6c38abf529a44a8856cb22a5f96e63b256..6f7f6b1c12a9c6c31ab9c265fd0cf8053059fa86 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 d2b9251a6294349780368789c17232ad2ac2437d..dfad53c3a370dc99289666db54c017b8393bed86 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 d5a7a2f3ab158fc3b91ece1a558231627026399d..488f55d0c6e91f1d0b19b75c7acf4f1f79948331 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 6f7f6b1c12a9c6c31ab9c265fd0cf8053059fa86..0948140e8e5b2bf03c526f2714cb6d67a2d3bca1 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 d147557103e94550958dd7e55ea066dd47e5cd68..497b28cff10425da6c6a0bf6324b5594a9e9d479 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 4c75035c839cdd776febb33691373d6ede568ea4..14779f9b6fa0f58d437e75c865584c5e7ef78ef7 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 3bd37860715c6c00d1a88954d99efb75065f6712..67acd12939a3a429c14e0064bdc3c2495108e30c 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 4e90a8fec45ee757e0b321a3cb60724e6e126c95..5d4099c446fc6ee1d696126afec5b65a13c670c0 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 0948140e8e5b2bf03c526f2714cb6d67a2d3bca1..74132098744e5a7f32389ee5cfbbab7208485a1d 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 74132098744e5a7f32389ee5cfbbab7208485a1d..82d8d404259bf73d88e7d0f9fd6a0fbd59449bc4 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 497b28cff10425da6c6a0bf6324b5594a9e9d479..cf89ed43c75c3e648f86ded942d4d803e4ab521f 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 6adcf27bd1aa18f6f96c1f2433861cdce8e38e09..d78b230dd91b2cf5dc0d16cc59311745b27d61b1 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 82d8d404259bf73d88e7d0f9fd6a0fbd59449bc4..5320a4c652cd05b99a82a243828f0434fb5f5044 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 3b7d0ecb8701c4b974781f9ef0bb9e8a123156d3..2ea99581d219b95e331430740af161bbd641056c 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 f946ebf9de81eab14ca9d5e51c2f773c9ed79c97..19da782fb6840a9a39d500db2355ca666154c7a7 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 cf89ed43c75c3e648f86ded942d4d803e4ab521f..791d0d0a44c319abe9db1f87eea139efbf82ccc1 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 ff969a8c7597d8accae7ceb93cffa0751012405e..362b714a9274cdc6d0448b2d1320b7482f23bb40 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 | 122 +++++++++------------ 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, 134 insertions(+), 153 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 5d85e751d482445c7bce29cda0ba08cb89e1fdfa..d1a64996551b60db95e4d97d574216744b59569a 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 e2c4af143c5fba1a0404389231af5f0c3ec1e171..3caa91a3b8127a30f66c613e85382af2b7285d5b 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 0e78cc07fbce76fa73f6c5106ea218b774042281..a3d95893f47b82bc46a84dcc6714f46462887c42 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 { - side: Side, - handle_size: f32, child: AnyElement, - state: Rc, - _state_handle: ElementStateHandle>, + handle_side: HandleSide, + handle_size: f32, + on_resize: Rc)>> } +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 14779f9b6fa0f58d437e75c865584c5e7ef78ef7..ac0df343ae4183f1627ab8e34a11b3aa48a2b351 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 67acd12939a3a429c14e0064bdc3c2495108e30c..df03d4c1f6d72b31e5e9b8d0896a4329b9942a29 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 5d4099c446fc6ee1d696126afec5b65a13c670c0..995c8be50b273d4337be3a75453ae39f8b6d78be 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 5320a4c652cd05b99a82a243828f0434fb5f5044..6ce1e4f691fdbbda679a42f200bbb66506d53155 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 dfad53c3a370dc99289666db54c017b8393bed86..2adbff51fe956cee0cb9799f250b1f549f65aeec 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 3caa91a3b8127a30f66c613e85382af2b7285d5b..f593615ae74db2c5fb1e1d5045978a9b9dc08d9f 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 a3d95893f47b82bc46a84dcc6714f46462887c42..da4b3473b3069ea343d7acdd0cc85c262e7a76cf 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 ac0df343ae4183f1627ab8e34a11b3aa48a2b351..c76d2481efcb27ffd2b42799fd76132e1822bb46 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 df03d4c1f6d72b31e5e9b8d0896a4329b9942a29..618ccb89ac812368a7d022054a57e5130e4971cd 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 6ce1e4f691fdbbda679a42f200bbb66506d53155..cafe8bd5dceedd411e71a32ca57a8f6f0bbf32d4 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 791d0d0a44c319abe9db1f87eea139efbf82ccc1..7bfe180d50640dc0798577574e4a0d5e6075ac03 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 c76d2481efcb27ffd2b42799fd76132e1822bb46..e271c9970b7ba9f29772274e29bed58b6127b1ff 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 cafe8bd5dceedd411e71a32ca57a8f6f0bbf32d4..86c2cc392932f24a5de77ed671ce11c827f2bbba 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 7bfe180d50640dc0798577574e4a0d5e6075ac03..ad717c362b0cdc1b4a973390655d1806ee9c5738 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 2e4fa62e8ed1b7bdade780e366f3c7e92b445264..768e0c6ed1155fd6de2e131031954e4aacbdd5bd 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 488f55d0c6e91f1d0b19b75c7acf4f1f79948331..65418dd99794acc754025b1a0c736ae9b878ecab 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 f593615ae74db2c5fb1e1d5045978a9b9dc08d9f..40b6a50816d155d0a4a2839e70b863306979bdd4 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 ed624922d57362d0040a1d7e28a54ae9b3f28e9c..6f2762db66144f1099bd05cee01be43f399b0141 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 29bb51be684ef52be337fd06bb64a37cfe321684..50f4607d6669f97c43c57c698b42379e47008a4a 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 30e782c7501ee19d80a92931c3300fa4b9dc529c..a216f6181cc734e1029503ffbe6c20590a21ce0a 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 55032b4bc1e79bf40e75c5be5a0a4b7e47f3e836..a1b35c1d65c58695cf2904729bc430898f8a9e5e 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 ad717c362b0cdc1b4a973390655d1806ee9c5738..f4ff8d5c917df637c56665a8e60100bd9437add5 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 fe359b0501b1549c22671e5513dcdf428ae71322..501debc876200e47edea06b44b5d70f342671135 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 e271c9970b7ba9f29772274e29bed58b6127b1ff..b1f0740df08c5f5be602c314a1445e1715df8fb2 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 995c8be50b273d4337be3a75453ae39f8b6d78be..f3bfbdd73061314ce63a041a8a860e1423aa8539 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 86c2cc392932f24a5de77ed671ce11c827f2bbba..6d0b34dc27983cd746c5b2e4a3a27b6f5e55e535 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 768e0c6ed1155fd6de2e131031954e4aacbdd5bd..37c010f0f93c78aae7a3cf3ec6c71f388f52d9bd 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 40b6a50816d155d0a4a2839e70b863306979bdd4..a566751fd5de5cb2e0b842dee466dd55c93c708d 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 6d0b34dc27983cd746c5b2e4a3a27b6f5e55e535..2e4303e70fcead396e4da224e1ee711cc68d1008 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 a216f6181cc734e1029503ffbe6c20590a21ce0a..6ccd1573bc3c53d9efd643b2a7636b59eb8557ab 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 f4ff8d5c917df637c56665a8e60100bd9437add5..af8c958db586c948ad99e8223814fd8bc3ecdc85 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 65418dd99794acc754025b1a0c736ae9b878ecab..9cc1ad908a75af1c9a4709e0864417d188895aec 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 b1f0740df08c5f5be602c314a1445e1715df8fb2..c448ac4e43e577acc2cf3820de0461f5ac3ed98a 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 f3bfbdd73061314ce63a041a8a860e1423aa8539..68783a5bc189e3fc05ad0a14cbad3a54bda8b56e 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 2e4303e70fcead396e4da224e1ee711cc68d1008..40e7288de0ae8431eacd5f9220b5a2c3820b4ac8 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 6ccd1573bc3c53d9efd643b2a7636b59eb8557ab..73738ab41322215e3d19f59d3baa7d3808115ef1 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 af8c958db586c948ad99e8223814fd8bc3ecdc85..06832e13859a7623b90bea3690af25b996d83683 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 9cc1ad908a75af1c9a4709e0864417d188895aec..5cb2e8d931df5a3ff1786b86b8aa84ff65fd8884 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 c448ac4e43e577acc2cf3820de0461f5ac3ed98a..63263b23c7304d1321b99a2c7462da1e906f6cb3 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 68783a5bc189e3fc05ad0a14cbad3a54bda8b56e..25302d7df5afc0542174c200acac037096b70ef1 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 40e7288de0ae8431eacd5f9220b5a2c3820b4ac8..edd6a5959f953dabee5e1155ef69fa7bde5125b3 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 73738ab41322215e3d19f59d3baa7d3808115ef1..681a7eda274df805c8a78c06ddebe6b2769c1932 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 a1b35c1d65c58695cf2904729bc430898f8a9e5e..5f6d46aa46c36e709eb21b32b69ceec1a45df332 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 06832e13859a7623b90bea3690af25b996d83683..0fd961a768572ef97418f9a82bfb6c43f367c22a 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 5cb2e8d931df5a3ff1786b86b8aa84ff65fd8884..26963fa2dd9eec5879d93db5efb17a50470da4e3 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 bd9bd6d2db0842a3cf40a630f7f83779856ee143..24a36865635b895908bd9f647be9f3880e5ab2a0 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 37c010f0f93c78aae7a3cf3ec6c71f388f52d9bd..246b6995587b906efd8583cce6f1b5dfe4b70a8f 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 25302d7df5afc0542174c200acac037096b70ef1..4e90121d590f7242168c518d21245b3db822d166 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 edd6a5959f953dabee5e1155ef69fa7bde5125b3..a387f05e653ddf2d71fdeec515e6131b2970d462 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 0fd961a768572ef97418f9a82bfb6c43f367c22a..278ea9d87961cd20cffaec0c1b2a9b048ed6fa77 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 8a40a5d1d3a6e43313458d7cdc32c4b89130fe39..fb7da693fc365ba12c4d8cf2202fd65fba0a8419 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 63263b23c7304d1321b99a2c7462da1e906f6cb3..a89d5440749549e7cab9dbffd4394484a474e293 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 4e90121d590f7242168c518d21245b3db822d166..4926a8775e84a1f3d1be262b1a819ef41e44bd30 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 a387f05e653ddf2d71fdeec515e6131b2970d462..5c501f64ad16a34d72ec5400dde972f6b359648d 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 278ea9d87961cd20cffaec0c1b2a9b048ed6fa77..e9fc2866aadf10229e52f3b4444a99e6baaab4de 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 2ea99581d219b95e331430740af161bbd641056c..3621a4e0d9096db36b88c2d08640f7b91fc900ce 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 ec301939f77ffae8b615d8f96f9ada8590545c8d..8fbcda02a32be4ba7e41e9e8452e94d7a1014c9e 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 86d69afe5f8203224f5db5f7fd319b75ecb993c8..4c874c4585ebb6cc852704581485cff17d52c047 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 69d5685ba02ceadeeb5bec364366c76121aa12a9..de0ad626a535cca8f18c6455435664403bb170f5 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 488ee27c0c155f4de4c34d259e0d07faa6f43411..d7f25cde5174b65a1c97acfd615314c3fe3b3382 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 3621a4e0d9096db36b88c2d08640f7b91fc900ce..f5d8c56a466481cba38b0a9823dda570e5c53e17 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 19da782fb6840a9a39d500db2355ca666154c7a7..9d997fcfad1378d227dc844673c6ee3b0e431b53 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 e9fc2866aadf10229e52f3b4444a99e6baaab4de..6cb9f66edbb9cc5df220093a25d094fc649881f9 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 f5d8c56a466481cba38b0a9823dda570e5c53e17..9cf13af681d226e2e25e4cb4fe03dc8c6440717a 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 9d997fcfad1378d227dc844673c6ee3b0e431b53..bc10c931bb545223ae0fa16dc68757d1bd0cd2bb 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 6cb9f66edbb9cc5df220093a25d094fc649881f9..30a2a2f4e3234d6d57bbb19902a114796be64d57 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 5c501f64ad16a34d72ec5400dde972f6b359648d..e115e1d5739f4e85cf814c96d6174ca25fbbca02 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 30a2a2f4e3234d6d57bbb19902a114796be64d57..01ed898de4c0a41be626b5edcf314265f691ea56 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 aeb26231d4651909f535549f1837e5bb9989a03e..cbc4b3bd45cf4131ae13e817dda4fdadf06f2691 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 01ed898de4c0a41be626b5edcf314265f691ea56..b26acf9b4dd409e703e3102f5f1ac007eef40df6 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 fb7da693fc365ba12c4d8cf2202fd65fba0a8419..35a430f63f2008f88840c17f17bbdfe7e17f9ef7 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 a89d5440749549e7cab9dbffd4394484a474e293..3476a6a155fefe895ba301d2fee4e47ddc7eeacd 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 4926a8775e84a1f3d1be262b1a819ef41e44bd30..4652b58072519b677df181fc85609b2ca28d9159 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 e115e1d5739f4e85cf814c96d6174ca25fbbca02..83bb54d3be3f554463279311281aedb2b8a1ea07 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 | 175 +++++++++++---------- 3 files changed, 246 insertions(+), 113 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 4652b58072519b677df181fc85609b2ca28d9159..1f4096880c9e88023ad57eea2be578ba28d52954 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 b26acf9b4dd409e703e3102f5f1ac007eef40df6..5dc7c066f7de1384a0ca339cd954a6d93427b8fe 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 35a430f63f2008f88840c17f17bbdfe7e17f9ef7..2608ea1ec41323a1aa77bef242742451d4c6b822 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); - - 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); - }); + 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 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); + }); - auto_update::notify_of_any_new_update(cx.weak_handle(), cx); + auto_update::notify_of_any_new_update(cx.weak_handle(), cx); - vim::observe_keystrokes(cx); + 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 - }); + 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 35484b33090fb6eacc4cb41de8e233b92a19a16d..d8cf01d20ca6adf689c9f3464196b63eaf497f7b 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 8a41368276dded678f6df7b60732175eaf5c8fd0..fac2588e0b25a21cb282fd02221b9268e2cf5471 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 da3c96956e613941f96d3e58077236f43876f25e..18211e4276dcdc61b58f9d2819b9b3de2d85735e 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 8fbcda02a32be4ba7e41e9e8452e94d7a1014c9e..9b4fd7ca510a49a12b439da29a6df8002f9dada0 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 bcff08d0056a0c01ada1fa589a264ab910b3adc4..2d1de6df75b9db382eea9ce5f826580bb4e1b946 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 5dc7c066f7de1384a0ca339cd954a6d93427b8fe..dfb8560843ac34b1b9423bc18cb49872d6851a68 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 2608ea1ec41323a1aa77bef242742451d4c6b822..77a3bf350f130925f7fcf484036513fd6de24a7f 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 | 1040 +++++++++++++++++------------------ 1 file changed, 520 insertions(+), 520 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 246b6995587b906efd8583cce6f1b5dfe4b70a8f..1fca19a83f7d4033719a887a46a0c80c2ea24fa7 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" + // 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 } - }, - { - "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" + ], + "shift-home": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true } - }, - // 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" + ], + "ctrl-shift-a": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true } - }, - { - "bindings": { - "cmd-k cmd-left": "workspace::ActivatePreviousPane", - "cmd-k cmd-right": "workspace::ActivateNextPane" + ], + "cmd-shift-right": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true } - }, - // 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" + ], + "shift-end": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true } - }, - // Bindings that should be unified with bindings for more general actions - { - "context": "Editor && renaming", - "bindings": { - "enter": "editor::ConfirmRename" + ], + "ctrl-shift-e": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true } - }, - { - "context": "Editor && showing_completions", - "bindings": { - "enter": "editor::ConfirmCompletion", - "tab": "editor::ConfirmCompletion" + ], + "ctrl-v": [ + "editor::MovePageDown", + { + "center_cursor": true } - }, - { - "context": "Editor && showing_code_actions", - "bindings": { - "enter": "editor::ConfirmCodeAction" + ], + "alt-v": [ + "editor::MovePageUp", + { + "center_cursor": true } - }, - // Custom bindings - { - "bindings": { - "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator", - "cmd-shift-c": "collab::ToggleContactsMenu", - "cmd-alt-i": "zed::DebugElements" + ], + "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 } - }, - { - "context": "Editor", - "bindings": { - "alt-enter": "editor::OpenExcerpts", - "cmd-f8": "editor::GoToHunk", - "cmd-shift-f8": "editor::GoToPrevHunk" + ], + "cmd-e": [ + "buffer_search::Deploy", + { + "focus": false } - }, - { - "context": "ProjectSearchBar", - "bindings": { - "cmd-enter": "project_search::SearchInNew" + ], + "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 } - }, - { - "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" + ], + "cmd-k cmd-d": [ + "editor::SelectNext", + { + "replace_newest": true } - }, - { - "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" - ] + ], + "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 2aea69e90b7cdda61afabba7988d569189b36de2..b8b57e8b8dde611aae3689ae1d30dd0a9ec1567a 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 8cd5a06675318ff3f48730cd360ac6deaa599469..aab5b8a41c2d00a5d445576c07cc952c0d020b73 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 761d80308466632a8ea43fc93105ea92d0eda656..010a305f161cc966c43fb88c1970c3dcd06e966b 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 9cf3f529249e6c6725f499ed535ed779694d3fc7..576719526da0104cb92b6e6c05b2f768c042da84 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 afd564fb56d5b4cecb19e43cf22f52b1ff9eb7dd..9c8073eada819f26ca944140025394c93023be3f 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,12 +301,23 @@ 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 { @@ -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 c327c4ded8a7e58f13ca1b436ce0dac3eeff2d03..c4693b340325994147b6da6d2d9ba67946efdbc5 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 78900eb0021f0545bfb06199fd23efcce0dea52b..6c7d61fef9625e9a5030ca0505114d37ba981de3 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 0c4b63081c0ebb1fbb9343fa2d9f95fbc83d7c17..be0c566bb136aaf2ba6bc97f921f85f91fa8abef 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 010a305f161cc966c43fb88c1970c3dcd06e966b..9159e8fa98eb85006726d8e737161aea628afc35 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 bc895034594178622aec581da3cd4322c203488d..0fafe7694204b2500b203db5edc6489ed094bf40 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 be0c566bb136aaf2ba6bc97f921f85f91fa8abef..4c4137b7f37979d4c12e2b89b71fae1f5804ea22 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 5f698d89a5c7887f4596334694f11e805cd317c7..aae900c8615236591804870a845fb136d34b91fa 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 | 39 ++++++++++++----------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index fbefd3984ef4eb9f161902e215cac487a4d15b37..76b66c5329ec9a133b8bd4b3402fe87c7f20cd8c 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; + + 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"); - 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_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 6c7d61fef9625e9a5030ca0505114d37ba981de3..bd50251cf1a56717814e2001f50c863213e6b2e1 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 c4693b340325994147b6da6d2d9ba67946efdbc5..8f44419ddc58787310fe64df9d67f9ec803364f4 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 bd50251cf1a56717814e2001f50c863213e6b2e1..495989c1bed17e13084b73189dc7bac562cae2dd 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 8f44419ddc58787310fe64df9d67f9ec803364f4..7e6868f9b095558532487da27904ae49d40a86fc 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 aae900c8615236591804870a845fb136d34b91fa..eb3dd12b33489f74392cc4d066e892d86832e4d3 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 76b66c5329ec9a133b8bd4b3402fe87c7f20cd8c..d27818d2028eec866d17cc687bb393f37e9cb6e5 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 eb3dd12b33489f74392cc4d066e892d86832e4d3..e07bd0c9cf8032666d1375d5a5a3e22bf559f3a6 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 e07bd0c9cf8032666d1375d5a5a3e22bf559f3a6..e1079f201b676286590d8b272cc4658d9c4777df 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 31a9f5cc03ed840af905f3652cf1ecc70830ddfc..b1c9e9c215324bbd6dcdf363d2aeb272ec86f057 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 7e6868f9b095558532487da27904ae49d40a86fc..6ca78cd935b0c35aa3303ba683364fd07449c4d1 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 501debc876200e47edea06b44b5d70f342671135..737d225784aeabe86edbdcf9a044748855ffc01f 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),