From 1919a826f9f2aeb3fdc3179e056bed2de5ad5c90 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 May 2023 15:02:26 -0600 Subject: [PATCH 001/131] Remove dock from workspace --- assets/keymaps/atom.json | 4 - assets/keymaps/default.json | 26 - assets/keymaps/jetbrains.json | 9 +- assets/keymaps/sublime_text.json | 7 - assets/keymaps/textmate.json | 4 - assets/settings/default.json | 10 - crates/collab/src/tests.rs | 1 - crates/settings/src/settings.rs | 49 +- crates/terminal_view/src/terminal_button.rs | 7 +- crates/theme/src/theme.rs | 10 - crates/workspace/src/dock.rs | 816 ------------------ .../workspace/src/dock/toggle_dock_button.rs | 126 --- crates/workspace/src/pane.rs | 90 +- crates/workspace/src/persistence.rs | 176 +--- crates/workspace/src/persistence/model.rs | 76 +- crates/workspace/src/workspace.rs | 137 +-- crates/zed/src/main.rs | 4 +- crates/zed/src/zed.rs | 1 - 18 files changed, 92 insertions(+), 1461 deletions(-) delete mode 100644 crates/workspace/src/dock.rs delete mode 100644 crates/workspace/src/dock/toggle_dock_button.rs diff --git a/assets/keymaps/atom.json b/assets/keymaps/atom.json index 99aae2638f..a1e8cf6d9d 100644 --- a/assets/keymaps/atom.json +++ b/assets/keymaps/atom.json @@ -62,9 +62,5 @@ "ctrl-f": "project_panel::ExpandSelectedEntry", "ctrl-shift-c": "project_panel::CopyPath" } - }, - { - "context": "Dock", - "bindings": {} } ] diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index b89940a751..6e8b519313 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -443,32 +443,6 @@ "cmd-enter": "project_search::SearchInNew" } }, - { - "context": "Workspace", - "bindings": { - "shift-escape": "dock::FocusDock" - } - }, - { - "bindings": { - "cmd-shift-k cmd-shift-right": "dock::AnchorDockRight", - "cmd-shift-k cmd-shift-down": "dock::AnchorDockBottom", - "cmd-shift-k cmd-shift-up": "dock::ExpandDock" - } - }, - { - "context": "Pane", - "bindings": { - "cmd-escape": "dock::AddTabToDock" - } - }, - { - "context": "Pane && docked", - "bindings": { - "shift-escape": "dock::HideDock", - "cmd-escape": "dock::RemoveTabFromDock" - } - }, { "context": "ProjectPanel", "bindings": { diff --git a/assets/keymaps/jetbrains.json b/assets/keymaps/jetbrains.json index 59e069e7f7..0dafe58d32 100644 --- a/assets/keymaps/jetbrains.json +++ b/assets/keymaps/jetbrains.json @@ -66,14 +66,7 @@ "cmd-shift-a": "command_palette::Toggle", "cmd-alt-o": "project_symbols::Toggle", "cmd-1": "workspace::ToggleLeftSidebar", - "cmd-6": "diagnostics::Deploy", - "alt-f12": "dock::FocusDock" - } - }, - { - "context": "Dock", - "bindings": { - "alt-f12": "dock::HideDock" + "cmd-6": "diagnostics::Deploy" } } ] diff --git a/assets/keymaps/sublime_text.json b/assets/keymaps/sublime_text.json index 373f3f4d3a..7a285ee824 100644 --- a/assets/keymaps/sublime_text.json +++ b/assets/keymaps/sublime_text.json @@ -45,18 +45,11 @@ { "context": "Workspace", "bindings": { - "ctrl-`": "dock::FocusDock", "cmd-k cmd-b": "workspace::ToggleLeftSidebar", "cmd-t": "file_finder::Toggle", "shift-cmd-r": "project_symbols::Toggle", // Currently busted: https://github.com/zed-industries/feedback/issues/898 "ctrl-0": "project_panel::ToggleFocus" } - }, - { - "context": "Dock", - "bindings": { - "ctrl-`": "dock::HideDock" - } } ] diff --git a/assets/keymaps/textmate.json b/assets/keymaps/textmate.json index 1abcaa376c..738b6603ff 100644 --- a/assets/keymaps/textmate.json +++ b/assets/keymaps/textmate.json @@ -83,9 +83,5 @@ { "context": "ProjectPanel", "bindings": {} - }, - { - "context": "Dock", - "bindings": {} } ] diff --git a/assets/settings/default.json b/assets/settings/default.json index 20d07acae0..58fb28bc62 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -59,16 +59,6 @@ // 4. Save when idle for a certain amount of time: // "autosave": { "after_delay": {"milliseconds": 500} }, "autosave": "off", - // Where to place the dock by default. This setting can take three - // values: - // - // 1. Position the dock attached to the bottom of the workspace - // "default_dock_anchor": "bottom" - // 2. Position the dock to the right of the workspace like a side panel - // "default_dock_anchor": "right" - // 3. Position the dock full screen over the entire workspace" - // "default_dock_anchor": "expanded" - "default_dock_anchor": "bottom", // Whether or not to remove any trailing whitespace from lines of a buffer // before saving it. "remove_trailing_whitespace_on_save": true, diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index 64768d1a47..aeb26231d4 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -195,7 +195,6 @@ impl TestServer { fs: fs.clone(), build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _| unimplemented!(), - dock_default_item_factory: |_, _| None, background_actions: || &[], }); diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 7ba52fcb5e..fde7f516a7 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -2,7 +2,7 @@ mod keymap_file; pub mod settings_file; pub mod watched_json; -use anyhow::{bail, Result}; +use anyhow::Result; use gpui::{ font_cache::{FamilyId, FontCache}, fonts, AssetSource, @@ -15,10 +15,6 @@ use schemars::{ }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; -use sqlez::{ - bindable::{Bind, Column, StaticColumnCount}, - statement::Statement, -}; use std::{ borrow::Cow, collections::HashMap, num::NonZeroU32, ops::Range, path::Path, str, sync::Arc, }; @@ -48,7 +44,6 @@ pub struct Settings { pub show_call_status_icon: bool, pub vim_mode: bool, pub autosave: Autosave, - pub default_dock_anchor: DockAnchor, pub editor_defaults: EditorSettings, pub editor_overrides: EditorSettings, pub git: GitSettings, @@ -340,43 +335,6 @@ impl TerminalSettings { } } -#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum DockAnchor { - #[default] - Bottom, - Right, - Expanded, -} - -impl StaticColumnCount for DockAnchor {} -impl Bind for DockAnchor { - fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { - match self { - DockAnchor::Bottom => "Bottom", - DockAnchor::Right => "Right", - DockAnchor::Expanded => "Expanded", - } - .bind(statement, start_index) - } -} - -impl Column for DockAnchor { - fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> { - String::column(statement, start_index).and_then(|(anchor_text, next_index)| { - Ok(( - match anchor_text.as_ref() { - "Bottom" => DockAnchor::Bottom, - "Right" => DockAnchor::Right, - "Expanded" => DockAnchor::Expanded, - _ => bail!("Stored dock anchor is incorrect"), - }, - next_index, - )) - }) - } -} - #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct SettingsFileContent { #[serde(default)] @@ -403,8 +361,6 @@ pub struct SettingsFileContent { pub vim_mode: Option, #[serde(default)] pub autosave: Option, - #[serde(default)] - pub default_dock_anchor: Option, #[serde(flatten)] pub editor: EditorSettings, #[serde(default)] @@ -501,7 +457,6 @@ impl Settings { show_call_status_icon: defaults.show_call_status_icon.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), - default_dock_anchor: defaults.default_dock_anchor.unwrap(), editor_defaults: EditorSettings { tab_size: required(defaults.editor.tab_size), hard_tabs: required(defaults.editor.hard_tabs), @@ -596,7 +551,6 @@ impl Settings { ); merge(&mut self.vim_mode, data.vim_mode); merge(&mut self.autosave, data.autosave); - merge(&mut self.default_dock_anchor, data.default_dock_anchor); merge(&mut self.base_keymap, data.base_keymap); merge(&mut self.features.copilot, data.features.copilot); @@ -796,7 +750,6 @@ impl Settings { show_call_status_icon: true, vim_mode: false, autosave: Autosave::Off, - default_dock_anchor: DockAnchor::Bottom, editor_defaults: EditorSettings { tab_size: Some(4.try_into().unwrap()), hard_tabs: Some(false), diff --git a/crates/terminal_view/src/terminal_button.rs b/crates/terminal_view/src/terminal_button.rs index a92f7285b5..7b0da416ff 100644 --- a/crates/terminal_view/src/terminal_button.rs +++ b/crates/terminal_view/src/terminal_button.rs @@ -8,7 +8,6 @@ use gpui::{ use settings::Settings; use std::any::TypeId; use workspace::{ - dock::{Dock, FocusDock}, item::ItemHandle, NewTerminal, StatusItemView, Workspace, }; @@ -85,8 +84,8 @@ impl View for TerminalButton { } else { if !active { if let Some(workspace) = this.workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - Dock::focus_dock(workspace, &Default::default(), cx) + workspace.update(cx, |_workspace, _cx| { + todo!() }) } } @@ -95,7 +94,7 @@ impl View for TerminalButton { .with_tooltip::( 0, "Show Terminal".into(), - Some(Box::new(FocusDock)), + None, // TODO! We need a new action here. theme.tooltip.clone(), cx, ), diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 0bd23a0b87..8c77295748 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -73,7 +73,6 @@ pub struct Workspace { pub joining_project_avatar: ImageStyle, pub joining_project_message: ContainedText, pub external_location_message: ContainedText, - pub dock: Dock, pub drop_target_overlay_color: Color, } @@ -296,15 +295,6 @@ pub struct Toolbar { pub nav_button: Interactive, } -#[derive(Clone, Deserialize, Default)] -pub struct Dock { - pub initial_size_right: f32, - pub initial_size_bottom: f32, - pub wash_color: Color, - pub panel: ContainerStyle, - pub maximized: ContainerStyle, -} - #[derive(Clone, Deserialize, Default)] pub struct Notifications { #[serde(flatten)] diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs deleted file mode 100644 index 7efcb7f9d3..0000000000 --- a/crates/workspace/src/dock.rs +++ /dev/null @@ -1,816 +0,0 @@ -mod toggle_dock_button; - -use collections::HashMap; -use gpui::{ - actions, - elements::{ChildView, Empty, MouseEventHandler, ParentElement, Side, Stack}, - geometry::vector::Vector2F, - platform::{CursorStyle, MouseButton}, - AnyElement, AppContext, Border, Element, SizeConstraint, ViewContext, ViewHandle, -}; -use settings::{DockAnchor, Settings}; -use theme::Theme; - -use crate::{sidebar::SidebarSide, BackgroundActions, ItemHandle, Pane, Workspace}; -pub use toggle_dock_button::ToggleDockButton; - -actions!( - dock, - [ - FocusDock, - HideDock, - AnchorDockRight, - AnchorDockBottom, - ExpandDock, - AddTabToDock, - RemoveTabFromDock, - ] -); - -pub fn init(cx: &mut AppContext) { - cx.add_action(Dock::focus_dock); - cx.add_action(Dock::hide_dock); - cx.add_action( - |workspace: &mut Workspace, _: &AnchorDockRight, cx: &mut ViewContext| { - Dock::move_dock(workspace, DockAnchor::Right, true, cx); - }, - ); - cx.add_action( - |workspace: &mut Workspace, _: &AnchorDockBottom, cx: &mut ViewContext| { - Dock::move_dock(workspace, DockAnchor::Bottom, true, cx) - }, - ); - cx.add_action( - |workspace: &mut Workspace, _: &ExpandDock, cx: &mut ViewContext| { - Dock::move_dock(workspace, DockAnchor::Expanded, true, cx) - }, - ); - cx.add_action( - |workspace: &mut Workspace, _: &AddTabToDock, cx: &mut ViewContext| { - if let Some(active_item) = workspace.active_item(cx) { - let item_id = active_item.id(); - - let from = workspace.active_pane(); - let to = workspace.dock_pane(); - if from.id() == to.id() { - return; - } - - let destination_index = to.read(cx).items_len() + 1; - - Pane::move_item( - workspace, - from.clone(), - to.clone(), - item_id, - destination_index, - cx, - ); - } - }, - ); - cx.add_action( - |workspace: &mut Workspace, _: &RemoveTabFromDock, cx: &mut ViewContext| { - if let Some(active_item) = workspace.active_item(cx) { - let item_id = active_item.id(); - - let from = workspace.dock_pane(); - let to = workspace - .last_active_center_pane - .as_ref() - .and_then(|pane| pane.upgrade(cx)) - .unwrap_or_else(|| { - workspace - .panes - .first() - .expect("There must be a pane") - .clone() - }); - - if from.id() == to.id() { - return; - } - - let destination_index = to.read(cx).items_len() + 1; - - Pane::move_item( - workspace, - from.clone(), - to.clone(), - item_id, - destination_index, - cx, - ); - } - }, - ); -} - -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum DockPosition { - Shown(DockAnchor), - Hidden(DockAnchor), -} - -impl Default for DockPosition { - fn default() -> Self { - DockPosition::Hidden(Default::default()) - } -} - -pub fn icon_for_dock_anchor(anchor: DockAnchor) -> &'static str { - match anchor { - DockAnchor::Right => "icons/dock_right_12.svg", - DockAnchor::Bottom => "icons/dock_bottom_12.svg", - DockAnchor::Expanded => "icons/dock_modal_12.svg", - } -} - -impl DockPosition { - pub fn is_visible(&self) -> bool { - match self { - DockPosition::Shown(_) => true, - DockPosition::Hidden(_) => false, - } - } - - pub fn anchor(&self) -> DockAnchor { - match self { - DockPosition::Shown(anchor) | DockPosition::Hidden(anchor) => *anchor, - } - } - - fn hide(self) -> Self { - match self { - DockPosition::Shown(anchor) => DockPosition::Hidden(anchor), - DockPosition::Hidden(_) => self, - } - } - - fn show(self) -> Self { - match self { - DockPosition::Hidden(anchor) => DockPosition::Shown(anchor), - DockPosition::Shown(_) => self, - } - } -} - -pub type DockDefaultItemFactory = - fn(workspace: &mut Workspace, cx: &mut ViewContext) -> Option>; - -pub struct Dock { - position: DockPosition, - panel_sizes: HashMap, - pane: ViewHandle, - default_item_factory: DockDefaultItemFactory, -} - -impl Dock { - pub fn new( - default_item_factory: DockDefaultItemFactory, - background_actions: BackgroundActions, - cx: &mut ViewContext, - ) -> Self { - let position = DockPosition::Hidden(cx.global::().default_dock_anchor); - let workspace = cx.weak_handle(); - let pane = - cx.add_view(|cx| Pane::new(workspace, Some(position.anchor()), background_actions, cx)); - pane.update(cx, |pane, cx| { - pane.set_active(false, cx); - }); - cx.subscribe(&pane, Workspace::handle_pane_event).detach(); - - Self { - pane, - panel_sizes: Default::default(), - position, - default_item_factory, - } - } - - pub fn pane(&self) -> &ViewHandle { - &self.pane - } - - pub fn visible_pane(&self) -> Option<&ViewHandle> { - self.position.is_visible().then(|| self.pane()) - } - - pub fn is_anchored_at(&self, anchor: DockAnchor) -> bool { - self.position.is_visible() && self.position.anchor() == anchor - } - - pub(crate) fn set_dock_position( - workspace: &mut Workspace, - new_position: DockPosition, - focus: bool, - cx: &mut ViewContext, - ) { - workspace.dock.position = new_position; - // Tell the pane about the new anchor position - workspace.dock.pane.update(cx, |pane, cx| { - pane.set_docked(Some(new_position.anchor()), cx) - }); - - if workspace.dock.position.is_visible() { - // Close the right sidebar if the dock is on the right side and the right sidebar is open - if workspace.dock.position.anchor() == DockAnchor::Right { - if workspace.right_sidebar().read(cx).is_open() { - workspace.toggle_sidebar(SidebarSide::Right, cx); - } - } - - // Ensure that the pane has at least one item or construct a default item to put in it - let pane = workspace.dock.pane.clone(); - if pane.read(cx).items().next().is_none() { - if let Some(item_to_add) = (workspace.dock.default_item_factory)(workspace, cx) { - Pane::add_item(workspace, &pane, item_to_add, focus, focus, None, cx); - } else { - workspace.dock.position = workspace.dock.position.hide(); - } - } else { - if focus { - cx.focus(&pane); - } - } - } else if let Some(last_active_center_pane) = workspace - .last_active_center_pane - .as_ref() - .and_then(|pane| pane.upgrade(cx)) - { - if focus { - cx.focus(&last_active_center_pane); - } - } - cx.emit(crate::Event::DockAnchorChanged); - workspace.serialize_workspace(cx); - cx.notify(); - } - - pub fn hide(workspace: &mut Workspace, cx: &mut ViewContext) { - Self::set_dock_position(workspace, workspace.dock.position.hide(), true, cx); - } - - pub fn show(workspace: &mut Workspace, focus: bool, cx: &mut ViewContext) { - Self::set_dock_position(workspace, workspace.dock.position.show(), focus, cx); - } - - pub fn hide_on_sidebar_shown( - workspace: &mut Workspace, - sidebar_side: SidebarSide, - cx: &mut ViewContext, - ) { - if (sidebar_side == SidebarSide::Right && workspace.dock.is_anchored_at(DockAnchor::Right)) - || workspace.dock.is_anchored_at(DockAnchor::Expanded) - { - Self::hide(workspace, cx); - } - } - - pub fn focus_dock(workspace: &mut Workspace, _: &FocusDock, cx: &mut ViewContext) { - Self::set_dock_position(workspace, workspace.dock.position.show(), true, cx); - } - - pub fn hide_dock(workspace: &mut Workspace, _: &HideDock, cx: &mut ViewContext) { - Self::set_dock_position(workspace, workspace.dock.position.hide(), true, cx); - } - - pub fn move_dock( - workspace: &mut Workspace, - new_anchor: DockAnchor, - focus: bool, - cx: &mut ViewContext, - ) { - Self::set_dock_position(workspace, DockPosition::Shown(new_anchor), focus, cx); - } - - pub fn render( - &self, - theme: &Theme, - anchor: DockAnchor, - cx: &mut ViewContext, - ) -> Option> { - let style = &theme.workspace.dock; - - self.position - .is_visible() - .then(|| self.position.anchor()) - .filter(|current_anchor| *current_anchor == anchor) - .map(|anchor| match anchor { - DockAnchor::Bottom | DockAnchor::Right => { - let mut panel_style = style.panel.clone(); - let (resize_side, initial_size) = if anchor == DockAnchor::Bottom { - panel_style.border = Border { - top: true, - bottom: false, - left: false, - right: false, - ..panel_style.border - }; - - (Side::Top, style.initial_size_bottom) - } else { - panel_style.border = Border { - top: false, - bottom: false, - left: true, - right: false, - ..panel_style.border - }; - (Side::Left, style.initial_size_right) - }; - - enum DockResizeHandle {} - - let resizable = ChildView::new(&self.pane, cx) - .contained() - .with_style(panel_style) - .with_resize_handle::( - resize_side as usize, - resize_side, - 4., - self.panel_sizes - .get(&anchor) - .copied() - .unwrap_or(initial_size), - cx, - ); - - let size = resizable.current_size(); - cx.defer(move |workspace, _| { - workspace.dock.panel_sizes.insert(anchor, size); - }); - - if anchor == DockAnchor::Right { - resizable.constrained().dynamically(|constraint, _, cx| { - SizeConstraint::new( - Vector2F::new(20., constraint.min.y()), - Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()), - ) - }) - } else { - resizable.constrained().dynamically(|constraint, _, cx| { - SizeConstraint::new( - Vector2F::new(constraint.min.x(), 50.), - Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8), - ) - }) - } - .into_any() - } - DockAnchor::Expanded => { - enum ExpandedDockWash {} - enum ExpandedDockPane {} - Stack::new() - .with_child( - // Render wash under the dock which when clicked hides it - MouseEventHandler::::new(0, cx, |_, _| { - Empty::new() - .contained() - .with_background_color(style.wash_color) - }) - .capture_all() - .on_down(MouseButton::Left, |_, workspace, cx| { - Dock::hide_dock(workspace, &Default::default(), cx) - }) - .with_cursor_style(CursorStyle::Arrow), - ) - .with_child( - MouseEventHandler::::new(0, cx, |_state, cx| { - ChildView::new(&self.pane, cx) - }) - // Make sure all events directly under the dock pane - // are captured - .capture_all() - .contained() - .with_style(style.maximized), - ) - .into_any() - } - }) - } - - pub fn position(&self) -> DockPosition { - self.position - } -} - -#[cfg(test)] -mod tests { - use std::{ - ops::{Deref, DerefMut}, - path::PathBuf, - sync::Arc, - }; - - use gpui::{AppContext, BorrowWindowContext, TestAppContext, ViewContext, WindowContext}; - use project::{FakeFs, Project}; - use settings::Settings; - use theme::ThemeRegistry; - - use super::*; - use crate::{ - dock, - item::{self, test::TestItem}, - persistence::model::{ - SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace, - }, - register_deserializable_item, - sidebar::Sidebar, - AppState, ItemHandle, Workspace, - }; - - pub fn default_item_factory( - _workspace: &mut Workspace, - cx: &mut ViewContext, - ) -> Option> { - Some(Box::new(cx.add_view(|_| TestItem::new()))) - } - - #[gpui::test] - async fn test_dock_workspace_infinite_loop(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); - - cx.update(|cx| { - register_deserializable_item::(cx); - }); - - let serialized_workspace = SerializedWorkspace { - id: 0, - location: Vec::::new().into(), - dock_position: dock::DockPosition::Shown(DockAnchor::Expanded), - center_group: SerializedPaneGroup::Pane(SerializedPane { - active: false, - children: vec![], - }), - dock_pane: SerializedPane { - active: true, - children: vec![SerializedItem { - active: true, - item_id: 0, - kind: "TestItem".into(), - }], - }, - left_sidebar_open: false, - bounds: Default::default(), - display: Default::default(), - }; - - let fs = FakeFs::new(cx.background()); - let project = Project::test(fs, [], cx).await; - - let (_, _workspace) = cx.add_window(|cx| { - Workspace::new( - Some(serialized_workspace), - 0, - project.clone(), - Arc::new(AppState { - languages: project.read(cx).languages().clone(), - themes: ThemeRegistry::new((), cx.font_cache().clone()), - client: project.read(cx).client(), - user_store: project.read(cx).user_store(), - fs: project.read(cx).fs().clone(), - build_window_options: |_, _, _| Default::default(), - initialize_workspace: |_, _, _| {}, - dock_default_item_factory: default_item_factory, - background_actions: || &[], - }), - cx, - ) - }); - - cx.foreground().run_until_parked(); - //Should terminate - } - - #[gpui::test] - async fn test_dock_hides_when_pane_empty(cx: &mut TestAppContext) { - let mut cx = DockTestContext::new(cx).await; - - // Closing the last item in the dock hides the dock - cx.move_dock(DockAnchor::Right); - let old_items = cx.dock_items(); - assert!(!old_items.is_empty()); - cx.close_dock_items().await; - cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Right)); - - // Reopening the dock adds a new item - cx.move_dock(DockAnchor::Right); - let new_items = cx.dock_items(); - assert!(!new_items.is_empty()); - assert!(new_items - .into_iter() - .all(|new_item| !old_items.contains(&new_item))); - } - - #[gpui::test] - async fn test_dock_panel_collisions(cx: &mut TestAppContext) { - let mut cx = DockTestContext::new(cx).await; - - // Dock closes when expanded for either panel - cx.move_dock(DockAnchor::Expanded); - cx.open_sidebar(SidebarSide::Left); - cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Expanded)); - cx.close_sidebar(SidebarSide::Left); - cx.move_dock(DockAnchor::Expanded); - cx.open_sidebar(SidebarSide::Right); - cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Expanded)); - - // Dock closes in the right position if the right sidebar is opened - cx.move_dock(DockAnchor::Right); - cx.open_sidebar(SidebarSide::Left); - cx.assert_dock_position(DockPosition::Shown(DockAnchor::Right)); - cx.open_sidebar(SidebarSide::Right); - cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Right)); - cx.close_sidebar(SidebarSide::Right); - - // Dock in bottom position ignores sidebars - cx.move_dock(DockAnchor::Bottom); - cx.open_sidebar(SidebarSide::Left); - cx.open_sidebar(SidebarSide::Right); - cx.assert_dock_position(DockPosition::Shown(DockAnchor::Bottom)); - - // Opening the dock in the right position closes the right sidebar - cx.move_dock(DockAnchor::Right); - cx.assert_sidebar_closed(SidebarSide::Right); - } - - #[gpui::test] - async fn test_focusing_panes_shows_and_hides_dock(cx: &mut TestAppContext) { - let mut cx = DockTestContext::new(cx).await; - - // Focusing an item not in the dock when expanded hides the dock - let center_item = cx.add_item_to_center_pane(); - cx.move_dock(DockAnchor::Expanded); - let dock_item = cx - .dock_items() - .get(0) - .cloned() - .expect("Dock should have an item at this point"); - center_item.update(&mut cx, |_, cx| cx.focus_self()); - cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Expanded)); - - // Focusing an item not in the dock when not expanded, leaves the dock open but inactive - cx.move_dock(DockAnchor::Right); - center_item.update(&mut cx, |_, cx| cx.focus_self()); - cx.assert_dock_position(DockPosition::Shown(DockAnchor::Right)); - cx.assert_dock_pane_inactive(); - cx.assert_workspace_pane_active(); - - // Focusing an item in the dock activates it's pane - dock_item.update(&mut cx, |_, cx| cx.focus_self()); - cx.assert_dock_position(DockPosition::Shown(DockAnchor::Right)); - cx.assert_dock_pane_active(); - cx.assert_workspace_pane_inactive(); - } - - #[gpui::test] - async fn test_toggle_dock_focus(cx: &mut TestAppContext) { - let mut cx = DockTestContext::new(cx).await; - - cx.move_dock(DockAnchor::Right); - cx.assert_dock_pane_active(); - cx.hide_dock(); - cx.move_dock(DockAnchor::Right); - cx.assert_dock_pane_active(); - } - - #[gpui::test] - async fn test_activate_next_and_prev_pane(cx: &mut TestAppContext) { - let mut cx = DockTestContext::new(cx).await; - - cx.move_dock(DockAnchor::Right); - cx.assert_dock_pane_active(); - - cx.update_workspace(|workspace, cx| workspace.activate_next_pane(cx)); - cx.assert_dock_pane_active(); - - cx.update_workspace(|workspace, cx| workspace.activate_previous_pane(cx)); - cx.assert_dock_pane_active(); - } - - struct DockTestContext<'a> { - pub cx: &'a mut TestAppContext, - pub window_id: usize, - pub workspace: ViewHandle, - } - - impl<'a> DockTestContext<'a> { - pub async fn new(cx: &'a mut TestAppContext) -> DockTestContext<'a> { - Settings::test_async(cx); - let fs = FakeFs::new(cx.background()); - - cx.update(|cx| init(cx)); - let project = Project::test(fs, [], cx).await; - let (window_id, workspace) = cx.add_window(|cx| { - Workspace::new( - None, - 0, - project.clone(), - Arc::new(AppState { - languages: project.read(cx).languages().clone(), - themes: ThemeRegistry::new((), cx.font_cache().clone()), - client: project.read(cx).client(), - user_store: project.read(cx).user_store(), - fs: project.read(cx).fs().clone(), - build_window_options: |_, _, _| Default::default(), - initialize_workspace: |_, _, _| {}, - dock_default_item_factory: default_item_factory, - background_actions: || &[], - }), - cx, - ) - }); - - workspace.update(cx, |workspace, cx| { - let left_panel = cx.add_view(|_| TestItem::new()); - workspace.left_sidebar().update(cx, |sidebar, cx| { - sidebar.add_item( - "icons/folder_tree_16.svg", - "Left Test Panel".to_string(), - left_panel.clone(), - cx, - ); - }); - - let right_panel = cx.add_view(|_| TestItem::new()); - workspace.right_sidebar().update(cx, |sidebar, cx| { - sidebar.add_item( - "icons/folder_tree_16.svg", - "Right Test Panel".to_string(), - right_panel.clone(), - cx, - ); - }); - }); - - Self { - cx, - window_id, - workspace, - } - } - - pub fn workspace(&self, read: F) -> T - where - F: FnOnce(&Workspace, &ViewContext) -> T, - { - self.workspace.read_with(self.cx, read) - } - - pub fn update_workspace(&mut self, update: F) -> T - where - F: FnOnce(&mut Workspace, &mut ViewContext) -> T, - { - self.workspace.update(self.cx, update) - } - - pub fn sidebar(&self, sidebar_side: SidebarSide, read: F) -> T - where - F: FnOnce(&Sidebar, &AppContext) -> T, - { - self.workspace(|workspace, cx| { - let sidebar = match sidebar_side { - SidebarSide::Left => workspace.left_sidebar(), - SidebarSide::Right => workspace.right_sidebar(), - } - .read(cx); - - read(sidebar, cx) - }) - } - - pub fn center_pane_handle(&self) -> ViewHandle { - self.workspace(|workspace, cx| { - workspace - .last_active_center_pane - .clone() - .and_then(|pane| pane.upgrade(cx)) - .unwrap_or_else(|| workspace.center.panes()[0].clone()) - }) - } - - pub fn add_item_to_center_pane(&mut self) -> ViewHandle { - self.update_workspace(|workspace, cx| { - let item = cx.add_view(|_| TestItem::new()); - let pane = workspace - .last_active_center_pane - .clone() - .and_then(|pane| pane.upgrade(cx)) - .unwrap_or_else(|| workspace.center.panes()[0].clone()); - Pane::add_item( - workspace, - &pane, - Box::new(item.clone()), - true, - true, - None, - cx, - ); - item - }) - } - - pub fn dock_pane(&self, read: F) -> T - where - F: FnOnce(&Pane, &AppContext) -> T, - { - self.workspace(|workspace, cx| { - let dock_pane = workspace.dock_pane().read(cx); - read(dock_pane, cx) - }) - } - - pub fn move_dock(&mut self, anchor: DockAnchor) { - self.update_workspace(|workspace, cx| Dock::move_dock(workspace, anchor, true, cx)); - } - - pub fn hide_dock(&mut self) { - self.cx.dispatch_action(self.window_id, HideDock); - } - - pub fn open_sidebar(&mut self, sidebar_side: SidebarSide) { - if !self.sidebar(sidebar_side, |sidebar, _| sidebar.is_open()) { - self.update_workspace(|workspace, cx| workspace.toggle_sidebar(sidebar_side, cx)); - } - } - - pub fn close_sidebar(&mut self, sidebar_side: SidebarSide) { - if self.sidebar(sidebar_side, |sidebar, _| sidebar.is_open()) { - self.update_workspace(|workspace, cx| workspace.toggle_sidebar(sidebar_side, cx)); - } - } - - pub fn dock_items(&self) -> Vec> { - self.dock_pane(|pane, cx| { - pane.items() - .map(|item| { - item.act_as::(cx) - .expect("Dock Test Context uses TestItems in the dock") - }) - .collect() - }) - } - - pub async fn close_dock_items(&mut self) { - self.update_workspace(|workspace, cx| { - Pane::close_items(workspace, workspace.dock_pane().clone(), cx, |_| true) - }) - .await - .expect("Could not close dock items") - } - - pub fn assert_dock_position(&self, expected_position: DockPosition) { - self.workspace(|workspace, _| assert_eq!(workspace.dock.position, expected_position)); - } - - pub fn assert_sidebar_closed(&self, sidebar_side: SidebarSide) { - assert!(!self.sidebar(sidebar_side, |sidebar, _| sidebar.is_open())); - } - - pub fn assert_workspace_pane_active(&self) { - assert!(self - .center_pane_handle() - .read_with(self.cx, |pane, _| pane.is_active())); - } - - pub fn assert_workspace_pane_inactive(&self) { - assert!(!self - .center_pane_handle() - .read_with(self.cx, |pane, _| pane.is_active())); - } - - pub fn assert_dock_pane_active(&self) { - assert!(self.dock_pane(|pane, _| pane.is_active())) - } - - pub fn assert_dock_pane_inactive(&self) { - assert!(!self.dock_pane(|pane, _| pane.is_active())) - } - } - - impl<'a> Deref for DockTestContext<'a> { - type Target = gpui::TestAppContext; - - fn deref(&self) -> &Self::Target { - self.cx - } - } - - impl<'a> DerefMut for DockTestContext<'a> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.cx - } - } - - impl BorrowWindowContext for DockTestContext<'_> { - fn read_with T>(&self, window_id: usize, f: F) -> T { - BorrowWindowContext::read_with(self.cx, window_id, f) - } - - fn update T>(&mut self, window_id: usize, f: F) -> T { - BorrowWindowContext::update(self.cx, window_id, f) - } - } -} diff --git a/crates/workspace/src/dock/toggle_dock_button.rs b/crates/workspace/src/dock/toggle_dock_button.rs deleted file mode 100644 index 1fda55b783..0000000000 --- a/crates/workspace/src/dock/toggle_dock_button.rs +++ /dev/null @@ -1,126 +0,0 @@ -use super::{icon_for_dock_anchor, Dock, FocusDock, HideDock}; -use crate::{handle_dropped_item, StatusItemView, Workspace}; -use gpui::{ - elements::{Empty, MouseEventHandler, Svg}, - platform::CursorStyle, - platform::MouseButton, - AnyElement, Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle, -}; -use settings::Settings; - -pub struct ToggleDockButton { - workspace: WeakViewHandle, -} - -impl ToggleDockButton { - pub fn new(workspace: ViewHandle, cx: &mut ViewContext) -> Self { - // When dock moves, redraw so that the icon and toggle status matches. - cx.subscribe(&workspace, |_, _, _, cx| cx.notify()).detach(); - - Self { - workspace: workspace.downgrade(), - } - } -} - -impl Entity for ToggleDockButton { - type Event = (); -} - -impl View for ToggleDockButton { - fn ui_name() -> &'static str { - "Dock Toggle" - } - - fn render(&mut self, cx: &mut gpui::ViewContext) -> AnyElement { - let workspace = self.workspace.upgrade(cx); - - if workspace.is_none() { - return Empty::new().into_any(); - } - - let workspace = workspace.unwrap(); - let dock_position = workspace.read(cx).dock.position; - let dock_pane = workspace.read(cx).dock_pane().clone(); - - let theme = cx.global::().theme.clone(); - - let button = MouseEventHandler::::new(0, cx, { - let theme = theme.clone(); - move |state, _| { - let style = theme - .workspace - .status_bar - .sidebar_buttons - .item - .style_for(state, dock_position.is_visible()); - - Svg::new(icon_for_dock_anchor(dock_position.anchor())) - .with_color(style.icon_color) - .constrained() - .with_width(style.icon_size) - .with_height(style.icon_size) - .contained() - .with_style(style.container) - } - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_up(MouseButton::Left, move |event, this, cx| { - let drop_index = dock_pane.read(cx).items_len() + 1; - handle_dropped_item( - event, - this.workspace.clone(), - &dock_pane.downgrade(), - drop_index, - false, - None, - cx, - ); - }); - - if dock_position.is_visible() { - button - .on_click(MouseButton::Left, |_, this, cx| { - if let Some(workspace) = this.workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - Dock::hide_dock(workspace, &Default::default(), cx) - }) - } - }) - .with_tooltip::( - 0, - "Hide Dock".into(), - Some(Box::new(HideDock)), - theme.tooltip.clone(), - cx, - ) - } else { - button - .on_click(MouseButton::Left, |_, this, cx| { - if let Some(workspace) = this.workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - Dock::focus_dock(workspace, &Default::default(), cx) - }) - } - }) - .with_tooltip::( - 0, - "Focus Dock".into(), - Some(Box::new(FocusDock)), - theme.tooltip.clone(), - cx, - ) - } - .into_any() - } -} - -impl StatusItemView for ToggleDockButton { - fn set_active_pane_item( - &mut self, - _active_pane_item: Option<&dyn crate::ItemHandle>, - _cx: &mut ViewContext, - ) { - //Not applicable - } -} diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 96320a4baf..1aed1c011e 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2,7 +2,6 @@ mod dragged_item_receiver; use super::{ItemHandle, SplitDirection}; use crate::{ - dock::{icon_for_dock_anchor, AnchorDockBottom, AnchorDockRight, Dock, ExpandDock}, item::WeakItemHandle, toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, Workspace, @@ -29,7 +28,7 @@ use gpui::{ }; use project::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; -use settings::{Autosave, DockAnchor, Settings}; +use settings::{Autosave, Settings}; use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc}; use theme::Theme; use util::ResultExt; @@ -148,7 +147,6 @@ pub struct Pane { toolbar: ViewHandle, tab_bar_context_menu: TabBarContextMenu, tab_context_menu: ViewHandle, - docked: Option, _background_actions: BackgroundActions, workspace: WeakViewHandle, has_focus: bool, @@ -204,7 +202,6 @@ pub enum ReorderBehavior { enum TabBarContextMenuKind { New, Split, - Dock, } struct TabBarContextMenu { @@ -224,7 +221,6 @@ impl TabBarContextMenu { impl Pane { pub fn new( workspace: WeakViewHandle, - docked: Option, background_actions: BackgroundActions, cx: &mut ViewContext, ) -> Self { @@ -256,7 +252,6 @@ impl Pane { handle: context_menu, }, tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)), - docked, _background_actions: background_actions, workspace, has_focus: false, @@ -280,11 +275,6 @@ impl Pane { self.has_focus } - pub fn set_docked(&mut self, docked: Option, cx: &mut ViewContext) { - self.docked = docked; - cx.notify(); - } - pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { ItemNavHistory { history: self.nav_history.clone(), @@ -1157,23 +1147,6 @@ impl Pane { self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split; } - fn deploy_dock_menu(&mut self, cx: &mut ViewContext) { - self.tab_bar_context_menu.handle.update(cx, |menu, cx| { - menu.show( - Default::default(), - AnchorCorner::TopRight, - vec![ - ContextMenuItem::action("Anchor Dock Right", AnchorDockRight), - ContextMenuItem::action("Anchor Dock Bottom", AnchorDockBottom), - ContextMenuItem::action("Expand Dock", ExpandDock), - ], - cx, - ); - }); - - self.tab_bar_context_menu.kind = TabBarContextMenuKind::Dock; - } - fn deploy_new_menu(&mut self, cx: &mut ViewContext) { self.tab_bar_context_menu.handle.update(cx, |menu, cx| { menu.show( @@ -1616,51 +1589,13 @@ impl Pane { self.tab_bar_context_menu .handle_if_kind(TabBarContextMenuKind::New), )) - .with_child( - self.docked - .map(|anchor| { - // Add the dock menu button if this pane is a dock - let dock_icon = icon_for_dock_anchor(anchor); - - render_tab_bar_button( - 1, - dock_icon, - cx, - |pane, cx| pane.deploy_dock_menu(cx), - self.tab_bar_context_menu - .handle_if_kind(TabBarContextMenuKind::Dock), - ) - }) - .unwrap_or_else(|| { - // Add the split menu if this pane is not a dock - render_tab_bar_button( - 2, - "icons/split_12.svg", - cx, - |pane, cx| pane.deploy_split_menu(cx), - self.tab_bar_context_menu - .handle_if_kind(TabBarContextMenuKind::Split), - ) - }), - ) - // Add the close dock button if this pane is a dock - .with_children(self.docked.map(|_| { - render_tab_bar_button( - 3, - "icons/x_mark_8.svg", - cx, - |this, cx| { - if let Some(workspace) = this.workspace.upgrade(cx) { - cx.window_context().defer(move |cx| { - workspace.update(cx, |workspace, cx| { - Dock::hide_dock(workspace, &Default::default(), cx) - }) - }); - } - }, - None, - ) - })) + .with_child(render_tab_bar_button( + 2, + "icons/split_12.svg", + cx, + |pane, cx| pane.deploy_split_menu(cx), + self.tab_bar_context_menu.handle_if_kind(TabBarContextMenuKind::Split), + )) .contained() .with_style(theme.workspace.tab_bar.pane_button_container) .flex(1., false) @@ -1737,11 +1672,7 @@ impl View for Pane { 0, self.active_item_index + 1, false, - if self.docked.is_some() { - None - } else { - Some(100.) - }, + Some(100.), cx, { let toolbar = self.toolbar.clone(); @@ -1843,9 +1774,6 @@ impl View for Pane { fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { Self::reset_to_default_keymap_context(keymap); - if self.docked.is_some() { - keymap.add_identifier("docked"); - } } } diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index d5d79c3ddd..3b7d0ecb87 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -11,7 +11,6 @@ use gpui::{platform::WindowBounds, Axis}; use util::{unzip_option, ResultExt}; use uuid::Uuid; -use crate::dock::DockPosition; use crate::WorkspaceId; use model::{ @@ -25,9 +24,9 @@ define_connection! { // workspaces( // workspace_id: usize, // Primary key for workspaces // workspace_location: Bincode>, - // dock_visible: bool, - // dock_anchor: DockAnchor, // 'Bottom' / 'Right' / 'Expanded' - // dock_pane: Option, // PaneId + // dock_visible: bool, // Deprecated + // dock_anchor: DockAnchor, // Deprecated + // dock_pane: Option, // Deprecated // left_sidebar_open: boolean, // timestamp: String, // UTC YYYY-MM-DD HH:MM:SS // window_state: String, // WindowBounds Discriminant @@ -71,10 +70,10 @@ define_connection! { CREATE TABLE workspaces( workspace_id INTEGER PRIMARY KEY, workspace_location BLOB UNIQUE, - dock_visible INTEGER, // Boolean - dock_anchor TEXT, // Enum: 'Bottom' / 'Right' / 'Expanded' - dock_pane INTEGER, // NULL indicates that we don't have a dock pane yet - left_sidebar_open INTEGER, //Boolean + dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed. + dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed. + dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed. + left_sidebar_open INTEGER, // Boolean timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, FOREIGN KEY(dock_pane) REFERENCES panes(pane_id) ) STRICT; @@ -146,11 +145,10 @@ impl WorkspaceDb { // Note that we re-assign the workspace_id here in case it's empty // and we've grabbed the most recent workspace - let (workspace_id, workspace_location, left_sidebar_open, dock_position, bounds, display): ( + let (workspace_id, workspace_location, left_sidebar_open, bounds, display): ( WorkspaceId, WorkspaceLocation, bool, - DockPosition, Option, Option, ) = self @@ -159,8 +157,6 @@ impl WorkspaceDb { workspace_id, workspace_location, left_sidebar_open, - dock_visible, - dock_anchor, window_state, window_x, window_y, @@ -178,15 +174,10 @@ impl WorkspaceDb { Some(SerializedWorkspace { id: workspace_id, location: workspace_location.clone(), - dock_pane: self - .get_dock_pane(workspace_id) - .context("Getting dock pane") - .log_err()?, center_group: self .get_center_pane_group(workspace_id) .context("Getting center group") .log_err()?, - dock_position, left_sidebar_open, bounds, display, @@ -200,7 +191,6 @@ impl WorkspaceDb { conn.with_savepoint("update_worktrees", || { // Clear out panes and pane_groups conn.exec_bound(sql!( - UPDATE workspaces SET dock_pane = NULL WHERE workspace_id = ?1; DELETE FROM pane_groups WHERE workspace_id = ?1; DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id) .expect("Clearing old panes"); @@ -216,41 +206,24 @@ impl WorkspaceDb { workspace_id, workspace_location, left_sidebar_open, - dock_visible, - dock_anchor, timestamp ) - VALUES (?1, ?2, ?3, ?4, ?5, CURRENT_TIMESTAMP) + VALUES (?1, ?2, ?3, CURRENT_TIMESTAMP) ON CONFLICT DO UPDATE SET workspace_location = ?2, left_sidebar_open = ?3, - dock_visible = ?4, - dock_anchor = ?5, timestamp = CURRENT_TIMESTAMP ))?(( workspace.id, &workspace.location, - workspace.left_sidebar_open, - workspace.dock_position, )) .context("Updating workspace")?; - // Save center pane group and dock pane + // Save center pane group Self::save_pane_group(conn, workspace.id, &workspace.center_group, None) .context("save pane group in save workspace")?; - let dock_id = Self::save_pane(conn, workspace.id, &workspace.dock_pane, None, true) - .context("save pane in save workspace")?; - - // Complete workspace initialization - conn.exec_bound(sql!( - UPDATE workspaces - SET dock_pane = ? - WHERE workspace_id = ? - ))?((dock_id, workspace.id)) - .context("Finishing initialization with dock pane")?; - Ok(()) }) .log_err(); @@ -402,32 +375,17 @@ impl WorkspaceDb { Ok(()) } SerializedPaneGroup::Pane(pane) => { - Self::save_pane(conn, workspace_id, &pane, parent, false)?; + Self::save_pane(conn, workspace_id, &pane, parent)?; Ok(()) } } } - fn get_dock_pane(&self, workspace_id: WorkspaceId) -> Result { - let (pane_id, active) = self.select_row_bound(sql!( - SELECT pane_id, active - FROM panes - WHERE pane_id = (SELECT dock_pane FROM workspaces WHERE workspace_id = ?) - ))?(workspace_id)? - .context("No dock pane for workspace")?; - - Ok(SerializedPane::new( - self.get_items(pane_id).context("Reading items")?, - active, - )) - } - fn save_pane( conn: &Connection, workspace_id: WorkspaceId, pane: &SerializedPane, - parent: Option<(GroupId, usize)>, // None indicates BOTH dock pane AND center_pane - dock: bool, + parent: Option<(GroupId, usize)>, ) -> Result { let pane_id = conn.select_row_bound::<_, i64>(sql!( INSERT INTO panes(workspace_id, active) @@ -436,13 +394,11 @@ impl WorkspaceDb { ))?((workspace_id, pane.active))? .ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?; - if !dock { - let (parent_id, order) = unzip_option(parent); - conn.exec_bound(sql!( - INSERT INTO center_panes(pane_id, parent_group_id, position) - VALUES (?, ?, ?) - ))?((pane_id, parent_id, order))?; - } + let (parent_id, order) = unzip_option(parent); + conn.exec_bound(sql!( + INSERT INTO center_panes(pane_id, parent_group_id, position) + VALUES (?, ?, ?) + ))?((pane_id, parent_id, order))?; Self::save_items(conn, workspace_id, pane_id, &pane.children).context("Saving items")?; @@ -498,10 +454,8 @@ impl WorkspaceDb { #[cfg(test)] mod tests { - use std::sync::Arc; use db::open_test_db; - use settings::DockAnchor; use super::*; @@ -578,20 +532,16 @@ mod tests { let mut workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), - dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom), center_group: Default::default(), - dock_pane: Default::default(), left_sidebar_open: true, bounds: Default::default(), display: Default::default(), }; - let mut workspace_2 = SerializedWorkspace { + let mut _workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), - dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded), center_group: Default::default(), - dock_pane: Default::default(), left_sidebar_open: false, bounds: Default::default(), display: Default::default(), @@ -606,7 +556,7 @@ mod tests { }) .await; - db.save_workspace(workspace_2.clone()).await; + db.save_workspace(_workspace_2.clone()).await; db.write(|conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) @@ -619,26 +569,27 @@ mod tests { db.save_workspace(workspace_1.clone()).await; db.save_workspace(workspace_1).await; - workspace_2.dock_pane.children.push(SerializedItem { - kind: Arc::from("Test"), - item_id: 10, - active: true, - }); - db.save_workspace(workspace_2).await; + todo!(); + // workspace_2.dock_pane.children.push(SerializedItem { + // kind: Arc::from("Test"), + // item_id: 10, + // active: true, + // }); + // db.save_workspace(workspace_2).await; - let test_text_2 = db - .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) - .unwrap()(2) - .unwrap() - .unwrap(); - assert_eq!(test_text_2, "test-text-2"); + // let test_text_2 = db + // .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) + // .unwrap()(2) + // .unwrap() + // .unwrap(); + // assert_eq!(test_text_2, "test-text-2"); - let test_text_1 = db - .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) - .unwrap()(1) - .unwrap() - .unwrap(); - assert_eq!(test_text_1, "test-text-1"); + // let test_text_1 = db + // .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) + // .unwrap()(1) + // .unwrap() + // .unwrap(); + // assert_eq!(test_text_1, "test-text-1"); } #[gpui::test] @@ -647,16 +598,6 @@ mod tests { let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await); - let dock_pane = crate::persistence::model::SerializedPane { - children: vec![ - SerializedItem::new("Terminal", 1, false), - SerializedItem::new("Terminal", 2, false), - SerializedItem::new("Terminal", 3, true), - SerializedItem::new("Terminal", 4, false), - ], - active: false, - }; - // ----------------- // | 1,2 | 5,6 | // | - - - | | @@ -697,9 +638,7 @@ mod tests { let workspace = SerializedWorkspace { id: 5, location: (["/tmp", "/tmp2"]).into(), - dock_position: DockPosition::Shown(DockAnchor::Bottom), center_group, - dock_pane, left_sidebar_open: true, bounds: Default::default(), display: Default::default(), @@ -727,9 +666,7 @@ mod tests { let workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), - dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom), center_group: Default::default(), - dock_pane: Default::default(), left_sidebar_open: true, bounds: Default::default(), display: Default::default(), @@ -738,9 +675,7 @@ mod tests { let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), - dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded), center_group: Default::default(), - dock_pane: Default::default(), left_sidebar_open: false, bounds: Default::default(), display: Default::default(), @@ -776,9 +711,7 @@ mod tests { let mut workspace_3 = SerializedWorkspace { id: 3, location: (&["/tmp", "/tmp2"]).into(), - dock_position: DockPosition::Shown(DockAnchor::Right), center_group: Default::default(), - dock_pane: Default::default(), left_sidebar_open: false, bounds: Default::default(), display: Default::default(), @@ -801,52 +734,23 @@ mod tests { ); } - use crate::dock::DockPosition; use crate::persistence::model::SerializedWorkspace; use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup}; fn default_workspace>( workspace_id: &[P], - dock_pane: SerializedPane, center_group: &SerializedPaneGroup, ) -> SerializedWorkspace { SerializedWorkspace { id: 4, location: workspace_id.into(), - dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Right), center_group: center_group.clone(), - dock_pane, left_sidebar_open: true, bounds: Default::default(), display: Default::default(), } } - #[gpui::test] - async fn test_basic_dock_pane() { - env_logger::try_init().ok(); - - let db = WorkspaceDb(open_test_db("basic_dock_pane").await); - - let dock_pane = crate::persistence::model::SerializedPane::new( - vec![ - SerializedItem::new("Terminal", 1, false), - SerializedItem::new("Terminal", 4, false), - SerializedItem::new("Terminal", 2, false), - SerializedItem::new("Terminal", 3, true), - ], - false, - ); - - let workspace = default_workspace(&["/tmp"], dock_pane, &Default::default()); - - db.save_workspace(workspace.clone()).await; - - let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); - - assert_eq!(workspace.dock_pane, new_workspace.dock_pane); - } - #[gpui::test] async fn test_simple_split() { env_logger::try_init().ok(); @@ -890,7 +794,7 @@ mod tests { ], }; - let workspace = default_workspace(&["/tmp"], Default::default(), ¢er_pane); + let workspace = default_workspace(&["/tmp"], ¢er_pane); db.save_workspace(workspace.clone()).await; @@ -939,7 +843,7 @@ mod tests { let id = &["/tmp"]; - let mut workspace = default_workspace(id, Default::default(), ¢er_pane); + let mut workspace = default_workspace(id, ¢er_pane); db.save_workspace(workspace.clone()).await; diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index e838d22f0d..3a348d6105 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -1,5 +1,5 @@ use crate::{ - dock::DockPosition, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, + ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, }; use anyhow::{anyhow, Context, Result}; use async_recursion::async_recursion; @@ -11,7 +11,6 @@ use gpui::{ platform::WindowBounds, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WeakViewHandle, }; use project::Project; -use settings::DockAnchor; use std::{ path::{Path, PathBuf}, sync::Arc, @@ -62,9 +61,7 @@ impl Column for WorkspaceLocation { pub struct SerializedWorkspace { pub id: WorkspaceId, pub location: WorkspaceLocation, - pub dock_position: DockPosition, pub center_group: SerializedPaneGroup, - pub dock_pane: SerializedPane, pub left_sidebar_open: bool, pub bounds: Option, pub display: Option, @@ -278,64 +275,39 @@ impl Column for SerializedItem { } } -impl StaticColumnCount for DockPosition { - fn column_count() -> usize { - 2 - } -} -impl Bind for DockPosition { - fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let next_index = statement.bind(self.is_visible(), start_index)?; - statement.bind(self.anchor(), next_index) - } -} - -impl Column for DockPosition { - fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { - let (visible, next_index) = bool::column(statement, start_index)?; - let (dock_anchor, next_index) = DockAnchor::column(statement, next_index)?; - let position = if visible { - DockPosition::Shown(dock_anchor) - } else { - DockPosition::Hidden(dock_anchor) - }; - Ok((position, next_index)) - } -} - #[cfg(test)] mod tests { use db::sqlez::connection::Connection; - use settings::DockAnchor; - use super::WorkspaceLocation; + // use super::WorkspaceLocation; #[test] fn test_workspace_round_trips() { - let db = Connection::open_memory(Some("workspace_id_round_trips")); + let _db = Connection::open_memory(Some("workspace_id_round_trips")); - db.exec(indoc::indoc! {" - CREATE TABLE workspace_id_test( - workspace_id INTEGER, - dock_anchor TEXT - );"}) - .unwrap()() - .unwrap(); + todo!(); + // db.exec(indoc::indoc! {" + // CREATE TABLE workspace_id_test( + // workspace_id INTEGER, + // dock_anchor TEXT + // );"}) + // .unwrap()() + // .unwrap(); - let workspace_id: WorkspaceLocation = WorkspaceLocation::from(&["\test2", "\test1"]); + // let workspace_id: WorkspaceLocation = WorkspaceLocation::from(&["\test2", "\test1"]); - db.exec_bound("INSERT INTO workspace_id_test(workspace_id, dock_anchor) VALUES (?,?)") - .unwrap()((&workspace_id, DockAnchor::Bottom)) - .unwrap(); + // db.exec_bound("INSERT INTO workspace_id_test(workspace_id, dock_anchor) VALUES (?,?)") + // .unwrap()((&workspace_id, DockAnchor::Bottom)) + // .unwrap(); - assert_eq!( - db.select_row("SELECT workspace_id, dock_anchor FROM workspace_id_test LIMIT 1") - .unwrap()() - .unwrap(), - Some(( - WorkspaceLocation::from(&["\test1", "\test2"]), - DockAnchor::Bottom - )) - ); + // assert_eq!( + // db.select_row("SELECT workspace_id, dock_anchor FROM workspace_id_test LIMIT 1") + // .unwrap()() + // .unwrap(), + // Some(( + // WorkspaceLocation::from(&["\test1", "\test2"]), + // DockAnchor::Bottom + // )) + // ); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a57cea526b..84d1832df7 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2,7 +2,6 @@ /// /// This may cause issues when you're trying to write tests that use workspace focus to add items at /// specific locations. -pub mod dock; pub mod item; pub mod notifications; pub mod pane; @@ -22,7 +21,6 @@ use client::{ Client, TypedEnvelope, UserStore, }; use collections::{hash_map, HashMap, HashSet}; -use dock::{Dock, DockDefaultItemFactory, ToggleDockButton}; use drag_and_drop::DragAndDrop; use futures::{ channel::{mpsc, oneshot}, @@ -63,7 +61,6 @@ use crate::{ persistence::model::{SerializedPane, SerializedPaneGroup, SerializedWorkspace}, }; use lazy_static::lazy_static; -use log::warn; use notifications::{NotificationHandle, NotifyResultExt}; pub use pane::*; pub use pane_group::*; @@ -75,7 +72,7 @@ pub use persistence::{ use postage::prelude::Stream; use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use serde::Deserialize; -use settings::{Autosave, DockAnchor, Settings}; +use settings::{Autosave, Settings}; use shared_screen::SharedScreen; use sidebar::{Sidebar, SidebarButtons, SidebarSide, ToggleSidebarItem}; use status_bar::StatusBar; @@ -185,7 +182,6 @@ impl_actions!(workspace, [ActivatePane]); pub fn init(app_state: Arc, cx: &mut AppContext) { pane::init(cx); - dock::init(cx); notifications::init(cx); cx.add_global_action({ @@ -362,7 +358,6 @@ pub struct AppState { pub build_window_options: fn(Option, Option, &dyn Platform) -> WindowOptions<'static>, pub initialize_workspace: fn(&mut Workspace, &Arc, &mut ViewContext), - pub dock_default_item_factory: DockDefaultItemFactory, pub background_actions: BackgroundActions, } @@ -386,7 +381,6 @@ impl AppState { user_store, initialize_workspace: |_, _, _| {}, build_window_options: |_, _, _| Default::default(), - dock_default_item_factory: |_, _| None, background_actions: || &[], }) } @@ -439,7 +433,6 @@ impl DelayedDebouncedEditAction { } pub enum Event { - DockAnchorChanged, PaneAdded(ViewHandle), ContactRequestedJoin(u64), } @@ -457,7 +450,6 @@ pub struct Workspace { last_active_center_pane: Option>, status_bar: ViewHandle, titlebar_item: Option, - dock: Dock, notifications: Vec<(TypeId, usize, Box)>, project: ModelHandle, leader_state: LeaderState, @@ -535,16 +527,10 @@ impl Workspace { let weak_handle = cx.weak_handle(); let center_pane = cx - .add_view(|cx| Pane::new(weak_handle.clone(), None, app_state.background_actions, cx)); + .add_view(|cx| Pane::new(weak_handle.clone(), app_state.background_actions, cx)); cx.subscribe(¢er_pane, Self::handle_pane_event).detach(); cx.focus(¢er_pane); cx.emit(Event::PaneAdded(center_pane.clone())); - let dock = Dock::new( - app_state.dock_default_item_factory, - app_state.background_actions, - cx, - ); - let dock_pane = dock.pane().clone(); let mut current_user = app_state.user_store.read(cx).watch_current_user(); let mut connection_status = app_state.client.status(); @@ -559,7 +545,6 @@ impl Workspace { } anyhow::Ok(()) }); - let handle = cx.handle(); // All leader updates are enqueued and then processed in a single task, so // that each asynchronous operation can be run in order. @@ -581,14 +566,12 @@ impl Workspace { let right_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Right)); let left_sidebar_buttons = cx.add_view(|cx| SidebarButtons::new(left_sidebar.clone(), weak_handle.clone(), cx)); - let toggle_dock = cx.add_view(|cx| ToggleDockButton::new(handle, cx)); let right_sidebar_buttons = cx.add_view(|cx| SidebarButtons::new(right_sidebar.clone(), weak_handle.clone(), cx)); let status_bar = cx.add_view(|cx| { let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); status_bar.add_left_item(left_sidebar_buttons, cx); status_bar.add_right_item(right_sidebar_buttons, cx); - status_bar.add_right_item(toggle_dock, cx); status_bar }); @@ -630,11 +613,7 @@ impl Workspace { modal: None, weak_self: weak_handle.clone(), center: PaneGroup::new(center_pane.clone()), - dock, - // When removing an item, the last element remaining in this array - // is used to find where focus should fallback to. As such, the order - // of these two variables is important. - panes: vec![dock_pane.clone(), center_pane.clone()], + panes: vec![center_pane.clone()], panes_by_item: Default::default(), active_pane: center_pane.clone(), last_active_center_pane: Some(center_pane.downgrade()), @@ -664,10 +643,6 @@ impl Workspace { cx.defer(move |_, cx| { Self::load_from_serialized_workspace(weak_handle, serialized_workspace, cx) }); - } else if project.read(cx).is_local() { - if cx.global::().default_dock_anchor != DockAnchor::Expanded { - Dock::show(&mut this, false, cx); - } } this @@ -1336,16 +1311,11 @@ impl Workspace { SidebarSide::Left => &mut self.left_sidebar, SidebarSide::Right => &mut self.right_sidebar, }; - let open = sidebar.update(cx, |sidebar, cx| { + sidebar.update(cx, |sidebar, cx| { let open = !sidebar.is_open(); sidebar.set_open(open, cx); - open }); - if open { - Dock::hide_on_sidebar_shown(self, sidebar_side, cx); - } - self.serialize_workspace(cx); cx.focus_self(); @@ -1369,8 +1339,6 @@ impl Workspace { }); if let Some(active_item) = active_item { - Dock::hide_on_sidebar_shown(self, action.sidebar_side, cx); - if active_item.is_focused(cx) { cx.focus_self(); } else { @@ -1401,8 +1369,6 @@ impl Workspace { sidebar.active_item().cloned() }); if let Some(active_item) = active_item { - Dock::hide_on_sidebar_shown(self, sidebar_side, cx); - if active_item.is_focused(cx) { cx.focus_self(); } else { @@ -1424,7 +1390,6 @@ impl Workspace { let pane = cx.add_view(|cx| { Pane::new( self.weak_handle(), - None, self.app_state.background_actions, cx, ) @@ -1466,16 +1431,12 @@ impl Workspace { cx: &mut ViewContext, ) -> Task, anyhow::Error>> { let pane = pane.unwrap_or_else(|| { - if !self.dock_active() { - self.active_pane().downgrade() - } else { - self.last_active_center_pane.clone().unwrap_or_else(|| { - self.panes - .first() - .expect("There must be an active pane") - .downgrade() - }) - } + self.last_active_center_pane.clone().unwrap_or_else(|| { + self.panes + .first() + .expect("There must be an active pane") + .downgrade() + }) }); let task = self.load_path(path.into(), cx); @@ -1599,15 +1560,7 @@ impl Workspace { status_bar.set_active_pane(&self.active_pane, cx); }); self.active_item_path_changed(cx); - - if &pane == self.dock_pane() { - Dock::show(self, true, cx); - } else { self.last_active_center_pane = Some(pane.downgrade()); - if self.dock.is_anchored_at(DockAnchor::Expanded) { - Dock::hide(self, cx); - } - } cx.notify(); } @@ -1630,13 +1583,11 @@ impl Workspace { event: &pane::Event, cx: &mut ViewContext, ) { - let is_dock = &pane == self.dock.pane(); match event { - pane::Event::Split(direction) if !is_dock => { + pane::Event::Split(direction) => { self.split_pane(pane, *direction, cx); } - pane::Event::Remove if !is_dock => self.remove_pane(pane, cx), - pane::Event::Remove if is_dock => Dock::hide(self, cx), + pane::Event::Remove => self.remove_pane(pane, cx), pane::Event::ActivateItem { local } => { if *local { self.unfollow(&pane, cx); @@ -1662,7 +1613,6 @@ impl Workspace { pane::Event::Focus => { self.handle_pane_focused(pane.clone(), cx); } - _ => {} } self.serialize_workspace(cx); @@ -1674,11 +1624,6 @@ impl Workspace { direction: SplitDirection, cx: &mut ViewContext, ) -> Option> { - if &pane == self.dock_pane() { - warn!("Can't split dock pane."); - return None; - } - let item = pane.read(cx).active_item()?; let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) { let new_pane = self.add_pane(cx); @@ -1702,10 +1647,6 @@ impl Workspace { ) { let Some(pane_to_split) = pane_to_split.upgrade(cx) else { return; }; let Some(from) = from.upgrade(cx) else { return; }; - if &pane_to_split == self.dock_pane() { - warn!("Can't split dock pane."); - return; - } let new_pane = self.add_pane(cx); Pane::move_item(self, from.clone(), new_pane.clone(), item_id_to_move, 0, cx); @@ -1723,11 +1664,6 @@ impl Workspace { cx: &mut ViewContext, ) -> Option>> { let pane_to_split = pane_to_split.upgrade(cx)?; - if &pane_to_split == self.dock_pane() { - warn!("Can't split dock pane."); - return None; - } - let new_pane = self.add_pane(cx); self.center .split(&pane_to_split, &new_pane, split_direction) @@ -1764,14 +1700,6 @@ impl Workspace { &self.active_pane } - pub fn dock_pane(&self) -> &ViewHandle { - self.dock.pane() - } - - fn dock_active(&self) -> bool { - &self.active_pane == self.dock.pane() - } - fn project_remote_id_changed(&mut self, remote_id: Option, cx: &mut ViewContext) { if let Some(remote_id) = remote_id { self.remote_entity_subscription = Some( @@ -2518,14 +2446,11 @@ impl Workspace { // - with_local_workspace() relies on this to not have other stuff open // when you open your log if !location.paths().is_empty() { - let dock_pane = serialize_pane_handle(self.dock.pane(), cx); let center_group = build_serialized_pane_group(&self.center.root, cx); let serialized_workspace = SerializedWorkspace { id: self.database_id, location, - dock_position: self.dock.position(), - dock_pane, center_group, left_sidebar_open: self.left_sidebar.read(cx).is_open(), bounds: Default::default(), @@ -2545,26 +2470,14 @@ impl Workspace { cx: &mut AppContext, ) { cx.spawn(|mut cx| async move { - let (project, dock_pane_handle, old_center_pane) = + let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| { ( workspace.project().clone(), - workspace.dock_pane().downgrade(), workspace.last_active_center_pane.clone(), ) })?; - serialized_workspace - .dock_pane - .deserialize_to( - &project, - &dock_pane_handle, - serialized_workspace.id, - &workspace, - &mut cx, - ) - .await?; - // Traverse the splits tree and add to things let center_group = serialized_workspace .center_group @@ -2602,18 +2515,6 @@ impl Workspace { workspace.toggle_sidebar(SidebarSide::Left, cx); } - // Note that without after_window, the focus_self() and - // the focus the dock generates start generating alternating - // focus due to the deferred execution each triggering each other - cx.after_window_update(move |workspace, cx| { - Dock::set_dock_position( - workspace, - serialized_workspace.dock_position, - true, - cx, - ); - }); - cx.notify(); })?; @@ -2634,7 +2535,6 @@ impl Workspace { fs: project.read(cx).fs().clone(), build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _| {}, - dock_default_item_factory: |_, _| None, background_actions: || &[], }); Self::new(None, 0, project, app_state, cx) @@ -2729,15 +2629,9 @@ impl View for Workspace { )) .flex(1., true), ) - .with_children(self.dock.render( - &theme, - DockAnchor::Bottom, - cx, - )), ) .flex(1., true), ) - .with_children(self.dock.render(&theme, DockAnchor::Right, cx)) .with_children( if self.right_sidebar.read(cx).active_item().is_some() { Some( @@ -2760,11 +2654,6 @@ impl View for Workspace { }) .with_child(Overlay::new( Stack::new() - .with_children(self.dock.render( - &theme, - DockAnchor::Expanded, - cx, - )) .with_children(self.modal.as_ref().map(|modal| { ChildView::new(modal, cx) .contained() diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f498078b52..ff969a8c75 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -52,7 +52,7 @@ use staff_mode::StaffMode; use theme::ThemeRegistry; use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use workspace::{ - dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, + item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, Workspace, }; use zed::{self, build_window_options, initialize_workspace, languages, menus}; @@ -205,7 +205,6 @@ fn main() { fs, build_window_options, initialize_workspace, - dock_default_item_factory, background_actions, }); cx.set_global(Arc::downgrade(&app_state)); @@ -776,7 +775,6 @@ pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] { &[ ("Go to file", &file_finder::Toggle), ("Open command palette", &command_palette::Toggle), - ("Focus the dock", &FocusDock), ("Open recent projects", &recent_projects::OpenRecent), ("Change your settings", &OpenSettings), ] diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index f687237bd2..46443eb77d 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -306,7 +306,6 @@ pub fn initialize_workspace( .detach(); cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone())); - cx.emit(workspace::Event::PaneAdded(workspace.dock_pane().clone())); let collab_titlebar_item = cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); From 03f8c1206a3c1f0d9abcbb8dd2745694f32b90f7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 May 2023 16:04:36 -0600 Subject: [PATCH 002/131] Rename Sidebar to Dock --- assets/keymaps/atom.json | 4 +- assets/keymaps/default.json | 2 +- assets/keymaps/jetbrains.json | 2 +- assets/keymaps/sublime_text.json | 2 +- assets/keymaps/textmate.json | 2 +- crates/copilot_button/src/copilot_button.rs | 2 +- crates/feedback/src/deploy_feedback_button.rs | 2 +- crates/project_panel/src/project_panel.rs | 2 +- crates/terminal_view/src/terminal_button.rs | 2 +- crates/theme/src/theme.rs | 12 +- crates/welcome/src/welcome.rs | 4 +- crates/workspace/src/{sidebar.rs => dock.rs} | 94 +++++++-------- crates/workspace/src/item.rs | 4 +- crates/workspace/src/workspace.rs | 114 +++++++++--------- crates/zed/src/menus.rs | 2 +- crates/zed/src/zed.rs | 12 +- styles/src/styleTree/statusBar.ts | 2 +- styles/src/styleTree/workspace.ts | 15 +-- 18 files changed, 133 insertions(+), 146 deletions(-) rename crates/workspace/src/{sidebar.rs => dock.rs} (81%) diff --git a/assets/keymaps/atom.json b/assets/keymaps/atom.json index a1e8cf6d9d..634aed322a 100644 --- a/assets/keymaps/atom.json +++ b/assets/keymaps/atom.json @@ -39,8 +39,8 @@ { "context": "Workspace", "bindings": { - "cmd-\\": "workspace::ToggleLeftSidebar", - "cmd-k cmd-b": "workspace::ToggleLeftSidebar", + "cmd-\\": "workspace::ToggleLeftDock", + "cmd-k cmd-b": "workspace::ToggleLeftDock", "cmd-t": "file_finder::Toggle", "cmd-shift-r": "project_symbols::Toggle" } diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 6e8b519313..cc36f79e9f 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -349,7 +349,7 @@ "workspace::ActivatePane", 8 ], - "cmd-b": "workspace::ToggleLeftSidebar", + "cmd-b": "workspace::ToggleLeftDock", "cmd-shift-f": "workspace::NewSearch", "cmd-k cmd-t": "theme_selector::Toggle", "cmd-k cmd-s": "zed::OpenKeymap", diff --git a/assets/keymaps/jetbrains.json b/assets/keymaps/jetbrains.json index 0dafe58d32..69a583e1e9 100644 --- a/assets/keymaps/jetbrains.json +++ b/assets/keymaps/jetbrains.json @@ -65,7 +65,7 @@ "bindings": { "cmd-shift-a": "command_palette::Toggle", "cmd-alt-o": "project_symbols::Toggle", - "cmd-1": "workspace::ToggleLeftSidebar", + "cmd-1": "workspace::ToggleLeftDock", "cmd-6": "diagnostics::Deploy" } } diff --git a/assets/keymaps/sublime_text.json b/assets/keymaps/sublime_text.json index 7a285ee824..2d32b77d58 100644 --- a/assets/keymaps/sublime_text.json +++ b/assets/keymaps/sublime_text.json @@ -45,7 +45,7 @@ { "context": "Workspace", "bindings": { - "cmd-k cmd-b": "workspace::ToggleLeftSidebar", + "cmd-k cmd-b": "workspace::ToggleLeftDock", "cmd-t": "file_finder::Toggle", "shift-cmd-r": "project_symbols::Toggle", // Currently busted: https://github.com/zed-industries/feedback/issues/898 diff --git a/assets/keymaps/textmate.json b/assets/keymaps/textmate.json index 738b6603ff..06be727429 100644 --- a/assets/keymaps/textmate.json +++ b/assets/keymaps/textmate.json @@ -68,7 +68,7 @@ { "context": "Workspace", "bindings": { - "cmd-alt-ctrl-d": "workspace::ToggleLeftSidebar", + "cmd-alt-ctrl-d": "workspace::ToggleLeftDock", "cmd-t": "file_finder::Toggle", "cmd-shift-t": "project_symbols::Toggle" } diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 4b0c9b494a..0d6bcc115c 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -64,7 +64,7 @@ impl View for CopilotButton { let style = theme .workspace .status_bar - .sidebar_buttons + .panel_buttons .item .style_for(state, active); diff --git a/crates/feedback/src/deploy_feedback_button.rs b/crates/feedback/src/deploy_feedback_button.rs index b464d00887..5fdb378eb6 100644 --- a/crates/feedback/src/deploy_feedback_button.rs +++ b/crates/feedback/src/deploy_feedback_button.rs @@ -40,7 +40,7 @@ impl View for DeployFeedbackButton { let style = &theme .workspace .status_bar - .sidebar_buttons + .panel_buttons .item .style_for(state, active); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 7602ff7db8..4a1d488eaf 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1327,7 +1327,7 @@ impl Entity for ProjectPanel { type Event = Event; } -impl workspace::sidebar::SidebarItem for ProjectPanel { +impl workspace::dock::DockItem for ProjectPanel { fn should_show_badge(&self, _: &AppContext) -> bool { false } diff --git a/crates/terminal_view/src/terminal_button.rs b/crates/terminal_view/src/terminal_button.rs index 7b0da416ff..cacd21c733 100644 --- a/crates/terminal_view/src/terminal_button.rs +++ b/crates/terminal_view/src/terminal_button.rs @@ -52,7 +52,7 @@ impl View for TerminalButton { let style = theme .workspace .status_bar - .sidebar_buttons + .panel_buttons .item .style_for(state, active); diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 8c77295748..c239e0ef40 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -61,7 +61,7 @@ pub struct Workspace { pub pane_divider: Border, pub leader_border_opacity: f32, pub leader_border_width: f32, - pub sidebar: Sidebar, + pub dock: Dock, pub status_bar: StatusBar, pub toolbar: Toolbar, pub breadcrumb_height: f32, @@ -335,16 +335,16 @@ pub struct StatusBar { pub auto_update_progress_message: TextStyle, pub auto_update_done_message: TextStyle, pub lsp_status: Interactive, - pub sidebar_buttons: StatusBarSidebarButtons, + pub panel_buttons: StatusBarPanelButtons, pub diagnostic_summary: Interactive, pub diagnostic_message: Interactive, } #[derive(Deserialize, Default)] -pub struct StatusBarSidebarButtons { +pub struct StatusBarPanelButtons { pub group_left: ContainerStyle, pub group_right: ContainerStyle, - pub item: Interactive, + pub item: Interactive, pub badge: ContainerStyle, } @@ -375,14 +375,14 @@ pub struct StatusBarLspStatus { } #[derive(Deserialize, Default)] -pub struct Sidebar { +pub struct Dock { pub initial_size: f32, #[serde(flatten)] pub container: ContainerStyle, } #[derive(Clone, Deserialize, Default)] -pub struct SidebarItem { +pub struct DockItem { #[serde(flatten)] pub container: ContainerStyle, pub icon_color: Color, diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index a3d91adc91..6adcf27bd1 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -10,7 +10,7 @@ use gpui::{ use settings::{settings_file::SettingsFile, Settings}; use workspace::{ - item::Item, open_new, sidebar::SidebarSide, AppState, PaneBackdrop, Welcome, Workspace, + item::Item, open_new, dock::DockPosition, AppState, PaneBackdrop, Welcome, Workspace, WorkspaceId, }; @@ -29,7 +29,7 @@ pub fn init(cx: &mut AppContext) { pub fn show_welcome_experience(app_state: &Arc, cx: &mut AppContext) { open_new(&app_state, cx, |workspace, cx| { - workspace.toggle_sidebar(SidebarSide::Left, cx); + workspace.toggle_dock(DockPosition::Left, cx); let welcome_page = cx.add_view(|cx| WelcomePage::new(workspace, cx)); workspace.add_item_to_center(Box::new(welcome_page.clone()), cx); cx.focus(&welcome_page); diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/dock.rs similarity index 81% rename from crates/workspace/src/sidebar.rs rename to crates/workspace/src/dock.rs index 6463ab7d24..88fc9576bd 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/dock.rs @@ -7,7 +7,7 @@ use serde::Deserialize; use settings::Settings; use std::rc::Rc; -pub trait SidebarItem: View { +pub trait DockItem: View { fn should_activate_item_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { false } @@ -19,16 +19,16 @@ pub trait SidebarItem: View { } } -pub trait SidebarItemHandle { +pub trait DockItemHandle { fn id(&self) -> usize; fn should_show_badge(&self, cx: &WindowContext) -> bool; fn is_focused(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; } -impl SidebarItemHandle for ViewHandle +impl DockItemHandle for ViewHandle where - T: SidebarItem, + T: DockItem, { fn id(&self) -> usize { self.id() @@ -47,26 +47,26 @@ where } } -impl From<&dyn SidebarItemHandle> for AnyViewHandle { - fn from(val: &dyn SidebarItemHandle) -> Self { +impl From<&dyn DockItemHandle> for AnyViewHandle { + fn from(val: &dyn DockItemHandle) -> Self { val.as_any().clone() } } -pub struct Sidebar { - sidebar_side: SidebarSide, +pub struct Dock { + position: DockPosition, items: Vec, is_open: bool, active_item_ix: usize, } #[derive(Clone, Copy, Debug, Deserialize, PartialEq)] -pub enum SidebarSide { +pub enum DockPosition { Left, Right, } -impl SidebarSide { +impl DockPosition { fn to_resizable_side(self) -> Side { match self { Self::Left => Side::Right, @@ -78,27 +78,27 @@ impl SidebarSide { struct Item { icon_path: &'static str, tooltip: String, - view: Rc, + view: Rc, _subscriptions: [Subscription; 2], } -pub struct SidebarButtons { - sidebar: ViewHandle, +pub struct PanelButtons { + dock: ViewHandle, workspace: WeakViewHandle, } #[derive(Clone, Debug, Deserialize, PartialEq)] -pub struct ToggleSidebarItem { - pub sidebar_side: SidebarSide, +pub struct ToggleDockItem { + pub dock_position: DockPosition, pub item_index: usize, } -impl_actions!(workspace, [ToggleSidebarItem]); +impl_actions!(workspace, [ToggleDockItem]); -impl Sidebar { - pub fn new(sidebar_side: SidebarSide) -> Self { +impl Dock { + pub fn new(position: DockPosition) -> Self { Self { - sidebar_side, + position, items: Default::default(), active_item_ix: 0, is_open: false, @@ -126,7 +126,7 @@ impl Sidebar { cx.notify(); } - pub fn add_item( + pub fn add_item( &mut self, icon_path: &'static str, tooltip: String, @@ -171,7 +171,7 @@ impl Sidebar { cx.notify(); } - pub fn active_item(&self) -> Option<&Rc> { + pub fn active_item(&self) -> Option<&Rc> { if self.is_open { self.items.get(self.active_item_ix).map(|item| &item.view) } else { @@ -180,25 +180,25 @@ impl Sidebar { } } -impl Entity for Sidebar { +impl Entity for Dock { type Event = (); } -impl View for Sidebar { +impl View for Dock { fn ui_name() -> &'static str { - "Sidebar" + "Dock" } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(active_item) = self.active_item() { enum ResizeHandleTag {} - let style = &cx.global::().theme.workspace.sidebar; + let style = &cx.global::().theme.workspace.dock; ChildView::new(active_item.as_any(), cx) .contained() .with_style(style.container) .with_resize_handle::( - self.sidebar_side as usize, - self.sidebar_side.to_resizable_side(), + self.position as usize, + self.position.to_resizable_side(), 4., style.initial_size, cx, @@ -210,43 +210,43 @@ impl View for Sidebar { } } -impl SidebarButtons { +impl PanelButtons { pub fn new( - sidebar: ViewHandle, + dock: ViewHandle, workspace: WeakViewHandle, cx: &mut ViewContext, ) -> Self { - cx.observe(&sidebar, |_, _, cx| cx.notify()).detach(); - Self { sidebar, workspace } + cx.observe(&dock, |_, _, cx| cx.notify()).detach(); + Self { dock, workspace } } } -impl Entity for SidebarButtons { +impl Entity for PanelButtons { type Event = (); } -impl View for SidebarButtons { +impl View for PanelButtons { fn ui_name() -> &'static str { - "SidebarToggleButton" + "DockToggleButton" } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { let theme = &cx.global::().theme; let tooltip_style = theme.tooltip.clone(); - let theme = &theme.workspace.status_bar.sidebar_buttons; - let sidebar = self.sidebar.read(cx); + let theme = &theme.workspace.status_bar.panel_buttons; + let dock = self.dock.read(cx); let item_style = theme.item.clone(); let badge_style = theme.badge; - let active_ix = sidebar.active_item_ix; - let is_open = sidebar.is_open; - let sidebar_side = sidebar.sidebar_side; - let group_style = match sidebar_side { - SidebarSide::Left => theme.group_left, - SidebarSide::Right => theme.group_right, + let active_ix = dock.active_item_ix; + let is_open = dock.is_open; + let dock_position = dock.position; + let group_style = match dock_position { + DockPosition::Left => theme.group_left, + DockPosition::Right => theme.group_right, }; #[allow(clippy::needless_collect)] - let items = sidebar + let items = dock .items .iter() .map(|item| (item.icon_path, item.tooltip.clone(), item.view.clone())) @@ -255,8 +255,8 @@ impl View for SidebarButtons { Flex::row() .with_children(items.into_iter().enumerate().map( |(ix, (icon_path, tooltip, item_view))| { - let action = ToggleSidebarItem { - sidebar_side, + let action = ToggleDockItem { + dock_position, item_index: ix, }; MouseEventHandler::::new(ix, cx, |state, cx| { @@ -291,7 +291,7 @@ impl View for SidebarButtons { let action = action.clone(); cx.window_context().defer(move |cx| { workspace.update(cx, |workspace, cx| { - workspace.toggle_sidebar_item(&action, cx) + workspace.toggle_panel(&action, cx) }); }); } @@ -312,7 +312,7 @@ impl View for SidebarButtons { } } -impl StatusItemView for SidebarButtons { +impl StatusItemView for PanelButtons { fn set_active_pane_item( &mut self, _: Option<&dyn crate::ItemHandle>, diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 43c4544611..49fd882b65 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -766,7 +766,7 @@ impl FollowableItemHandle for ViewHandle { #[cfg(test)] pub(crate) mod test { use super::{Item, ItemEvent}; - use crate::{sidebar::SidebarItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; + use crate::{dock::DockItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; use gpui::{ elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -1060,5 +1060,5 @@ pub(crate) mod test { } } - impl SidebarItem for TestItem {} + impl DockItem for TestItem {} } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 84d1832df7..4ab9e07526 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -9,7 +9,7 @@ pub mod pane_group; mod persistence; pub mod searchable; pub mod shared_screen; -pub mod sidebar; +pub mod dock; mod status_bar; mod toolbar; @@ -74,7 +74,7 @@ use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use serde::Deserialize; use settings::{Autosave, Settings}; use shared_screen::SharedScreen; -use sidebar::{Sidebar, SidebarButtons, SidebarSide, ToggleSidebarItem}; +use dock::{Dock, PanelButtons, DockPosition, ToggleDockItem}; use status_bar::StatusBar; pub use status_bar::StatusItemView; use theme::{Theme, ThemeRegistry}; @@ -114,7 +114,7 @@ actions!( ActivatePreviousPane, ActivateNextPane, FollowNextCollaborator, - ToggleLeftSidebar, + ToggleLeftDock, NewTerminal, NewSearch, Feedback, @@ -229,7 +229,7 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { workspace.save_active_item(true, cx).detach_and_log_err(cx); }, ); - cx.add_action(Workspace::toggle_sidebar_item); + cx.add_action(Workspace::toggle_panel); cx.add_action(Workspace::focus_center); cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| { workspace.activate_previous_pane(cx) @@ -237,8 +237,8 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| { workspace.activate_next_pane(cx) }); - cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftSidebar, cx| { - workspace.toggle_sidebar(SidebarSide::Left, cx); + cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| { + workspace.toggle_dock(DockPosition::Left, cx); }); cx.add_action(Workspace::activate_pane_at_index); @@ -442,8 +442,8 @@ pub struct Workspace { remote_entity_subscription: Option, modal: Option, center: PaneGroup, - left_sidebar: ViewHandle, - right_sidebar: ViewHandle, + left_dock: ViewHandle, + right_dock: ViewHandle, panes: Vec>, panes_by_item: HashMap>, active_pane: ViewHandle, @@ -562,16 +562,16 @@ impl Workspace { cx.emit_global(WorkspaceCreated(weak_handle.clone())); - let left_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Left)); - let right_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Right)); - let left_sidebar_buttons = - cx.add_view(|cx| SidebarButtons::new(left_sidebar.clone(), weak_handle.clone(), cx)); - let right_sidebar_buttons = - cx.add_view(|cx| SidebarButtons::new(right_sidebar.clone(), weak_handle.clone(), cx)); + let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left)); + let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right)); + let left_dock_buttons = + cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx)); + let right_dock_buttons = + cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); let status_bar = cx.add_view(|cx| { let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); - status_bar.add_left_item(left_sidebar_buttons, cx); - status_bar.add_right_item(right_sidebar_buttons, cx); + status_bar.add_left_item(left_dock_buttons, cx); + status_bar.add_right_item(right_dock_buttons, cx); status_bar }); @@ -621,8 +621,8 @@ impl Workspace { titlebar_item: None, notifications: Default::default(), remote_entity_subscription: None, - left_sidebar, - right_sidebar, + left_dock: left_dock, + right_dock: right_dock, project: project.clone(), leader_state: Default::default(), follower_states_by_leader: Default::default(), @@ -813,12 +813,12 @@ impl Workspace { self.weak_self.clone() } - pub fn left_sidebar(&self) -> &ViewHandle { - &self.left_sidebar + pub fn left_dock(&self) -> &ViewHandle { + &self.left_dock } - pub fn right_sidebar(&self) -> &ViewHandle { - &self.right_sidebar + pub fn right_dock(&self) -> &ViewHandle { + &self.right_dock } pub fn status_bar(&self) -> &ViewHandle { @@ -1306,14 +1306,14 @@ impl Workspace { } } - pub fn toggle_sidebar(&mut self, sidebar_side: SidebarSide, cx: &mut ViewContext) { - let sidebar = match sidebar_side { - SidebarSide::Left => &mut self.left_sidebar, - SidebarSide::Right => &mut self.right_sidebar, + pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { + let dock = match dock_side { + DockPosition::Left => &mut self.left_dock, + DockPosition::Right => &mut self.right_dock, }; - sidebar.update(cx, |sidebar, cx| { - let open = !sidebar.is_open(); - sidebar.set_open(open, cx); + dock.update(cx, |dock, cx| { + let open = !dock.is_open(); + dock.set_open(open, cx); }); self.serialize_workspace(cx); @@ -1322,19 +1322,19 @@ impl Workspace { cx.notify(); } - pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext) { - let sidebar = match action.sidebar_side { - SidebarSide::Left => &mut self.left_sidebar, - SidebarSide::Right => &mut self.right_sidebar, + pub fn toggle_panel(&mut self, action: &ToggleDockItem, cx: &mut ViewContext) { + let dock = match action.dock_position { + DockPosition::Left => &mut self.left_dock, + DockPosition::Right => &mut self.right_dock, }; - let active_item = sidebar.update(cx, move |sidebar, cx| { - if sidebar.is_open() && sidebar.active_item_ix() == action.item_index { - sidebar.set_open(false, cx); + let active_item = dock.update(cx, move |dock, cx| { + if dock.is_open() && dock.active_item_ix() == action.item_index { + dock.set_open(false, cx); None } else { - sidebar.set_open(true, cx); - sidebar.activate_item(action.item_index, cx); - sidebar.active_item().cloned() + dock.set_open(true, cx); + dock.activate_item(action.item_index, cx); + dock.active_item().cloned() } }); @@ -1353,20 +1353,20 @@ impl Workspace { cx.notify(); } - pub fn toggle_sidebar_item_focus( + pub fn toggle_panel_focus( &mut self, - sidebar_side: SidebarSide, - item_index: usize, + dock_position: DockPosition, + panel_index: usize, cx: &mut ViewContext, ) { - let sidebar = match sidebar_side { - SidebarSide::Left => &mut self.left_sidebar, - SidebarSide::Right => &mut self.right_sidebar, + let dock = match dock_position { + DockPosition::Left => &mut self.left_dock, + DockPosition::Right => &mut self.right_dock, }; - let active_item = sidebar.update(cx, |sidebar, cx| { - sidebar.set_open(true, cx); - sidebar.activate_item(item_index, cx); - sidebar.active_item().cloned() + let active_item = dock.update(cx, |dock, cx| { + dock.set_open(true, cx); + dock.activate_item(panel_index, cx); + dock.active_item().cloned() }); if let Some(active_item) = active_item { if active_item.is_focused(cx) { @@ -2452,7 +2452,7 @@ impl Workspace { id: self.database_id, location, center_group, - left_sidebar_open: self.left_sidebar.read(cx).is_open(), + left_sidebar_open: self.left_dock.read(cx).is_open(), bounds: Default::default(), display: Default::default(), }; @@ -2509,10 +2509,10 @@ impl Workspace { } } - if workspace.left_sidebar().read(cx).is_open() + if workspace.left_dock().read(cx).is_open() != serialized_workspace.left_sidebar_open { - workspace.toggle_sidebar(SidebarSide::Left, cx); + workspace.toggle_dock(DockPosition::Left, cx); } cx.notify(); @@ -2596,9 +2596,9 @@ impl View for Workspace { let project = self.project.clone(); Flex::row() .with_children( - if self.left_sidebar.read(cx).active_item().is_some() { + if self.left_dock.read(cx).active_item().is_some() { Some( - ChildView::new(&self.left_sidebar, cx) + ChildView::new(&self.left_dock, cx) .constrained() .dynamically(|constraint, _, cx| { SizeConstraint::new( @@ -2633,9 +2633,9 @@ impl View for Workspace { .flex(1., true), ) .with_children( - if self.right_sidebar.read(cx).active_item().is_some() { + if self.right_dock.read(cx).active_item().is_some() { Some( - ChildView::new(&self.right_sidebar, cx) + ChildView::new(&self.right_dock, cx) .constrained() .dynamically(|constraint, _, cx| { SizeConstraint::new( @@ -2803,7 +2803,7 @@ pub fn open_paths( workspace.update(&mut cx, |workspace, cx| { if contains_directory { - workspace.toggle_sidebar(SidebarSide::Left, cx); + workspace.toggle_dock(DockPosition::Left, cx); } })?; diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 741b61f323..a98147c51b 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -89,7 +89,7 @@ pub fn menus() -> Vec> { MenuItem::action("Zoom Out", super::DecreaseBufferFontSize), MenuItem::action("Reset Zoom", super::ResetBufferFontSize), MenuItem::separator(), - MenuItem::action("Toggle Left Sidebar", workspace::ToggleLeftSidebar), + MenuItem::action("Toggle Left Dock", workspace::ToggleLeftDock), MenuItem::submenu(Menu { name: "Editor Layout", items: vec![ diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 46443eb77d..b3968e45fc 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -36,7 +36,7 @@ use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; pub use workspace; use workspace::{ - create_and_open_local_file, open_new, sidebar::SidebarSide, AppState, NewFile, NewWindow, + create_and_open_local_file, open_new, dock::DockPosition, AppState, NewFile, NewWindow, Workspace, }; @@ -242,7 +242,7 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { |workspace: &mut Workspace, _: &project_panel::ToggleFocus, cx: &mut ViewContext| { - workspace.toggle_sidebar_item_focus(SidebarSide::Left, 0, cx); + workspace.toggle_panel_focus(DockPosition::Left, 0, cx); }, ); cx.add_global_action({ @@ -312,8 +312,8 @@ pub fn initialize_workspace( workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); let project_panel = ProjectPanel::new(workspace, cx); - workspace.left_sidebar().update(cx, |sidebar, cx| { - sidebar.add_item( + workspace.left_dock().update(cx, |dock, cx| { + dock.add_item( "icons/folder_tree_16.svg", "Project Panel".to_string(), project_panel, @@ -658,7 +658,7 @@ mod tests { .unwrap(); workspace_1.update(cx, |workspace, cx| { assert_eq!(workspace.worktrees(cx).count(), 2); - assert!(workspace.left_sidebar().read(cx).is_open()); + assert!(workspace.left_dock().read(cx).is_open()); assert!(workspace.active_pane().is_focused(cx)); }); @@ -701,7 +701,7 @@ mod tests { .collect::>(), &[Path::new("/root/c").into(), Path::new("/root/d").into()] ); - assert!(workspace.left_sidebar().read(cx).is_open()); + assert!(workspace.left_dock().read(cx).is_open()); assert!(workspace.active_pane().is_focused(cx)); }); } diff --git a/styles/src/styleTree/statusBar.ts b/styles/src/styleTree/statusBar.ts index c55160c336..8a711992ec 100644 --- a/styles/src/styleTree/statusBar.ts +++ b/styles/src/styleTree/statusBar.ts @@ -93,7 +93,7 @@ export default function statusBar(colorScheme: ColorScheme) { }, }, }, - sidebarButtons: { + panelButtons: { groupLeft: {}, groupRight: {}, item: { diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 9d0c4de9f7..fe359b0501 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -118,7 +118,7 @@ export default function workspace(colorScheme: ColorScheme) { }, cursor: "Arrow", }, - sidebar: { + dock: { initialSize: 240, border: border(layer, { left: true, right: true }), }, @@ -310,19 +310,6 @@ export default function workspace(colorScheme: ColorScheme) { width: 400, margin: { right: 10, bottom: 10 }, }, - dock: { - initialSizeRight: 640, - initialSizeBottom: 304, - wash_color: withOpacity(background(colorScheme.highest), 0.5), - panel: { - border: border(colorScheme.middle), - }, - maximized: { - margin: 32, - border: border(colorScheme.highest, { overlay: true }), - shadow: colorScheme.modalShadow, - }, - }, dropTargetOverlayColor: withOpacity(foreground(layer, "variant"), 0.5), } } From 1ddbda509535fc9e17bdabcbfe204537de472a2c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 May 2023 16:13:36 -0600 Subject: [PATCH 003/131] Rename DockItem to Panel --- crates/copilot_button/src/copilot_button.rs | 2 +- crates/feedback/src/deploy_feedback_button.rs | 2 +- crates/project_panel/src/project_panel.rs | 2 +- crates/terminal_view/src/terminal_button.rs | 2 +- crates/theme/src/theme.rs | 4 +-- crates/workspace/src/dock.rs | 27 ++++++++++--------- crates/workspace/src/item.rs | 4 +-- crates/workspace/src/workspace.rs | 4 +-- styles/src/styleTree/statusBar.ts | 2 +- 9 files changed, 25 insertions(+), 24 deletions(-) diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 0d6bcc115c..884d1358f7 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -65,7 +65,7 @@ impl View for CopilotButton { .workspace .status_bar .panel_buttons - .item + .button .style_for(state, active); Flex::row() diff --git a/crates/feedback/src/deploy_feedback_button.rs b/crates/feedback/src/deploy_feedback_button.rs index 5fdb378eb6..fc90b38750 100644 --- a/crates/feedback/src/deploy_feedback_button.rs +++ b/crates/feedback/src/deploy_feedback_button.rs @@ -41,7 +41,7 @@ impl View for DeployFeedbackButton { .workspace .status_bar .panel_buttons - .item + .button .style_for(state, active); Svg::new("icons/feedback_16.svg") diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 4a1d488eaf..35ab314f3a 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1327,7 +1327,7 @@ impl Entity for ProjectPanel { type Event = Event; } -impl workspace::dock::DockItem for ProjectPanel { +impl workspace::dock::Panel for ProjectPanel { fn should_show_badge(&self, _: &AppContext) -> bool { false } diff --git a/crates/terminal_view/src/terminal_button.rs b/crates/terminal_view/src/terminal_button.rs index cacd21c733..0747f86d2d 100644 --- a/crates/terminal_view/src/terminal_button.rs +++ b/crates/terminal_view/src/terminal_button.rs @@ -53,7 +53,7 @@ impl View for TerminalButton { .workspace .status_bar .panel_buttons - .item + .button .style_for(state, active); Flex::row() diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index c239e0ef40..bc6d639447 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -344,7 +344,7 @@ pub struct StatusBar { pub struct StatusBarPanelButtons { pub group_left: ContainerStyle, pub group_right: ContainerStyle, - pub item: Interactive, + pub button: Interactive, pub badge: ContainerStyle, } @@ -382,7 +382,7 @@ pub struct Dock { } #[derive(Clone, Deserialize, Default)] -pub struct DockItem { +pub struct PanelButton { #[serde(flatten)] pub container: ContainerStyle, pub icon_color: Color, diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 88fc9576bd..432ca646c8 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -7,7 +7,7 @@ use serde::Deserialize; use settings::Settings; use std::rc::Rc; -pub trait DockItem: View { +pub trait Panel: View { fn should_activate_item_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { false } @@ -19,16 +19,17 @@ pub trait DockItem: View { } } -pub trait DockItemHandle { +pub trait PanelHandle { + fn id(&self) -> usize; fn should_show_badge(&self, cx: &WindowContext) -> bool; fn is_focused(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; } -impl DockItemHandle for ViewHandle +impl PanelHandle for ViewHandle where - T: DockItem, + T: Panel, { fn id(&self) -> usize { self.id() @@ -47,8 +48,8 @@ where } } -impl From<&dyn DockItemHandle> for AnyViewHandle { - fn from(val: &dyn DockItemHandle) -> Self { +impl From<&dyn PanelHandle> for AnyViewHandle { + fn from(val: &dyn PanelHandle) -> Self { val.as_any().clone() } } @@ -78,7 +79,7 @@ impl DockPosition { struct Item { icon_path: &'static str, tooltip: String, - view: Rc, + view: Rc, _subscriptions: [Subscription; 2], } @@ -88,12 +89,12 @@ pub struct PanelButtons { } #[derive(Clone, Debug, Deserialize, PartialEq)] -pub struct ToggleDockItem { +pub struct TogglePanel { pub dock_position: DockPosition, pub item_index: usize, } -impl_actions!(workspace, [ToggleDockItem]); +impl_actions!(workspace, [TogglePanel]); impl Dock { pub fn new(position: DockPosition) -> Self { @@ -126,7 +127,7 @@ impl Dock { cx.notify(); } - pub fn add_item( + pub fn add_item( &mut self, icon_path: &'static str, tooltip: String, @@ -171,7 +172,7 @@ impl Dock { cx.notify(); } - pub fn active_item(&self) -> Option<&Rc> { + pub fn active_item(&self) -> Option<&Rc> { if self.is_open { self.items.get(self.active_item_ix).map(|item| &item.view) } else { @@ -235,7 +236,7 @@ impl View for PanelButtons { let tooltip_style = theme.tooltip.clone(); let theme = &theme.workspace.status_bar.panel_buttons; let dock = self.dock.read(cx); - let item_style = theme.item.clone(); + let item_style = theme.button.clone(); let badge_style = theme.badge; let active_ix = dock.active_item_ix; let is_open = dock.is_open; @@ -255,7 +256,7 @@ impl View for PanelButtons { Flex::row() .with_children(items.into_iter().enumerate().map( |(ix, (icon_path, tooltip, item_view))| { - let action = ToggleDockItem { + let action = TogglePanel { dock_position, item_index: ix, }; diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 49fd882b65..e7571c1bf6 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -766,7 +766,7 @@ impl FollowableItemHandle for ViewHandle { #[cfg(test)] pub(crate) mod test { use super::{Item, ItemEvent}; - use crate::{dock::DockItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; + use crate::{dock::Panel, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; use gpui::{ elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -1060,5 +1060,5 @@ pub(crate) mod test { } } - impl DockItem for TestItem {} + impl Panel for TestItem {} } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 4ab9e07526..f1bcb3928f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -74,7 +74,7 @@ use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use serde::Deserialize; use settings::{Autosave, Settings}; use shared_screen::SharedScreen; -use dock::{Dock, PanelButtons, DockPosition, ToggleDockItem}; +use dock::{Dock, PanelButtons, DockPosition, TogglePanel}; use status_bar::StatusBar; pub use status_bar::StatusItemView; use theme::{Theme, ThemeRegistry}; @@ -1322,7 +1322,7 @@ impl Workspace { cx.notify(); } - pub fn toggle_panel(&mut self, action: &ToggleDockItem, cx: &mut ViewContext) { + pub fn toggle_panel(&mut self, action: &TogglePanel, cx: &mut ViewContext) { let dock = match action.dock_position { DockPosition::Left => &mut self.left_dock, DockPosition::Right => &mut self.right_dock, diff --git a/styles/src/styleTree/statusBar.ts b/styles/src/styleTree/statusBar.ts index 8a711992ec..614b6ab34d 100644 --- a/styles/src/styleTree/statusBar.ts +++ b/styles/src/styleTree/statusBar.ts @@ -96,7 +96,7 @@ export default function statusBar(colorScheme: ColorScheme) { panelButtons: { groupLeft: {}, groupRight: {}, - item: { + button: { ...statusContainer, iconSize: 16, iconColor: foreground(layer, "variant"), From 6ddc610fa21905f366d5032a381e2f857a400754 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 8 May 2023 18:31:08 +0200 Subject: [PATCH 004/131] WIP: Start on `TerminalPanel` --- crates/terminal_view/src/terminal_panel.rs | 76 +++++++++++++++++++ crates/terminal_view/src/terminal_view.rs | 1 + crates/theme/src/theme.rs | 1 + crates/workspace/src/dock.rs | 4 +- crates/workspace/src/pane.rs | 9 +-- crates/workspace/src/workspace.rs | 87 +++++++++++++--------- crates/zed/src/zed.rs | 18 +++-- styles/src/styleTree/statusBar.ts | 1 + 8 files changed, 149 insertions(+), 48 deletions(-) create mode 100644 crates/terminal_view/src/terminal_panel.rs diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs new file mode 100644 index 0000000000..19b34209a3 --- /dev/null +++ b/crates/terminal_view/src/terminal_panel.rs @@ -0,0 +1,76 @@ +use gpui::{elements::*, Entity, ModelHandle, View, ViewContext, ViewHandle, WeakViewHandle}; +use project::Project; +use settings::{Settings, WorkingDirectory}; +use util::ResultExt; +use workspace::{dock::Panel, Pane, Workspace}; + +use crate::TerminalView; + +pub struct TerminalPanel { + project: ModelHandle, + pane: ViewHandle, + workspace: WeakViewHandle, +} + +impl TerminalPanel { + pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + Self { + project: workspace.project().clone(), + pane: cx.add_view(|cx| { + Pane::new( + workspace.weak_handle(), + workspace.app_state().background_actions, + cx, + ) + }), + workspace: workspace.weak_handle(), + } + } +} + +impl Entity for TerminalPanel { + type Event = (); +} + +impl View for TerminalPanel { + fn ui_name() -> &'static str { + "TerminalPanel" + } + + fn render(&mut self, cx: &mut ViewContext) -> gpui::AnyElement { + ChildView::new(&self.pane, cx).into_any() + } + + fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { + if self.pane.read(cx).items_len() == 0 { + if let Some(workspace) = self.workspace.upgrade(cx) { + let working_directory_strategy = cx + .global::() + .terminal_overrides + .working_directory + .clone() + .unwrap_or(WorkingDirectory::CurrentProjectDirectory); + let working_directory = crate::get_working_directory( + workspace.read(cx), + cx, + working_directory_strategy, + ); + let window_id = cx.window_id(); + if let Some(terminal) = self.project.update(cx, |project, cx| { + project + .create_terminal(working_directory, window_id, cx) + .log_err() + }) { + workspace.update(cx, |workspace, cx| { + let terminal = Box::new(cx.add_view(|cx| { + TerminalView::new(terminal, workspace.database_id(), cx) + })); + Pane::add_item(workspace, &self.pane, terminal, true, true, None, cx); + }); + } + } + } + } +} + +impl Panel for TerminalPanel {} diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index dfb2334dc5..e3a5153ef3 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1,6 +1,7 @@ mod persistence; pub mod terminal_button; pub mod terminal_element; +pub mod terminal_panel; use context_menu::{ContextMenu, ContextMenuItem}; use dirs::home_dir; diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index bc6d639447..67d07a3998 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -343,6 +343,7 @@ pub struct StatusBar { #[derive(Deserialize, Default)] pub struct StatusBarPanelButtons { pub group_left: ContainerStyle, + pub group_bottom: ContainerStyle, pub group_right: ContainerStyle, pub button: Interactive, pub badge: ContainerStyle, diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 432ca646c8..70708837e6 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -20,7 +20,6 @@ pub trait Panel: View { } pub trait PanelHandle { - fn id(&self) -> usize; fn should_show_badge(&self, cx: &WindowContext) -> bool; fn is_focused(&self, cx: &WindowContext) -> bool; @@ -64,6 +63,7 @@ pub struct Dock { #[derive(Clone, Copy, Debug, Deserialize, PartialEq)] pub enum DockPosition { Left, + Bottom, Right, } @@ -71,6 +71,7 @@ impl DockPosition { fn to_resizable_side(self) -> Side { match self { Self::Left => Side::Right, + Self::Bottom => Side::Bottom, Self::Right => Side::Left, } } @@ -243,6 +244,7 @@ impl View for PanelButtons { let dock_position = dock.position; let group_style = match dock_position { DockPosition::Left => theme.group_left, + DockPosition::Bottom => theme.group_bottom, DockPosition::Right => theme.group_right, }; diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 1aed1c011e..2597b4af81 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2,9 +2,7 @@ mod dragged_item_receiver; use super::{ItemHandle, SplitDirection}; use crate::{ - item::WeakItemHandle, - toolbar::Toolbar, - Item, NewFile, NewSearch, NewTerminal, Workspace, + item::WeakItemHandle, toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, Workspace, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -485,7 +483,7 @@ impl Pane { } } - pub(crate) fn add_item( + pub fn add_item( workspace: &mut Workspace, pane: &ViewHandle, item: Box, @@ -1594,7 +1592,8 @@ impl Pane { "icons/split_12.svg", cx, |pane, cx| pane.deploy_split_menu(cx), - self.tab_bar_context_menu.handle_if_kind(TabBarContextMenuKind::Split), + self.tab_bar_context_menu + .handle_if_kind(TabBarContextMenuKind::Split), )) .contained() .with_style(theme.workspace.tab_bar.pane_button_container) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f1bcb3928f..ee02a97439 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1,3 +1,4 @@ +pub mod dock; /// NOTE: Focus only 'takes' after an update has flushed_effects. /// /// This may cause issues when you're trying to write tests that use workspace focus to add items at @@ -9,7 +10,6 @@ pub mod pane_group; mod persistence; pub mod searchable; pub mod shared_screen; -pub mod dock; mod status_bar; mod toolbar; @@ -60,6 +60,7 @@ use crate::{ notifications::simple_message_notification::MessageNotification, persistence::model::{SerializedPane, SerializedPaneGroup, SerializedWorkspace}, }; +use dock::{Dock, DockPosition, PanelButtons, TogglePanel}; use lazy_static::lazy_static; use notifications::{NotificationHandle, NotifyResultExt}; pub use pane::*; @@ -74,7 +75,6 @@ use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use serde::Deserialize; use settings::{Autosave, Settings}; use shared_screen::SharedScreen; -use dock::{Dock, PanelButtons, DockPosition, TogglePanel}; use status_bar::StatusBar; pub use status_bar::StatusItemView; use theme::{Theme, ThemeRegistry}; @@ -443,6 +443,7 @@ pub struct Workspace { modal: Option, center: PaneGroup, left_dock: ViewHandle, + bottom_dock: ViewHandle, right_dock: ViewHandle, panes: Vec>, panes_by_item: HashMap>, @@ -526,8 +527,8 @@ impl Workspace { let weak_handle = cx.weak_handle(); - let center_pane = cx - .add_view(|cx| Pane::new(weak_handle.clone(), app_state.background_actions, cx)); + let center_pane = + cx.add_view(|cx| Pane::new(weak_handle.clone(), app_state.background_actions, cx)); cx.subscribe(¢er_pane, Self::handle_pane_event).detach(); cx.focus(¢er_pane); cx.emit(Event::PaneAdded(center_pane.clone())); @@ -563,14 +564,18 @@ impl Workspace { cx.emit_global(WorkspaceCreated(weak_handle.clone())); let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left)); + let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom)); let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right)); let left_dock_buttons = cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx)); + let bottom_dock_buttons = + cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx)); let right_dock_buttons = cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); let status_bar = cx.add_view(|cx| { let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); status_bar.add_left_item(left_dock_buttons, cx); + status_bar.add_right_item(bottom_dock_buttons, cx); status_bar.add_right_item(right_dock_buttons, cx); status_bar }); @@ -621,8 +626,9 @@ impl Workspace { titlebar_item: None, notifications: Default::default(), remote_entity_subscription: None, - left_dock: left_dock, - right_dock: right_dock, + left_dock, + bottom_dock, + right_dock, project: project.clone(), leader_state: Default::default(), follower_states_by_leader: Default::default(), @@ -817,6 +823,10 @@ impl Workspace { &self.left_dock } + pub fn bottom_dock(&self) -> &ViewHandle { + &self.bottom_dock + } + pub fn right_dock(&self) -> &ViewHandle { &self.right_dock } @@ -1309,6 +1319,7 @@ impl Workspace { pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { let dock = match dock_side { DockPosition::Left => &mut self.left_dock, + DockPosition::Bottom => &mut self.bottom_dock, DockPosition::Right => &mut self.right_dock, }; dock.update(cx, |dock, cx| { @@ -1325,6 +1336,7 @@ impl Workspace { pub fn toggle_panel(&mut self, action: &TogglePanel, cx: &mut ViewContext) { let dock = match action.dock_position { DockPosition::Left => &mut self.left_dock, + DockPosition::Bottom => &mut self.bottom_dock, DockPosition::Right => &mut self.right_dock, }; let active_item = dock.update(cx, move |dock, cx| { @@ -1361,6 +1373,7 @@ impl Workspace { ) { let dock = match dock_position { DockPosition::Left => &mut self.left_dock, + DockPosition::Bottom => &mut self.bottom_dock, DockPosition::Right => &mut self.right_dock, }; let active_item = dock.update(cx, |dock, cx| { @@ -1387,13 +1400,8 @@ impl Workspace { } fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { - let pane = cx.add_view(|cx| { - Pane::new( - self.weak_handle(), - self.app_state.background_actions, - cx, - ) - }); + let pane = + cx.add_view(|cx| Pane::new(self.weak_handle(), self.app_state.background_actions, cx)); cx.subscribe(&pane, Self::handle_pane_event).detach(); self.panes.push(pane.clone()); cx.focus(&pane); @@ -1560,7 +1568,7 @@ impl Workspace { status_bar.set_active_pane(&self.active_pane, cx); }); self.active_item_path_changed(cx); - self.last_active_center_pane = Some(pane.downgrade()); + self.last_active_center_pane = Some(pane.downgrade()); cx.notify(); } @@ -2470,13 +2478,12 @@ impl Workspace { cx: &mut AppContext, ) { cx.spawn(|mut cx| async move { - let (project, old_center_pane) = - workspace.read_with(&cx, |workspace, _| { - ( - workspace.project().clone(), - workspace.last_active_center_pane.clone(), - ) - })?; + let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| { + ( + workspace.project().clone(), + workspace.last_active_center_pane.clone(), + ) + })?; // Traverse the splits tree and add to things let center_group = serialized_workspace @@ -2615,22 +2622,28 @@ impl View for Workspace { }, ) .with_child( - FlexItem::new( - Flex::column() - .with_child( - FlexItem::new(self.center.render( - &project, - &theme, - &self.follower_states_by_leader, - self.active_call(), - self.active_pane(), - &self.app_state, - cx, - )) - .flex(1., true), - ) - ) - .flex(1., true), + Flex::column() + .with_child( + FlexItem::new(self.center.render( + &project, + &theme, + &self.follower_states_by_leader, + self.active_call(), + self.active_pane(), + &self.app_state, + cx, + )) + .flex(1., true), + ) + .with_children( + if self.bottom_dock.read(cx).active_item().is_some() + { + Some(ChildView::new(&self.bottom_dock, cx)) + } else { + None + }, + ) + .flex(1., true), ) .with_children( if self.right_dock.read(cx).active_item().is_some() { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index b3968e45fc..52b0ad0898 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -31,12 +31,12 @@ use serde::Deserialize; use serde_json::to_string_pretty; use settings::{Settings, DEFAULT_SETTINGS_ASSET_PATH}; use std::{borrow::Cow, str, sync::Arc}; -use terminal_view::terminal_button::TerminalButton; +use terminal_view::terminal_panel::TerminalPanel; use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; pub use workspace; use workspace::{ - create_and_open_local_file, open_new, dock::DockPosition, AppState, NewFile, NewWindow, + create_and_open_local_file, dock::DockPosition, open_new, AppState, NewFile, NewWindow, Workspace, }; @@ -318,10 +318,19 @@ pub fn initialize_workspace( "Project Panel".to_string(), project_panel, cx, - ) + ); + }); + + let terminal_panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); + workspace.bottom_dock().update(cx, |dock, cx| { + dock.add_item( + "icons/terminal_12.svg", + "Terminals".to_string(), + terminal_panel, + cx, + ); }); - let toggle_terminal = cx.add_view(|cx| TerminalButton::new(workspace_handle.clone(), cx)); let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx)); let diagnostic_summary = cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); @@ -335,7 +344,6 @@ pub fn initialize_workspace( workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(activity_indicator, cx); - status_bar.add_right_item(toggle_terminal, cx); status_bar.add_right_item(feedback_button, cx); status_bar.add_right_item(copilot, cx); status_bar.add_right_item(active_buffer_language, cx); diff --git a/styles/src/styleTree/statusBar.ts b/styles/src/styleTree/statusBar.ts index 614b6ab34d..eca537c150 100644 --- a/styles/src/styleTree/statusBar.ts +++ b/styles/src/styleTree/statusBar.ts @@ -95,6 +95,7 @@ export default function statusBar(colorScheme: ColorScheme) { }, panelButtons: { groupLeft: {}, + groupBottom: {}, groupRight: {}, button: { ...statusContainer, From 916612caf1fd12aaa9e782b61f36bb3f2579cc73 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 09:56:22 +0200 Subject: [PATCH 005/131] Prevent dragging items that aren't terminals to the terminal panel --- crates/terminal_view/src/terminal_panel.rs | 15 ++- crates/workspace/src/pane.rs | 36 ++++--- .../src/pane/dragged_item_receiver.rs | 101 ++++++++++-------- 3 files changed, 88 insertions(+), 64 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 19b34209a3..ddc53537b7 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -2,7 +2,7 @@ use gpui::{elements::*, Entity, ModelHandle, View, ViewContext, ViewHandle, Weak use project::Project; use settings::{Settings, WorkingDirectory}; use util::ResultExt; -use workspace::{dock::Panel, Pane, Workspace}; +use workspace::{dock::Panel, DraggedItem, Pane, Workspace}; use crate::TerminalView; @@ -17,11 +17,20 @@ impl TerminalPanel { Self { project: workspace.project().clone(), pane: cx.add_view(|cx| { - Pane::new( + let window_id = cx.window_id(); + let mut pane = Pane::new( workspace.weak_handle(), workspace.app_state().background_actions, cx, - ) + ); + pane.on_can_drop(move |drag_and_drop, cx| { + drag_and_drop + .currently_dragged::(window_id) + .map_or(false, |(_, item)| { + item.handle.act_as::(cx).is_some() + }) + }); + pane }), workspace: workspace.weak_handle(), } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 2597b4af81..fc3fda8e6a 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -7,8 +7,8 @@ use crate::{ use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; use context_menu::{ContextMenu, ContextMenuItem}; -use drag_and_drop::Draggable; -pub use dragged_item_receiver::{dragged_item_receiver, handle_dropped_item}; +use drag_and_drop::{DragAndDrop, Draggable}; +use dragged_item_receiver::dragged_item_receiver; use futures::StreamExt; use gpui::{ actions, @@ -148,6 +148,7 @@ pub struct Pane { _background_actions: BackgroundActions, workspace: WeakViewHandle, has_focus: bool, + can_drop: Rc, &WindowContext) -> bool>, } pub struct ItemNavHistory { @@ -185,9 +186,9 @@ pub struct NavigationEntry { pub data: Option>, } -struct DraggedItem { - item: Box, - pane: WeakViewHandle, +pub struct DraggedItem { + pub handle: Box, + pub pane: WeakViewHandle, } pub enum ReorderBehavior { @@ -253,6 +254,7 @@ impl Pane { _background_actions: background_actions, workspace, has_focus: false, + can_drop: Rc::new(|_, _| true), } } @@ -273,6 +275,13 @@ impl Pane { self.has_focus } + pub fn on_can_drop(&mut self, can_drop: F) + where + F: 'static + Fn(&DragAndDrop, &WindowContext) -> bool, + { + self.can_drop = Rc::new(can_drop); + } + pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { ItemNavHistory { history: self.nav_history.clone(), @@ -1293,7 +1302,7 @@ impl Pane { row.add_child({ enum TabDragReceiver {} let mut receiver = - dragged_item_receiver::(ix, ix, true, None, cx, { + dragged_item_receiver::(self, ix, ix, true, None, cx, { let item = item.clone(); let pane = pane.clone(); let detail = detail.clone(); @@ -1372,7 +1381,7 @@ impl Pane { receiver.as_draggable( DraggedItem { - item, + handle: item, pane: pane.clone(), }, { @@ -1382,7 +1391,7 @@ impl Pane { move |dragged_item: &DraggedItem, cx: &mut ViewContext| { let tab_style = &theme.workspace.tab_bar.dragged_tab; Self::render_dragged_tab( - &dragged_item.item, + &dragged_item.handle, dragged_item.pane.clone(), false, detail, @@ -1402,7 +1411,7 @@ impl Pane { let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false); enum Filler {} row.add_child( - dragged_item_receiver::(0, filler_index, true, None, cx, |_, _| { + dragged_item_receiver::(self, 0, filler_index, true, None, cx, |_, _| { Empty::new() .contained() .with_style(filler_style.container) @@ -1601,11 +1610,7 @@ impl Pane { .into_any() } - fn render_blank_pane( - &mut self, - theme: &Theme, - _cx: &mut ViewContext, - ) -> AnyElement { + fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext) -> AnyElement { let background = theme.workspace.background; Empty::new() .contained() @@ -1668,6 +1673,7 @@ impl View for Pane { .with_child({ enum PaneContentTabDropTarget {} dragged_item_receiver::( + self, 0, self.active_item_index + 1, false, @@ -1696,7 +1702,7 @@ impl View for Pane { enum EmptyPane {} let theme = cx.global::().theme.clone(); - dragged_item_receiver::(0, 0, false, None, cx, |_, cx| { + dragged_item_receiver::(self, 0, 0, false, None, cx, |_, cx| { self.render_blank_pane(&theme, cx) }) .on_down(MouseButton::Left, |_, _, cx| { diff --git a/crates/workspace/src/pane/dragged_item_receiver.rs b/crates/workspace/src/pane/dragged_item_receiver.rs index 961205b9ee..003cf1e4ef 100644 --- a/crates/workspace/src/pane/dragged_item_receiver.rs +++ b/crates/workspace/src/pane/dragged_item_receiver.rs @@ -15,6 +15,7 @@ use crate::{Pane, SplitDirection, Workspace}; use super::DraggedItem; pub fn dragged_item_receiver( + pane: &Pane, region_id: usize, drop_index: usize, allow_same_pane: bool, @@ -27,22 +28,24 @@ where D: Element, F: FnOnce(&mut MouseState, &mut ViewContext) -> D, { - MouseEventHandler::::above(region_id, cx, |state, cx| { + let drag_and_drop = cx.global::>(); + let drag_position = if (pane.can_drop)(drag_and_drop, cx) { + drag_and_drop + .currently_dragged::(cx.window_id()) + .map(|(drag_position, _)| drag_position) + .or_else(|| { + drag_and_drop + .currently_dragged::(cx.window_id()) + .map(|(drag_position, _)| drag_position) + }) + } else { + None + }; + + let mut handler = MouseEventHandler::::above(region_id, cx, |state, cx| { // Observing hovered will cause a render when the mouse enters regardless // of if mouse position was accessed before - let drag_position = if state.hovered() { - cx.global::>() - .currently_dragged::(cx.window_id()) - .map(|(drag_position, _)| drag_position) - .or_else(|| { - cx.global::>() - .currently_dragged::(cx.window_id()) - .map(|(drag_position, _)| drag_position) - }) - } else { - None - }; - + let drag_position = if state.hovered() { drag_position } else { None }; Stack::new() .with_child(render_child(state, cx)) .with_children(drag_position.map(|drag_position| { @@ -67,38 +70,44 @@ where } }) })) - }) - .on_up(MouseButton::Left, { - move |event, pane, cx| { - let workspace = pane.workspace.clone(); - let pane = cx.weak_handle(); - handle_dropped_item( - event, - workspace, - &pane, - drop_index, - allow_same_pane, - split_margin, - cx, - ); - cx.notify(); - } - }) - .on_move(|_, _, cx| { - let drag_and_drop = cx.global::>(); + }); - if drag_and_drop - .currently_dragged::(cx.window_id()) - .is_some() - || drag_and_drop - .currently_dragged::(cx.window_id()) - .is_some() - { - cx.notify(); - } else { - cx.propagate_event(); - } - }) + if drag_position.is_some() { + handler = handler + .on_up(MouseButton::Left, { + move |event, pane, cx| { + let workspace = pane.workspace.clone(); + let pane = cx.weak_handle(); + handle_dropped_item( + event, + workspace, + &pane, + drop_index, + allow_same_pane, + split_margin, + cx, + ); + cx.notify(); + } + }) + .on_move(|_, _, cx| { + let drag_and_drop = cx.global::>(); + + if drag_and_drop + .currently_dragged::(cx.window_id()) + .is_some() + || drag_and_drop + .currently_dragged::(cx.window_id()) + .is_some() + { + cx.notify(); + } else { + cx.propagate_event(); + } + }) + } + + handler } pub fn handle_dropped_item( @@ -118,7 +127,7 @@ pub fn handle_dropped_item( let action = if let Some((_, dragged_item)) = drag_and_drop.currently_dragged::(cx.window_id()) { - Action::Move(dragged_item.pane.clone(), dragged_item.item.id()) + Action::Move(dragged_item.pane.clone(), dragged_item.handle.id()) } else if let Some((_, project_entry)) = drag_and_drop.currently_dragged::(cx.window_id()) { From 506f978c41b100c5355730a0c40b07210f25782a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 10:17:35 +0200 Subject: [PATCH 006/131] Automatically close terminal dock when the last terminal was closed --- crates/terminal_view/src/terminal_panel.rs | 68 +++++++++++++++------- crates/workspace/src/dock.rs | 15 ++++- crates/workspace/src/workspace.rs | 29 ++++++++- 3 files changed, 85 insertions(+), 27 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index ddc53537b7..7d715019c3 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1,44 +1,66 @@ -use gpui::{elements::*, Entity, ModelHandle, View, ViewContext, ViewHandle, WeakViewHandle}; +use crate::TerminalView; +use gpui::{ + elements::*, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle, + WeakViewHandle, +}; use project::Project; use settings::{Settings, WorkingDirectory}; use util::ResultExt; -use workspace::{dock::Panel, DraggedItem, Pane, Workspace}; +use workspace::{dock::Panel, pane, DraggedItem, Pane, Workspace}; -use crate::TerminalView; +pub enum Event { + Close, +} pub struct TerminalPanel { project: ModelHandle, pane: ViewHandle, workspace: WeakViewHandle, + _subscription: Subscription, } impl TerminalPanel { pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + let pane = cx.add_view(|cx| { + let window_id = cx.window_id(); + let mut pane = Pane::new( + workspace.weak_handle(), + workspace.app_state().background_actions, + cx, + ); + pane.on_can_drop(move |drag_and_drop, cx| { + drag_and_drop + .currently_dragged::(window_id) + .map_or(false, |(_, item)| { + item.handle.act_as::(cx).is_some() + }) + }); + pane + }); + let subscription = cx.subscribe(&pane, Self::handle_pane_event); Self { project: workspace.project().clone(), - pane: cx.add_view(|cx| { - let window_id = cx.window_id(); - let mut pane = Pane::new( - workspace.weak_handle(), - workspace.app_state().background_actions, - cx, - ); - pane.on_can_drop(move |drag_and_drop, cx| { - drag_and_drop - .currently_dragged::(window_id) - .map_or(false, |(_, item)| { - item.handle.act_as::(cx).is_some() - }) - }); - pane - }), + pane, workspace: workspace.weak_handle(), + _subscription: subscription, + } + } + + fn handle_pane_event( + &mut self, + _pane: ViewHandle, + event: &pane::Event, + cx: &mut ViewContext, + ) { + match event { + pane::Event::Remove => cx.emit(Event::Close), + _ => {} } } } impl Entity for TerminalPanel { - type Event = (); + type Event = Event; } impl View for TerminalPanel { @@ -82,4 +104,8 @@ impl View for TerminalPanel { } } -impl Panel for TerminalPanel {} +impl Panel for TerminalPanel { + fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { + matches!(event, Event::Close) + } +} diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 70708837e6..3acda44297 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -8,7 +8,10 @@ use settings::Settings; use std::rc::Rc; pub trait Panel: View { - fn should_activate_item_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + false + } + fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { false } fn should_show_badge(&self, _: &AppContext) -> bool { @@ -53,6 +56,10 @@ impl From<&dyn PanelHandle> for AnyViewHandle { } } +pub enum Event { + Close, +} + pub struct Dock { position: DockPosition, items: Vec, @@ -138,7 +145,7 @@ impl Dock { let subscriptions = [ cx.observe(&view, |_, _, cx| cx.notify()), cx.subscribe(&view, |this, view, event, cx| { - if view.read(cx).should_activate_item_on_event(event, cx) { + if view.read(cx).should_activate_on_event(event, cx) { if let Some(ix) = this .items .iter() @@ -146,6 +153,8 @@ impl Dock { { this.activate_item(ix, cx); } + } else if view.read(cx).should_close_on_event(event, cx) { + cx.emit(Event::Close); } }), ]; @@ -183,7 +192,7 @@ impl Dock { } impl Entity for Dock { - type Event = (); + type Event = Event; } impl View for Dock { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ee02a97439..e79f071267 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -461,7 +461,7 @@ pub struct Workspace { leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: WorkspaceId, app_state: Arc, - _window_subscriptions: [Subscription; 3], + _subscriptions: Vec, _apply_leader_updates: Task>, _observe_current_user: Task>, } @@ -592,7 +592,7 @@ impl Workspace { active_call = Some((call, subscriptions)); } - let subscriptions = [ + let subscriptions = vec![ cx.observe_fullscreen(|_, _, cx| cx.notify()), cx.observe_window_activation(Self::on_window_activation_changed), cx.observe_window_bounds(move |_, mut bounds, display, cx| { @@ -612,6 +612,9 @@ impl Workspace { .spawn(DB.set_window_bounds(workspace_id, bounds, display)) .detach_and_log_err(cx); }), + Self::register_dock(&left_dock, cx), + Self::register_dock(&bottom_dock, cx), + Self::register_dock(&right_dock, cx), ]; let mut this = Workspace { @@ -640,7 +643,7 @@ impl Workspace { _observe_current_user, _apply_leader_updates, leader_updates_tx, - _window_subscriptions: subscriptions, + _subscriptions: subscriptions, }; this.project_remote_id_changed(project.read(cx).remote_id(), cx); cx.defer(|this, cx| this.update_window_title(cx)); @@ -1316,6 +1319,26 @@ impl Workspace { } } + fn register_dock(dock: &ViewHandle, cx: &mut ViewContext) -> Subscription { + cx.subscribe(dock, Self::handle_dock_event) + } + + fn handle_dock_event( + &mut self, + dock: ViewHandle, + event: &dock::Event, + cx: &mut ViewContext, + ) { + match event { + dock::Event::Close => { + dock.update(cx, |dock, cx| dock.set_open(false, cx)); + self.serialize_workspace(cx); + cx.focus_self(); + cx.notify(); + } + } + } + pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { let dock = match dock_side { DockPosition::Left => &mut self.left_dock, From 02066afb0e801f8226f8a155682bf60706286e79 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 11:45:39 +0200 Subject: [PATCH 007/131] Don't pass `&mut Workspace` when closing items in a `Pane` This allows closing items via actions even in the `TerminalPanel` where the `Pane` is not directly owned by a `Workspace`. --- crates/workspace/src/item.rs | 2 +- crates/workspace/src/pane.rs | 268 ++++++++---------------------- crates/workspace/src/workspace.rs | 43 ++--- crates/zed/src/zed.rs | 56 +++---- 4 files changed, 110 insertions(+), 259 deletions(-) diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index e7571c1bf6..0ae9e4f0ea 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -437,7 +437,7 @@ impl ItemHandle for ViewHandle { for item_event in T::to_item_events(event).into_iter() { match item_event { ItemEvent::CloseItem => { - Pane::close_item_by_id(workspace, pane, item.id(), cx) + pane.update(cx, |pane, cx| pane.close_item_by_id(item.id(), cx)) .detach_and_log_err(cx); return; } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index fc3fda8e6a..c02c5d7ef4 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -681,187 +681,108 @@ impl Pane { } pub fn close_active_item( - workspace: &mut Workspace, + &mut self, _: &CloseActiveItem, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - let pane = pane_handle.read(cx); - - if pane.items.is_empty() { + if self.items.is_empty() { return None; } - let active_item_id = pane.items[pane.active_item_index].id(); - - let task = Self::close_item_by_id(workspace, pane_handle, active_item_id, cx); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + let active_item_id = self.items[self.active_item_index].id(); + Some(self.close_item_by_id(active_item_id, cx)) } pub fn close_item_by_id( - workspace: &mut Workspace, - pane: ViewHandle, + &mut self, item_id_to_close: usize, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Task> { - Self::close_items(workspace, pane, cx, move |view_id| { - view_id == item_id_to_close - }) + self.close_items(cx, move |view_id| view_id == item_id_to_close) } pub fn close_inactive_items( - workspace: &mut Workspace, + &mut self, _: &CloseInactiveItems, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - let pane = pane_handle.read(cx); - let active_item_id = pane.items[pane.active_item_index].id(); - - let task = Self::close_items(workspace, pane_handle, cx, move |item_id| { - item_id != active_item_id - }); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + let active_item_id = self.items[self.active_item_index].id(); + Some(self.close_items(cx, move |item_id| item_id != active_item_id)) } pub fn close_clean_items( - workspace: &mut Workspace, + &mut self, _: &CloseCleanItems, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - let pane = pane_handle.read(cx); - - let item_ids: Vec<_> = pane + let item_ids: Vec<_> = self .items() .filter(|item| !item.is_dirty(cx)) .map(|item| item.id()) .collect(); - - let task = Self::close_items(workspace, pane_handle, cx, move |item_id| { - item_ids.contains(&item_id) - }); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + Some(self.close_items(cx, move |item_id| item_ids.contains(&item_id))) } pub fn close_items_to_the_left( - workspace: &mut Workspace, + &mut self, _: &CloseItemsToTheLeft, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - let pane = pane_handle.read(cx); - let active_item_id = pane.items[pane.active_item_index].id(); - - let task = Self::close_items_to_the_left_by_id(workspace, pane_handle, active_item_id, cx); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + let active_item_id = self.items[self.active_item_index].id(); + Some(self.close_items_to_the_left_by_id(active_item_id, cx)) } pub fn close_items_to_the_left_by_id( - workspace: &mut Workspace, - pane: ViewHandle, + &mut self, item_id: usize, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Task> { - let item_ids: Vec<_> = pane - .read(cx) + let item_ids: Vec<_> = self .items() .take_while(|item| item.id() != item_id) .map(|item| item.id()) .collect(); - - let task = Self::close_items(workspace, pane, cx, move |item_id| { - item_ids.contains(&item_id) - }); - - cx.foreground().spawn(async move { - task.await?; - Ok(()) - }) + self.close_items(cx, move |item_id| item_ids.contains(&item_id)) } pub fn close_items_to_the_right( - workspace: &mut Workspace, + &mut self, _: &CloseItemsToTheRight, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - let pane = pane_handle.read(cx); - let active_item_id = pane.items[pane.active_item_index].id(); - - let task = Self::close_items_to_the_right_by_id(workspace, pane_handle, active_item_id, cx); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + let active_item_id = self.items[self.active_item_index].id(); + Some(self.close_items_to_the_right_by_id(active_item_id, cx)) } pub fn close_items_to_the_right_by_id( - workspace: &mut Workspace, - pane: ViewHandle, + &mut self, item_id: usize, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Task> { - let item_ids: Vec<_> = pane - .read(cx) + let item_ids: Vec<_> = self .items() .rev() .take_while(|item| item.id() != item_id) .map(|item| item.id()) .collect(); - - let task = Self::close_items(workspace, pane, cx, move |item_id| { - item_ids.contains(&item_id) - }); - - cx.foreground().spawn(async move { - task.await?; - Ok(()) - }) + self.close_items(cx, move |item_id| item_ids.contains(&item_id)) } pub fn close_all_items( - workspace: &mut Workspace, + &mut self, _: &CloseAllItems, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - - let task = Self::close_items(workspace, pane_handle, cx, move |_| true); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + Some(self.close_items(cx, move |_| true)) } pub fn close_items( - workspace: &mut Workspace, - pane: ViewHandle, - cx: &mut ViewContext, + &mut self, + cx: &mut ViewContext, should_close: impl 'static + Fn(usize) -> bool, ) -> Task> { - let project = workspace.project().clone(); - // Find the items to close. let mut items_to_close = Vec::new(); - for item in &pane.read(cx).items { + for item in &self.items { if should_close(item.id()) { items_to_close.push(item.boxed_clone()); } @@ -873,8 +794,8 @@ impl Pane { // of what content they would be saving. items_to_close.sort_by_key(|item| !item.is_singleton(cx)); - let pane = pane.downgrade(); - cx.spawn(|workspace, mut cx| async move { + let workspace = self.workspace.clone(); + cx.spawn(|pane, mut cx| async move { let mut saved_project_items_ids = HashSet::default(); for item in items_to_close.clone() { // Find the item's current index and its set of project item models. Avoid @@ -892,7 +813,7 @@ impl Pane { // Check if this view has any project items that are not open anywhere else // in the workspace, AND that the user has not already been prompted to save. // If there are any such project entries, prompt the user to save this item. - workspace.read_with(&cx, |workspace, cx| { + let project = workspace.read_with(&cx, |workspace, cx| { for item in workspace.items(cx) { if !items_to_close .iter() @@ -902,6 +823,7 @@ impl Pane { project_item_ids.retain(|id| !other_project_item_ids.contains(id)); } } + workspace.project().clone() })?; let should_save = project_item_ids .iter() @@ -1200,14 +1122,11 @@ impl Pane { // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command. vec![ ContextMenuItem::handler("Close Inactive Item", { - let workspace = self.workspace.clone(); let pane = target_pane.clone(); move |cx| { - if let Some((workspace, pane)) = - workspace.upgrade(cx).zip(pane.upgrade(cx)) - { - workspace.update(cx, |workspace, cx| { - Self::close_item_by_id(workspace, pane, target_item_id, cx) + if let Some(pane) = pane.upgrade(cx) { + pane.update(cx, |pane, cx| { + pane.close_item_by_id(target_item_id, cx) .detach_and_log_err(cx); }) } @@ -1216,39 +1135,23 @@ impl Pane { ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), ContextMenuItem::action("Close Clean Items", CloseCleanItems), ContextMenuItem::handler("Close Items To The Left", { - let workspace = self.workspace.clone(); let pane = target_pane.clone(); move |cx| { - if let Some((workspace, pane)) = - workspace.upgrade(cx).zip(pane.upgrade(cx)) - { - workspace.update(cx, |workspace, cx| { - Self::close_items_to_the_left_by_id( - workspace, - pane, - target_item_id, - cx, - ) - .detach_and_log_err(cx); + if let Some(pane) = pane.upgrade(cx) { + pane.update(cx, |pane, cx| { + pane.close_items_to_the_left_by_id(target_item_id, cx) + .detach_and_log_err(cx); }) } } }), ContextMenuItem::handler("Close Items To The Right", { - let workspace = self.workspace.clone(); let pane = target_pane.clone(); move |cx| { - if let Some((workspace, pane)) = - workspace.upgrade(cx).zip(pane.upgrade(cx)) - { - workspace.update(cx, |workspace, cx| { - Self::close_items_to_the_right_by_id( - workspace, - pane, - target_item_id, - cx, - ) - .detach_and_log_err(cx); + if let Some(pane) = pane.upgrade(cx) { + pane.update(cx, |pane, cx| { + pane.close_items_to_the_right_by_id(target_item_id, cx) + .detach_and_log_err(cx); }) } } @@ -1336,20 +1239,7 @@ impl Pane { .on_click(MouseButton::Middle, { let item_id = item.id(); move |_, pane, cx| { - let workspace = pane.workspace.clone(); - let pane = cx.weak_handle(); - cx.window_context().defer(move |cx| { - if let Some((workspace, pane)) = - workspace.upgrade(cx).zip(pane.upgrade(cx)) - { - workspace.update(cx, |workspace, cx| { - Self::close_item_by_id( - workspace, pane, item_id, cx, - ) - .detach_and_log_err(cx); - }); - } - }); + pane.close_item_by_id(item_id, cx).detach_and_log_err(cx); } }) .on_down( @@ -1556,12 +1446,9 @@ impl Pane { let pane = pane.clone(); cx.window_context().defer(move |cx| { if let Some(pane) = pane.upgrade(cx) { - if let Some(workspace) = pane.read(cx).workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - Self::close_item_by_id(workspace, pane, item_id, cx) - .detach_and_log_err(cx); - }); - } + pane.update(cx, |pane, cx| { + pane.close_item_by_id(item_id, cx).detach_and_log_err(cx); + }); } }); } @@ -2028,9 +1915,10 @@ mod tests { let project = Project::test(fs, None, cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - workspace.update(cx, |workspace, cx| { - assert!(Pane::close_active_item(workspace, &CloseActiveItem, cx).is_none()) + pane.update(cx, |pane, cx| { + assert!(pane.close_active_item(&CloseActiveItem, cx).is_none()) }); } @@ -2327,30 +2215,22 @@ mod tests { add_labeled_item(&workspace, &pane, "1", false, cx); assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_active_item(workspace, &CloseActiveItem, cx); - }); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, ["A", "B*", "C", "D"], cx); pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx)); assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_active_item(workspace, &CloseActiveItem, cx); - }); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, ["A", "B*", "C"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_active_item(workspace, &CloseActiveItem, cx); - }); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, ["A", "C*"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_active_item(workspace, &CloseActiveItem, cx); - }); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, ["A*"], cx); } @@ -2366,8 +2246,8 @@ mod tests { set_labeled_items(&workspace, &pane, ["A", "B", "C*", "D", "E"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_inactive_items(workspace, &CloseInactiveItems, cx); + pane.update(cx, |pane, cx| { + pane.close_inactive_items(&CloseInactiveItems, cx) }); deterministic.run_until_parked(); @@ -2390,9 +2270,7 @@ mod tests { add_labeled_item(&workspace, &pane, "E", false, cx); assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_clean_items(workspace, &CloseCleanItems, cx); - }); + pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, ["A^", "C*^"], cx); @@ -2412,8 +2290,8 @@ mod tests { set_labeled_items(&workspace, &pane, ["A", "B", "C*", "D", "E"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_items_to_the_left(workspace, &CloseItemsToTheLeft, cx); + pane.update(cx, |pane, cx| { + pane.close_items_to_the_left(&CloseItemsToTheLeft, cx) }); deterministic.run_until_parked(); @@ -2434,8 +2312,8 @@ mod tests { set_labeled_items(&workspace, &pane, ["A", "B", "C*", "D", "E"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_items_to_the_right(workspace, &CloseItemsToTheRight, cx); + pane.update(cx, |pane, cx| { + pane.close_items_to_the_right(&CloseItemsToTheRight, cx) }); deterministic.run_until_parked(); @@ -2456,9 +2334,7 @@ mod tests { add_labeled_item(&workspace, &pane, "C", false, cx); assert_item_labels(&pane, ["A", "B", "C*"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_all_items(workspace, &CloseAllItems, cx); - }); + pane.update(cx, |pane, cx| pane.close_all_items(&CloseAllItems, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, [], cx); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e79f071267..59a8e97f3d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3125,6 +3125,7 @@ mod tests { let project = Project::test(fs, ["root1".as_ref()], cx).await; let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let worktree_id = project.read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() }); @@ -3167,12 +3168,11 @@ mod tests { }); // Close the active item - workspace - .update(cx, |workspace, cx| { - Pane::close_active_item(workspace, &Default::default(), cx).unwrap() - }) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_active_item(&Default::default(), cx).unwrap() + }) + .await + .unwrap(); assert_eq!( cx.current_window_title(window_id).as_deref(), Some("one.txt — root1") @@ -3281,18 +3281,13 @@ mod tests { workspace.active_pane().clone() }); - let close_items = workspace.update(cx, |workspace, cx| { - pane.update(cx, |pane, cx| { - pane.activate_item(1, true, true, cx); - assert_eq!(pane.active_item().unwrap().id(), item2.id()); - }); - + let close_items = pane.update(cx, |pane, cx| { + pane.activate_item(1, true, true, cx); + assert_eq!(pane.active_item().unwrap().id(), item2.id()); let item1_id = item1.id(); let item3_id = item3.id(); let item4_id = item4.id(); - Pane::close_items(workspace, pane.clone(), cx, move |id| { - [item1_id, item3_id, item4_id].contains(&id) - }) + pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id)) }); cx.foreground().run_until_parked(); @@ -3426,10 +3421,7 @@ mod tests { // once for project entry 0, and once for project entry 2. After those two // prompts, the task should complete. - let close = workspace.update(cx, |workspace, cx| { - Pane::close_items(workspace, left_pane.clone(), cx, |_| true) - }); - + let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true)); cx.foreground().run_until_parked(); left_pane.read_with(cx, |pane, cx| { assert_eq!( @@ -3464,6 +3456,7 @@ mod tests { let project = Project::test(fs, [], cx).await; let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let item = cx.add_view(window_id, |cx| { TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) @@ -3536,11 +3529,7 @@ mod tests { item.is_dirty = true; }); - workspace - .update(cx, |workspace, cx| { - let pane = workspace.active_pane().clone(); - Pane::close_items(workspace, pane, cx, move |id| id == item_id) - }) + pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id)) .await .unwrap(); assert!(!cx.has_pending_prompt(window_id)); @@ -3561,10 +3550,8 @@ mod tests { item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); // Ensure autosave is prevented for deleted files also when closing the buffer. - let _close_items = workspace.update(cx, |workspace, cx| { - let pane = workspace.active_pane().clone(); - Pane::close_items(workspace, pane, cx, move |id| id == item_id) - }); + let _close_items = + pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id)); deterministic.run_until_parked(); assert!(cx.has_pending_prompt(window_id)); item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 52b0ad0898..662638301d 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -734,6 +734,7 @@ mod tests { .unwrap() .downcast::() .unwrap(); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let editor = workspace.read_with(cx, |workspace, cx| { workspace .active_item(cx) @@ -756,9 +757,9 @@ mod tests { assert!(cx.is_window_edited(workspace.window_id())); // Closing the item restores the window's edited state. - let close = workspace.update(cx, |workspace, cx| { + let close = pane.update(cx, |pane, cx| { drop(editor); - Pane::close_active_item(workspace, &Default::default(), cx).unwrap() + pane.close_active_item(&Default::default(), cx).unwrap() }); executor.run_until_parked(); cx.simulate_prompt_answer(workspace.window_id(), 1); @@ -1384,6 +1385,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let entries = cx.read(|cx| workspace.file_project_paths(cx)); let file1 = entries[0].clone(); @@ -1501,14 +1503,13 @@ mod tests { // Go forward to an item that has been closed, ensuring it gets re-opened at the same // location. - workspace - .update(cx, |workspace, cx| { - let editor3_id = editor3.id(); - drop(editor3); - Pane::close_item_by_id(workspace, workspace.active_pane().clone(), editor3_id, cx) - }) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + let editor3_id = editor3.id(); + drop(editor3); + pane.close_item_by_id(editor3_id, cx) + }) + .await + .unwrap(); workspace .update(cx, |w, cx| Pane::go_forward(w, None, cx)) .await @@ -1537,14 +1538,13 @@ mod tests { ); // Go back to an item that has been closed and removed from disk, ensuring it gets skipped. - workspace - .update(cx, |workspace, cx| { - let editor2_id = editor2.id(); - drop(editor2); - Pane::close_item_by_id(workspace, workspace.active_pane().clone(), editor2_id, cx) - }) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + let editor2_id = editor2.id(); + drop(editor2); + pane.close_item_by_id(editor2_id, cx) + }) + .await + .unwrap(); app_state .fs .remove_file(Path::new("/root/a/file2"), Default::default()) @@ -1693,34 +1693,22 @@ mod tests { assert_eq!(active_path(&workspace, cx), Some(file4.clone())); // Close all the pane items in some arbitrary order. - workspace - .update(cx, |workspace, cx| { - Pane::close_item_by_id(workspace, pane.clone(), file1_item_id, cx) - }) + pane.update(cx, |pane, cx| pane.close_item_by_id(file1_item_id, cx)) .await .unwrap(); assert_eq!(active_path(&workspace, cx), Some(file4.clone())); - workspace - .update(cx, |workspace, cx| { - Pane::close_item_by_id(workspace, pane.clone(), file4_item_id, cx) - }) + pane.update(cx, |pane, cx| pane.close_item_by_id(file4_item_id, cx)) .await .unwrap(); assert_eq!(active_path(&workspace, cx), Some(file3.clone())); - workspace - .update(cx, |workspace, cx| { - Pane::close_item_by_id(workspace, pane.clone(), file2_item_id, cx) - }) + pane.update(cx, |pane, cx| pane.close_item_by_id(file2_item_id, cx)) .await .unwrap(); assert_eq!(active_path(&workspace, cx), Some(file3.clone())); - workspace - .update(cx, |workspace, cx| { - Pane::close_item_by_id(workspace, pane.clone(), file3_item_id, cx) - }) + pane.update(cx, |pane, cx| pane.close_item_by_id(file3_item_id, cx)) .await .unwrap(); assert_eq!(active_path(&workspace, cx), None); From e6be35c9a54d498e0c21cc2a54fc9f7076cc1e20 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 12:21:35 +0200 Subject: [PATCH 008/131] Show terminal count in panel button --- crates/project_panel/src/project_panel.rs | 6 +--- crates/terminal_view/src/terminal_panel.rs | 18 ++++++++++-- crates/theme/src/theme.rs | 1 - crates/workspace/src/dock.rs | 33 +++++++++++----------- 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 35ab314f3a..cecf6c114f 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1327,11 +1327,7 @@ impl Entity for ProjectPanel { type Event = Event; } -impl workspace::dock::Panel for ProjectPanel { - fn should_show_badge(&self, _: &AppContext) -> bool { - false - } -} +impl workspace::dock::Panel for ProjectPanel {} impl ClipboardEntry { fn is_cut(&self) -> bool { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 7d715019c3..ce70f08391 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -16,7 +16,7 @@ pub struct TerminalPanel { project: ModelHandle, pane: ViewHandle, workspace: WeakViewHandle, - _subscription: Subscription, + _subscriptions: Vec, } impl TerminalPanel { @@ -37,12 +37,15 @@ impl TerminalPanel { }); pane }); - let subscription = cx.subscribe(&pane, Self::handle_pane_event); + let subscriptions = vec![ + cx.observe(&pane, |_, _, cx| cx.notify()), + cx.subscribe(&pane, Self::handle_pane_event), + ]; Self { project: workspace.project().clone(), pane, workspace: workspace.weak_handle(), - _subscription: subscription, + _subscriptions: subscriptions, } } @@ -108,4 +111,13 @@ impl Panel for TerminalPanel { fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { matches!(event, Event::Close) } + + fn label(&self, cx: &AppContext) -> Option { + let count = self.pane.read(cx).items_len(); + if count == 0 { + None + } else { + Some(count.to_string()) + } + } } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 67d07a3998..2d4c520127 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -346,7 +346,6 @@ pub struct StatusBarPanelButtons { pub group_bottom: ContainerStyle, pub group_right: ContainerStyle, pub button: Interactive, - pub badge: ContainerStyle, } #[derive(Deserialize, Default)] diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 3acda44297..57d18fd368 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -14,8 +14,8 @@ pub trait Panel: View { fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { false } - fn should_show_badge(&self, _: &AppContext) -> bool { - false + fn label(&self, _: &AppContext) -> Option { + None } fn contains_focused_view(&self, _: &AppContext) -> bool { false @@ -24,7 +24,7 @@ pub trait Panel: View { pub trait PanelHandle { fn id(&self) -> usize; - fn should_show_badge(&self, cx: &WindowContext) -> bool; + fn label(&self, cx: &WindowContext) -> Option; fn is_focused(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; } @@ -37,8 +37,8 @@ where self.id() } - fn should_show_badge(&self, cx: &WindowContext) -> bool { - self.read(cx).should_show_badge(cx) + fn label(&self, cx: &WindowContext) -> Option { + self.read(cx).label(cx) } fn is_focused(&self, cx: &WindowContext) -> bool { @@ -247,7 +247,6 @@ impl View for PanelButtons { let theme = &theme.workspace.status_bar.panel_buttons; let dock = self.dock.read(cx); let item_style = theme.button.clone(); - let badge_style = theme.badge; let active_ix = dock.active_item_ix; let is_open = dock.is_open; let dock_position = dock.position; @@ -274,23 +273,25 @@ impl View for PanelButtons { MouseEventHandler::::new(ix, cx, |state, cx| { let is_active = is_open && ix == active_ix; let style = item_style.style_for(state, is_active); - Stack::new() - .with_child(Svg::new(icon_path).with_color(style.icon_color)) - .with_children(if !is_active && item_view.should_show_badge(cx) { + Flex::row() + .with_child( + Svg::new(icon_path) + .with_color(style.icon_color) + .constrained() + .with_width(style.icon_size) + .aligned(), + ) + .with_children(if let Some(label) = item_view.label(cx) { Some( - Empty::new() - .collapsed() + Label::new(label, style.label.text.clone()) .contained() - .with_style(badge_style) - .aligned() - .bottom() - .right(), + .with_style(style.label.container) + .aligned(), ) } else { None }) .constrained() - .with_width(style.icon_size) .with_height(style.icon_size) .contained() .with_style(style.container) From 26fe7a81f34f69ec993f993eb1523dbe2d8cb480 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 12:29:16 +0200 Subject: [PATCH 009/131] Prevent splits in the terminal panel --- crates/terminal_view/src/terminal_panel.rs | 1 + crates/workspace/src/pane.rs | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index ce70f08391..5b1a7d20df 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -28,6 +28,7 @@ impl TerminalPanel { workspace.app_state().background_actions, cx, ); + pane.set_can_split(false, cx); pane.on_can_drop(move |drag_and_drop, cx| { drag_and_drop .currently_dragged::(window_id) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index c02c5d7ef4..fd617e1685 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -149,6 +149,7 @@ pub struct Pane { workspace: WeakViewHandle, has_focus: bool, can_drop: Rc, &WindowContext) -> bool>, + can_split: bool, } pub struct ItemNavHistory { @@ -255,6 +256,7 @@ impl Pane { workspace, has_focus: false, can_drop: Rc::new(|_, _| true), + can_split: true, } } @@ -282,6 +284,11 @@ impl Pane { self.can_drop = Rc::new(can_drop); } + pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext) { + self.can_split = can_split; + cx.notify(); + } + pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { ItemNavHistory { history: self.nav_history.clone(), @@ -1563,8 +1570,8 @@ impl View for Pane { self, 0, self.active_item_index + 1, - false, - Some(100.), + !self.can_split, + if self.can_split { Some(100.) } else { None }, cx, { let toolbar = self.toolbar.clone(); From ad7f32d7d2c2fc9e3a1dba920f6796ce8402297f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 14:46:15 +0200 Subject: [PATCH 010/131] Fix bottom dock resizing --- crates/workspace/src/dock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 57d18fd368..2b23ceb7f3 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -78,7 +78,7 @@ impl DockPosition { fn to_resizable_side(self) -> Side { match self { Self::Left => Side::Right, - Self::Bottom => Side::Bottom, + Self::Bottom => Side::Top, Self::Right => Side::Left, } } From 634b699281a3ab66a690fa93cead04e78d94aa9c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 15:05:29 +0200 Subject: [PATCH 011/131] Allow customization of `Pane` tab bar buttons --- crates/terminal_view/src/terminal_panel.rs | 72 +++++++----- crates/workspace/src/pane.rs | 128 +++++++++++---------- 2 files changed, 114 insertions(+), 86 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 5b1a7d20df..90072ea652 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -21,6 +21,7 @@ pub struct TerminalPanel { impl TerminalPanel { pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + let this = cx.weak_handle(); let pane = cx.add_view(|cx| { let window_id = cx.window_id(); let mut pane = Pane::new( @@ -36,6 +37,23 @@ impl TerminalPanel { item.handle.act_as::(cx).is_some() }) }); + pane.set_render_tab_bar_buttons(cx, move |_, cx| { + let this = this.clone(); + Pane::render_tab_bar_button( + 0, + "icons/plus_12.svg", + cx, + move |_, cx| { + let this = this.clone(); + cx.window_context().defer(move |cx| { + if let Some(this) = this.upgrade(cx) { + this.update(cx, |this, cx| this.add_terminal(cx)); + } + }) + }, + None, + ) + }); pane }); let subscriptions = vec![ @@ -61,6 +79,33 @@ impl TerminalPanel { _ => {} } } + + fn add_terminal(&mut self, cx: &mut ViewContext) { + if let Some(workspace) = self.workspace.upgrade(cx) { + let working_directory_strategy = cx + .global::() + .terminal_overrides + .working_directory + .clone() + .unwrap_or(WorkingDirectory::CurrentProjectDirectory); + let working_directory = + crate::get_working_directory(workspace.read(cx), cx, working_directory_strategy); + let window_id = cx.window_id(); + if let Some(terminal) = self.project.update(cx, |project, cx| { + project + .create_terminal(working_directory, window_id, cx) + .log_err() + }) { + workspace.update(cx, |workspace, cx| { + let terminal = + Box::new(cx.add_view(|cx| { + TerminalView::new(terminal, workspace.database_id(), cx) + })); + Pane::add_item(workspace, &self.pane, terminal, true, true, None, cx); + }); + } + } + } } impl Entity for TerminalPanel { @@ -78,32 +123,7 @@ impl View for TerminalPanel { fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { if self.pane.read(cx).items_len() == 0 { - if let Some(workspace) = self.workspace.upgrade(cx) { - let working_directory_strategy = cx - .global::() - .terminal_overrides - .working_directory - .clone() - .unwrap_or(WorkingDirectory::CurrentProjectDirectory); - let working_directory = crate::get_working_directory( - workspace.read(cx), - cx, - working_directory_strategy, - ); - let window_id = cx.window_id(); - if let Some(terminal) = self.project.update(cx, |project, cx| { - project - .create_terminal(working_directory, window_id, cx) - .log_err() - }) { - workspace.update(cx, |workspace, cx| { - let terminal = Box::new(cx.add_view(|cx| { - TerminalView::new(terminal, workspace.database_id(), cx) - })); - Pane::add_item(workspace, &self.pane, terminal, true, true, None, cx); - }); - } - } + self.add_terminal(cx) } } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index fd617e1685..0523f082ea 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -150,6 +150,7 @@ pub struct Pane { has_focus: bool, can_drop: Rc, &WindowContext) -> bool>, can_split: bool, + render_tab_bar_buttons: Rc) -> AnyElement>, } pub struct ItemNavHistory { @@ -257,6 +258,27 @@ impl Pane { has_focus: false, can_drop: Rc::new(|_, _| true), can_split: true, + render_tab_bar_buttons: Rc::new(|pane, cx| { + Flex::row() + // New menu + .with_child(Self::render_tab_bar_button( + 0, + "icons/plus_12.svg", + cx, + |pane, cx| pane.deploy_new_menu(cx), + pane.tab_bar_context_menu + .handle_if_kind(TabBarContextMenuKind::New), + )) + .with_child(Self::render_tab_bar_button( + 2, + "icons/split_12.svg", + cx, + |pane, cx| pane.deploy_split_menu(cx), + pane.tab_bar_context_menu + .handle_if_kind(TabBarContextMenuKind::Split), + )) + .into_any() + }), } } @@ -289,6 +311,14 @@ impl Pane { cx.notify(); } + pub fn set_render_tab_bar_buttons(&mut self, cx: &mut ViewContext, render: F) + where + F: 'static + Fn(&mut Pane, &mut ViewContext) -> AnyElement, + { + self.render_tab_bar_buttons = Rc::new(render); + cx.notify(); + } + pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { ItemNavHistory { history: self.nav_history.clone(), @@ -1475,33 +1505,37 @@ impl Pane { .into_any() } - fn render_tab_bar_buttons( - &mut self, - theme: &Theme, - cx: &mut ViewContext, - ) -> AnyElement { - Flex::row() - // New menu - .with_child(render_tab_bar_button( - 0, - "icons/plus_12.svg", - cx, - |pane, cx| pane.deploy_new_menu(cx), - self.tab_bar_context_menu - .handle_if_kind(TabBarContextMenuKind::New), - )) - .with_child(render_tab_bar_button( - 2, - "icons/split_12.svg", - cx, - |pane, cx| pane.deploy_split_menu(cx), - self.tab_bar_context_menu - .handle_if_kind(TabBarContextMenuKind::Split), - )) - .contained() - .with_style(theme.workspace.tab_bar.pane_button_container) + pub fn render_tab_bar_button)>( + index: usize, + icon: &'static str, + cx: &mut ViewContext, + on_click: F, + context_menu: Option>, + ) -> AnyElement { + enum TabBarButton {} + + Stack::new() + .with_child( + MouseEventHandler::::new(index, cx, |mouse_state, cx| { + let theme = &cx.global::().theme.workspace.tab_bar; + let style = theme.pane_button.style_for(mouse_state, false); + Svg::new(icon) + .with_color(style.color) + .constrained() + .with_width(style.icon_width) + .aligned() + .constrained() + .with_width(style.button_width) + .with_height(style.button_width) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx)), + ) + .with_children( + context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()), + ) .flex(1., false) - .into_any() + .into_any_named("tab bar button") } fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext) -> AnyElement { @@ -1554,7 +1588,14 @@ impl View for Pane { .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs")); if self.is_active { - tab_row.add_child(self.render_tab_bar_buttons(&theme, cx)) + let render_tab_bar_buttons = self.render_tab_bar_buttons.clone(); + tab_row.add_child( + (render_tab_bar_buttons)(self, cx) + .contained() + .with_style(theme.workspace.tab_bar.pane_button_container) + .flex(1., false) + .into_any(), + ) } stack.add_child(tab_row); @@ -1676,39 +1717,6 @@ impl View for Pane { } } -fn render_tab_bar_button)>( - index: usize, - icon: &'static str, - cx: &mut ViewContext, - on_click: F, - context_menu: Option>, -) -> AnyElement { - enum TabBarButton {} - - Stack::new() - .with_child( - MouseEventHandler::::new(index, cx, |mouse_state, cx| { - let theme = &cx.global::().theme.workspace.tab_bar; - let style = theme.pane_button.style_for(mouse_state, false); - Svg::new(icon) - .with_color(style.color) - .constrained() - .with_width(style.icon_width) - .aligned() - .constrained() - .with_width(style.button_width) - .with_height(style.button_width) - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx)), - ) - .with_children( - context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()), - ) - .flex(1., false) - .into_any_named("tab bar button") -} - impl ItemNavHistory { pub fn push(&self, data: Option, cx: &mut WindowContext) { self.history.borrow_mut().push(data, self.item.clone(), cx); From 641f5d1107b282127115d812368704b161748cca Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 17:38:18 +0200 Subject: [PATCH 012/131] Ensure `ctrl-`` works in the terminal panel --- crates/terminal_view/src/terminal_panel.rs | 12 +++++++++--- crates/terminal_view/src/terminal_view.rs | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 90072ea652..d963025f89 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -8,6 +8,10 @@ use settings::{Settings, WorkingDirectory}; use util::ResultExt; use workspace::{dock::Panel, pane, DraggedItem, Pane, Workspace}; +pub fn init(cx: &mut AppContext) { + cx.add_action(TerminalPanel::add_terminal); +} + pub enum Event { Close, } @@ -47,7 +51,9 @@ impl TerminalPanel { let this = this.clone(); cx.window_context().defer(move |cx| { if let Some(this) = this.upgrade(cx) { - this.update(cx, |this, cx| this.add_terminal(cx)); + this.update(cx, |this, cx| { + this.add_terminal(&Default::default(), cx); + }); } }) }, @@ -80,7 +86,7 @@ impl TerminalPanel { } } - fn add_terminal(&mut self, cx: &mut ViewContext) { + fn add_terminal(&mut self, _: &workspace::NewTerminal, cx: &mut ViewContext) { if let Some(workspace) = self.workspace.upgrade(cx) { let working_directory_strategy = cx .global::() @@ -123,7 +129,7 @@ impl View for TerminalPanel { fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { if self.pane.read(cx).items_len() == 0 { - self.add_terminal(cx) + self.add_terminal(&Default::default(), cx) } } } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index e3a5153ef3..b318b572b5 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -64,6 +64,7 @@ actions!( impl_actions!(terminal, [SendText, SendKeystroke]); pub fn init(cx: &mut AppContext) { + terminal_panel::init(cx); cx.add_action(TerminalView::deploy); register_deserializable_item::(cx); From 37d3ed5f5f6358d44d0546e24d709b8d3e804787 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 17:38:54 +0200 Subject: [PATCH 013/131] Focus new item if pane was focused when removing previous active item Previously, we were relying on the item getting blurred and the workspace receiving focus, which would in turn focus the active pane. This doesn't play well with docks because they aren't part of the workspace panes. --- crates/workspace/src/pane.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 0523f082ea..30e782c750 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -903,7 +903,8 @@ impl Pane { // to activating the item to the left .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1)); - self.activate_item(index_to_activate, activate_pane, activate_pane, cx); + let should_activate = activate_pane || self.has_focus; + self.activate_item(index_to_activate, should_activate, should_activate, cx); } let item = self.items.remove(item_index); From 45df09245b867a37475f405c0a7ee9e9a2222ac5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 18:06:59 +0200 Subject: [PATCH 014/131] Remove unused code Co-Authored-By: Nathan Sobo --- crates/workspace/src/dock.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 2b23ceb7f3..492f61c86f 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -17,9 +17,6 @@ pub trait Panel: View { fn label(&self, _: &AppContext) -> Option { None } - fn contains_focused_view(&self, _: &AppContext) -> bool { - false - } } pub trait PanelHandle { @@ -42,7 +39,7 @@ where } fn is_focused(&self, cx: &WindowContext) -> bool { - ViewHandle::is_focused(self, cx) || self.read(cx).contains_focused_view(cx) + ViewHandle::is_focused(self, cx) } fn as_any(&self) -> &AnyViewHandle { From 8f12489937ce86d997b1f6fc73783f7231995a79 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 18:57:25 +0200 Subject: [PATCH 015/131] WIP: Allow panels to be moved Co-Authored-By: Nathan Sobo --- crates/project_panel/src/project_panel.rs | 32 +++- crates/terminal_view/src/terminal_panel.rs | 2 +- crates/workspace/src/dock.rs | 161 +++++++++++---------- crates/workspace/src/item.rs | 30 +++- crates/workspace/src/workspace.rs | 11 +- crates/zed/src/zed.rs | 13 +- 6 files changed, 157 insertions(+), 92 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index cecf6c114f..f326d665d0 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -28,7 +28,7 @@ use std::{ }; use theme::ProjectPanelEntry; use unicase::UniCase; -use workspace::Workspace; +use workspace::{dock::DockPosition, Workspace}; const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX; @@ -1327,7 +1327,35 @@ impl Entity for ProjectPanel { type Event = Event; } -impl workspace::dock::Panel for ProjectPanel {} +impl workspace::dock::Panel for ProjectPanel { + fn position(&self, cx: &gpui::WindowContext) -> DockPosition { + todo!() + } + + fn position_is_valid(&self, position: DockPosition) -> bool { + matches!(position, DockPosition::Left | DockPosition::Right) + } + + fn icon_path(&self) -> &'static str { + "icons/folder_tree_16.svg" + } + + fn icon_tooltip(&self) -> String { + "Project Panel".into() + } + + fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + todo!() + } + + fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + false + } + + fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + false + } +} impl ClipboardEntry { fn is_cut(&self) -> bool { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index d963025f89..be3c3de9d0 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -139,7 +139,7 @@ impl Panel for TerminalPanel { matches!(event, Event::Close) } - fn label(&self, cx: &AppContext) -> Option { + fn icon_label(&self, cx: &AppContext) -> Option { let count = self.pane.read(cx).items_len(); if count == 0 { None diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 492f61c86f..a378e00ec3 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -8,20 +8,25 @@ use settings::Settings; use std::rc::Rc; pub trait Panel: View { - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - false - } - fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - false - } - fn label(&self, _: &AppContext) -> Option { + fn position(&self, cx: &WindowContext) -> DockPosition; + fn position_is_valid(&self, position: DockPosition) -> bool; + fn icon_path(&self) -> &'static str; + fn icon_tooltip(&self) -> String; + fn icon_label(&self, _: &AppContext) -> Option { None } + fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; + fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; + fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; } pub trait PanelHandle { fn id(&self) -> usize; - fn label(&self, cx: &WindowContext) -> Option; + fn position(&self, cx: &WindowContext) -> DockPosition; + fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; + fn icon_path(&self, cx: &WindowContext) -> &'static str; + fn icon_tooltip(&self, cx: &WindowContext) -> String; + fn icon_label(&self, cx: &WindowContext) -> Option; fn is_focused(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; } @@ -34,8 +39,24 @@ where self.id() } - fn label(&self, cx: &WindowContext) -> Option { - self.read(cx).label(cx) + fn position(&self, cx: &WindowContext) -> DockPosition { + self.read(cx).position(cx) + } + + fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool { + self.read(cx).position_is_valid(position) + } + + fn icon_path(&self, cx: &WindowContext) -> &'static str { + self.read(cx).icon_path() + } + + fn icon_tooltip(&self, cx: &WindowContext) -> String { + self.read(cx).icon_tooltip() + } + + fn icon_label(&self, cx: &WindowContext) -> Option { + self.read(cx).icon_label(cx) } fn is_focused(&self, cx: &WindowContext) -> bool { @@ -82,8 +103,6 @@ impl DockPosition { } struct Item { - icon_path: &'static str, - tooltip: String, view: Rc, _subscriptions: [Subscription; 2], } @@ -132,13 +151,7 @@ impl Dock { cx.notify(); } - pub fn add_item( - &mut self, - icon_path: &'static str, - tooltip: String, - view: ViewHandle, - cx: &mut ViewContext, - ) { + pub fn add_panel(&mut self, view: ViewHandle, cx: &mut ViewContext) { let subscriptions = [ cx.observe(&view, |_, _, cx| cx.notify()), cx.subscribe(&view, |this, view, event, cx| { @@ -157,8 +170,6 @@ impl Dock { ]; self.items.push(Item { - icon_path, - tooltip, view: Rc::new(view), _subscriptions: subscriptions, }); @@ -235,15 +246,15 @@ impl Entity for PanelButtons { impl View for PanelButtons { fn ui_name() -> &'static str { - "DockToggleButton" + "PanelButtons" } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { let theme = &cx.global::().theme; let tooltip_style = theme.tooltip.clone(); let theme = &theme.workspace.status_bar.panel_buttons; - let dock = self.dock.read(cx); let item_style = theme.button.clone(); + let dock = self.dock.read(cx); let active_ix = dock.active_item_ix; let is_open = dock.is_open; let dock_position = dock.position; @@ -253,69 +264,65 @@ impl View for PanelButtons { DockPosition::Right => theme.group_right, }; - #[allow(clippy::needless_collect)] let items = dock .items .iter() - .map(|item| (item.icon_path, item.tooltip.clone(), item.view.clone())) + .map(|item| item.view.clone()) .collect::>(); - Flex::row() - .with_children(items.into_iter().enumerate().map( - |(ix, (icon_path, tooltip, item_view))| { - let action = TogglePanel { - dock_position, - item_index: ix, - }; - MouseEventHandler::::new(ix, cx, |state, cx| { - let is_active = is_open && ix == active_ix; - let style = item_style.style_for(state, is_active); - Flex::row() - .with_child( - Svg::new(icon_path) - .with_color(style.icon_color) - .constrained() - .with_width(style.icon_size) + .with_children(items.into_iter().enumerate().map(|(ix, view)| { + let action = TogglePanel { + dock_position, + item_index: ix, + }; + MouseEventHandler::::new(ix, cx, |state, cx| { + let is_active = is_open && ix == active_ix; + let style = item_style.style_for(state, is_active); + Flex::row() + .with_child( + Svg::new(view.icon_path(cx)) + .with_color(style.icon_color) + .constrained() + .with_width(style.icon_size) + .aligned(), + ) + .with_children(if let Some(label) = view.icon_label(cx) { + Some( + Label::new(label, style.label.text.clone()) + .contained() + .with_style(style.label.container) .aligned(), ) - .with_children(if let Some(label) = item_view.label(cx) { - Some( - Label::new(label, style.label.text.clone()) - .contained() - .with_style(style.label.container) - .aligned(), - ) - } else { - None - }) - .constrained() - .with_height(style.icon_size) - .contained() - .with_style(style.container) - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, { - let action = action.clone(); - move |_, this, cx| { - if let Some(workspace) = this.workspace.upgrade(cx) { - let action = action.clone(); - cx.window_context().defer(move |cx| { - workspace.update(cx, |workspace, cx| { - workspace.toggle_panel(&action, cx) - }); + } else { + None + }) + .constrained() + .with_height(style.icon_size) + .contained() + .with_style(style.container) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, { + let action = action.clone(); + move |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + let action = action.clone(); + cx.window_context().defer(move |cx| { + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel(&action, cx) }); - } + }); } - }) - .with_tooltip::( - ix, - tooltip, - Some(Box::new(action)), - tooltip_style.clone(), - cx, - ) - }, - )) + } + }) + .with_tooltip::( + ix, + view.icon_tooltip(cx), + Some(Box::new(action)), + tooltip_style.clone(), + cx, + ) + })) .contained() .with_style(group_style) .into_any() diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 0ae9e4f0ea..ddee068d46 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -1060,5 +1060,33 @@ pub(crate) mod test { } } - impl Panel for TestItem {} + impl Panel for TestItem { + fn position(&self, cx: &gpui::WindowContext) -> crate::dock::DockPosition { + unimplemented!() + } + + fn position_is_valid(&self, position: crate::dock::DockPosition) -> bool { + unimplemented!() + } + + fn icon_path(&self) -> &'static str { + unimplemented!() + } + + fn icon_tooltip(&self) -> String { + unimplemented!() + } + + fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + unimplemented!() + } + + fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + unimplemented!() + } + + fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + unimplemented!() + } + } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 59a8e97f3d..d86b25b746 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -60,7 +60,7 @@ use crate::{ notifications::simple_message_notification::MessageNotification, persistence::model::{SerializedPane, SerializedPaneGroup, SerializedWorkspace}, }; -use dock::{Dock, DockPosition, PanelButtons, TogglePanel}; +use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle, TogglePanel}; use lazy_static::lazy_static; use notifications::{NotificationHandle, NotifyResultExt}; pub use pane::*; @@ -834,6 +834,15 @@ impl Workspace { &self.right_dock } + pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) { + let dock = match panel.position(cx) { + DockPosition::Left => &mut self.left_dock, + DockPosition::Bottom => &mut self.bottom_dock, + DockPosition::Right => &mut self.right_dock, + }; + dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); + } + pub fn status_bar(&self) -> &ViewHandle { &self.status_bar } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 662638301d..394661da41 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -311,19 +311,12 @@ pub fn initialize_workspace( cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); - let project_panel = ProjectPanel::new(workspace, cx); - workspace.left_dock().update(cx, |dock, cx| { - dock.add_item( - "icons/folder_tree_16.svg", - "Project Panel".to_string(), - project_panel, - cx, - ); - }); + let project_panel = cx.add_view(|cx| ProjectPanel::new(workspace, cx)); + workspace.add_panel(panel, cx); let terminal_panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); workspace.bottom_dock().update(cx, |dock, cx| { - dock.add_item( + dock.add_panel( "icons/terminal_12.svg", "Terminals".to_string(), terminal_panel, From 6645323f1bee4bfc09c6f1256f5e2b83d568fda1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 13:28:34 -0600 Subject: [PATCH 016/131] WIP --- crates/project_panel/src/project_panel.rs | 2 +- crates/settings/src/settings.rs | 50 +++++++++++++++++++++- crates/terminal_view/src/terminal_panel.rs | 30 +++++++++++-- crates/workspace/src/dock.rs | 10 +++++ crates/workspace/src/item.rs | 4 +- crates/zed/src/zed.rs | 13 ++---- 6 files changed, 92 insertions(+), 17 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index f326d665d0..a595ec6225 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1329,7 +1329,7 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { - todo!() + cx.global::().project_panel_overrides.dock.into() } fn position_is_valid(&self, position: DockPosition) -> bool { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index fde7f516a7..528812af87 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -44,6 +44,8 @@ pub struct Settings { pub show_call_status_icon: bool, pub vim_mode: bool, pub autosave: Autosave, + pub project_panel_defaults: ProjectPanelSettings, + pub project_panel_overrides: ProjectPanelSettings, pub editor_defaults: EditorSettings, pub editor_overrides: EditorSettings, pub git: GitSettings, @@ -129,6 +131,14 @@ impl TelemetrySettings { } } +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all="lowercase")] +pub enum DockPosition { + Left, + Right, + Bottom, +} + #[derive(Clone, Debug, Default)] pub struct CopilotSettings { pub disabled_globs: Vec, @@ -156,6 +166,19 @@ pub enum GitGutter { pub struct GitGutterConfig {} +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct ProjectPanelSettings { + pub dock: DockPosition +} + +impl Default for ProjectPanelSettings { + fn default() -> Self { + Self { + dock: DockPosition::Left + } + } +} + #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct EditorSettings { pub tab_size: Option, @@ -237,7 +260,7 @@ impl Default for HourFormat { } } -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct TerminalSettings { pub shell: Option, pub working_directory: Option, @@ -250,8 +273,29 @@ pub struct TerminalSettings { pub alternate_scroll: Option, pub option_as_meta: Option, pub copy_on_select: Option, + pub dock: DockPosition, } +impl Default for TerminalSettings { + fn default() -> Self { + Self { + shell:Default::default(), + working_directory:Default::default(), + font_size:Default::default(), + font_family:Default::default(), + line_height:Default::default(), + font_features:Default::default(), + env:Default::default(), + blinking:Default::default(), + alternate_scroll:Default::default(), + option_as_meta:Default::default(), + copy_on_select:Default::default(), + dock: DockPosition::Bottom, + } + } +} + + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] #[serde(rename_all = "snake_case")] pub enum TerminalLineHeight { @@ -457,6 +501,8 @@ impl Settings { show_call_status_icon: defaults.show_call_status_icon.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), + project_panel_defaults: Default::default(), + project_panel_overrides: Default::default(), editor_defaults: EditorSettings { tab_size: required(defaults.editor.tab_size), hard_tabs: required(defaults.editor.hard_tabs), @@ -750,6 +796,8 @@ impl Settings { show_call_status_icon: true, vim_mode: false, autosave: Autosave::Off, + project_panel_defaults: Default::default(), + project_panel_overrides: Default::default(), editor_defaults: EditorSettings { tab_size: Some(4.try_into().unwrap()), hard_tabs: Some(false), diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index be3c3de9d0..e9cdb5a1ea 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -6,7 +6,7 @@ use gpui::{ use project::Project; use settings::{Settings, WorkingDirectory}; use util::ResultExt; -use workspace::{dock::Panel, pane, DraggedItem, Pane, Workspace}; +use workspace::{dock::{Panel, DockPosition}, pane, DraggedItem, Pane, Workspace}; pub fn init(cx: &mut AppContext) { cx.add_action(TerminalPanel::add_terminal); @@ -135,8 +135,20 @@ impl View for TerminalPanel { } impl Panel for TerminalPanel { - fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { - matches!(event, Event::Close) + fn position(&self, cx: &gpui::WindowContext) -> DockPosition { + cx.global::().terminal_overrides.dock.into() + } + + fn position_is_valid(&self, _: DockPosition) -> bool { + true + } + + fn icon_path(&self) -> &'static str { + "icons/terminal_12.svg" + } + + fn icon_tooltip(&self) -> String { + "Terminals".to_string() } fn icon_label(&self, cx: &AppContext) -> Option { @@ -147,4 +159,16 @@ impl Panel for TerminalPanel { Some(count.to_string()) } } + + fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + todo!() + } + + fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + todo!() + } + + fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { + matches!(event, Event::Close) + } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index a378e00ec3..100b8e8661 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -92,6 +92,16 @@ pub enum DockPosition { Right, } +impl From for DockPosition { + fn from(value: settings::DockPosition) -> Self { + match value { + settings::DockPosition::Left => Self::Left, + settings::DockPosition::Bottom => Self::Bottom, + settings::DockPosition::Right => Self::Right, + } + } +} + impl DockPosition { fn to_resizable_side(self) -> Side { match self { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index ddee068d46..a4e034d741 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -1061,11 +1061,11 @@ pub(crate) mod test { } impl Panel for TestItem { - fn position(&self, cx: &gpui::WindowContext) -> crate::dock::DockPosition { + fn position(&self, _cx: &gpui::WindowContext) -> crate::dock::DockPosition { unimplemented!() } - fn position_is_valid(&self, position: crate::dock::DockPosition) -> bool { + fn position_is_valid(&self, _position: crate::dock::DockPosition) -> bool { unimplemented!() } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 394661da41..8a40a5d1d3 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -311,18 +311,11 @@ pub fn initialize_workspace( cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); - let project_panel = cx.add_view(|cx| ProjectPanel::new(workspace, cx)); - workspace.add_panel(panel, cx); + let project_panel = ProjectPanel::new(workspace, cx); + workspace.add_panel(project_panel, cx); let terminal_panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); - workspace.bottom_dock().update(cx, |dock, cx| { - dock.add_panel( - "icons/terminal_12.svg", - "Terminals".to_string(), - terminal_panel, - cx, - ); - }); + workspace.add_panel(terminal_panel, cx); let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx)); let diagnostic_summary = From 0d78266ddb15772d946922068d557dbfb7e7e5a4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 16:37:10 -0600 Subject: [PATCH 017/131] Replace todo with unimplemented to reduce distractions --- crates/editor/src/items.rs | 12 ++++++------ crates/gpui/src/elements/list.rs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index e971af943a..fc7c1cd968 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1222,27 +1222,27 @@ mod tests { } fn as_local(&self) -> Option<&dyn language::LocalFile> { - todo!() + unimplemented!() } fn mtime(&self) -> SystemTime { - todo!() + unimplemented!() } fn file_name<'a>(&'a self, _: &'a gpui::AppContext) -> &'a std::ffi::OsStr { - todo!() + unimplemented!() } fn is_deleted(&self) -> bool { - todo!() + unimplemented!() } fn as_any(&self) -> &dyn std::any::Any { - todo!() + unimplemented!() } fn to_proto(&self) -> rpc::proto::File { - todo!() + unimplemented!() } } } diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index ca73196c8b..1cf8cc986f 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -990,7 +990,7 @@ mod tests { _: &mut V, _: &mut ViewContext, ) { - todo!() + unimplemented!() } fn rect_for_text_range( @@ -1003,7 +1003,7 @@ mod tests { _: &V, _: &ViewContext, ) -> Option { - todo!() + unimplemented!() } fn debug(&self, _: RectF, _: &(), _: &(), _: &V, _: &ViewContext) -> serde_json::Value { From 6a7feb4c4c0fea7ef5ab9c01a5029f43930519ed Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 17:26:54 -0600 Subject: [PATCH 018/131] Allow the project panel to be docked right or left Co-Authored-By: Joseph Lyons --- assets/settings/default.json | 5 ++++ crates/project_panel/src/project_panel.rs | 20 +++++++++++-- crates/settings/src/settings.rs | 4 ++- crates/terminal_view/src/terminal_panel.rs | 2 +- crates/workspace/src/dock.rs | 35 ++++++++++++---------- crates/workspace/src/item.rs | 2 +- crates/workspace/src/workspace.rs | 22 ++++++++++++-- 7 files changed, 66 insertions(+), 24 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 58fb28bc62..5d85e751d4 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -107,6 +107,9 @@ // Automatically update Zed "auto_update": true, // Git gutter behavior configuration. + "project_panel": { + "dock": "left" + }, "git": { // Control whether the git gutter is shown. May take 2 values: // 1. Show the gutter @@ -149,6 +152,8 @@ // } // } "shell": "system", + // Where to dock terminals panel. Can be 'left', 'right', 'bottom'. + "dock": "bottom", // What working directory to use when launching the terminal. // May take 4 values: // 1. Use the current file's project directory. Will Fallback to the diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index a595ec6225..8ee701c3cc 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -135,12 +135,25 @@ pub enum Event { entry_id: ProjectEntryId, focus_opened_item: bool, }, + DockPositionChanged, } impl ProjectPanel { pub fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> ViewHandle { let project = workspace.project().clone(); let project_panel = cx.add_view(|cx: &mut ViewContext| { + // Update the dock position when the setting changes. + let mut old_dock_position = cx.global::().project_panel_overrides.dock; + dbg!(old_dock_position); + cx.observe_global::(move |_, cx| { + let new_dock_position = cx.global::().project_panel_overrides.dock; + dbg!(new_dock_position); + if new_dock_position != old_dock_position { + old_dock_position = new_dock_position; + cx.emit(Event::DockPositionChanged); + } + }).detach(); + cx.observe(&project, |this, _, cx| { this.update_visible_entries(None, cx); cx.notify(); @@ -242,7 +255,8 @@ impl ProjectPanel { } } } - } + }, + Event::DockPositionChanged => {}, } }) .detach(); @@ -1344,8 +1358,8 @@ impl workspace::dock::Panel for ProjectPanel { "Project Panel".into() } - fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - todo!() + fn should_change_position_on_event(event: &Self::Event) -> bool { + matches!(event, Event::DockPositionChanged) } fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 528812af87..0eb9eb9e5d 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -131,7 +131,7 @@ impl TelemetrySettings { } } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq)] #[serde(rename_all="lowercase")] pub enum DockPosition { Left, @@ -407,6 +407,7 @@ pub struct SettingsFileContent { pub autosave: Option, #[serde(flatten)] pub editor: EditorSettings, + pub project_panel: ProjectPanelSettings, #[serde(default)] pub journal: JournalSettings, #[serde(default)] @@ -609,6 +610,7 @@ impl Settings { } } self.editor_overrides = data.editor; + self.project_panel_overrides = data.project_panel; self.git_overrides = data.git.unwrap_or_default(); self.journal_overrides = data.journal; self.terminal_defaults.font_size = data.terminal.font_size; diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index e9cdb5a1ea..e2bf2c9e32 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -160,7 +160,7 @@ impl Panel for TerminalPanel { } } - fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_change_position_on_event(_: &Self::Event) -> bool { todo!() } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 100b8e8661..0e2d84e626 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -15,7 +15,7 @@ pub trait Panel: View { fn icon_label(&self, _: &AppContext) -> Option { None } - fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; + fn should_change_position_on_event(_: &Self::Event) -> bool; fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; } @@ -80,7 +80,7 @@ pub enum Event { pub struct Dock { position: DockPosition, - items: Vec, + panels: Vec, is_open: bool, active_item_ix: usize, } @@ -112,8 +112,8 @@ impl DockPosition { } } -struct Item { - view: Rc, +struct PanelEntry { + panel: Rc, _subscriptions: [Subscription; 2], } @@ -134,7 +134,7 @@ impl Dock { pub fn new(position: DockPosition) -> Self { Self { position, - items: Default::default(), + panels: Default::default(), active_item_ix: 0, is_open: false, } @@ -161,15 +161,15 @@ impl Dock { cx.notify(); } - pub fn add_panel(&mut self, view: ViewHandle, cx: &mut ViewContext) { + pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) { let subscriptions = [ - cx.observe(&view, |_, _, cx| cx.notify()), - cx.subscribe(&view, |this, view, event, cx| { + cx.observe(&panel, |_, _, cx| cx.notify()), + cx.subscribe(&panel, |this, view, event, cx| { if view.read(cx).should_activate_on_event(event, cx) { if let Some(ix) = this - .items + .panels .iter() - .position(|item| item.view.id() == view.id()) + .position(|item| item.panel.id() == view.id()) { this.activate_item(ix, cx); } @@ -179,13 +179,18 @@ impl Dock { }), ]; - self.items.push(Item { - view: Rc::new(view), + self.panels.push(PanelEntry { + panel: Rc::new(panel), _subscriptions: subscriptions, }); cx.notify() } + pub fn remove_panel(&mut self, panel: &ViewHandle, cx: &mut ViewContext) { + self.panels.retain(|entry| entry.panel.id() != panel.id()); + cx.notify(); + } + pub fn activate_item(&mut self, item_ix: usize, cx: &mut ViewContext) { self.active_item_ix = item_ix; cx.notify(); @@ -202,7 +207,7 @@ impl Dock { pub fn active_item(&self) -> Option<&Rc> { if self.is_open { - self.items.get(self.active_item_ix).map(|item| &item.view) + self.panels.get(self.active_item_ix).map(|item| &item.panel) } else { None } @@ -275,9 +280,9 @@ impl View for PanelButtons { }; let items = dock - .items + .panels .iter() - .map(|item| item.view.clone()) + .map(|item| item.panel.clone()) .collect::>(); Flex::row() .with_children(items.into_iter().enumerate().map(|(ix, view)| { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index a4e034d741..d2b9251a62 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -1077,7 +1077,7 @@ pub(crate) mod test { unimplemented!() } - fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_change_position_on_event(_: &Self::Event) -> bool { unimplemented!() } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index d86b25b746..1708555f50 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -836,10 +836,26 @@ impl Workspace { pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) { let dock = match panel.position(cx) { - DockPosition::Left => &mut self.left_dock, - DockPosition::Bottom => &mut self.bottom_dock, - DockPosition::Right => &mut self.right_dock, + DockPosition::Left => &self.left_dock, + DockPosition::Bottom => &self.bottom_dock, + DockPosition::Right => &self.right_dock, }; + + cx.subscribe(&panel, { + let mut dock = dock.clone(); + move |this, panel, event, cx| { + if T::should_change_position_on_event(event) { + dock.update(cx, |dock, cx| dock.remove_panel(&panel, cx)); + dock = match panel.read(cx).position(cx) { + DockPosition::Left => &this.left_dock, + DockPosition::Bottom => &this.bottom_dock, + DockPosition::Right => &this.right_dock, + }.clone(); + dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); + } + } + }).detach(); + dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); } From ba248244ec94724af99a41c9ec791ac437cb78e4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 17:36:49 -0600 Subject: [PATCH 019/131] Allow terminal to be docked left, bottom, or right Co-Authored-By: Joseph Lyons --- crates/project_panel/src/project_panel.rs | 2 -- crates/terminal_view/src/terminal_panel.rs | 16 +++++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 8ee701c3cc..c22e297a02 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -144,10 +144,8 @@ impl ProjectPanel { let project_panel = cx.add_view(|cx: &mut ViewContext| { // Update the dock position when the setting changes. let mut old_dock_position = cx.global::().project_panel_overrides.dock; - dbg!(old_dock_position); cx.observe_global::(move |_, cx| { let new_dock_position = cx.global::().project_panel_overrides.dock; - dbg!(new_dock_position); if new_dock_position != old_dock_position { old_dock_position = new_dock_position; cx.emit(Event::DockPositionChanged); diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index e2bf2c9e32..58deda9236 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -14,6 +14,7 @@ pub fn init(cx: &mut AppContext) { pub enum Event { Close, + DockPositionChanged, } pub struct TerminalPanel { @@ -25,6 +26,15 @@ pub struct TerminalPanel { impl TerminalPanel { pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + let mut old_dock_position = cx.global::().terminal_overrides.dock; + cx.observe_global::(move |_, cx| { + let new_dock_position = cx.global::().terminal_overrides.dock; + if new_dock_position != old_dock_position { + old_dock_position = new_dock_position; + cx.emit(Event::DockPositionChanged); + } + }).detach(); + let this = cx.weak_handle(); let pane = cx.add_view(|cx| { let window_id = cx.window_id(); @@ -160,12 +170,12 @@ impl Panel for TerminalPanel { } } - fn should_change_position_on_event(_: &Self::Event) -> bool { - todo!() + fn should_change_position_on_event(event: &Self::Event) -> bool { + matches!(event, Event::DockPositionChanged) } fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - todo!() + false } fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { From f554877ff47ccb11a5c9b4fb1971241ab3407f3a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 17:41:24 -0600 Subject: [PATCH 020/131] Render the bottom dock buttons before the right dock buttons --- crates/workspace/src/workspace.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1708555f50..bfc0d8228f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -575,8 +575,8 @@ impl Workspace { let status_bar = cx.add_view(|cx| { let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); status_bar.add_left_item(left_dock_buttons, cx); - status_bar.add_right_item(bottom_dock_buttons, cx); status_bar.add_right_item(right_dock_buttons, cx); + status_bar.add_right_item(bottom_dock_buttons, cx); status_bar }); From 9173916ab800959e0a7a46e463e4f1c801027e9d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 20:24:37 -0600 Subject: [PATCH 021/131] Remove unused terminal_button module --- crates/terminal_view/src/terminal_button.rs | 173 -------------------- crates/terminal_view/src/terminal_view.rs | 1 - 2 files changed, 174 deletions(-) delete mode 100644 crates/terminal_view/src/terminal_button.rs diff --git a/crates/terminal_view/src/terminal_button.rs b/crates/terminal_view/src/terminal_button.rs deleted file mode 100644 index 0747f86d2d..0000000000 --- a/crates/terminal_view/src/terminal_button.rs +++ /dev/null @@ -1,173 +0,0 @@ -use crate::TerminalView; -use context_menu::{ContextMenu, ContextMenuItem}; -use gpui::{ - elements::*, - platform::{CursorStyle, MouseButton}, - AnyElement, Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle, -}; -use settings::Settings; -use std::any::TypeId; -use workspace::{ - item::ItemHandle, - NewTerminal, StatusItemView, Workspace, -}; - -pub struct TerminalButton { - workspace: WeakViewHandle, - popup_menu: ViewHandle, -} - -impl Entity for TerminalButton { - type Event = (); -} - -impl View for TerminalButton { - fn ui_name() -> &'static str { - "TerminalButton" - } - - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let workspace = self.workspace.upgrade(cx); - let project = match workspace { - Some(workspace) => workspace.read(cx).project().read(cx), - None => return Empty::new().into_any(), - }; - - let focused_view = cx.focused_view_id(); - let active = focused_view - .map(|view_id| { - cx.view_type_id(cx.window_id(), view_id) == Some(TypeId::of::()) - }) - .unwrap_or(false); - - let has_terminals = !project.local_terminal_handles().is_empty(); - let terminal_count = project.local_terminal_handles().len() as i32; - let theme = cx.global::().theme.clone(); - - Stack::new() - .with_child( - MouseEventHandler::::new(0, cx, { - let theme = theme.clone(); - move |state, _cx| { - let style = theme - .workspace - .status_bar - .panel_buttons - .button - .style_for(state, active); - - Flex::row() - .with_child( - Svg::new("icons/terminal_12.svg") - .with_color(style.icon_color) - .constrained() - .with_width(style.icon_size) - .aligned() - .into_any_named("terminals-icon"), - ) - .with_children(has_terminals.then(|| { - Label::new(terminal_count.to_string(), style.label.text.clone()) - .contained() - .with_style(style.label.container) - .aligned() - })) - .constrained() - .with_height(style.icon_size) - .contained() - .with_style(style.container) - } - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, this, cx| { - if has_terminals { - this.deploy_terminal_menu(cx); - } else { - if !active { - if let Some(workspace) = this.workspace.upgrade(cx) { - workspace.update(cx, |_workspace, _cx| { - todo!() - }) - } - } - }; - }) - .with_tooltip::( - 0, - "Show Terminal".into(), - None, // TODO! We need a new action here. - theme.tooltip.clone(), - cx, - ), - ) - .with_child(ChildView::new(&self.popup_menu, cx).aligned().top().right()) - .into_any_named("terminal button") - } -} - -impl TerminalButton { - pub fn new(workspace: ViewHandle, cx: &mut ViewContext) -> Self { - let button_view_id = cx.view_id(); - cx.observe(&workspace, |_, _, cx| cx.notify()).detach(); - Self { - workspace: workspace.downgrade(), - popup_menu: cx.add_view(|cx| { - let mut menu = ContextMenu::new(button_view_id, cx); - menu.set_position_mode(OverlayPositionMode::Local); - menu - }), - } - } - - pub fn deploy_terminal_menu(&mut self, cx: &mut ViewContext) { - let mut menu_options = vec![ContextMenuItem::action("New Terminal", NewTerminal)]; - - if let Some(workspace) = self.workspace.upgrade(cx) { - let project = workspace.read(cx).project().read(cx); - let local_terminal_handles = project.local_terminal_handles(); - - if !local_terminal_handles.is_empty() { - menu_options.push(ContextMenuItem::Separator) - } - - for local_terminal_handle in local_terminal_handles { - if let Some(terminal) = local_terminal_handle.upgrade(cx) { - let workspace = self.workspace.clone(); - let local_terminal_handle = local_terminal_handle.clone(); - menu_options.push(ContextMenuItem::handler( - terminal.read(cx).title(), - move |cx| { - if let Some(workspace) = workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - let terminal = workspace - .items_of_type::(cx) - .find(|terminal| { - terminal.read(cx).model().downgrade() - == local_terminal_handle - }); - if let Some(terminal) = terminal { - workspace.activate_item(&terminal, cx); - } - }); - } - }, - )) - } - } - } - - self.popup_menu.update(cx, |menu, cx| { - menu.show( - Default::default(), - AnchorCorner::BottomRight, - menu_options, - cx, - ); - }); - } -} - -impl StatusItemView for TerminalButton { - fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, cx: &mut ViewContext) { - cx.notify(); - } -} diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index b318b572b5..2c33cc8f39 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1,5 +1,4 @@ mod persistence; -pub mod terminal_button; pub mod terminal_element; pub mod terminal_panel; From 6e3ce6332a2af20525dc928137237847dac212d7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 10:43:42 +0200 Subject: [PATCH 022/131] Fix panic when not specifying a `project_panel` setting --- crates/project_panel/src/project_panel.rs | 13 ++++--- crates/settings/src/settings.rs | 43 +++++++++-------------- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index c22e297a02..85694ec629 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -150,7 +150,8 @@ impl ProjectPanel { old_dock_position = new_dock_position; cx.emit(Event::DockPositionChanged); } - }).detach(); + }) + .detach(); cx.observe(&project, |this, _, cx| { this.update_visible_entries(None, cx); @@ -253,8 +254,8 @@ impl ProjectPanel { } } } - }, - Event::DockPositionChanged => {}, + } + Event::DockPositionChanged => {} } }) .detach(); @@ -1341,7 +1342,11 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { - cx.global::().project_panel_overrides.dock.into() + cx.global::() + .project_panel_overrides + .dock + .map(Into::into) + .unwrap_or(DockPosition::Left) } fn position_is_valid(&self, position: DockPosition) -> bool { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 0eb9eb9e5d..a49d84c6a8 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -132,7 +132,7 @@ impl TelemetrySettings { } #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq)] -#[serde(rename_all="lowercase")] +#[serde(rename_all = "lowercase")] pub enum DockPosition { Left, Right, @@ -166,17 +166,9 @@ pub enum GitGutter { pub struct GitGutterConfig {} -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct ProjectPanelSettings { - pub dock: DockPosition -} - -impl Default for ProjectPanelSettings { - fn default() -> Self { - Self { - dock: DockPosition::Left - } - } + pub dock: Option, } #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] @@ -279,23 +271,22 @@ pub struct TerminalSettings { impl Default for TerminalSettings { fn default() -> Self { Self { - shell:Default::default(), - working_directory:Default::default(), - font_size:Default::default(), - font_family:Default::default(), - line_height:Default::default(), - font_features:Default::default(), - env:Default::default(), - blinking:Default::default(), - alternate_scroll:Default::default(), - option_as_meta:Default::default(), - copy_on_select:Default::default(), + shell: Default::default(), + working_directory: Default::default(), + font_size: Default::default(), + font_family: Default::default(), + line_height: Default::default(), + font_features: Default::default(), + env: Default::default(), + blinking: Default::default(), + alternate_scroll: Default::default(), + option_as_meta: Default::default(), + copy_on_select: Default::default(), dock: DockPosition::Bottom, } } } - #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] #[serde(rename_all = "snake_case")] pub enum TerminalLineHeight { @@ -407,7 +398,7 @@ pub struct SettingsFileContent { pub autosave: Option, #[serde(flatten)] pub editor: EditorSettings, - pub project_panel: ProjectPanelSettings, + pub project_panel: Option, #[serde(default)] pub journal: JournalSettings, #[serde(default)] @@ -502,7 +493,7 @@ impl Settings { show_call_status_icon: defaults.show_call_status_icon.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), - project_panel_defaults: Default::default(), + project_panel_defaults: defaults.project_panel.unwrap(), project_panel_overrides: Default::default(), editor_defaults: EditorSettings { tab_size: required(defaults.editor.tab_size), @@ -610,7 +601,7 @@ impl Settings { } } self.editor_overrides = data.editor; - self.project_panel_overrides = data.project_panel; + self.project_panel_overrides = data.project_panel.unwrap_or_default(); self.git_overrides = data.git.unwrap_or_default(); self.journal_overrides = data.journal; self.terminal_defaults.font_size = data.terminal.font_size; From 8fa379bbc5c09c7e4e57b4aa0b59f0dfc35ca8d2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 11:06:37 +0200 Subject: [PATCH 023/131] Maintain panel visibility when changing its position --- crates/workspace/src/dock.rs | 18 ++++++++++++++++-- crates/workspace/src/workspace.rs | 23 +++++++++++++++++++---- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 0e2d84e626..3d5c4b6c38 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -187,8 +187,22 @@ impl Dock { } pub fn remove_panel(&mut self, panel: &ViewHandle, cx: &mut ViewContext) { - self.panels.retain(|entry| entry.panel.id() != panel.id()); - cx.notify(); + if let Some(panel_ix) = self + .panels + .iter() + .position(|item| item.panel.id() == panel.id()) + { + if panel_ix == self.active_item_ix { + self.active_item_ix = 0; + cx.emit(Event::Close); + } + self.panels.remove(panel_ix); + cx.notify(); + } + } + + pub fn panels_len(&self) -> usize { + self.panels.len() } pub fn activate_item(&mut self, item_ix: usize, cx: &mut ViewContext) { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index bfc0d8228f..d147557103 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -845,16 +845,31 @@ impl Workspace { let mut dock = dock.clone(); move |this, panel, event, cx| { if T::should_change_position_on_event(event) { - dock.update(cx, |dock, cx| dock.remove_panel(&panel, cx)); + let mut was_visible = false; + dock.update(cx, |dock, cx| { + was_visible = dock.is_open() + && dock + .active_item() + .map_or(false, |item| item.as_any().is::()); + dock.remove_panel(&panel, cx); + }); dock = match panel.read(cx).position(cx) { DockPosition::Left => &this.left_dock, DockPosition::Bottom => &this.bottom_dock, DockPosition::Right => &this.right_dock, - }.clone(); - dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); + } + .clone(); + dock.update(cx, |dock, cx| { + dock.add_panel(panel, cx); + if was_visible { + dock.set_open(true, cx); + dock.activate_item(dock.panels_len() - 1, cx); + } + }); } } - }).detach(); + }) + .detach(); dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); } From f28eee88b64a69ed436763c856105d29dc7705c0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 14:05:48 +0200 Subject: [PATCH 024/131] Add context menu to change panel position --- crates/project_panel/src/project_panel.rs | 44 +++-- crates/settings/src/settings.rs | 30 +--- crates/terminal_view/src/terminal_panel.rs | 24 ++- crates/workspace/src/dock.rs | 181 +++++++++++++++------ crates/workspace/src/item.rs | 8 + 5 files changed, 190 insertions(+), 97 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 85694ec629..4c75035c83 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -17,7 +17,7 @@ use gpui::{ }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; -use settings::Settings; +use settings::{settings_file::SettingsFile, Settings}; use std::{ cmp::Ordering, collections::{hash_map, HashMap}, @@ -28,7 +28,10 @@ use std::{ }; use theme::ProjectPanelEntry; use unicase::UniCase; -use workspace::{dock::DockPosition, Workspace}; +use workspace::{ + dock::{DockPosition, Panel}, + Workspace, +}; const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX; @@ -142,17 +145,6 @@ impl ProjectPanel { pub fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> ViewHandle { let project = workspace.project().clone(); let project_panel = cx.add_view(|cx: &mut ViewContext| { - // Update the dock position when the setting changes. - let mut old_dock_position = cx.global::().project_panel_overrides.dock; - cx.observe_global::(move |_, cx| { - let new_dock_position = cx.global::().project_panel_overrides.dock; - if new_dock_position != old_dock_position { - old_dock_position = new_dock_position; - cx.emit(Event::DockPositionChanged); - } - }) - .detach(); - cx.observe(&project, |this, _, cx| { this.update_visible_entries(None, cx); cx.notify(); @@ -224,6 +216,18 @@ impl ProjectPanel { workspace: workspace.weak_handle(), }; this.update_visible_entries(None, cx); + + // Update the dock position when the setting changes. + let mut old_dock_position = this.position(cx); + cx.observe_global::(move |this, cx| { + let new_dock_position = this.position(cx); + if new_dock_position != old_dock_position { + old_dock_position = new_dock_position; + cx.emit(Event::DockPositionChanged); + } + }) + .detach(); + this }); @@ -1342,17 +1346,25 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { - cx.global::() + let settings = cx.global::(); + settings .project_panel_overrides .dock - .map(Into::into) - .unwrap_or(DockPosition::Left) + .or(settings.project_panel_defaults.dock) + .unwrap() + .into() } fn position_is_valid(&self, position: DockPosition) -> bool { matches!(position, DockPosition::Left | DockPosition::Right) } + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { + SettingsFile::update(cx, move |settings| { + settings.project_panel.dock = Some(position.into()) + }) + } + fn icon_path(&self) -> &'static str { "icons/folder_tree_16.svg" } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index a49d84c6a8..3bd3786071 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -252,7 +252,7 @@ impl Default for HourFormat { } } -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct TerminalSettings { pub shell: Option, pub working_directory: Option, @@ -265,26 +265,7 @@ pub struct TerminalSettings { pub alternate_scroll: Option, pub option_as_meta: Option, pub copy_on_select: Option, - pub dock: DockPosition, -} - -impl Default for TerminalSettings { - fn default() -> Self { - Self { - shell: Default::default(), - working_directory: Default::default(), - font_size: Default::default(), - font_family: Default::default(), - line_height: Default::default(), - font_features: Default::default(), - env: Default::default(), - blinking: Default::default(), - alternate_scroll: Default::default(), - option_as_meta: Default::default(), - copy_on_select: Default::default(), - dock: DockPosition::Bottom, - } - } + pub dock: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] @@ -398,7 +379,8 @@ pub struct SettingsFileContent { pub autosave: Option, #[serde(flatten)] pub editor: EditorSettings, - pub project_panel: Option, + #[serde(default)] + pub project_panel: ProjectPanelSettings, #[serde(default)] pub journal: JournalSettings, #[serde(default)] @@ -493,7 +475,7 @@ impl Settings { show_call_status_icon: defaults.show_call_status_icon.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), - project_panel_defaults: defaults.project_panel.unwrap(), + project_panel_defaults: defaults.project_panel, project_panel_overrides: Default::default(), editor_defaults: EditorSettings { tab_size: required(defaults.editor.tab_size), @@ -601,7 +583,7 @@ impl Settings { } } self.editor_overrides = data.editor; - self.project_panel_overrides = data.project_panel.unwrap_or_default(); + self.project_panel_overrides = data.project_panel; self.git_overrides = data.git.unwrap_or_default(); self.journal_overrides = data.journal; self.terminal_defaults.font_size = data.terminal.font_size; diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 58deda9236..4e90a8fec4 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -4,9 +4,12 @@ use gpui::{ WeakViewHandle, }; use project::Project; -use settings::{Settings, WorkingDirectory}; +use settings::{settings_file::SettingsFile, Settings, WorkingDirectory}; use util::ResultExt; -use workspace::{dock::{Panel, DockPosition}, pane, DraggedItem, Pane, Workspace}; +use workspace::{ + dock::{DockPosition, Panel}, + pane, DraggedItem, Pane, Workspace, +}; pub fn init(cx: &mut AppContext) { cx.add_action(TerminalPanel::add_terminal); @@ -33,7 +36,8 @@ impl TerminalPanel { old_dock_position = new_dock_position; cx.emit(Event::DockPositionChanged); } - }).detach(); + }) + .detach(); let this = cx.weak_handle(); let pane = cx.add_view(|cx| { @@ -146,13 +150,25 @@ impl View for TerminalPanel { impl Panel for TerminalPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { - cx.global::().terminal_overrides.dock.into() + let settings = cx.global::(); + settings + .terminal_overrides + .dock + .or(settings.terminal_defaults.dock) + .unwrap() + .into() } fn position_is_valid(&self, _: DockPosition) -> bool { true } + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { + SettingsFile::update(cx, move |settings| { + settings.terminal.dock = Some(position.into()); + }); + } + fn icon_path(&self) -> &'static str { "icons/terminal_12.svg" } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 3d5c4b6c38..6f7f6b1c12 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,4 +1,5 @@ use crate::{StatusItemView, Workspace}; +use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, @@ -10,6 +11,7 @@ use std::rc::Rc; pub trait Panel: View { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; fn icon_label(&self, _: &AppContext) -> Option { @@ -24,6 +26,7 @@ pub trait PanelHandle { fn id(&self) -> usize; fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; + fn set_position(&self, position: DockPosition, cx: &mut WindowContext); fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -47,6 +50,10 @@ where self.read(cx).position_is_valid(position) } + fn set_position(&self, position: DockPosition, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.set_position(position, cx)) + } + fn icon_path(&self, cx: &WindowContext) -> &'static str { self.read(cx).icon_path() } @@ -102,7 +109,25 @@ impl From for DockPosition { } } +impl From for settings::DockPosition { + fn from(value: DockPosition) -> settings::DockPosition { + match value { + DockPosition::Left => settings::DockPosition::Left, + DockPosition::Bottom => settings::DockPosition::Bottom, + DockPosition::Right => settings::DockPosition::Right, + } + } +} + impl DockPosition { + fn to_label(&self) -> &'static str { + match self { + Self::Left => "left", + Self::Bottom => "bottom", + Self::Right => "right", + } + } + fn to_resizable_side(self) -> Side { match self { Self::Left => Side::Right, @@ -114,6 +139,7 @@ impl DockPosition { struct PanelEntry { panel: Rc, + context_menu: ViewHandle, _subscriptions: [Subscription; 2], } @@ -179,8 +205,14 @@ impl Dock { }), ]; + let dock_view_id = cx.view_id(); self.panels.push(PanelEntry { panel: Rc::new(panel), + context_menu: cx.add_view(|cx| { + let mut menu = ContextMenu::new(dock_view_id, cx); + menu.set_position_mode(OverlayPositionMode::Local); + menu + }), _subscriptions: subscriptions, }); cx.notify() @@ -292,66 +324,109 @@ impl View for PanelButtons { DockPosition::Bottom => theme.group_bottom, DockPosition::Right => theme.group_right, }; + let menu_corner = match dock_position { + DockPosition::Left => AnchorCorner::BottomLeft, + DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight, + }; let items = dock .panels .iter() - .map(|item| item.panel.clone()) + .map(|item| (item.panel.clone(), item.context_menu.clone())) .collect::>(); Flex::row() - .with_children(items.into_iter().enumerate().map(|(ix, view)| { - let action = TogglePanel { - dock_position, - item_index: ix, - }; - MouseEventHandler::::new(ix, cx, |state, cx| { - let is_active = is_open && ix == active_ix; - let style = item_style.style_for(state, is_active); - Flex::row() - .with_child( - Svg::new(view.icon_path(cx)) - .with_color(style.icon_color) - .constrained() - .with_width(style.icon_size) - .aligned(), - ) - .with_children(if let Some(label) = view.icon_label(cx) { - Some( - Label::new(label, style.label.text.clone()) - .contained() - .with_style(style.label.container) - .aligned(), + .with_children( + items + .into_iter() + .enumerate() + .map(|(ix, (view, context_menu))| { + let action = TogglePanel { + dock_position, + item_index: ix, + }; + + Stack::new() + .with_child( + MouseEventHandler::::new(ix, cx, |state, cx| { + let is_active = is_open && ix == active_ix; + let style = item_style.style_for(state, is_active); + Flex::row() + .with_child( + Svg::new(view.icon_path(cx)) + .with_color(style.icon_color) + .constrained() + .with_width(style.icon_size) + .aligned(), + ) + .with_children(if let Some(label) = view.icon_label(cx) { + Some( + Label::new(label, style.label.text.clone()) + .contained() + .with_style(style.label.container) + .aligned(), + ) + } else { + None + }) + .constrained() + .with_height(style.icon_size) + .contained() + .with_style(style.container) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, { + let action = action.clone(); + move |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + let action = action.clone(); + cx.window_context().defer(move |cx| { + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel(&action, cx) + }); + }); + } + } + }) + .on_click(MouseButton::Right, { + let view = view.clone(); + let menu = context_menu.clone(); + move |_, _, cx| { + const POSITIONS: [DockPosition; 3] = [ + DockPosition::Left, + DockPosition::Right, + DockPosition::Bottom, + ]; + + menu.update(cx, |menu, cx| { + let items = POSITIONS + .into_iter() + .filter(|position| { + *position != dock_position + && view.position_is_valid(*position, cx) + }) + .map(|position| { + let view = view.clone(); + ContextMenuItem::handler( + format!("Dock {}", position.to_label()), + move |cx| view.set_position(position, cx), + ) + }) + .collect(); + menu.show(Default::default(), menu_corner, items, cx); + }) + } + }) + .with_tooltip::( + ix, + view.icon_tooltip(cx), + Some(Box::new(action)), + tooltip_style.clone(), + cx, + ), ) - } else { - None - }) - .constrained() - .with_height(style.icon_size) - .contained() - .with_style(style.container) - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, { - let action = action.clone(); - move |_, this, cx| { - if let Some(workspace) = this.workspace.upgrade(cx) { - let action = action.clone(); - cx.window_context().defer(move |cx| { - workspace.update(cx, |workspace, cx| { - workspace.toggle_panel(&action, cx) - }); - }); - } - } - }) - .with_tooltip::( - ix, - view.icon_tooltip(cx), - Some(Box::new(action)), - tooltip_style.clone(), - cx, - ) - })) + .with_child(ChildView::new(&context_menu, cx)) + }), + ) .contained() .with_style(group_style) .into_any() diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index d2b9251a62..dfad53c3a3 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -1069,6 +1069,14 @@ pub(crate) mod test { unimplemented!() } + fn set_position( + &mut self, + _position: crate::dock::DockPosition, + _cx: &mut ViewContext, + ) { + unimplemented!() + } + fn icon_path(&self) -> &'static str { unimplemented!() } From 9b9d53fcf8247fea9fca3323c301dd5667a2bd20 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 15:40:46 +0200 Subject: [PATCH 025/131] Focus the root view if the previously-focused view isn't rendered --- crates/gpui/src/app.rs | 57 +++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index d5a7a2f3ab..488f55d0c6 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1451,27 +1451,13 @@ impl AppContext { self.views_metadata.remove(&(window_id, view_id)); let mut view = self.views.remove(&(window_id, view_id)).unwrap(); view.release(self); - let change_focus_to = self.windows.get_mut(&window_id).and_then(|window| { + if let Some(window) = self.windows.get_mut(&window_id) { window.parents.remove(&view_id); window .invalidation .get_or_insert_with(Default::default) .removed .push(view_id); - if window.focused_view_id == Some(view_id) { - Some(window.root_view().id()) - } else { - None - } - }); - - if let Some(view_id) = change_focus_to { - self.pending_effects - .push_back(Effect::Focus(FocusEffect::View { - window_id, - view_id: Some(view_id), - is_forced: false, - })); } self.pending_effects @@ -1710,6 +1696,25 @@ impl AppContext { cx.invalidate(invalidation, appearance); if cx.layout(refreshing).log_err().is_some() { updated_windows.insert(window_id); + + // When the previously-focused view isn't rendered and + // there isn't any pending focus, focus the root view. + if let Some(focused_view_id) = cx.focused_view_id() { + let root_view_id = cx.window.root_view().id(); + if focused_view_id != root_view_id + && !cx.window.parents.contains_key(&focused_view_id) + && !focus_effects.contains_key(&window_id) + { + focus_effects.insert( + window_id, + FocusEffect::View { + window_id, + view_id: Some(root_view_id), + is_forced: false, + }, + ); + } + } } } }); @@ -1886,9 +1891,27 @@ impl AppContext { fn handle_focus_effect(&mut self, effect: FocusEffect) { let window_id = effect.window_id(); self.update_window(window_id, |cx| { + // Ensure the newly-focused view has been rendered, otherwise focus + // the root view instead. let focused_id = match effect { - FocusEffect::View { view_id, .. } => view_id, - FocusEffect::ViewParent { view_id, .. } => cx.ancestors(view_id).skip(1).next(), + FocusEffect::View { view_id, .. } => { + if let Some(view_id) = view_id { + if cx.window.parents.contains_key(&view_id) { + Some(view_id) + } else { + Some(cx.root_view().id()) + } + } else { + None + } + } + FocusEffect::ViewParent { view_id, .. } => Some( + cx.window + .parents + .get(&view_id) + .copied() + .unwrap_or(cx.root_view().id()), + ), }; let focus_changed = cx.window.focused_view_id != focused_id; From e507eadb4b08d284d04a906ddd80517193104ceb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 15:43:44 +0200 Subject: [PATCH 026/131] Rely on root getting re-focused when panel isn't rendered This achieves two things: - When a panel like `TerminalPanel` closes its last tab, the focus is returned to the root view (the `Workspace`) - When a panel is moved from a dock to another, it will get rendered in another spot but the focus will be kept on the panel This also ensures the focus is not lost when a panel is moved from a dock to another because that view --- crates/workspace/src/dock.rs | 11 +++------- crates/workspace/src/workspace.rs | 35 +++++++++++-------------------- 2 files changed, 15 insertions(+), 31 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 6f7f6b1c12..0948140e8e 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -81,10 +81,6 @@ impl From<&dyn PanelHandle> for AnyViewHandle { } } -pub enum Event { - Close, -} - pub struct Dock { position: DockPosition, panels: Vec, @@ -182,7 +178,6 @@ impl Dock { } pub fn toggle_open(&mut self, cx: &mut ViewContext) { - if self.is_open {} self.is_open = !self.is_open; cx.notify(); } @@ -200,7 +195,7 @@ impl Dock { this.activate_item(ix, cx); } } else if view.read(cx).should_close_on_event(event, cx) { - cx.emit(Event::Close); + this.set_open(false, cx); } }), ]; @@ -226,7 +221,7 @@ impl Dock { { if panel_ix == self.active_item_ix { self.active_item_ix = 0; - cx.emit(Event::Close); + self.set_open(false, cx); } self.panels.remove(panel_ix); cx.notify(); @@ -261,7 +256,7 @@ impl Dock { } impl Entity for Dock { - type Event = Event; + type Event = (); } impl View for Dock { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index d147557103..497b28cff1 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -612,9 +612,18 @@ impl Workspace { .spawn(DB.set_window_bounds(workspace_id, bounds, display)) .detach_and_log_err(cx); }), - Self::register_dock(&left_dock, cx), - Self::register_dock(&bottom_dock, cx), - Self::register_dock(&right_dock, cx), + cx.observe(&left_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), + cx.observe(&bottom_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), + cx.observe(&right_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), ]; let mut this = Workspace { @@ -1359,26 +1368,6 @@ impl Workspace { } } - fn register_dock(dock: &ViewHandle, cx: &mut ViewContext) -> Subscription { - cx.subscribe(dock, Self::handle_dock_event) - } - - fn handle_dock_event( - &mut self, - dock: ViewHandle, - event: &dock::Event, - cx: &mut ViewContext, - ) { - match event { - dock::Event::Close => { - dock.update(cx, |dock, cx| dock.set_open(false, cx)); - self.serialize_workspace(cx); - cx.focus_self(); - cx.notify(); - } - } - } - pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { let dock = match dock_side { DockPosition::Left => &mut self.left_dock, From 0ccb4a50e6b952ccf2b25e506521fc6c028889f2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 16:52:10 +0200 Subject: [PATCH 027/131] Create more specific dock position settings associated with each panel --- crates/project_panel/src/project_panel.rs | 17 ++++++++++---- crates/settings/src/settings.rs | 27 ++++++++++++++-------- crates/terminal_view/src/terminal_panel.rs | 18 +++++++++++---- crates/workspace/src/dock.rs | 20 ---------------- 4 files changed, 44 insertions(+), 38 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 4c75035c83..14779f9b6f 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1347,12 +1347,15 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { let settings = cx.global::(); - settings + let dock = settings .project_panel_overrides .dock .or(settings.project_panel_defaults.dock) - .unwrap() - .into() + .unwrap(); + match dock { + settings::ProjectPanelDockPosition::Left => DockPosition::Left, + settings::ProjectPanelDockPosition::Right => DockPosition::Right, + } } fn position_is_valid(&self, position: DockPosition) -> bool { @@ -1361,7 +1364,13 @@ impl workspace::dock::Panel for ProjectPanel { fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { SettingsFile::update(cx, move |settings| { - settings.project_panel.dock = Some(position.into()) + let dock = match position { + DockPosition::Left | DockPosition::Bottom => { + settings::ProjectPanelDockPosition::Left + } + DockPosition::Right => settings::ProjectPanelDockPosition::Right, + }; + settings.project_panel.dock = Some(dock); }) } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 3bd3786071..67acd12939 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -131,14 +131,6 @@ impl TelemetrySettings { } } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum DockPosition { - Left, - Right, - Bottom, -} - #[derive(Clone, Debug, Default)] pub struct CopilotSettings { pub disabled_globs: Vec, @@ -168,7 +160,14 @@ pub struct GitGutterConfig {} #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct ProjectPanelSettings { - pub dock: Option, + pub dock: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum ProjectPanelDockPosition { + Left, + Right, } #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] @@ -265,7 +264,15 @@ pub struct TerminalSettings { pub alternate_scroll: Option, pub option_as_meta: Option, pub copy_on_select: Option, - pub dock: Option, + pub dock: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum TerminalDockPosition { + Left, + Bottom, + Right, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 4e90a8fec4..5d4099c446 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -4,7 +4,7 @@ use gpui::{ WeakViewHandle, }; use project::Project; -use settings::{settings_file::SettingsFile, Settings, WorkingDirectory}; +use settings::{settings_file::SettingsFile, Settings, TerminalDockPosition, WorkingDirectory}; use util::ResultExt; use workspace::{ dock::{DockPosition, Panel}, @@ -151,12 +151,17 @@ impl View for TerminalPanel { impl Panel for TerminalPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { let settings = cx.global::(); - settings + let dock = settings .terminal_overrides .dock .or(settings.terminal_defaults.dock) .unwrap() - .into() + .into(); + match dock { + settings::TerminalDockPosition::Left => DockPosition::Left, + settings::TerminalDockPosition::Bottom => DockPosition::Bottom, + settings::TerminalDockPosition::Right => DockPosition::Right, + } } fn position_is_valid(&self, _: DockPosition) -> bool { @@ -165,7 +170,12 @@ impl Panel for TerminalPanel { fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { SettingsFile::update(cx, move |settings| { - settings.terminal.dock = Some(position.into()); + let dock = match position { + DockPosition::Left => TerminalDockPosition::Left, + DockPosition::Bottom => TerminalDockPosition::Bottom, + DockPosition::Right => TerminalDockPosition::Right, + }; + settings.terminal.dock = Some(dock); }); } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 0948140e8e..7413209874 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -95,26 +95,6 @@ pub enum DockPosition { Right, } -impl From for DockPosition { - fn from(value: settings::DockPosition) -> Self { - match value { - settings::DockPosition::Left => Self::Left, - settings::DockPosition::Bottom => Self::Bottom, - settings::DockPosition::Right => Self::Right, - } - } -} - -impl From for settings::DockPosition { - fn from(value: DockPosition) -> settings::DockPosition { - match value { - DockPosition::Left => settings::DockPosition::Left, - DockPosition::Bottom => settings::DockPosition::Bottom, - DockPosition::Right => settings::DockPosition::Right, - } - } -} - impl DockPosition { fn to_label(&self) -> &'static str { match self { From cc21421ea86914e036ffef810dfa62bfc566c940 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 10 May 2023 09:54:20 -0600 Subject: [PATCH 028/131] Rename item to panel in various locations --- crates/workspace/src/dock.rs | 66 +++++++++++++++---------------- crates/workspace/src/workspace.rs | 20 +++++----- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 7413209874..82d8d40425 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -83,9 +83,9 @@ impl From<&dyn PanelHandle> for AnyViewHandle { pub struct Dock { position: DockPosition, - panels: Vec, + panel_entries: Vec, is_open: bool, - active_item_ix: usize, + active_panel_index: usize, } #[derive(Clone, Copy, Debug, Deserialize, PartialEq)] @@ -127,7 +127,7 @@ pub struct PanelButtons { #[derive(Clone, Debug, Deserialize, PartialEq)] pub struct TogglePanel { pub dock_position: DockPosition, - pub item_index: usize, + pub panel_index: usize, } impl_actions!(workspace, [TogglePanel]); @@ -136,8 +136,8 @@ impl Dock { pub fn new(position: DockPosition) -> Self { Self { position, - panels: Default::default(), - active_item_ix: 0, + panel_entries: Default::default(), + active_panel_index: 0, is_open: false, } } @@ -146,8 +146,8 @@ impl Dock { self.is_open } - pub fn active_item_ix(&self) -> usize { - self.active_item_ix + pub fn active_panel_index(&self) -> usize { + self.active_panel_index } pub fn set_open(&mut self, open: bool, cx: &mut ViewContext) { @@ -168,11 +168,11 @@ impl Dock { cx.subscribe(&panel, |this, view, event, cx| { if view.read(cx).should_activate_on_event(event, cx) { if let Some(ix) = this - .panels + .panel_entries .iter() - .position(|item| item.panel.id() == view.id()) + .position(|entry| entry.panel.id() == view.id()) { - this.activate_item(ix, cx); + this.activate_panel(ix, cx); } } else if view.read(cx).should_close_on_event(event, cx) { this.set_open(false, cx); @@ -181,7 +181,7 @@ impl Dock { ]; let dock_view_id = cx.view_id(); - self.panels.push(PanelEntry { + self.panel_entries.push(PanelEntry { panel: Rc::new(panel), context_menu: cx.add_view(|cx| { let mut menu = ContextMenu::new(dock_view_id, cx); @@ -195,40 +195,40 @@ impl Dock { pub fn remove_panel(&mut self, panel: &ViewHandle, cx: &mut ViewContext) { if let Some(panel_ix) = self - .panels + .panel_entries .iter() - .position(|item| item.panel.id() == panel.id()) + .position(|entry| entry.panel.id() == panel.id()) { - if panel_ix == self.active_item_ix { - self.active_item_ix = 0; + if panel_ix == self.active_panel_index { + self.active_panel_index = 0; self.set_open(false, cx); } - self.panels.remove(panel_ix); + self.panel_entries.remove(panel_ix); cx.notify(); } } pub fn panels_len(&self) -> usize { - self.panels.len() + self.panel_entries.len() } - pub fn activate_item(&mut self, item_ix: usize, cx: &mut ViewContext) { - self.active_item_ix = item_ix; + pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { + self.active_panel_index = panel_ix; cx.notify(); } - pub fn toggle_item(&mut self, item_ix: usize, cx: &mut ViewContext) { - if self.active_item_ix == item_ix { + pub fn toggle_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { + if self.active_panel_index == panel_ix { self.is_open = false; } else { - self.active_item_ix = item_ix; + self.active_panel_index = panel_ix; } cx.notify(); } - pub fn active_item(&self) -> Option<&Rc> { + pub fn active_panel(&self) -> Option<&Rc> { if self.is_open { - self.panels.get(self.active_item_ix).map(|item| &item.panel) + self.panel_entries.get(self.active_panel_index).map(|entry| &entry.panel) } else { None } @@ -245,10 +245,10 @@ impl View for Dock { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - if let Some(active_item) = self.active_item() { + if let Some(active_panel) = self.active_panel() { enum ResizeHandleTag {} let style = &cx.global::().theme.workspace.dock; - ChildView::new(active_item.as_any(), cx) + ChildView::new(active_panel.as_any(), cx) .contained() .with_style(style.container) .with_resize_handle::( @@ -289,9 +289,9 @@ impl View for PanelButtons { let theme = &cx.global::().theme; let tooltip_style = theme.tooltip.clone(); let theme = &theme.workspace.status_bar.panel_buttons; - let item_style = theme.button.clone(); + let button_style = theme.button.clone(); let dock = self.dock.read(cx); - let active_ix = dock.active_item_ix; + let active_ix = dock.active_panel_index; let is_open = dock.is_open; let dock_position = dock.position; let group_style = match dock_position { @@ -304,27 +304,27 @@ impl View for PanelButtons { DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight, }; - let items = dock - .panels + let panels = dock + .panel_entries .iter() .map(|item| (item.panel.clone(), item.context_menu.clone())) .collect::>(); Flex::row() .with_children( - items + panels .into_iter() .enumerate() .map(|(ix, (view, context_menu))| { let action = TogglePanel { dock_position, - item_index: ix, + panel_index: ix, }; Stack::new() .with_child( MouseEventHandler::::new(ix, cx, |state, cx| { let is_active = is_open && ix == active_ix; - let style = item_style.style_for(state, is_active); + let style = button_style.style_for(state, is_active); Flex::row() .with_child( Svg::new(view.icon_path(cx)) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 497b28cff1..cf89ed43c7 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -858,7 +858,7 @@ impl Workspace { dock.update(cx, |dock, cx| { was_visible = dock.is_open() && dock - .active_item() + .active_panel() .map_or(false, |item| item.as_any().is::()); dock.remove_panel(&panel, cx); }); @@ -872,7 +872,7 @@ impl Workspace { dock.add_panel(panel, cx); if was_visible { dock.set_open(true, cx); - dock.activate_item(dock.panels_len() - 1, cx); + dock.activate_panel(dock.panels_len() - 1, cx); } }); } @@ -1392,13 +1392,13 @@ impl Workspace { DockPosition::Right => &mut self.right_dock, }; let active_item = dock.update(cx, move |dock, cx| { - if dock.is_open() && dock.active_item_ix() == action.item_index { + if dock.is_open() && dock.active_panel_index() == action.panel_index { dock.set_open(false, cx); None } else { dock.set_open(true, cx); - dock.activate_item(action.item_index, cx); - dock.active_item().cloned() + dock.activate_panel(action.panel_index, cx); + dock.active_panel().cloned() } }); @@ -1430,8 +1430,8 @@ impl Workspace { }; let active_item = dock.update(cx, |dock, cx| { dock.set_open(true, cx); - dock.activate_item(panel_index, cx); - dock.active_item().cloned() + dock.activate_panel(panel_index, cx); + dock.active_panel().cloned() }); if let Some(active_item) = active_item { if active_item.is_focused(cx) { @@ -2655,7 +2655,7 @@ impl View for Workspace { let project = self.project.clone(); Flex::row() .with_children( - if self.left_dock.read(cx).active_item().is_some() { + if self.left_dock.read(cx).active_panel().is_some() { Some( ChildView::new(&self.left_dock, cx) .constrained() @@ -2688,7 +2688,7 @@ impl View for Workspace { .flex(1., true), ) .with_children( - if self.bottom_dock.read(cx).active_item().is_some() + if self.bottom_dock.read(cx).active_panel().is_some() { Some(ChildView::new(&self.bottom_dock, cx)) } else { @@ -2698,7 +2698,7 @@ impl View for Workspace { .flex(1., true), ) .with_children( - if self.right_dock.read(cx).active_item().is_some() { + if self.right_dock.read(cx).active_panel().is_some() { Some( ChildView::new(&self.right_dock, cx) .constrained() From 55496693166f9b17a4371a1497bcfeb01a91dbfd Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 10 May 2023 10:41:03 -0600 Subject: [PATCH 029/131] Test moving panels Co-Authored-By: Julia Risley --- crates/welcome/src/welcome.rs | 2 +- crates/workspace/src/dock.rs | 71 +++++++++++++++- crates/workspace/src/persistence.rs | 6 +- crates/workspace/src/persistence/model.rs | 4 +- crates/workspace/src/workspace.rs | 98 ++++++++++++++++++++++- crates/zed/src/main.rs | 3 +- 6 files changed, 169 insertions(+), 15 deletions(-) diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 6adcf27bd1..d78b230dd9 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -10,7 +10,7 @@ use gpui::{ use settings::{settings_file::SettingsFile, Settings}; use workspace::{ - item::Item, open_new, dock::DockPosition, AppState, PaneBackdrop, Welcome, Workspace, + dock::DockPosition, item::Item, open_new, AppState, PaneBackdrop, Welcome, Workspace, WorkspaceId, }; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 82d8d40425..5320a4c652 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -202,6 +202,8 @@ impl Dock { if panel_ix == self.active_panel_index { self.active_panel_index = 0; self.set_open(false, cx); + } else if panel_ix < self.active_panel_index { + self.active_panel_index -= 1; } self.panel_entries.remove(panel_ix); cx.notify(); @@ -228,7 +230,9 @@ impl Dock { pub fn active_panel(&self) -> Option<&Rc> { if self.is_open { - self.panel_entries.get(self.active_panel_index).map(|entry| &entry.panel) + self.panel_entries + .get(self.active_panel_index) + .map(|entry| &entry.panel) } else { None } @@ -416,3 +420,68 @@ impl StatusItemView for PanelButtons { ) { } } + +#[cfg(test)] +pub(crate) mod test { + use super::*; + use gpui::Entity; + + pub enum TestPanelEvent { + PositionChanged, + Activated, + Closed, + } + + pub struct TestPanel { + pub position: DockPosition, + } + + impl Entity for TestPanel { + type Event = TestPanelEvent; + } + + impl View for TestPanel { + fn ui_name() -> &'static str { + "TestPanel" + } + + fn render(&mut self, _: &mut ViewContext<'_, '_, Self>) -> AnyElement { + Empty::new().into_any() + } + } + + impl Panel for TestPanel { + fn position(&self, _: &gpui::WindowContext) -> super::DockPosition { + self.position + } + + fn position_is_valid(&self, _: super::DockPosition) -> bool { + true + } + + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { + self.position = position; + cx.emit(TestPanelEvent::PositionChanged); + } + + fn icon_path(&self) -> &'static str { + "icons/test_panel.svg" + } + + fn icon_tooltip(&self) -> String { + "Test Panel".into() + } + + fn should_change_position_on_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::PositionChanged) + } + + fn should_activate_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool { + matches!(event, TestPanelEvent::Activated) + } + + fn should_close_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool { + matches!(event, TestPanelEvent::Closed) + } + } +} diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 3b7d0ecb87..2ea99581d2 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -214,10 +214,7 @@ impl WorkspaceDb { workspace_location = ?2, left_sidebar_open = ?3, timestamp = CURRENT_TIMESTAMP - ))?(( - workspace.id, - &workspace.location, - )) + ))?((workspace.id, &workspace.location)) .context("Updating workspace")?; // Save center pane group @@ -454,7 +451,6 @@ impl WorkspaceDb { #[cfg(test)] mod tests { - use db::open_test_db; use super::*; diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index f946ebf9de..19da782fb6 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -1,6 +1,4 @@ -use crate::{ - ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, -}; +use crate::{ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId}; use anyhow::{anyhow, Context, Result}; use async_recursion::async_recursion; use db::sqlez::{ diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index cf89ed43c7..791d0d0a44 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -859,7 +859,7 @@ impl Workspace { was_visible = dock.is_open() && dock .active_panel() - .map_or(false, |item| item.as_any().is::()); + .map_or(false, |active_panel| active_panel.id() == panel.id()); dock.remove_panel(&panel, cx); }); dock = match panel.read(cx).position(cx) { @@ -2688,7 +2688,11 @@ impl View for Workspace { .flex(1., true), ) .with_children( - if self.bottom_dock.read(cx).active_panel().is_some() + if self + .bottom_dock + .read(cx) + .active_panel() + .is_some() { Some(ChildView::new(&self.bottom_dock, cx)) } else { @@ -3074,7 +3078,10 @@ fn parse_pixel_position_env_var(value: &str) -> Option { mod tests { use std::{cell::RefCell, rc::Rc}; - use crate::item::test::{TestItem, TestItemEvent, TestProjectItem}; + use crate::{ + dock::test::TestPanel, + item::test::{TestItem, TestItemEvent, TestProjectItem}, + }; use super::*; use fs::FakeFs; @@ -3644,4 +3651,89 @@ mod tests { assert!(pane.can_navigate_forward()); }); } + + #[gpui::test] + async fn test_panels(deterministic: Arc, cx: &mut gpui::TestAppContext) { + deterministic.forbid_parking(); + Settings::test_async(cx); + let fs = FakeFs::new(cx.background()); + + let project = Project::test(fs, [], cx).await; + let (_window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + + let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { + // Add panel_1 on the left, panel_2 on the right. + let panel_1 = cx.add_view(|_| TestPanel { + position: DockPosition::Left, + }); + workspace.add_panel(panel_1.clone(), cx); + workspace + .left_dock() + .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); + let panel_2 = cx.add_view(|_| TestPanel { + position: DockPosition::Right, + }); + workspace.add_panel(panel_2.clone(), cx); + workspace + .right_dock() + .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + + assert_eq!( + workspace.left_dock().read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + assert_eq!( + workspace.right_dock().read(cx).active_panel().unwrap().id(), + panel_2.id() + ); + + (panel_1, panel_2) + }); + + // Move panel_1 to the right + panel_1.update(cx, |panel_1, cx| { + panel_1.set_position(DockPosition::Right, cx) + }); + + workspace.update(cx, |workspace, cx| { + // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right. + // Since it was the only panel on the left, the left dock should now be closed. + assert!(!workspace.left_dock().read(cx).is_open()); + assert!(workspace.left_dock().read(cx).active_panel().is_none()); + assert_eq!( + workspace.right_dock().read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + + // Now we move panel_2 to the left + panel_2.set_position(DockPosition::Left, cx); + }); + + workspace.update(cx, |workspace, cx| { + // Since panel_2 was not visible on the right, we don't open the left dock. + assert!(!workspace.left_dock().read(cx).is_open()); + // And the right dock is unaffected in it's displaying of panel_1 + assert!(workspace.right_dock().read(cx).is_open()); + assert_eq!( + workspace.right_dock().read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + }); + + // Move panel_1 back to the left + panel_1.update(cx, |panel_1, cx| { + panel_1.set_position(DockPosition::Left, cx) + }); + + workspace.update(cx, |workspace, cx| { + // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. + assert!(workspace.left_dock().read(cx).is_open()); + assert_eq!( + workspace.left_dock().read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + // And right the dock should be closed as it no longer has any panels. + assert!(!workspace.right_dock().read(cx).is_open()); + }); + } } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index ff969a8c75..362b714a92 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -52,8 +52,7 @@ use staff_mode::StaffMode; use theme::ThemeRegistry; use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use workspace::{ - item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, - Workspace, + item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, Workspace, }; use zed::{self, build_window_options, initialize_workspace, languages, menus}; From 214354b4da5aa7ac3595dde4cd9d374b66c38f34 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 11 May 2023 23:25:05 -0600 Subject: [PATCH 030/131] Make panels independently resizable --- assets/settings/default.json | 3 +- crates/gpui/src/elements.rs | 20 ++-- crates/gpui/src/elements/resizable.rs | 124 +++++++++------------ crates/project_panel/src/project_panel.rs | 13 ++- crates/settings/src/settings.rs | 31 ++++-- crates/terminal_view/src/terminal_panel.rs | 8 ++ crates/workspace/src/dock.rs | 50 +++++++-- crates/workspace/src/item.rs | 40 +------ 8 files changed, 135 insertions(+), 154 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 5d85e751d4..d1a6499655 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -108,7 +108,8 @@ "auto_update": true, // Git gutter behavior configuration. "project_panel": { - "dock": "left" + "dock": "left", + "default_width": 240 }, "git": { // Control whether the git gutter is shown. May take 2 values: diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index e2c4af143c..3caa91a3b8 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -187,25 +187,21 @@ pub trait Element: 'static { Tooltip::new::(id, text, action, style, self.into_any(), cx) } - fn with_resize_handle( + fn resizable( self, - element_id: usize, - side: Side, - handle_size: f32, - initial_size: f32, - cx: &mut ViewContext, + side: HandleSide, + size: f32, + on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext), ) -> Resizable where Self: 'static + Sized, { - Resizable::new::( + Resizable::new( self.into_any(), - element_id, side, - handle_size, - initial_size, - cx, - ) + size, + on_resize + ) } } diff --git a/crates/gpui/src/elements/resizable.rs b/crates/gpui/src/elements/resizable.rs index 0e78cc07fb..a3d95893f4 100644 --- a/crates/gpui/src/elements/resizable.rs +++ b/crates/gpui/src/elements/resizable.rs @@ -1,4 +1,4 @@ -use std::{cell::Cell, rc::Rc}; +use std::{cell::RefCell, rc::Rc}; use pathfinder_geometry::vector::{vec2f, Vector2F}; use serde_json::json; @@ -7,25 +7,23 @@ use crate::{ geometry::rect::RectF, platform::{CursorStyle, MouseButton}, scene::MouseDrag, - AnyElement, Axis, Element, ElementStateHandle, LayoutContext, MouseRegion, SceneBuilder, View, - ViewContext, + AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, View, + ViewContext, SizeConstraint, }; -use super::{ConstrainedBox, Hook}; - #[derive(Copy, Clone, Debug)] -pub enum Side { +pub enum HandleSide { Top, Bottom, Left, Right, } -impl Side { +impl HandleSide { fn axis(&self) -> Axis { match self { - Side::Left | Side::Right => Axis::Horizontal, - Side::Top | Side::Bottom => Axis::Vertical, + HandleSide::Left | HandleSide::Right => Axis::Horizontal, + HandleSide::Top | HandleSide::Bottom => Axis::Vertical, } } @@ -33,8 +31,8 @@ impl Side { /// then top-to-bottom fn before_content(self) -> bool { match self { - Side::Left | Side::Top => true, - Side::Right | Side::Bottom => false, + HandleSide::Left | HandleSide::Top => true, + HandleSide::Right | HandleSide::Bottom => false, } } @@ -55,14 +53,14 @@ impl Side { fn of_rect(&self, bounds: RectF, handle_size: f32) -> RectF { match self { - Side::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)), - Side::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())), - Side::Bottom => { + HandleSide::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)), + HandleSide::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())), + HandleSide::Bottom => { let mut origin = bounds.lower_left(); origin.set_y(origin.y() - handle_size); RectF::new(origin, vec2f(bounds.width(), handle_size)) } - Side::Right => { + HandleSide::Right => { let mut origin = bounds.upper_right(); origin.set_x(origin.x() - handle_size); RectF::new(origin, vec2f(handle_size, bounds.height())) @@ -71,69 +69,44 @@ impl Side { } } -struct ResizeHandleState { - actual_dimension: Cell, - custom_dimension: Cell, +pub struct Resizable { + child: AnyElement, + handle_side: HandleSide, + handle_size: f32, + on_resize: Rc)>> } -pub struct Resizable { - side: Side, - handle_size: f32, - child: AnyElement, - state: Rc, - _state_handle: ElementStateHandle>, -} +const DEFAULT_HANDLE_SIZE: f32 = 4.0; impl Resizable { - pub fn new( + pub fn new( child: AnyElement, - element_id: usize, - side: Side, - handle_size: f32, - initial_size: f32, - cx: &mut ViewContext, + handle_side: HandleSide, + size: f32, + on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext) ) -> Self { - let state_handle = cx.element_state::>( - element_id, - Rc::new(ResizeHandleState { - actual_dimension: Cell::new(initial_size), - custom_dimension: Cell::new(initial_size), - }), - ); - - let state = state_handle.read(cx).clone(); - - let child = Hook::new({ - let constrained = ConstrainedBox::new(child); - match side.axis() { - Axis::Horizontal => constrained.with_max_width(state.custom_dimension.get()), - Axis::Vertical => constrained.with_max_height(state.custom_dimension.get()), - } - }) - .on_after_layout({ - let state = state.clone(); - move |size, _| { - state.actual_dimension.set(side.relevant_component(size)); - } - }) + let child = match handle_side.axis() { + Axis::Horizontal => child.constrained().with_max_width(size), + Axis::Vertical => child.constrained().with_max_height(size), + } .into_any(); Self { - side, child, - handle_size, - state, - _state_handle: state_handle, + handle_side, + handle_size: DEFAULT_HANDLE_SIZE, + on_resize: Rc::new(RefCell::new(on_resize)), } } - pub fn current_size(&self) -> f32 { - self.state.actual_dimension.get() + pub fn with_handle_size(mut self, handle_size: f32) -> Self { + self.handle_size = handle_size; + self } } impl Element for Resizable { - type LayoutState = (); + type LayoutState = SizeConstraint; type PaintState = (); fn layout( @@ -142,7 +115,7 @@ impl Element for Resizable { view: &mut V, cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { - (self.child.layout(constraint, view, cx), ()) + (self.child.layout(constraint, view, cx), constraint) } fn paint( @@ -150,34 +123,37 @@ impl Element for Resizable { scene: &mut SceneBuilder, bounds: pathfinder_geometry::rect::RectF, visible_bounds: pathfinder_geometry::rect::RectF, - _child_size: &mut Self::LayoutState, + constraint: &mut SizeConstraint, view: &mut V, cx: &mut ViewContext, ) -> Self::PaintState { scene.push_stacking_context(None, None); - let handle_region = self.side.of_rect(bounds, self.handle_size); + let handle_region = self.handle_side.of_rect(bounds, self.handle_size); enum ResizeHandle {} scene.push_mouse_region( - MouseRegion::new::(cx.view_id(), self.side as usize, handle_region) + MouseRegion::new::(cx.view_id(), self.handle_side as usize, handle_region) .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere .on_drag(MouseButton::Left, { - let state = self.state.clone(); - let side = self.side; - move |e, _: &mut V, cx| { - let prev_width = state.actual_dimension.get(); - state - .custom_dimension - .set(0f32.max(prev_width + side.compute_delta(e)).round()); - cx.notify(); + let bounds = bounds.clone(); + let side = self.handle_side; + let prev_size = side.relevant_component(bounds.size()); + let min_size = side.relevant_component(constraint.min); + let max_size = side.relevant_component(constraint.max); + let on_resize = self.on_resize.clone(); + move |event, view: &mut V, cx| { + let new_size = min_size.max(prev_size + side.compute_delta(event)).min(max_size).round(); + if new_size != prev_size { + on_resize.borrow_mut()(view, new_size, cx); + } } }), ); scene.push_cursor_region(crate::CursorRegion { bounds: handle_region, - style: match self.side.axis() { + style: match self.handle_side.axis() { Axis::Horizontal => CursorStyle::ResizeLeftRight, Axis::Vertical => CursorStyle::ResizeUpDown, }, diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 14779f9b6f..ac0df343ae 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1347,12 +1347,9 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { let settings = cx.global::(); - let dock = settings - .project_panel_overrides - .dock - .or(settings.project_panel_defaults.dock) - .unwrap(); - match dock { + match settings + .project_panel + .dock { settings::ProjectPanelDockPosition::Left => DockPosition::Left, settings::ProjectPanelDockPosition::Right => DockPosition::Right, } @@ -1374,6 +1371,10 @@ impl workspace::dock::Panel for ProjectPanel { }) } + fn default_size(&self, cx: &gpui::WindowContext) -> f32 { + cx.global::().project_panel.default_width + } + fn icon_path(&self) -> &'static str { "icons/folder_tree_16.svg" } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 67acd12939..df03d4c1f6 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -44,8 +44,7 @@ pub struct Settings { pub show_call_status_icon: bool, pub vim_mode: bool, pub autosave: Autosave, - pub project_panel_defaults: ProjectPanelSettings, - pub project_panel_overrides: ProjectPanelSettings, + pub project_panel: ProjectPanelSettings, pub editor_defaults: EditorSettings, pub editor_overrides: EditorSettings, pub git: GitSettings, @@ -158,9 +157,15 @@ pub enum GitGutter { pub struct GitGutterConfig {} -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct ProjectPanelSettings { + pub dock: ProjectPanelDockPosition, + pub default_width: f32, +} +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct ProjectPanelSettingsContent { pub dock: Option, + pub default_width: Option, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)] @@ -253,6 +258,8 @@ impl Default for HourFormat { #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct TerminalSettings { + pub default_width: Option, + pub default_height: Option, pub shell: Option, pub working_directory: Option, pub font_size: Option, @@ -387,7 +394,7 @@ pub struct SettingsFileContent { #[serde(flatten)] pub editor: EditorSettings, #[serde(default)] - pub project_panel: ProjectPanelSettings, + pub project_panel: ProjectPanelSettingsContent, #[serde(default)] pub journal: JournalSettings, #[serde(default)] @@ -423,7 +430,6 @@ pub struct Features { } #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] pub struct FeaturesContent { pub copilot: Option, } @@ -482,8 +488,10 @@ impl Settings { show_call_status_icon: defaults.show_call_status_icon.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), - project_panel_defaults: defaults.project_panel, - project_panel_overrides: Default::default(), + project_panel: ProjectPanelSettings { + dock: defaults.project_panel.dock.unwrap(), + default_width: defaults.project_panel.default_width.unwrap(), + }, editor_defaults: EditorSettings { tab_size: required(defaults.editor.tab_size), hard_tabs: required(defaults.editor.hard_tabs), @@ -590,7 +598,8 @@ impl Settings { } } self.editor_overrides = data.editor; - self.project_panel_overrides = data.project_panel; + merge(&mut self.project_panel.dock, data.project_panel.dock); + merge(&mut self.project_panel.default_width, data.project_panel.default_width); self.git_overrides = data.git.unwrap_or_default(); self.journal_overrides = data.journal; self.terminal_defaults.font_size = data.terminal.font_size; @@ -778,8 +787,10 @@ impl Settings { show_call_status_icon: true, vim_mode: false, autosave: Autosave::Off, - project_panel_defaults: Default::default(), - project_panel_overrides: Default::default(), + project_panel: ProjectPanelSettings { + dock: ProjectPanelDockPosition::Left, + default_width: 240., + }, editor_defaults: EditorSettings { tab_size: Some(4.try_into().unwrap()), hard_tabs: Some(false), diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 5d4099c446..995c8be50b 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -179,6 +179,14 @@ impl Panel for TerminalPanel { }); } + fn default_size(&self, cx: &gpui::WindowContext) -> f32 { + let settings = &cx.global::().terminal_overrides; + match self.position(cx) { + DockPosition::Left | DockPosition::Right => settings.default_width.unwrap_or(640.), + DockPosition::Bottom => settings.default_height.unwrap_or(320.), + } + } + fn icon_path(&self) -> &'static str { "icons/terminal_12.svg" } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 5320a4c652..6ce1e4f691 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -12,6 +12,7 @@ pub trait Panel: View { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); + fn default_size(&self, cx: &WindowContext) -> f32; fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; fn icon_label(&self, _: &AppContext) -> Option { @@ -27,6 +28,7 @@ pub trait PanelHandle { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; fn set_position(&self, position: DockPosition, cx: &mut WindowContext); + fn default_size(&self, cx: &WindowContext) -> f32; fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -54,6 +56,10 @@ where self.update(cx, |this, cx| this.set_position(position, cx)) } + fn default_size(&self, cx: &WindowContext) -> f32 { + self.read(cx).default_size(cx) + } + fn icon_path(&self, cx: &WindowContext) -> &'static str { self.read(cx).icon_path() } @@ -104,17 +110,18 @@ impl DockPosition { } } - fn to_resizable_side(self) -> Side { + fn to_resize_handle_side(self) -> HandleSide { match self { - Self::Left => Side::Right, - Self::Bottom => Side::Top, - Self::Right => Side::Left, + Self::Left => HandleSide::Right, + Self::Bottom => HandleSide::Top, + Self::Right => HandleSide::Left, } } } struct PanelEntry { panel: Rc, + size: f32, context_menu: ViewHandle, _subscriptions: [Subscription; 2], } @@ -181,8 +188,10 @@ impl Dock { ]; let dock_view_id = cx.view_id(); + let size = panel.default_size(cx); self.panel_entries.push(PanelEntry { panel: Rc::new(panel), + size, context_menu: cx.add_view(|cx| { let mut menu = ContextMenu::new(dock_view_id, cx); menu.set_position_mode(OverlayPositionMode::Local); @@ -237,6 +246,23 @@ impl Dock { None } } + + pub fn active_panel_size(&self) -> Option { + if self.is_open { + self.panel_entries + .get(self.active_panel_index) + .map(|entry| entry.size) + } else { + None + } + } + + pub fn resize_active_panel(&mut self, size: f32, cx: &mut ViewContext) { + if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) { + entry.size = size; + cx.notify(); + } + } } impl Entity for Dock { @@ -250,18 +276,14 @@ impl View for Dock { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(active_panel) = self.active_panel() { - enum ResizeHandleTag {} + let size = self.active_panel_size().unwrap(); let style = &cx.global::().theme.workspace.dock; ChildView::new(active_panel.as_any(), cx) .contained() .with_style(style.container) - .with_resize_handle::( - self.position as usize, - self.position.to_resizable_side(), - 4., - style.initial_size, - cx, - ) + .resizable(self.position.to_resize_handle_side(), size, |dock: &mut Self, size, cx| { + dock.resize_active_panel(size, cx); + }) .into_any() } else { Empty::new().into_any() @@ -464,6 +486,10 @@ pub(crate) mod test { cx.emit(TestPanelEvent::PositionChanged); } + fn default_size(&self, _: &WindowContext) -> f32 { + 300. + } + fn icon_path(&self) -> &'static str { "icons/test_panel.svg" } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index dfad53c3a3..2adbff51fe 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -766,7 +766,7 @@ impl FollowableItemHandle for ViewHandle { #[cfg(test)] pub(crate) mod test { use super::{Item, ItemEvent}; - use crate::{dock::Panel, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; + use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; use gpui::{ elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -1059,42 +1059,4 @@ pub(crate) mod test { Task::Ready(Some(anyhow::Ok(view))) } } - - impl Panel for TestItem { - fn position(&self, _cx: &gpui::WindowContext) -> crate::dock::DockPosition { - unimplemented!() - } - - fn position_is_valid(&self, _position: crate::dock::DockPosition) -> bool { - unimplemented!() - } - - fn set_position( - &mut self, - _position: crate::dock::DockPosition, - _cx: &mut ViewContext, - ) { - unimplemented!() - } - - fn icon_path(&self) -> &'static str { - unimplemented!() - } - - fn icon_tooltip(&self) -> String { - unimplemented!() - } - - fn should_change_position_on_event(_: &Self::Event) -> bool { - unimplemented!() - } - - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - unimplemented!() - } - - fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - unimplemented!() - } - } } From bd795d7607f7ee03e67babc70049d2f7cfd61319 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 12 May 2023 15:15:11 -0600 Subject: [PATCH 031/131] Preserve panel size when re-docking between left and right --- crates/gpui/src/elements.rs | 7 +--- crates/gpui/src/elements/resizable.rs | 45 +++++++++++--------- crates/project_panel/src/project_panel.rs | 8 ++-- crates/settings/src/settings.rs | 5 ++- crates/workspace/src/dock.rs | 42 ++++++++++++++++--- crates/workspace/src/workspace.rs | 51 ++++++++++++++++++++--- 6 files changed, 117 insertions(+), 41 deletions(-) diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index 3caa91a3b8..f593615ae7 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -196,12 +196,7 @@ pub trait Element: 'static { where Self: 'static + Sized, { - Resizable::new( - self.into_any(), - side, - size, - on_resize - ) + Resizable::new(self.into_any(), side, size, on_resize) } } diff --git a/crates/gpui/src/elements/resizable.rs b/crates/gpui/src/elements/resizable.rs index a3d95893f4..da4b3473b3 100644 --- a/crates/gpui/src/elements/resizable.rs +++ b/crates/gpui/src/elements/resizable.rs @@ -7,8 +7,8 @@ use crate::{ geometry::rect::RectF, platform::{CursorStyle, MouseButton}, scene::MouseDrag, - AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, View, - ViewContext, SizeConstraint, + AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View, + ViewContext, }; #[derive(Copy, Clone, Debug)] @@ -73,7 +73,7 @@ pub struct Resizable { child: AnyElement, handle_side: HandleSide, handle_size: f32, - on_resize: Rc)>> + on_resize: Rc)>>, } const DEFAULT_HANDLE_SIZE: f32 = 4.0; @@ -83,7 +83,7 @@ impl Resizable { child: AnyElement, handle_side: HandleSide, size: f32, - on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext) + on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext), ) -> Self { let child = match handle_side.axis() { Axis::Horizontal => child.constrained().with_max_width(size), @@ -133,22 +133,29 @@ impl Element for Resizable { enum ResizeHandle {} scene.push_mouse_region( - MouseRegion::new::(cx.view_id(), self.handle_side as usize, handle_region) - .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere - .on_drag(MouseButton::Left, { - let bounds = bounds.clone(); - let side = self.handle_side; - let prev_size = side.relevant_component(bounds.size()); - let min_size = side.relevant_component(constraint.min); - let max_size = side.relevant_component(constraint.max); - let on_resize = self.on_resize.clone(); - move |event, view: &mut V, cx| { - let new_size = min_size.max(prev_size + side.compute_delta(event)).min(max_size).round(); - if new_size != prev_size { - on_resize.borrow_mut()(view, new_size, cx); - } + MouseRegion::new::( + cx.view_id(), + self.handle_side as usize, + handle_region, + ) + .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere + .on_drag(MouseButton::Left, { + let bounds = bounds.clone(); + let side = self.handle_side; + let prev_size = side.relevant_component(bounds.size()); + let min_size = side.relevant_component(constraint.min); + let max_size = side.relevant_component(constraint.max); + let on_resize = self.on_resize.clone(); + move |event, view: &mut V, cx| { + let new_size = min_size + .max(prev_size + side.compute_delta(event)) + .min(max_size) + .round(); + if new_size != prev_size { + on_resize.borrow_mut()(view, new_size, cx); } - }), + } + }), ); scene.push_cursor_region(crate::CursorRegion { diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index ac0df343ae..c76d2481ef 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -12,8 +12,8 @@ use gpui::{ geometry::vector::Vector2F, keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, - AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext, - ViewHandle, WeakViewHandle, + AnyElement, AppContext, Axis, ClipboardItem, Element, Entity, ModelHandle, Task, View, + ViewContext, ViewHandle, WeakViewHandle, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; @@ -1347,9 +1347,7 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { let settings = cx.global::(); - match settings - .project_panel - .dock { + match settings.project_panel.dock { settings::ProjectPanelDockPosition::Left => DockPosition::Left, settings::ProjectPanelDockPosition::Right => DockPosition::Right, } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index df03d4c1f6..618ccb89ac 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -599,7 +599,10 @@ impl Settings { } self.editor_overrides = data.editor; merge(&mut self.project_panel.dock, data.project_panel.dock); - merge(&mut self.project_panel.default_width, data.project_panel.default_width); + merge( + &mut self.project_panel.default_width, + data.project_panel.default_width, + ); self.git_overrides = data.git.unwrap_or_default(); self.journal_overrides = data.journal; self.terminal_defaults.font_size = data.terminal.font_size; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 6ce1e4f691..cafe8bd5dc 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -2,7 +2,8 @@ use crate::{StatusItemView, Workspace}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, - AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, + AppContext, Axis, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, + WindowContext, }; use serde::Deserialize; use settings::Settings; @@ -117,6 +118,13 @@ impl DockPosition { Self::Right => HandleSide::Left, } } + + pub fn axis(&self) -> Axis { + match self { + Self::Left | Self::Right => Axis::Horizontal, + Self::Bottom => Axis::Vertical, + } + } } struct PanelEntry { @@ -247,6 +255,23 @@ impl Dock { } } + pub fn panel_size(&self, panel: &dyn PanelHandle) -> Option { + self.panel_entries + .iter() + .find(|entry| entry.panel.id() == panel.id()) + .map(|entry| entry.size) + } + + pub fn resize_panel(&mut self, panel: &dyn PanelHandle, size: f32) { + let entry = self + .panel_entries + .iter_mut() + .find(|entry| entry.panel.id() == panel.id()); + if let Some(entry) = entry { + entry.size = size; + } + } + pub fn active_panel_size(&self) -> Option { if self.is_open { self.panel_entries @@ -281,9 +306,13 @@ impl View for Dock { ChildView::new(active_panel.as_any(), cx) .contained() .with_style(style.container) - .resizable(self.position.to_resize_handle_side(), size, |dock: &mut Self, size, cx| { - dock.resize_active_panel(size, cx); - }) + .resizable( + self.position.to_resize_handle_side(), + size, + |dock: &mut Self, size, cx| { + dock.resize_active_panel(size, cx); + }, + ) .into_any() } else { Empty::new().into_any() @@ -487,7 +516,10 @@ pub(crate) mod test { } fn default_size(&self, _: &WindowContext) -> f32 { - 300. + match self.position.axis() { + Axis::Horizontal => 300., + Axis::Vertical => 200., + } } fn icon_path(&self) -> &'static str { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 791d0d0a44..7bfe180d50 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -852,10 +852,18 @@ impl Workspace { cx.subscribe(&panel, { let mut dock = dock.clone(); + let mut prev_position = panel.position(cx); move |this, panel, event, cx| { if T::should_change_position_on_event(event) { + let new_position = panel.read(cx).position(cx); let mut was_visible = false; + let mut size = None; dock.update(cx, |dock, cx| { + if new_position.axis() == prev_position.axis() { + size = dock.panel_size(&panel); + } + prev_position = new_position; + was_visible = dock.is_open() && dock .active_panel() @@ -869,7 +877,11 @@ impl Workspace { } .clone(); dock.update(cx, |dock, cx| { - dock.add_panel(panel, cx); + dock.add_panel(panel.clone(), cx); + if let Some(size) = size { + dock.resize_panel(&panel, size); + } + if was_visible { dock.set_open(true, cx); dock.activate_panel(dock.panels_len() - 1, cx); @@ -3678,10 +3690,17 @@ mod tests { .right_dock() .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + let left_dock = workspace.left_dock(); assert_eq!( - workspace.left_dock().read(cx).active_panel().unwrap().id(), + left_dock.read(cx).active_panel().unwrap().id(), panel_1.id() ); + assert_eq!( + left_dock.read(cx).active_panel_size().unwrap(), + panel_1.default_size(cx) + ); + + left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx)); assert_eq!( workspace.right_dock().read(cx).active_panel().unwrap().id(), panel_2.id() @@ -3700,10 +3719,12 @@ mod tests { // Since it was the only panel on the left, the left dock should now be closed. assert!(!workspace.left_dock().read(cx).is_open()); assert!(workspace.left_dock().read(cx).active_panel().is_none()); + let right_dock = workspace.right_dock(); assert_eq!( - workspace.right_dock().read(cx).active_panel().unwrap().id(), + right_dock.read(cx).active_panel().unwrap().id(), panel_1.id() ); + assert_eq!(right_dock.read(cx).active_panel_size().unwrap(), 1337.); // Now we move panel_2 to the left panel_2.set_position(DockPosition::Left, cx); @@ -3727,13 +3748,33 @@ mod tests { workspace.update(cx, |workspace, cx| { // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. - assert!(workspace.left_dock().read(cx).is_open()); + let left_dock = workspace.left_dock(); + assert!(left_dock.read(cx).is_open()); assert_eq!( - workspace.left_dock().read(cx).active_panel().unwrap().id(), + left_dock.read(cx).active_panel().unwrap().id(), panel_1.id() ); + assert_eq!(left_dock.read(cx).active_panel_size().unwrap(), 1337.); // And right the dock should be closed as it no longer has any panels. assert!(!workspace.right_dock().read(cx).is_open()); + + // Now we move panel_1 to the bottom + panel_1.set_position(DockPosition::Bottom, cx); + }); + + workspace.update(cx, |workspace, cx| { + // Since panel_1 was visible on the left, we close the left dock. + assert!(!workspace.left_dock().read(cx).is_open()); + // The bottom dock is sized based on the panel's default size, + // since the panel orientation changed from vertical to horizontal. + assert_eq!( + workspace + .bottom_dock() + .read(cx) + .active_panel_size() + .unwrap(), + panel_1.default_size(cx), + ); }); } } From 6c608538428820ddd3df182438bb03720498dcdd Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 12 May 2023 15:44:09 -0600 Subject: [PATCH 032/131] Don't close panel on event unless active; add tests --- crates/project_panel/src/project_panel.rs | 4 +- crates/workspace/src/dock.rs | 12 ++++-- crates/workspace/src/workspace.rs | 48 ++++++++++++++++++++--- 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index c76d2481ef..e271c9970b 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -12,8 +12,8 @@ use gpui::{ geometry::vector::Vector2F, keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, - AnyElement, AppContext, Axis, ClipboardItem, Element, Entity, ModelHandle, Task, View, - ViewContext, ViewHandle, WeakViewHandle, + AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext, + ViewHandle, WeakViewHandle, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index cafe8bd5dc..86c2cc3929 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -180,16 +180,20 @@ impl Dock { pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) { let subscriptions = [ cx.observe(&panel, |_, _, cx| cx.notify()), - cx.subscribe(&panel, |this, view, event, cx| { - if view.read(cx).should_activate_on_event(event, cx) { + cx.subscribe(&panel, |this, panel, event, cx| { + if panel.read(cx).should_activate_on_event(event, cx) { if let Some(ix) = this .panel_entries .iter() - .position(|entry| entry.panel.id() == view.id()) + .position(|entry| entry.panel.id() == panel.id()) { + this.set_open(true, cx); this.activate_panel(ix, cx); + cx.focus(&panel); } - } else if view.read(cx).should_close_on_event(event, cx) { + } else if panel.read(cx).should_close_on_event(event, cx) + && this.active_panel().map_or(false, |p| p.id() == panel.id()) + { this.set_open(false, cx); } }), diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 7bfe180d50..ad717c362b 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3091,7 +3091,7 @@ mod tests { use std::{cell::RefCell, rc::Rc}; use crate::{ - dock::test::TestPanel, + dock::test::{TestPanel, TestPanelEvent}, item::test::{TestItem, TestItemEvent, TestProjectItem}, }; @@ -3767,14 +3767,50 @@ mod tests { assert!(!workspace.left_dock().read(cx).is_open()); // The bottom dock is sized based on the panel's default size, // since the panel orientation changed from vertical to horizontal. + let bottom_dock = workspace.bottom_dock(); assert_eq!( - workspace - .bottom_dock() - .read(cx) - .active_panel_size() - .unwrap(), + bottom_dock.read(cx).active_panel_size().unwrap(), panel_1.default_size(cx), ); + // Close bottom dock and move panel_1 back to the left. + bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); + panel_1.set_position(DockPosition::Left, cx); + }); + + // Emit activated event on panel 1 + panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated)); + + // Now the left dock is open and panel_1 is active and focused. + workspace.read_with(cx, |workspace, cx| { + let left_dock = workspace.left_dock(); + assert!(left_dock.read(cx).is_open()); + assert_eq!( + left_dock.read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + assert!(panel_1.is_focused(cx)); + }); + + // Emit closed event on panel 2, which is not active + panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed)); + + // Wo don't close the left dock, because panel_2 wasn't the active panel + workspace.read_with(cx, |workspace, cx| { + let left_dock = workspace.left_dock(); + assert!(left_dock.read(cx).is_open()); + assert_eq!( + left_dock.read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + }); + + // Emit closed event on panel 1, which is active + panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed)); + + // Now the left dock is closed, because panel_1 was the active panel + workspace.read_with(cx, |workspace, cx| { + let left_dock = workspace.left_dock(); + assert!(!left_dock.read(cx).is_open()); }); } } From ba50b35de691f692c9624f11491d2f78d9a31ef0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 13 May 2023 14:34:09 -0600 Subject: [PATCH 033/131] wip --- assets/keymaps/default.json | 1037 +++++++++-------- crates/gpui/src/app.rs | 6 + crates/gpui/src/elements.rs | 14 +- .../gpui/src/elements/mouse_event_handler.rs | 21 +- crates/theme/src/theme.rs | 2 + crates/workspace/src/pane.rs | 23 + crates/workspace/src/pane_group.rs | 8 +- crates/workspace/src/workspace.rs | 39 +- styles/src/styleTree/workspace.ts | 9 + 9 files changed, 634 insertions(+), 525 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 2e4fa62e8e..768e0c6ed1 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -1,534 +1,535 @@ [ - // Standard macOS bindings - { - "bindings": { - "up": "menu::SelectPrev", - "pageup": "menu::SelectFirst", - "shift-pageup": "menu::SelectFirst", - "ctrl-p": "menu::SelectPrev", - "down": "menu::SelectNext", - "pagedown": "menu::SelectLast", - "shift-pagedown": "menu::SelectFirst", - "ctrl-n": "menu::SelectNext", - "cmd-up": "menu::SelectFirst", - "cmd-down": "menu::SelectLast", - "enter": "menu::Confirm", - "escape": "menu::Cancel", - "ctrl-c": "menu::Cancel", - "cmd-{": "pane::ActivatePrevItem", - "cmd-}": "pane::ActivateNextItem", - "alt-cmd-left": "pane::ActivatePrevItem", - "alt-cmd-right": "pane::ActivateNextItem", - "cmd-w": "pane::CloseActiveItem", - "alt-cmd-t": "pane::CloseInactiveItems", - "cmd-k u": "pane::CloseCleanItems", - "cmd-k cmd-w": "pane::CloseAllItems", - "cmd-shift-w": "workspace::CloseWindow", - "cmd-s": "workspace::Save", - "cmd-shift-s": "workspace::SaveAs", - "cmd-=": "zed::IncreaseBufferFontSize", - "cmd--": "zed::DecreaseBufferFontSize", - "cmd-0": "zed::ResetBufferFontSize", - "cmd-,": "zed::OpenSettings", - "cmd-q": "zed::Quit", - "cmd-h": "zed::Hide", - "alt-cmd-h": "zed::HideOthers", - "cmd-m": "zed::Minimize", - "ctrl-cmd-f": "zed::ToggleFullScreen", - "cmd-n": "workspace::NewFile", - "cmd-shift-n": "workspace::NewWindow", - "cmd-o": "workspace::Open", - "alt-cmd-o": "projects::OpenRecent", - "ctrl-`": "workspace::NewTerminal" - } - }, - { - "context": "Editor", - "bindings": { - "escape": "editor::Cancel", - "backspace": "editor::Backspace", - "shift-backspace": "editor::Backspace", - "ctrl-h": "editor::Backspace", - "delete": "editor::Delete", - "ctrl-d": "editor::Delete", - "tab": "editor::Tab", - "shift-tab": "editor::TabPrev", - "ctrl-k": "editor::CutToEndOfLine", - "ctrl-t": "editor::Transpose", - "cmd-backspace": "editor::DeleteToBeginningOfLine", - "cmd-delete": "editor::DeleteToEndOfLine", - "alt-backspace": "editor::DeleteToPreviousWordStart", - "alt-delete": "editor::DeleteToNextWordEnd", - "alt-h": "editor::DeleteToPreviousWordStart", - "alt-d": "editor::DeleteToNextWordEnd", - "cmd-x": "editor::Cut", - "cmd-c": "editor::Copy", - "cmd-v": "editor::Paste", - "cmd-z": "editor::Undo", - "cmd-shift-z": "editor::Redo", - "up": "editor::MoveUp", - "pageup": "editor::PageUp", - "shift-pageup": "editor::MovePageUp", - "home": "editor::MoveToBeginningOfLine", - "down": "editor::MoveDown", - "pagedown": "editor::PageDown", - "shift-pagedown": "editor::MovePageDown", - "end": "editor::MoveToEndOfLine", - "left": "editor::MoveLeft", - "right": "editor::MoveRight", - "ctrl-p": "editor::MoveUp", - "ctrl-n": "editor::MoveDown", - "ctrl-b": "editor::MoveLeft", - "ctrl-f": "editor::MoveRight", - "ctrl-l": "editor::NextScreen", - "alt-left": "editor::MoveToPreviousWordStart", - "alt-b": "editor::MoveToPreviousWordStart", - "alt-right": "editor::MoveToNextWordEnd", - "alt-f": "editor::MoveToNextWordEnd", - "cmd-left": "editor::MoveToBeginningOfLine", - "ctrl-a": "editor::MoveToBeginningOfLine", - "cmd-right": "editor::MoveToEndOfLine", - "ctrl-e": "editor::MoveToEndOfLine", - "cmd-up": "editor::MoveToBeginning", - "cmd-down": "editor::MoveToEnd", - "shift-up": "editor::SelectUp", - "ctrl-shift-p": "editor::SelectUp", - "shift-down": "editor::SelectDown", - "ctrl-shift-n": "editor::SelectDown", - "shift-left": "editor::SelectLeft", - "ctrl-shift-b": "editor::SelectLeft", - "shift-right": "editor::SelectRight", - "ctrl-shift-f": "editor::SelectRight", - "alt-shift-left": "editor::SelectToPreviousWordStart", - "alt-shift-b": "editor::SelectToPreviousWordStart", - "alt-shift-right": "editor::SelectToNextWordEnd", - "alt-shift-f": "editor::SelectToNextWordEnd", - "cmd-shift-up": "editor::SelectToBeginning", - "cmd-shift-down": "editor::SelectToEnd", - "cmd-a": "editor::SelectAll", - "cmd-l": "editor::SelectLine", - "cmd-shift-i": "editor::Format", - "cmd-shift-left": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true + // Standard macOS bindings + { + "bindings": { + "up": "menu::SelectPrev", + "pageup": "menu::SelectFirst", + "shift-pageup": "menu::SelectFirst", + "ctrl-p": "menu::SelectPrev", + "down": "menu::SelectNext", + "pagedown": "menu::SelectLast", + "shift-pagedown": "menu::SelectFirst", + "ctrl-n": "menu::SelectNext", + "cmd-up": "menu::SelectFirst", + "cmd-down": "menu::SelectLast", + "enter": "menu::Confirm", + "escape": "menu::Cancel", + "ctrl-c": "menu::Cancel", + "cmd-{": "pane::ActivatePrevItem", + "cmd-}": "pane::ActivateNextItem", + "alt-cmd-left": "pane::ActivatePrevItem", + "alt-cmd-right": "pane::ActivateNextItem", + "cmd-w": "pane::CloseActiveItem", + "alt-cmd-t": "pane::CloseInactiveItems", + "cmd-k u": "pane::CloseCleanItems", + "cmd-k cmd-w": "pane::CloseAllItems", + "cmd-shift-w": "workspace::CloseWindow", + "cmd-s": "workspace::Save", + "cmd-shift-s": "workspace::SaveAs", + "cmd-=": "zed::IncreaseBufferFontSize", + "cmd--": "zed::DecreaseBufferFontSize", + "cmd-0": "zed::ResetBufferFontSize", + "cmd-,": "zed::OpenSettings", + "cmd-q": "zed::Quit", + "cmd-h": "zed::Hide", + "alt-cmd-h": "zed::HideOthers", + "cmd-m": "zed::Minimize", + "ctrl-cmd-f": "zed::ToggleFullScreen", + "cmd-n": "workspace::NewFile", + "cmd-shift-n": "workspace::NewWindow", + "cmd-o": "workspace::Open", + "alt-cmd-o": "projects::OpenRecent", + "ctrl-`": "workspace::NewTerminal" } - ], - "shift-home": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true + }, + { + "context": "Editor", + "bindings": { + "escape": "editor::Cancel", + "backspace": "editor::Backspace", + "shift-backspace": "editor::Backspace", + "ctrl-h": "editor::Backspace", + "delete": "editor::Delete", + "ctrl-d": "editor::Delete", + "tab": "editor::Tab", + "shift-tab": "editor::TabPrev", + "ctrl-k": "editor::CutToEndOfLine", + "ctrl-t": "editor::Transpose", + "cmd-backspace": "editor::DeleteToBeginningOfLine", + "cmd-delete": "editor::DeleteToEndOfLine", + "alt-backspace": "editor::DeleteToPreviousWordStart", + "alt-delete": "editor::DeleteToNextWordEnd", + "alt-h": "editor::DeleteToPreviousWordStart", + "alt-d": "editor::DeleteToNextWordEnd", + "cmd-x": "editor::Cut", + "cmd-c": "editor::Copy", + "cmd-v": "editor::Paste", + "cmd-z": "editor::Undo", + "cmd-shift-z": "editor::Redo", + "up": "editor::MoveUp", + "pageup": "editor::PageUp", + "shift-pageup": "editor::MovePageUp", + "home": "editor::MoveToBeginningOfLine", + "down": "editor::MoveDown", + "pagedown": "editor::PageDown", + "shift-pagedown": "editor::MovePageDown", + "end": "editor::MoveToEndOfLine", + "left": "editor::MoveLeft", + "right": "editor::MoveRight", + "ctrl-p": "editor::MoveUp", + "ctrl-n": "editor::MoveDown", + "ctrl-b": "editor::MoveLeft", + "ctrl-f": "editor::MoveRight", + "ctrl-l": "editor::NextScreen", + "alt-left": "editor::MoveToPreviousWordStart", + "alt-b": "editor::MoveToPreviousWordStart", + "alt-right": "editor::MoveToNextWordEnd", + "alt-f": "editor::MoveToNextWordEnd", + "cmd-left": "editor::MoveToBeginningOfLine", + "ctrl-a": "editor::MoveToBeginningOfLine", + "cmd-right": "editor::MoveToEndOfLine", + "ctrl-e": "editor::MoveToEndOfLine", + "cmd-up": "editor::MoveToBeginning", + "cmd-down": "editor::MoveToEnd", + "shift-up": "editor::SelectUp", + "ctrl-shift-p": "editor::SelectUp", + "shift-down": "editor::SelectDown", + "ctrl-shift-n": "editor::SelectDown", + "shift-left": "editor::SelectLeft", + "ctrl-shift-b": "editor::SelectLeft", + "shift-right": "editor::SelectRight", + "ctrl-shift-f": "editor::SelectRight", + "alt-shift-left": "editor::SelectToPreviousWordStart", + "alt-shift-b": "editor::SelectToPreviousWordStart", + "alt-shift-right": "editor::SelectToNextWordEnd", + "alt-shift-f": "editor::SelectToNextWordEnd", + "cmd-shift-up": "editor::SelectToBeginning", + "cmd-shift-down": "editor::SelectToEnd", + "cmd-a": "editor::SelectAll", + "cmd-l": "editor::SelectLine", + "cmd-shift-i": "editor::Format", + "cmd-shift-left": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true + } + ], + "shift-home": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true + } + ], + "ctrl-shift-a": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true + } + ], + "cmd-shift-right": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true + } + ], + "shift-end": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true + } + ], + "ctrl-shift-e": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true + } + ], + "ctrl-v": [ + "editor::MovePageDown", + { + "center_cursor": true + } + ], + "alt-v": [ + "editor::MovePageUp", + { + "center_cursor": true + } + ], + "ctrl-cmd-space": "editor::ShowCharacterPalette" } - ], - "ctrl-shift-a": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true + }, + { + "context": "Editor && mode == full", + "bindings": { + "enter": "editor::Newline", + "cmd-shift-enter": "editor::NewlineAbove", + "cmd-enter": "editor::NewlineBelow", + "alt-z": "editor::ToggleSoftWrap", + "cmd-f": [ + "buffer_search::Deploy", + { + "focus": true + } + ], + "cmd-e": [ + "buffer_search::Deploy", + { + "focus": false + } + ], + "alt-\\": "copilot::Suggest", + "alt-]": "copilot::NextSuggestion", + "alt-[": "copilot::PreviousSuggestion" } - ], - "cmd-shift-right": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true + }, + { + "context": "Editor && mode == auto_height", + "bindings": { + "alt-enter": "editor::Newline", + "cmd-alt-enter": "editor::NewlineBelow" } - ], - "shift-end": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true + }, + { + "context": "BufferSearchBar > Editor", + "bindings": { + "escape": "buffer_search::Dismiss", + "tab": "buffer_search::FocusEditor", + "enter": "search::SelectNextMatch", + "shift-enter": "search::SelectPrevMatch" } - ], - "ctrl-shift-e": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true + }, + { + "context": "ProjectSearchBar > Editor", + "bindings": { + "escape": "project_search::ToggleFocus" } - ], - "ctrl-v": [ - "editor::MovePageDown", - { - "center_cursor": true + }, + { + "context": "ProjectSearchView > Editor", + "bindings": { + "escape": "project_search::ToggleFocus" } - ], - "alt-v": [ - "editor::MovePageUp", - { - "center_cursor": true + }, + { + "context": "Pane", + "bindings": { + "cmd-f": "project_search::ToggleFocus", + "cmd-g": "search::SelectNextMatch", + "cmd-shift-g": "search::SelectPrevMatch", + "alt-cmd-c": "search::ToggleCaseSensitive", + "alt-cmd-w": "search::ToggleWholeWord", + "alt-cmd-r": "search::ToggleRegex", + "shift-escape": "pane::ToggleZoom" } - ], - "ctrl-cmd-space": "editor::ShowCharacterPalette" - } - }, - { - "context": "Editor && mode == full", - "bindings": { - "enter": "editor::Newline", - "cmd-shift-enter": "editor::NewlineAbove", - "cmd-enter": "editor::NewlineBelow", - "alt-z": "editor::ToggleSoftWrap", - "cmd-f": [ - "buffer_search::Deploy", - { - "focus": true + }, + // Bindings from VS Code + { + "context": "Editor", + "bindings": { + "cmd-[": "editor::Outdent", + "cmd-]": "editor::Indent", + "cmd-alt-up": "editor::AddSelectionAbove", + "cmd-ctrl-p": "editor::AddSelectionAbove", + "cmd-alt-down": "editor::AddSelectionBelow", + "cmd-ctrl-n": "editor::AddSelectionBelow", + "cmd-d": [ + "editor::SelectNext", + { + "replace_newest": false + } + ], + "cmd-k cmd-d": [ + "editor::SelectNext", + { + "replace_newest": true + } + ], + "cmd-k cmd-i": "editor::Hover", + "cmd-/": [ + "editor::ToggleComments", + { + "advance_downwards": false + } + ], + "alt-up": "editor::SelectLargerSyntaxNode", + "alt-down": "editor::SelectSmallerSyntaxNode", + "cmd-u": "editor::UndoSelection", + "cmd-shift-u": "editor::RedoSelection", + "f8": "editor::GoToDiagnostic", + "shift-f8": "editor::GoToPrevDiagnostic", + "f2": "editor::Rename", + "f12": "editor::GoToDefinition", + "cmd-f12": "editor::GoToTypeDefinition", + "alt-shift-f12": "editor::FindAllReferences", + "ctrl-m": "editor::MoveToEnclosingBracket", + "alt-cmd-[": "editor::Fold", + "alt-cmd-]": "editor::UnfoldLines", + "ctrl-space": "editor::ShowCompletions", + "cmd-.": "editor::ToggleCodeActions", + "alt-cmd-r": "editor::RevealInFinder" } - ], - "cmd-e": [ - "buffer_search::Deploy", - { - "focus": false + }, + { + "context": "Editor && mode == full", + "bindings": { + "cmd-shift-o": "outline::Toggle", + "ctrl-g": "go_to_line::Toggle" } - ], - "alt-\\": "copilot::Suggest", - "alt-]": "copilot::NextSuggestion", - "alt-[": "copilot::PreviousSuggestion" - } - }, - { - "context": "Editor && mode == auto_height", - "bindings": { - "alt-enter": "editor::Newline", - "cmd-alt-enter": "editor::NewlineBelow" - } - }, - { - "context": "BufferSearchBar > Editor", - "bindings": { - "escape": "buffer_search::Dismiss", - "tab": "buffer_search::FocusEditor", - "enter": "search::SelectNextMatch", - "shift-enter": "search::SelectPrevMatch" - } - }, - { - "context": "ProjectSearchBar > Editor", - "bindings": { - "escape": "project_search::ToggleFocus" - } - }, - { - "context": "ProjectSearchView > Editor", - "bindings": { - "escape": "project_search::ToggleFocus" - } - }, - { - "context": "Pane", - "bindings": { - "cmd-f": "project_search::ToggleFocus", - "cmd-g": "search::SelectNextMatch", - "cmd-shift-g": "search::SelectPrevMatch", - "alt-cmd-c": "search::ToggleCaseSensitive", - "alt-cmd-w": "search::ToggleWholeWord", - "alt-cmd-r": "search::ToggleRegex" - } - }, - // Bindings from VS Code - { - "context": "Editor", - "bindings": { - "cmd-[": "editor::Outdent", - "cmd-]": "editor::Indent", - "cmd-alt-up": "editor::AddSelectionAbove", - "cmd-ctrl-p": "editor::AddSelectionAbove", - "cmd-alt-down": "editor::AddSelectionBelow", - "cmd-ctrl-n": "editor::AddSelectionBelow", - "cmd-d": [ - "editor::SelectNext", - { - "replace_newest": false + }, + { + "context": "Pane", + "bindings": { + "ctrl-1": [ + "pane::ActivateItem", + 0 + ], + "ctrl-2": [ + "pane::ActivateItem", + 1 + ], + "ctrl-3": [ + "pane::ActivateItem", + 2 + ], + "ctrl-4": [ + "pane::ActivateItem", + 3 + ], + "ctrl-5": [ + "pane::ActivateItem", + 4 + ], + "ctrl-6": [ + "pane::ActivateItem", + 5 + ], + "ctrl-7": [ + "pane::ActivateItem", + 6 + ], + "ctrl-8": [ + "pane::ActivateItem", + 7 + ], + "ctrl-9": [ + "pane::ActivateItem", + 8 + ], + "ctrl-0": "pane::ActivateLastItem", + "ctrl--": "pane::GoBack", + "ctrl-_": "pane::GoForward", + "cmd-shift-t": "pane::ReopenClosedItem", + "cmd-shift-f": "project_search::ToggleFocus" } - ], - "cmd-k cmd-d": [ - "editor::SelectNext", - { - "replace_newest": true + }, + { + "context": "Workspace", + "bindings": { + "cmd-1": [ + "workspace::ActivatePane", + 0 + ], + "cmd-2": [ + "workspace::ActivatePane", + 1 + ], + "cmd-3": [ + "workspace::ActivatePane", + 2 + ], + "cmd-4": [ + "workspace::ActivatePane", + 3 + ], + "cmd-5": [ + "workspace::ActivatePane", + 4 + ], + "cmd-6": [ + "workspace::ActivatePane", + 5 + ], + "cmd-7": [ + "workspace::ActivatePane", + 6 + ], + "cmd-8": [ + "workspace::ActivatePane", + 7 + ], + "cmd-9": [ + "workspace::ActivatePane", + 8 + ], + "cmd-b": "workspace::ToggleLeftDock", + "cmd-shift-f": "workspace::NewSearch", + "cmd-k cmd-t": "theme_selector::Toggle", + "cmd-k cmd-s": "zed::OpenKeymap", + "cmd-t": "project_symbols::Toggle", + "cmd-p": "file_finder::Toggle", + "cmd-shift-p": "command_palette::Toggle", + "cmd-shift-m": "diagnostics::Deploy", + "cmd-shift-e": "project_panel::ToggleFocus", + "cmd-alt-s": "workspace::SaveAll", + "cmd-k m": "language_selector::Toggle" } - ], - "cmd-k cmd-i": "editor::Hover", - "cmd-/": [ - "editor::ToggleComments", - { - "advance_downwards": false + }, + // Bindings from Sublime Text + { + "context": "Editor", + "bindings": { + "ctrl-shift-k": "editor::DeleteLine", + "cmd-shift-d": "editor::DuplicateLine", + "cmd-shift-l": "editor::SplitSelectionIntoLines", + "ctrl-cmd-up": "editor::MoveLineUp", + "ctrl-cmd-down": "editor::MoveLineDown", + "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart", + "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", + "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", + "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", + "ctrl-alt-left": "editor::MoveToPreviousSubwordStart", + "ctrl-alt-b": "editor::MoveToPreviousSubwordStart", + "ctrl-alt-right": "editor::MoveToNextSubwordEnd", + "ctrl-alt-f": "editor::MoveToNextSubwordEnd", + "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart", + "ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart", + "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd", + "ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd" + } + }, + { + "bindings": { + "cmd-k cmd-left": "workspace::ActivatePreviousPane", + "cmd-k cmd-right": "workspace::ActivateNextPane" + } + }, + // Bindings from Atom + { + "context": "Pane", + "bindings": { + "cmd-k up": "pane::SplitUp", + "cmd-k down": "pane::SplitDown", + "cmd-k left": "pane::SplitLeft", + "cmd-k right": "pane::SplitRight" + } + }, + // Bindings that should be unified with bindings for more general actions + { + "context": "Editor && renaming", + "bindings": { + "enter": "editor::ConfirmRename" + } + }, + { + "context": "Editor && showing_completions", + "bindings": { + "enter": "editor::ConfirmCompletion", + "tab": "editor::ConfirmCompletion" + } + }, + { + "context": "Editor && showing_code_actions", + "bindings": { + "enter": "editor::ConfirmCodeAction" + } + }, + // Custom bindings + { + "bindings": { + "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator", + "cmd-shift-c": "collab::ToggleContactsMenu", + "cmd-alt-i": "zed::DebugElements" + } + }, + { + "context": "Editor", + "bindings": { + "alt-enter": "editor::OpenExcerpts", + "cmd-f8": "editor::GoToHunk", + "cmd-shift-f8": "editor::GoToPrevHunk" + } + }, + { + "context": "ProjectSearchBar", + "bindings": { + "cmd-enter": "project_search::SearchInNew" + } + }, + { + "context": "ProjectPanel", + "bindings": { + "left": "project_panel::CollapseSelectedEntry", + "right": "project_panel::ExpandSelectedEntry", + "cmd-x": "project_panel::Cut", + "cmd-c": "project_panel::Copy", + "cmd-v": "project_panel::Paste", + "cmd-alt-c": "project_panel::CopyPath", + "alt-cmd-shift-c": "project_panel::CopyRelativePath", + "f2": "project_panel::Rename", + "backspace": "project_panel::Delete", + "alt-cmd-r": "project_panel::RevealInFinder" + } + }, + { + "context": "Terminal", + "bindings": { + "ctrl-cmd-space": "terminal::ShowCharacterPalette", + "cmd-c": "terminal::Copy", + "cmd-v": "terminal::Paste", + "cmd-k": "terminal::Clear", + // Some nice conveniences + "cmd-backspace": [ + "terminal::SendText", + "\u0015" + ], + "cmd-right": [ + "terminal::SendText", + "\u0005" + ], + "cmd-left": [ + "terminal::SendText", + "\u0001" + ], + // Terminal.app compatability + "alt-left": [ + "terminal::SendText", + "\u001bb" + ], + "alt-right": [ + "terminal::SendText", + "\u001bf" + ], + // There are conflicting bindings for these keys in the global context. + // these bindings override them, remove at your own risk: + "up": [ + "terminal::SendKeystroke", + "up" + ], + "pageup": [ + "terminal::SendKeystroke", + "pageup" + ], + "down": [ + "terminal::SendKeystroke", + "down" + ], + "pagedown": [ + "terminal::SendKeystroke", + "pagedown" + ], + "escape": [ + "terminal::SendKeystroke", + "escape" + ], + "enter": [ + "terminal::SendKeystroke", + "enter" + ], + "ctrl-c": [ + "terminal::SendKeystroke", + "ctrl-c" + ] } - ], - "alt-up": "editor::SelectLargerSyntaxNode", - "alt-down": "editor::SelectSmallerSyntaxNode", - "cmd-u": "editor::UndoSelection", - "cmd-shift-u": "editor::RedoSelection", - "f8": "editor::GoToDiagnostic", - "shift-f8": "editor::GoToPrevDiagnostic", - "f2": "editor::Rename", - "f12": "editor::GoToDefinition", - "cmd-f12": "editor::GoToTypeDefinition", - "alt-shift-f12": "editor::FindAllReferences", - "ctrl-m": "editor::MoveToEnclosingBracket", - "alt-cmd-[": "editor::Fold", - "alt-cmd-]": "editor::UnfoldLines", - "ctrl-space": "editor::ShowCompletions", - "cmd-.": "editor::ToggleCodeActions", - "alt-cmd-r": "editor::RevealInFinder" } - }, - { - "context": "Editor && mode == full", - "bindings": { - "cmd-shift-o": "outline::Toggle", - "ctrl-g": "go_to_line::Toggle" - } - }, - { - "context": "Pane", - "bindings": { - "ctrl-1": [ - "pane::ActivateItem", - 0 - ], - "ctrl-2": [ - "pane::ActivateItem", - 1 - ], - "ctrl-3": [ - "pane::ActivateItem", - 2 - ], - "ctrl-4": [ - "pane::ActivateItem", - 3 - ], - "ctrl-5": [ - "pane::ActivateItem", - 4 - ], - "ctrl-6": [ - "pane::ActivateItem", - 5 - ], - "ctrl-7": [ - "pane::ActivateItem", - 6 - ], - "ctrl-8": [ - "pane::ActivateItem", - 7 - ], - "ctrl-9": [ - "pane::ActivateItem", - 8 - ], - "ctrl-0": "pane::ActivateLastItem", - "ctrl--": "pane::GoBack", - "ctrl-_": "pane::GoForward", - "cmd-shift-t": "pane::ReopenClosedItem", - "cmd-shift-f": "project_search::ToggleFocus" - } - }, - { - "context": "Workspace", - "bindings": { - "cmd-1": [ - "workspace::ActivatePane", - 0 - ], - "cmd-2": [ - "workspace::ActivatePane", - 1 - ], - "cmd-3": [ - "workspace::ActivatePane", - 2 - ], - "cmd-4": [ - "workspace::ActivatePane", - 3 - ], - "cmd-5": [ - "workspace::ActivatePane", - 4 - ], - "cmd-6": [ - "workspace::ActivatePane", - 5 - ], - "cmd-7": [ - "workspace::ActivatePane", - 6 - ], - "cmd-8": [ - "workspace::ActivatePane", - 7 - ], - "cmd-9": [ - "workspace::ActivatePane", - 8 - ], - "cmd-b": "workspace::ToggleLeftDock", - "cmd-shift-f": "workspace::NewSearch", - "cmd-k cmd-t": "theme_selector::Toggle", - "cmd-k cmd-s": "zed::OpenKeymap", - "cmd-t": "project_symbols::Toggle", - "cmd-p": "file_finder::Toggle", - "cmd-shift-p": "command_palette::Toggle", - "cmd-shift-m": "diagnostics::Deploy", - "cmd-shift-e": "project_panel::ToggleFocus", - "cmd-alt-s": "workspace::SaveAll", - "cmd-k m": "language_selector::Toggle" - } - }, - // Bindings from Sublime Text - { - "context": "Editor", - "bindings": { - "ctrl-shift-k": "editor::DeleteLine", - "cmd-shift-d": "editor::DuplicateLine", - "cmd-shift-l": "editor::SplitSelectionIntoLines", - "ctrl-cmd-up": "editor::MoveLineUp", - "ctrl-cmd-down": "editor::MoveLineDown", - "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart", - "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", - "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", - "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", - "ctrl-alt-left": "editor::MoveToPreviousSubwordStart", - "ctrl-alt-b": "editor::MoveToPreviousSubwordStart", - "ctrl-alt-right": "editor::MoveToNextSubwordEnd", - "ctrl-alt-f": "editor::MoveToNextSubwordEnd", - "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart", - "ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart", - "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd", - "ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd" - } - }, - { - "bindings": { - "cmd-k cmd-left": "workspace::ActivatePreviousPane", - "cmd-k cmd-right": "workspace::ActivateNextPane" - } - }, - // Bindings from Atom - { - "context": "Pane", - "bindings": { - "cmd-k up": "pane::SplitUp", - "cmd-k down": "pane::SplitDown", - "cmd-k left": "pane::SplitLeft", - "cmd-k right": "pane::SplitRight" - } - }, - // Bindings that should be unified with bindings for more general actions - { - "context": "Editor && renaming", - "bindings": { - "enter": "editor::ConfirmRename" - } - }, - { - "context": "Editor && showing_completions", - "bindings": { - "enter": "editor::ConfirmCompletion", - "tab": "editor::ConfirmCompletion" - } - }, - { - "context": "Editor && showing_code_actions", - "bindings": { - "enter": "editor::ConfirmCodeAction" - } - }, - // Custom bindings - { - "bindings": { - "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator", - "cmd-shift-c": "collab::ToggleContactsMenu", - "cmd-alt-i": "zed::DebugElements" - } - }, - { - "context": "Editor", - "bindings": { - "alt-enter": "editor::OpenExcerpts", - "cmd-f8": "editor::GoToHunk", - "cmd-shift-f8": "editor::GoToPrevHunk" - } - }, - { - "context": "ProjectSearchBar", - "bindings": { - "cmd-enter": "project_search::SearchInNew" - } - }, - { - "context": "ProjectPanel", - "bindings": { - "left": "project_panel::CollapseSelectedEntry", - "right": "project_panel::ExpandSelectedEntry", - "cmd-x": "project_panel::Cut", - "cmd-c": "project_panel::Copy", - "cmd-v": "project_panel::Paste", - "cmd-alt-c": "project_panel::CopyPath", - "alt-cmd-shift-c": "project_panel::CopyRelativePath", - "f2": "project_panel::Rename", - "backspace": "project_panel::Delete", - "alt-cmd-r": "project_panel::RevealInFinder" - } - }, - { - "context": "Terminal", - "bindings": { - "ctrl-cmd-space": "terminal::ShowCharacterPalette", - "cmd-c": "terminal::Copy", - "cmd-v": "terminal::Paste", - "cmd-k": "terminal::Clear", - // Some nice conveniences - "cmd-backspace": [ - "terminal::SendText", - "\u0015" - ], - "cmd-right": [ - "terminal::SendText", - "\u0005" - ], - "cmd-left": [ - "terminal::SendText", - "\u0001" - ], - // Terminal.app compatability - "alt-left": [ - "terminal::SendText", - "\u001bb" - ], - "alt-right": [ - "terminal::SendText", - "\u001bf" - ], - // There are conflicting bindings for these keys in the global context. - // these bindings override them, remove at your own risk: - "up": [ - "terminal::SendKeystroke", - "up" - ], - "pageup": [ - "terminal::SendKeystroke", - "pageup" - ], - "down": [ - "terminal::SendKeystroke", - "down" - ], - "pagedown": [ - "terminal::SendKeystroke", - "pagedown" - ], - "escape": [ - "terminal::SendKeystroke", - "escape" - ], - "enter": [ - "terminal::SendKeystroke", - "enter" - ], - "ctrl-c": [ - "terminal::SendKeystroke", - "ctrl-c" - ] - } - } ] diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 488f55d0c6..65418dd997 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3816,6 +3816,12 @@ impl PartialEq for ViewHandle { } } +impl PartialEq for ViewHandle { + fn eq(&self, other: &AnyViewHandle) -> bool { + self.window_id == other.window_id && self.view_id == other.view_id + } +} + impl PartialEq> for ViewHandle { fn eq(&self, other: &WeakViewHandle) -> bool { self.window_id == other.window_id && self.view_id == other.view_id diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index f593615ae7..40b6a50816 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -33,8 +33,11 @@ use crate::{ rect::RectF, vector::{vec2f, Vector2F}, }, - json, Action, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle, - WindowContext, + json, + platform::MouseButton, + scene::MouseDown, + Action, EventContext, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, + WeakViewHandle, WindowContext, }; use anyhow::{anyhow, Result}; use collections::HashMap; @@ -198,6 +201,13 @@ pub trait Element: 'static { { Resizable::new(self.into_any(), side, size, on_resize) } + + fn mouse(self, region_id: usize) -> MouseEventHandler + where + Self: Sized, + { + MouseEventHandler::for_child(self.into_any(), region_id) + } } trait AnyElementState { diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index ed624922d5..6f2762db66 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -32,10 +32,25 @@ pub struct MouseEventHandler { /// Element which provides a render_child callback with a MouseState and paints a mouse /// region under (or above) it for easy mouse event handling. impl MouseEventHandler { - pub fn new(region_id: usize, cx: &mut ViewContext, render_child: F) -> Self + pub fn for_child(child: impl Element, region_id: usize) -> Self { + Self { + child: child.into_any(), + region_id, + cursor_style: None, + handlers: Default::default(), + notify_on_hover: false, + notify_on_click: false, + hoverable: false, + above: false, + padding: Default::default(), + _tag: PhantomData, + } + } + + pub fn new(region_id: usize, cx: &mut ViewContext, render_child: F) -> Self where - D: Element, - F: FnOnce(&mut MouseState, &mut ViewContext) -> D, + E: Element, + F: FnOnce(&mut MouseState, &mut ViewContext) -> E, { let mut mouse_state = cx.mouse_state::(region_id); let child = render_child(&mut mouse_state, cx).into_any(); diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 29bb51be68..50f4607d66 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -68,6 +68,8 @@ pub struct Workspace { pub breadcrumbs: Interactive, pub disconnected_overlay: ContainedText, pub modal: ContainerStyle, + pub zoomed_foreground: ContainerStyle, + pub zoomed_background: ContainerStyle, pub notification: ContainerStyle, pub notifications: Notifications, pub joining_project_avatar: ImageStyle, diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 30e782c750..a216f6181c 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -69,6 +69,7 @@ actions!( SplitUp, SplitRight, SplitDown, + ToggleZoom, ] ); @@ -91,6 +92,7 @@ const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; pub type BackgroundActions = fn() -> &'static [(&'static str, &'static dyn Action)]; pub fn init(cx: &mut AppContext) { + cx.add_action(Pane::toggle_zoom); cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| { pane.activate_item(action.0, true, true, cx); }); @@ -132,12 +134,15 @@ pub enum Event { Split(SplitDirection), ChangeItemTitle, Focus, + ZoomIn, + ZoomOut, } pub struct Pane { items: Vec>, activation_history: Vec, is_active: bool, + zoomed: bool, active_item_index: usize, last_focused_view_by_item: HashMap, autoscroll: bool, @@ -236,6 +241,7 @@ impl Pane { items: Vec::new(), activation_history: Vec::new(), is_active: true, + zoomed: false, active_item_index: 0, last_focused_view_by_item: Default::default(), autoscroll: false, @@ -655,6 +661,14 @@ impl Pane { self.items.iter().position(|i| i.id() == item.id()) } + pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { + if self.zoomed { + cx.emit(Event::ZoomOut); + } else { + cx.emit(Event::ZoomIn); + } + } + pub fn activate_item( &mut self, index: usize, @@ -1546,6 +1560,15 @@ impl Pane { .with_background_color(background) .into_any() } + + pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { + self.zoomed = zoomed; + cx.notify(); + } + + pub fn is_zoomed(&self) -> bool { + self.zoomed + } } impl Entity for Pane { diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 55032b4bc1..a1b35c1d65 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -142,6 +142,12 @@ impl Member { match self { Member::Pane(pane) => { + let pane_element = if pane.read(cx).is_zoomed() { + Empty::new().into_any() + } else { + ChildView::new(pane, cx).into_any() + }; + let leader = follower_states .iter() .find_map(|(leader_id, follower_states)| { @@ -258,7 +264,7 @@ impl Member { }; Stack::new() - .with_child(ChildView::new(pane, cx).contained().with_border(border)) + .with_child(pane_element.contained().with_border(border)) .with_children(leader_status_box) .into_any() } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ad717c362b..f4ff8d5c91 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -441,6 +441,7 @@ pub struct Workspace { weak_self: WeakViewHandle, remote_entity_subscription: Option, modal: Option, + zoomed: Option, center: PaneGroup, left_dock: ViewHandle, bottom_dock: ViewHandle, @@ -627,8 +628,9 @@ impl Workspace { ]; let mut this = Workspace { - modal: None, weak_self: weak_handle.clone(), + modal: None, + zoomed: None, center: PaneGroup::new(center_pane.clone()), panes: vec![center_pane.clone()], panes_by_item: Default::default(), @@ -1303,6 +1305,16 @@ impl Workspace { } } + pub fn zoom_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext) { + self.zoomed = Some(view); + cx.notify(); + } + + pub fn zoom_out(&mut self, cx: &mut ViewContext) { + self.zoomed.take(); + cx.notify(); + } + pub fn items<'a>( &'a self, cx: &'a AppContext, @@ -1685,6 +1697,16 @@ impl Workspace { pane::Event::Focus => { self.handle_pane_focused(pane.clone(), cx); } + pane::Event::ZoomIn => { + pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); + self.zoom_in(pane.into_any(), cx); + } + pane::Event::ZoomOut => { + if self.zoomed.as_ref().map_or(false, |zoomed| *zoomed == pane) { + pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); + self.zoom_out(cx); + } + } } self.serialize_workspace(cx); @@ -2735,6 +2757,21 @@ impl View for Workspace { }) .with_child(Overlay::new( Stack::new() + .with_children(self.zoomed.as_ref().map(|zoomed| { + enum ZoomBackground {} + + ChildView::new(zoomed, cx) + .contained() + .with_style(theme.workspace.zoomed_foreground) + .aligned() + .contained() + .with_style(theme.workspace.zoomed_background) + .mouse::(0) + .capture_all() + .on_down(MouseButton::Left, |_, this: &mut Self, cx| { + this.zoom_out(cx); + }) + })) .with_children(self.modal.as_ref().map(|modal| { ChildView::new(modal, cx) .contained() diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index fe359b0501..501debc876 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -118,6 +118,15 @@ export default function workspace(colorScheme: ColorScheme) { }, cursor: "Arrow", }, + zoomedBackground: { + padding: 10, + cursor: "Arrow", + background: withOpacity(background(colorScheme.lowest), 0.5) + }, + zoomedForeground: { + shadow: colorScheme.modalShadow, + border: border(colorScheme.highest, { overlay: true }), + }, dock: { initialSize: 240, border: border(layer, { left: true, right: true }), From c03e470fe6e0ef750f784d757ea31ddf689c3d05 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 May 2023 17:10:30 +0200 Subject: [PATCH 034/131] Introduce `Panel::can_zoom` --- crates/project_panel/src/project_panel.rs | 4 ++++ crates/terminal_view/src/terminal_panel.rs | 10 +++++++--- crates/workspace/src/dock.rs | 18 +++++++++++++++--- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index e271c9970b..b1f0740df0 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1373,6 +1373,10 @@ impl workspace::dock::Panel for ProjectPanel { cx.global::().project_panel.default_width } + fn can_zoom(&self, _cx: &gpui::WindowContext) -> bool { + false + } + fn icon_path(&self) -> &'static str { "icons/folder_tree_16.svg" } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 995c8be50b..f3bfbdd730 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1,7 +1,7 @@ use crate::TerminalView; use gpui::{ elements::*, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle, - WeakViewHandle, + WeakViewHandle, WindowContext, }; use project::Project; use settings::{settings_file::SettingsFile, Settings, TerminalDockPosition, WorkingDirectory}; @@ -149,7 +149,7 @@ impl View for TerminalPanel { } impl Panel for TerminalPanel { - fn position(&self, cx: &gpui::WindowContext) -> DockPosition { + fn position(&self, cx: &WindowContext) -> DockPosition { let settings = cx.global::(); let dock = settings .terminal_overrides @@ -179,7 +179,7 @@ impl Panel for TerminalPanel { }); } - fn default_size(&self, cx: &gpui::WindowContext) -> f32 { + fn default_size(&self, cx: &WindowContext) -> f32 { let settings = &cx.global::().terminal_overrides; match self.position(cx) { DockPosition::Left | DockPosition::Right => settings.default_width.unwrap_or(640.), @@ -187,6 +187,10 @@ impl Panel for TerminalPanel { } } + fn can_zoom(&self, _: &WindowContext) -> bool { + true + } + fn icon_path(&self) -> &'static str { "icons/terminal_12.svg" } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 86c2cc3929..6d0b34dc27 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -9,11 +9,16 @@ use serde::Deserialize; use settings::Settings; use std::rc::Rc; +pub fn init(cx: &mut AppContext) { + cx.capture_action(Dock::toggle_zoom); +} + pub trait Panel: View { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); fn default_size(&self, cx: &WindowContext) -> f32; + fn can_zoom(&self, cx: &WindowContext) -> bool; fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; fn icon_label(&self, _: &AppContext) -> Option { @@ -30,6 +35,7 @@ pub trait PanelHandle { fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; fn set_position(&self, position: DockPosition, cx: &mut WindowContext); fn default_size(&self, cx: &WindowContext) -> f32; + fn can_zoom(&self, cx: &WindowContext) -> bool; fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -61,6 +67,10 @@ where self.read(cx).default_size(cx) } + fn can_zoom(&self, cx: &WindowContext) -> bool { + self.read(cx).can_zoom(cx) + } + fn icon_path(&self, cx: &WindowContext) -> &'static str { self.read(cx).icon_path() } @@ -313,9 +323,7 @@ impl View for Dock { .resizable( self.position.to_resize_handle_side(), size, - |dock: &mut Self, size, cx| { - dock.resize_active_panel(size, cx); - }, + |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), ) .into_any() } else { @@ -526,6 +534,10 @@ pub(crate) mod test { } } + fn can_zoom(&self, _cx: &WindowContext) -> bool { + unimplemented!() + } + fn icon_path(&self) -> &'static str { "icons/test_panel.svg" } From adf361b374b7a293881795aa24093bac8aae5751 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 16 May 2023 11:49:48 +0200 Subject: [PATCH 035/131] Implement zooming for panes and docks --- assets/keymaps/default.json | 2 +- crates/gpui/src/elements.rs | 7 +-- crates/workspace/src/dock.rs | 77 +++++++++++++++++++------- crates/workspace/src/pane.rs | 10 ++-- crates/workspace/src/workspace.rs | 89 +++++++++++++++++++------------ 5 files changed, 122 insertions(+), 63 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 768e0c6ed1..37c010f0f9 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -220,7 +220,7 @@ "alt-cmd-c": "search::ToggleCaseSensitive", "alt-cmd-w": "search::ToggleWholeWord", "alt-cmd-r": "search::ToggleRegex", - "shift-escape": "pane::ToggleZoom" + "shift-escape": "workspace::ToggleZoom" } }, // Bindings from VS Code diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index 40b6a50816..a566751fd5 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -33,11 +33,8 @@ use crate::{ rect::RectF, vector::{vec2f, Vector2F}, }, - json, - platform::MouseButton, - scene::MouseDown, - Action, EventContext, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, - WeakViewHandle, WindowContext, + json, Action, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle, + WindowContext, }; use anyhow::{anyhow, Result}; use collections::HashMap; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 6d0b34dc27..2e4303e70f 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,4 +1,4 @@ -use crate::{StatusItemView, Workspace}; +use crate::{StatusItemView, ToggleZoom, Workspace}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, @@ -10,7 +10,7 @@ use settings::Settings; use std::rc::Rc; pub fn init(cx: &mut AppContext) { - cx.capture_action(Dock::toggle_zoom); + cx.add_action(Dock::toggle_zoom); } pub trait Panel: View { @@ -98,6 +98,10 @@ impl From<&dyn PanelHandle> for AnyViewHandle { } } +pub enum Event { + ZoomIn, +} + pub struct Dock { position: DockPosition, panel_entries: Vec, @@ -141,6 +145,7 @@ struct PanelEntry { panel: Rc, size: f32, context_menu: ViewHandle, + zoomed: bool, _subscriptions: [Subscription; 2], } @@ -187,6 +192,25 @@ impl Dock { cx.notify(); } + pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { + for (ix, entry) in self.panel_entries.iter_mut().enumerate() { + if ix == self.active_panel_index && entry.panel.can_zoom(cx) { + entry.zoomed = zoomed; + } else { + entry.zoomed = false; + } + } + + cx.notify(); + } + + pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { + cx.propagate_action(); + if !self.active_entry().map_or(false, |entry| entry.zoomed) { + cx.emit(Event::ZoomIn); + } + } + pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) { let subscriptions = [ cx.observe(&panel, |_, _, cx| cx.notify()), @@ -214,6 +238,7 @@ impl Dock { self.panel_entries.push(PanelEntry { panel: Rc::new(panel), size, + zoomed: false, context_menu: cx.add_view(|cx| { let mut menu = ContextMenu::new(dock_view_id, cx); menu.set_position_mode(OverlayPositionMode::Local); @@ -260,10 +285,22 @@ impl Dock { } pub fn active_panel(&self) -> Option<&Rc> { + let entry = self.active_entry()?; + Some(&entry.panel) + } + + fn active_entry(&self) -> Option<&PanelEntry> { if self.is_open { - self.panel_entries - .get(self.active_panel_index) - .map(|entry| &entry.panel) + self.panel_entries.get(self.active_panel_index) + } else { + None + } + } + + pub fn zoomed_panel(&self) -> Option { + let entry = self.active_entry()?; + if entry.zoomed { + Some(entry.panel.as_any().clone()) } else { None } @@ -305,7 +342,7 @@ impl Dock { } impl Entity for Dock { - type Event = (); + type Event = Event; } impl View for Dock { @@ -314,18 +351,22 @@ impl View for Dock { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - if let Some(active_panel) = self.active_panel() { - let size = self.active_panel_size().unwrap(); - let style = &cx.global::().theme.workspace.dock; - ChildView::new(active_panel.as_any(), cx) - .contained() - .with_style(style.container) - .resizable( - self.position.to_resize_handle_side(), - size, - |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), - ) - .into_any() + if let Some(active_entry) = self.active_entry() { + if active_entry.zoomed { + Empty::new().into_any() + } else { + let size = self.active_panel_size().unwrap(); + let style = &cx.global::().theme.workspace.dock; + ChildView::new(active_entry.panel.as_any(), cx) + .contained() + .with_style(style.container) + .resizable( + self.position.to_resize_handle_side(), + size, + |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), + ) + .into_any() + } } else { Empty::new().into_any() } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index a216f6181c..6ccd1573bc 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2,7 +2,8 @@ mod dragged_item_receiver; use super::{ItemHandle, SplitDirection}; use crate::{ - item::WeakItemHandle, toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, Workspace, + item::WeakItemHandle, toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, ToggleZoom, + Workspace, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -69,7 +70,6 @@ actions!( SplitUp, SplitRight, SplitDown, - ToggleZoom, ] ); @@ -135,7 +135,6 @@ pub enum Event { ChangeItemTitle, Focus, ZoomIn, - ZoomOut, } pub struct Pane { @@ -662,9 +661,8 @@ impl Pane { } pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { - if self.zoomed { - cx.emit(Event::ZoomOut); - } else { + cx.propagate_action(); + if !self.zoomed { cx.emit(Event::ZoomIn); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f4ff8d5c91..af8c958db5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -119,7 +119,8 @@ actions!( NewSearch, Feedback, Restart, - Welcome + Welcome, + ToggleZoom, ] ); @@ -181,6 +182,7 @@ pub type WorkspaceId = i64; impl_actions!(workspace, [ActivatePane]); pub fn init(app_state: Arc, cx: &mut AppContext) { + dock::init(cx); pane::init(cx); notifications::init(cx); @@ -230,6 +232,7 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { }, ); cx.add_action(Workspace::toggle_panel); + cx.add_action(Workspace::toggle_zoom); cx.add_action(Workspace::focus_center); cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| { workspace.activate_previous_pane(cx) @@ -441,7 +444,6 @@ pub struct Workspace { weak_self: WeakViewHandle, remote_entity_subscription: Option, modal: Option, - zoomed: Option, center: PaneGroup, left_dock: ViewHandle, bottom_dock: ViewHandle, @@ -593,7 +595,7 @@ impl Workspace { active_call = Some((call, subscriptions)); } - let subscriptions = vec![ + let mut subscriptions = vec![ cx.observe_fullscreen(|_, _, cx| cx.notify()), cx.observe_window_activation(Self::on_window_activation_changed), cx.observe_window_bounds(move |_, mut bounds, display, cx| { @@ -613,24 +615,14 @@ impl Workspace { .spawn(DB.set_window_bounds(workspace_id, bounds, display)) .detach_and_log_err(cx); }), - cx.observe(&left_dock, |this, _, cx| { - this.serialize_workspace(cx); - cx.notify(); - }), - cx.observe(&bottom_dock, |this, _, cx| { - this.serialize_workspace(cx); - cx.notify(); - }), - cx.observe(&right_dock, |this, _, cx| { - this.serialize_workspace(cx); - cx.notify(); - }), ]; + subscriptions.extend(Self::register_dock(&left_dock, cx)); + subscriptions.extend(Self::register_dock(&bottom_dock, cx)); + subscriptions.extend(Self::register_dock(&right_dock, cx)); let mut this = Workspace { weak_self: weak_handle.clone(), modal: None, - zoomed: None, center: PaneGroup::new(center_pane.clone()), panes: vec![center_pane.clone()], panes_by_item: Default::default(), @@ -1305,14 +1297,16 @@ impl Workspace { } } - pub fn zoom_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext) { - self.zoomed = Some(view); - cx.notify(); - } - - pub fn zoom_out(&mut self, cx: &mut ViewContext) { - self.zoomed.take(); - cx.notify(); + fn zoomed(&self, cx: &AppContext) -> Option { + self.left_dock + .read(cx) + .zoomed_panel() + .or(self.bottom_dock.read(cx).zoomed_panel()) + .or(self.right_dock.read(cx).zoomed_panel()) + .or_else(|| { + let pane = self.panes.iter().find(|pane| pane.read(cx).is_zoomed())?; + Some(pane.clone().into_any()) + }) } pub fn items<'a>( @@ -1470,11 +1464,46 @@ impl Workspace { cx.notify(); } + fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { + // Any time the zoom is toggled we will zoom out all panes and docks. Then, + // the dock or pane that was zoomed will emit an event to zoom itself back in. + self.zoom_out(cx); + } + + fn zoom_out(&mut self, cx: &mut ViewContext) { + for pane in &self.panes { + pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); + } + + self.left_dock + .update(cx, |dock, cx| dock.set_zoomed(false, cx)); + self.bottom_dock + .update(cx, |dock, cx| dock.set_zoomed(false, cx)); + self.right_dock + .update(cx, |dock, cx| dock.set_zoomed(false, cx)); + + cx.notify(); + } + pub fn focus_center(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { cx.focus_self(); cx.notify(); } + fn register_dock(dock: &ViewHandle, cx: &mut ViewContext) -> [Subscription; 2] { + [ + cx.observe(dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), + cx.subscribe(dock, |_, dock, event, cx| { + dock.update(cx, |dock, cx| match event { + dock::Event::ZoomIn => dock.set_zoomed(true, cx), + }) + }), + ] + } + fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { let pane = cx.add_view(|cx| Pane::new(self.weak_handle(), self.app_state.background_actions, cx)); @@ -1699,13 +1728,7 @@ impl Workspace { } pane::Event::ZoomIn => { pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); - self.zoom_in(pane.into_any(), cx); - } - pane::Event::ZoomOut => { - if self.zoomed.as_ref().map_or(false, |zoomed| *zoomed == pane) { - pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); - self.zoom_out(cx); - } + cx.notify(); } } @@ -2757,10 +2780,10 @@ impl View for Workspace { }) .with_child(Overlay::new( Stack::new() - .with_children(self.zoomed.as_ref().map(|zoomed| { + .with_children(self.zoomed(cx).map(|zoomed| { enum ZoomBackground {} - ChildView::new(zoomed, cx) + ChildView::new(&zoomed, cx) .contained() .with_style(theme.workspace.zoomed_foreground) .aligned() From f87ae6032e6eb0d11fcf039b64aa9b6c729000b4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 16 May 2023 18:01:18 +0200 Subject: [PATCH 036/131] Don't rely on action propagation for zooming in and out Co-Authored-By: Antonio Scandurra --- crates/gpui/src/app.rs | 6 ++ crates/project_panel/src/project_panel.rs | 8 ++- crates/terminal_view/src/terminal_panel.rs | 16 +++++- crates/workspace/src/dock.rs | 67 +++++++++++++--------- crates/workspace/src/pane.rs | 6 +- crates/workspace/src/workspace.rs | 55 ++++++++---------- 6 files changed, 94 insertions(+), 64 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 65418dd997..9cc1ad908a 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3972,6 +3972,12 @@ impl Clone for AnyViewHandle { } } +impl PartialEq for AnyViewHandle { + fn eq(&self, other: &Self) -> bool { + self.window_id == other.window_id && self.view_id == other.view_id + } +} + impl PartialEq> for AnyViewHandle { fn eq(&self, other: &ViewHandle) -> bool { self.window_id == other.window_id && self.view_id == other.view_id diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index b1f0740df0..c448ac4e43 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1373,10 +1373,16 @@ impl workspace::dock::Panel for ProjectPanel { cx.global::().project_panel.default_width } - fn can_zoom(&self, _cx: &gpui::WindowContext) -> bool { + fn should_zoom_in_on_event(_: &Self::Event) -> bool { false } + fn should_zoom_out_on_event(_: &Self::Event) -> bool { + false + } + + fn set_zoomed(&mut self, _: bool, _: &mut ViewContext) {} + fn icon_path(&self) -> &'static str { "icons/folder_tree_16.svg" } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index f3bfbdd730..68783a5bc1 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -18,6 +18,8 @@ pub fn init(cx: &mut AppContext) { pub enum Event { Close, DockPositionChanged, + ZoomIn, + ZoomOut, } pub struct TerminalPanel { @@ -96,6 +98,8 @@ impl TerminalPanel { ) { match event { pane::Event::Remove => cx.emit(Event::Close), + pane::Event::ZoomIn => cx.emit(Event::ZoomIn), + pane::Event::ZoomOut => cx.emit(Event::ZoomOut), _ => {} } } @@ -187,8 +191,16 @@ impl Panel for TerminalPanel { } } - fn can_zoom(&self, _: &WindowContext) -> bool { - true + fn should_zoom_in_on_event(event: &Event) -> bool { + matches!(event, Event::ZoomIn) + } + + fn should_zoom_out_on_event(event: &Event) -> bool { + matches!(event, Event::ZoomOut) + } + + fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { + self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx)); } fn icon_path(&self) -> &'static str { diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 2e4303e70f..40e7288de0 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,4 +1,4 @@ -use crate::{StatusItemView, ToggleZoom, Workspace}; +use crate::{StatusItemView, Workspace}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, @@ -9,22 +9,20 @@ use serde::Deserialize; use settings::Settings; use std::rc::Rc; -pub fn init(cx: &mut AppContext) { - cx.add_action(Dock::toggle_zoom); -} - pub trait Panel: View { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); fn default_size(&self, cx: &WindowContext) -> f32; - fn can_zoom(&self, cx: &WindowContext) -> bool; fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; fn icon_label(&self, _: &AppContext) -> Option { None } fn should_change_position_on_event(_: &Self::Event) -> bool; + fn should_zoom_in_on_event(_: &Self::Event) -> bool; + fn should_zoom_out_on_event(_: &Self::Event) -> bool; + fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext); fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; } @@ -34,8 +32,8 @@ pub trait PanelHandle { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; fn set_position(&self, position: DockPosition, cx: &mut WindowContext); + fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext); fn default_size(&self, cx: &WindowContext) -> f32; - fn can_zoom(&self, cx: &WindowContext) -> bool; fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -67,8 +65,8 @@ where self.read(cx).default_size(cx) } - fn can_zoom(&self, cx: &WindowContext) -> bool { - self.read(cx).can_zoom(cx) + fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.set_zoomed(zoomed, cx)) } fn icon_path(&self, cx: &WindowContext) -> &'static str { @@ -98,10 +96,6 @@ impl From<&dyn PanelHandle> for AnyViewHandle { } } -pub enum Event { - ZoomIn, -} - pub struct Dock { position: DockPosition, panel_entries: Vec, @@ -192,22 +186,33 @@ impl Dock { cx.notify(); } - pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { - for (ix, entry) in self.panel_entries.iter_mut().enumerate() { - if ix == self.active_panel_index && entry.panel.can_zoom(cx) { - entry.zoomed = zoomed; - } else { + pub fn set_panel_zoomed( + &mut self, + panel: &AnyViewHandle, + zoomed: bool, + cx: &mut ViewContext, + ) { + for entry in &mut self.panel_entries { + if entry.panel.as_any() == panel { + if zoomed != entry.zoomed { + entry.zoomed = zoomed; + entry.panel.set_zoomed(zoomed, cx); + } + } else if entry.zoomed { entry.zoomed = false; + entry.panel.set_zoomed(false, cx); } } cx.notify(); } - pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { - cx.propagate_action(); - if !self.active_entry().map_or(false, |entry| entry.zoomed) { - cx.emit(Event::ZoomIn); + pub fn zoom_out(&mut self, cx: &mut ViewContext) { + for entry in &mut self.panel_entries { + if entry.zoomed { + entry.zoomed = false; + entry.panel.set_zoomed(false, cx); + } } } @@ -342,7 +347,7 @@ impl Dock { } impl Entity for Dock { - type Event = Event; + type Event = (); } impl View for Dock { @@ -568,6 +573,10 @@ pub(crate) mod test { cx.emit(TestPanelEvent::PositionChanged); } + fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext) { + unimplemented!() + } + fn default_size(&self, _: &WindowContext) -> f32 { match self.position.axis() { Axis::Horizontal => 300., @@ -575,10 +584,6 @@ pub(crate) mod test { } } - fn can_zoom(&self, _cx: &WindowContext) -> bool { - unimplemented!() - } - fn icon_path(&self) -> &'static str { "icons/test_panel.svg" } @@ -591,6 +596,14 @@ pub(crate) mod test { matches!(event, TestPanelEvent::PositionChanged) } + fn should_zoom_in_on_event(_: &Self::Event) -> bool { + false + } + + fn should_zoom_out_on_event(_: &Self::Event) -> bool { + false + } + fn should_activate_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool { matches!(event, TestPanelEvent::Activated) } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 6ccd1573bc..73738ab413 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -135,6 +135,7 @@ pub enum Event { ChangeItemTitle, Focus, ZoomIn, + ZoomOut, } pub struct Pane { @@ -661,8 +662,9 @@ impl Pane { } pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { - cx.propagate_action(); - if !self.zoomed { + if self.zoomed { + cx.emit(Event::ZoomOut); + } else { cx.emit(Event::ZoomIn); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index af8c958db5..06832e1385 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -182,7 +182,6 @@ pub type WorkspaceId = i64; impl_actions!(workspace, [ActivatePane]); pub fn init(app_state: Arc, cx: &mut AppContext) { - dock::init(cx); pane::init(cx); notifications::init(cx); @@ -232,7 +231,6 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { }, ); cx.add_action(Workspace::toggle_panel); - cx.add_action(Workspace::toggle_zoom); cx.add_action(Workspace::focus_center); cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| { workspace.activate_previous_pane(cx) @@ -595,7 +593,7 @@ impl Workspace { active_call = Some((call, subscriptions)); } - let mut subscriptions = vec![ + let subscriptions = vec![ cx.observe_fullscreen(|_, _, cx| cx.notify()), cx.observe_window_activation(Self::on_window_activation_changed), cx.observe_window_bounds(move |_, mut bounds, display, cx| { @@ -615,10 +613,19 @@ impl Workspace { .spawn(DB.set_window_bounds(workspace_id, bounds, display)) .detach_and_log_err(cx); }), + cx.observe(&left_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), + cx.observe(&bottom_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), + cx.observe(&right_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), ]; - subscriptions.extend(Self::register_dock(&left_dock, cx)); - subscriptions.extend(Self::register_dock(&bottom_dock, cx)); - subscriptions.extend(Self::register_dock(&right_dock, cx)); let mut this = Workspace { weak_self: weak_handle.clone(), @@ -881,6 +888,11 @@ impl Workspace { dock.activate_panel(dock.panels_len() - 1, cx); } }); + } else if T::should_zoom_in_on_event(event) { + this.zoom_out(cx); + dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx)); + } else if T::should_zoom_out_on_event(event) { + this.zoom_out(cx); } } }) @@ -1464,23 +1476,14 @@ impl Workspace { cx.notify(); } - fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { - // Any time the zoom is toggled we will zoom out all panes and docks. Then, - // the dock or pane that was zoomed will emit an event to zoom itself back in. - self.zoom_out(cx); - } - fn zoom_out(&mut self, cx: &mut ViewContext) { for pane in &self.panes { pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); } - self.left_dock - .update(cx, |dock, cx| dock.set_zoomed(false, cx)); - self.bottom_dock - .update(cx, |dock, cx| dock.set_zoomed(false, cx)); - self.right_dock - .update(cx, |dock, cx| dock.set_zoomed(false, cx)); + self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx)); + self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx)); + self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx)); cx.notify(); } @@ -1490,20 +1493,6 @@ impl Workspace { cx.notify(); } - fn register_dock(dock: &ViewHandle, cx: &mut ViewContext) -> [Subscription; 2] { - [ - cx.observe(dock, |this, _, cx| { - this.serialize_workspace(cx); - cx.notify(); - }), - cx.subscribe(dock, |_, dock, event, cx| { - dock.update(cx, |dock, cx| match event { - dock::Event::ZoomIn => dock.set_zoomed(true, cx), - }) - }), - ] - } - fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { let pane = cx.add_view(|cx| Pane::new(self.weak_handle(), self.app_state.background_actions, cx)); @@ -1727,9 +1716,11 @@ impl Workspace { self.handle_pane_focused(pane.clone(), cx); } pane::Event::ZoomIn => { + self.zoom_out(cx); pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); cx.notify(); } + pane::Event::ZoomOut => self.zoom_out(cx), } self.serialize_workspace(cx); From 981129ef8ece66868ae17172542e82a837bded5c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 May 2023 15:06:58 +0200 Subject: [PATCH 037/131] Show a panel/pane as zoomed only if it's the active item in workspace --- crates/gpui/src/app.rs | 2 +- crates/project_panel/src/project_panel.rs | 34 ++++- crates/terminal_view/src/terminal_panel.rs | 20 ++- crates/workspace/src/dock.rs | 87 ++++++++----- crates/workspace/src/pane.rs | 8 +- crates/workspace/src/pane_group.rs | 10 +- crates/workspace/src/workspace.rs | 143 ++++++++++++--------- 7 files changed, 195 insertions(+), 109 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 9cc1ad908a..5cb2e8d931 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -4224,7 +4224,7 @@ impl Hash for WeakViewHandle { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct AnyWeakViewHandle { window_id: usize, view_id: usize, diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index c448ac4e43..63263b23c7 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -13,7 +13,7 @@ use gpui::{ keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext, - ViewHandle, WeakViewHandle, + ViewHandle, WeakViewHandle, WindowContext, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; @@ -48,6 +48,7 @@ pub struct ProjectPanel { context_menu: ViewHandle, dragged_entry_destination: Option>, workspace: WeakViewHandle, + has_focus: bool, } #[derive(Copy, Clone)] @@ -139,6 +140,7 @@ pub enum Event { focus_opened_item: bool, }, DockPositionChanged, + Focus, } impl ProjectPanel { @@ -214,6 +216,7 @@ impl ProjectPanel { context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)), dragged_entry_destination: None, workspace: workspace.weak_handle(), + has_focus: false, }; this.update_visible_entries(None, cx); @@ -259,7 +262,7 @@ impl ProjectPanel { } } } - Event::DockPositionChanged => {} + _ => {} } }) .detach(); @@ -1338,6 +1341,17 @@ impl View for ProjectPanel { Self::reset_to_default_keymap_context(keymap); keymap.add_identifier("menu"); } + + fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { + if !self.has_focus { + self.has_focus = true; + cx.emit(Event::Focus); + } + } + + fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext) { + self.has_focus = false; + } } impl Entity for ProjectPanel { @@ -1345,7 +1359,7 @@ impl Entity for ProjectPanel { } impl workspace::dock::Panel for ProjectPanel { - fn position(&self, cx: &gpui::WindowContext) -> DockPosition { + fn position(&self, cx: &WindowContext) -> DockPosition { let settings = cx.global::(); match settings.project_panel.dock { settings::ProjectPanelDockPosition::Left => DockPosition::Left, @@ -1369,7 +1383,7 @@ impl workspace::dock::Panel for ProjectPanel { }) } - fn default_size(&self, cx: &gpui::WindowContext) -> f32 { + fn default_size(&self, cx: &WindowContext) -> f32 { cx.global::().project_panel.default_width } @@ -1395,13 +1409,21 @@ impl workspace::dock::Panel for ProjectPanel { matches!(event, Event::DockPositionChanged) } - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_activate_on_event(_: &Self::Event) -> bool { false } - fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_close_on_event(_: &Self::Event) -> bool { false } + + fn has_focus(&self, _: &WindowContext) -> bool { + self.has_focus + } + + fn is_focus_event(event: &Self::Event) -> bool { + matches!(event, Event::Focus) + } } impl ClipboardEntry { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 68783a5bc1..25302d7df5 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -20,6 +20,7 @@ pub enum Event { DockPositionChanged, ZoomIn, ZoomOut, + Focus, } pub struct TerminalPanel { @@ -100,6 +101,7 @@ impl TerminalPanel { pane::Event::Remove => cx.emit(Event::Close), pane::Event::ZoomIn => cx.emit(Event::ZoomIn), pane::Event::ZoomOut => cx.emit(Event::ZoomOut), + pane::Event::Focus => cx.emit(Event::Focus), _ => {} } } @@ -149,6 +151,10 @@ impl View for TerminalPanel { if self.pane.read(cx).items_len() == 0 { self.add_terminal(&Default::default(), cx) } + + if cx.is_self_focused() { + cx.focus(&self.pane); + } } } @@ -211,7 +217,7 @@ impl Panel for TerminalPanel { "Terminals".to_string() } - fn icon_label(&self, cx: &AppContext) -> Option { + fn icon_label(&self, cx: &WindowContext) -> Option { let count = self.pane.read(cx).items_len(); if count == 0 { None @@ -224,11 +230,19 @@ impl Panel for TerminalPanel { matches!(event, Event::DockPositionChanged) } - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_activate_on_event(_: &Self::Event) -> bool { false } - fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { + fn should_close_on_event(event: &Event) -> bool { matches!(event, Event::Close) } + + fn has_focus(&self, cx: &WindowContext) -> bool { + self.pane.read(cx).has_focus() + } + + fn is_focus_event(event: &Self::Event) -> bool { + matches!(event, Event::Focus) + } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 40e7288de0..edd6a5959f 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,9 +1,8 @@ use crate::{StatusItemView, Workspace}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ - elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, - AppContext, Axis, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, - WindowContext, + elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, Axis, + Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use serde::Deserialize; use settings::Settings; @@ -16,15 +15,17 @@ pub trait Panel: View { fn default_size(&self, cx: &WindowContext) -> f32; fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; - fn icon_label(&self, _: &AppContext) -> Option { + fn icon_label(&self, _: &WindowContext) -> Option { None } fn should_change_position_on_event(_: &Self::Event) -> bool; fn should_zoom_in_on_event(_: &Self::Event) -> bool; fn should_zoom_out_on_event(_: &Self::Event) -> bool; fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext); - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; - fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; + fn should_activate_on_event(_: &Self::Event) -> bool; + fn should_close_on_event(_: &Self::Event) -> bool; + fn has_focus(&self, cx: &WindowContext) -> bool; + fn is_focus_event(_: &Self::Event) -> bool; } pub trait PanelHandle { @@ -37,7 +38,7 @@ pub trait PanelHandle { fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; - fn is_focused(&self, cx: &WindowContext) -> bool; + fn has_focus(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; } @@ -81,8 +82,8 @@ where self.read(cx).icon_label(cx) } - fn is_focused(&self, cx: &WindowContext) -> bool { - ViewHandle::is_focused(self, cx) + fn has_focus(&self, cx: &WindowContext) -> bool { + self.read(cx).has_focus(cx) } fn as_any(&self) -> &AnyViewHandle { @@ -170,6 +171,11 @@ impl Dock { self.is_open } + pub fn has_focus(&self, cx: &WindowContext) -> bool { + self.active_panel() + .map_or(false, |panel| panel.has_focus(cx)) + } + pub fn active_panel_index(&self) -> usize { self.active_panel_index } @@ -220,7 +226,7 @@ impl Dock { let subscriptions = [ cx.observe(&panel, |_, _, cx| cx.notify()), cx.subscribe(&panel, |this, panel, event, cx| { - if panel.read(cx).should_activate_on_event(event, cx) { + if T::should_activate_on_event(event) { if let Some(ix) = this .panel_entries .iter() @@ -230,7 +236,7 @@ impl Dock { this.activate_panel(ix, cx); cx.focus(&panel); } - } else if panel.read(cx).should_close_on_event(event, cx) + } else if T::should_close_on_event(event) && this.active_panel().map_or(false, |p| p.id() == panel.id()) { this.set_open(false, cx); @@ -302,10 +308,10 @@ impl Dock { } } - pub fn zoomed_panel(&self) -> Option { + pub fn zoomed_panel(&self) -> Option> { let entry = self.active_entry()?; if entry.zoomed { - Some(entry.panel.as_any().clone()) + Some(entry.panel.clone()) } else { None } @@ -344,6 +350,24 @@ impl Dock { cx.notify(); } } + + pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement { + if let Some(active_entry) = self.active_entry() { + let style = &cx.global::().theme.workspace.dock; + Empty::new() + .into_any() + .contained() + .with_style(style.container) + .resizable( + self.position.to_resize_handle_side(), + active_entry.size, + |_, _, _| {}, + ) + .into_any() + } else { + Empty::new().into_any() + } + } } impl Entity for Dock { @@ -357,21 +381,16 @@ impl View for Dock { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(active_entry) = self.active_entry() { - if active_entry.zoomed { - Empty::new().into_any() - } else { - let size = self.active_panel_size().unwrap(); - let style = &cx.global::().theme.workspace.dock; - ChildView::new(active_entry.panel.as_any(), cx) - .contained() - .with_style(style.container) - .resizable( - self.position.to_resize_handle_side(), - size, - |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), - ) - .into_any() - } + let style = &cx.global::().theme.workspace.dock; + ChildView::new(active_entry.panel.as_any(), cx) + .contained() + .with_style(style.container) + .resizable( + self.position.to_resize_handle_side(), + active_entry.size, + |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), + ) + .into_any() } else { Empty::new().into_any() } @@ -604,12 +623,20 @@ pub(crate) mod test { false } - fn should_activate_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool { + fn should_activate_on_event(event: &Self::Event) -> bool { matches!(event, TestPanelEvent::Activated) } - fn should_close_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool { + fn should_close_on_event(event: &Self::Event) -> bool { matches!(event, TestPanelEvent::Closed) } + + fn has_focus(&self, _cx: &WindowContext) -> bool { + unimplemented!() + } + + fn is_focus_event(_: &Self::Event) -> bool { + unimplemented!() + } } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 73738ab413..681a7eda27 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1699,7 +1699,11 @@ impl View for Pane { } fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { - self.has_focus = true; + if !self.has_focus { + self.has_focus = true; + cx.emit(Event::Focus); + } + self.toolbar.update(cx, |toolbar, cx| { toolbar.pane_focus_update(true, cx); }); @@ -1725,8 +1729,6 @@ impl View for Pane { .insert(active_item.id(), focused.downgrade()); } } - - cx.emit(Event::Focus); } fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index a1b35c1d65..5f6d46aa46 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -7,7 +7,7 @@ use gpui::{ elements::*, geometry::{rect::RectF, vector::Vector2F}, platform::{CursorStyle, MouseButton}, - Axis, Border, ModelHandle, ViewContext, ViewHandle, + AnyViewHandle, Axis, Border, ModelHandle, ViewContext, ViewHandle, }; use project::Project; use serde::Deserialize; @@ -72,6 +72,7 @@ impl PaneGroup { follower_states: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, active_pane: &ViewHandle, + zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { @@ -81,6 +82,7 @@ impl PaneGroup { follower_states, active_call, active_pane, + zoomed, app_state, cx, ) @@ -135,6 +137,7 @@ impl Member { follower_states: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, active_pane: &ViewHandle, + zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { @@ -142,7 +145,7 @@ impl Member { match self { Member::Pane(pane) => { - let pane_element = if pane.read(cx).is_zoomed() { + let pane_element = if Some(&**pane) == zoomed { Empty::new().into_any() } else { ChildView::new(pane, cx).into_any() @@ -274,6 +277,7 @@ impl Member { follower_states, active_call, active_pane, + zoomed, app_state, cx, ), @@ -378,6 +382,7 @@ impl PaneAxis { follower_state: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, active_pane: &ViewHandle, + zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { @@ -395,6 +400,7 @@ impl PaneAxis { follower_state, active_call, active_pane, + zoomed, app_state, cx, ); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 06832e1385..0fd961a768 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -893,6 +893,8 @@ impl Workspace { dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx)); } else if T::should_zoom_out_on_event(event) { this.zoom_out(cx); + } else if T::is_focus_event(event) { + cx.notify(); } } }) @@ -1309,16 +1311,42 @@ impl Workspace { } } - fn zoomed(&self, cx: &AppContext) -> Option { - self.left_dock - .read(cx) - .zoomed_panel() - .or(self.bottom_dock.read(cx).zoomed_panel()) - .or(self.right_dock.read(cx).zoomed_panel()) - .or_else(|| { - let pane = self.panes.iter().find(|pane| pane.read(cx).is_zoomed())?; - Some(pane.clone().into_any()) - }) + fn zoomed(&self, cx: &WindowContext) -> Option { + self.zoomed_panel_for_dock(DockPosition::Left, cx) + .or_else(|| self.zoomed_panel_for_dock(DockPosition::Bottom, cx)) + .or_else(|| self.zoomed_panel_for_dock(DockPosition::Right, cx)) + .or_else(|| self.zoomed_pane(cx)) + } + + fn zoomed_panel_for_dock( + &self, + position: DockPosition, + cx: &WindowContext, + ) -> Option { + let (dock, other_docks) = match position { + DockPosition::Left => (&self.left_dock, [&self.bottom_dock, &self.right_dock]), + DockPosition::Bottom => (&self.bottom_dock, [&self.left_dock, &self.right_dock]), + DockPosition::Right => (&self.right_dock, [&self.left_dock, &self.bottom_dock]), + }; + + let zoomed_panel = dock.read(&cx).zoomed_panel()?; + if other_docks.iter().all(|dock| !dock.read(cx).has_focus(cx)) + && !self.active_pane.read(cx).has_focus() + { + Some(zoomed_panel.as_any().clone()) + } else { + None + } + } + + fn zoomed_pane(&self, cx: &WindowContext) -> Option { + let active_pane = self.active_pane.read(cx); + let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock]; + if active_pane.is_zoomed() && docks.iter().all(|dock| !dock.read(cx).has_focus(cx)) { + Some(self.active_pane.clone().into_any()) + } else { + None + } } pub fn items<'a>( @@ -1433,7 +1461,7 @@ impl Workspace { }); if let Some(active_item) = active_item { - if active_item.is_focused(cx) { + if active_item.has_focus(cx) { cx.focus_self(); } else { cx.focus(active_item.as_any()); @@ -1464,7 +1492,7 @@ impl Workspace { dock.active_panel().cloned() }); if let Some(active_item) = active_item { - if active_item.is_focused(cx) { + if active_item.has_focus(cx) { cx.focus_self(); } else { cx.focus(active_item.as_any()); @@ -1663,7 +1691,6 @@ impl Workspace { }); self.active_item_path_changed(cx); self.last_active_center_pane = Some(pane.downgrade()); - cx.notify(); } self.update_followers( @@ -1677,6 +1704,8 @@ impl Workspace { }), cx, ); + + cx.notify(); } fn handle_pane_event( @@ -1716,9 +1745,11 @@ impl Workspace { self.handle_pane_focused(pane.clone(), cx); } pane::Event::ZoomIn => { - self.zoom_out(cx); - pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); - cx.notify(); + if pane == self.active_pane { + self.zoom_out(cx); + pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); + cx.notify(); + } } pane::Event::ZoomOut => self.zoom_out(cx), } @@ -2646,6 +2677,33 @@ impl Workspace { }); Self::new(None, 0, project, app_state, cx) } + + fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option> { + let dock = match position { + DockPosition::Left => &self.left_dock, + DockPosition::Right => &self.right_dock, + DockPosition::Bottom => &self.bottom_dock, + }; + let active_panel = dock.read(cx).active_panel()?; + let element = if Some(active_panel.as_any()) == self.zoomed(cx).as_ref() { + dock.read(cx).render_placeholder(cx) + } else { + ChildView::new(dock, cx).into_any() + }; + + Some( + element + .constrained() + .dynamically(move |constraint, _, cx| match position { + DockPosition::Left | DockPosition::Right => SizeConstraint::new( + Vector2F::new(20., constraint.min.y()), + Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()), + ), + _ => constraint, + }) + .into_any(), + ) + } } fn notify_if_database_failed(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { @@ -2702,25 +2760,7 @@ impl View for Workspace { .with_child({ let project = self.project.clone(); Flex::row() - .with_children( - if self.left_dock.read(cx).active_panel().is_some() { - Some( - ChildView::new(&self.left_dock, cx) - .constrained() - .dynamically(|constraint, _, cx| { - SizeConstraint::new( - Vector2F::new(20., constraint.min.y()), - Vector2F::new( - cx.window_size().x() * 0.8, - constraint.max.y(), - ), - ) - }), - ) - } else { - None - }, - ) + .with_children(self.render_dock(DockPosition::Left, cx)) .with_child( Flex::column() .with_child( @@ -2730,44 +2770,18 @@ impl View for Workspace { &self.follower_states_by_leader, self.active_call(), self.active_pane(), + self.zoomed(cx).as_ref(), &self.app_state, cx, )) .flex(1., true), ) .with_children( - if self - .bottom_dock - .read(cx) - .active_panel() - .is_some() - { - Some(ChildView::new(&self.bottom_dock, cx)) - } else { - None - }, + self.render_dock(DockPosition::Bottom, cx), ) .flex(1., true), ) - .with_children( - if self.right_dock.read(cx).active_panel().is_some() { - Some( - ChildView::new(&self.right_dock, cx) - .constrained() - .dynamically(|constraint, _, cx| { - SizeConstraint::new( - Vector2F::new(20., constraint.min.y()), - Vector2F::new( - cx.window_size().x() * 0.8, - constraint.max.y(), - ), - ) - }), - ) - } else { - None - }, - ) + .with_children(self.render_dock(DockPosition::Right, cx)) }) .with_child(Overlay::new( Stack::new() @@ -2810,6 +2824,7 @@ impl View for Workspace { if cx.is_self_focused() { cx.focus(&self.active_pane); } + cx.notify(); } } From 747fbfadeb994e2d52bc42f36cc3baa557d735f0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 May 2023 17:12:12 +0200 Subject: [PATCH 038/131] Notify old/new ancestors of the focused view when they change --- crates/gpui/src/app.rs | 48 ++++++++++++++++++++++++++++++++--- crates/gpui/src/app/window.rs | 7 ++--- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 5cb2e8d931..26963fa2dd 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1694,12 +1694,54 @@ impl AppContext { if let Some(invalidation) = invalidation { let appearance = cx.window.platform_window.appearance(); cx.invalidate(invalidation, appearance); - if cx.layout(refreshing).log_err().is_some() { + if let Some(old_parents) = cx.layout(refreshing).log_err() { updated_windows.insert(window_id); - // When the previously-focused view isn't rendered and - // there isn't any pending focus, focus the root view. if let Some(focused_view_id) = cx.focused_view_id() { + let old_ancestors = std::iter::successors( + Some(focused_view_id), + |&view_id| old_parents.get(&view_id).copied(), + ) + .collect::>(); + let new_ancestors = + cx.ancestors(focused_view_id).collect::>(); + + // Notify the old ancestors of the focused view when they don't contain it anymore. + for old_ancestor in old_ancestors.iter().copied() { + if !new_ancestors.contains(&old_ancestor) { + if let Some(mut view) = + cx.views.remove(&(window_id, old_ancestor)) + { + view.focus_out( + focused_view_id, + cx, + old_ancestor, + ); + cx.views + .insert((window_id, old_ancestor), view); + } + } + } + + // Notify the new ancestors of the focused view if they contain it now. + for new_ancestor in new_ancestors.iter().copied() { + if !old_ancestors.contains(&new_ancestor) { + if let Some(mut view) = + cx.views.remove(&(window_id, new_ancestor)) + { + view.focus_in( + focused_view_id, + cx, + new_ancestor, + ); + cx.views + .insert((window_id, new_ancestor), view); + } + } + } + + // When the previously-focused view isn't rendered and + // there isn't any pending focus, focus the root view. let root_view_id = cx.window.root_view().id(); if focused_view_id != root_view_id && !cx.window.parents.contains_key(&focused_view_id) diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index bd9bd6d2db..24a3686563 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -29,6 +29,7 @@ use sqlez::{ }; use std::{ any::TypeId, + mem, ops::{Deref, DerefMut, Range}, }; use util::ResultExt; @@ -890,7 +891,7 @@ impl<'a> WindowContext<'a> { Ok(element) } - pub(crate) fn layout(&mut self, refreshing: bool) -> Result<()> { + pub(crate) fn layout(&mut self, refreshing: bool) -> Result> { let window_size = self.window.platform_window.content_size(); let root_view_id = self.window.root_view().id(); let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap(); @@ -923,11 +924,11 @@ impl<'a> WindowContext<'a> { } } - self.window.parents = new_parents; + let old_parents = mem::replace(&mut self.window.parents, new_parents); self.window .rendered_views .insert(root_view_id, rendered_root); - Ok(()) + Ok(old_parents) } pub(crate) fn paint(&mut self) -> Result { From f09744454699e0160d32c8a40ccca61e878480ea Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 May 2023 17:35:10 +0200 Subject: [PATCH 039/131] Rebind `ctrl-`` to toggle terminal panel focus Also, add `ctrl-~` to create new terminals. Co-Authored-By: Mikayla Maki --- assets/keymaps/default.json | 3 +- crates/terminal_view/src/terminal_panel.rs | 6 ++- crates/workspace/src/dock.rs | 6 +++ crates/workspace/src/workspace.rs | 45 ++++++++++------------ crates/zed/src/zed.rs | 16 +++++--- 5 files changed, 42 insertions(+), 34 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 37c010f0f9..246b699558 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -39,7 +39,8 @@ "cmd-shift-n": "workspace::NewWindow", "cmd-o": "workspace::Open", "alt-cmd-o": "projects::OpenRecent", - "ctrl-`": "workspace::NewTerminal" + "ctrl-~": "workspace::NewTerminal", + "ctrl-`": "terminal_panel::ToggleFocus" } }, { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 25302d7df5..4e90121d59 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1,7 +1,7 @@ use crate::TerminalView; use gpui::{ - elements::*, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle, - WeakViewHandle, WindowContext, + actions, elements::*, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, + ViewHandle, WeakViewHandle, WindowContext, }; use project::Project; use settings::{settings_file::SettingsFile, Settings, TerminalDockPosition, WorkingDirectory}; @@ -11,6 +11,8 @@ use workspace::{ pane, DraggedItem, Pane, Workspace, }; +actions!(terminal_panel, [ToggleFocus]); + pub fn init(cx: &mut AppContext) { cx.add_action(TerminalPanel::add_terminal); } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index edd6a5959f..a387f05e65 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -176,6 +176,12 @@ impl Dock { .map_or(false, |panel| panel.has_focus(cx)) } + pub fn panel_index(&self) -> Option { + self.panel_entries + .iter() + .position(|entry| entry.panel.as_any().is::()) + } + pub fn active_panel_index(&self) -> usize { self.active_panel_index } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 0fd961a768..278ea9d879 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -116,6 +116,7 @@ actions!( FollowNextCollaborator, ToggleLeftDock, NewTerminal, + ToggleTerminalFocus, NewSearch, Feedback, Restart, @@ -1475,33 +1476,27 @@ impl Workspace { cx.notify(); } - pub fn toggle_panel_focus( - &mut self, - dock_position: DockPosition, - panel_index: usize, - cx: &mut ViewContext, - ) { - let dock = match dock_position { - DockPosition::Left => &mut self.left_dock, - DockPosition::Bottom => &mut self.bottom_dock, - DockPosition::Right => &mut self.right_dock, - }; - let active_item = dock.update(cx, |dock, cx| { - dock.set_open(true, cx); - dock.activate_panel(panel_index, cx); - dock.active_panel().cloned() - }); - if let Some(active_item) = active_item { - if active_item.has_focus(cx) { - cx.focus_self(); - } else { - cx.focus(active_item.as_any()); + pub fn toggle_panel_focus(&mut self, cx: &mut ViewContext) { + for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { + if let Some(panel_index) = dock.read(cx).panel_index::() { + let active_item = dock.update(cx, |dock, cx| { + dock.set_open(true, cx); + dock.activate_panel(panel_index, cx); + dock.active_panel().cloned() + }); + if let Some(active_item) = active_item { + if active_item.has_focus(cx) { + cx.focus_self(); + } else { + cx.focus(active_item.as_any()); + } + } + + self.serialize_workspace(cx); + cx.notify(); + break; } } - - self.serialize_workspace(cx); - - cx.notify(); } fn zoom_out(&mut self, cx: &mut ViewContext) { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 8a40a5d1d3..fb7da693fc 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -31,14 +31,11 @@ use serde::Deserialize; use serde_json::to_string_pretty; use settings::{Settings, DEFAULT_SETTINGS_ASSET_PATH}; use std::{borrow::Cow, str, sync::Arc}; -use terminal_view::terminal_panel::TerminalPanel; +use terminal_view::terminal_panel::{self, TerminalPanel}; use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; pub use workspace; -use workspace::{ - create_and_open_local_file, dock::DockPosition, open_new, AppState, NewFile, NewWindow, - Workspace, -}; +use workspace::{create_and_open_local_file, open_new, AppState, NewFile, NewWindow, Workspace}; #[derive(Deserialize, Clone, PartialEq)] pub struct OpenBrowser { @@ -242,7 +239,14 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { |workspace: &mut Workspace, _: &project_panel::ToggleFocus, cx: &mut ViewContext| { - workspace.toggle_panel_focus(DockPosition::Left, 0, cx); + workspace.toggle_panel_focus::(cx); + }, + ); + cx.add_action( + |workspace: &mut Workspace, + _: &terminal_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace.toggle_panel_focus::(cx); }, ); cx.add_global_action({ From 05fb0519241490856d62d6ce783dde88b44975ee Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 May 2023 17:51:11 +0200 Subject: [PATCH 040/131] Store whether a panel is zoomed in the panel itself Co-Authored-By: Mikayla Maki --- crates/project_panel/src/project_panel.rs | 4 ++++ crates/terminal_view/src/terminal_panel.rs | 4 ++++ crates/workspace/src/dock.rs | 25 +++++++++++++--------- crates/workspace/src/workspace.rs | 2 +- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 63263b23c7..a89d544074 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1395,6 +1395,10 @@ impl workspace::dock::Panel for ProjectPanel { false } + fn is_zoomed(&self, _: &WindowContext) -> bool { + false + } + fn set_zoomed(&mut self, _: bool, _: &mut ViewContext) {} fn icon_path(&self) -> &'static str { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 4e90121d59..4926a8775e 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -207,6 +207,10 @@ impl Panel for TerminalPanel { matches!(event, Event::ZoomOut) } + fn is_zoomed(&self, cx: &WindowContext) -> bool { + self.pane.read(cx).is_zoomed() + } + fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx)); } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index a387f05e65..5c501f64ad 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -21,6 +21,7 @@ pub trait Panel: View { fn should_change_position_on_event(_: &Self::Event) -> bool; fn should_zoom_in_on_event(_: &Self::Event) -> bool; fn should_zoom_out_on_event(_: &Self::Event) -> bool; + fn is_zoomed(&self, cx: &WindowContext) -> bool; fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext); fn should_activate_on_event(_: &Self::Event) -> bool; fn should_close_on_event(_: &Self::Event) -> bool; @@ -33,6 +34,7 @@ pub trait PanelHandle { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; fn set_position(&self, position: DockPosition, cx: &mut WindowContext); + fn is_zoomed(&self, cx: &WindowContext) -> bool; fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext); fn default_size(&self, cx: &WindowContext) -> f32; fn icon_path(&self, cx: &WindowContext) -> &'static str; @@ -66,6 +68,10 @@ where self.read(cx).default_size(cx) } + fn is_zoomed(&self, cx: &WindowContext) -> bool { + self.read(cx).is_zoomed(cx) + } + fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext) { self.update(cx, |this, cx| this.set_zoomed(zoomed, cx)) } @@ -140,7 +146,6 @@ struct PanelEntry { panel: Rc, size: f32, context_menu: ViewHandle, - zoomed: bool, _subscriptions: [Subscription; 2], } @@ -206,12 +211,10 @@ impl Dock { ) { for entry in &mut self.panel_entries { if entry.panel.as_any() == panel { - if zoomed != entry.zoomed { - entry.zoomed = zoomed; + if zoomed != entry.panel.is_zoomed(cx) { entry.panel.set_zoomed(zoomed, cx); } - } else if entry.zoomed { - entry.zoomed = false; + } else if entry.panel.is_zoomed(cx) { entry.panel.set_zoomed(false, cx); } } @@ -221,8 +224,7 @@ impl Dock { pub fn zoom_out(&mut self, cx: &mut ViewContext) { for entry in &mut self.panel_entries { - if entry.zoomed { - entry.zoomed = false; + if entry.panel.is_zoomed(cx) { entry.panel.set_zoomed(false, cx); } } @@ -255,7 +257,6 @@ impl Dock { self.panel_entries.push(PanelEntry { panel: Rc::new(panel), size, - zoomed: false, context_menu: cx.add_view(|cx| { let mut menu = ContextMenu::new(dock_view_id, cx); menu.set_position_mode(OverlayPositionMode::Local); @@ -314,9 +315,9 @@ impl Dock { } } - pub fn zoomed_panel(&self) -> Option> { + pub fn zoomed_panel(&self, cx: &WindowContext) -> Option> { let entry = self.active_entry()?; - if entry.zoomed { + if entry.panel.is_zoomed(cx) { Some(entry.panel.clone()) } else { None @@ -598,6 +599,10 @@ pub(crate) mod test { cx.emit(TestPanelEvent::PositionChanged); } + fn is_zoomed(&self, _: &WindowContext) -> bool { + unimplemented!() + } + fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext) { unimplemented!() } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 278ea9d879..e9fc2866aa 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1330,7 +1330,7 @@ impl Workspace { DockPosition::Right => (&self.right_dock, [&self.left_dock, &self.bottom_dock]), }; - let zoomed_panel = dock.read(&cx).zoomed_panel()?; + let zoomed_panel = dock.read(&cx).zoomed_panel(cx)?; if other_docks.iter().all(|dock| !dock.read(cx).has_focus(cx)) && !self.active_pane.read(cx).has_focus() { From 489841761704c4b3c5976c8eadc9730e405f4d8d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 May 2023 18:21:35 +0200 Subject: [PATCH 041/131] Drop foreign key constraint from workspaces.dock_pane to panes table Co-Authored-By: Nathan Sobo Co-Authored-By: Mikayla Maki --- crates/workspace/src/persistence.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 2ea99581d2..3621a4e0d9 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -130,6 +130,27 @@ define_connection! { ALTER TABLE workspaces ADD COLUMN window_width REAL; ALTER TABLE workspaces ADD COLUMN window_height REAL; ALTER TABLE workspaces ADD COLUMN display BLOB; + ), + // Drop foreign key constraint from workspaces.dock_pane to panes table. + sql!( + CREATE TABLE workspaces_2( + workspace_id INTEGER PRIMARY KEY, + workspace_location BLOB UNIQUE, + dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed. + dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed. + dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed. + left_sidebar_open INTEGER, // Boolean + timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, + window_state TEXT, + window_x REAL, + window_y REAL, + window_width REAL, + window_height REAL, + display BLOB + ) STRICT; + INSERT INTO workspaces_2 SELECT * FROM workspaces; + DROP TABLE workspaces; + ALTER TABLE workspaces_2 RENAME TO workspaces; )]; } From 89d8bb1425be55627b038b05a7357946d3ddc9d5 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 17 May 2023 17:34:20 -0700 Subject: [PATCH 042/131] WIP: Add persistence to new docks --- crates/gpui/src/platform.rs | 8 +-- crates/sqlez/src/bindable.rs | 2 +- crates/sqlez/src/statement.rs | 6 +- crates/sqlez/src/typed_statements.rs | 6 +- crates/workspace/src/persistence.rs | 52 +++++++++++++++-- crates/workspace/src/persistence/model.rs | 65 +++++++++++++++++++++- crates/workspace/src/workspace.rs | 68 +++++++++++++++++++++-- 7 files changed, 181 insertions(+), 26 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index ec301939f7..8fbcda02a3 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -222,21 +222,21 @@ impl Bind for WindowBounds { fn bind(&self, statement: &Statement, start_index: i32) -> Result { let (region, next_index) = match self { WindowBounds::Fullscreen => { - let next_index = statement.bind("Fullscreen", start_index)?; + let next_index = statement.bind(&"Fullscreen", start_index)?; (None, next_index) } WindowBounds::Maximized => { - let next_index = statement.bind("Maximized", start_index)?; + let next_index = statement.bind(&"Maximized", start_index)?; (None, next_index) } WindowBounds::Fixed(region) => { - let next_index = statement.bind("Fixed", start_index)?; + let next_index = statement.bind(&"Fixed", start_index)?; (Some(*region), next_index) } }; statement.bind( - region.map(|region| { + ®ion.map(|region| { ( region.min_x(), region.min_y(), diff --git a/crates/sqlez/src/bindable.rs b/crates/sqlez/src/bindable.rs index 86d69afe5f..4c874c4585 100644 --- a/crates/sqlez/src/bindable.rs +++ b/crates/sqlez/src/bindable.rs @@ -27,7 +27,7 @@ impl StaticColumnCount for bool {} impl Bind for bool { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement - .bind(self.then_some(1).unwrap_or(0), start_index) + .bind(&self.then_some(1).unwrap_or(0), start_index) .with_context(|| format!("Failed to bind bool at index {start_index}")) } } diff --git a/crates/sqlez/src/statement.rs b/crates/sqlez/src/statement.rs index 69d5685ba0..de0ad626a5 100644 --- a/crates/sqlez/src/statement.rs +++ b/crates/sqlez/src/statement.rs @@ -236,7 +236,7 @@ impl<'a> Statement<'a> { Ok(str::from_utf8(slice)?) } - pub fn bind(&self, value: T, index: i32) -> Result { + pub fn bind(&self, value: &T, index: i32) -> Result { debug_assert!(index > 0); Ok(value.bind(self, index)?) } @@ -258,7 +258,7 @@ impl<'a> Statement<'a> { } } - pub fn with_bindings(&mut self, bindings: impl Bind) -> Result<&mut Self> { + pub fn with_bindings(&mut self, bindings: &impl Bind) -> Result<&mut Self> { self.bind(bindings, 1)?; Ok(self) } @@ -464,7 +464,7 @@ mod test { connection .exec(indoc! {" CREATE TABLE texts ( - text TEXT + text TEXT )"}) .unwrap()() .unwrap(); diff --git a/crates/sqlez/src/typed_statements.rs b/crates/sqlez/src/typed_statements.rs index 488ee27c0c..d7f25cde51 100644 --- a/crates/sqlez/src/typed_statements.rs +++ b/crates/sqlez/src/typed_statements.rs @@ -29,7 +29,7 @@ impl Connection { query: &str, ) -> Result Result<()>> { let mut statement = Statement::prepare(self, query)?; - Ok(move |bindings| statement.with_bindings(bindings)?.exec()) + Ok(move |bindings| statement.with_bindings(&bindings)?.exec()) } /// Prepare a statement which has no bindings and returns a `Vec`. @@ -55,7 +55,7 @@ impl Connection { query: &str, ) -> Result Result>> { let mut statement = Statement::prepare(self, query)?; - Ok(move |bindings| statement.with_bindings(bindings)?.rows::()) + Ok(move |bindings| statement.with_bindings(&bindings)?.rows::()) } /// Prepare a statement that selects a single row from the database. @@ -87,7 +87,7 @@ impl Connection { let mut statement = Statement::prepare(self, query)?; Ok(move |bindings| { statement - .with_bindings(bindings) + .with_bindings(&bindings) .context("Bindings failed")? .maybe_row::() .context("Maybe row failed") diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 3621a4e0d9..f5d8c56a46 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -18,6 +18,9 @@ use model::{ WorkspaceLocation, }; +use self::model::DockStructure; + + define_connection! { // Current schema shape using pseudo-rust syntax: // @@ -151,6 +154,15 @@ define_connection! { INSERT INTO workspaces_2 SELECT * FROM workspaces; DROP TABLE workspaces; ALTER TABLE workspaces_2 RENAME TO workspaces; + ), + // Add panels related information + sql!( + ALTER TABLE workspaces ADD COLUMN left_dock_visible INTEGER; //bool + ALTER TABLE workspaces ADD COLUMN left_dock_size REAL; + ALTER TABLE workspaces ADD COLUMN right_dock_visible INTEGER; //bool + ALTER TABLE workspaces ADD COLUMN right_dock_size REAL; + ALTER TABLE workspaces ADD COLUMN bottom_dock_visible INTEGER; //bool + ALTER TABLE workspaces ADD COLUMN bottom_dock_size REAL; )]; } @@ -166,12 +178,13 @@ impl WorkspaceDb { // Note that we re-assign the workspace_id here in case it's empty // and we've grabbed the most recent workspace - let (workspace_id, workspace_location, left_sidebar_open, bounds, display): ( + let (workspace_id, workspace_location, left_sidebar_open, bounds, display, docks): ( WorkspaceId, WorkspaceLocation, bool, Option, Option, + DockStructure ) = self .select_row_bound(sql! { SELECT @@ -183,7 +196,13 @@ impl WorkspaceDb { window_y, window_width, window_height, - display + display, + left_dock_visible, + left_dock_size, + right_dock_visible, + right_dock_size, + bottom_dock_visible, + bottom_dock_size FROM workspaces WHERE workspace_location = ? }) @@ -202,6 +221,7 @@ impl WorkspaceDb { left_sidebar_open, bounds, display, + docks }) } @@ -227,15 +247,27 @@ impl WorkspaceDb { workspace_id, workspace_location, left_sidebar_open, + left_dock_visible, + left_dock_size, + right_dock_visible, + right_dock_size, + bottom_dock_visible, + bottom_dock_size, timestamp ) - VALUES (?1, ?2, ?3, CURRENT_TIMESTAMP) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, CURRENT_TIMESTAMP) ON CONFLICT DO UPDATE SET workspace_location = ?2, left_sidebar_open = ?3, + left_dock_visible = ?4, + left_dock_size = ?5, + right_dock_visible = ?6, + right_dock_size = ?7, + bottom_dock_visible = ?8, + bottom_dock_size = ?9, timestamp = CURRENT_TIMESTAMP - ))?((workspace.id, &workspace.location)) + ))?((workspace.id, &workspace.location, workspace.left_sidebar_open, workspace.docks)) .context("Updating workspace")?; // Save center pane group @@ -549,19 +581,22 @@ mod tests { let mut workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), - center_group: Default::default(), left_sidebar_open: true, + center_group: Default::default(), bounds: Default::default(), display: Default::default(), + docks: Default::default() }; let mut _workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), - center_group: Default::default(), left_sidebar_open: false, + center_group: Default::default(), bounds: Default::default(), display: Default::default(), + docks: Default::default() + }; db.save_workspace(workspace_1.clone()).await; @@ -659,6 +694,7 @@ mod tests { left_sidebar_open: true, bounds: Default::default(), display: Default::default(), + docks: Default::default() }; db.save_workspace(workspace.clone()).await; @@ -687,6 +723,7 @@ mod tests { left_sidebar_open: true, bounds: Default::default(), display: Default::default(), + docks: Default::default() }; let mut workspace_2 = SerializedWorkspace { @@ -696,6 +733,7 @@ mod tests { left_sidebar_open: false, bounds: Default::default(), display: Default::default(), + docks: Default::default() }; db.save_workspace(workspace_1.clone()).await; @@ -732,6 +770,7 @@ mod tests { left_sidebar_open: false, bounds: Default::default(), display: Default::default(), + docks: Default::default() }; db.save_workspace(workspace_3.clone()).await; @@ -765,6 +804,7 @@ mod tests { left_sidebar_open: true, bounds: Default::default(), display: Default::default(), + docks: Default::default() } } diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index 19da782fb6..9d997fcfad 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -63,6 +63,65 @@ pub struct SerializedWorkspace { pub left_sidebar_open: bool, pub bounds: Option, pub display: Option, + pub docks: DockStructure, +} + +#[derive(Debug, PartialEq, Clone, Default)] +pub struct DockStructure { + pub(crate) left: DockData, + pub(crate) right: DockData, + pub(crate) bottom: DockData, +} + +impl Column for DockStructure { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (left, next_index) = DockData::column(statement, start_index)?; + let (right, next_index) = DockData::column(statement, next_index)?; + let (bottom, next_index) = DockData::column(statement, next_index)?; + Ok(( + DockStructure { + left, + right, + bottom, + }, + next_index, + )) + } +} + +impl Bind for DockStructure { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + let next_index = statement.bind(&self.left, start_index)?; + let next_index = statement.bind(&self.right, next_index)?; + statement.bind(&self.bottom, next_index) + } +} + +#[derive(Debug, PartialEq, Clone, Default)] +pub struct DockData { + pub(crate) visible: bool, + pub(crate) size: Option, +} + +impl Column for DockData { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (visible, next_index) = Option::::column(statement, start_index)?; + let (size, next_index) = Option::::column(statement, next_index)?; + Ok(( + DockData { + visible: visible.unwrap_or(false), + size, + }, + next_index, + )) + } +} + +impl Bind for DockData { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + let next_index = statement.bind(&self.visible, start_index)?; + statement.bind(&self.size, next_index) + } } #[derive(Debug, PartialEq, Eq, Clone)] @@ -251,9 +310,9 @@ impl StaticColumnCount for SerializedItem { } impl Bind for &SerializedItem { fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let next_index = statement.bind(self.kind.clone(), start_index)?; - let next_index = statement.bind(self.item_id, next_index)?; - statement.bind(self.active, next_index) + let next_index = statement.bind(&self.kind, start_index)?; + let next_index = statement.bind(&self.item_id, next_index)?; + statement.bind(&self.active, next_index) } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e9fc2866aa..6cb9f66edb 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -58,7 +58,9 @@ use std::{ use crate::{ notifications::simple_message_notification::MessageNotification, - persistence::model::{SerializedPane, SerializedPaneGroup, SerializedWorkspace}, + persistence::model::{ + DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace, + }, }; use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle, TogglePanel}; use lazy_static::lazy_static; @@ -2575,12 +2577,51 @@ impl Workspace { } } + fn build_serialized_docks(this: &Workspace, cx: &AppContext) -> DockStructure { + let left_dock = this.left_dock.read(cx); + let left_visible = left_dock.is_open(); + let left_size = left_dock + .active_panel() + .map(|panel| left_dock.panel_size(panel.as_ref())) + .flatten(); + + let right_dock = this.right_dock.read(cx); + let right_visible = right_dock.is_open(); + let right_size = right_dock + .active_panel() + .map(|panel| right_dock.panel_size(panel.as_ref())) + .flatten(); + + let bottom_dock = this.bottom_dock.read(cx); + let bottom_visible = bottom_dock.is_open(); + let bottom_size = bottom_dock + .active_panel() + .map(|panel| bottom_dock.panel_size(panel.as_ref())) + .flatten(); + + DockStructure { + left: DockData { + visible: left_visible, + size: left_size, + }, + right: DockData { + visible: right_visible, + size: right_size, + }, + bottom: DockData { + visible: bottom_visible, + size: bottom_size, + }, + } + } + if let Some(location) = self.location(cx) { // Load bearing special case: // - with_local_workspace() relies on this to not have other stuff open // when you open your log if !location.paths().is_empty() { let center_group = build_serialized_pane_group(&self.center.root, cx); + let docks = build_serialized_docks(self, cx); let serialized_workspace = SerializedWorkspace { id: self.database_id, @@ -2589,6 +2630,7 @@ impl Workspace { left_sidebar_open: self.left_dock.read(cx).is_open(), bounds: Default::default(), display: Default::default(), + docks, }; cx.background() @@ -2642,11 +2684,25 @@ impl Workspace { } } - if workspace.left_dock().read(cx).is_open() - != serialized_workspace.left_sidebar_open - { - workspace.toggle_dock(DockPosition::Left, cx); - } + let docks = serialized_workspace.docks; + workspace.left_dock.update(cx, |dock, cx| { + dock.set_open(docks.left.visible, cx); + if let Some(size) = docks.left.size { + dock.resize_active_panel(size, cx); + } + }); + workspace.right_dock.update(cx, |dock, cx| { + dock.set_open(docks.right.visible, cx); + if let Some(size) = docks.right.size { + dock.resize_active_panel(size, cx); + } + }); + workspace.bottom_dock.update(cx, |dock, cx| { + dock.set_open(docks.bottom.visible, cx); + if let Some(size) = docks.bottom.size { + dock.resize_active_panel(size, cx); + } + }); cx.notify(); })?; From f2ad17dbc0392c8b49c0054bd281da360feeccd2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 May 2023 15:35:46 +0200 Subject: [PATCH 043/131] WIP --- crates/workspace/src/persistence.rs | 68 +++++++++-------------- crates/workspace/src/persistence/model.rs | 9 ++- crates/workspace/src/workspace.rs | 52 +++++++++-------- 3 files changed, 56 insertions(+), 73 deletions(-) diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index f5d8c56a46..9cf13af681 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -20,7 +20,6 @@ use model::{ use self::model::DockStructure; - define_connection! { // Current schema shape using pseudo-rust syntax: // @@ -158,11 +157,11 @@ define_connection! { // Add panels related information sql!( ALTER TABLE workspaces ADD COLUMN left_dock_visible INTEGER; //bool - ALTER TABLE workspaces ADD COLUMN left_dock_size REAL; + ALTER TABLE workspaces ADD COLUMN left_dock_active_panel TEXT; ALTER TABLE workspaces ADD COLUMN right_dock_visible INTEGER; //bool - ALTER TABLE workspaces ADD COLUMN right_dock_size REAL; + ALTER TABLE workspaces ADD COLUMN right_dock_active_panel TEXT; ALTER TABLE workspaces ADD COLUMN bottom_dock_visible INTEGER; //bool - ALTER TABLE workspaces ADD COLUMN bottom_dock_size REAL; + ALTER TABLE workspaces ADD COLUMN bottom_dock_active_panel TEXT; )]; } @@ -178,19 +177,17 @@ impl WorkspaceDb { // Note that we re-assign the workspace_id here in case it's empty // and we've grabbed the most recent workspace - let (workspace_id, workspace_location, left_sidebar_open, bounds, display, docks): ( + let (workspace_id, workspace_location, bounds, display, docks): ( WorkspaceId, WorkspaceLocation, - bool, Option, Option, - DockStructure + DockStructure, ) = self .select_row_bound(sql! { SELECT workspace_id, workspace_location, - left_sidebar_open, window_state, window_x, window_y, @@ -198,11 +195,11 @@ impl WorkspaceDb { window_height, display, left_dock_visible, - left_dock_size, + left_dock_active_panel, right_dock_visible, - right_dock_size, + right_dock_active_panel, bottom_dock_visible, - bottom_dock_size + bottom_dock_active_panel FROM workspaces WHERE workspace_location = ? }) @@ -218,10 +215,9 @@ impl WorkspaceDb { .get_center_pane_group(workspace_id) .context("Getting center group") .log_err()?, - left_sidebar_open, bounds, display, - docks + docks, }) } @@ -246,28 +242,26 @@ impl WorkspaceDb { INSERT INTO workspaces( workspace_id, workspace_location, - left_sidebar_open, left_dock_visible, - left_dock_size, + left_dock_active_panel, right_dock_visible, - right_dock_size, + right_dock_active_panel, bottom_dock_visible, - bottom_dock_size, + bottom_dock_active_panel, timestamp ) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, CURRENT_TIMESTAMP) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, CURRENT_TIMESTAMP) ON CONFLICT DO UPDATE SET workspace_location = ?2, - left_sidebar_open = ?3, - left_dock_visible = ?4, - left_dock_size = ?5, - right_dock_visible = ?6, - right_dock_size = ?7, - bottom_dock_visible = ?8, - bottom_dock_size = ?9, + left_dock_visible = ?3, + left_dock_active_panel = ?4, + right_dock_visible = ?5, + right_dock_active_panel = ?6, + bottom_dock_visible = ?7, + bottom_dock_active_panel = ?8, timestamp = CURRENT_TIMESTAMP - ))?((workspace.id, &workspace.location, workspace.left_sidebar_open, workspace.docks)) + ))?((workspace.id, &workspace.location, workspace.docks)) .context("Updating workspace")?; // Save center pane group @@ -581,22 +575,19 @@ mod tests { let mut workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), - left_sidebar_open: true, center_group: Default::default(), bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), }; let mut _workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), - left_sidebar_open: false, center_group: Default::default(), bounds: Default::default(), display: Default::default(), - docks: Default::default() - + docks: Default::default(), }; db.save_workspace(workspace_1.clone()).await; @@ -691,10 +682,9 @@ mod tests { id: 5, location: (["/tmp", "/tmp2"]).into(), center_group, - left_sidebar_open: true, bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), }; db.save_workspace(workspace.clone()).await; @@ -720,20 +710,18 @@ mod tests { id: 1, location: (["/tmp", "/tmp2"]).into(), center_group: Default::default(), - left_sidebar_open: true, bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), }; let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), center_group: Default::default(), - left_sidebar_open: false, bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), }; db.save_workspace(workspace_1.clone()).await; @@ -767,10 +755,9 @@ mod tests { id: 3, location: (&["/tmp", "/tmp2"]).into(), center_group: Default::default(), - left_sidebar_open: false, bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), }; db.save_workspace(workspace_3.clone()).await; @@ -801,10 +788,9 @@ mod tests { id: 4, location: workspace_id.into(), center_group: center_group.clone(), - left_sidebar_open: true, bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), } } diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index 9d997fcfad..bc10c931bb 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -60,7 +60,6 @@ pub struct SerializedWorkspace { pub id: WorkspaceId, pub location: WorkspaceLocation, pub center_group: SerializedPaneGroup, - pub left_sidebar_open: bool, pub bounds: Option, pub display: Option, pub docks: DockStructure, @@ -100,17 +99,17 @@ impl Bind for DockStructure { #[derive(Debug, PartialEq, Clone, Default)] pub struct DockData { pub(crate) visible: bool, - pub(crate) size: Option, + pub(crate) active_panel: Option, } impl Column for DockData { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { let (visible, next_index) = Option::::column(statement, start_index)?; - let (size, next_index) = Option::::column(statement, next_index)?; + let (active_panel, next_index) = Option::::column(statement, next_index)?; Ok(( DockData { visible: visible.unwrap_or(false), - size, + active_panel, }, next_index, )) @@ -120,7 +119,7 @@ impl Column for DockData { impl Bind for DockData { fn bind(&self, statement: &Statement, start_index: i32) -> Result { let next_index = statement.bind(&self.visible, start_index)?; - statement.bind(&self.size, next_index) + statement.bind(&self.active_panel, next_index) } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6cb9f66edb..30a2a2f4e3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2580,39 +2580,45 @@ impl Workspace { fn build_serialized_docks(this: &Workspace, cx: &AppContext) -> DockStructure { let left_dock = this.left_dock.read(cx); let left_visible = left_dock.is_open(); - let left_size = left_dock - .active_panel() - .map(|panel| left_dock.panel_size(panel.as_ref())) - .flatten(); + let left_active_panel = left_dock.active_panel().and_then(|panel| { + Some( + cx.view_ui_name(panel.as_any().window_id(), panel.id())? + .to_string(), + ) + }); let right_dock = this.right_dock.read(cx); let right_visible = right_dock.is_open(); - let right_size = right_dock - .active_panel() - .map(|panel| right_dock.panel_size(panel.as_ref())) - .flatten(); + let right_active_panel = right_dock.active_panel().and_then(|panel| { + Some( + cx.view_ui_name(panel.as_any().window_id(), panel.id())? + .to_string(), + ) + }); let bottom_dock = this.bottom_dock.read(cx); let bottom_visible = bottom_dock.is_open(); - let bottom_size = bottom_dock - .active_panel() - .map(|panel| bottom_dock.panel_size(panel.as_ref())) - .flatten(); + let bottom_active_panel = bottom_dock.active_panel().and_then(|panel| { + Some( + cx.view_ui_name(panel.as_any().window_id(), panel.id())? + .to_string(), + ) + }); - DockStructure { + dbg!(DockStructure { left: DockData { visible: left_visible, - size: left_size, + active_panel: left_active_panel, }, right: DockData { visible: right_visible, - size: right_size, + active_panel: right_active_panel, }, bottom: DockData { visible: bottom_visible, - size: bottom_size, + active_panel: bottom_active_panel, }, - } + }) } if let Some(location) = self.location(cx) { @@ -2627,7 +2633,6 @@ impl Workspace { id: self.database_id, location, center_group, - left_sidebar_open: self.left_dock.read(cx).is_open(), bounds: Default::default(), display: Default::default(), docks, @@ -2686,22 +2691,15 @@ impl Workspace { let docks = serialized_workspace.docks; workspace.left_dock.update(cx, |dock, cx| { + dbg!(docks.left.visible); dock.set_open(docks.left.visible, cx); - if let Some(size) = docks.left.size { - dock.resize_active_panel(size, cx); - } + dbg!(dock.is_open()); }); workspace.right_dock.update(cx, |dock, cx| { dock.set_open(docks.right.visible, cx); - if let Some(size) = docks.right.size { - dock.resize_active_panel(size, cx); - } }); workspace.bottom_dock.update(cx, |dock, cx| { dock.set_open(docks.bottom.visible, cx); - if let Some(size) = docks.bottom.size { - dock.resize_active_panel(size, cx); - } }); cx.notify(); From 4bda5c4d696b8d6abeb2e8ccd85e3bf38e0b74d5 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 May 2023 17:08:51 -0700 Subject: [PATCH 044/131] Optimize LSP watched file reporting in 2 simple ways * Convert globs to relative paths in advance. This avoids needing to convert every changed path to an absolute path before performing glob matching. * Avoid duplicate reporting for language servers with multiple worktrees. --- crates/project/src/lsp_glob_set.rs | 8 +++++ crates/project/src/project.rs | 54 +++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/crates/project/src/lsp_glob_set.rs b/crates/project/src/lsp_glob_set.rs index daac344a0a..d4d661b6a1 100644 --- a/crates/project/src/lsp_glob_set.rs +++ b/crates/project/src/lsp_glob_set.rs @@ -73,6 +73,14 @@ impl LspGlobSet { } } +impl std::fmt::Debug for LspGlobSet { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_set() + .entries(self.patterns.iter().map(|p| p.as_str())) + .finish() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 13809622f9..23b4984e55 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -226,7 +226,7 @@ pub enum LanguageServerState { language: Arc, adapter: Arc, server: Arc, - watched_paths: LspGlobSet, + watched_paths: HashMap, simulate_disk_based_diagnostics_completion: Option>, }, } @@ -2869,7 +2869,25 @@ impl Project { { watched_paths.clear(); for watcher in params.watchers { - watched_paths.add_pattern(&watcher.glob_pattern).log_err(); + for worktree in &self.worktrees { + if let Some(worktree) = worktree.upgrade(cx) { + let worktree = worktree.read(cx); + if let Some(abs_path) = worktree.abs_path().to_str() { + if let Some(suffix) = watcher + .glob_pattern + .strip_prefix(abs_path) + .and_then(|s| s.strip_prefix(std::path::MAIN_SEPARATOR)) + { + watched_paths + .entry(worktree.id()) + .or_default() + .add_pattern(suffix) + .log_err(); + break; + } + } + } + } } cx.notify(); } @@ -4708,24 +4726,34 @@ impl Project { cx: &mut ModelContext, ) { let worktree_id = worktree_handle.read(cx).id(); + let mut language_server_ids = self + .language_server_ids + .iter() + .filter_map(|((server_worktree_id, _), server_id)| { + (*server_worktree_id == worktree_id).then_some(*server_id) + }) + .collect::>(); + language_server_ids.sort(); + language_server_ids.dedup(); + let abs_path = worktree_handle.read(cx).abs_path(); - for ((server_worktree_id, _), server_id) in &self.language_server_ids { - if *server_worktree_id == worktree_id { - if let Some(server) = self.language_servers.get(server_id) { - if let LanguageServerState::Running { - server, - watched_paths, - .. - } = server - { + for server_id in &language_server_ids { + if let Some(server) = self.language_servers.get(server_id) { + if let LanguageServerState::Running { + server, + watched_paths, + .. + } = server + { + if let Some(watched_paths) = watched_paths.get(&worktree_id) { let params = lsp::DidChangeWatchedFilesParams { changes: changes .iter() .filter_map(|((path, _), change)| { - let path = abs_path.join(path); if watched_paths.matches(&path) { Some(lsp::FileEvent { - uri: lsp::Url::from_file_path(path).unwrap(), + uri: lsp::Url::from_file_path(abs_path.join(path)) + .unwrap(), typ: match change { PathChange::Added => lsp::FileChangeType::CREATED, PathChange::Removed => lsp::FileChangeType::DELETED, From 3d6b7283647ec2444b6d7fc647e4e9400fcc6172 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 May 2023 14:18:11 +0200 Subject: [PATCH 045/131] Activate the correct panel when deserializing workspace --- crates/workspace/src/dock.rs | 14 +++++++++++--- crates/workspace/src/workspace.rs | 23 ++++++++++++++++++----- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 5c501f64ad..e115e1d573 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,8 +1,9 @@ use crate::{StatusItemView, Workspace}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ - elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, Axis, - Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, + elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, + AppContext, Axis, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, + WindowContext, }; use serde::Deserialize; use settings::Settings; @@ -181,12 +182,19 @@ impl Dock { .map_or(false, |panel| panel.has_focus(cx)) } - pub fn panel_index(&self) -> Option { + pub fn panel_index_for_type(&self) -> Option { self.panel_entries .iter() .position(|entry| entry.panel.as_any().is::()) } + pub fn panel_index_for_ui_name(&self, ui_name: &str, cx: &AppContext) -> Option { + self.panel_entries.iter().position(|entry| { + let panel = entry.panel.as_any(); + cx.view_ui_name(panel.window_id(), panel.id()) == Some(ui_name) + }) + } + pub fn active_panel_index(&self) -> usize { self.active_panel_index } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 30a2a2f4e3..01ed898de4 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1480,7 +1480,7 @@ impl Workspace { pub fn toggle_panel_focus(&mut self, cx: &mut ViewContext) { for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { - if let Some(panel_index) = dock.read(cx).panel_index::() { + if let Some(panel_index) = dock.read(cx).panel_index_for_type::() { let active_item = dock.update(cx, |dock, cx| { dock.set_open(true, cx); dock.activate_panel(panel_index, cx); @@ -2605,7 +2605,7 @@ impl Workspace { ) }); - dbg!(DockStructure { + DockStructure { left: DockData { visible: left_visible, active_panel: left_active_panel, @@ -2618,7 +2618,7 @@ impl Workspace { visible: bottom_visible, active_panel: bottom_active_panel, }, - }) + } } if let Some(location) = self.location(cx) { @@ -2691,15 +2691,28 @@ impl Workspace { let docks = serialized_workspace.docks; workspace.left_dock.update(cx, |dock, cx| { - dbg!(docks.left.visible); dock.set_open(docks.left.visible, cx); - dbg!(dock.is_open()); + if let Some(active_panel) = docks.left.active_panel { + if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { + dock.activate_panel(ix, cx); + } + } }); workspace.right_dock.update(cx, |dock, cx| { dock.set_open(docks.right.visible, cx); + if let Some(active_panel) = docks.right.active_panel { + if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { + dock.activate_panel(ix, cx); + } + } }); workspace.bottom_dock.update(cx, |dock, cx| { dock.set_open(docks.bottom.visible, cx); + if let Some(active_panel) = docks.bottom.active_panel { + if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { + dock.activate_panel(ix, cx); + } + } }); cx.notify(); From 924ec961ffa1a41b7f7e86829b501e0a64f32d4f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 May 2023 15:27:18 +0200 Subject: [PATCH 046/131] Toggle project panel when opening new workspace in a dock-agnostic way --- crates/collab/src/tests.rs | 2 +- crates/workspace/src/workspace.rs | 44 +++++++++++-------------------- crates/zed/src/zed.rs | 20 +++++++++++++- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index aeb26231d4..cbc4b3bd45 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -194,7 +194,7 @@ impl TestServer { themes: ThemeRegistry::new((), cx.font_cache()), fs: fs.clone(), build_window_options: |_, _, _| Default::default(), - initialize_workspace: |_, _, _| unimplemented!(), + initialize_workspace: |_, _, _, _| unimplemented!(), background_actions: || &[], }); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 01ed898de4..b26acf9b4d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -361,7 +361,7 @@ pub struct AppState { pub fs: Arc, pub build_window_options: fn(Option, Option, &dyn Platform) -> WindowOptions<'static>, - pub initialize_workspace: fn(&mut Workspace, &Arc, &mut ViewContext), + pub initialize_workspace: fn(&mut Workspace, bool, &Arc, &mut ViewContext), pub background_actions: BackgroundActions, } @@ -383,7 +383,7 @@ impl AppState { fs, languages, user_store, - initialize_workspace: |_, _, _| {}, + initialize_workspace: |_, _, _, _| {}, build_window_options: |_, _, _| Default::default(), background_actions: || &[], }) @@ -733,6 +733,7 @@ impl Workspace { let build_workspace = |cx: &mut ViewContext, serialized_workspace: Option| { + let was_deserialized = serialized_workspace.is_some(); let mut workspace = Workspace::new( serialized_workspace, workspace_id, @@ -740,7 +741,12 @@ impl Workspace { app_state.clone(), cx, ); - (app_state.initialize_workspace)(&mut workspace, &app_state, cx); + (app_state.initialize_workspace)( + &mut workspace, + was_deserialized, + &app_state, + cx, + ); workspace }; @@ -2734,7 +2740,7 @@ impl Workspace { user_store: project.read(cx).user_store(), fs: project.read(cx).fs().clone(), build_window_options: |_, _, _| Default::default(), - initialize_workspace: |_, _, _| {}, + initialize_workspace: |_, _, _, _| {}, background_actions: || &[], }); Self::new(None, 0, project, app_state, cx) @@ -2998,28 +3004,11 @@ pub fn open_paths( .await, )) } else { - let contains_directory = - futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path))) - .await - .contains(&false); - - cx.update(|cx| { - let task = - Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx); - - cx.spawn(|mut cx| async move { - let (workspace, items) = task.await; - - workspace.update(&mut cx, |workspace, cx| { - if contains_directory { - workspace.toggle_dock(DockPosition::Left, cx); - } - })?; - - anyhow::Ok((workspace, items)) + Ok(cx + .update(|cx| { + Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx) }) - }) - .await + .await) } }) } @@ -3109,9 +3098,8 @@ pub fn join_remote_project( let (_, workspace) = cx.add_window( (app_state.build_window_options)(None, None, cx.platform().as_ref()), |cx| { - let mut workspace = - Workspace::new(Default::default(), 0, project, app_state.clone(), cx); - (app_state.initialize_workspace)(&mut workspace, &app_state, cx); + let mut workspace = Workspace::new(None, 0, project, app_state.clone(), cx); + (app_state.initialize_workspace)(&mut workspace, false, &app_state, cx); workspace }, ); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index fb7da693fc..35a430f63f 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -35,7 +35,10 @@ use terminal_view::terminal_panel::{self, TerminalPanel}; use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; pub use workspace; -use workspace::{create_and_open_local_file, open_new, AppState, NewFile, NewWindow, Workspace}; +use workspace::{ + create_and_open_local_file, dock::PanelHandle, open_new, AppState, NewFile, NewWindow, + Workspace, +}; #[derive(Deserialize, Clone, PartialEq)] pub struct OpenBrowser { @@ -279,6 +282,7 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { pub fn initialize_workspace( workspace: &mut Workspace, + was_deserialized: bool, app_state: &Arc, cx: &mut ViewContext, ) { @@ -316,7 +320,21 @@ pub fn initialize_workspace( workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); let project_panel = ProjectPanel::new(workspace, cx); + let project_panel_position = project_panel.position(cx); workspace.add_panel(project_panel, cx); + if !was_deserialized + && workspace + .project() + .read(cx) + .visible_worktrees(cx) + .any(|tree| { + tree.read(cx) + .root_entry() + .map_or(false, |entry| entry.is_dir()) + }) + { + workspace.toggle_dock(project_panel_position, cx); + } let terminal_panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); workspace.add_panel(terminal_panel, cx); From e49281699c690897e7c96d8e7c6253b51259c717 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 May 2023 16:07:47 +0200 Subject: [PATCH 047/131] Add new terminal when the terminal panel is activated, and not on focus --- crates/project_panel/src/project_panel.rs | 2 + crates/terminal_view/src/terminal_panel.rs | 61 ++++++++++++---------- crates/workspace/src/dock.rs | 33 ++++++++---- 3 files changed, 58 insertions(+), 38 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index a89d544074..3476a6a155 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1401,6 +1401,8 @@ impl workspace::dock::Panel for ProjectPanel { fn set_zoomed(&mut self, _: bool, _: &mut ViewContext) {} + fn set_active(&mut self, _: bool, _: &mut ViewContext) {} + fn icon_path(&self) -> &'static str { "icons/folder_tree_16.svg" } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 4926a8775e..4652b58072 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1,9 +1,8 @@ use crate::TerminalView; use gpui::{ - actions, elements::*, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, - ViewHandle, WeakViewHandle, WindowContext, + actions, anyhow, elements::*, AppContext, Entity, Subscription, View, ViewContext, ViewHandle, + WeakViewHandle, WindowContext, }; -use project::Project; use settings::{settings_file::SettingsFile, Settings, TerminalDockPosition, WorkingDirectory}; use util::ResultExt; use workspace::{ @@ -26,7 +25,6 @@ pub enum Event { } pub struct TerminalPanel { - project: ModelHandle, pane: ViewHandle, workspace: WeakViewHandle, _subscriptions: Vec, @@ -86,7 +84,6 @@ impl TerminalPanel { cx.subscribe(&pane, Self::handle_pane_event), ]; Self { - project: workspace.project().clone(), pane, workspace: workspace.weak_handle(), _subscriptions: subscriptions, @@ -109,30 +106,34 @@ impl TerminalPanel { } fn add_terminal(&mut self, _: &workspace::NewTerminal, cx: &mut ViewContext) { - if let Some(workspace) = self.workspace.upgrade(cx) { - let working_directory_strategy = cx - .global::() - .terminal_overrides - .working_directory - .clone() - .unwrap_or(WorkingDirectory::CurrentProjectDirectory); - let working_directory = - crate::get_working_directory(workspace.read(cx), cx, working_directory_strategy); - let window_id = cx.window_id(); - if let Some(terminal) = self.project.update(cx, |project, cx| { - project - .create_terminal(working_directory, window_id, cx) - .log_err() - }) { - workspace.update(cx, |workspace, cx| { + let workspace = self.workspace.clone(); + cx.spawn(|this, mut cx| async move { + let pane = this.read_with(&cx, |this, _| this.pane.clone())?; + workspace.update(&mut cx, |workspace, cx| { + let working_directory_strategy = cx + .global::() + .terminal_overrides + .working_directory + .clone() + .unwrap_or(WorkingDirectory::CurrentProjectDirectory); + let working_directory = + crate::get_working_directory(workspace, cx, working_directory_strategy); + let window_id = cx.window_id(); + if let Some(terminal) = workspace.project().update(cx, |project, cx| { + project + .create_terminal(working_directory, window_id, cx) + .log_err() + }) { let terminal = Box::new(cx.add_view(|cx| { TerminalView::new(terminal, workspace.database_id(), cx) })); - Pane::add_item(workspace, &self.pane, terminal, true, true, None, cx); - }); - } - } + Pane::add_item(workspace, &pane, terminal, true, true, None, cx); + } + })?; + anyhow::Ok(()) + }) + .detach_and_log_err(cx); } } @@ -150,10 +151,6 @@ impl View for TerminalPanel { } fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { - if self.pane.read(cx).items_len() == 0 { - self.add_terminal(&Default::default(), cx) - } - if cx.is_self_focused() { cx.focus(&self.pane); } @@ -215,6 +212,12 @@ impl Panel for TerminalPanel { self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx)); } + fn set_active(&mut self, active: bool, cx: &mut ViewContext) { + if active && self.pane.read(cx).items_len() == 0 { + self.add_terminal(&Default::default(), cx) + } + } + fn icon_path(&self) -> &'static str { "icons/terminal_12.svg" } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index e115e1d573..83bb54d3be 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -24,6 +24,7 @@ pub trait Panel: View { fn should_zoom_out_on_event(_: &Self::Event) -> bool; fn is_zoomed(&self, cx: &WindowContext) -> bool; fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext); + fn set_active(&mut self, active: bool, cx: &mut ViewContext); fn should_activate_on_event(_: &Self::Event) -> bool; fn should_close_on_event(_: &Self::Event) -> bool; fn has_focus(&self, cx: &WindowContext) -> bool; @@ -37,6 +38,7 @@ pub trait PanelHandle { fn set_position(&self, position: DockPosition, cx: &mut WindowContext); fn is_zoomed(&self, cx: &WindowContext) -> bool; fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext); + fn set_active(&self, active: bool, cx: &mut WindowContext); fn default_size(&self, cx: &WindowContext) -> f32; fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; @@ -77,6 +79,10 @@ where self.update(cx, |this, cx| this.set_zoomed(zoomed, cx)) } + fn set_active(&self, active: bool, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.set_active(active, cx)) + } + fn icon_path(&self, cx: &WindowContext) -> &'static str { self.read(cx).icon_path() } @@ -202,12 +208,16 @@ impl Dock { pub fn set_open(&mut self, open: bool, cx: &mut ViewContext) { if open != self.is_open { self.is_open = open; + if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + active_panel.panel.set_active(open, cx); + } + cx.notify(); } } pub fn toggle_open(&mut self, cx: &mut ViewContext) { - self.is_open = !self.is_open; + self.set_open(!self.is_open, cx); cx.notify(); } @@ -297,17 +307,18 @@ impl Dock { } pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { - self.active_panel_index = panel_ix; - cx.notify(); - } + if panel_ix != self.active_panel_index { + if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + active_panel.panel.set_active(false, cx); + } - pub fn toggle_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { - if self.active_panel_index == panel_ix { - self.is_open = false; - } else { self.active_panel_index = panel_ix; + if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + active_panel.panel.set_active(true, cx); + } + + cx.notify(); } - cx.notify(); } pub fn active_panel(&self) -> Option<&Rc> { @@ -615,6 +626,10 @@ pub(crate) mod test { unimplemented!() } + fn set_active(&mut self, _active: bool, _cx: &mut ViewContext) { + unimplemented!() + } + fn default_size(&self, _: &WindowContext) -> f32 { match self.position.axis() { Axis::Horizontal => 300., From 583b15badc0d7a5561061d32a95e8a4472c3c4cf Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 19 May 2023 18:04:12 +0300 Subject: [PATCH 048/131] When the file is deleted via project panel, close it in editors --- crates/gpui/src/app/test_app_context.rs | 2 +- crates/project/src/project.rs | 7 + crates/project_panel/src/project_panel.rs | 180 ++++++++++++++++++++++ crates/workspace/src/pane.rs | 28 ++++ crates/workspace/src/workspace.rs | 6 + 5 files changed, 222 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/app/test_app_context.rs b/crates/gpui/src/app/test_app_context.rs index 4af436a7b8..e956c4ca0d 100644 --- a/crates/gpui/src/app/test_app_context.rs +++ b/crates/gpui/src/app/test_app_context.rs @@ -270,7 +270,7 @@ impl TestAppContext { .borrow_mut() .pop_front() .expect("prompt was not called"); - let _ = done_tx.try_send(answer); + done_tx.try_send(answer).ok(); } pub fn has_pending_prompt(&self, window_id: usize) -> bool { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 13809622f9..106dd4d817 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -213,6 +213,7 @@ pub enum Event { RemoteIdChanged(Option), DisconnectedFromHost, Closed, + DeletedEntry(ProjectEntryId), CollaboratorUpdated { old_peer_id: proto::PeerId, new_peer_id: proto::PeerId, @@ -977,6 +978,9 @@ impl Project { cx: &mut ModelContext, ) -> Option>> { let worktree = self.worktree_for_entry(entry_id, cx)?; + + cx.emit(Event::DeletedEntry(entry_id)); + if self.is_local() { worktree.update(cx, |worktree, cx| { worktree.as_local_mut().unwrap().delete_entry(entry_id, cx) @@ -5146,6 +5150,9 @@ impl Project { mut cx: AsyncAppContext, ) -> Result { let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id); + + this.update(&mut cx, |_, cx| cx.emit(Event::DeletedEntry(entry_id))); + let worktree = this.read_with(&cx, |this, cx| { this.worktree_for_entry(entry_id, cx) .ok_or_else(|| anyhow!("worktree not found")) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 683ce8ad06..8606649398 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1378,6 +1378,7 @@ mod tests { use serde_json::json; use settings::SettingsStore; use std::{collections::HashSet, path::Path}; + use workspace::{pane, AppState}; #[gpui::test] async fn test_visible_list(cx: &mut gpui::TestAppContext) { @@ -1853,6 +1854,95 @@ mod tests { ); } + #[gpui::test] + async fn test_remove_opened_file(cx: &mut gpui::TestAppContext) { + init_test_with_editor(cx); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/src", + json!({ + "test": { + "first.rs": "// First Rust file", + "second.rs": "// Second Rust file", + "third.rs": "// Third Rust file", + } + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await; + let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); + + toggle_expand_dir(&panel, "src/test", cx); + select_path(&panel, "src/test/first.rs", cx); + panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx)); + cx.foreground().run_until_parked(); + assert_eq!( + visible_entries_as_strings(&panel, 0..10, cx), + &[ + "v src", + " v test", + " first.rs <== selected", + " second.rs", + " third.rs" + ] + ); + ensure_single_file_is_opened(window_id, &workspace, "test/first.rs", cx); + + submit_deletion(window_id, &panel, cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..10, cx), + &[ + "v src", + " v test", + " second.rs", + " third.rs" + ], + "Project panel should have no deleted file, no other file is selected in it" + ); + ensure_no_open_items_and_panes(window_id, &workspace, cx); + + select_path(&panel, "src/test/second.rs", cx); + panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx)); + cx.foreground().run_until_parked(); + assert_eq!( + visible_entries_as_strings(&panel, 0..10, cx), + &[ + "v src", + " v test", + " second.rs <== selected", + " third.rs" + ] + ); + ensure_single_file_is_opened(window_id, &workspace, "test/second.rs", cx); + + cx.update_window(window_id, |cx| { + let active_items = workspace + .read(cx) + .panes() + .iter() + .filter_map(|pane| pane.read(cx).active_item()) + .collect::>(); + assert_eq!(active_items.len(), 1); + let open_editor = active_items + .into_iter() + .next() + .unwrap() + .downcast::() + .expect("Open item should be an editor"); + open_editor.update(cx, |editor, cx| editor.set_text("Another text!", cx)); + }); + submit_deletion(window_id, &panel, cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..10, cx), + &["v src", " v test", " third.rs"], + "Project panel should have no deleted file, with one last file remaining" + ); + ensure_no_open_items_and_panes(window_id, &workspace, cx); + } + fn toggle_expand_dir( panel: &ViewHandle, path: impl AsRef, @@ -1956,4 +2046,94 @@ mod tests { workspace::init_settings(cx); }); } + + fn init_test_with_editor(cx: &mut TestAppContext) { + cx.foreground().forbid_parking(); + cx.update(|cx| { + let app_state = AppState::test(cx); + theme::init((), cx); + language::init(cx); + editor::init(cx); + pane::init(cx); + workspace::init(app_state.clone(), cx); + }); + } + + fn ensure_single_file_is_opened( + window_id: usize, + workspace: &ViewHandle, + expected_path: &str, + cx: &mut TestAppContext, + ) { + cx.read_window(window_id, |cx| { + let workspace = workspace.read(cx); + let worktrees = workspace.worktrees(cx).collect::>(); + assert_eq!(worktrees.len(), 1); + let worktree_id = WorktreeId::from_usize(worktrees[0].id()); + + let open_project_paths = workspace + .panes() + .iter() + .filter_map(|pane| pane.read(cx).active_item()?.project_path(cx)) + .collect::>(); + assert_eq!( + open_project_paths, + vec![ProjectPath { + worktree_id, + path: Arc::from(Path::new(expected_path)) + }], + "Should have opened file, selected in project panel" + ); + }); + } + + fn submit_deletion( + window_id: usize, + panel: &ViewHandle, + cx: &mut TestAppContext, + ) { + assert!( + !cx.has_pending_prompt(window_id), + "Should have no prompts before the deletion" + ); + panel.update(cx, |panel, cx| { + panel + .delete(&Delete, cx) + .expect("Deletion start") + .detach_and_log_err(cx); + }); + assert!( + cx.has_pending_prompt(window_id), + "Should have a prompt after the deletion" + ); + cx.simulate_prompt_answer(window_id, 0); + assert!( + !cx.has_pending_prompt(window_id), + "Should have no prompts after prompt was replied to" + ); + cx.foreground().run_until_parked(); + } + + fn ensure_no_open_items_and_panes( + window_id: usize, + workspace: &ViewHandle, + cx: &mut TestAppContext, + ) { + assert!( + !cx.has_pending_prompt(window_id), + "Should have no prompts after deletion operation closes the file" + ); + cx.read_window(window_id, |cx| { + let open_project_paths = workspace + .read(cx) + .panes() + .iter() + .filter_map(|pane| pane.read(cx).active_item()?.project_path(cx)) + .collect::>(); + assert!( + open_project_paths.is_empty(), + "Deleted file's buffer should be closed, but got open files: {open_project_paths:?}" + ); + }); + } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 368afcd16c..c54f8d393b 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1305,6 +1305,25 @@ impl Pane { &self.toolbar } + pub fn delete_item( + &mut self, + entry_id: ProjectEntryId, + cx: &mut ViewContext, + ) -> Option<()> { + let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| { + if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] { + Some((i, item.id())) + } else { + None + } + })?; + + self.remove_item(item_index_to_delete, false, cx); + self.nav_history.borrow_mut().remove_item(item_id); + + Some(()) + } + fn update_toolbar(&mut self, cx: &mut ViewContext) { let active_item = self .items @@ -2007,6 +2026,15 @@ impl NavHistory { }); } } + + fn remove_item(&mut self, item_id: usize) { + self.paths_by_item.remove(&item_id); + self.backward_stack + .retain(|entry| entry.item.id() != item_id); + self.forward_stack + .retain(|entry| entry.item.id() != item_id); + self.closed_stack.retain(|entry| entry.item.id() != item_id); + } } impl PaneNavHistory { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 28ad294798..11703aba6e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -537,6 +537,12 @@ impl Workspace { cx.remove_window(); } + project::Event::DeletedEntry(entry_id) => { + for pane in this.panes.iter() { + pane.update(cx, |pane, cx| pane.delete_item(*entry_id, cx)); + } + } + _ => {} } cx.notify() From 459cc9c9597ef31911330dc8130526590ebad7c4 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 May 2023 09:13:31 -0700 Subject: [PATCH 049/131] Optimize matching of multiple file-watch globs using the globset crate --- Cargo.lock | 4 +- Cargo.toml | 1 + crates/project/Cargo.toml | 2 +- crates/project/src/lsp_glob_set.rs | 129 ---------------------------- crates/project/src/project.rs | 34 +++++--- crates/project/src/project_tests.rs | 55 ++++++------ crates/project/src/search.rs | 70 ++++++--------- crates/search/Cargo.toml | 2 +- crates/search/src/project_search.rs | 74 ++++++++-------- 9 files changed, 119 insertions(+), 252 deletions(-) delete mode 100644 crates/project/src/lsp_glob_set.rs diff --git a/Cargo.lock b/Cargo.lock index d339ac3256..7129cc8b81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4851,7 +4851,7 @@ dependencies = [ "fuzzy", "git", "git2", - "glob", + "globset", "gpui", "ignore", "itertools", @@ -5949,7 +5949,7 @@ dependencies = [ "collections", "editor", "futures 0.3.28", - "glob", + "globset", "gpui", "language", "log", diff --git a/Cargo.toml b/Cargo.toml index f14e1c7355..c31624b5fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,6 +77,7 @@ async-trait = { version = "0.1" } ctor = { version = "0.1" } env_logger = { version = "0.9" } futures = { version = "0.3" } +globset = { version = "0.4" } glob = { version = "0.3.1" } lazy_static = { version = "1.4.0" } log = { version = "0.4.16", features = ["kv_unstable_serde"] } diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 190f1d96a8..d6578c87ba 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -42,7 +42,7 @@ anyhow.workspace = true async-trait.workspace = true backtrace = "0.3" futures.workspace = true -glob.workspace = true +globset.workspace = true ignore = "0.4" lazy_static.workspace = true log.workspace = true diff --git a/crates/project/src/lsp_glob_set.rs b/crates/project/src/lsp_glob_set.rs deleted file mode 100644 index d4d661b6a1..0000000000 --- a/crates/project/src/lsp_glob_set.rs +++ /dev/null @@ -1,129 +0,0 @@ -use anyhow::{anyhow, Result}; -use std::path::Path; - -#[derive(Default)] -pub struct LspGlobSet { - patterns: Vec, -} - -impl LspGlobSet { - pub fn clear(&mut self) { - self.patterns.clear(); - } - - /// Add a pattern to the glob set. - /// - /// LSP's glob syntax supports bash-style brace expansion. For example, - /// the pattern '*.{js,ts}' would match all JavaScript or TypeScript files. - /// This is not a part of the standard libc glob syntax, and isn't supported - /// by the `glob` crate. So we pre-process the glob patterns, producing a - /// separate glob `Pattern` object for each part of a brace expansion. - pub fn add_pattern(&mut self, pattern: &str) -> Result<()> { - // Find all of the ranges of `pattern` that contain matched curly braces. - let mut expansion_ranges = Vec::new(); - let mut expansion_start_ix = None; - for (ix, c) in pattern.match_indices(|c| ['{', '}'].contains(&c)) { - match c { - "{" => { - if expansion_start_ix.is_some() { - return Err(anyhow!("nested braces in glob patterns aren't supported")); - } - expansion_start_ix = Some(ix); - } - "}" => { - if let Some(start_ix) = expansion_start_ix { - expansion_ranges.push(start_ix..ix + 1); - } - expansion_start_ix = None; - } - _ => {} - } - } - - // Starting with a single pattern, process each brace expansion by cloning - // the pattern once per element of the expansion. - let mut unexpanded_patterns = vec![]; - let mut expanded_patterns = vec![pattern.to_string()]; - - for outer_range in expansion_ranges.into_iter().rev() { - let inner_range = (outer_range.start + 1)..(outer_range.end - 1); - std::mem::swap(&mut unexpanded_patterns, &mut expanded_patterns); - for unexpanded_pattern in unexpanded_patterns.drain(..) { - for part in unexpanded_pattern[inner_range.clone()].split(',') { - let mut expanded_pattern = unexpanded_pattern.clone(); - expanded_pattern.replace_range(outer_range.clone(), part); - expanded_patterns.push(expanded_pattern); - } - } - } - - // Parse the final glob patterns and add them to the set. - for pattern in expanded_patterns { - let pattern = glob::Pattern::new(&pattern)?; - self.patterns.push(pattern); - } - - Ok(()) - } - - pub fn matches(&self, path: &Path) -> bool { - self.patterns - .iter() - .any(|pattern| pattern.matches_path(path)) - } -} - -impl std::fmt::Debug for LspGlobSet { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_set() - .entries(self.patterns.iter().map(|p| p.as_str())) - .finish() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_glob_set() { - let mut watch = LspGlobSet::default(); - watch.add_pattern("/a/**/*.rs").unwrap(); - watch.add_pattern("/a/**/Cargo.toml").unwrap(); - - assert!(watch.matches("/a/b.rs".as_ref())); - assert!(watch.matches("/a/b/c.rs".as_ref())); - - assert!(!watch.matches("/b/c.rs".as_ref())); - assert!(!watch.matches("/a/b.ts".as_ref())); - } - - #[test] - fn test_brace_expansion() { - let mut watch = LspGlobSet::default(); - watch.add_pattern("/a/*.{ts,js,tsx}").unwrap(); - - assert!(watch.matches("/a/one.js".as_ref())); - assert!(watch.matches("/a/two.ts".as_ref())); - assert!(watch.matches("/a/three.tsx".as_ref())); - - assert!(!watch.matches("/a/one.j".as_ref())); - assert!(!watch.matches("/a/two.s".as_ref())); - assert!(!watch.matches("/a/three.t".as_ref())); - assert!(!watch.matches("/a/four.t".as_ref())); - assert!(!watch.matches("/a/five.xt".as_ref())); - } - - #[test] - fn test_multiple_brace_expansion() { - let mut watch = LspGlobSet::default(); - watch.add_pattern("/a/{one,two,three}.{b*c,d*e}").unwrap(); - - assert!(watch.matches("/a/one.bic".as_ref())); - assert!(watch.matches("/a/two.dole".as_ref())); - assert!(watch.matches("/a/three.deeee".as_ref())); - - assert!(!watch.matches("/a/four.bic".as_ref())); - assert!(!watch.matches("/a/one.be".as_ref())); - } -} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 23b4984e55..4b759c4240 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1,6 +1,5 @@ mod ignore; mod lsp_command; -mod lsp_glob_set; mod project_settings; pub mod search; pub mod terminals; @@ -19,6 +18,7 @@ use futures::{ future::{try_join_all, Shared}, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt, }; +use globset::{Glob, GlobSet, GlobSetBuilder}; use gpui::{ AnyModelHandle, AppContext, AsyncAppContext, BorrowAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle, @@ -41,7 +41,6 @@ use lsp::{ DocumentHighlightKind, LanguageServer, LanguageServerId, }; use lsp_command::*; -use lsp_glob_set::LspGlobSet; use postage::watch; use project_settings::ProjectSettings; use rand::prelude::*; @@ -226,7 +225,7 @@ pub enum LanguageServerState { language: Arc, adapter: Arc, server: Arc, - watched_paths: HashMap, + watched_paths: HashMap, simulate_disk_based_diagnostics_completion: Option>, }, } @@ -2867,8 +2866,10 @@ impl Project { if let Some(LanguageServerState::Running { watched_paths, .. }) = self.language_servers.get_mut(&language_server_id) { - watched_paths.clear(); + eprintln!("change watch"); + let mut builders = HashMap::default(); for watcher in params.watchers { + eprintln!(" {}", watcher.glob_pattern); for worktree in &self.worktrees { if let Some(worktree) = worktree.upgrade(cx) { let worktree = worktree.read(cx); @@ -2878,17 +2879,26 @@ impl Project { .strip_prefix(abs_path) .and_then(|s| s.strip_prefix(std::path::MAIN_SEPARATOR)) { - watched_paths - .entry(worktree.id()) - .or_default() - .add_pattern(suffix) - .log_err(); + if let Some(glob) = Glob::new(suffix).log_err() { + builders + .entry(worktree.id()) + .or_insert_with(|| GlobSetBuilder::new()) + .add(glob); + } break; } } } } } + + watched_paths.clear(); + for (worktree_id, builder) in builders { + if let Ok(globset) = builder.build() { + watched_paths.insert(worktree_id, globset); + } + } + cx.notify(); } } @@ -4725,6 +4735,10 @@ impl Project { changes: &HashMap<(Arc, ProjectEntryId), PathChange>, cx: &mut ModelContext, ) { + if changes.is_empty() { + return; + } + let worktree_id = worktree_handle.read(cx).id(); let mut language_server_ids = self .language_server_ids @@ -4750,7 +4764,7 @@ impl Project { changes: changes .iter() .filter_map(|((path, _), change)| { - if watched_paths.matches(&path) { + if watched_paths.is_match(&path) { Some(lsp::FileEvent { uri: lsp::Url::from_file_path(abs_path.join(path)) .unwrap(), diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index e7b1a84924..69bcea8ce0 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1,6 +1,7 @@ use crate::{worktree::WorktreeHandle, Event, *}; use fs::{FakeFs, LineEnding, RealFs}; use futures::{future, StreamExt}; +use globset::Glob; use gpui::{executor::Deterministic, test::subscribe, AppContext}; use language::{ language_settings::{AllLanguageSettings, LanguageSettingsContent}, @@ -505,7 +506,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon register_options: serde_json::to_value( lsp::DidChangeWatchedFilesRegistrationOptions { watchers: vec![lsp::FileSystemWatcher { - glob_pattern: "*.{rs,c}".to_string(), + glob_pattern: "/the-root/*.{rs,c}".to_string(), kind: None, }], }, @@ -3393,7 +3394,7 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) { search_query, false, true, - vec![glob::Pattern::new("*.odd").unwrap()], + vec![Glob::new("*.odd").unwrap().compile_matcher()], Vec::new() ), cx @@ -3411,7 +3412,7 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) { search_query, false, true, - vec![glob::Pattern::new("*.rs").unwrap()], + vec![Glob::new("*.rs").unwrap().compile_matcher()], Vec::new() ), cx @@ -3433,8 +3434,8 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) { false, true, vec![ - glob::Pattern::new("*.ts").unwrap(), - glob::Pattern::new("*.odd").unwrap(), + Glob::new("*.ts").unwrap().compile_matcher(), + Glob::new("*.odd").unwrap().compile_matcher(), ], Vec::new() ), @@ -3457,9 +3458,9 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) { false, true, vec![ - glob::Pattern::new("*.rs").unwrap(), - glob::Pattern::new("*.ts").unwrap(), - glob::Pattern::new("*.odd").unwrap(), + Glob::new("*.rs").unwrap().compile_matcher(), + Glob::new("*.ts").unwrap().compile_matcher(), + Glob::new("*.odd").unwrap().compile_matcher(), ], Vec::new() ), @@ -3504,7 +3505,7 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) { false, true, Vec::new(), - vec![glob::Pattern::new("*.odd").unwrap()], + vec![Glob::new("*.odd").unwrap().compile_matcher()], ), cx ) @@ -3527,7 +3528,7 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) { false, true, Vec::new(), - vec![glob::Pattern::new("*.rs").unwrap()], + vec![Glob::new("*.rs").unwrap().compile_matcher()], ), cx ) @@ -3549,8 +3550,8 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) { true, Vec::new(), vec![ - glob::Pattern::new("*.ts").unwrap(), - glob::Pattern::new("*.odd").unwrap(), + Glob::new("*.ts").unwrap().compile_matcher(), + Glob::new("*.odd").unwrap().compile_matcher(), ], ), cx @@ -3573,9 +3574,9 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) { true, Vec::new(), vec![ - glob::Pattern::new("*.rs").unwrap(), - glob::Pattern::new("*.ts").unwrap(), - glob::Pattern::new("*.odd").unwrap(), + Glob::new("*.rs").unwrap().compile_matcher(), + Glob::new("*.ts").unwrap().compile_matcher(), + Glob::new("*.odd").unwrap().compile_matcher(), ], ), cx @@ -3612,8 +3613,8 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex search_query, false, true, - vec![glob::Pattern::new("*.odd").unwrap()], - vec![glob::Pattern::new("*.odd").unwrap()], + vec![Glob::new("*.odd").unwrap().compile_matcher()], + vec![Glob::new("*.odd").unwrap().compile_matcher()], ), cx ) @@ -3630,8 +3631,8 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex search_query, false, true, - vec![glob::Pattern::new("*.ts").unwrap()], - vec![glob::Pattern::new("*.ts").unwrap()], + vec![Glob::new("*.ts").unwrap().compile_matcher()], + vec![Glob::new("*.ts").unwrap().compile_matcher()], ), cx ) @@ -3649,12 +3650,12 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex false, true, vec![ - glob::Pattern::new("*.ts").unwrap(), - glob::Pattern::new("*.odd").unwrap() + Glob::new("*.ts").unwrap().compile_matcher(), + Glob::new("*.odd").unwrap().compile_matcher() ], vec![ - glob::Pattern::new("*.ts").unwrap(), - glob::Pattern::new("*.odd").unwrap() + Glob::new("*.ts").unwrap().compile_matcher(), + Glob::new("*.odd").unwrap().compile_matcher() ], ), cx @@ -3673,12 +3674,12 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex false, true, vec![ - glob::Pattern::new("*.ts").unwrap(), - glob::Pattern::new("*.odd").unwrap() + Glob::new("*.ts").unwrap().compile_matcher(), + Glob::new("*.odd").unwrap().compile_matcher() ], vec![ - glob::Pattern::new("*.rs").unwrap(), - glob::Pattern::new("*.odd").unwrap() + Glob::new("*.rs").unwrap().compile_matcher(), + Glob::new("*.odd").unwrap().compile_matcher() ], ), cx diff --git a/crates/project/src/search.rs b/crates/project/src/search.rs index ed139c97d3..4b4126fef2 100644 --- a/crates/project/src/search.rs +++ b/crates/project/src/search.rs @@ -1,6 +1,7 @@ use aho_corasick::{AhoCorasick, AhoCorasickBuilder}; use anyhow::Result; use client::proto; +use globset::{Glob, GlobMatcher}; use itertools::Itertools; use language::{char_kind, Rope}; use regex::{Regex, RegexBuilder}; @@ -19,8 +20,8 @@ pub enum SearchQuery { query: Arc, whole_word: bool, case_sensitive: bool, - files_to_include: Vec, - files_to_exclude: Vec, + files_to_include: Vec, + files_to_exclude: Vec, }, Regex { regex: Regex, @@ -28,8 +29,8 @@ pub enum SearchQuery { multiline: bool, whole_word: bool, case_sensitive: bool, - files_to_include: Vec, - files_to_exclude: Vec, + files_to_include: Vec, + files_to_exclude: Vec, }, } @@ -38,8 +39,8 @@ impl SearchQuery { query: impl ToString, whole_word: bool, case_sensitive: bool, - files_to_include: Vec, - files_to_exclude: Vec, + files_to_include: Vec, + files_to_exclude: Vec, ) -> Self { let query = query.to_string(); let search = AhoCorasickBuilder::new() @@ -60,8 +61,8 @@ impl SearchQuery { query: impl ToString, whole_word: bool, case_sensitive: bool, - files_to_include: Vec, - files_to_exclude: Vec, + files_to_include: Vec, + files_to_exclude: Vec, ) -> Result { let mut query = query.to_string(); let initial_query = Arc::from(query.as_str()); @@ -95,40 +96,16 @@ impl SearchQuery { message.query, message.whole_word, message.case_sensitive, - message - .files_to_include - .split(',') - .map(str::trim) - .filter(|glob_str| !glob_str.is_empty()) - .map(|glob_str| glob::Pattern::new(glob_str)) - .collect::>()?, - message - .files_to_exclude - .split(',') - .map(str::trim) - .filter(|glob_str| !glob_str.is_empty()) - .map(|glob_str| glob::Pattern::new(glob_str)) - .collect::>()?, + deserialize_globs(&message.files_to_include)?, + deserialize_globs(&message.files_to_exclude)?, ) } else { Ok(Self::text( message.query, message.whole_word, message.case_sensitive, - message - .files_to_include - .split(',') - .map(str::trim) - .filter(|glob_str| !glob_str.is_empty()) - .map(|glob_str| glob::Pattern::new(glob_str)) - .collect::>()?, - message - .files_to_exclude - .split(',') - .map(str::trim) - .filter(|glob_str| !glob_str.is_empty()) - .map(|glob_str| glob::Pattern::new(glob_str)) - .collect::>()?, + deserialize_globs(&message.files_to_include)?, + deserialize_globs(&message.files_to_exclude)?, )) } } @@ -143,12 +120,12 @@ impl SearchQuery { files_to_include: self .files_to_include() .iter() - .map(ToString::to_string) + .map(|g| g.glob().to_string()) .join(","), files_to_exclude: self .files_to_exclude() .iter() - .map(ToString::to_string) + .map(|g| g.glob().to_string()) .join(","), } } @@ -289,7 +266,7 @@ impl SearchQuery { matches!(self, Self::Regex { .. }) } - pub fn files_to_include(&self) -> &[glob::Pattern] { + pub fn files_to_include(&self) -> &[GlobMatcher] { match self { Self::Text { files_to_include, .. @@ -300,7 +277,7 @@ impl SearchQuery { } } - pub fn files_to_exclude(&self) -> &[glob::Pattern] { + pub fn files_to_exclude(&self) -> &[GlobMatcher] { match self { Self::Text { files_to_exclude, .. @@ -317,14 +294,23 @@ impl SearchQuery { !self .files_to_exclude() .iter() - .any(|exclude_glob| exclude_glob.matches_path(file_path)) + .any(|exclude_glob| exclude_glob.is_match(file_path)) && (self.files_to_include().is_empty() || self .files_to_include() .iter() - .any(|include_glob| include_glob.matches_path(file_path))) + .any(|include_glob| include_glob.is_match(file_path))) } None => self.files_to_include().is_empty(), } } } + +fn deserialize_globs(glob_set: &str) -> Result> { + glob_set + .split(',') + .map(str::trim) + .filter(|glob_str| !glob_str.is_empty()) + .map(|glob_str| Ok(Glob::new(glob_str)?.compile_matcher())) + .collect() +} diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 14e658e8f8..7ef388f7c0 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -27,7 +27,7 @@ serde.workspace = true serde_derive.workspace = true smallvec.workspace = true smol.workspace = true -glob.workspace = true +globset.workspace = true [dev-dependencies] client = { path = "../client", features = ["test-support"] } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 17f86c153c..d96d77eb00 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -2,12 +2,14 @@ use crate::{ SearchOption, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex, ToggleWholeWord, }; +use anyhow::Result; use collections::HashMap; use editor::{ items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, MultiBuffer, SelectAll, MAX_TAB_TITLE_LEN, }; use futures::StreamExt; +use globset::{Glob, GlobMatcher}; use gpui::{ actions, elements::*, @@ -571,46 +573,30 @@ impl ProjectSearchView { fn build_search_query(&mut self, cx: &mut ViewContext) -> Option { let text = self.query_editor.read(cx).text(cx); - let included_files = match self - .included_files_editor - .read(cx) - .text(cx) - .split(',') - .map(str::trim) - .filter(|glob_str| !glob_str.is_empty()) - .map(|glob_str| glob::Pattern::new(glob_str)) - .collect::>() - { - Ok(included_files) => { - self.panels_with_errors.remove(&InputPanel::Include); - included_files - } - Err(_e) => { - self.panels_with_errors.insert(InputPanel::Include); - cx.notify(); - return None; - } - }; - let excluded_files = match self - .excluded_files_editor - .read(cx) - .text(cx) - .split(',') - .map(str::trim) - .filter(|glob_str| !glob_str.is_empty()) - .map(|glob_str| glob::Pattern::new(glob_str)) - .collect::>() - { - Ok(excluded_files) => { - self.panels_with_errors.remove(&InputPanel::Exclude); - excluded_files - } - Err(_e) => { - self.panels_with_errors.insert(InputPanel::Exclude); - cx.notify(); - return None; - } - }; + let included_files = + match Self::load_glob_set(&self.included_files_editor.read(cx).text(cx)) { + Ok(included_files) => { + self.panels_with_errors.remove(&InputPanel::Include); + included_files + } + Err(_e) => { + self.panels_with_errors.insert(InputPanel::Include); + cx.notify(); + return None; + } + }; + let excluded_files = + match Self::load_glob_set(&self.excluded_files_editor.read(cx).text(cx)) { + Ok(excluded_files) => { + self.panels_with_errors.remove(&InputPanel::Exclude); + excluded_files + } + Err(_e) => { + self.panels_with_errors.insert(InputPanel::Exclude); + cx.notify(); + return None; + } + }; if self.regex { match SearchQuery::regex( text, @@ -640,6 +626,14 @@ impl ProjectSearchView { } } + fn load_glob_set(text: &str) -> Result> { + text.split(',') + .map(str::trim) + .filter(|glob_str| !glob_str.is_empty()) + .map(|glob_str| anyhow::Ok(Glob::new(glob_str)?.compile_matcher())) + .collect() + } + fn select_match(&mut self, direction: Direction, cx: &mut ViewContext) { if let Some(index) = self.active_match_index { let match_ranges = self.model.read(cx).match_ranges.clone(); From 847d1e73a33e075e3129e72d1b47eb2e83566568 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 May 2023 09:36:46 -0700 Subject: [PATCH 050/131] Replace remaining usages of glob crate with globset --- Cargo.lock | 4 +--- Cargo.toml | 1 - crates/copilot_button/src/copilot_button.rs | 5 ++--- crates/editor/Cargo.toml | 2 -- crates/language/Cargo.toml | 2 +- crates/language/src/language_settings.rs | 7 ++++--- crates/settings/Cargo.toml | 1 - 7 files changed, 8 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7129cc8b81..3ae96f4751 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2038,7 +2038,6 @@ dependencies = [ "futures 0.3.28", "fuzzy", "git", - "glob", "gpui", "indoc", "itertools", @@ -3442,7 +3441,7 @@ dependencies = [ "futures 0.3.28", "fuzzy", "git", - "glob", + "globset", "gpui", "indoc", "lazy_static", @@ -6111,7 +6110,6 @@ dependencies = [ "collections", "fs", "futures 0.3.28", - "glob", "gpui", "json_comments", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index c31624b5fe..4854be0c7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,6 @@ ctor = { version = "0.1" } env_logger = { version = "0.9" } futures = { version = "0.3" } globset = { version = "0.4" } -glob = { version = "0.3.1" } lazy_static = { version = "1.4.0" } log = { version = "0.4.16", features = ["kv_unstable_serde"] } ordered-float = { version = "2.1.1" } diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 73cd8f6a1d..fdd0da1970 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -335,10 +335,9 @@ async fn configure_disabled_globs( .get::(None) .copilot .disabled_globs - .clone() .iter() - .map(|glob| glob.as_str().to_string()) - .collect::>() + .map(|glob| glob.glob().to_string()) + .collect() }); if let Some(path_to_disable) = &path_to_disable { diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index fc7bf4b8ab..325883b7c0 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -49,7 +49,6 @@ workspace = { path = "../workspace" } aho-corasick = "0.7" anyhow.workspace = true futures.workspace = true -glob.workspace = true indoc = "1.0.4" itertools = "0.10" lazy_static.workspace = true @@ -82,7 +81,6 @@ workspace = { path = "../workspace", features = ["test-support"] } ctor.workspace = true env_logger.workspace = true -glob.workspace = true rand.workspace = true unindent.workspace = true tree-sitter = "0.20" diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 5a7644d98e..7e81620e5c 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -41,7 +41,7 @@ anyhow.workspace = true async-broadcast = "0.4" async-trait.workspace = true futures.workspace = true -glob.workspace = true +globset.workspace = true lazy_static.workspace = true log.workspace = true parking_lot.workspace = true diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index b47982819a..d877304f1d 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -1,5 +1,6 @@ use anyhow::Result; use collections::HashMap; +use globset::GlobMatcher; use gpui::AppContext; use schemars::{ schema::{InstanceType, ObjectValidation, Schema, SchemaObject}, @@ -45,7 +46,7 @@ pub struct LanguageSettings { #[derive(Clone, Debug, Default)] pub struct CopilotSettings { pub feature_enabled: bool, - pub disabled_globs: Vec, + pub disabled_globs: Vec, } #[derive(Clone, Serialize, Deserialize, JsonSchema)] @@ -151,7 +152,7 @@ impl AllLanguageSettings { .copilot .disabled_globs .iter() - .any(|glob| glob.matches_path(path)) + .any(|glob| glob.is_match(path)) } pub fn copilot_enabled(&self, language_name: Option<&str>, path: Option<&Path>) -> bool { @@ -236,7 +237,7 @@ impl settings::Setting for AllLanguageSettings { feature_enabled: copilot_enabled, disabled_globs: copilot_globs .iter() - .filter_map(|pattern| glob::Pattern::new(pattern).ok()) + .filter_map(|g| Some(globset::Glob::new(g).ok()?.compile_matcher())) .collect(), }, defaults, diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 2cb6637ead..1ec0ff4a63 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -22,7 +22,6 @@ util = { path = "../util" } anyhow.workspace = true futures.workspace = true -glob.workspace = true json_comments = "0.2" lazy_static.workspace = true postage.workspace = true From 5ff49bde317ba4cacc827fb8f91458b9f02dd6c2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 May 2023 19:26:32 +0200 Subject: [PATCH 051/131] Serialize and deserialize `TerminalPanel` --- crates/terminal_view/src/terminal_panel.rs | 115 +++++++++++++- crates/workspace/src/workspace.rs | 69 ++++---- crates/zed/src/zed.rs | 173 +++++++++++---------- 3 files changed, 245 insertions(+), 112 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 4652b58072..1f4096880c 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1,15 +1,20 @@ use crate::TerminalView; +use db::kvp::KEY_VALUE_STORE; use gpui::{ - actions, anyhow, elements::*, AppContext, Entity, Subscription, View, ViewContext, ViewHandle, - WeakViewHandle, WindowContext, + actions, anyhow::Result, elements::*, serde_json, AppContext, AsyncAppContext, Entity, + Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; +use serde::{Deserialize, Serialize}; use settings::{settings_file::SettingsFile, Settings, TerminalDockPosition, WorkingDirectory}; -use util::ResultExt; +use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel}, + item::Item, pane, DraggedItem, Pane, Workspace, }; +const TERMINAL_PANEL_KEY: &'static str = "TerminalPanel"; + actions!(terminal_panel, [ToggleFocus]); pub fn init(cx: &mut AppContext) { @@ -27,6 +32,7 @@ pub enum Event { pub struct TerminalPanel { pane: ViewHandle, workspace: WeakViewHandle, + pending_serialization: Task>, _subscriptions: Vec, } @@ -86,10 +92,79 @@ impl TerminalPanel { Self { pane, workspace: workspace.weak_handle(), + pending_serialization: Task::ready(None), _subscriptions: subscriptions, } } + pub fn load( + workspace: WeakViewHandle, + cx: AsyncAppContext, + ) -> Task>> { + cx.spawn(|mut cx| async move { + let serialized_panel = if let Some(panel) = cx + .background() + .spawn(async move { KEY_VALUE_STORE.read_kvp(TERMINAL_PANEL_KEY) }) + .await? + { + Some(serde_json::from_str::(&panel)?) + } else { + None + }; + let (panel, pane, items) = workspace.update(&mut cx, |workspace, cx| { + let panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); + let items = if let Some(serialized_panel) = serialized_panel.as_ref() { + panel.update(cx, |panel, cx| { + panel.pane.update(cx, |_, cx| { + serialized_panel + .items + .iter() + .map(|item_id| { + TerminalView::deserialize( + workspace.project().clone(), + workspace.weak_handle(), + workspace.database_id(), + *item_id, + cx, + ) + }) + .collect::>() + }) + }) + } else { + Default::default() + }; + let pane = panel.read(cx).pane.clone(); + (panel, pane, items) + })?; + + let items = futures::future::join_all(items).await; + workspace.update(&mut cx, |workspace, cx| { + let active_item_id = serialized_panel + .as_ref() + .and_then(|panel| panel.active_item_id); + let mut active_ix = None; + for item in items { + if let Some(item) = item.log_err() { + let item_id = item.id(); + Pane::add_item(workspace, &pane, Box::new(item), false, false, None, cx); + if Some(item_id) == active_item_id { + active_ix = Some(pane.read(cx).items_len() - 1); + } + } + } + + if let Some(active_ix) = active_ix { + pane.update(cx, |pane, cx| { + pane.activate_item(active_ix, false, false, cx) + }); + } + })?; + + Ok(panel) + }) + } + fn handle_pane_event( &mut self, _pane: ViewHandle, @@ -97,6 +172,8 @@ impl TerminalPanel { cx: &mut ViewContext, ) { match event { + pane::Event::ActivateItem { .. } => self.serialize(cx), + pane::Event::RemoveItem { .. } => self.serialize(cx), pane::Event::Remove => cx.emit(Event::Close), pane::Event::ZoomIn => cx.emit(Event::ZoomIn), pane::Event::ZoomOut => cx.emit(Event::ZoomOut), @@ -131,10 +208,36 @@ impl TerminalPanel { Pane::add_item(workspace, &pane, terminal, true, true, None, cx); } })?; + this.update(&mut cx, |this, cx| this.serialize(cx))?; anyhow::Ok(()) }) .detach_and_log_err(cx); } + + fn serialize(&mut self, cx: &mut ViewContext) { + let items = self + .pane + .read(cx) + .items() + .map(|item| item.id()) + .collect::>(); + let active_item_id = self.pane.read(cx).active_item().map(|item| item.id()); + self.pending_serialization = cx.background().spawn( + async move { + KEY_VALUE_STORE + .write_kvp( + TERMINAL_PANEL_KEY.into(), + serde_json::to_string(&SerializedTerminalPanel { + items, + active_item_id, + })?, + ) + .await?; + anyhow::Ok(()) + } + .log_err(), + ); + } } impl Entity for TerminalPanel { @@ -255,3 +358,9 @@ impl Panel for TerminalPanel { matches!(event, Event::Focus) } } + +#[derive(Serialize, Deserialize)] +struct SerializedTerminalPanel { + items: Vec, + active_item_id: Option, +} diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index b26acf9b4d..5dc7c066f7 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -361,7 +361,8 @@ pub struct AppState { pub fs: Arc, pub build_window_options: fn(Option, Option, &dyn Platform) -> WindowOptions<'static>, - pub initialize_workspace: fn(&mut Workspace, bool, &Arc, &mut ViewContext), + pub initialize_workspace: + fn(WeakViewHandle, bool, Arc, AsyncAppContext) -> Task>, pub background_actions: BackgroundActions, } @@ -383,7 +384,7 @@ impl AppState { fs, languages, user_store, - initialize_workspace: |_, _, _, _| {}, + initialize_workspace: |_, _, _, _| Task::ready(Ok(())), build_window_options: |_, _, _| Default::default(), background_actions: || &[], }) @@ -730,31 +731,19 @@ impl Workspace { )) }); - let build_workspace = - |cx: &mut ViewContext, - serialized_workspace: Option| { - let was_deserialized = serialized_workspace.is_some(); - let mut workspace = Workspace::new( - serialized_workspace, - workspace_id, - project_handle.clone(), - app_state.clone(), - cx, - ); - (app_state.initialize_workspace)( - &mut workspace, - was_deserialized, - &app_state, - cx, - ); - workspace - }; + let was_deserialized = serialized_workspace.is_some(); let workspace = requesting_window_id .and_then(|window_id| { cx.update(|cx| { cx.replace_root_view(window_id, |cx| { - build_workspace(cx, serialized_workspace.take()) + Workspace::new( + serialized_workspace.take(), + workspace_id, + project_handle.clone(), + app_state.clone(), + cx, + ) }) }) }) @@ -794,11 +783,28 @@ impl Workspace { // Use the serialized workspace to construct the new window cx.add_window( (app_state.build_window_options)(bounds, display, cx.platform().as_ref()), - |cx| build_workspace(cx, serialized_workspace), + |cx| { + Workspace::new( + serialized_workspace, + workspace_id, + project_handle.clone(), + app_state.clone(), + cx, + ) + }, ) .1 }); + (app_state.initialize_workspace)( + workspace.downgrade(), + was_deserialized, + app_state.clone(), + cx.clone(), + ) + .await + .log_err(); + let workspace = workspace.downgrade(); notify_if_database_failed(&workspace, &mut cx); @@ -2740,7 +2746,7 @@ impl Workspace { user_store: project.read(cx).user_store(), fs: project.read(cx).fs().clone(), build_window_options: |_, _, _| Default::default(), - initialize_workspace: |_, _, _, _| {}, + initialize_workspace: |_, _, _, _| Task::ready(Ok(())), background_actions: || &[], }); Self::new(None, 0, project, app_state, cx) @@ -3097,12 +3103,17 @@ pub fn join_remote_project( let (_, workspace) = cx.add_window( (app_state.build_window_options)(None, None, cx.platform().as_ref()), - |cx| { - let mut workspace = Workspace::new(None, 0, project, app_state.clone(), cx); - (app_state.initialize_workspace)(&mut workspace, false, &app_state, cx); - workspace - }, + |cx| Workspace::new(None, 0, project, app_state.clone(), cx), ); + (app_state.initialize_workspace)( + workspace.downgrade(), + false, + app_state.clone(), + cx.clone(), + ) + .await + .log_err(); + workspace.downgrade() }; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 35a430f63f..2608ea1ec4 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -18,10 +18,11 @@ use feedback::{ use futures::StreamExt; use gpui::{ actions, + anyhow::{self, Result}, geometry::vector::vec2f, impl_actions, platform::{Platform, PromptLevel, TitlebarOptions, WindowBounds, WindowKind, WindowOptions}, - AppContext, ViewContext, + AppContext, AsyncAppContext, Task, ViewContext, WeakViewHandle, }; pub use lsp; pub use project; @@ -281,93 +282,105 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { } pub fn initialize_workspace( - workspace: &mut Workspace, + workspace_handle: WeakViewHandle, was_deserialized: bool, - app_state: &Arc, - cx: &mut ViewContext, -) { - let workspace_handle = cx.handle(); - cx.subscribe(&workspace_handle, { - move |workspace, _, event, cx| { - if let workspace::Event::PaneAdded(pane) = event { - pane.update(cx, |pane, cx| { - pane.toolbar().update(cx, |toolbar, cx| { - let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace)); - toolbar.add_item(breadcrumbs, cx); - let buffer_search_bar = cx.add_view(BufferSearchBar::new); - toolbar.add_item(buffer_search_bar, cx); - let project_search_bar = cx.add_view(|_| ProjectSearchBar::new()); - toolbar.add_item(project_search_bar, cx); - let submit_feedback_button = cx.add_view(|_| SubmitFeedbackButton::new()); - toolbar.add_item(submit_feedback_button, cx); - let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new()); - toolbar.add_item(feedback_info_text, cx); - let lsp_log_item = cx.add_view(|_| { - lsp_log::LspLogToolbarItemView::new(workspace.project().clone()) + app_state: Arc, + cx: AsyncAppContext, +) -> Task> { + cx.spawn(|mut cx| async move { + workspace_handle.update(&mut cx, |workspace, cx| { + let workspace_handle = cx.handle(); + cx.subscribe(&workspace_handle, { + move |workspace, _, event, cx| { + if let workspace::Event::PaneAdded(pane) = event { + pane.update(cx, |pane, cx| { + pane.toolbar().update(cx, |toolbar, cx| { + let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace)); + toolbar.add_item(breadcrumbs, cx); + let buffer_search_bar = cx.add_view(BufferSearchBar::new); + toolbar.add_item(buffer_search_bar, cx); + let project_search_bar = cx.add_view(|_| ProjectSearchBar::new()); + toolbar.add_item(project_search_bar, cx); + let submit_feedback_button = + cx.add_view(|_| SubmitFeedbackButton::new()); + toolbar.add_item(submit_feedback_button, cx); + let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new()); + toolbar.add_item(feedback_info_text, cx); + let lsp_log_item = cx.add_view(|_| { + lsp_log::LspLogToolbarItemView::new(workspace.project().clone()) + }); + toolbar.add_item(lsp_log_item, cx); + }) }); - toolbar.add_item(lsp_log_item, cx); - }) - }); - } - } - }) - .detach(); - - cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone())); - - let collab_titlebar_item = - cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); - workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); - - let project_panel = ProjectPanel::new(workspace, cx); - let project_panel_position = project_panel.position(cx); - workspace.add_panel(project_panel, cx); - if !was_deserialized - && workspace - .project() - .read(cx) - .visible_worktrees(cx) - .any(|tree| { - tree.read(cx) - .root_entry() - .map_or(false, |entry| entry.is_dir()) + } + } }) - { - workspace.toggle_dock(project_panel_position, cx); - } + .detach(); - let terminal_panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); - workspace.add_panel(terminal_panel, cx); + cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone())); - let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx)); - let diagnostic_summary = - cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); - let activity_indicator = - activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx); - let active_buffer_language = - cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace)); - let feedback_button = - cx.add_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)); - let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); - workspace.status_bar().update(cx, |status_bar, cx| { - status_bar.add_left_item(diagnostic_summary, cx); - status_bar.add_left_item(activity_indicator, cx); - status_bar.add_right_item(feedback_button, cx); - status_bar.add_right_item(copilot, cx); - status_bar.add_right_item(active_buffer_language, cx); - status_bar.add_right_item(cursor_position, cx); - }); + let collab_titlebar_item = + cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); + workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); - auto_update::notify_of_any_new_update(cx.weak_handle(), cx); + let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx)); + let diagnostic_summary = + cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); + let activity_indicator = activity_indicator::ActivityIndicator::new( + workspace, + app_state.languages.clone(), + cx, + ); + let active_buffer_language = + cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace)); + let feedback_button = cx.add_view(|_| { + feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace) + }); + let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); + workspace.status_bar().update(cx, |status_bar, cx| { + status_bar.add_left_item(diagnostic_summary, cx); + status_bar.add_left_item(activity_indicator, cx); + status_bar.add_right_item(feedback_button, cx); + status_bar.add_right_item(copilot, cx); + status_bar.add_right_item(active_buffer_language, cx); + status_bar.add_right_item(cursor_position, cx); + }); - vim::observe_keystrokes(cx); + auto_update::notify_of_any_new_update(cx.weak_handle(), cx); - cx.on_window_should_close(|workspace, cx| { - if let Some(task) = workspace.close(&Default::default(), cx) { - task.detach_and_log_err(cx); - } - false - }); + vim::observe_keystrokes(cx); + + cx.on_window_should_close(|workspace, cx| { + if let Some(task) = workspace.close(&Default::default(), cx) { + task.detach_and_log_err(cx); + } + false + }); + + let project_panel = ProjectPanel::new(workspace, cx); + let project_panel_position = project_panel.position(cx); + workspace.add_panel(project_panel, cx); + if !was_deserialized + && workspace + .project() + .read(cx) + .visible_worktrees(cx) + .any(|tree| { + tree.read(cx) + .root_entry() + .map_or(false, |entry| entry.is_dir()) + }) + { + workspace.toggle_dock(project_panel_position, cx); + } + })?; + + let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone()).await?; + workspace_handle.update(&mut cx, |workspace, cx| { + workspace.add_panel(terminal_panel, cx) + })?; + Ok(()) + }) } pub fn build_window_options( From 5a8fb18c20363da73179efc4e105648e7300a57e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 May 2023 19:35:00 +0200 Subject: [PATCH 052/131] Show workspace only after initializing it --- crates/collab_ui/src/incoming_call_notification.rs | 1 + crates/collab_ui/src/project_shared_notification.rs | 1 + crates/copilot/src/sign_in.rs | 1 + crates/gpui/src/platform.rs | 2 ++ crates/gpui/src/platform/mac/window.rs | 2 +- crates/workspace/src/workspace.rs | 2 ++ crates/zed/src/zed.rs | 3 ++- 7 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index 35484b3309..d8cf01d20c 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -42,6 +42,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { titlebar: None, center: false, focus: false, + show: true, kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index 8a41368276..fac2588e0b 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -36,6 +36,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { titlebar: None, center: false, focus: false, + show: true, kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index da3c96956e..18211e4276 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -74,6 +74,7 @@ fn create_copilot_auth_window( titlebar: None, center: true, focus: true, + show: true, kind: WindowKind::Normal, is_movable: true, screen: None, diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 8fbcda02a3..9b4fd7ca51 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -173,6 +173,7 @@ pub struct WindowOptions<'a> { pub titlebar: Option>, pub center: bool, pub focus: bool, + pub show: bool, pub kind: WindowKind, pub is_movable: bool, pub screen: Option>, @@ -376,6 +377,7 @@ impl<'a> Default for WindowOptions<'a> { }), center: false, focus: true, + show: true, kind: WindowKind::Normal, is_movable: true, screen: None, diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index bcff08d005..2d1de6df75 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -614,7 +614,7 @@ impl Window { } if options.focus { native_window.makeKeyAndOrderFront_(nil); - } else { + } else if options.show { native_window.orderFront_(nil); } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5dc7c066f7..dfb8560843 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -805,6 +805,8 @@ impl Workspace { .await .log_err(); + cx.update_window(workspace.window_id(), |cx| cx.activate_window()); + let workspace = workspace.downgrade(); notify_if_database_failed(&workspace, &mut cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 2608ea1ec4..77a3bf350f 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -398,7 +398,8 @@ pub fn build_window_options( traffic_light_position: Some(vec2f(8., 8.)), }), center: false, - focus: true, + focus: false, + show: false, kind: WindowKind::Normal, is_movable: true, bounds, From 679278821663074836084bb80cdb8a1ba469a197 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 11:08:58 -0700 Subject: [PATCH 053/131] Remove unnescessary double lookup --- crates/project/src/worktree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 403d893425..61f12f7de9 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -125,7 +125,7 @@ impl Snapshot { let mut max_len = 0; let mut current_candidate = None; for (work_directory, repo) in (&self.repository_entries).iter() { - if repo.contains(self, path) { + if path.starts_with(&work_directory.0) { if work_directory.0.as_os_str().len() >= max_len { current_candidate = Some(repo); max_len = work_directory.0.as_os_str().len(); From 729a93db6b2ad9372a7249e690be3ebdc49436bd Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 May 2023 11:29:02 -0700 Subject: [PATCH 054/131] Optimize retrieving repos for entries when rendering the project panel Co-authored-by: Mikayla --- crates/project/src/worktree.rs | 63 +++++++++++++++++++++-- crates/project_panel/src/project_panel.rs | 11 ++-- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 403d893425..25470927a8 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1643,8 +1643,38 @@ impl Snapshot { self.traverse_from_offset(true, include_ignored, 0) } - pub fn repositories(&self) -> impl Iterator { - self.repository_entries.values() + pub fn repositories(&self) -> impl Iterator, &RepositoryEntry)> { + self.repository_entries + .iter() + .map(|(path, entry)| (&path.0, entry)) + } + + /// Given an ordered iterator of entries, returns an iterator of those entries, + /// along with their containing git repository. + pub fn entries_with_repos<'a>( + &'a self, + entries: impl 'a + Iterator, + ) -> impl 'a + Iterator)> { + let mut containing_repos = Vec::<(&Arc, &RepositoryEntry)>::new(); + let mut repositories = self.repositories().peekable(); + entries.map(move |entry| { + while let Some((repo_path, _)) = containing_repos.last() { + if !entry.path.starts_with(repo_path) { + containing_repos.pop(); + } else { + break; + } + } + while let Some((repo_path, _)) = repositories.peek() { + if entry.path.starts_with(repo_path) { + containing_repos.push(repositories.next().unwrap()); + } else { + break; + } + } + let repo = containing_repos.last().map(|(_, repo)| *repo); + (entry, repo) + }) } pub fn paths(&self) -> impl Iterator> { @@ -4008,6 +4038,7 @@ mod tests { #[gpui::test] async fn test_git_repository_for_path(cx: &mut TestAppContext) { let root = temp_tree(json!({ + "c.txt": "", "dir1": { ".git": {}, "deps": { @@ -4022,7 +4053,6 @@ mod tests { "b.txt": "" } }, - "c.txt": "", })); let http_client = FakeHttpClient::with_404_response(); @@ -4062,6 +4092,33 @@ mod tests { .map(|directory| directory.as_ref().to_owned()), Some(Path::new("dir1/deps/dep1").to_owned()) ); + + let entries = tree.files(false, 0); + + let paths_with_repos = tree + .entries_with_repos(entries) + .map(|(entry, repo)| { + ( + entry.path.as_ref(), + repo.and_then(|repo| { + repo.work_directory(&tree) + .map(|work_directory| work_directory.0.to_path_buf()) + }), + ) + }) + .collect::>(); + + assert_eq!( + paths_with_repos, + &[ + (Path::new("c.txt"), None), + ( + Path::new("dir1/deps/dep1/src/a.txt"), + Some(Path::new("dir1/deps/dep1").into()) + ), + (Path::new("dir1/src/b.txt"), Some(Path::new("dir1").into())), + ] + ); }); let repo_update_events = Arc::new(Mutex::new(vec![])); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 683ce8ad06..a599bac110 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1010,14 +1010,11 @@ impl ProjectPanel { .unwrap_or(&[]); let entry_range = range.start.saturating_sub(ix)..end_ix - ix; - for entry in &visible_worktree_entries[entry_range] { - let path = &entry.path; + for (entry, repo) in + snapshot.entries_with_repos(visible_worktree_entries[entry_range].iter()) + { let status = (entry.path.parent().is_some() && !entry.is_ignored) - .then(|| { - snapshot - .repo_for(path) - .and_then(|entry| entry.status_for_path(&snapshot, path)) - }) + .then(|| repo.and_then(|repo| repo.status_for_path(&snapshot, &entry.path))) .flatten(); let mut details = EntryDetails { From 9f157bdb6712734e86285347c389906f8ddd60d8 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 11:30:10 -0700 Subject: [PATCH 055/131] Remove unescessary methods --- crates/project/src/worktree.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 61f12f7de9..f178d7162a 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -169,10 +169,6 @@ impl RepositoryEntry { .map(|entry| RepositoryWorkDirectory(entry.path.clone())) } - pub(crate) fn contains(&self, snapshot: &Snapshot, path: &Path) -> bool { - self.work_directory.contains(snapshot, path) - } - pub fn status_for_file(&self, snapshot: &Snapshot, path: &Path) -> Option { self.work_directory .relativize(snapshot, path) @@ -305,14 +301,6 @@ impl AsRef for RepositoryWorkDirectory { pub struct WorkDirectoryEntry(ProjectEntryId); impl WorkDirectoryEntry { - // Note that these paths should be relative to the worktree root. - pub(crate) fn contains(&self, snapshot: &Snapshot, path: &Path) -> bool { - snapshot - .entry_for_id(self.0) - .map(|entry| path.starts_with(&entry.path)) - .unwrap_or(false) - } - pub(crate) fn relativize(&self, worktree: &Snapshot, path: &Path) -> Option { worktree.entry_for_id(self.0).and_then(|entry| { path.strip_prefix(&entry.path) From 2c8fffc4f8e1667ecc6eb1779c1a03f7de07393c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 19 May 2023 21:47:00 +0300 Subject: [PATCH 056/131] Use better name for the method that closes deleted buffers co-authored-by: Max --- crates/workspace/src/pane.rs | 2 +- crates/workspace/src/workspace.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index c54f8d393b..39f1a9fd9e 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1305,7 +1305,7 @@ impl Pane { &self.toolbar } - pub fn delete_item( + pub fn handle_deleted_project_item( &mut self, entry_id: ProjectEntryId, cx: &mut ViewContext, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 11703aba6e..8a5ce7a105 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -539,7 +539,9 @@ impl Workspace { project::Event::DeletedEntry(entry_id) => { for pane in this.panes.iter() { - pane.update(cx, |pane, cx| pane.delete_item(*entry_id, cx)); + pane.update(cx, |pane, cx| { + pane.handle_deleted_project_item(*entry_id, cx) + }); } } From 065f71d671829caafb1cd15a6391d2470de511f1 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 19 May 2023 22:06:47 +0300 Subject: [PATCH 057/131] Do not refocus project search query on ESC press --- crates/search/src/project_search.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index d96d77eb00..915957401d 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -48,7 +48,7 @@ pub fn init(cx: &mut AppContext) { cx.add_action(ProjectSearchBar::search_in_new); cx.add_action(ProjectSearchBar::select_next_match); cx.add_action(ProjectSearchBar::select_prev_match); - cx.add_action(ProjectSearchBar::toggle_focus); + cx.add_action(ProjectSearchBar::move_focus_to_results); cx.capture_action(ProjectSearchBar::tab); cx.capture_action(ProjectSearchBar::tab_previous); add_toggle_option_action::(SearchOption::CaseSensitive, cx); @@ -794,18 +794,16 @@ impl ProjectSearchBar { } } - fn toggle_focus(pane: &mut Pane, _: &ToggleFocus, cx: &mut ViewContext) { + fn move_focus_to_results(pane: &mut Pane, _: &ToggleFocus, cx: &mut ViewContext) { if let Some(search_view) = pane .active_item() .and_then(|item| item.downcast::()) { search_view.update(cx, |search_view, cx| { - if search_view.query_editor.is_focused(cx) { - if !search_view.model.read(cx).match_ranges.is_empty() { - search_view.focus_results_editor(cx); - } - } else { - search_view.focus_query_editor(cx); + if search_view.query_editor.is_focused(cx) + && !search_view.model.read(cx).match_ranges.is_empty() + { + search_view.focus_results_editor(cx); } }); } else { From 63593337493270cc82d2cff815dfe3d89ae4daf1 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 May 2023 13:49:24 -0700 Subject: [PATCH 058/131] Don't store next_entry_id on worktree's local snapshot --- crates/project/src/worktree.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 26b52b848e..7431199d28 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -331,7 +331,6 @@ pub struct LocalSnapshot { // work_directory_id git_repositories: TreeMap, removed_entry_ids: HashMap, - next_entry_id: Arc, snapshot: Snapshot, } @@ -418,7 +417,6 @@ impl Worktree { ignores_by_parent_abs_path: Default::default(), removed_entry_ids: Default::default(), git_repositories: Default::default(), - next_entry_id, snapshot: Snapshot { id: WorktreeId::from_usize(cx.model_id()), abs_path: abs_path.clone(), @@ -437,7 +435,7 @@ impl Worktree { Entry::new( Arc::from(Path::new("")), &metadata, - &snapshot.next_entry_id, + &next_entry_id, snapshot.root_char_bag, ), fs.as_ref(), @@ -481,6 +479,7 @@ impl Worktree { let events = fs.watch(&abs_path, Duration::from_millis(100)).await; BackgroundScanner::new( snapshot, + next_entry_id, fs, scan_states_tx, background, @@ -1229,7 +1228,6 @@ impl LocalWorktree { let mut prev_snapshot = LocalSnapshot { ignores_by_parent_abs_path: Default::default(), removed_entry_ids: Default::default(), - next_entry_id: Default::default(), git_repositories: Default::default(), snapshot: Snapshot { id: WorktreeId(worktree_id as usize), @@ -2571,6 +2569,7 @@ struct BackgroundScanner { executor: Arc, refresh_requests_rx: channel::Receiver<(Vec, barrier::Sender)>, prev_state: Mutex, + next_entry_id: Arc, finished_initial_scan: bool, } @@ -2582,6 +2581,7 @@ struct BackgroundScannerState { impl BackgroundScanner { fn new( snapshot: LocalSnapshot, + next_entry_id: Arc, fs: Arc, status_updates_tx: UnboundedSender, executor: Arc, @@ -2592,6 +2592,7 @@ impl BackgroundScanner { status_updates_tx, executor, refresh_requests_rx, + next_entry_id, prev_state: Mutex::new(BackgroundScannerState { snapshot: snapshot.snapshot.clone(), event_paths: Default::default(), @@ -2864,7 +2865,7 @@ impl BackgroundScanner { ( snapshot.abs_path().clone(), snapshot.root_char_bag, - snapshot.next_entry_id.clone(), + self.next_entry_id.clone(), ) }; let mut child_paths = self.fs.read_dir(&job.abs_path).await?; @@ -3036,7 +3037,7 @@ impl BackgroundScanner { let mut fs_entry = Entry::new( path.clone(), &metadata, - snapshot.next_entry_id.as_ref(), + self.next_entry_id.as_ref(), snapshot.root_char_bag, ); fs_entry.is_ignored = ignore_stack.is_all(); From 32c71579063e7db4adeb4efa374c47cbb143a668 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 May 2023 14:28:26 -0700 Subject: [PATCH 059/131] :art: Make worktree repositories more consistent --- crates/project/src/worktree.rs | 59 ++++++++++++----------- crates/project_panel/src/project_panel.rs | 2 +- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 7431199d28..24e8a00482 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -120,25 +120,6 @@ pub struct Snapshot { completed_scan_id: usize, } -impl Snapshot { - pub fn repo_for(&self, path: &Path) -> Option { - let mut max_len = 0; - let mut current_candidate = None; - for (work_directory, repo) in (&self.repository_entries).iter() { - if path.starts_with(&work_directory.0) { - if work_directory.0.as_os_str().len() >= max_len { - current_candidate = Some(repo); - max_len = work_directory.0.as_os_str().len(); - } else { - break; - } - } - } - - current_candidate.map(|entry| entry.to_owned()) - } -} - #[derive(Clone, Debug, PartialEq, Eq)] pub struct RepositoryEntry { pub(crate) work_directory: WorkDirectoryEntry, @@ -900,7 +881,7 @@ impl LocalWorktree { let mut index_task = None; - if let Some(repo) = snapshot.repo_for(&path) { + if let Some(repo) = snapshot.repository_for_path(&path) { let repo_path = repo.work_directory.relativize(self, &path).unwrap(); if let Some(repo) = self.git_repositories.get(&*repo.work_directory) { let repo = repo.repo_ptr.to_owned(); @@ -1635,9 +1616,27 @@ impl Snapshot { .map(|(path, entry)| (&path.0, entry)) } + /// Get the repository whose work directory contains the given path. + pub fn repository_for_path(&self, path: &Path) -> Option { + let mut max_len = 0; + let mut current_candidate = None; + for (work_directory, repo) in (&self.repository_entries).iter() { + if path.starts_with(&work_directory.0) { + if work_directory.0.as_os_str().len() >= max_len { + current_candidate = Some(repo); + max_len = work_directory.0.as_os_str().len(); + } else { + break; + } + } + } + + current_candidate.map(|entry| entry.to_owned()) + } + /// Given an ordered iterator of entries, returns an iterator of those entries, /// along with their containing git repository. - pub fn entries_with_repos<'a>( + pub fn entries_with_repositories<'a>( &'a self, entries: impl 'a + Iterator, ) -> impl 'a + Iterator)> { @@ -3077,7 +3076,7 @@ impl BackgroundScanner { .any(|component| component.as_os_str() == *DOT_GIT) { let scan_id = snapshot.scan_id; - let repo = snapshot.repo_for(&path)?; + let repo = snapshot.repository_for_path(&path)?; let repo_path = repo.work_directory.relativize(&snapshot, &path)?; @@ -3153,7 +3152,7 @@ impl BackgroundScanner { return None; } - let repo = snapshot.repo_for(&path)?; + let repo = snapshot.repository_for_path(&path)?; let work_dir = repo.work_directory(snapshot)?; let work_dir_id = repo.work_directory.clone(); @@ -4064,9 +4063,9 @@ mod tests { tree.read_with(cx, |tree, _cx| { let tree = tree.as_local().unwrap(); - assert!(tree.repo_for("c.txt".as_ref()).is_none()); + assert!(tree.repository_for_path("c.txt".as_ref()).is_none()); - let entry = tree.repo_for("dir1/src/b.txt".as_ref()).unwrap(); + let entry = tree.repository_for_path("dir1/src/b.txt".as_ref()).unwrap(); assert_eq!( entry .work_directory(tree) @@ -4074,7 +4073,9 @@ mod tests { Some(Path::new("dir1").to_owned()) ); - let entry = tree.repo_for("dir1/deps/dep1/src/a.txt".as_ref()).unwrap(); + let entry = tree + .repository_for_path("dir1/deps/dep1/src/a.txt".as_ref()) + .unwrap(); assert_eq!( entry .work_directory(tree) @@ -4085,7 +4086,7 @@ mod tests { let entries = tree.files(false, 0); let paths_with_repos = tree - .entries_with_repos(entries) + .entries_with_repositories(entries) .map(|(entry, repo)| { ( entry.path.as_ref(), @@ -4138,7 +4139,9 @@ mod tests { tree.read_with(cx, |tree, _cx| { let tree = tree.as_local().unwrap(); - assert!(tree.repo_for("dir1/src/b.txt".as_ref()).is_none()); + assert!(tree + .repository_for_path("dir1/src/b.txt".as_ref()) + .is_none()); }); } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 93c8f41cef..ce0dd9e222 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1011,7 +1011,7 @@ impl ProjectPanel { let entry_range = range.start.saturating_sub(ix)..end_ix - ix; for (entry, repo) in - snapshot.entries_with_repos(visible_worktree_entries[entry_range].iter()) + snapshot.entries_with_repositories(visible_worktree_entries[entry_range].iter()) { let status = (entry.path.parent().is_some() && !entry.is_ignored) .then(|| repo.and_then(|repo| repo.status_for_path(&snapshot, &entry.path))) From c193b0b8fc03abf8ff31982fe81c7cec73e25f51 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 15:10:29 -0700 Subject: [PATCH 060/131] Add guards to other pane index removals --- crates/workspace/src/pane.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 39f1a9fd9e..a196133f3a 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -711,10 +711,7 @@ impl Pane { let pane_handle = workspace.active_pane().clone(); let pane = pane_handle.read(cx); - if pane.items.is_empty() { - return None; - } - let active_item_id = pane.items[pane.active_item_index].id(); + let active_item_id = pane.items.get(pane.active_item_index)?.id(); let task = Self::close_item_by_id(workspace, pane_handle, active_item_id, cx); @@ -742,7 +739,8 @@ impl Pane { ) -> 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 active_item_id = pane.items.get(pane.active_item_index)?.id(); let task = Self::close_items(workspace, pane_handle, cx, move |item_id| { item_id != active_item_id @@ -785,7 +783,7 @@ impl Pane { ) -> 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 active_item_id = pane.items.get(pane.active_item_index)?.id(); let task = Self::close_items_to_the_left_by_id(workspace, pane_handle, active_item_id, cx); @@ -825,7 +823,7 @@ impl Pane { ) -> 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 active_item_id = pane.items.get(pane.active_item_index)?.id(); let task = Self::close_items_to_the_right_by_id(workspace, pane_handle, active_item_id, cx); From 7ae642b9b8746252b2b1cbb42d75c096fea295df Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 May 2023 14:23:35 -0700 Subject: [PATCH 061/131] Avoid storing removed_entry_ids on the LocalSnapshot --- crates/project/src/worktree.rs | 247 ++++++++++++++++++--------------- 1 file changed, 138 insertions(+), 109 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 24e8a00482..b7cb82f628 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -227,7 +227,7 @@ impl RepositoryEntry { work_directory_id: self.work_directory_id().to_proto(), branch: self.branch.as_ref().map(|str| str.to_string()), removed_repo_paths: removed_statuses, - updated_statuses: updated_statuses, + updated_statuses, } } } @@ -307,12 +307,22 @@ impl<'a> From for WorkDirectoryEntry { #[derive(Debug, Clone)] pub struct LocalSnapshot { - ignores_by_parent_abs_path: HashMap, (Arc, bool)>, // (gitignore, needs_update) - // The ProjectEntryId corresponds to the entry for the .git dir - // work_directory_id - git_repositories: TreeMap, - removed_entry_ids: HashMap, snapshot: Snapshot, + /// All of the gitignore files in the worktree, indexed by their relative path. + /// The boolean indicates whether the gitignore needs to be updated. + ignores_by_parent_abs_path: HashMap, (Arc, bool)>, + /// All of the git repositories in the worktree, indexed by the project entry + /// id of their parent directory. + git_repositories: TreeMap, +} + +pub struct LocalMutableSnapshot { + snapshot: LocalSnapshot, + /// The ids of all of the entries that were removed from the snapshot + /// as part of the current update. These entry ids may be re-used + /// if the same inode is discovered at a new path, or if the given + /// path is re-created after being deleted. + removed_entry_ids: HashMap, } #[derive(Debug, Clone)] @@ -346,6 +356,20 @@ impl DerefMut for LocalSnapshot { } } +impl Deref for LocalMutableSnapshot { + type Target = LocalSnapshot; + + fn deref(&self) -> &Self::Target { + &self.snapshot + } +} + +impl DerefMut for LocalMutableSnapshot { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.snapshot + } +} + enum ScanState { Started, Updated { @@ -396,7 +420,6 @@ impl Worktree { let mut snapshot = LocalSnapshot { ignores_by_parent_abs_path: Default::default(), - removed_entry_ids: Default::default(), git_repositories: Default::default(), snapshot: Snapshot { id: WorktreeId::from_usize(cx.model_id()), @@ -1208,7 +1231,6 @@ impl LocalWorktree { let mut share_tx = Some(share_tx); let mut prev_snapshot = LocalSnapshot { ignores_by_parent_abs_path: Default::default(), - removed_entry_ids: Default::default(), git_repositories: Default::default(), snapshot: Snapshot { id: WorktreeId(worktree_id as usize), @@ -1910,8 +1932,6 @@ impl LocalSnapshot { } } - self.reuse_entry_id(&mut entry); - if entry.kind == EntryKind::PendingDir { if let Some(existing_entry) = self.entries_by_path.get(&PathKey(entry.path.clone()), &()) @@ -1940,60 +1960,6 @@ impl LocalSnapshot { entry } - fn populate_dir( - &mut self, - parent_path: Arc, - entries: impl IntoIterator, - ignore: Option>, - fs: &dyn Fs, - ) { - let mut parent_entry = if let Some(parent_entry) = - self.entries_by_path.get(&PathKey(parent_path.clone()), &()) - { - parent_entry.clone() - } else { - log::warn!( - "populating a directory {:?} that has been removed", - parent_path - ); - return; - }; - - match parent_entry.kind { - EntryKind::PendingDir => { - parent_entry.kind = EntryKind::Dir; - } - EntryKind::Dir => {} - _ => return, - } - - if let Some(ignore) = ignore { - self.ignores_by_parent_abs_path - .insert(self.abs_path.join(&parent_path).into(), (ignore, false)); - } - - if parent_path.file_name() == Some(&DOT_GIT) { - self.build_repo(parent_path, fs); - } - - let mut entries_by_path_edits = vec![Edit::Insert(parent_entry)]; - let mut entries_by_id_edits = Vec::new(); - - for mut entry in entries { - self.reuse_entry_id(&mut entry); - entries_by_id_edits.push(Edit::Insert(PathEntry { - id: entry.id, - path: entry.path.clone(), - is_ignored: entry.is_ignored, - scan_id: self.scan_id, - })); - entries_by_path_edits.push(Edit::Insert(entry)); - } - - self.entries_by_path.edit(entries_by_path_edits, &()); - self.entries_by_id.edit(entries_by_id_edits, &()); - } - fn build_repo(&mut self, parent_path: Arc, fs: &dyn Fs) -> Option<()> { let abs_path = self.abs_path.join(&parent_path); let work_dir: Arc = parent_path.parent().unwrap().into(); @@ -2041,46 +2007,6 @@ impl LocalSnapshot { Some(()) } - fn reuse_entry_id(&mut self, entry: &mut Entry) { - if let Some(removed_entry_id) = self.removed_entry_ids.remove(&entry.inode) { - entry.id = removed_entry_id; - } else if let Some(existing_entry) = self.entry_for_path(&entry.path) { - entry.id = existing_entry.id; - } - } - - fn remove_path(&mut self, path: &Path) { - let mut new_entries; - let removed_entries; - { - let mut cursor = self.entries_by_path.cursor::(); - new_entries = cursor.slice(&TraversalTarget::Path(path), Bias::Left, &()); - removed_entries = cursor.slice(&TraversalTarget::PathSuccessor(path), Bias::Left, &()); - new_entries.push_tree(cursor.suffix(&()), &()); - } - self.entries_by_path = new_entries; - - let mut entries_by_id_edits = Vec::new(); - for entry in removed_entries.cursor::<()>() { - let removed_entry_id = self - .removed_entry_ids - .entry(entry.inode) - .or_insert(entry.id); - *removed_entry_id = cmp::max(*removed_entry_id, entry.id); - entries_by_id_edits.push(Edit::Remove(entry.id)); - } - self.entries_by_id.edit(entries_by_id_edits, &()); - - if path.file_name() == Some(&GITIGNORE) { - let abs_parent_path = self.abs_path.join(path.parent().unwrap()); - if let Some((_, needs_update)) = self - .ignores_by_parent_abs_path - .get_mut(abs_parent_path.as_path()) - { - *needs_update = true; - } - } - } fn ancestor_inodes_for_path(&self, path: &Path) -> TreeSet { let mut inodes = TreeSet::default(); @@ -2120,6 +2046,109 @@ impl LocalSnapshot { } } +impl LocalMutableSnapshot { + fn reuse_entry_id(&mut self, entry: &mut Entry) { + if let Some(removed_entry_id) = self.removed_entry_ids.remove(&entry.inode) { + entry.id = removed_entry_id; + } else if let Some(existing_entry) = self.entry_for_path(&entry.path) { + entry.id = existing_entry.id; + } + } + + fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs) -> Entry { + self.reuse_entry_id(&mut entry); + self.snapshot.insert_entry(entry, fs) + } + + fn populate_dir( + &mut self, + parent_path: Arc, + entries: impl IntoIterator, + ignore: Option>, + fs: &dyn Fs, + ) { + let mut parent_entry = if let Some(parent_entry) = + self.entries_by_path.get(&PathKey(parent_path.clone()), &()) + { + parent_entry.clone() + } else { + log::warn!( + "populating a directory {:?} that has been removed", + parent_path + ); + return; + }; + + match parent_entry.kind { + EntryKind::PendingDir => { + parent_entry.kind = EntryKind::Dir; + } + EntryKind::Dir => {} + _ => return, + } + + if let Some(ignore) = ignore { + let abs_parent_path = self.abs_path.join(&parent_path).into(); + self.ignores_by_parent_abs_path + .insert(abs_parent_path, (ignore, false)); + } + + if parent_path.file_name() == Some(&DOT_GIT) { + self.build_repo(parent_path, fs); + } + + let mut entries_by_path_edits = vec![Edit::Insert(parent_entry)]; + let mut entries_by_id_edits = Vec::new(); + + for mut entry in entries { + self.reuse_entry_id(&mut entry); + entries_by_id_edits.push(Edit::Insert(PathEntry { + id: entry.id, + path: entry.path.clone(), + is_ignored: entry.is_ignored, + scan_id: self.scan_id, + })); + entries_by_path_edits.push(Edit::Insert(entry)); + } + + self.entries_by_path.edit(entries_by_path_edits, &()); + self.entries_by_id.edit(entries_by_id_edits, &()); + } + + fn remove_path(&mut self, path: &Path) { + let mut new_entries; + let removed_entries; + { + let mut cursor = self.entries_by_path.cursor::(); + new_entries = cursor.slice(&TraversalTarget::Path(path), Bias::Left, &()); + removed_entries = cursor.slice(&TraversalTarget::PathSuccessor(path), Bias::Left, &()); + new_entries.push_tree(cursor.suffix(&()), &()); + } + self.entries_by_path = new_entries; + + let mut entries_by_id_edits = Vec::new(); + for entry in removed_entries.cursor::<()>() { + let removed_entry_id = self + .removed_entry_ids + .entry(entry.inode) + .or_insert(entry.id); + *removed_entry_id = cmp::max(*removed_entry_id, entry.id); + entries_by_id_edits.push(Edit::Remove(entry.id)); + } + self.entries_by_id.edit(entries_by_id_edits, &()); + + if path.file_name() == Some(&GITIGNORE) { + let abs_parent_path = self.abs_path.join(path.parent().unwrap()); + if let Some((_, needs_update)) = self + .ignores_by_parent_abs_path + .get_mut(abs_parent_path.as_path()) + { + *needs_update = true; + } + } + } +} + async fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result { let contents = fs.load(abs_path).await?; let parent = abs_path.parent().unwrap_or_else(|| Path::new("/")); @@ -2562,7 +2591,7 @@ impl<'a> sum_tree::Dimension<'a, EntrySummary> for PathKey { } struct BackgroundScanner { - snapshot: Mutex, + snapshot: Mutex, fs: Arc, status_updates_tx: UnboundedSender, executor: Arc, @@ -2596,7 +2625,10 @@ impl BackgroundScanner { snapshot: snapshot.snapshot.clone(), event_paths: Default::default(), }), - snapshot: Mutex::new(snapshot), + snapshot: Mutex::new(LocalMutableSnapshot { + snapshot, + removed_entry_ids: Default::default(), + }), finished_initial_scan: false, } } @@ -2750,10 +2782,7 @@ impl BackgroundScanner { .is_some() }); snapshot.snapshot.repository_entries = git_repository_entries; - - snapshot.removed_entry_ids.clear(); snapshot.completed_scan_id = snapshot.scan_id; - drop(snapshot); self.send_status_update(false, None); From 2a41a32aac5948c5a16bd2640bec7acce3f88ccc Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 12:11:38 -0700 Subject: [PATCH 062/131] Calculate y offsets correctly --- crates/editor/src/element.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 0a17fc8baf..35279913c5 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -42,6 +42,7 @@ use language::{ }; use project::ProjectPath; use smallvec::SmallVec; +use text::Point; use std::{ borrow::Cow, cmp::{self, Ordering}, @@ -1058,11 +1059,13 @@ impl EditorElement { .buffer_snapshot .git_diff_hunks_in_range(0..(max_row.floor() as u32), false) { - let start_y = y_for_row(hunk.buffer_range.start as f32); + let start_display = Point::new(hunk.buffer_range.start, 0).to_display_point(&layout.position_map.snapshot.display_snapshot); + let end_display = Point::new(hunk.buffer_range.end, 0).to_display_point(&layout.position_map.snapshot.display_snapshot); + let start_y = y_for_row(start_display.row() as f32); let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end { - y_for_row((hunk.buffer_range.end + 1) as f32) + y_for_row((end_display.row() + 1) as f32) } else { - y_for_row((hunk.buffer_range.end) as f32) + y_for_row((end_display.row()) as f32) }; if end_y - start_y < 1. { From 560160b1008646e39ad40779dfcbf4017057d2c6 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 15:06:38 -0700 Subject: [PATCH 063/131] Batch anchor conversions in git hunk iterator --- crates/git/src/diff.rs | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/crates/git/src/diff.rs b/crates/git/src/diff.rs index b28af26f16..a2349649c5 100644 --- a/crates/git/src/diff.rs +++ b/crates/git/src/diff.rs @@ -1,6 +1,6 @@ -use std::ops::Range; +use std::{cell::RefCell, iter, ops::Range}; use sum_tree::SumTree; -use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point}; +use text::{Anchor, BufferSnapshot, Point}; pub use git2 as libgit; use libgit::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch}; @@ -94,25 +94,37 @@ impl BufferDiff { !before_start && !after_end }); - std::iter::from_fn(move || { + use std::rc::Rc; + let cell = Rc::new(RefCell::new(None)); + + let anchor_iter = std::iter::from_fn(move || { if reversed { cursor.prev(buffer); } else { cursor.next(buffer); } - let hunk = cursor.item()?; + cursor.item() + }) + .flat_map({ + let cell = cell.clone(); + move |hunk| { + *cell.borrow_mut() = Some(hunk.diff_base_byte_range.clone()); + iter::once(&hunk.buffer_range.start).chain(iter::once(&hunk.buffer_range.end)) + } + }); - let range = hunk.buffer_range.to_point(buffer); - let end_row = if range.end.column > 0 { - range.end.row + 1 - } else { - range.end.row - }; + let mut summaries = buffer.summaries_for_anchors::(anchor_iter); + iter::from_fn(move || { + let start = summaries.next()?; + let end = summaries.next()?; + let base = (cell.borrow_mut()).clone()?; + + let end_row = if end.column > 0 { end.row + 1 } else { end.row }; Some(DiffHunk { - buffer_range: range.start.row..end_row, - diff_base_byte_range: hunk.diff_base_byte_range.clone(), + buffer_range: start.row..end_row, + diff_base_byte_range: base, }) }) } @@ -279,6 +291,8 @@ pub fn assert_hunks( #[cfg(test)] mod tests { + use std::assert_eq; + use super::*; use text::Buffer; use unindent::Unindent as _; From 623a177fe6ab363a0dbcc2cae509a3a2fc281331 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 15:30:10 -0700 Subject: [PATCH 064/131] Fix bug where git diff hunks would not extend through a soft wrap --- crates/editor/src/element.rs | 8 ++++---- crates/editor/src/git.rs | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 35279913c5..8a74a08c86 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -652,7 +652,7 @@ impl EditorElement { //TODO: This rendering is entirely a horrible hack DiffHunkStatus::Removed => { - let row = *display_row_range.start(); + let row = display_row_range.start; let offset = line_height / 2.; let start_y = row as f32 * line_height - offset - scroll_top; @@ -674,11 +674,11 @@ impl EditorElement { } }; - let start_row = *display_row_range.start(); - let end_row = *display_row_range.end(); + let start_row = display_row_range.start; + let end_row = display_row_range.end; let start_y = start_row as f32 * line_height - scroll_top; - let end_y = end_row as f32 * line_height - scroll_top + line_height; + let end_y = end_row as f32 * line_height - scroll_top; let width = diff_style.width_em * line_height; let highlight_origin = bounds.origin() + vec2f(-width, start_y); diff --git a/crates/editor/src/git.rs b/crates/editor/src/git.rs index 549d74a0b5..5055cc6466 100644 --- a/crates/editor/src/git.rs +++ b/crates/editor/src/git.rs @@ -1,4 +1,5 @@ -use std::ops::RangeInclusive; + +use std::ops::Range; use git::diff::{DiffHunk, DiffHunkStatus}; use language::Point; @@ -15,7 +16,7 @@ pub enum DisplayDiffHunk { }, Unfolded { - display_row_range: RangeInclusive, + display_row_range: Range, status: DiffHunkStatus, }, } @@ -26,7 +27,7 @@ impl DisplayDiffHunk { &DisplayDiffHunk::Folded { display_row } => display_row, DisplayDiffHunk::Unfolded { display_row_range, .. - } => *display_row_range.start(), + } => display_row_range.start, } } @@ -36,7 +37,7 @@ impl DisplayDiffHunk { DisplayDiffHunk::Unfolded { display_row_range, .. - } => display_row_range.clone(), + } => display_row_range.start..=display_row_range.end - 1, }; range.contains(&display_row) @@ -80,13 +81,12 @@ pub fn diff_hunk_to_display(hunk: DiffHunk, snapshot: &DisplaySnapshot) -> let hunk_end_row_inclusive = hunk .buffer_range .end - .saturating_sub(1) .max(hunk.buffer_range.start); let hunk_end_point = Point::new(hunk_end_row_inclusive, 0); let end = hunk_end_point.to_display_point(snapshot).row(); DisplayDiffHunk::Unfolded { - display_row_range: start..=end, + display_row_range: start..end, status: hunk.status(), } } From c795c9b8449077bdcd34996bf424d4c28c9e6b2a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 16:23:21 -0700 Subject: [PATCH 065/131] Rearrange git tests in worktree Add support for renaming work directories --- crates/editor/src/element.rs | 8 +- crates/editor/src/git.rs | 6 +- crates/project/src/worktree.rs | 901 ++++++++++++++++++--------------- 3 files changed, 503 insertions(+), 412 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 8a74a08c86..7285db7366 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -42,7 +42,6 @@ use language::{ }; use project::ProjectPath; use smallvec::SmallVec; -use text::Point; use std::{ borrow::Cow, cmp::{self, Ordering}, @@ -51,6 +50,7 @@ use std::{ ops::Range, sync::Arc, }; +use text::Point; use workspace::{item::Item, GitGutterSetting, WorkspaceSettings}; enum FoldMarkers {} @@ -1059,8 +1059,10 @@ impl EditorElement { .buffer_snapshot .git_diff_hunks_in_range(0..(max_row.floor() as u32), false) { - let start_display = Point::new(hunk.buffer_range.start, 0).to_display_point(&layout.position_map.snapshot.display_snapshot); - let end_display = Point::new(hunk.buffer_range.end, 0).to_display_point(&layout.position_map.snapshot.display_snapshot); + let start_display = Point::new(hunk.buffer_range.start, 0) + .to_display_point(&layout.position_map.snapshot.display_snapshot); + let end_display = Point::new(hunk.buffer_range.end, 0) + .to_display_point(&layout.position_map.snapshot.display_snapshot); let start_y = y_for_row(start_display.row() as f32); let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end { y_for_row((end_display.row() + 1) as f32) diff --git a/crates/editor/src/git.rs b/crates/editor/src/git.rs index 5055cc6466..3452138126 100644 --- a/crates/editor/src/git.rs +++ b/crates/editor/src/git.rs @@ -1,4 +1,3 @@ - use std::ops::Range; use git::diff::{DiffHunk, DiffHunkStatus}; @@ -78,10 +77,7 @@ pub fn diff_hunk_to_display(hunk: DiffHunk, snapshot: &DisplaySnapshot) -> } else { let start = hunk_start_point.to_display_point(snapshot).row(); - let hunk_end_row_inclusive = hunk - .buffer_range - .end - .max(hunk.buffer_range.start); + let hunk_end_row_inclusive = hunk.buffer_range.end.max(hunk.buffer_range.start); let hunk_end_point = Point::new(hunk_end_row_inclusive, 0); let end = hunk_end_point.to_display_point(snapshot).row(); diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index b7cb82f628..b4f188a2c3 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -150,13 +150,6 @@ impl RepositoryEntry { .map(|entry| RepositoryWorkDirectory(entry.path.clone())) } - pub fn status_for_file(&self, snapshot: &Snapshot, path: &Path) -> Option { - self.work_directory - .relativize(snapshot, path) - .and_then(|repo_path| self.statuses.get(&repo_path)) - .cloned() - } - pub fn status_for_path(&self, snapshot: &Snapshot, path: &Path) -> Option { self.work_directory .relativize(snapshot, path) @@ -182,6 +175,14 @@ impl RepositoryEntry { }) } + #[cfg(any(test, feature = "test-support"))] + pub fn status_for_file(&self, snapshot: &Snapshot, path: &Path) -> Option { + self.work_directory + .relativize(snapshot, path) + .and_then(|repo_path| (&self.statuses).get(&repo_path)) + .cloned() + } + pub fn build_update(&self, other: &Self) -> proto::RepositoryEntry { let mut updated_statuses: Vec = Vec::new(); let mut removed_statuses: Vec = Vec::new(); @@ -1638,6 +1639,11 @@ impl Snapshot { .map(|(path, entry)| (&path.0, entry)) } + /// Get the repository whose work directory contains the given path. + pub fn repository_for_work_directory(&self, path: &Path) -> Option { + self.repository_entries.get(&RepositoryWorkDirectory(path.into())).cloned() + } + /// Get the repository whose work directory contains the given path. pub fn repository_for_path(&self, path: &Path) -> Option { let mut max_len = 0; @@ -1653,7 +1659,7 @@ impl Snapshot { } } - current_candidate.map(|entry| entry.to_owned()) + current_candidate.cloned() } /// Given an ordered iterator of entries, returns an iterator of those entries, @@ -3105,6 +3111,17 @@ impl BackgroundScanner { .any(|component| component.as_os_str() == *DOT_GIT) { let scan_id = snapshot.scan_id; + + if let Some(repository) = snapshot.repository_for_work_directory(path) { + let entry = repository.work_directory.0; + snapshot.git_repositories.remove(&entry); + snapshot + .snapshot + .repository_entries + .remove(&RepositoryWorkDirectory(path.into())); + return Some(()); + } + let repo = snapshot.repository_for_path(&path)?; let repo_path = repo.work_directory.relativize(&snapshot, &path)?; @@ -3975,6 +3992,8 @@ mod tests { #[gpui::test] async fn test_rescan_with_gitignore(cx: &mut TestAppContext) { + // .gitignores are handled explicitly by Zed and do not use the git + // machinery that the git_tests module checks let parent_dir = temp_tree(json!({ ".gitignore": "ancestor-ignored-file1\nancestor-ignored-file2\n", "tree": { @@ -4052,402 +4071,6 @@ mod tests { }); } - #[gpui::test] - async fn test_git_repository_for_path(cx: &mut TestAppContext) { - let root = temp_tree(json!({ - "c.txt": "", - "dir1": { - ".git": {}, - "deps": { - "dep1": { - ".git": {}, - "src": { - "a.txt": "" - } - } - }, - "src": { - "b.txt": "" - } - }, - })); - - let http_client = FakeHttpClient::with_404_response(); - let client = cx.read(|cx| Client::new(http_client, cx)); - let tree = Worktree::local( - client, - root.path(), - true, - Arc::new(RealFs), - Default::default(), - &mut cx.to_async(), - ) - .await - .unwrap(); - - cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) - .await; - tree.flush_fs_events(cx).await; - - tree.read_with(cx, |tree, _cx| { - let tree = tree.as_local().unwrap(); - - assert!(tree.repository_for_path("c.txt".as_ref()).is_none()); - - let entry = tree.repository_for_path("dir1/src/b.txt".as_ref()).unwrap(); - assert_eq!( - entry - .work_directory(tree) - .map(|directory| directory.as_ref().to_owned()), - Some(Path::new("dir1").to_owned()) - ); - - let entry = tree - .repository_for_path("dir1/deps/dep1/src/a.txt".as_ref()) - .unwrap(); - assert_eq!( - entry - .work_directory(tree) - .map(|directory| directory.as_ref().to_owned()), - Some(Path::new("dir1/deps/dep1").to_owned()) - ); - - let entries = tree.files(false, 0); - - let paths_with_repos = tree - .entries_with_repositories(entries) - .map(|(entry, repo)| { - ( - entry.path.as_ref(), - repo.and_then(|repo| { - repo.work_directory(&tree) - .map(|work_directory| work_directory.0.to_path_buf()) - }), - ) - }) - .collect::>(); - - assert_eq!( - paths_with_repos, - &[ - (Path::new("c.txt"), None), - ( - Path::new("dir1/deps/dep1/src/a.txt"), - Some(Path::new("dir1/deps/dep1").into()) - ), - (Path::new("dir1/src/b.txt"), Some(Path::new("dir1").into())), - ] - ); - }); - - let repo_update_events = Arc::new(Mutex::new(vec![])); - tree.update(cx, |_, cx| { - let repo_update_events = repo_update_events.clone(); - cx.subscribe(&tree, move |_, _, event, _| { - if let Event::UpdatedGitRepositories(update) = event { - repo_update_events.lock().push(update.clone()); - } - }) - .detach(); - }); - - std::fs::write(root.path().join("dir1/.git/random_new_file"), "hello").unwrap(); - tree.flush_fs_events(cx).await; - - assert_eq!( - repo_update_events.lock()[0] - .keys() - .cloned() - .collect::>>(), - vec![Path::new("dir1").into()] - ); - - std::fs::remove_dir_all(root.path().join("dir1/.git")).unwrap(); - tree.flush_fs_events(cx).await; - - tree.read_with(cx, |tree, _cx| { - let tree = tree.as_local().unwrap(); - - assert!(tree - .repository_for_path("dir1/src/b.txt".as_ref()) - .is_none()); - }); - } - - #[gpui::test] - async fn test_git_status(cx: &mut TestAppContext) { - #[track_caller] - fn git_init(path: &Path) -> git2::Repository { - git2::Repository::init(path).expect("Failed to initialize git repository") - } - - #[track_caller] - fn git_add(path: &Path, repo: &git2::Repository) { - let mut index = repo.index().expect("Failed to get index"); - index.add_path(path).expect("Failed to add a.txt"); - index.write().expect("Failed to write index"); - } - - #[track_caller] - fn git_remove_index(path: &Path, repo: &git2::Repository) { - let mut index = repo.index().expect("Failed to get index"); - index.remove_path(path).expect("Failed to add a.txt"); - index.write().expect("Failed to write index"); - } - - #[track_caller] - fn git_commit(msg: &'static str, repo: &git2::Repository) { - use git2::Signature; - - let signature = Signature::now("test", "test@zed.dev").unwrap(); - let oid = repo.index().unwrap().write_tree().unwrap(); - let tree = repo.find_tree(oid).unwrap(); - if let Some(head) = repo.head().ok() { - let parent_obj = head.peel(git2::ObjectType::Commit).unwrap(); - - let parent_commit = parent_obj.as_commit().unwrap(); - - repo.commit( - Some("HEAD"), - &signature, - &signature, - msg, - &tree, - &[parent_commit], - ) - .expect("Failed to commit with parent"); - } else { - repo.commit(Some("HEAD"), &signature, &signature, msg, &tree, &[]) - .expect("Failed to commit"); - } - } - - #[track_caller] - fn git_stash(repo: &mut git2::Repository) { - use git2::Signature; - - let signature = Signature::now("test", "test@zed.dev").unwrap(); - repo.stash_save(&signature, "N/A", None) - .expect("Failed to stash"); - } - - #[track_caller] - fn git_reset(offset: usize, repo: &git2::Repository) { - let head = repo.head().expect("Couldn't get repo head"); - let object = head.peel(git2::ObjectType::Commit).unwrap(); - let commit = object.as_commit().unwrap(); - let new_head = commit - .parents() - .inspect(|parnet| { - parnet.message(); - }) - .skip(offset) - .next() - .expect("Not enough history"); - repo.reset(&new_head.as_object(), git2::ResetType::Soft, None) - .expect("Could not reset"); - } - - #[allow(dead_code)] - #[track_caller] - fn git_status(repo: &git2::Repository) -> HashMap { - repo.statuses(None) - .unwrap() - .iter() - .map(|status| (status.path().unwrap().to_string(), status.status())) - .collect() - } - - const IGNORE_RULE: &'static str = "**/target"; - - let root = temp_tree(json!({ - "project": { - "a.txt": "a", - "b.txt": "bb", - "c": { - "d": { - "e.txt": "eee" - } - }, - "f.txt": "ffff", - "target": { - "build_file": "???" - }, - ".gitignore": IGNORE_RULE - }, - - })); - - let http_client = FakeHttpClient::with_404_response(); - let client = cx.read(|cx| Client::new(http_client, cx)); - let tree = Worktree::local( - client, - root.path(), - true, - Arc::new(RealFs), - Default::default(), - &mut cx.to_async(), - ) - .await - .unwrap(); - - cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) - .await; - - const A_TXT: &'static str = "a.txt"; - const B_TXT: &'static str = "b.txt"; - const E_TXT: &'static str = "c/d/e.txt"; - const F_TXT: &'static str = "f.txt"; - const DOTGITIGNORE: &'static str = ".gitignore"; - const BUILD_FILE: &'static str = "target/build_file"; - - let work_dir = root.path().join("project"); - let mut repo = git_init(work_dir.as_path()); - repo.add_ignore_rule(IGNORE_RULE).unwrap(); - git_add(Path::new(A_TXT), &repo); - git_add(Path::new(E_TXT), &repo); - git_add(Path::new(DOTGITIGNORE), &repo); - git_commit("Initial commit", &repo); - - std::fs::write(work_dir.join(A_TXT), "aa").unwrap(); - - tree.flush_fs_events(cx).await; - - // Check that the right git state is observed on startup - tree.read_with(cx, |tree, _cx| { - let snapshot = tree.snapshot(); - assert_eq!(snapshot.repository_entries.iter().count(), 1); - let (dir, repo) = snapshot.repository_entries.iter().next().unwrap(); - assert_eq!(dir.0.as_ref(), Path::new("project")); - - assert_eq!(repo.statuses.iter().count(), 3); - assert_eq!( - repo.statuses.get(&Path::new(A_TXT).into()), - Some(&GitFileStatus::Modified) - ); - assert_eq!( - repo.statuses.get(&Path::new(B_TXT).into()), - Some(&GitFileStatus::Added) - ); - assert_eq!( - repo.statuses.get(&Path::new(F_TXT).into()), - Some(&GitFileStatus::Added) - ); - }); - - git_add(Path::new(A_TXT), &repo); - git_add(Path::new(B_TXT), &repo); - git_commit("Committing modified and added", &repo); - tree.flush_fs_events(cx).await; - - // Check that repo only changes are tracked - tree.read_with(cx, |tree, _cx| { - let snapshot = tree.snapshot(); - let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - - assert_eq!(repo.statuses.iter().count(), 1); - assert_eq!( - repo.statuses.get(&Path::new(F_TXT).into()), - Some(&GitFileStatus::Added) - ); - }); - - git_reset(0, &repo); - git_remove_index(Path::new(B_TXT), &repo); - git_stash(&mut repo); - std::fs::write(work_dir.join(E_TXT), "eeee").unwrap(); - std::fs::write(work_dir.join(BUILD_FILE), "this should be ignored").unwrap(); - tree.flush_fs_events(cx).await; - - // Check that more complex repo changes are tracked - tree.read_with(cx, |tree, _cx| { - let snapshot = tree.snapshot(); - let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - - assert_eq!(repo.statuses.iter().count(), 3); - assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); - assert_eq!( - repo.statuses.get(&Path::new(B_TXT).into()), - Some(&GitFileStatus::Added) - ); - assert_eq!( - repo.statuses.get(&Path::new(E_TXT).into()), - Some(&GitFileStatus::Modified) - ); - assert_eq!( - repo.statuses.get(&Path::new(F_TXT).into()), - Some(&GitFileStatus::Added) - ); - }); - - std::fs::remove_file(work_dir.join(B_TXT)).unwrap(); - std::fs::remove_dir_all(work_dir.join("c")).unwrap(); - std::fs::write( - work_dir.join(DOTGITIGNORE), - [IGNORE_RULE, "f.txt"].join("\n"), - ) - .unwrap(); - - git_add(Path::new(DOTGITIGNORE), &repo); - git_commit("Committing modified git ignore", &repo); - - tree.flush_fs_events(cx).await; - - // Check that non-repo behavior is tracked - tree.read_with(cx, |tree, _cx| { - let snapshot = tree.snapshot(); - let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - - assert_eq!(repo.statuses.iter().count(), 0); - }); - - let mut renamed_dir_name = "first_directory/second_directory"; - const RENAMED_FILE: &'static str = "rf.txt"; - - std::fs::create_dir_all(work_dir.join(renamed_dir_name)).unwrap(); - std::fs::write( - work_dir.join(renamed_dir_name).join(RENAMED_FILE), - "new-contents", - ) - .unwrap(); - - tree.flush_fs_events(cx).await; - - tree.read_with(cx, |tree, _cx| { - let snapshot = tree.snapshot(); - let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - - assert_eq!(repo.statuses.iter().count(), 1); - assert_eq!( - repo.statuses - .get(&Path::new(renamed_dir_name).join(RENAMED_FILE).into()), - Some(&GitFileStatus::Added) - ); - }); - - renamed_dir_name = "new_first_directory/second_directory"; - - std::fs::rename( - work_dir.join("first_directory"), - work_dir.join("new_first_directory"), - ) - .unwrap(); - - tree.flush_fs_events(cx).await; - - tree.read_with(cx, |tree, _cx| { - let snapshot = tree.snapshot(); - let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - - assert_eq!(repo.statuses.iter().count(), 1); - assert_eq!( - repo.statuses - .get(&Path::new(renamed_dir_name).join(RENAMED_FILE).into()), - Some(&GitFileStatus::Added) - ); - }); - } - #[gpui::test] async fn test_write_file(cx: &mut TestAppContext) { let dir = temp_tree(json!({ @@ -5100,4 +4723,474 @@ mod tests { paths } } + + mod git_tests { + use super::*; + use pretty_assertions::assert_eq; + + #[gpui::test] + async fn test_rename_work_directory(cx: &mut TestAppContext) { + let root = temp_tree(json!({ + "projects": { + "project1": { + "a": "", + "b": "", + } + }, + + })); + let root_path = root.path(); + + let http_client = FakeHttpClient::with_404_response(); + let client = cx.read(|cx| Client::new(http_client, cx)); + let tree = Worktree::local( + client, + root_path, + true, + Arc::new(RealFs), + Default::default(), + &mut cx.to_async(), + ) + .await + .unwrap(); + + let repo = git_init(&root_path.join("projects/project1")); + git_add("a", &repo); + git_commit("init", &repo); + std::fs::write(root_path.join("projects/project1/a"), "aa").ok(); + + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + + tree.flush_fs_events(cx).await; + + cx.read(|cx| { + let tree = tree.read(cx); + let (work_dir, repo) = tree.repositories().next().unwrap(); + assert_eq!(work_dir.as_ref(), Path::new("projects/project1")); + assert_eq!( + repo.status_for_file(tree, Path::new("projects/project1/a")), + Some(GitFileStatus::Modified) + ); + assert_eq!( + repo.status_for_file(tree, Path::new("projects/project1/b")), + Some(GitFileStatus::Added) + ); + }); + dbg!("RENAMING"); + std::fs::rename( + root_path.join("projects/project1"), + root_path.join("projects/project2"), + ) + .ok(); + tree.flush_fs_events(cx).await; + + cx.read(|cx| { + let tree = tree.read(cx); + let (work_dir, repo) = tree.repositories().next().unwrap(); + assert_eq!(work_dir.as_ref(), Path::new("projects/project2")); + assert_eq!( + repo.status_for_file(tree, Path::new("projects/project2/a")), + Some(GitFileStatus::Modified) + ); + assert_eq!( + repo.status_for_file(tree, Path::new("projects/project2/b")), + Some(GitFileStatus::Added) + ); + }); + } + + #[gpui::test] + async fn test_git_repository_for_path(cx: &mut TestAppContext) { + let root = temp_tree(json!({ + "c.txt": "", + "dir1": { + ".git": {}, + "deps": { + "dep1": { + ".git": {}, + "src": { + "a.txt": "" + } + } + }, + "src": { + "b.txt": "" + } + }, + })); + + let http_client = FakeHttpClient::with_404_response(); + let client = cx.read(|cx| Client::new(http_client, cx)); + let tree = Worktree::local( + client, + root.path(), + true, + Arc::new(RealFs), + Default::default(), + &mut cx.to_async(), + ) + .await + .unwrap(); + + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + tree.flush_fs_events(cx).await; + + tree.read_with(cx, |tree, _cx| { + let tree = tree.as_local().unwrap(); + + assert!(tree.repository_for_path("c.txt".as_ref()).is_none()); + + let entry = tree.repository_for_path("dir1/src/b.txt".as_ref()).unwrap(); + assert_eq!( + entry + .work_directory(tree) + .map(|directory| directory.as_ref().to_owned()), + Some(Path::new("dir1").to_owned()) + ); + + let entry = tree.repository_for_path("dir1/deps/dep1/src/a.txt".as_ref()).unwrap(); + assert_eq!( + entry + .work_directory(tree) + .map(|directory| directory.as_ref().to_owned()), + Some(Path::new("dir1/deps/dep1").to_owned()) + ); + + let entries = tree.files(false, 0); + + let paths_with_repos = tree + .entries_with_repositories(entries) + .map(|(entry, repo)| { + ( + entry.path.as_ref(), + repo.and_then(|repo| { + repo.work_directory(&tree) + .map(|work_directory| work_directory.0.to_path_buf()) + }), + ) + }) + .collect::>(); + + assert_eq!( + paths_with_repos, + &[ + (Path::new("c.txt"), None), + ( + Path::new("dir1/deps/dep1/src/a.txt"), + Some(Path::new("dir1/deps/dep1").into()) + ), + (Path::new("dir1/src/b.txt"), Some(Path::new("dir1").into())), + ] + ); + }); + + let repo_update_events = Arc::new(Mutex::new(vec![])); + tree.update(cx, |_, cx| { + let repo_update_events = repo_update_events.clone(); + cx.subscribe(&tree, move |_, _, event, _| { + if let Event::UpdatedGitRepositories(update) = event { + repo_update_events.lock().push(update.clone()); + } + }) + .detach(); + }); + + std::fs::write(root.path().join("dir1/.git/random_new_file"), "hello").unwrap(); + tree.flush_fs_events(cx).await; + + assert_eq!( + repo_update_events.lock()[0] + .keys() + .cloned() + .collect::>>(), + vec![Path::new("dir1").into()] + ); + + std::fs::remove_dir_all(root.path().join("dir1/.git")).unwrap(); + tree.flush_fs_events(cx).await; + + tree.read_with(cx, |tree, _cx| { + let tree = tree.as_local().unwrap(); + + assert!(tree.repository_for_path("dir1/src/b.txt".as_ref()).is_none()); + }); + } + + #[gpui::test] + async fn test_git_status(cx: &mut TestAppContext) { + const IGNORE_RULE: &'static str = "**/target"; + + let root = temp_tree(json!({ + "project": { + "a.txt": "a", + "b.txt": "bb", + "c": { + "d": { + "e.txt": "eee" + } + }, + "f.txt": "ffff", + "target": { + "build_file": "???" + }, + ".gitignore": IGNORE_RULE + }, + + })); + + let http_client = FakeHttpClient::with_404_response(); + let client = cx.read(|cx| Client::new(http_client, cx)); + let tree = Worktree::local( + client, + root.path(), + true, + Arc::new(RealFs), + Default::default(), + &mut cx.to_async(), + ) + .await + .unwrap(); + + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + + const A_TXT: &'static str = "a.txt"; + const B_TXT: &'static str = "b.txt"; + const E_TXT: &'static str = "c/d/e.txt"; + const F_TXT: &'static str = "f.txt"; + const DOTGITIGNORE: &'static str = ".gitignore"; + const BUILD_FILE: &'static str = "target/build_file"; + + let work_dir = root.path().join("project"); + let mut repo = git_init(work_dir.as_path()); + repo.add_ignore_rule(IGNORE_RULE).unwrap(); + git_add(Path::new(A_TXT), &repo); + git_add(Path::new(E_TXT), &repo); + git_add(Path::new(DOTGITIGNORE), &repo); + git_commit("Initial commit", &repo); + + std::fs::write(work_dir.join(A_TXT), "aa").unwrap(); + + tree.flush_fs_events(cx).await; + + // Check that the right git state is observed on startup + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + assert_eq!(snapshot.repository_entries.iter().count(), 1); + let (dir, repo) = snapshot.repository_entries.iter().next().unwrap(); + assert_eq!(dir.0.as_ref(), Path::new("project")); + + assert_eq!(repo.statuses.iter().count(), 3); + assert_eq!( + repo.statuses.get(&Path::new(A_TXT).into()), + Some(&GitFileStatus::Modified) + ); + assert_eq!( + repo.statuses.get(&Path::new(B_TXT).into()), + Some(&GitFileStatus::Added) + ); + assert_eq!( + repo.statuses.get(&Path::new(F_TXT).into()), + Some(&GitFileStatus::Added) + ); + }); + + git_add(Path::new(A_TXT), &repo); + git_add(Path::new(B_TXT), &repo); + git_commit("Committing modified and added", &repo); + tree.flush_fs_events(cx).await; + + // Check that repo only changes are tracked + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); + + assert_eq!(repo.statuses.iter().count(), 1); + assert_eq!( + repo.statuses.get(&Path::new(F_TXT).into()), + Some(&GitFileStatus::Added) + ); + }); + + git_reset(0, &repo); + git_remove_index(Path::new(B_TXT), &repo); + git_stash(&mut repo); + std::fs::write(work_dir.join(E_TXT), "eeee").unwrap(); + std::fs::write(work_dir.join(BUILD_FILE), "this should be ignored").unwrap(); + tree.flush_fs_events(cx).await; + + // Check that more complex repo changes are tracked + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); + + assert_eq!(repo.statuses.iter().count(), 3); + assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); + assert_eq!( + repo.statuses.get(&Path::new(B_TXT).into()), + Some(&GitFileStatus::Added) + ); + assert_eq!( + repo.statuses.get(&Path::new(E_TXT).into()), + Some(&GitFileStatus::Modified) + ); + assert_eq!( + repo.statuses.get(&Path::new(F_TXT).into()), + Some(&GitFileStatus::Added) + ); + }); + + std::fs::remove_file(work_dir.join(B_TXT)).unwrap(); + std::fs::remove_dir_all(work_dir.join("c")).unwrap(); + std::fs::write( + work_dir.join(DOTGITIGNORE), + [IGNORE_RULE, "f.txt"].join("\n"), + ) + .unwrap(); + + git_add(Path::new(DOTGITIGNORE), &repo); + git_commit("Committing modified git ignore", &repo); + + tree.flush_fs_events(cx).await; + + // Check that non-repo behavior is tracked + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); + + assert_eq!(repo.statuses.iter().count(), 0); + }); + + let mut renamed_dir_name = "first_directory/second_directory"; + const RENAMED_FILE: &'static str = "rf.txt"; + + std::fs::create_dir_all(work_dir.join(renamed_dir_name)).unwrap(); + std::fs::write( + work_dir.join(renamed_dir_name).join(RENAMED_FILE), + "new-contents", + ) + .unwrap(); + + tree.flush_fs_events(cx).await; + + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); + + assert_eq!(repo.statuses.iter().count(), 1); + assert_eq!( + repo.statuses + .get(&Path::new(renamed_dir_name).join(RENAMED_FILE).into()), + Some(&GitFileStatus::Added) + ); + }); + + renamed_dir_name = "new_first_directory/second_directory"; + + std::fs::rename( + work_dir.join("first_directory"), + work_dir.join("new_first_directory"), + ) + .unwrap(); + + tree.flush_fs_events(cx).await; + + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); + + assert_eq!(repo.statuses.iter().count(), 1); + assert_eq!( + repo.statuses + .get(&Path::new(renamed_dir_name).join(RENAMED_FILE).into()), + Some(&GitFileStatus::Added) + ); + }); + } + + #[track_caller] + fn git_init(path: &Path) -> git2::Repository { + git2::Repository::init(path).expect("Failed to initialize git repository") + } + + #[track_caller] + fn git_add>(path: P, repo: &git2::Repository) { + let path = path.as_ref(); + let mut index = repo.index().expect("Failed to get index"); + index.add_path(path).expect("Failed to add a.txt"); + index.write().expect("Failed to write index"); + } + + #[track_caller] + fn git_remove_index(path: &Path, repo: &git2::Repository) { + let mut index = repo.index().expect("Failed to get index"); + index.remove_path(path).expect("Failed to add a.txt"); + index.write().expect("Failed to write index"); + } + + #[track_caller] + fn git_commit(msg: &'static str, repo: &git2::Repository) { + use git2::Signature; + + let signature = Signature::now("test", "test@zed.dev").unwrap(); + let oid = repo.index().unwrap().write_tree().unwrap(); + let tree = repo.find_tree(oid).unwrap(); + if let Some(head) = repo.head().ok() { + let parent_obj = head.peel(git2::ObjectType::Commit).unwrap(); + + let parent_commit = parent_obj.as_commit().unwrap(); + + repo.commit( + Some("HEAD"), + &signature, + &signature, + msg, + &tree, + &[parent_commit], + ) + .expect("Failed to commit with parent"); + } else { + repo.commit(Some("HEAD"), &signature, &signature, msg, &tree, &[]) + .expect("Failed to commit"); + } + } + + #[track_caller] + fn git_stash(repo: &mut git2::Repository) { + use git2::Signature; + + let signature = Signature::now("test", "test@zed.dev").unwrap(); + repo.stash_save(&signature, "N/A", None) + .expect("Failed to stash"); + } + + #[track_caller] + fn git_reset(offset: usize, repo: &git2::Repository) { + let head = repo.head().expect("Couldn't get repo head"); + let object = head.peel(git2::ObjectType::Commit).unwrap(); + let commit = object.as_commit().unwrap(); + let new_head = commit + .parents() + .inspect(|parnet| { + parnet.message(); + }) + .skip(offset) + .next() + .expect("Not enough history"); + repo.reset(&new_head.as_object(), git2::ResetType::Soft, None) + .expect("Could not reset"); + } + + #[allow(dead_code)] + #[track_caller] + fn git_status(repo: &git2::Repository) -> HashMap { + repo.statuses(None) + .unwrap() + .iter() + .map(|status| (status.path().unwrap().to_string(), status.status())) + .collect() + } + } } From 14744292719a29ca87ad0eaf466c0a81579cb0a5 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 16:32:07 -0700 Subject: [PATCH 066/131] fmt --- crates/project/src/worktree.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index b4f188a2c3..4d6853d2ee 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1641,7 +1641,9 @@ impl Snapshot { /// Get the repository whose work directory contains the given path. pub fn repository_for_work_directory(&self, path: &Path) -> Option { - self.repository_entries.get(&RepositoryWorkDirectory(path.into())).cloned() + self.repository_entries + .get(&RepositoryWorkDirectory(path.into())) + .cloned() } /// Get the repository whose work directory contains the given path. @@ -4850,7 +4852,9 @@ mod tests { Some(Path::new("dir1").to_owned()) ); - let entry = tree.repository_for_path("dir1/deps/dep1/src/a.txt".as_ref()).unwrap(); + let entry = tree + .repository_for_path("dir1/deps/dep1/src/a.txt".as_ref()) + .unwrap(); assert_eq!( entry .work_directory(tree) @@ -4914,7 +4918,9 @@ mod tests { tree.read_with(cx, |tree, _cx| { let tree = tree.as_local().unwrap(); - assert!(tree.repository_for_path("dir1/src/b.txt".as_ref()).is_none()); + assert!(tree + .repository_for_path("dir1/src/b.txt".as_ref()) + .is_none()); }); } From fb11c3e4bf559ffe07563b2b453cf31f52529fa1 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 May 2023 16:52:30 -0700 Subject: [PATCH 067/131] Remove stray prints --- crates/project/src/project.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 15741a27f1..f91cd999f9 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2870,10 +2870,8 @@ impl Project { if let Some(LanguageServerState::Running { watched_paths, .. }) = self.language_servers.get_mut(&language_server_id) { - eprintln!("change watch"); let mut builders = HashMap::default(); for watcher in params.watchers { - eprintln!(" {}", watcher.glob_pattern); for worktree in &self.worktrees { if let Some(worktree) = worktree.upgrade(cx) { let worktree = worktree.read(cx); From 43e301eeef57ac731556ea81b84f03967afb0205 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 16:52:57 -0700 Subject: [PATCH 068/131] refine batched anchor conversions co-authored-by: max --- crates/git/src/diff.rs | 34 +++++++++++++++++----------------- crates/project/src/project.rs | 2 -- crates/text/src/text.rs | 17 +++++++++++++---- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/crates/git/src/diff.rs b/crates/git/src/diff.rs index a2349649c5..09a0d930c5 100644 --- a/crates/git/src/diff.rs +++ b/crates/git/src/diff.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, iter, ops::Range}; +use std::{iter, ops::Range}; use sum_tree::SumTree; use text::{Anchor, BufferSnapshot, Point}; @@ -94,9 +94,6 @@ impl BufferDiff { !before_start && !after_end }); - use std::rc::Rc; - let cell = Rc::new(RefCell::new(None)); - let anchor_iter = std::iter::from_fn(move || { if reversed { cursor.prev(buffer); @@ -106,25 +103,28 @@ impl BufferDiff { cursor.item() }) - .flat_map({ - let cell = cell.clone(); - move |hunk| { - *cell.borrow_mut() = Some(hunk.diff_base_byte_range.clone()); - iter::once(&hunk.buffer_range.start).chain(iter::once(&hunk.buffer_range.end)) - } + .flat_map(move |hunk| { + [ + (&hunk.buffer_range.start, hunk.diff_base_byte_range.start), + (&hunk.buffer_range.end, hunk.diff_base_byte_range.end), + ] + .into_iter() }); - let mut summaries = buffer.summaries_for_anchors::(anchor_iter); + let mut summaries = buffer.summaries_for_anchors_with_payload::(anchor_iter); iter::from_fn(move || { - let start = summaries.next()?; - let end = summaries.next()?; - let base = (cell.borrow_mut()).clone()?; + let (start_point, start_base) = summaries.next()?; + let (end_point, end_base) = summaries.next()?; - let end_row = if end.column > 0 { end.row + 1 } else { end.row }; + let end_row = if end_point.column > 0 { + end_point.row + 1 + } else { + end_point.row + }; Some(DiffHunk { - buffer_range: start.row..end_row, - diff_base_byte_range: base, + buffer_range: start_point.row..end_row, + diff_base_byte_range: start_base..end_base, }) }) } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 15741a27f1..f91cd999f9 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2870,10 +2870,8 @@ impl Project { if let Some(LanguageServerState::Running { watched_paths, .. }) = self.language_servers.get_mut(&language_server_id) { - eprintln!("change watch"); let mut builders = HashMap::default(); for watcher in params.watchers { - eprintln!(" {}", watcher.glob_pattern); for worktree in &self.worktrees { if let Some(worktree) = worktree.upgrade(cx) { let worktree = worktree.read(cx); diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 86bb7f4a26..278fd058fe 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1783,6 +1783,15 @@ impl BufferSnapshot { where D: 'a + TextDimension, A: 'a + IntoIterator, + { + let anchors = anchors.into_iter(); + self.summaries_for_anchors_with_payload::(anchors.map(|a| (a, ()))).map(|d| d.0) + } + + pub fn summaries_for_anchors_with_payload<'a, D, A, T>(&'a self, anchors: A) -> impl 'a + Iterator + where + D: 'a + TextDimension, + A: 'a + IntoIterator, { let anchors = anchors.into_iter(); let mut insertion_cursor = self.insertions.cursor::(); @@ -1790,11 +1799,11 @@ impl BufferSnapshot { let mut text_cursor = self.visible_text.cursor(0); let mut position = D::default(); - anchors.map(move |anchor| { + anchors.map(move |(anchor, payload)| { if *anchor == Anchor::MIN { - return D::default(); + return (D::default(), payload); } else if *anchor == Anchor::MAX { - return D::from_text_summary(&self.visible_text.summary()); + return (D::from_text_summary(&self.visible_text.summary()), payload); } let anchor_key = InsertionFragmentKey { @@ -1825,7 +1834,7 @@ impl BufferSnapshot { } position.add_assign(&text_cursor.summary(fragment_offset)); - position.clone() + (position.clone(), payload) }) } From e32233c8268216ec46ba12475ab35984cf4452e4 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 May 2023 17:15:05 -0700 Subject: [PATCH 069/131] Fix spurious setting error logs on non-existent setting keys --- crates/client/src/client.rs | 2 +- crates/editor/src/editor_settings.rs | 5 ++- crates/language/src/language_settings.rs | 2 +- crates/project/src/project_settings.rs | 2 +- crates/settings/src/settings_store.rs | 42 ++++++++++++---------- crates/workspace/src/workspace_settings.rs | 2 +- 6 files changed, 29 insertions(+), 26 deletions(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 311d9a2b88..c9b83d805a 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -339,7 +339,7 @@ pub struct TelemetrySettings { pub metrics: bool, } -#[derive(Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Clone, Serialize, Deserialize, JsonSchema)] pub struct TelemetrySettingsContent { pub diagnostics: Option, pub metrics: Option, diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 5108d27408..a3f38a3dc0 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -10,17 +10,16 @@ pub struct EditorSettings { pub show_scrollbars: ShowScrollbars, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ShowScrollbars { - #[default] Auto, System, Always, Never, } -#[derive(Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] pub struct EditorSettingsContent { pub cursor_blink: Option, pub hover_popover_enabled: Option, diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index d877304f1d..c98297c036 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -49,7 +49,7 @@ pub struct CopilotSettings { pub disabled_globs: Vec, } -#[derive(Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] pub struct AllLanguageSettingsContent { #[serde(default)] pub features: Option, diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index 92e8cfcca7..c542d1d13f 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use settings::Setting; use std::sync::Arc; -#[derive(Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] pub struct ProjectSettings { #[serde(default)] pub lsp: HashMap, LspSettings>, diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index dd81b05434..329b290a31 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -25,7 +25,7 @@ pub trait Setting: 'static { const KEY: Option<&'static str>; /// The type that is stored in an individual JSON file. - type FileContent: Clone + Serialize + DeserializeOwned + JsonSchema; + type FileContent: Clone + Default + Serialize + DeserializeOwned + JsonSchema; /// The logic for combining together values from one or more JSON files into the /// final value for this setting. @@ -460,11 +460,12 @@ impl SettingsStore { // If the global settings file changed, reload the global value for the field. if changed_local_path.is_none() { - setting_value.set_global_value(setting_value.load_setting( - &default_settings, - &user_settings_stack, - cx, - )?); + if let Some(value) = setting_value + .load_setting(&default_settings, &user_settings_stack, cx) + .log_err() + { + setting_value.set_global_value(value); + } } // Reload the local values for the setting. @@ -495,14 +496,12 @@ impl SettingsStore { continue; } - setting_value.set_local_value( - path.clone(), - setting_value.load_setting( - &default_settings, - &user_settings_stack, - cx, - )?, - ); + if let Some(value) = setting_value + .load_setting(&default_settings, &user_settings_stack, cx) + .log_err() + { + setting_value.set_local_value(path.clone(), value); + } } } } @@ -536,7 +535,12 @@ impl AnySettingValue for SettingValue { fn deserialize_setting(&self, mut json: &serde_json::Value) -> Result { if let Some(key) = T::KEY { - json = json.get(key).unwrap_or(&serde_json::Value::Null); + if let Some(value) = json.get(key) { + json = value; + } else { + let value = T::FileContent::default(); + return Ok(DeserializedSetting(Box::new(value))); + } } let value = T::FileContent::deserialize(json)?; Ok(DeserializedSetting(Box::new(value))) @@ -1126,7 +1130,7 @@ mod tests { staff: bool, } - #[derive(Clone, Serialize, Deserialize, JsonSchema)] + #[derive(Default, Clone, Serialize, Deserialize, JsonSchema)] struct UserSettingsJson { name: Option, age: Option, @@ -1170,7 +1174,7 @@ mod tests { key2: String, } - #[derive(Clone, Serialize, Deserialize, JsonSchema)] + #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] struct MultiKeySettingsJson { key1: Option, key2: Option, @@ -1203,7 +1207,7 @@ mod tests { Hour24, } - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] + #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)] struct JournalSettingsJson { pub path: Option, pub hour_format: Option, @@ -1223,7 +1227,7 @@ mod tests { } } - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] + #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] struct LanguageSettings { #[serde(default)] languages: HashMap, diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs index 41e4796491..4ec0a22b06 100644 --- a/crates/workspace/src/workspace_settings.rs +++ b/crates/workspace/src/workspace_settings.rs @@ -17,7 +17,7 @@ pub struct WorkspaceSettings { pub git: GitSettings, } -#[derive(Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] pub struct WorkspaceSettingsContent { pub active_pane_magnification: Option, pub confirm_quit: Option, From bbb68c523cc4bfac234e08015cf2fa6673500f6b Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 18:09:47 -0700 Subject: [PATCH 070/131] Refactored apart the forward and the backwards iterator for diff hunks --- crates/collab/src/tests/integration_tests.rs | 18 +-- crates/editor/src/editor.rs | 132 +++++++++++------- crates/editor/src/element.rs | 4 +- crates/editor/src/multi_buffer.rs | 85 ++++++++--- crates/editor/src/test/editor_test_context.rs | 4 + crates/git/src/diff.rs | 47 +++++-- crates/language/src/buffer.rs | 14 +- crates/project/src/worktree.rs | 2 +- crates/text/src/text.rs | 8 +- 9 files changed, 213 insertions(+), 101 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index d0625066d5..807510d705 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -2437,7 +2437,7 @@ async fn test_git_diff_base_change( buffer_local_a.read_with(cx_a, |buffer, _| { assert_eq!(buffer.diff_base(), Some(diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_row_range(0..4, false), + buffer.snapshot().git_diff_hunks_in_row_range(0..4), &buffer, &diff_base, &[(1..2, "", "two\n")], @@ -2457,7 +2457,7 @@ async fn test_git_diff_base_change( buffer_remote_a.read_with(cx_b, |buffer, _| { assert_eq!(buffer.diff_base(), Some(diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_row_range(0..4, false), + buffer.snapshot().git_diff_hunks_in_row_range(0..4), &buffer, &diff_base, &[(1..2, "", "two\n")], @@ -2481,7 +2481,7 @@ async fn test_git_diff_base_change( assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_row_range(0..4, false), + buffer.snapshot().git_diff_hunks_in_row_range(0..4), &buffer, &diff_base, &[(2..3, "", "three\n")], @@ -2492,7 +2492,7 @@ async fn test_git_diff_base_change( buffer_remote_a.read_with(cx_b, |buffer, _| { assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_row_range(0..4, false), + buffer.snapshot().git_diff_hunks_in_row_range(0..4), &buffer, &diff_base, &[(2..3, "", "three\n")], @@ -2535,7 +2535,7 @@ async fn test_git_diff_base_change( buffer_local_b.read_with(cx_a, |buffer, _| { assert_eq!(buffer.diff_base(), Some(diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_row_range(0..4, false), + buffer.snapshot().git_diff_hunks_in_row_range(0..4), &buffer, &diff_base, &[(1..2, "", "two\n")], @@ -2555,7 +2555,7 @@ async fn test_git_diff_base_change( buffer_remote_b.read_with(cx_b, |buffer, _| { assert_eq!(buffer.diff_base(), Some(diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_row_range(0..4, false), + buffer.snapshot().git_diff_hunks_in_row_range(0..4), &buffer, &diff_base, &[(1..2, "", "two\n")], @@ -2583,12 +2583,12 @@ async fn test_git_diff_base_change( "{:?}", buffer .snapshot() - .git_diff_hunks_in_row_range(0..4, false) + .git_diff_hunks_in_row_range(0..4) .collect::>() ); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_row_range(0..4, false), + buffer.snapshot().git_diff_hunks_in_row_range(0..4), &buffer, &diff_base, &[(2..3, "", "three\n")], @@ -2599,7 +2599,7 @@ async fn test_git_diff_base_change( buffer_remote_b.read_with(cx_b, |buffer, _| { assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_row_range(0..4, false), + buffer.snapshot().git_diff_hunks_in_row_range(0..4), &buffer, &diff_base, &[(2..3, "", "three\n")], diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0fb7a10a16..7207e3c91c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -20,6 +20,7 @@ mod editor_tests; #[cfg(any(test, feature = "test-support"))] pub mod test; +use ::git::diff::DiffHunk; use aho_corasick::AhoCorasick; use anyhow::{anyhow, Result}; use blink_manager::BlinkManager; @@ -527,7 +528,7 @@ pub struct EditorSnapshot { impl EditorSnapshot { fn has_scrollbar_info(&self) -> bool { self.buffer_snapshot - .git_diff_hunks_in_range(0..self.max_point().row(), false) + .git_diff_hunks_in_range(0..self.max_point().row()) .next() .is_some() } @@ -5569,68 +5570,91 @@ impl Editor { } fn go_to_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext) { - self.go_to_hunk_impl(Direction::Next, cx) - } - - fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext) { - self.go_to_hunk_impl(Direction::Prev, cx) - } - - pub fn go_to_hunk_impl(&mut self, direction: Direction, cx: &mut ViewContext) { let snapshot = self .display_map .update(cx, |display_map, cx| display_map.snapshot(cx)); let selection = self.selections.newest::(cx); - fn seek_in_direction( - this: &mut Editor, - snapshot: &DisplaySnapshot, - initial_point: Point, - is_wrapped: bool, - direction: Direction, - cx: &mut ViewContext, - ) -> bool { - let hunks = if direction == Direction::Next { + if !self.seek_in_direction( + &snapshot, + selection.head(), + false, + snapshot + .buffer_snapshot + .git_diff_hunks_in_range((selection.head().row + 1)..u32::MAX), + cx, + ) { + let wrapped_point = Point::zero(); + self.seek_in_direction( + &snapshot, + wrapped_point, + true, snapshot .buffer_snapshot - .git_diff_hunks_in_range(initial_point.row..u32::MAX, false) - } else { - snapshot - .buffer_snapshot - .git_diff_hunks_in_range(0..initial_point.row, true) - }; - - let display_point = initial_point.to_display_point(snapshot); - let mut hunks = hunks - .map(|hunk| diff_hunk_to_display(hunk, &snapshot)) - .skip_while(|hunk| { - if is_wrapped { - false - } else { - hunk.contains_display_row(display_point.row()) - } - }) - .dedup(); - - if let Some(hunk) = hunks.next() { - this.change_selections(Some(Autoscroll::fit()), cx, |s| { - let row = hunk.start_display_row(); - let point = DisplayPoint::new(row, 0); - s.select_display_ranges([point..point]); - }); - - true - } else { - false - } + .git_diff_hunks_in_range((wrapped_point.row + 1)..u32::MAX), + cx, + ); } + } - if !seek_in_direction(self, &snapshot, selection.head(), false, direction, cx) { - let wrapped_point = match direction { - Direction::Next => Point::zero(), - Direction::Prev => snapshot.buffer_snapshot.max_point(), - }; - seek_in_direction(self, &snapshot, wrapped_point, true, direction, cx); + fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext) { + let snapshot = self + .display_map + .update(cx, |display_map, cx| display_map.snapshot(cx)); + let selection = self.selections.newest::(cx); + + if !self.seek_in_direction( + &snapshot, + selection.head(), + false, + snapshot + .buffer_snapshot + .git_diff_hunks_in_range_rev(0..selection.head().row), + cx, + ) { + let wrapped_point = snapshot.buffer_snapshot.max_point(); + self.seek_in_direction( + &snapshot, + wrapped_point, + true, + snapshot + .buffer_snapshot + .git_diff_hunks_in_range_rev(0..wrapped_point.row), + cx, + ); + } + } + + fn seek_in_direction( + &mut self, + snapshot: &DisplaySnapshot, + initial_point: Point, + is_wrapped: bool, + hunks: impl Iterator>, + cx: &mut ViewContext, + ) -> bool { + let display_point = initial_point.to_display_point(snapshot); + let mut hunks = hunks + .map(|hunk| diff_hunk_to_display(hunk, &snapshot)) + .skip_while(|hunk| { + if is_wrapped { + false + } else { + hunk.contains_display_row(display_point.row()) + } + }) + .dedup(); + + if let Some(hunk) = hunks.next() { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + let row = hunk.start_display_row(); + let point = DisplayPoint::new(row, 0); + s.select_display_ranges([point..point]); + }); + + true + } else { + false } } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7285db7366..57dc3293f6 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1057,7 +1057,7 @@ impl EditorElement { .position_map .snapshot .buffer_snapshot - .git_diff_hunks_in_range(0..(max_row.floor() as u32), false) + .git_diff_hunks_in_range(0..(max_row.floor() as u32)) { let start_display = Point::new(hunk.buffer_range.start, 0) .to_display_point(&layout.position_map.snapshot.display_snapshot); @@ -1274,7 +1274,7 @@ impl EditorElement { .row; buffer_snapshot - .git_diff_hunks_in_range(buffer_start_row..buffer_end_row, false) + .git_diff_hunks_in_range(buffer_start_row..buffer_end_row) .map(|hunk| diff_hunk_to_display(hunk, snapshot)) .dedup() .collect() diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index f3e8fd7440..1423473e1a 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -2841,20 +2841,15 @@ impl MultiBufferSnapshot { }) } - pub fn git_diff_hunks_in_range<'a>( + pub fn git_diff_hunks_in_range_rev<'a>( &'a self, row_range: Range, - reversed: bool, ) -> impl 'a + Iterator> { let mut cursor = self.excerpts.cursor::(); - if reversed { - cursor.seek(&Point::new(row_range.end, 0), Bias::Left, &()); - if cursor.item().is_none() { - cursor.prev(&()); - } - } else { - cursor.seek(&Point::new(row_range.start, 0), Bias::Right, &()); + cursor.seek(&Point::new(row_range.end, 0), Bias::Left, &()); + if cursor.item().is_none() { + cursor.prev(&()); } std::iter::from_fn(move || { @@ -2884,7 +2879,7 @@ impl MultiBufferSnapshot { let buffer_hunks = excerpt .buffer - .git_diff_hunks_intersecting_range(buffer_start..buffer_end, reversed) + .git_diff_hunks_intersecting_range_rev(buffer_start..buffer_end) .filter_map(move |hunk| { let start = multibuffer_start.row + hunk @@ -2904,12 +2899,70 @@ impl MultiBufferSnapshot { }) }); - if reversed { - cursor.prev(&()); - } else { - cursor.next(&()); + cursor.prev(&()); + + Some(buffer_hunks) + }) + .flatten() + } + + pub fn git_diff_hunks_in_range<'a>( + &'a self, + row_range: Range, + ) -> impl 'a + Iterator> { + let mut cursor = self.excerpts.cursor::(); + + cursor.seek(&Point::new(row_range.start, 0), Bias::Right, &()); + + std::iter::from_fn(move || { + let excerpt = cursor.item()?; + let multibuffer_start = *cursor.start(); + let multibuffer_end = multibuffer_start + excerpt.text_summary.lines; + if multibuffer_start.row >= row_range.end { + return None; } + let mut buffer_start = excerpt.range.context.start; + let mut buffer_end = excerpt.range.context.end; + let excerpt_start_point = buffer_start.to_point(&excerpt.buffer); + let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines; + + if row_range.start > multibuffer_start.row { + let buffer_start_point = + excerpt_start_point + Point::new(row_range.start - multibuffer_start.row, 0); + buffer_start = excerpt.buffer.anchor_before(buffer_start_point); + } + + if row_range.end < multibuffer_end.row { + let buffer_end_point = + excerpt_start_point + Point::new(row_range.end - multibuffer_start.row, 0); + buffer_end = excerpt.buffer.anchor_before(buffer_end_point); + } + + let buffer_hunks = excerpt + .buffer + .git_diff_hunks_intersecting_range(buffer_start..buffer_end) + .filter_map(move |hunk| { + let start = multibuffer_start.row + + hunk + .buffer_range + .start + .saturating_sub(excerpt_start_point.row); + let end = multibuffer_start.row + + hunk + .buffer_range + .end + .min(excerpt_end_point.row + 1) + .saturating_sub(excerpt_start_point.row); + + Some(DiffHunk { + buffer_range: start..end, + diff_base_byte_range: hunk.diff_base_byte_range.clone(), + }) + }); + + cursor.next(&()); + Some(buffer_hunks) }) .flatten() @@ -4647,7 +4700,7 @@ mod tests { assert_eq!( snapshot - .git_diff_hunks_in_range(0..12, false) + .git_diff_hunks_in_range(0..12) .map(|hunk| (hunk.status(), hunk.buffer_range)) .collect::>(), &expected, @@ -4655,7 +4708,7 @@ mod tests { assert_eq!( snapshot - .git_diff_hunks_in_range(0..12, true) + .git_diff_hunks_in_range_rev(0..12) .map(|hunk| (hunk.status(), hunk.buffer_range)) .collect::>(), expected diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index ced99a3f23..e520562ebb 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -204,6 +204,7 @@ impl<'a> EditorTestContext<'a> { self.assert_selections(expected_selections, marked_text.to_string()) } + #[track_caller] pub fn assert_editor_background_highlights(&mut self, marked_text: &str) { let expected_ranges = self.ranges(marked_text); let actual_ranges: Vec> = self.update_editor(|editor, cx| { @@ -220,6 +221,7 @@ impl<'a> EditorTestContext<'a> { assert_set_eq!(actual_ranges, expected_ranges); } + #[track_caller] pub fn assert_editor_text_highlights(&mut self, marked_text: &str) { let expected_ranges = self.ranges(marked_text); let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx)); @@ -233,12 +235,14 @@ impl<'a> EditorTestContext<'a> { assert_set_eq!(actual_ranges, expected_ranges); } + #[track_caller] pub fn assert_editor_selections(&mut self, expected_selections: Vec>) { let expected_marked_text = generate_marked_text(&self.buffer_text(), &expected_selections, true); self.assert_selections(expected_selections, expected_marked_text) } + #[track_caller] fn assert_selections( &mut self, expected_selections: Vec>, diff --git a/crates/git/src/diff.rs b/crates/git/src/diff.rs index 09a0d930c5..8704f85005 100644 --- a/crates/git/src/diff.rs +++ b/crates/git/src/diff.rs @@ -1,6 +1,6 @@ use std::{iter, ops::Range}; use sum_tree::SumTree; -use text::{Anchor, BufferSnapshot, Point}; +use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point}; pub use git2 as libgit; use libgit::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch}; @@ -75,18 +75,17 @@ impl BufferDiff { &'a self, range: Range, buffer: &'a BufferSnapshot, - reversed: bool, ) -> impl 'a + Iterator> { let start = buffer.anchor_before(Point::new(range.start, 0)); let end = buffer.anchor_after(Point::new(range.end, 0)); - self.hunks_intersecting_range(start..end, buffer, reversed) + + self.hunks_intersecting_range(start..end, buffer) } pub fn hunks_intersecting_range<'a>( &'a self, range: Range, buffer: &'a BufferSnapshot, - reversed: bool, ) -> impl 'a + Iterator> { let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| { let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt(); @@ -95,12 +94,7 @@ impl BufferDiff { }); let anchor_iter = std::iter::from_fn(move || { - if reversed { - cursor.prev(buffer); - } else { - cursor.next(buffer); - } - + cursor.next(buffer); cursor.item() }) .flat_map(move |hunk| { @@ -129,6 +123,35 @@ impl BufferDiff { }) } + pub fn hunks_intersecting_range_rev<'a>( + &'a self, + range: Range, + buffer: &'a BufferSnapshot, + ) -> impl 'a + Iterator> { + let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| { + let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt(); + let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt(); + !before_start && !after_end + }); + + std::iter::from_fn(move || { + cursor.prev(buffer); + + let hunk = cursor.item()?; + let range = hunk.buffer_range.to_point(buffer); + let end_row = if range.end.column > 0 { + range.end.row + 1 + } else { + range.end.row + }; + + Some(DiffHunk { + buffer_range: range.start.row..end_row, + diff_base_byte_range: hunk.diff_base_byte_range.clone(), + }) + }) + } + pub fn clear(&mut self, buffer: &text::BufferSnapshot) { self.last_buffer_version = Some(buffer.version().clone()); self.tree = SumTree::new(); @@ -163,7 +186,7 @@ impl BufferDiff { fn hunks<'a>(&'a self, text: &'a BufferSnapshot) -> impl 'a + Iterator> { let start = text.anchor_before(Point::new(0, 0)); let end = text.anchor_after(Point::new(u32::MAX, u32::MAX)); - self.hunks_intersecting_range(start..end, text, false) + self.hunks_intersecting_range(start..end, text) } fn diff<'a>(head: &'a str, current: &'a str) -> Option> { @@ -379,7 +402,7 @@ mod tests { assert_eq!(diff.hunks(&buffer).count(), 8); assert_hunks( - diff.hunks_in_row_range(7..12, &buffer, false), + diff.hunks_in_row_range(7..12, &buffer), &buffer, &diff_base, &[ diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index aee646091a..3a97702487 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -2509,18 +2509,22 @@ impl BufferSnapshot { pub fn git_diff_hunks_in_row_range<'a>( &'a self, range: Range, - reversed: bool, ) -> impl 'a + Iterator> { - self.git_diff.hunks_in_row_range(range, self, reversed) + self.git_diff.hunks_in_row_range(range, self) } pub fn git_diff_hunks_intersecting_range<'a>( &'a self, range: Range, - reversed: bool, ) -> impl 'a + Iterator> { - self.git_diff - .hunks_intersecting_range(range, self, reversed) + self.git_diff.hunks_intersecting_range(range, self) + } + + pub fn git_diff_hunks_intersecting_range_rev<'a>( + &'a self, + range: Range, + ) -> impl 'a + Iterator> { + self.git_diff.hunks_intersecting_range_rev(range, self) } pub fn diagnostics_in_range<'a, T, O>( diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 4d6853d2ee..4f898aa91d 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -4779,7 +4779,7 @@ mod tests { Some(GitFileStatus::Added) ); }); - dbg!("RENAMING"); + std::fs::rename( root_path.join("projects/project1"), root_path.join("projects/project2"), diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 278fd058fe..dcfaf818d1 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1785,10 +1785,14 @@ impl BufferSnapshot { A: 'a + IntoIterator, { let anchors = anchors.into_iter(); - self.summaries_for_anchors_with_payload::(anchors.map(|a| (a, ()))).map(|d| d.0) + self.summaries_for_anchors_with_payload::(anchors.map(|a| (a, ()))) + .map(|d| d.0) } - pub fn summaries_for_anchors_with_payload<'a, D, A, T>(&'a self, anchors: A) -> impl 'a + Iterator + pub fn summaries_for_anchors_with_payload<'a, D, A, T>( + &'a self, + anchors: A, + ) -> impl 'a + Iterator where D: 'a + TextDimension, A: 'a + IntoIterator, From 986eafd84e59f7dbf28ac1dc78f6a896a40a5fad Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 20 May 2023 09:54:38 -0600 Subject: [PATCH 071/131] Enable test-support on editor in tests --- crates/activity_indicator/Cargo.toml | 3 +++ crates/copilot_button/Cargo.toml | 3 +++ crates/feedback/Cargo.toml | 3 +++ crates/file_finder/Cargo.toml | 1 + crates/go_to_line/Cargo.toml | 3 +++ crates/journal/Cargo.toml | 3 +++ crates/language_selector/Cargo.toml | 3 +++ crates/lsp_log/Cargo.toml | 1 + crates/outline/Cargo.toml | 3 +++ crates/picker/Cargo.toml | 1 + crates/project_symbols/Cargo.toml | 1 + crates/recent_projects/Cargo.toml | 3 +++ crates/terminal_view/Cargo.toml | 1 + crates/theme_selector/Cargo.toml | 3 +++ crates/welcome/Cargo.toml | 3 +++ 15 files changed, 35 insertions(+) diff --git a/crates/activity_indicator/Cargo.toml b/crates/activity_indicator/Cargo.toml index 917383234a..43d16e6b9b 100644 --- a/crates/activity_indicator/Cargo.toml +++ b/crates/activity_indicator/Cargo.toml @@ -21,3 +21,6 @@ workspace = { path = "../workspace" } futures.workspace = true smallvec.workspace = true + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/copilot_button/Cargo.toml b/crates/copilot_button/Cargo.toml index ad3febd68c..50fbaa64ee 100644 --- a/crates/copilot_button/Cargo.toml +++ b/crates/copilot_button/Cargo.toml @@ -23,3 +23,6 @@ workspace = { path = "../workspace" } anyhow.workspace = true smol.workspace = true futures.workspace = true + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index e74e14ff4c..ddd6ab0009 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -35,3 +35,6 @@ serde_derive.workspace = true sysinfo = "0.27.1" tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } urlencoding = "2.1.2" + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/file_finder/Cargo.toml b/crates/file_finder/Cargo.toml index cae3fa25ca..6f6be7427b 100644 --- a/crates/file_finder/Cargo.toml +++ b/crates/file_finder/Cargo.toml @@ -23,6 +23,7 @@ workspace = { path = "../workspace" } postage.workspace = true [dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } language = { path = "../language", features = ["test-support"] } workspace = { path = "../workspace", features = ["test-support"] } diff --git a/crates/go_to_line/Cargo.toml b/crates/go_to_line/Cargo.toml index 441f7ef7e4..b32b4aaf13 100644 --- a/crates/go_to_line/Cargo.toml +++ b/crates/go_to_line/Cargo.toml @@ -18,3 +18,6 @@ workspace = { path = "../workspace" } postage.workspace = true theme = { path = "../theme" } util = { path = "../util" } + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/journal/Cargo.toml b/crates/journal/Cargo.toml index c1d9bde89e..b7cbc62559 100644 --- a/crates/journal/Cargo.toml +++ b/crates/journal/Cargo.toml @@ -22,3 +22,6 @@ serde.workspace = true schemars.workspace = true log.workspace = true shellexpand = "2.1.0" + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/language_selector/Cargo.toml b/crates/language_selector/Cargo.toml index e7b3d8d4be..f6e213f25f 100644 --- a/crates/language_selector/Cargo.toml +++ b/crates/language_selector/Cargo.toml @@ -20,3 +20,6 @@ settings = { path = "../settings" } util = { path = "../util" } workspace = { path = "../workspace" } anyhow.workspace = true + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/lsp_log/Cargo.toml b/crates/lsp_log/Cargo.toml index 8741f0a4cf..6f47057b44 100644 --- a/crates/lsp_log/Cargo.toml +++ b/crates/lsp_log/Cargo.toml @@ -24,6 +24,7 @@ serde.workspace = true anyhow.workspace = true [dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } unindent.workspace = true diff --git a/crates/outline/Cargo.toml b/crates/outline/Cargo.toml index 95272b063e..f4e2b849fa 100644 --- a/crates/outline/Cargo.toml +++ b/crates/outline/Cargo.toml @@ -22,3 +22,6 @@ workspace = { path = "../workspace" } ordered-float.workspace = true postage.workspace = true smol.workspace = true + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/picker/Cargo.toml b/crates/picker/Cargo.toml index b723cd788c..54e4b15ad5 100644 --- a/crates/picker/Cargo.toml +++ b/crates/picker/Cargo.toml @@ -20,6 +20,7 @@ workspace = { path = "../workspace" } parking_lot.workspace = true [dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } serde_json.workspace = true workspace = { path = "../workspace", features = ["test-support"] } diff --git a/crates/project_symbols/Cargo.toml b/crates/project_symbols/Cargo.toml index 7e23e42b26..85939634ad 100644 --- a/crates/project_symbols/Cargo.toml +++ b/crates/project_symbols/Cargo.toml @@ -27,6 +27,7 @@ smol.workspace = true [dev-dependencies] futures.workspace = true +editor = { path = "../editor", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } language = { path = "../language", features = ["test-support"] } diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml index d9e7546f34..14f8853c9c 100644 --- a/crates/recent_projects/Cargo.toml +++ b/crates/recent_projects/Cargo.toml @@ -24,3 +24,6 @@ workspace = { path = "../workspace" } ordered-float.workspace = true postage.workspace = true smol.workspace = true + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/terminal_view/Cargo.toml b/crates/terminal_view/Cargo.toml index 3a25317870..a42d6c550e 100644 --- a/crates/terminal_view/Cargo.toml +++ b/crates/terminal_view/Cargo.toml @@ -39,6 +39,7 @@ serde_derive.workspace = true [dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } client = { path = "../client", features = ["test-support"]} project = { path = "../project", features = ["test-support"]} diff --git a/crates/theme_selector/Cargo.toml b/crates/theme_selector/Cargo.toml index ac3a85d89a..377f64aad6 100644 --- a/crates/theme_selector/Cargo.toml +++ b/crates/theme_selector/Cargo.toml @@ -23,3 +23,6 @@ log.workspace = true parking_lot.workspace = true postage.workspace = true smol.workspace = true + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index 65f5151584..ea01f822a7 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -30,3 +30,6 @@ anyhow.workspace = true log.workspace = true schemars.workspace = true serde.workspace = true + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } From c701901c7b808b9f621bdfe5c60ed83bc6e33d0e Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Sun, 21 May 2023 23:38:01 -0400 Subject: [PATCH 072/131] Fix get preview channel changes script --- script/get-preview-channel-changes | 69 ++++++++++++++++-------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/script/get-preview-channel-changes b/script/get-preview-channel-changes index ac1dcb5b6e..4690c88a3c 100755 --- a/script/get-preview-channel-changes +++ b/script/get-preview-channel-changes @@ -2,8 +2,8 @@ const { execFileSync } = require("child_process"); const { GITHUB_ACCESS_TOKEN } = process.env; -const PR_REGEX = /pull request #(\d+)/; -const FIXES_REGEX = /(fixes|closes) (.+[/#]\d+.*)$/im; +const PR_REGEX = /#\d+/ // Ex: matches on #4241 +const FIXES_REGEX = /(fixes|closes|completes) (.+[/#]\d+.*)$/im; main(); @@ -15,7 +15,7 @@ async function main() { { encoding: "utf8" } ) .split("\n") - .filter((t) => t.startsWith("v") && t.endsWith('-pre')); + .filter((t) => t.startsWith("v") && t.endsWith("-pre")); // Print the previous release console.log(`Changes from ${oldTag} to ${newTag}\n`); @@ -34,37 +34,11 @@ async function main() { } // Get the PRs merged between those two tags. - const pullRequestNumbers = execFileSync( - "git", - [ - "log", - `${oldTag}..${newTag}`, - "--oneline", - "--grep", - "Merge pull request", - ], - { encoding: "utf8" } - ) - .split("\n") - .filter((line) => line.length > 0) - .map((line) => line.match(PR_REGEX)[1]); + const pullRequestNumbers = getPullRequestNumbers(oldTag, newTag) // Get the PRs that were cherry-picked between main and the old tag. - const existingPullRequestNumbers = new Set(execFileSync( - "git", - [ - "log", - `main..${oldTag}`, - "--oneline", - "--grep", - "Merge pull request", - ], - { encoding: "utf8" } - ) - .split("\n") - .filter((line) => line.length > 0) - .map((line) => line.match(PR_REGEX)[1])); - + const existingPullRequestNumbers = new Set(getPullRequestNumbers("main", oldTag)) + // Filter out those existing PRs from the set of new PRs. const newPullRequestNumbers = pullRequestNumbers.filter(number => !existingPullRequestNumbers.has(number)); @@ -86,10 +60,39 @@ async function main() { console.log(" URL: ", webURL); // If the pull request contains a 'closes' line, print the closed issue. - const fixesMatch = (pullRequest.body || '').match(FIXES_REGEX); + const fixesMatch = (pullRequest.body || "").match(FIXES_REGEX); if (fixesMatch) { const fixedIssueURL = fixesMatch[2]; console.log(" Issue: ", fixedIssueURL); } + + let releaseNotes = (pullRequest.body || "").split("Release Notes:")[1]; + + if (releaseNotes) { + releaseNotes = releaseNotes.trim() + console.log(" Release Notes:"); + console.log(` ${releaseNotes}`); + } } } + +function getPullRequestNumbers(oldTag, newTag) { + const pullRequestNumbers = execFileSync( + "git", + [ + "log", + `${oldTag}..${newTag}`, + "--oneline" + ], + { encoding: "utf8" } + ) + .split("\n") + .filter(line => line.length > 0) + .map(line => { + const match = line.match(/#(\d+)/); + return match ? match[1] : null; + }) + .filter(line => line) + + return pullRequestNumbers +} From cb1b64e51ba3d0866834965def43343f82050b79 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Mon, 22 May 2023 00:02:19 -0400 Subject: [PATCH 073/131] Fix up formatting for get preview channel changes script --- script/get-preview-channel-changes | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/script/get-preview-channel-changes b/script/get-preview-channel-changes index 4690c88a3c..47623125f9 100755 --- a/script/get-preview-channel-changes +++ b/script/get-preview-channel-changes @@ -34,16 +34,16 @@ async function main() { } // Get the PRs merged between those two tags. - const pullRequestNumbers = getPullRequestNumbers(oldTag, newTag) + const pullRequestNumbers = getPullRequestNumbers(oldTag, newTag); // Get the PRs that were cherry-picked between main and the old tag. - const existingPullRequestNumbers = new Set(getPullRequestNumbers("main", oldTag)) + const existingPullRequestNumbers = new Set(getPullRequestNumbers("main", oldTag)); // Filter out those existing PRs from the set of new PRs. const newPullRequestNumbers = pullRequestNumbers.filter(number => !existingPullRequestNumbers.has(number)); // Fetch the pull requests from the GitHub API. - console.log("Merged Pull requests:") + console.log("Merged Pull requests:"); for (const pullRequestNumber of newPullRequestNumbers) { const webURL = `https://github.com/zed-industries/zed/pull/${pullRequestNumber}`; const apiURL = `https://api.github.com/repos/zed-industries/zed/pulls/${pullRequestNumber}`; @@ -57,22 +57,24 @@ async function main() { // Print the pull request title and URL. const pullRequest = await response.json(); console.log("*", pullRequest.title); - console.log(" URL: ", webURL); + console.log(" PR URL: ", webURL); // If the pull request contains a 'closes' line, print the closed issue. const fixesMatch = (pullRequest.body || "").match(FIXES_REGEX); if (fixesMatch) { const fixedIssueURL = fixesMatch[2]; - console.log(" Issue: ", fixedIssueURL); + console.log(" Issue URL: ", fixedIssueURL); } let releaseNotes = (pullRequest.body || "").split("Release Notes:")[1]; if (releaseNotes) { - releaseNotes = releaseNotes.trim() + releaseNotes = releaseNotes.trim(); console.log(" Release Notes:"); console.log(` ${releaseNotes}`); } + + console.log() } } @@ -92,7 +94,7 @@ function getPullRequestNumbers(oldTag, newTag) { const match = line.match(/#(\d+)/); return match ? match[1] : null; }) - .filter(line => line) + .filter(line => line); - return pullRequestNumbers + return pullRequestNumbers; } From 2098ac2c7732159328a8b6df426726e12ef94447 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 10:41:48 +0200 Subject: [PATCH 074/131] Fix formatting for keymap.json --- assets/keymaps/default.json | 1066 +++++++++++++++++------------------ 1 file changed, 533 insertions(+), 533 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 246b699558..1fca19a83f 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -1,536 +1,536 @@ [ - // Standard macOS bindings - { - "bindings": { - "up": "menu::SelectPrev", - "pageup": "menu::SelectFirst", - "shift-pageup": "menu::SelectFirst", - "ctrl-p": "menu::SelectPrev", - "down": "menu::SelectNext", - "pagedown": "menu::SelectLast", - "shift-pagedown": "menu::SelectFirst", - "ctrl-n": "menu::SelectNext", - "cmd-up": "menu::SelectFirst", - "cmd-down": "menu::SelectLast", - "enter": "menu::Confirm", - "escape": "menu::Cancel", - "ctrl-c": "menu::Cancel", - "cmd-{": "pane::ActivatePrevItem", - "cmd-}": "pane::ActivateNextItem", - "alt-cmd-left": "pane::ActivatePrevItem", - "alt-cmd-right": "pane::ActivateNextItem", - "cmd-w": "pane::CloseActiveItem", - "alt-cmd-t": "pane::CloseInactiveItems", - "cmd-k u": "pane::CloseCleanItems", - "cmd-k cmd-w": "pane::CloseAllItems", - "cmd-shift-w": "workspace::CloseWindow", - "cmd-s": "workspace::Save", - "cmd-shift-s": "workspace::SaveAs", - "cmd-=": "zed::IncreaseBufferFontSize", - "cmd--": "zed::DecreaseBufferFontSize", - "cmd-0": "zed::ResetBufferFontSize", - "cmd-,": "zed::OpenSettings", - "cmd-q": "zed::Quit", - "cmd-h": "zed::Hide", - "alt-cmd-h": "zed::HideOthers", - "cmd-m": "zed::Minimize", - "ctrl-cmd-f": "zed::ToggleFullScreen", - "cmd-n": "workspace::NewFile", - "cmd-shift-n": "workspace::NewWindow", - "cmd-o": "workspace::Open", - "alt-cmd-o": "projects::OpenRecent", - "ctrl-~": "workspace::NewTerminal", - "ctrl-`": "terminal_panel::ToggleFocus" - } - }, - { - "context": "Editor", - "bindings": { - "escape": "editor::Cancel", - "backspace": "editor::Backspace", - "shift-backspace": "editor::Backspace", - "ctrl-h": "editor::Backspace", - "delete": "editor::Delete", - "ctrl-d": "editor::Delete", - "tab": "editor::Tab", - "shift-tab": "editor::TabPrev", - "ctrl-k": "editor::CutToEndOfLine", - "ctrl-t": "editor::Transpose", - "cmd-backspace": "editor::DeleteToBeginningOfLine", - "cmd-delete": "editor::DeleteToEndOfLine", - "alt-backspace": "editor::DeleteToPreviousWordStart", - "alt-delete": "editor::DeleteToNextWordEnd", - "alt-h": "editor::DeleteToPreviousWordStart", - "alt-d": "editor::DeleteToNextWordEnd", - "cmd-x": "editor::Cut", - "cmd-c": "editor::Copy", - "cmd-v": "editor::Paste", - "cmd-z": "editor::Undo", - "cmd-shift-z": "editor::Redo", - "up": "editor::MoveUp", - "pageup": "editor::PageUp", - "shift-pageup": "editor::MovePageUp", - "home": "editor::MoveToBeginningOfLine", - "down": "editor::MoveDown", - "pagedown": "editor::PageDown", - "shift-pagedown": "editor::MovePageDown", - "end": "editor::MoveToEndOfLine", - "left": "editor::MoveLeft", - "right": "editor::MoveRight", - "ctrl-p": "editor::MoveUp", - "ctrl-n": "editor::MoveDown", - "ctrl-b": "editor::MoveLeft", - "ctrl-f": "editor::MoveRight", - "ctrl-l": "editor::NextScreen", - "alt-left": "editor::MoveToPreviousWordStart", - "alt-b": "editor::MoveToPreviousWordStart", - "alt-right": "editor::MoveToNextWordEnd", - "alt-f": "editor::MoveToNextWordEnd", - "cmd-left": "editor::MoveToBeginningOfLine", - "ctrl-a": "editor::MoveToBeginningOfLine", - "cmd-right": "editor::MoveToEndOfLine", - "ctrl-e": "editor::MoveToEndOfLine", - "cmd-up": "editor::MoveToBeginning", - "cmd-down": "editor::MoveToEnd", - "shift-up": "editor::SelectUp", - "ctrl-shift-p": "editor::SelectUp", - "shift-down": "editor::SelectDown", - "ctrl-shift-n": "editor::SelectDown", - "shift-left": "editor::SelectLeft", - "ctrl-shift-b": "editor::SelectLeft", - "shift-right": "editor::SelectRight", - "ctrl-shift-f": "editor::SelectRight", - "alt-shift-left": "editor::SelectToPreviousWordStart", - "alt-shift-b": "editor::SelectToPreviousWordStart", - "alt-shift-right": "editor::SelectToNextWordEnd", - "alt-shift-f": "editor::SelectToNextWordEnd", - "cmd-shift-up": "editor::SelectToBeginning", - "cmd-shift-down": "editor::SelectToEnd", - "cmd-a": "editor::SelectAll", - "cmd-l": "editor::SelectLine", - "cmd-shift-i": "editor::Format", - "cmd-shift-left": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true - } - ], - "shift-home": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true - } - ], - "ctrl-shift-a": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true - } - ], - "cmd-shift-right": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true - } - ], - "shift-end": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true - } - ], - "ctrl-shift-e": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true - } - ], - "ctrl-v": [ - "editor::MovePageDown", - { - "center_cursor": true - } - ], - "alt-v": [ - "editor::MovePageUp", - { - "center_cursor": true - } - ], - "ctrl-cmd-space": "editor::ShowCharacterPalette" - } - }, - { - "context": "Editor && mode == full", - "bindings": { - "enter": "editor::Newline", - "cmd-shift-enter": "editor::NewlineAbove", - "cmd-enter": "editor::NewlineBelow", - "alt-z": "editor::ToggleSoftWrap", - "cmd-f": [ - "buffer_search::Deploy", - { - "focus": true - } - ], - "cmd-e": [ - "buffer_search::Deploy", - { - "focus": false - } - ], - "alt-\\": "copilot::Suggest", - "alt-]": "copilot::NextSuggestion", - "alt-[": "copilot::PreviousSuggestion" - } - }, - { - "context": "Editor && mode == auto_height", - "bindings": { - "alt-enter": "editor::Newline", - "cmd-alt-enter": "editor::NewlineBelow" - } - }, - { - "context": "BufferSearchBar > Editor", - "bindings": { - "escape": "buffer_search::Dismiss", - "tab": "buffer_search::FocusEditor", - "enter": "search::SelectNextMatch", - "shift-enter": "search::SelectPrevMatch" - } - }, - { - "context": "ProjectSearchBar > Editor", - "bindings": { - "escape": "project_search::ToggleFocus" - } - }, - { - "context": "ProjectSearchView > Editor", - "bindings": { - "escape": "project_search::ToggleFocus" - } - }, - { - "context": "Pane", - "bindings": { - "cmd-f": "project_search::ToggleFocus", - "cmd-g": "search::SelectNextMatch", - "cmd-shift-g": "search::SelectPrevMatch", - "alt-cmd-c": "search::ToggleCaseSensitive", - "alt-cmd-w": "search::ToggleWholeWord", - "alt-cmd-r": "search::ToggleRegex", - "shift-escape": "workspace::ToggleZoom" - } - }, - // Bindings from VS Code - { - "context": "Editor", - "bindings": { - "cmd-[": "editor::Outdent", - "cmd-]": "editor::Indent", - "cmd-alt-up": "editor::AddSelectionAbove", - "cmd-ctrl-p": "editor::AddSelectionAbove", - "cmd-alt-down": "editor::AddSelectionBelow", - "cmd-ctrl-n": "editor::AddSelectionBelow", - "cmd-d": [ - "editor::SelectNext", - { - "replace_newest": false - } - ], - "cmd-k cmd-d": [ - "editor::SelectNext", - { - "replace_newest": true - } - ], - "cmd-k cmd-i": "editor::Hover", - "cmd-/": [ - "editor::ToggleComments", - { - "advance_downwards": false - } - ], - "alt-up": "editor::SelectLargerSyntaxNode", - "alt-down": "editor::SelectSmallerSyntaxNode", - "cmd-u": "editor::UndoSelection", - "cmd-shift-u": "editor::RedoSelection", - "f8": "editor::GoToDiagnostic", - "shift-f8": "editor::GoToPrevDiagnostic", - "f2": "editor::Rename", - "f12": "editor::GoToDefinition", - "cmd-f12": "editor::GoToTypeDefinition", - "alt-shift-f12": "editor::FindAllReferences", - "ctrl-m": "editor::MoveToEnclosingBracket", - "alt-cmd-[": "editor::Fold", - "alt-cmd-]": "editor::UnfoldLines", - "ctrl-space": "editor::ShowCompletions", - "cmd-.": "editor::ToggleCodeActions", - "alt-cmd-r": "editor::RevealInFinder" - } - }, - { - "context": "Editor && mode == full", - "bindings": { - "cmd-shift-o": "outline::Toggle", - "ctrl-g": "go_to_line::Toggle" - } - }, - { - "context": "Pane", - "bindings": { - "ctrl-1": [ - "pane::ActivateItem", - 0 - ], - "ctrl-2": [ - "pane::ActivateItem", - 1 - ], - "ctrl-3": [ - "pane::ActivateItem", - 2 - ], - "ctrl-4": [ - "pane::ActivateItem", - 3 - ], - "ctrl-5": [ - "pane::ActivateItem", - 4 - ], - "ctrl-6": [ - "pane::ActivateItem", - 5 - ], - "ctrl-7": [ - "pane::ActivateItem", - 6 - ], - "ctrl-8": [ - "pane::ActivateItem", - 7 - ], - "ctrl-9": [ - "pane::ActivateItem", - 8 - ], - "ctrl-0": "pane::ActivateLastItem", - "ctrl--": "pane::GoBack", - "ctrl-_": "pane::GoForward", - "cmd-shift-t": "pane::ReopenClosedItem", - "cmd-shift-f": "project_search::ToggleFocus" - } - }, - { - "context": "Workspace", - "bindings": { - "cmd-1": [ - "workspace::ActivatePane", - 0 - ], - "cmd-2": [ - "workspace::ActivatePane", - 1 - ], - "cmd-3": [ - "workspace::ActivatePane", - 2 - ], - "cmd-4": [ - "workspace::ActivatePane", - 3 - ], - "cmd-5": [ - "workspace::ActivatePane", - 4 - ], - "cmd-6": [ - "workspace::ActivatePane", - 5 - ], - "cmd-7": [ - "workspace::ActivatePane", - 6 - ], - "cmd-8": [ - "workspace::ActivatePane", - 7 - ], - "cmd-9": [ - "workspace::ActivatePane", - 8 - ], - "cmd-b": "workspace::ToggleLeftDock", - "cmd-shift-f": "workspace::NewSearch", - "cmd-k cmd-t": "theme_selector::Toggle", - "cmd-k cmd-s": "zed::OpenKeymap", - "cmd-t": "project_symbols::Toggle", - "cmd-p": "file_finder::Toggle", - "cmd-shift-p": "command_palette::Toggle", - "cmd-shift-m": "diagnostics::Deploy", - "cmd-shift-e": "project_panel::ToggleFocus", - "cmd-alt-s": "workspace::SaveAll", - "cmd-k m": "language_selector::Toggle" - } - }, - // Bindings from Sublime Text - { - "context": "Editor", - "bindings": { - "ctrl-shift-k": "editor::DeleteLine", - "cmd-shift-d": "editor::DuplicateLine", - "cmd-shift-l": "editor::SplitSelectionIntoLines", - "ctrl-cmd-up": "editor::MoveLineUp", - "ctrl-cmd-down": "editor::MoveLineDown", - "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart", - "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", - "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", - "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", - "ctrl-alt-left": "editor::MoveToPreviousSubwordStart", - "ctrl-alt-b": "editor::MoveToPreviousSubwordStart", - "ctrl-alt-right": "editor::MoveToNextSubwordEnd", - "ctrl-alt-f": "editor::MoveToNextSubwordEnd", - "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart", - "ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart", - "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd", - "ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd" - } - }, - { - "bindings": { - "cmd-k cmd-left": "workspace::ActivatePreviousPane", - "cmd-k cmd-right": "workspace::ActivateNextPane" - } - }, - // Bindings from Atom - { - "context": "Pane", - "bindings": { - "cmd-k up": "pane::SplitUp", - "cmd-k down": "pane::SplitDown", - "cmd-k left": "pane::SplitLeft", - "cmd-k right": "pane::SplitRight" - } - }, - // Bindings that should be unified with bindings for more general actions - { - "context": "Editor && renaming", - "bindings": { - "enter": "editor::ConfirmRename" - } - }, - { - "context": "Editor && showing_completions", - "bindings": { - "enter": "editor::ConfirmCompletion", - "tab": "editor::ConfirmCompletion" - } - }, - { - "context": "Editor && showing_code_actions", - "bindings": { - "enter": "editor::ConfirmCodeAction" - } - }, - // Custom bindings - { - "bindings": { - "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator", - "cmd-shift-c": "collab::ToggleContactsMenu", - "cmd-alt-i": "zed::DebugElements" - } - }, - { - "context": "Editor", - "bindings": { - "alt-enter": "editor::OpenExcerpts", - "cmd-f8": "editor::GoToHunk", - "cmd-shift-f8": "editor::GoToPrevHunk" - } - }, - { - "context": "ProjectSearchBar", - "bindings": { - "cmd-enter": "project_search::SearchInNew" - } - }, - { - "context": "ProjectPanel", - "bindings": { - "left": "project_panel::CollapseSelectedEntry", - "right": "project_panel::ExpandSelectedEntry", - "cmd-x": "project_panel::Cut", - "cmd-c": "project_panel::Copy", - "cmd-v": "project_panel::Paste", - "cmd-alt-c": "project_panel::CopyPath", - "alt-cmd-shift-c": "project_panel::CopyRelativePath", - "f2": "project_panel::Rename", - "backspace": "project_panel::Delete", - "alt-cmd-r": "project_panel::RevealInFinder" - } - }, - { - "context": "Terminal", - "bindings": { - "ctrl-cmd-space": "terminal::ShowCharacterPalette", - "cmd-c": "terminal::Copy", - "cmd-v": "terminal::Paste", - "cmd-k": "terminal::Clear", - // Some nice conveniences - "cmd-backspace": [ - "terminal::SendText", - "\u0015" - ], - "cmd-right": [ - "terminal::SendText", - "\u0005" - ], - "cmd-left": [ - "terminal::SendText", - "\u0001" - ], - // Terminal.app compatability - "alt-left": [ - "terminal::SendText", - "\u001bb" - ], - "alt-right": [ - "terminal::SendText", - "\u001bf" - ], - // There are conflicting bindings for these keys in the global context. - // these bindings override them, remove at your own risk: - "up": [ - "terminal::SendKeystroke", - "up" - ], - "pageup": [ - "terminal::SendKeystroke", - "pageup" - ], - "down": [ - "terminal::SendKeystroke", - "down" - ], - "pagedown": [ - "terminal::SendKeystroke", - "pagedown" - ], - "escape": [ - "terminal::SendKeystroke", - "escape" - ], - "enter": [ - "terminal::SendKeystroke", - "enter" - ], - "ctrl-c": [ - "terminal::SendKeystroke", - "ctrl-c" - ] - } + // Standard macOS bindings + { + "bindings": { + "up": "menu::SelectPrev", + "pageup": "menu::SelectFirst", + "shift-pageup": "menu::SelectFirst", + "ctrl-p": "menu::SelectPrev", + "down": "menu::SelectNext", + "pagedown": "menu::SelectLast", + "shift-pagedown": "menu::SelectFirst", + "ctrl-n": "menu::SelectNext", + "cmd-up": "menu::SelectFirst", + "cmd-down": "menu::SelectLast", + "enter": "menu::Confirm", + "escape": "menu::Cancel", + "ctrl-c": "menu::Cancel", + "cmd-{": "pane::ActivatePrevItem", + "cmd-}": "pane::ActivateNextItem", + "alt-cmd-left": "pane::ActivatePrevItem", + "alt-cmd-right": "pane::ActivateNextItem", + "cmd-w": "pane::CloseActiveItem", + "alt-cmd-t": "pane::CloseInactiveItems", + "cmd-k u": "pane::CloseCleanItems", + "cmd-k cmd-w": "pane::CloseAllItems", + "cmd-shift-w": "workspace::CloseWindow", + "cmd-s": "workspace::Save", + "cmd-shift-s": "workspace::SaveAs", + "cmd-=": "zed::IncreaseBufferFontSize", + "cmd--": "zed::DecreaseBufferFontSize", + "cmd-0": "zed::ResetBufferFontSize", + "cmd-,": "zed::OpenSettings", + "cmd-q": "zed::Quit", + "cmd-h": "zed::Hide", + "alt-cmd-h": "zed::HideOthers", + "cmd-m": "zed::Minimize", + "ctrl-cmd-f": "zed::ToggleFullScreen", + "cmd-n": "workspace::NewFile", + "cmd-shift-n": "workspace::NewWindow", + "cmd-o": "workspace::Open", + "alt-cmd-o": "projects::OpenRecent", + "ctrl-~": "workspace::NewTerminal", + "ctrl-`": "terminal_panel::ToggleFocus" } + }, + { + "context": "Editor", + "bindings": { + "escape": "editor::Cancel", + "backspace": "editor::Backspace", + "shift-backspace": "editor::Backspace", + "ctrl-h": "editor::Backspace", + "delete": "editor::Delete", + "ctrl-d": "editor::Delete", + "tab": "editor::Tab", + "shift-tab": "editor::TabPrev", + "ctrl-k": "editor::CutToEndOfLine", + "ctrl-t": "editor::Transpose", + "cmd-backspace": "editor::DeleteToBeginningOfLine", + "cmd-delete": "editor::DeleteToEndOfLine", + "alt-backspace": "editor::DeleteToPreviousWordStart", + "alt-delete": "editor::DeleteToNextWordEnd", + "alt-h": "editor::DeleteToPreviousWordStart", + "alt-d": "editor::DeleteToNextWordEnd", + "cmd-x": "editor::Cut", + "cmd-c": "editor::Copy", + "cmd-v": "editor::Paste", + "cmd-z": "editor::Undo", + "cmd-shift-z": "editor::Redo", + "up": "editor::MoveUp", + "pageup": "editor::PageUp", + "shift-pageup": "editor::MovePageUp", + "home": "editor::MoveToBeginningOfLine", + "down": "editor::MoveDown", + "pagedown": "editor::PageDown", + "shift-pagedown": "editor::MovePageDown", + "end": "editor::MoveToEndOfLine", + "left": "editor::MoveLeft", + "right": "editor::MoveRight", + "ctrl-p": "editor::MoveUp", + "ctrl-n": "editor::MoveDown", + "ctrl-b": "editor::MoveLeft", + "ctrl-f": "editor::MoveRight", + "ctrl-l": "editor::NextScreen", + "alt-left": "editor::MoveToPreviousWordStart", + "alt-b": "editor::MoveToPreviousWordStart", + "alt-right": "editor::MoveToNextWordEnd", + "alt-f": "editor::MoveToNextWordEnd", + "cmd-left": "editor::MoveToBeginningOfLine", + "ctrl-a": "editor::MoveToBeginningOfLine", + "cmd-right": "editor::MoveToEndOfLine", + "ctrl-e": "editor::MoveToEndOfLine", + "cmd-up": "editor::MoveToBeginning", + "cmd-down": "editor::MoveToEnd", + "shift-up": "editor::SelectUp", + "ctrl-shift-p": "editor::SelectUp", + "shift-down": "editor::SelectDown", + "ctrl-shift-n": "editor::SelectDown", + "shift-left": "editor::SelectLeft", + "ctrl-shift-b": "editor::SelectLeft", + "shift-right": "editor::SelectRight", + "ctrl-shift-f": "editor::SelectRight", + "alt-shift-left": "editor::SelectToPreviousWordStart", + "alt-shift-b": "editor::SelectToPreviousWordStart", + "alt-shift-right": "editor::SelectToNextWordEnd", + "alt-shift-f": "editor::SelectToNextWordEnd", + "cmd-shift-up": "editor::SelectToBeginning", + "cmd-shift-down": "editor::SelectToEnd", + "cmd-a": "editor::SelectAll", + "cmd-l": "editor::SelectLine", + "cmd-shift-i": "editor::Format", + "cmd-shift-left": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true + } + ], + "shift-home": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true + } + ], + "ctrl-shift-a": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true + } + ], + "cmd-shift-right": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true + } + ], + "shift-end": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true + } + ], + "ctrl-shift-e": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true + } + ], + "ctrl-v": [ + "editor::MovePageDown", + { + "center_cursor": true + } + ], + "alt-v": [ + "editor::MovePageUp", + { + "center_cursor": true + } + ], + "ctrl-cmd-space": "editor::ShowCharacterPalette" + } + }, + { + "context": "Editor && mode == full", + "bindings": { + "enter": "editor::Newline", + "cmd-shift-enter": "editor::NewlineAbove", + "cmd-enter": "editor::NewlineBelow", + "alt-z": "editor::ToggleSoftWrap", + "cmd-f": [ + "buffer_search::Deploy", + { + "focus": true + } + ], + "cmd-e": [ + "buffer_search::Deploy", + { + "focus": false + } + ], + "alt-\\": "copilot::Suggest", + "alt-]": "copilot::NextSuggestion", + "alt-[": "copilot::PreviousSuggestion" + } + }, + { + "context": "Editor && mode == auto_height", + "bindings": { + "alt-enter": "editor::Newline", + "cmd-alt-enter": "editor::NewlineBelow" + } + }, + { + "context": "BufferSearchBar > Editor", + "bindings": { + "escape": "buffer_search::Dismiss", + "tab": "buffer_search::FocusEditor", + "enter": "search::SelectNextMatch", + "shift-enter": "search::SelectPrevMatch" + } + }, + { + "context": "ProjectSearchBar > Editor", + "bindings": { + "escape": "project_search::ToggleFocus" + } + }, + { + "context": "ProjectSearchView > Editor", + "bindings": { + "escape": "project_search::ToggleFocus" + } + }, + { + "context": "Pane", + "bindings": { + "cmd-f": "project_search::ToggleFocus", + "cmd-g": "search::SelectNextMatch", + "cmd-shift-g": "search::SelectPrevMatch", + "alt-cmd-c": "search::ToggleCaseSensitive", + "alt-cmd-w": "search::ToggleWholeWord", + "alt-cmd-r": "search::ToggleRegex", + "shift-escape": "workspace::ToggleZoom" + } + }, + // Bindings from VS Code + { + "context": "Editor", + "bindings": { + "cmd-[": "editor::Outdent", + "cmd-]": "editor::Indent", + "cmd-alt-up": "editor::AddSelectionAbove", + "cmd-ctrl-p": "editor::AddSelectionAbove", + "cmd-alt-down": "editor::AddSelectionBelow", + "cmd-ctrl-n": "editor::AddSelectionBelow", + "cmd-d": [ + "editor::SelectNext", + { + "replace_newest": false + } + ], + "cmd-k cmd-d": [ + "editor::SelectNext", + { + "replace_newest": true + } + ], + "cmd-k cmd-i": "editor::Hover", + "cmd-/": [ + "editor::ToggleComments", + { + "advance_downwards": false + } + ], + "alt-up": "editor::SelectLargerSyntaxNode", + "alt-down": "editor::SelectSmallerSyntaxNode", + "cmd-u": "editor::UndoSelection", + "cmd-shift-u": "editor::RedoSelection", + "f8": "editor::GoToDiagnostic", + "shift-f8": "editor::GoToPrevDiagnostic", + "f2": "editor::Rename", + "f12": "editor::GoToDefinition", + "cmd-f12": "editor::GoToTypeDefinition", + "alt-shift-f12": "editor::FindAllReferences", + "ctrl-m": "editor::MoveToEnclosingBracket", + "alt-cmd-[": "editor::Fold", + "alt-cmd-]": "editor::UnfoldLines", + "ctrl-space": "editor::ShowCompletions", + "cmd-.": "editor::ToggleCodeActions", + "alt-cmd-r": "editor::RevealInFinder" + } + }, + { + "context": "Editor && mode == full", + "bindings": { + "cmd-shift-o": "outline::Toggle", + "ctrl-g": "go_to_line::Toggle" + } + }, + { + "context": "Pane", + "bindings": { + "ctrl-1": [ + "pane::ActivateItem", + 0 + ], + "ctrl-2": [ + "pane::ActivateItem", + 1 + ], + "ctrl-3": [ + "pane::ActivateItem", + 2 + ], + "ctrl-4": [ + "pane::ActivateItem", + 3 + ], + "ctrl-5": [ + "pane::ActivateItem", + 4 + ], + "ctrl-6": [ + "pane::ActivateItem", + 5 + ], + "ctrl-7": [ + "pane::ActivateItem", + 6 + ], + "ctrl-8": [ + "pane::ActivateItem", + 7 + ], + "ctrl-9": [ + "pane::ActivateItem", + 8 + ], + "ctrl-0": "pane::ActivateLastItem", + "ctrl--": "pane::GoBack", + "ctrl-_": "pane::GoForward", + "cmd-shift-t": "pane::ReopenClosedItem", + "cmd-shift-f": "project_search::ToggleFocus" + } + }, + { + "context": "Workspace", + "bindings": { + "cmd-1": [ + "workspace::ActivatePane", + 0 + ], + "cmd-2": [ + "workspace::ActivatePane", + 1 + ], + "cmd-3": [ + "workspace::ActivatePane", + 2 + ], + "cmd-4": [ + "workspace::ActivatePane", + 3 + ], + "cmd-5": [ + "workspace::ActivatePane", + 4 + ], + "cmd-6": [ + "workspace::ActivatePane", + 5 + ], + "cmd-7": [ + "workspace::ActivatePane", + 6 + ], + "cmd-8": [ + "workspace::ActivatePane", + 7 + ], + "cmd-9": [ + "workspace::ActivatePane", + 8 + ], + "cmd-b": "workspace::ToggleLeftDock", + "cmd-shift-f": "workspace::NewSearch", + "cmd-k cmd-t": "theme_selector::Toggle", + "cmd-k cmd-s": "zed::OpenKeymap", + "cmd-t": "project_symbols::Toggle", + "cmd-p": "file_finder::Toggle", + "cmd-shift-p": "command_palette::Toggle", + "cmd-shift-m": "diagnostics::Deploy", + "cmd-shift-e": "project_panel::ToggleFocus", + "cmd-alt-s": "workspace::SaveAll", + "cmd-k m": "language_selector::Toggle" + } + }, + // Bindings from Sublime Text + { + "context": "Editor", + "bindings": { + "ctrl-shift-k": "editor::DeleteLine", + "cmd-shift-d": "editor::DuplicateLine", + "cmd-shift-l": "editor::SplitSelectionIntoLines", + "ctrl-cmd-up": "editor::MoveLineUp", + "ctrl-cmd-down": "editor::MoveLineDown", + "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart", + "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", + "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", + "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", + "ctrl-alt-left": "editor::MoveToPreviousSubwordStart", + "ctrl-alt-b": "editor::MoveToPreviousSubwordStart", + "ctrl-alt-right": "editor::MoveToNextSubwordEnd", + "ctrl-alt-f": "editor::MoveToNextSubwordEnd", + "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart", + "ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart", + "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd", + "ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd" + } + }, + { + "bindings": { + "cmd-k cmd-left": "workspace::ActivatePreviousPane", + "cmd-k cmd-right": "workspace::ActivateNextPane" + } + }, + // Bindings from Atom + { + "context": "Pane", + "bindings": { + "cmd-k up": "pane::SplitUp", + "cmd-k down": "pane::SplitDown", + "cmd-k left": "pane::SplitLeft", + "cmd-k right": "pane::SplitRight" + } + }, + // Bindings that should be unified with bindings for more general actions + { + "context": "Editor && renaming", + "bindings": { + "enter": "editor::ConfirmRename" + } + }, + { + "context": "Editor && showing_completions", + "bindings": { + "enter": "editor::ConfirmCompletion", + "tab": "editor::ConfirmCompletion" + } + }, + { + "context": "Editor && showing_code_actions", + "bindings": { + "enter": "editor::ConfirmCodeAction" + } + }, + // Custom bindings + { + "bindings": { + "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator", + "cmd-shift-c": "collab::ToggleContactsMenu", + "cmd-alt-i": "zed::DebugElements" + } + }, + { + "context": "Editor", + "bindings": { + "alt-enter": "editor::OpenExcerpts", + "cmd-f8": "editor::GoToHunk", + "cmd-shift-f8": "editor::GoToPrevHunk" + } + }, + { + "context": "ProjectSearchBar", + "bindings": { + "cmd-enter": "project_search::SearchInNew" + } + }, + { + "context": "ProjectPanel", + "bindings": { + "left": "project_panel::CollapseSelectedEntry", + "right": "project_panel::ExpandSelectedEntry", + "cmd-x": "project_panel::Cut", + "cmd-c": "project_panel::Copy", + "cmd-v": "project_panel::Paste", + "cmd-alt-c": "project_panel::CopyPath", + "alt-cmd-shift-c": "project_panel::CopyRelativePath", + "f2": "project_panel::Rename", + "backspace": "project_panel::Delete", + "alt-cmd-r": "project_panel::RevealInFinder" + } + }, + { + "context": "Terminal", + "bindings": { + "ctrl-cmd-space": "terminal::ShowCharacterPalette", + "cmd-c": "terminal::Copy", + "cmd-v": "terminal::Paste", + "cmd-k": "terminal::Clear", + // Some nice conveniences + "cmd-backspace": [ + "terminal::SendText", + "\u0015" + ], + "cmd-right": [ + "terminal::SendText", + "\u0005" + ], + "cmd-left": [ + "terminal::SendText", + "\u0001" + ], + // Terminal.app compatability + "alt-left": [ + "terminal::SendText", + "\u001bb" + ], + "alt-right": [ + "terminal::SendText", + "\u001bf" + ], + // There are conflicting bindings for these keys in the global context. + // these bindings override them, remove at your own risk: + "up": [ + "terminal::SendKeystroke", + "up" + ], + "pageup": [ + "terminal::SendKeystroke", + "pageup" + ], + "down": [ + "terminal::SendKeystroke", + "down" + ], + "pagedown": [ + "terminal::SendKeystroke", + "pagedown" + ], + "escape": [ + "terminal::SendKeystroke", + "escape" + ], + "enter": [ + "terminal::SendKeystroke", + "enter" + ], + "ctrl-c": [ + "terminal::SendKeystroke", + "ctrl-c" + ] + } + } ] From 7190840081212e1013dad588dbb3af913c89b304 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 22 May 2023 09:12:36 -0400 Subject: [PATCH 075/131] Add paragraph based vertical movements --- assets/keymaps/default.json | 4 ++ crates/editor/src/editor.rs | 82 +++++++++++++++++++++++++++++++++++ crates/editor/src/movement.rs | 38 ++++++++++++++++ 3 files changed, 124 insertions(+) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 01a09e0cba..cab4333f74 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -67,10 +67,12 @@ "cmd-z": "editor::Undo", "cmd-shift-z": "editor::Redo", "up": "editor::MoveUp", + "ctrl-up": "editor::MoveToStartOfParagraph", "pageup": "editor::PageUp", "shift-pageup": "editor::MovePageUp", "home": "editor::MoveToBeginningOfLine", "down": "editor::MoveDown", + "ctrl-down": "editor::MoveToEndOfParagraph", "pagedown": "editor::PageDown", "shift-pagedown": "editor::MovePageDown", "end": "editor::MoveToEndOfLine", @@ -103,6 +105,8 @@ "alt-shift-b": "editor::SelectToPreviousWordStart", "alt-shift-right": "editor::SelectToNextWordEnd", "alt-shift-f": "editor::SelectToNextWordEnd", + "ctrl-shift-up": "editor::SelectToStartOfParagraph", + "ctrl-shift-down": "editor::SelectToEndOfParagraph", "cmd-shift-up": "editor::SelectToBeginning", "cmd-shift-down": "editor::SelectToEnd", "cmd-a": "editor::SelectAll", diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 7207e3c91c..cf614fccf7 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -216,6 +216,8 @@ actions!( MoveToNextSubwordEnd, MoveToBeginningOfLine, MoveToEndOfLine, + MoveToStartOfParagraph, + MoveToEndOfParagraph, MoveToBeginning, MoveToEnd, SelectUp, @@ -226,6 +228,8 @@ actions!( SelectToPreviousSubwordStart, SelectToNextWordEnd, SelectToNextSubwordEnd, + SelectToStartOfParagraph, + SelectToEndOfParagraph, SelectToBeginning, SelectToEnd, SelectAll, @@ -337,6 +341,8 @@ pub fn init(cx: &mut AppContext) { cx.add_action(Editor::move_to_next_subword_end); cx.add_action(Editor::move_to_beginning_of_line); cx.add_action(Editor::move_to_end_of_line); + cx.add_action(Editor::move_to_start_of_paragraph); + cx.add_action(Editor::move_to_end_of_paragraph); cx.add_action(Editor::move_to_beginning); cx.add_action(Editor::move_to_end); cx.add_action(Editor::select_up); @@ -349,6 +355,8 @@ pub fn init(cx: &mut AppContext) { cx.add_action(Editor::select_to_next_subword_end); cx.add_action(Editor::select_to_beginning_of_line); cx.add_action(Editor::select_to_end_of_line); + cx.add_action(Editor::select_to_start_of_paragraph); + cx.add_action(Editor::select_to_end_of_paragraph); cx.add_action(Editor::select_to_beginning); cx.add_action(Editor::select_to_end); cx.add_action(Editor::select_all); @@ -4762,6 +4770,80 @@ impl Editor { }); } + pub fn move_to_start_of_paragraph( + &mut self, + _: &MoveToStartOfParagraph, + cx: &mut ViewContext, + ) { + if matches!(self.mode, EditorMode::SingleLine) { + cx.propagate_action(); + return; + } + + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_with(|map, selection| { + selection.collapse_to( + movement::start_of_paragraph(map, selection.head()), + SelectionGoal::None, + ) + }); + }) + } + + pub fn move_to_end_of_paragraph( + &mut self, + _: &MoveToEndOfParagraph, + cx: &mut ViewContext, + ) { + if matches!(self.mode, EditorMode::SingleLine) { + cx.propagate_action(); + return; + } + + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_with(|map, selection| { + selection.collapse_to( + movement::end_of_paragraph(map, selection.head()), + SelectionGoal::None, + ) + }); + }) + } + + pub fn select_to_start_of_paragraph( + &mut self, + _: &SelectToStartOfParagraph, + cx: &mut ViewContext, + ) { + if matches!(self.mode, EditorMode::SingleLine) { + cx.propagate_action(); + return; + } + + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_heads_with(|map, head, _| { + (movement::start_of_paragraph(map, head), SelectionGoal::None) + }); + }) + } + + pub fn select_to_end_of_paragraph( + &mut self, + _: &SelectToEndOfParagraph, + cx: &mut ViewContext, + ) { + if matches!(self.mode, EditorMode::SingleLine) { + cx.propagate_action(); + return; + } + + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_heads_with(|map, head, _| { + (movement::end_of_paragraph(map, head), SelectionGoal::None) + }); + }) + } + pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext) { if matches!(self.mode, EditorMode::SingleLine) { cx.propagate_action(); diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 6c9bd6cb4f..523a0af964 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -193,6 +193,44 @@ pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPo }) } +pub fn start_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint { + let point = display_point.to_point(map); + if point.row == 0 { + return map.max_point(); + } + + let mut found_non_blank_line = false; + for row in (0..point.row + 1).rev() { + let blank = map.buffer_snapshot.is_line_blank(row); + if found_non_blank_line && blank { + return Point::new(row, 0).to_display_point(map); + } + + found_non_blank_line |= !blank; + } + + DisplayPoint::zero() +} + +pub fn end_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint { + let point = display_point.to_point(map); + if point.row == map.max_buffer_row() { + return DisplayPoint::zero(); + } + + let mut found_non_blank_line = false; + for row in point.row..map.max_buffer_row() + 1 { + let blank = map.buffer_snapshot.is_line_blank(row); + if found_non_blank_line && blank { + return Point::new(row, 0).to_display_point(map); + } + + found_non_blank_line |= !blank; + } + + map.max_point() +} + /// Scans for a boundary preceding the given start point `from` until a boundary is found, indicated by the /// given predicate returning true. The predicate is called with the character to the left and right /// of the candidate boundary location, and will be called with `\n` characters indicating the start From 10e947cb5f8948817036d6302c32dd9a0a043d41 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 15:55:44 +0200 Subject: [PATCH 076/131] Persist project and terminal panel sizes --- Cargo.lock | 1 + crates/project_panel/Cargo.toml | 2 + crates/project_panel/src/project_panel.rs | 77 ++++++++++++++++++++-- crates/terminal/src/terminal.rs | 1 + crates/terminal_view/src/terminal_panel.rs | 36 ++++++++-- crates/workspace/src/dock.rs | 47 +++++++------ crates/workspace/src/workspace.rs | 20 ++---- crates/zed/src/zed.rs | 9 +-- 8 files changed, 139 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2aea69e90b..b8b57e8b8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4888,6 +4888,7 @@ version = "0.1.0" dependencies = [ "client", "context_menu", + "db", "drag_and_drop", "editor", "futures 0.3.28", diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml index 8cd5a06675..aab5b8a41c 100644 --- a/crates/project_panel/Cargo.toml +++ b/crates/project_panel/Cargo.toml @@ -10,6 +10,7 @@ doctest = false [dependencies] context_menu = { path = "../context_menu" } +db = { path = "../db" } drag_and_drop = { path = "../drag_and_drop" } editor = { path = "../editor" } gpui = { path = "../gpui" } @@ -23,6 +24,7 @@ postage.workspace = true futures.workspace = true schemars.workspace = true serde.workspace = true +serde_json.workspace = true unicase = "2.6" [dev-dependencies] diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 761d803084..010a305f16 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1,10 +1,11 @@ use context_menu::{ContextMenu, ContextMenuItem}; +use db::kvp::KEY_VALUE_STORE; use drag_and_drop::{DragAndDrop, Draggable}; use editor::{Cancel, Editor}; use futures::stream::StreamExt; use gpui::{ actions, - anyhow::{anyhow, Result}, + anyhow::{self, anyhow, Result}, elements::{ AnchorCorner, ChildView, ComponentHost, ContainerStyle, Empty, Flex, MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, @@ -12,8 +13,8 @@ use gpui::{ geometry::vector::Vector2F, keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, - AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext, - ViewHandle, WeakViewHandle, WindowContext, + AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, ModelHandle, Task, + View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{ @@ -33,11 +34,13 @@ use std::{ }; use theme::{ui::FileName, ProjectPanelEntry}; use unicase::UniCase; +use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel}, Workspace, }; +const PROJECT_PANEL_KEY: &'static str = "ProjectPanel"; const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX; #[derive(Deserialize)] @@ -67,6 +70,7 @@ pub struct ProjectPanelSettingsContent { } #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] pub enum ProjectPanelDockPosition { Left, Right, @@ -87,6 +91,8 @@ pub struct ProjectPanel { dragged_entry_destination: Option>, workspace: WeakViewHandle, has_focus: bool, + width: Option, + pending_serialization: Task>, } #[derive(Copy, Clone)] @@ -183,8 +189,13 @@ pub enum Event { Focus, } +#[derive(Serialize, Deserialize)] +struct SerializedProjectPanel { + width: Option, +} + impl ProjectPanel { - pub fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> ViewHandle { + fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> ViewHandle { let project = workspace.project().clone(); let project_panel = cx.add_view(|cx: &mut ViewContext| { cx.observe(&project, |this, _, cx| { @@ -258,6 +269,8 @@ impl ProjectPanel { dragged_entry_destination: None, workspace: workspace.weak_handle(), has_focus: false, + width: None, + pending_serialization: Task::ready(None), }; this.update_visible_entries(None, cx); @@ -311,6 +324,51 @@ impl ProjectPanel { project_panel } + pub fn load( + workspace: WeakViewHandle, + cx: AsyncAppContext, + ) -> Task>> { + cx.spawn(|mut cx| async move { + let serialized_panel = if let Some(panel) = cx + .background() + .spawn(async move { KEY_VALUE_STORE.read_kvp(PROJECT_PANEL_KEY) }) + .await + .log_err() + .flatten() + { + Some(serde_json::from_str::(&panel)?) + } else { + None + }; + workspace.update(&mut cx, |workspace, cx| { + let panel = ProjectPanel::new(workspace, cx); + if let Some(serialized_panel) = serialized_panel { + panel.update(cx, |panel, cx| { + panel.width = serialized_panel.width; + cx.notify(); + }); + } + panel + }) + }) + } + + fn serialize(&mut self, cx: &mut ViewContext) { + let width = self.width; + self.pending_serialization = cx.background().spawn( + async move { + KEY_VALUE_STORE + .write_kvp( + PROJECT_PANEL_KEY.into(), + serde_json::to_string(&SerializedProjectPanel { width })?, + ) + .await?; + anyhow::Ok(()) + } + .log_err(), + ); + } + fn deploy_context_menu( &mut self, position: Vector2F, @@ -1435,8 +1493,15 @@ impl workspace::dock::Panel for ProjectPanel { ); } - fn default_size(&self, cx: &WindowContext) -> f32 { - settings::get::(cx).default_width + fn size(&self, cx: &WindowContext) -> f32 { + self.width + .unwrap_or_else(|| settings::get::(cx).default_width) + } + + fn set_size(&mut self, size: f32, cx: &mut ViewContext) { + self.width = Some(size); + self.serialize(cx); + cx.notify(); } fn should_zoom_in_on_event(_: &Self::Event) -> bool { diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 9cf3f52924..576719526d 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -120,6 +120,7 @@ pub fn init(cx: &mut AppContext) { } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] pub enum TerminalDockPosition { Left, Bottom, diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index afd564fb56..9c8073eada 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -37,12 +37,14 @@ pub struct TerminalPanel { pane: ViewHandle, fs: Arc, workspace: WeakViewHandle, + width: Option, + height: Option, pending_serialization: Task>, _subscriptions: Vec, } impl TerminalPanel { - pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { let weak_self = cx.weak_handle(); let pane = cx.add_view(|cx| { let window_id = cx.window_id(); @@ -90,6 +92,8 @@ impl TerminalPanel { fs: workspace.app_state().fs.clone(), workspace: workspace.weak_handle(), pending_serialization: Task::ready(None), + width: None, + height: None, _subscriptions: subscriptions, }; let mut old_dock_position = this.position(cx); @@ -112,7 +116,9 @@ impl TerminalPanel { let serialized_panel = if let Some(panel) = cx .background() .spawn(async move { KEY_VALUE_STORE.read_kvp(TERMINAL_PANEL_KEY) }) - .await? + .await + .log_err() + .flatten() { Some(serde_json::from_str::(&panel)?) } else { @@ -122,6 +128,9 @@ impl TerminalPanel { let panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); let items = if let Some(serialized_panel) = serialized_panel.as_ref() { panel.update(cx, |panel, cx| { + cx.notify(); + panel.height = serialized_panel.height; + panel.width = serialized_panel.width; panel.pane.update(cx, |_, cx| { serialized_panel .items @@ -226,6 +235,8 @@ impl TerminalPanel { .map(|item| item.id()) .collect::>(); let active_item_id = self.pane.read(cx).active_item().map(|item| item.id()); + let height = self.height; + let width = self.width; self.pending_serialization = cx.background().spawn( async move { KEY_VALUE_STORE @@ -234,6 +245,8 @@ impl TerminalPanel { serde_json::to_string(&SerializedTerminalPanel { items, active_item_id, + height, + width, })?, ) .await?; @@ -288,14 +301,25 @@ impl Panel for TerminalPanel { }); } - fn default_size(&self, cx: &WindowContext) -> f32 { + fn size(&self, cx: &WindowContext) -> f32 { let settings = settings::get::(cx); match self.position(cx) { - DockPosition::Left | DockPosition::Right => settings.default_width, - DockPosition::Bottom => settings.default_height, + DockPosition::Left | DockPosition::Right => { + self.width.unwrap_or_else(|| settings.default_width) + } + DockPosition::Bottom => self.height.unwrap_or_else(|| settings.default_height), } } + fn set_size(&mut self, size: f32, cx: &mut ViewContext) { + match self.position(cx) { + DockPosition::Left | DockPosition::Right => self.width = Some(size), + DockPosition::Bottom => self.height = Some(size), + } + self.serialize(cx); + cx.notify(); + } + fn should_zoom_in_on_event(event: &Event) -> bool { matches!(event, Event::ZoomIn) } @@ -360,4 +384,6 @@ impl Panel for TerminalPanel { struct SerializedTerminalPanel { items: Vec, active_item_id: Option, + width: Option, + height: Option, } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index c327c4ded8..c4693b3403 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -13,7 +13,8 @@ pub trait Panel: View { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); - fn default_size(&self, cx: &WindowContext) -> f32; + fn size(&self, cx: &WindowContext) -> f32; + fn set_size(&mut self, size: f32, cx: &mut ViewContext); fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; fn icon_label(&self, _: &WindowContext) -> Option { @@ -39,7 +40,8 @@ pub trait PanelHandle { fn is_zoomed(&self, cx: &WindowContext) -> bool; fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext); fn set_active(&self, active: bool, cx: &mut WindowContext); - fn default_size(&self, cx: &WindowContext) -> f32; + fn size(&self, cx: &WindowContext) -> f32; + fn set_size(&self, size: f32, cx: &mut WindowContext); fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -67,8 +69,12 @@ where self.update(cx, |this, cx| this.set_position(position, cx)) } - fn default_size(&self, cx: &WindowContext) -> f32 { - self.read(cx).default_size(cx) + fn size(&self, cx: &WindowContext) -> f32 { + self.read(cx).size(cx) + } + + fn set_size(&self, size: f32, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.set_size(size, cx)) } fn is_zoomed(&self, cx: &WindowContext) -> bool { @@ -151,7 +157,6 @@ impl DockPosition { struct PanelEntry { panel: Rc, - size: f32, context_menu: ViewHandle, _subscriptions: [Subscription; 2], } @@ -271,10 +276,8 @@ impl Dock { ]; let dock_view_id = cx.view_id(); - let size = panel.default_size(cx); self.panel_entries.push(PanelEntry { panel: Rc::new(panel), - size, context_menu: cx.add_view(|cx| { let mut menu = ContextMenu::new(dock_view_id, cx); menu.set_position_mode(OverlayPositionMode::Local); @@ -343,28 +346,18 @@ impl Dock { } } - pub fn panel_size(&self, panel: &dyn PanelHandle) -> Option { + pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option { self.panel_entries .iter() .find(|entry| entry.panel.id() == panel.id()) - .map(|entry| entry.size) + .map(|entry| entry.panel.size(cx)) } - pub fn resize_panel(&mut self, panel: &dyn PanelHandle, size: f32) { - let entry = self - .panel_entries - .iter_mut() - .find(|entry| entry.panel.id() == panel.id()); - if let Some(entry) = entry { - entry.size = size; - } - } - - pub fn active_panel_size(&self) -> Option { + pub fn active_panel_size(&self, cx: &WindowContext) -> Option { if self.is_open { self.panel_entries .get(self.active_panel_index) - .map(|entry| entry.size) + .map(|entry| entry.panel.size(cx)) } else { None } @@ -372,7 +365,7 @@ impl Dock { pub fn resize_active_panel(&mut self, size: f32, cx: &mut ViewContext) { if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) { - entry.size = size; + entry.panel.set_size(size, cx); cx.notify(); } } @@ -386,7 +379,7 @@ impl Dock { .with_style(style.container) .resizable( self.position.to_resize_handle_side(), - active_entry.size, + active_entry.panel.size(cx), |_, _, _| {}, ) .into_any() @@ -413,7 +406,7 @@ impl View for Dock { .with_style(style.container) .resizable( self.position.to_resize_handle_side(), - active_entry.size, + active_entry.panel.size(cx), |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), ) .into_any() @@ -630,13 +623,17 @@ pub(crate) mod test { unimplemented!() } - fn default_size(&self, _: &WindowContext) -> f32 { + fn size(&self, _: &WindowContext) -> f32 { match self.position.axis() { Axis::Horizontal => 300., Axis::Vertical => 200., } } + fn set_size(&mut self, _: f32, _: &mut ViewContext) { + unimplemented!() + } + fn icon_path(&self) -> &'static str { "icons/test_panel.svg" } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 78900eb002..6c7d61fef9 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -853,11 +853,7 @@ impl Workspace { if T::should_change_position_on_event(event) { let new_position = panel.read(cx).position(cx); let mut was_visible = false; - let mut size = None; dock.update(cx, |dock, cx| { - if new_position.axis() == prev_position.axis() { - size = dock.panel_size(&panel); - } prev_position = new_position; was_visible = dock.is_open() @@ -874,10 +870,6 @@ impl Workspace { .clone(); dock.update(cx, |dock, cx| { dock.add_panel(panel.clone(), cx); - if let Some(size) = size { - dock.resize_panel(&panel, size); - } - if was_visible { dock.set_open(true, cx); dock.activate_panel(dock.panels_len() - 1, cx); @@ -3961,8 +3953,8 @@ mod tests { panel_1.id() ); assert_eq!( - left_dock.read(cx).active_panel_size().unwrap(), - panel_1.default_size(cx) + left_dock.read(cx).active_panel_size(cx).unwrap(), + panel_1.size(cx) ); left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx)); @@ -3989,7 +3981,7 @@ mod tests { right_dock.read(cx).active_panel().unwrap().id(), panel_1.id() ); - assert_eq!(right_dock.read(cx).active_panel_size().unwrap(), 1337.); + assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); // Now we move panel_2 to the left panel_2.set_position(DockPosition::Left, cx); @@ -4019,7 +4011,7 @@ mod tests { left_dock.read(cx).active_panel().unwrap().id(), panel_1.id() ); - assert_eq!(left_dock.read(cx).active_panel_size().unwrap(), 1337.); + assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); // And right the dock should be closed as it no longer has any panels. assert!(!workspace.right_dock().read(cx).is_open()); @@ -4034,8 +4026,8 @@ mod tests { // since the panel orientation changed from vertical to horizontal. let bottom_dock = workspace.bottom_dock(); assert_eq!( - bottom_dock.read(cx).active_panel_size().unwrap(), - panel_1.default_size(cx), + bottom_dock.read(cx).active_panel_size(cx).unwrap(), + panel_1.size(cx), ); // Close bottom dock and move panel_1 back to the left. bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 0c4b63081c..be0c566bb1 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -335,8 +335,12 @@ pub fn initialize_workspace( } false }); + })?; - let project_panel = ProjectPanel::new(workspace, cx); + let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone()); + let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone()); + let (project_panel, terminal_panel) = futures::try_join!(project_panel, terminal_panel)?; + workspace_handle.update(&mut cx, |workspace, cx| { let project_panel_position = project_panel.position(cx); workspace.add_panel(project_panel, cx); if !was_deserialized @@ -352,10 +356,7 @@ pub fn initialize_workspace( { workspace.toggle_dock(project_panel_position, cx); } - })?; - let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone()).await?; - workspace_handle.update(&mut cx, |workspace, cx| { workspace.add_panel(terminal_panel, cx) })?; Ok(()) From 185a624b99a2cd5cd73bab32d2a43973b8c21339 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 16:18:53 +0200 Subject: [PATCH 077/131] Fix some project panel tests --- crates/project_panel/src/project_panel.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 010a305f16..9159e8fa98 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -2243,6 +2243,7 @@ mod tests { theme::init((), cx); language::init(cx); editor::init_settings(cx); + crate::init(cx); workspace::init_settings(cx); }); } @@ -2255,6 +2256,7 @@ mod tests { language::init(cx); editor::init(cx); pane::init(cx); + crate::init(cx); workspace::init(app_state.clone(), cx); }); } From 44903bc193fc0b8ef04990aa3fd9073117fe9514 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 22 May 2023 11:48:07 -0400 Subject: [PATCH 078/131] Add test for paragraph vertical movements --- crates/editor/src/editor_tests.rs | 112 ++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 9a21429301..180de155e9 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -1243,6 +1243,118 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { }); } +#[gpui::test] +async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); + + let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache())); + cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height)); + + cx.set_state( + &r#"ˇone + two + + three + fourˇ + five + + six"# + .unindent(), + ); + + cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx)); + cx.assert_editor_state( + &r#"one + two + ˇ + three + four + five + ˇ + six"# + .unindent(), + ); + + cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx)); + cx.assert_editor_state( + &r#"one + two + + three + four + five + ˇ + sixˇ"# + .unindent(), + ); + + cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx)); + cx.assert_editor_state( + &r#"ˇone + two + + three + four + five + + sixˇ"# + .unindent(), + ); + + cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx)); + cx.assert_editor_state( + &r#"ˇone + two + ˇ + three + four + five + + six"# + .unindent(), + ); + + cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx)); + cx.assert_editor_state( + &r#"ˇone + two + + three + four + five + + sixˇ"# + .unindent(), + ); + + cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx)); + cx.assert_editor_state( + &r#"one + two + + three + four + five + ˇ + sixˇ"# + .unindent(), + ); + + cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx)); + cx.assert_editor_state( + &r#"one + two + ˇ + three + four + five + ˇ + six"# + .unindent(), + ); +} + #[gpui::test] async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); From 21ada545b07030398a90bf1ef1dbe386cb6cd26c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 22 May 2023 08:48:37 -0700 Subject: [PATCH 079/131] Remove assertions about behavior on invalid settings file --- crates/settings/src/settings_store.rs | 31 --------------------------- 1 file changed, 31 deletions(-) diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 329b290a31..71b3cc635f 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -830,37 +830,6 @@ mod tests { store.register_setting::(cx); store.register_setting::(cx); store.register_setting::(cx); - - // error - missing required field in default settings - store - .set_default_settings( - r#"{ - "user": { - "name": "John Doe", - "age": 30, - "staff": false - } - }"#, - cx, - ) - .unwrap_err(); - - // error - type error in default settings - store - .set_default_settings( - r#"{ - "turbo": "the-wrong-type", - "user": { - "name": "John Doe", - "age": 30, - "staff": false - } - }"#, - cx, - ) - .unwrap_err(); - - // valid default settings. store .set_default_settings( r#"{ From 4aa2858b2b69e22b9769df48db5bec1427fade58 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 18:05:08 +0200 Subject: [PATCH 080/131] Transfer focus to root view only if previously-focused view was dropped --- crates/gpui/src/app.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index bc89503459..0fafe76942 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1749,11 +1749,11 @@ impl AppContext { } } - // When the previously-focused view isn't rendered and + // When the previously-focused view has been dropped and // there isn't any pending focus, focus the root view. let root_view_id = cx.window.root_view().id(); if focused_view_id != root_view_id - && !cx.window.parents.contains_key(&focused_view_id) + && !cx.views.contains_key(&(window_id, focused_view_id)) && !focus_effects.contains_key(&window_id) { focus_effects.insert( @@ -1942,12 +1942,12 @@ impl AppContext { fn handle_focus_effect(&mut self, effect: FocusEffect) { let window_id = effect.window_id(); self.update_window(window_id, |cx| { - // Ensure the newly-focused view has been rendered, otherwise focus + // Ensure the newly-focused view still exists, otherwise focus // the root view instead. let focused_id = match effect { FocusEffect::View { view_id, .. } => { if let Some(view_id) = view_id { - if cx.window.parents.contains_key(&view_id) { + if cx.views.contains_key(&(window_id, view_id)) { Some(view_id) } else { Some(cx.root_view().id()) From b80cb737452fe5fd84d4d295c1cfcb826496070f Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Mon, 22 May 2023 12:09:54 -0400 Subject: [PATCH 081/131] Add git to project panel in theme, use different values for dark and light --- styles/src/styleTree/editor.ts | 15 +++++++++++---- styles/src/styleTree/projectPanel.ts | 13 ++++++++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index 7caa8b1c67..2443717e4d 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -3,9 +3,10 @@ import { ColorScheme, Layer, StyleSets } from "../themes/common/colorScheme" import { background, border, borderColor, foreground, text } from "./components" import hoverPopover from "./hoverPopover" -import { SyntaxHighlightStyle, buildSyntax } from "../themes/common/syntax" +import { buildSyntax } from "../themes/common/syntax" export default function editor(colorScheme: ColorScheme) { + const { isLight } = colorScheme let layer = colorScheme.highest const autocompleteItem = { @@ -97,9 +98,15 @@ export default function editor(colorScheme: ColorScheme) { foldBackground: foreground(layer, "variant"), }, diff: { - deleted: foreground(layer, "negative"), - modified: foreground(layer, "warning"), - inserted: foreground(layer, "positive"), + deleted: isLight + ? colorScheme.ramps.red(0.5).hex() + : colorScheme.ramps.red(0.4).hex(), + modified: isLight + ? colorScheme.ramps.yellow(0.3).hex() + : colorScheme.ramps.yellow(0.5).hex(), + inserted: isLight + ? colorScheme.ramps.green(0.4).hex() + : colorScheme.ramps.green(0.5).hex(), removedWidthEm: 0.275, widthEm: 0.22, cornerRadius: 0.2, diff --git a/styles/src/styleTree/projectPanel.ts b/styles/src/styleTree/projectPanel.ts index 3d06a683ab..0c32ee7132 100644 --- a/styles/src/styleTree/projectPanel.ts +++ b/styles/src/styleTree/projectPanel.ts @@ -3,6 +3,7 @@ import { withOpacity } from "../utils/color" import { background, border, foreground, text } from "./components" export default function projectPanel(colorScheme: ColorScheme) { + const { isLight } = colorScheme let layer = colorScheme.middle let baseEntry = { @@ -28,6 +29,16 @@ export default function projectPanel(colorScheme: ColorScheme) { background: background(layer, "active"), text: text(layer, "mono", "active", { size: "sm" }), }, + status: { + git: { + modified: isLight + ? colorScheme.ramps.yellow(0.6).hex() + : colorScheme.ramps.yellow(0.5).hex(), + inserted: isLight + ? colorScheme.ramps.green(0.4).hex() + : colorScheme.ramps.green(0.5).hex(), + } + } } return { @@ -79,6 +90,6 @@ export default function projectPanel(colorScheme: ColorScheme) { background: background(layer, "on"), text: text(layer, "mono", "on", { size: "sm" }), selection: colorScheme.players[0], - }, + } } } From deaf60005c258d282159b263a34a07c85ddde528 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 18:21:12 +0200 Subject: [PATCH 082/131] Fix more tests --- crates/zed/src/zed.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index be0c566bb1..4c4137b7f3 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1396,7 +1396,7 @@ mod tests { cx.foreground().run_until_parked(); workspace.read_with(cx, |workspace, _| { - assert_eq!(workspace.panes().len(), 2); //Center pane + Dock pane + assert_eq!(workspace.panes().len(), 1); assert_eq!(workspace.active_pane(), &pane_1); }); @@ -1406,7 +1406,7 @@ mod tests { cx.foreground().run_until_parked(); workspace.read_with(cx, |workspace, cx| { - assert_eq!(workspace.panes().len(), 2); + assert_eq!(workspace.panes().len(), 1); assert!(workspace.active_item(cx).is_none()); }); @@ -2088,6 +2088,8 @@ mod tests { language::init(cx); editor::init(cx); pane::init(cx); + project_panel::init(cx); + terminal_view::init(cx); app_state }) } From 5b7e852903c3b1e9b4c42505ef44f9d47187e16a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 18:28:30 +0200 Subject: [PATCH 083/131] Await closing of items in tests --- crates/workspace/src/pane.rs | 59 ++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 5f698d89a5..aae900c861 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2343,23 +2343,31 @@ mod tests { add_labeled_item(&workspace, &pane, "1", false, cx); assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "B*", "C", "D"], cx); pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx)); assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "B*", "C"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "C*"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A*"], cx); } @@ -2376,9 +2384,10 @@ mod tests { pane.update(cx, |pane, cx| { pane.close_inactive_items(&CloseInactiveItems, cx) - }); - - deterministic.run_until_parked(); + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["C*"], cx); } @@ -2398,9 +2407,10 @@ mod tests { add_labeled_item(&workspace, &pane, "E", false, cx); assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx); - pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)); - - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A^", "C*^"], cx); } @@ -2420,9 +2430,10 @@ mod tests { pane.update(cx, |pane, cx| { pane.close_items_to_the_left(&CloseItemsToTheLeft, cx) - }); - - deterministic.run_until_parked(); + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["C*", "D", "E"], cx); } @@ -2442,9 +2453,10 @@ mod tests { pane.update(cx, |pane, cx| { pane.close_items_to_the_right(&CloseItemsToTheRight, cx) - }); - - deterministic.run_until_parked(); + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "B", "C*"], cx); } @@ -2462,9 +2474,10 @@ mod tests { add_labeled_item(&workspace, &pane, "C", false, cx); assert_item_labels(&pane, ["A", "B", "C*"], cx); - pane.update(cx, |pane, cx| pane.close_all_items(&CloseAllItems, cx)); - - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_all_items(&CloseAllItems, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, [], cx); } From 75a0742c90028ae3e0128714ecea27b658a37280 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 18:32:28 +0200 Subject: [PATCH 084/131] Uncomment persistence test --- crates/workspace/src/persistence.rs | 37 ++++++++++++----------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index fbefd3984e..76b66c5329 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -579,7 +579,7 @@ mod tests { docks: Default::default(), }; - let mut _workspace_2 = SerializedWorkspace { + let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), center_group: Default::default(), @@ -597,7 +597,7 @@ mod tests { }) .await; - db.save_workspace(_workspace_2.clone()).await; + db.save_workspace(workspace_2.clone()).await; db.write(|conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) @@ -609,28 +609,21 @@ mod tests { workspace_1.location = (["/tmp", "/tmp3"]).into(); db.save_workspace(workspace_1.clone()).await; db.save_workspace(workspace_1).await; + db.save_workspace(workspace_2).await; - todo!(); - // workspace_2.dock_pane.children.push(SerializedItem { - // kind: Arc::from("Test"), - // item_id: 10, - // active: true, - // }); - // db.save_workspace(workspace_2).await; + let test_text_2 = db + .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) + .unwrap()(2) + .unwrap() + .unwrap(); + assert_eq!(test_text_2, "test-text-2"); - // let test_text_2 = db - // .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) - // .unwrap()(2) - // .unwrap() - // .unwrap(); - // assert_eq!(test_text_2, "test-text-2"); - - // let test_text_1 = db - // .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) - // .unwrap()(1) - // .unwrap() - // .unwrap(); - // assert_eq!(test_text_1, "test-text-1"); + let test_text_1 = db + .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) + .unwrap()(1) + .unwrap() + .unwrap(); + assert_eq!(test_text_1, "test-text-1"); } #[gpui::test] From 2200a22c07c4939e1f9f3370442e25531521335c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 09:55:59 -0700 Subject: [PATCH 085/131] Wire project panel themes into rust --- crates/project_panel/src/project_panel.rs | 33 ++++++++------ crates/theme/src/theme.rs | 12 +++++ crates/theme/src/ui.rs | 55 +---------------------- styles/src/styleTree/projectPanel.ts | 23 +++++----- 4 files changed, 47 insertions(+), 76 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index ce0dd9e222..dd28bdd73e 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -6,8 +6,8 @@ use gpui::{ actions, anyhow::{anyhow, Result}, elements::{ - AnchorCorner, ChildView, ComponentHost, ContainerStyle, Empty, Flex, MouseEventHandler, - ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, + AnchorCorner, ChildView, ContainerStyle, Empty, Flex, Label, + MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, }, geometry::vector::Vector2F, keymap_matcher::KeymapContext, @@ -28,7 +28,7 @@ use std::{ path::Path, sync::Arc, }; -use theme::{ui::FileName, ProjectPanelEntry}; +use theme::ProjectPanelEntry; use unicase::UniCase; use workspace::Workspace; @@ -1079,6 +1079,17 @@ impl ProjectPanel { let kind = details.kind; let show_editor = details.is_editing && !details.is_processing; + let mut filename_text_style = style.text.clone(); + filename_text_style.color = details + .git_status + .as_ref() + .map(|status| match status { + GitFileStatus::Added => style.status.git.inserted, + GitFileStatus::Modified => style.status.git.modified, + GitFileStatus::Conflict => style.text.color, + }) + .unwrap_or(style.text.color); + Flex::row() .with_child( if kind == EntryKind::Dir { @@ -1106,16 +1117,12 @@ impl ProjectPanel { .flex(1.0, true) .into_any() } else { - ComponentHost::new(FileName::new( - details.filename.clone(), - details.git_status, - FileName::style(style.text.clone(), &theme::current(cx)), - )) - .contained() - .with_margin_left(style.icon_spacing) - .aligned() - .left() - .into_any() + Label::new(details.filename.clone(), filename_text_style) + .contained() + .with_margin_left(style.icon_spacing) + .aligned() + .left() + .into_any() }) .constrained() .with_height(style.height) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index eb404cdaad..5a79946589 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -446,6 +446,18 @@ pub struct ProjectPanelEntry { pub icon_color: Color, pub icon_size: f32, pub icon_spacing: f32, + pub status: EntryStatus, +} + +#[derive(Clone, Debug, Deserialize, Default)] +pub struct EntryStatus { + pub git: GitProjectStatus, +} + +#[derive(Clone, Debug, Deserialize, Default)] +pub struct GitProjectStatus { + pub modified: Color, + pub inserted: Color, } #[derive(Clone, Debug, Deserialize, Default)] diff --git a/crates/theme/src/ui.rs b/crates/theme/src/ui.rs index e4df24c89f..1799f37aed 100644 --- a/crates/theme/src/ui.rs +++ b/crates/theme/src/ui.rs @@ -1,6 +1,5 @@ use std::borrow::Cow; -use fs::repository::GitFileStatus; use gpui::{ color::Color, elements::{ @@ -12,11 +11,11 @@ use gpui::{ platform, platform::MouseButton, scene::MouseClick, - Action, AnyElement, Element, EventContext, MouseState, View, ViewContext, + Action, Element, EventContext, MouseState, View, ViewContext, }; use serde::Deserialize; -use crate::{ContainedText, Interactive, Theme}; +use crate::{ContainedText, Interactive}; #[derive(Clone, Deserialize, Default)] pub struct CheckboxStyle { @@ -253,53 +252,3 @@ where .constrained() .with_height(style.dimensions().y()) } - -pub struct FileName { - filename: String, - git_status: Option, - style: FileNameStyle, -} - -pub struct FileNameStyle { - template_style: LabelStyle, - git_inserted: Color, - git_modified: Color, - git_deleted: Color, -} - -impl FileName { - pub fn new(filename: String, git_status: Option, style: FileNameStyle) -> Self { - FileName { - filename, - git_status, - style, - } - } - - pub fn style>(style: I, theme: &Theme) -> FileNameStyle { - FileNameStyle { - template_style: style.into(), - git_inserted: theme.editor.diff.inserted, - git_modified: theme.editor.diff.modified, - git_deleted: theme.editor.diff.deleted, - } - } -} - -impl gpui::elements::Component for FileName { - fn render(&self, _: &mut V, _: &mut ViewContext) -> AnyElement { - // Prepare colors for git statuses - let mut filename_text_style = self.style.template_style.text.clone(); - filename_text_style.color = self - .git_status - .as_ref() - .map(|status| match status { - GitFileStatus::Added => self.style.git_inserted, - GitFileStatus::Modified => self.style.git_modified, - GitFileStatus::Conflict => self.style.git_deleted, - }) - .unwrap_or(self.style.template_style.text.color); - - Label::new(self.filename.clone(), filename_text_style).into_any() - } -} diff --git a/styles/src/styleTree/projectPanel.ts b/styles/src/styleTree/projectPanel.ts index 0c32ee7132..976cdede5c 100644 --- a/styles/src/styleTree/projectPanel.ts +++ b/styles/src/styleTree/projectPanel.ts @@ -13,6 +13,17 @@ export default function projectPanel(colorScheme: ColorScheme) { iconSpacing: 8, } + let status = { + git: { + modified: isLight + ? colorScheme.ramps.yellow(0.6).hex() + : colorScheme.ramps.yellow(0.5).hex(), + inserted: isLight + ? colorScheme.ramps.green(0.4).hex() + : colorScheme.ramps.green(0.5).hex(), + } + } + let entry = { ...baseEntry, text: text(layer, "mono", "variant", { size: "sm" }), @@ -29,16 +40,7 @@ export default function projectPanel(colorScheme: ColorScheme) { background: background(layer, "active"), text: text(layer, "mono", "active", { size: "sm" }), }, - status: { - git: { - modified: isLight - ? colorScheme.ramps.yellow(0.6).hex() - : colorScheme.ramps.yellow(0.5).hex(), - inserted: isLight - ? colorScheme.ramps.green(0.4).hex() - : colorScheme.ramps.green(0.5).hex(), - } - } + status } return { @@ -70,6 +72,7 @@ export default function projectPanel(colorScheme: ColorScheme) { entry, draggedEntry: { ...baseEntry, + status, text: text(layer, "mono", "on", { size: "sm" }), background: withOpacity(background(layer, "on"), 0.9), border: border(layer), From 3ca95678f1698c42699847c6d5034a35b5401713 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 18:57:14 +0200 Subject: [PATCH 086/131] Avoid leaking docks when adding panels --- crates/workspace/src/workspace.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6c7d61fef9..bd50251cf1 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -477,7 +477,7 @@ pub struct Workspace { leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: WorkspaceId, app_state: Arc, - _subscriptions: Vec, + subscriptions: Vec, _apply_leader_updates: Task>, _observe_current_user: Task>, pane_history_timestamp: Arc, @@ -683,7 +683,7 @@ impl Workspace { _observe_current_user, _apply_leader_updates, leader_updates_tx, - _subscriptions: subscriptions, + subscriptions, pane_history_timestamp, }; this.project_remote_id_changed(project.read(cx).remote_id(), cx); @@ -846,7 +846,7 @@ impl Workspace { DockPosition::Right => &self.right_dock, }; - cx.subscribe(&panel, { + self.subscriptions.push(cx.subscribe(&panel, { let mut dock = dock.clone(); let mut prev_position = panel.position(cx); move |this, panel, event, cx| { @@ -884,8 +884,7 @@ impl Workspace { cx.notify(); } } - }) - .detach(); + })); dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); } From af73c1af06b6165550d32dab9d9c5b980e4bbace Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 10:03:02 -0700 Subject: [PATCH 087/131] Add seperate scrollbar styling --- crates/editor/src/element.rs | 2 +- crates/theme/src/theme.rs | 8 ++++++++ crates/theme/src/ui.rs | 2 +- styles/src/styleTree/editor.ts | 11 +++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 57dc3293f6..502975c144 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1052,7 +1052,7 @@ impl EditorElement { ..Default::default() }); - let diff_style = theme::current(cx).editor.diff.clone(); + let diff_style = &theme::current(cx).editor.scrollbar.git; for hunk in layout .position_map .snapshot diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 5a79946589..c589137e73 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -682,6 +682,14 @@ pub struct Scrollbar { pub thumb: ContainerStyle, pub width: f32, pub min_height_factor: f32, + pub git: GitDiffColors +} + +#[derive(Clone, Deserialize, Default)] +pub struct GitDiffColors { + pub inserted: Color, + pub modified: Color, + pub deleted: Color } #[derive(Clone, Deserialize, Default)] diff --git a/crates/theme/src/ui.rs b/crates/theme/src/ui.rs index 1799f37aed..b86bfca8c4 100644 --- a/crates/theme/src/ui.rs +++ b/crates/theme/src/ui.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use gpui::{ color::Color, elements::{ - ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label, LabelStyle, + ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label, MouseEventHandler, ParentElement, Stack, Svg, }, fonts::TextStyle, diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index 2443717e4d..a13ae49c7d 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -247,6 +247,17 @@ export default function editor(colorScheme: ColorScheme) { color: borderColor(layer, "variant"), }, }, + git: { + deleted: isLight + ? colorScheme.ramps.red(0.5).hex() + : colorScheme.ramps.red(0.4).hex(), + modified: isLight + ? colorScheme.ramps.yellow(0.3).hex() + : colorScheme.ramps.yellow(0.5).hex(), + inserted: isLight + ? colorScheme.ramps.green(0.4).hex() + : colorScheme.ramps.green(0.5).hex(), + } }, compositionMark: { underline: { From a355b4c135c1c9f656bc3c3fa3057a052ffea196 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 10:09:55 -0700 Subject: [PATCH 088/131] Add conflict styles to project panel --- crates/theme/src/theme.rs | 1 + styles/src/styleTree/projectPanel.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index c589137e73..b5ac126a69 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -458,6 +458,7 @@ pub struct EntryStatus { pub struct GitProjectStatus { pub modified: Color, pub inserted: Color, + pub conflict: Color, } #[derive(Clone, Debug, Deserialize, Default)] diff --git a/styles/src/styleTree/projectPanel.ts b/styles/src/styleTree/projectPanel.ts index 976cdede5c..81dded151e 100644 --- a/styles/src/styleTree/projectPanel.ts +++ b/styles/src/styleTree/projectPanel.ts @@ -21,6 +21,9 @@ export default function projectPanel(colorScheme: ColorScheme) { inserted: isLight ? colorScheme.ramps.green(0.4).hex() : colorScheme.ramps.green(0.5).hex(), + conflict: isLight + ? colorScheme.ramps.red(0.4).hex() + : colorScheme.ramps.red(0.5).hex(), } } From ef81813d569fb1d815eaba2f5aebaa7a4157610f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 10:10:47 -0700 Subject: [PATCH 089/131] Wire in conflict styling --- crates/project_panel/src/project_panel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index dd28bdd73e..22b6b3fd75 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1086,7 +1086,7 @@ impl ProjectPanel { .map(|status| match status { GitFileStatus::Added => style.status.git.inserted, GitFileStatus::Modified => style.status.git.modified, - GitFileStatus::Conflict => style.text.color, + GitFileStatus::Conflict => style.status.git.conflict, }) .unwrap_or(style.text.color); From 048498e39be19fdef8e196b67c7b97ee88c65c25 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 19:30:31 +0200 Subject: [PATCH 090/131] Test zooming panels --- crates/workspace/src/dock.rs | 60 +++++++++++++++++++++---------- crates/workspace/src/workspace.rs | 32 +++++++++++++---- 2 files changed, 68 insertions(+), 24 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index c4693b3403..8f44419ddc 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -577,10 +577,29 @@ pub(crate) mod test { PositionChanged, Activated, Closed, + ZoomIn, + ZoomOut, + Focus, } pub struct TestPanel { pub position: DockPosition, + pub zoomed: bool, + pub active: bool, + pub has_focus: bool, + pub size: f32, + } + + impl TestPanel { + pub fn new(position: DockPosition) -> Self { + Self { + position, + zoomed: false, + active: false, + has_focus: false, + size: 300., + } + } } impl Entity for TestPanel { @@ -595,6 +614,14 @@ pub(crate) mod test { fn render(&mut self, _: &mut ViewContext<'_, '_, Self>) -> AnyElement { Empty::new().into_any() } + + fn focus_in(&mut self, _: AnyViewHandle, _: &mut ViewContext) { + self.has_focus = true; + } + + fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext) { + self.has_focus = false; + } } impl Panel for TestPanel { @@ -612,26 +639,23 @@ pub(crate) mod test { } fn is_zoomed(&self, _: &WindowContext) -> bool { - unimplemented!() + self.zoomed } - fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext) { - unimplemented!() + fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext) { + self.zoomed = zoomed; } - fn set_active(&mut self, _active: bool, _cx: &mut ViewContext) { - unimplemented!() + fn set_active(&mut self, active: bool, _cx: &mut ViewContext) { + self.active = active; } fn size(&self, _: &WindowContext) -> f32 { - match self.position.axis() { - Axis::Horizontal => 300., - Axis::Vertical => 200., - } + self.size } - fn set_size(&mut self, _: f32, _: &mut ViewContext) { - unimplemented!() + fn set_size(&mut self, size: f32, _: &mut ViewContext) { + self.size = size; } fn icon_path(&self) -> &'static str { @@ -646,12 +670,12 @@ pub(crate) mod test { matches!(event, TestPanelEvent::PositionChanged) } - fn should_zoom_in_on_event(_: &Self::Event) -> bool { - false + fn should_zoom_in_on_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::ZoomIn) } - fn should_zoom_out_on_event(_: &Self::Event) -> bool { - false + fn should_zoom_out_on_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::ZoomOut) } fn should_activate_on_event(event: &Self::Event) -> bool { @@ -663,11 +687,11 @@ pub(crate) mod test { } fn has_focus(&self, _cx: &WindowContext) -> bool { - unimplemented!() + self.has_focus } - fn is_focus_event(_: &Self::Event) -> bool { - unimplemented!() + fn is_focus_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::Focus) } } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index bd50251cf1..495989c1be 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3931,16 +3931,12 @@ mod tests { let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { // Add panel_1 on the left, panel_2 on the right. - let panel_1 = cx.add_view(|_| TestPanel { - position: DockPosition::Left, - }); + let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left)); workspace.add_panel(panel_1.clone(), cx); workspace .left_dock() .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); - let panel_2 = cx.add_view(|_| TestPanel { - position: DockPosition::Right, - }); + let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right)); workspace.add_panel(panel_2.clone(), cx); workspace .right_dock() @@ -4060,6 +4056,30 @@ mod tests { ); }); + // Emitting a ZoomIn event shows the panel as zoomed. + panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn)); + workspace.read_with(cx, |workspace, cx| { + assert_eq!(workspace.zoomed(cx), Some(panel_1.clone().into_any())); + }); + + // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed. + workspace.update(cx, |_, cx| cx.focus_self()); + workspace.read_with(cx, |workspace, cx| { + assert_eq!(workspace.zoomed(cx), None); + }); + + // When focus is transferred back to the panel, it is zoomed again. + panel_1.update(cx, |_, cx| cx.focus_self()); + workspace.read_with(cx, |workspace, cx| { + assert_eq!(workspace.zoomed(cx), Some(panel_1.clone().into_any())); + }); + + // Emitting a ZoomOut event unzooms the panel. + panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut)); + workspace.read_with(cx, |workspace, cx| { + assert_eq!(workspace.zoomed(cx), None); + }); + // Emit closed event on panel 1, which is active panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed)); From 6f39d49b184a55d344d6d6920abea74197d5aa1b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 19:33:11 +0200 Subject: [PATCH 091/131] Fix warnings --- crates/workspace/src/dock.rs | 3 ++- crates/workspace/src/pane.rs | 8 ++++---- crates/workspace/src/persistence.rs | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 8f44419ddc..7e6868f9b0 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -615,8 +615,9 @@ pub(crate) mod test { Empty::new().into_any() } - fn focus_in(&mut self, _: AnyViewHandle, _: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { self.has_focus = true; + cx.emit(TestPanelEvent::Focus); } fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext) { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index aae900c861..eb3dd12b33 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2325,7 +2325,7 @@ mod tests { } #[gpui::test] - async fn test_remove_item_ordering(deterministic: Arc, cx: &mut TestAppContext) { + async fn test_remove_item_ordering(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); @@ -2372,7 +2372,7 @@ mod tests { } #[gpui::test] - async fn test_close_inactive_items(deterministic: Arc, cx: &mut TestAppContext) { + async fn test_close_inactive_items(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); @@ -2392,7 +2392,7 @@ mod tests { } #[gpui::test] - async fn test_close_clean_items(deterministic: Arc, cx: &mut TestAppContext) { + async fn test_close_clean_items(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); @@ -2461,7 +2461,7 @@ mod tests { } #[gpui::test] - async fn test_close_all_items(deterministic: Arc, cx: &mut TestAppContext) { + async fn test_close_all_items(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 76b66c5329..d27818d202 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -579,7 +579,7 @@ mod tests { docks: Default::default(), }; - let mut workspace_2 = SerializedWorkspace { + let workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), center_group: Default::default(), From 33f6c56b1465e5628d791f2b2f74307157064c47 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 19:36:01 +0200 Subject: [PATCH 092/131] Fix more warnings --- crates/workspace/src/pane.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index eb3dd12b33..e07bd0c9cf 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2415,10 +2415,7 @@ mod tests { } #[gpui::test] - async fn test_close_items_to_the_left( - deterministic: Arc, - cx: &mut TestAppContext, - ) { + async fn test_close_items_to_the_left(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); @@ -2438,10 +2435,7 @@ mod tests { } #[gpui::test] - async fn test_close_items_to_the_right( - deterministic: Arc, - cx: &mut TestAppContext, - ) { + async fn test_close_items_to_the_right(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); From f22067b236bdb0bebb3c58b0790ec78b0d4a549b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 19:38:57 +0200 Subject: [PATCH 093/131] Remove unused imports --- crates/workspace/src/pane.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index e07bd0c9cf..e1079f201b 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2028,11 +2028,9 @@ impl Element for PaneBackdrop { #[cfg(test)] mod tests { - use std::sync::Arc; - use super::*; use crate::item::test::{TestItem, TestProjectItem}; - use gpui::{executor::Deterministic, TestAppContext}; + use gpui::TestAppContext; use project::FakeFs; use settings::SettingsStore; From 8669dcdc81332f1d3f1cf89a45d3e919a6349277 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 10:55:44 -0700 Subject: [PATCH 094/131] Make scrollbar content detection cheaper Remove scrollbars from multibuffers co-authored-by: max --- crates/editor/src/editor.rs | 8 +-- crates/editor/src/element.rs | 93 ++++++++++++++++--------------- crates/editor/src/multi_buffer.rs | 9 +++ crates/git/src/diff.rs | 4 ++ 4 files changed, 65 insertions(+), 49 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 7207e3c91c..0a4204ba2a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -526,11 +526,9 @@ pub struct EditorSnapshot { } impl EditorSnapshot { - fn has_scrollbar_info(&self) -> bool { - self.buffer_snapshot - .git_diff_hunks_in_range(0..self.max_point().row()) - .next() - .is_some() + fn has_scrollbar_info(&self, is_singleton: bool) -> bool { + is_singleton && self.buffer_snapshot + .has_git_diffs() } } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 57dc3293f6..5a057390d5 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1052,51 +1052,53 @@ impl EditorElement { ..Default::default() }); - let diff_style = theme::current(cx).editor.diff.clone(); - for hunk in layout - .position_map - .snapshot - .buffer_snapshot - .git_diff_hunks_in_range(0..(max_row.floor() as u32)) - { - let start_display = Point::new(hunk.buffer_range.start, 0) - .to_display_point(&layout.position_map.snapshot.display_snapshot); - let end_display = Point::new(hunk.buffer_range.end, 0) - .to_display_point(&layout.position_map.snapshot.display_snapshot); - let start_y = y_for_row(start_display.row() as f32); - let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end { - y_for_row((end_display.row() + 1) as f32) - } else { - y_for_row((end_display.row()) as f32) - }; + if layout.is_singleton { + let diff_style = theme::current(cx).editor.diff.clone(); + for hunk in layout + .position_map + .snapshot + .buffer_snapshot + .git_diff_hunks_in_range(0..(max_row.floor() as u32)) + { + let start_display = Point::new(hunk.buffer_range.start, 0) + .to_display_point(&layout.position_map.snapshot.display_snapshot); + let end_display = Point::new(hunk.buffer_range.end, 0) + .to_display_point(&layout.position_map.snapshot.display_snapshot); + let start_y = y_for_row(start_display.row() as f32); + let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end { + y_for_row((end_display.row() + 1) as f32) + } else { + y_for_row((end_display.row()) as f32) + }; - if end_y - start_y < 1. { - end_y = start_y + 1.; + if end_y - start_y < 1. { + end_y = start_y + 1.; + } + let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y)); + + let color = match hunk.status() { + DiffHunkStatus::Added => diff_style.inserted, + DiffHunkStatus::Modified => diff_style.modified, + DiffHunkStatus::Removed => diff_style.deleted, + }; + + let border = Border { + width: 1., + color: style.thumb.border.color, + overlay: false, + top: false, + right: true, + bottom: false, + left: true, + }; + + scene.push_quad(Quad { + bounds, + background: Some(color), + border, + corner_radius: style.thumb.corner_radius, + }) } - let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y)); - - let color = match hunk.status() { - DiffHunkStatus::Added => diff_style.inserted, - DiffHunkStatus::Modified => diff_style.modified, - DiffHunkStatus::Removed => diff_style.deleted, - }; - - let border = Border { - width: 1., - color: style.thumb.border.color, - overlay: false, - top: false, - right: true, - bottom: false, - left: true, - }; - - scene.push_quad(Quad { - bounds, - background: Some(color), - border, - corner_radius: style.thumb.corner_radius, - }) } scene.push_quad(Quad { @@ -2067,7 +2069,8 @@ impl Element for EditorElement { let show_scrollbars = match settings::get::(cx).show_scrollbars { ShowScrollbars::Auto => { - snapshot.has_scrollbar_info() || editor.scroll_manager.scrollbars_visible() + snapshot.has_scrollbar_info(is_singleton) + || editor.scroll_manager.scrollbars_visible() } ShowScrollbars::System => editor.scroll_manager.scrollbars_visible(), ShowScrollbars::Always => true, @@ -2290,6 +2293,7 @@ impl Element for EditorElement { text_size, scrollbar_row_range, show_scrollbars, + is_singleton, max_row, gutter_margin, active_rows, @@ -2445,6 +2449,7 @@ pub struct LayoutState { selections: Vec<(ReplicaId, Vec)>, scrollbar_row_range: Range, show_scrollbars: bool, + is_singleton: bool, max_row: u32, context_menu: Option<(DisplayPoint, AnyElement)>, code_actions_indicator: Option<(u32, AnyElement)>, diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 1423473e1a..6a61775697 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -2841,6 +2841,15 @@ impl MultiBufferSnapshot { }) } + pub fn has_git_diffs(&self) -> bool { + for excerpt in self.excerpts.iter() { + if !excerpt.buffer.git_diff.is_empty() { + return true; + } + } + false + } + pub fn git_diff_hunks_in_range_rev<'a>( &'a self, row_range: Range, diff --git a/crates/git/src/diff.rs b/crates/git/src/diff.rs index 8704f85005..8260dfc98d 100644 --- a/crates/git/src/diff.rs +++ b/crates/git/src/diff.rs @@ -71,6 +71,10 @@ impl BufferDiff { } } + pub fn is_empty(&self) -> bool { + self.tree.is_empty() + } + pub fn hunks_in_row_range<'a>( &'a self, range: Range, From 6264b2478809b807e36dc33f0aa27be421bf5c0c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 11:04:36 -0700 Subject: [PATCH 095/131] fmt --- crates/editor/src/editor.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0a4204ba2a..12f5b3b872 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -527,8 +527,7 @@ pub struct EditorSnapshot { impl EditorSnapshot { fn has_scrollbar_info(&self, is_singleton: bool) -> bool { - is_singleton && self.buffer_snapshot - .has_git_diffs() + is_singleton && self.buffer_snapshot.has_git_diffs() } } From 6f4b6eec5b2584c33602928a0bc64f9f0af18f01 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 11:10:13 -0700 Subject: [PATCH 096/131] Diagnostics pane was not focusable with the mouse --- crates/diagnostics/src/diagnostics.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index a202a6082c..27f75ea896 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -33,7 +33,7 @@ use theme::ThemeSettings; use util::TryFutureExt; use workspace::{ item::{BreadcrumbText, Item, ItemEvent, ItemHandle}, - ItemNavHistory, Pane, ToolbarItemLocation, Workspace, + ItemNavHistory, Pane, ToolbarItemLocation, Workspace, PaneBackdrop, }; actions!(diagnostics, [Deploy]); @@ -90,11 +90,12 @@ impl View for ProjectDiagnosticsEditor { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if self.path_states.is_empty() { let theme = &theme::current(cx).project_diagnostics; - Label::new("No problems in workspace", theme.empty_message.clone()) + PaneBackdrop::new(cx.view_id(), Label::new("No problems in workspace", theme.empty_message.clone()) .aligned() .contained() .with_style(theme.container) - .into_any() + .into_any()).into_any() + } else { ChildView::new(&self.editor, cx).into_any() } From 04355215b2d1489a763345ac73078399bad7d18f Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Mon, 22 May 2023 14:44:05 -0400 Subject: [PATCH 097/131] Indent all release note lines for each PR --- script/get-preview-channel-changes | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/script/get-preview-channel-changes b/script/get-preview-channel-changes index 47623125f9..5a0be3ed66 100755 --- a/script/get-preview-channel-changes +++ b/script/get-preview-channel-changes @@ -69,9 +69,12 @@ async function main() { let releaseNotes = (pullRequest.body || "").split("Release Notes:")[1]; if (releaseNotes) { - releaseNotes = releaseNotes.trim(); + releaseNotes = releaseNotes.trim().split("\n") console.log(" Release Notes:"); - console.log(` ${releaseNotes}`); + + for (const line of releaseNotes) { + console.log(` ${line}`); + } } console.log() From e59c8e9d611405eb7063751f2ba87d2d0bc72324 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 11:44:23 -0700 Subject: [PATCH 098/131] Fix diagnostic pane not being closeable --- crates/diagnostics/src/diagnostics.rs | 26 +++++++++++++++++--------- crates/editor/src/editor.rs | 2 ++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 27f75ea896..182efdfdd6 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -33,7 +33,7 @@ use theme::ThemeSettings; use util::TryFutureExt; use workspace::{ item::{BreadcrumbText, Item, ItemEvent, ItemHandle}, - ItemNavHistory, Pane, ToolbarItemLocation, Workspace, PaneBackdrop, + ItemNavHistory, Pane, PaneBackdrop, ToolbarItemLocation, Workspace, }; actions!(diagnostics, [Deploy]); @@ -90,12 +90,15 @@ impl View for ProjectDiagnosticsEditor { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if self.path_states.is_empty() { let theme = &theme::current(cx).project_diagnostics; - PaneBackdrop::new(cx.view_id(), Label::new("No problems in workspace", theme.empty_message.clone()) - .aligned() - .contained() - .with_style(theme.container) - .into_any()).into_any() - + PaneBackdrop::new( + cx.view_id(), + Label::new("No problems in workspace", theme.empty_message.clone()) + .aligned() + .contained() + .with_style(theme.container) + .into_any(), + ) + .into_any() } else { ChildView::new(&self.editor, cx).into_any() } @@ -162,8 +165,13 @@ impl ProjectDiagnosticsEditor { editor.set_vertical_scroll_margin(5, cx); editor }); - cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())) - .detach(); + cx.subscribe(&editor, |this, _, event, cx| { + cx.emit(event.clone()); + if event == &editor::Event::Focused && this.path_states.is_empty() { + cx.focus_self() + } + }) + .detach(); let project = project_handle.read(cx); let paths_to_update = project diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 7207e3c91c..94fa23939b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7128,6 +7128,7 @@ pub enum Event { BufferEdited, Edited, Reparsed, + Focused, Blurred, DirtyChanged, Saved, @@ -7181,6 +7182,7 @@ impl View for Editor { fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { if cx.is_self_focused() { let focused_event = EditorFocused(cx.handle()); + cx.emit(Event::Focused); cx.emit_global(focused_event); } if let Some(rename) = self.pending_rename.as_ref() { From bafc1d922e756cd369bea7cada4c85bc0c561595 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 11:46:52 -0700 Subject: [PATCH 099/131] fmt --- crates/diagnostics/src/diagnostics.rs | 5 +++-- crates/editor/src/editor.rs | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 182efdfdd6..29ea1273e2 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -105,8 +105,9 @@ impl View for ProjectDiagnosticsEditor { } fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - if cx.is_self_focused() && !self.path_states.is_empty() { - cx.focus(&self.editor); + dbg!("Focus in"); + if dbg!(cx.is_self_focused()) && dbg!(!self.path_states.is_empty()) { + dbg!(cx.focus(&self.editor)); } } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 94fa23939b..6a43e5a93b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7180,6 +7180,7 @@ impl View for Editor { } fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + dbg!("Editor Focus in"); if cx.is_self_focused() { let focused_event = EditorFocused(cx.handle()); cx.emit(Event::Focused); From 9c32b774aad9c152fd330f801a842ac2c8676720 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Mon, 22 May 2023 14:53:07 -0400 Subject: [PATCH 100/131] Add example of changelog line in PR template --- .github/pull_request_template.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8d16a59bc1..f66fdce683 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,4 +2,5 @@ Release Notes: -* [[Added foo / Fixed bar / No notes]] +* (Added|Fixed|Improved) ... ([#](https://github.com/zed-industries/community/issues/)). +* ... From 49566e5677affcf3a7a1378897ebf14ba61f07c3 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Mon, 22 May 2023 15:00:57 -0400 Subject: [PATCH 101/131] Update pull_request_template.md --- .github/pull_request_template.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f66fdce683..67a7ed31c9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,5 +2,12 @@ Release Notes: -* (Added|Fixed|Improved) ... ([#](https://github.com/zed-industries/community/issues/)). +Use `N/A` in this section if this item should be skipped in the release notes. + +Add release note lines here: + +* (Added|Fixed|Improved) ... ([#](https://github.com/zed-industries/community/issues/)). * ... + +If the release notes are only intended for a specific release channel only, add `(-only)` to the end of the release note line. +These will be removed by the person making the release. From 366f13bb5c5a9fa41c75deff76ee400ac9a7a31a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 12:12:56 -0700 Subject: [PATCH 102/131] Adjust scrollbar settings to be expandable --- assets/settings/default.json | 31 ++++++++++++++++------------ crates/editor/src/editor.rs | 6 ------ crates/editor/src/editor_settings.rs | 18 +++++++++++++--- crates/editor/src/element.rs | 21 +++++++++++-------- 4 files changed, 45 insertions(+), 31 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 4f149edb10..84db3f8ea1 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -52,19 +52,24 @@ // 3. Draw all invisible symbols: // "all" "show_whitespaces": "selection", - // Whether to show the scrollbar in the editor. - // This setting can take four values: - // - // 1. Show the scrollbar if there's important information or - // follow the system's configured behavior (default): - // "auto" - // 2. Match the system's configured behavior: - // "system" - // 3. Always show the scrollbar: - // "always" - // 4. Never show the scrollbar: - // "never" - "show_scrollbars": "auto", + // Scrollbar related settings + "scrollbar": { + // When to show the scrollbar in the editor. + // This setting can take four values: + // + // 1. Show the scrollbar if there's important information or + // follow the system's configured behavior (default): + // "auto" + // 2. Match the system's configured behavior: + // "system" + // 3. Always show the scrollbar: + // "always" + // 4. Never show the scrollbar: + // "never" + "when_to_show": "auto", + // Whether to show git diff indicators in the scrollbar. + "git_diff": true + }, // Whether the screen sharing icon is shown in the os status bar. "show_call_status_icon": true, // Whether to use language servers to provide code intelligence. diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 814435059e..d8bf71e2f2 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -533,12 +533,6 @@ pub struct EditorSnapshot { ongoing_scroll: OngoingScroll, } -impl EditorSnapshot { - fn has_scrollbar_info(&self, is_singleton: bool) -> bool { - is_singleton && self.buffer_snapshot.has_git_diffs() - } -} - #[derive(Clone, Debug)] struct SelectionHistoryEntry { selections: Arc<[Selection]>, diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index a3f38a3dc0..05045ca490 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -7,12 +7,18 @@ pub struct EditorSettings { pub cursor_blink: bool, pub hover_popover_enabled: bool, pub show_completions_on_input: bool, - pub show_scrollbars: ShowScrollbars, + pub scrollbar: Scrollbar, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct Scrollbar { + pub when_to_show: ShowScrollbar, + pub git_diff: bool } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "snake_case")] -pub enum ShowScrollbars { +pub enum ShowScrollbar { Auto, System, Always, @@ -24,7 +30,13 @@ pub struct EditorSettingsContent { pub cursor_blink: Option, pub hover_popover_enabled: Option, pub show_completions_on_input: Option, - pub show_scrollbars: Option, + pub scrollbar: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct ScrollbarContent { + pub when_to_show: Option, + pub git_diff: Option } impl Setting for EditorSettings { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 5a057390d5..b5e09fc86c 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -5,7 +5,7 @@ use super::{ }; use crate::{ display_map::{BlockStyle, DisplaySnapshot, FoldStatus, TransformBlock}, - editor_settings::ShowScrollbars, + editor_settings::ShowScrollbar, git::{diff_hunk_to_display, DisplayDiffHunk}, hover_popover::{ hide_hover, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, @@ -1052,7 +1052,7 @@ impl EditorElement { ..Default::default() }); - if layout.is_singleton { + if layout.is_singleton && settings::get::(cx).scrollbar.git_diff { let diff_style = theme::current(cx).editor.diff.clone(); for hunk in layout .position_map @@ -2067,14 +2067,17 @@ impl Element for EditorElement { )); } - let show_scrollbars = match settings::get::(cx).show_scrollbars { - ShowScrollbars::Auto => { - snapshot.has_scrollbar_info(is_singleton) - || editor.scroll_manager.scrollbars_visible() + let scrollbar_settings = &settings::get::(cx).scrollbar; + let show_scrollbars = match scrollbar_settings.when_to_show { + ShowScrollbar::Auto => { + // Git + (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs()) + // Scrollmanager + || editor.scroll_manager.scrollbars_visible() } - ShowScrollbars::System => editor.scroll_manager.scrollbars_visible(), - ShowScrollbars::Always => true, - ShowScrollbars::Never => false, + ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(), + ShowScrollbar::Always => true, + ShowScrollbar::Never => false, }; let include_root = editor From 687ccd4c6feb25031f9a490257e87ea624159493 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 12:13:23 -0700 Subject: [PATCH 103/131] fmt --- crates/editor/src/editor_settings.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 05045ca490..d97006e583 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -13,7 +13,7 @@ pub struct EditorSettings { #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct Scrollbar { pub when_to_show: ShowScrollbar, - pub git_diff: bool + pub git_diff: bool, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] @@ -36,7 +36,7 @@ pub struct EditorSettingsContent { #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct ScrollbarContent { pub when_to_show: Option, - pub git_diff: Option + pub git_diff: Option, } impl Setting for EditorSettings { From cfdf9198dac938140daa662325bd5c043e69cbe0 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 12:16:47 -0700 Subject: [PATCH 104/131] Switch back to --- assets/settings/default.json | 2 +- crates/editor/src/editor_settings.rs | 4 ++-- crates/editor/src/element.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 84db3f8ea1..e4e7a8c522 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -66,7 +66,7 @@ // "always" // 4. Never show the scrollbar: // "never" - "when_to_show": "auto", + "show": "auto", // Whether to show git diff indicators in the scrollbar. "git_diff": true }, diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index d97006e583..7f01834b16 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -12,7 +12,7 @@ pub struct EditorSettings { #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct Scrollbar { - pub when_to_show: ShowScrollbar, + pub show: ShowScrollbar, pub git_diff: bool, } @@ -35,7 +35,7 @@ pub struct EditorSettingsContent { #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct ScrollbarContent { - pub when_to_show: Option, + pub show: Option, pub git_diff: Option, } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index b5e09fc86c..13a24bfc2c 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2068,7 +2068,7 @@ impl Element for EditorElement { } let scrollbar_settings = &settings::get::(cx).scrollbar; - let show_scrollbars = match scrollbar_settings.when_to_show { + let show_scrollbars = match scrollbar_settings.show { ShowScrollbar::Auto => { // Git (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs()) From 7689cdf3f944f44e28f14da389c07f4c417df47d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 22 May 2023 12:53:22 -0700 Subject: [PATCH 105/131] Clear old diagnostics when restarting a language server --- crates/language/src/buffer.rs | 15 +++-- crates/language/src/diagnostic_set.rs | 4 ++ crates/project/src/project.rs | 17 +++++ crates/project/src/project_tests.rs | 89 +++++++++++++++++++++++++++ crates/project/src/worktree.rs | 39 ++++++++++++ 5 files changed, 160 insertions(+), 4 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 3a97702487..5539d1d941 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1644,10 +1644,17 @@ impl Buffer { cx: &mut ModelContext, ) { if lamport_timestamp > self.diagnostics_timestamp { - match self.diagnostics.binary_search_by_key(&server_id, |e| e.0) { - Err(ix) => self.diagnostics.insert(ix, (server_id, diagnostics)), - Ok(ix) => self.diagnostics[ix].1 = diagnostics, - }; + let ix = self.diagnostics.binary_search_by_key(&server_id, |e| e.0); + if diagnostics.len() == 0 { + if let Ok(ix) = ix { + self.diagnostics.remove(ix); + } + } else { + match ix { + Err(ix) => self.diagnostics.insert(ix, (server_id, diagnostics)), + Ok(ix) => self.diagnostics[ix].1 = diagnostics, + }; + } self.diagnostics_timestamp = lamport_timestamp; self.diagnostics_update_count += 1; self.text.lamport_clock.observe(lamport_timestamp); diff --git a/crates/language/src/diagnostic_set.rs b/crates/language/src/diagnostic_set.rs index 948a7ee394..f269fce88d 100644 --- a/crates/language/src/diagnostic_set.rs +++ b/crates/language/src/diagnostic_set.rs @@ -80,6 +80,10 @@ impl DiagnosticSet { } } + pub fn len(&self) -> usize { + self.diagnostics.summary().count + } + pub fn iter(&self) -> impl Iterator> { self.diagnostics.iter() } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f91cd999f9..5b5d903822 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2565,6 +2565,23 @@ impl Project { } } + for buffer in self.opened_buffers.values() { + if let Some(buffer) = buffer.upgrade(cx) { + buffer.update(cx, |buffer, cx| { + buffer.update_diagnostics(server_id, Default::default(), cx); + }); + } + } + for worktree in &self.worktrees { + if let Some(worktree) = worktree.upgrade(cx) { + worktree.update(cx, |worktree, cx| { + if let Some(worktree) = worktree.as_local_mut() { + worktree.clear_diagnostics_for_language_server(server_id, cx); + } + }); + } + } + self.language_server_statuses.remove(&server_id); cx.notify(); diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 69bcea8ce0..e48ce6258b 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -926,6 +926,95 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC }); } +#[gpui::test] +async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAppContext) { + init_test(cx); + + let mut language = Language::new( + LanguageConfig { + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + None, + ); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.background()); + fs.insert_tree("/dir", json!({ "a.rs": "x" })).await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + // Publish diagnostics + let fake_server = fake_servers.next().await.unwrap(); + fake_server.notify::(lsp::PublishDiagnosticsParams { + uri: Url::from_file_path("/dir/a.rs").unwrap(), + version: None, + diagnostics: vec![lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)), + severity: Some(lsp::DiagnosticSeverity::ERROR), + message: "the message".to_string(), + ..Default::default() + }], + }); + + cx.foreground().run_until_parked(); + buffer.read_with(cx, |buffer, _| { + assert_eq!( + buffer + .snapshot() + .diagnostics_in_range::<_, usize>(0..1, false) + .map(|entry| entry.diagnostic.message.clone()) + .collect::>(), + ["the message".to_string()] + ); + }); + project.read_with(cx, |project, cx| { + assert_eq!( + project.diagnostic_summary(cx), + DiagnosticSummary { + error_count: 1, + warning_count: 0, + } + ); + }); + + project.update(cx, |project, cx| { + project.restart_language_servers_for_buffers([buffer.clone()], cx); + }); + + // The diagnostics are cleared. + cx.foreground().run_until_parked(); + buffer.read_with(cx, |buffer, _| { + assert_eq!( + buffer + .snapshot() + .diagnostics_in_range::<_, usize>(0..1, false) + .map(|entry| entry.diagnostic.message.clone()) + .collect::>(), + Vec::::new(), + ); + }); + project.read_with(cx, |project, cx| { + assert_eq!( + project.diagnostic_summary(cx), + DiagnosticSummary { + error_count: 0, + warning_count: 0, + } + ); + }); +} + #[gpui::test] async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::TestAppContext) { init_test(cx); diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 4f898aa91d..7df99b577b 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -737,6 +737,45 @@ impl LocalWorktree { self.diagnostics.get(path).cloned().unwrap_or_default() } + pub fn clear_diagnostics_for_language_server( + &mut self, + server_id: LanguageServerId, + _: &mut ModelContext, + ) { + let worktree_id = self.id().to_proto(); + self.diagnostic_summaries + .retain(|path, summaries_by_server_id| { + if summaries_by_server_id.remove(&server_id).is_some() { + if let Some(share) = self.share.as_ref() { + self.client + .send(proto::UpdateDiagnosticSummary { + project_id: share.project_id, + worktree_id, + summary: Some(proto::DiagnosticSummary { + path: path.to_string_lossy().to_string(), + language_server_id: server_id.0 as u64, + error_count: 0, + warning_count: 0, + }), + }) + .log_err(); + } + !summaries_by_server_id.is_empty() + } else { + true + } + }); + + self.diagnostics.retain(|_, diagnostics_by_server_id| { + if let Ok(ix) = diagnostics_by_server_id.binary_search_by_key(&server_id, |e| e.0) { + diagnostics_by_server_id.remove(ix); + !diagnostics_by_server_id.is_empty() + } else { + true + } + }); + } + pub fn update_diagnostics( &mut self, server_id: LanguageServerId, From 96224fa7e84e97779911f17a9e1e65dd446cd71a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 13:57:07 -0700 Subject: [PATCH 106/131] Only fire update diff base when the dot repo is scanned --- crates/project/src/worktree.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 4f898aa91d..8d0896d2d1 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -329,7 +329,7 @@ pub struct LocalMutableSnapshot { #[derive(Debug, Clone)] pub struct LocalRepositoryEntry { pub(crate) scan_id: usize, - pub(crate) full_scan_id: usize, + pub(crate) git_dir_scan_id: usize, pub(crate) repo_ptr: Arc>, /// Path to the actual .git folder. /// Note: if .git is a file, this points to the folder indicated by the .git file @@ -830,7 +830,7 @@ impl LocalWorktree { old_repos.next(); } Ordering::Equal => { - if old_repo.scan_id != new_repo.scan_id { + if old_repo.git_dir_scan_id != new_repo.git_dir_scan_id { if let Some(entry) = self.entry_for_id(**new_entry_id) { diff.insert(entry.path.clone(), (*new_repo).clone()); } @@ -2006,7 +2006,7 @@ impl LocalSnapshot { work_dir_id, LocalRepositoryEntry { scan_id, - full_scan_id: scan_id, + git_dir_scan_id: scan_id, repo_ptr: repo, git_dir_path: parent_path.clone(), }, @@ -3166,7 +3166,7 @@ impl BackgroundScanner { snapshot.build_repo(dot_git_dir.into(), fs); return None; }; - if repo.full_scan_id == scan_id { + if repo.git_dir_scan_id == scan_id { return None; } (*entry_id, repo.repo_ptr.to_owned()) @@ -3183,7 +3183,7 @@ impl BackgroundScanner { snapshot.git_repositories.update(&entry_id, |entry| { entry.scan_id = scan_id; - entry.full_scan_id = scan_id; + entry.git_dir_scan_id = scan_id; }); snapshot.repository_entries.update(&work_dir, |entry| { @@ -3212,7 +3212,7 @@ impl BackgroundScanner { let local_repo = snapshot.get_local_repo(&repo)?.to_owned(); // Short circuit if we've already scanned everything - if local_repo.full_scan_id == scan_id { + if local_repo.git_dir_scan_id == scan_id { return None; } From 11eb9b17c998dc436a6d3a3cebc91a57fccb6110 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Mon, 22 May 2023 17:47:52 -0400 Subject: [PATCH 107/131] Update project panel & scroll bar git colors --- styles/src/styleTree/editor.ts | 24 ++++++++++++------------ styles/src/styleTree/projectPanel.ts | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index a13ae49c7d..786f010242 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -108,8 +108,8 @@ export default function editor(colorScheme: ColorScheme) { ? colorScheme.ramps.green(0.4).hex() : colorScheme.ramps.green(0.5).hex(), removedWidthEm: 0.275, - widthEm: 0.22, - cornerRadius: 0.2, + widthEm: 0.15, + cornerRadius: 0.05, }, /** Highlights matching occurences of what is under the cursor * as well as matched brackets @@ -241,22 +241,22 @@ export default function editor(colorScheme: ColorScheme) { border: border(layer, "variant", { left: true }), }, thumb: { - background: withOpacity(background(layer, "inverted"), 0.4), + background: withOpacity(background(layer, "inverted"), 0.3), border: { width: 1, color: borderColor(layer, "variant"), }, }, git: { - deleted: isLight - ? colorScheme.ramps.red(0.5).hex() - : colorScheme.ramps.red(0.4).hex(), - modified: isLight - ? colorScheme.ramps.yellow(0.3).hex() - : colorScheme.ramps.yellow(0.5).hex(), - inserted: isLight - ? colorScheme.ramps.green(0.4).hex() - : colorScheme.ramps.green(0.5).hex(), + deleted: isLight + ? withOpacity(colorScheme.ramps.red(0.5).hex(), 0.8) + : withOpacity(colorScheme.ramps.red(0.4).hex(), 0.8), + modified: isLight + ? withOpacity(colorScheme.ramps.yellow(0.5).hex(), 0.8) + : withOpacity(colorScheme.ramps.yellow(0.4).hex(), 0.8), + inserted: isLight + ? withOpacity(colorScheme.ramps.green(0.5).hex(), 0.8) + : withOpacity(colorScheme.ramps.green(0.4).hex(), 0.8), } }, compositionMark: { diff --git a/styles/src/styleTree/projectPanel.ts b/styles/src/styleTree/projectPanel.ts index 81dded151e..7af5952154 100644 --- a/styles/src/styleTree/projectPanel.ts +++ b/styles/src/styleTree/projectPanel.ts @@ -19,11 +19,11 @@ export default function projectPanel(colorScheme: ColorScheme) { ? colorScheme.ramps.yellow(0.6).hex() : colorScheme.ramps.yellow(0.5).hex(), inserted: isLight - ? colorScheme.ramps.green(0.4).hex() + ? colorScheme.ramps.green(0.45).hex() : colorScheme.ramps.green(0.5).hex(), conflict: isLight - ? colorScheme.ramps.red(0.4).hex() - : colorScheme.ramps.red(0.5).hex(), + ? colorScheme.ramps.red(0.6).hex() + : colorScheme.ramps.red(0.5).hex() } } From 5dfb0e36910afa9738fc9ee9346b249d295413af Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 14:56:35 -0700 Subject: [PATCH 108/131] remove border from thumb --- styles/src/styleTree/editor.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index 786f010242..91c1318359 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -245,6 +245,10 @@ export default function editor(colorScheme: ColorScheme) { border: { width: 1, color: borderColor(layer, "variant"), + top: false, + right: true, + left: true, + bottom: false, }, }, git: { From 9bec74f1d4cd4b147918f49d1402545b587edd28 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 15:04:51 -0700 Subject: [PATCH 109/131] fmt --- crates/editor/src/editor.rs | 1 - crates/editor/src/element.rs | 1 - crates/project_panel/src/project_panel.rs | 4 ++-- crates/theme/src/theme.rs | 4 ++-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 57d5ec6e95..ce67a59bd3 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7253,7 +7253,6 @@ impl View for Editor { } fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - dbg!("Editor Focus in"); if cx.is_self_focused() { let focused_event = EditorFocused(cx.handle()); cx.emit(Event::Focused); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index ba9adfdd16..13a24bfc2c 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1052,7 +1052,6 @@ impl EditorElement { ..Default::default() }); - if layout.is_singleton && settings::get::(cx).scrollbar.git_diff { let diff_style = theme::current(cx).editor.diff.clone(); for hunk in layout diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 22b6b3fd75..a7895172d5 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -6,8 +6,8 @@ use gpui::{ actions, anyhow::{anyhow, Result}, elements::{ - AnchorCorner, ChildView, ContainerStyle, Empty, Flex, Label, - MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, + AnchorCorner, ChildView, ContainerStyle, Empty, Flex, Label, MouseEventHandler, + ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, }, geometry::vector::Vector2F, keymap_matcher::KeymapContext, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index b5ac126a69..cd2bf90b7e 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -683,14 +683,14 @@ pub struct Scrollbar { pub thumb: ContainerStyle, pub width: f32, pub min_height_factor: f32, - pub git: GitDiffColors + pub git: GitDiffColors, } #[derive(Clone, Deserialize, Default)] pub struct GitDiffColors { pub inserted: Color, pub modified: Color, - pub deleted: Color + pub deleted: Color, } #[derive(Clone, Deserialize, Default)] From 2d1c4a1971ffd8a214b9f21eec5e632b1a32acdd Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 15:31:14 -0700 Subject: [PATCH 110/131] Revert "Touch up git in project panel and scroll bar styling" --- crates/editor/src/editor.rs | 1 + crates/project_panel/src/project_panel.rs | 31 +++++------- crates/theme/src/theme.rs | 21 --------- crates/theme/src/ui.rs | 57 +++++++++++++++++++++-- styles/src/styleTree/editor.ts | 36 +++----------- styles/src/styleTree/projectPanel.ts | 19 +------- 6 files changed, 75 insertions(+), 90 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ce67a59bd3..57d5ec6e95 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7253,6 +7253,7 @@ impl View for Editor { } fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + dbg!("Editor Focus in"); if cx.is_self_focused() { let focused_event = EditorFocused(cx.handle()); cx.emit(Event::Focused); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index a7895172d5..ce0dd9e222 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -6,7 +6,7 @@ use gpui::{ actions, anyhow::{anyhow, Result}, elements::{ - AnchorCorner, ChildView, ContainerStyle, Empty, Flex, Label, MouseEventHandler, + AnchorCorner, ChildView, ComponentHost, ContainerStyle, Empty, Flex, MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, }, geometry::vector::Vector2F, @@ -28,7 +28,7 @@ use std::{ path::Path, sync::Arc, }; -use theme::ProjectPanelEntry; +use theme::{ui::FileName, ProjectPanelEntry}; use unicase::UniCase; use workspace::Workspace; @@ -1079,17 +1079,6 @@ impl ProjectPanel { let kind = details.kind; let show_editor = details.is_editing && !details.is_processing; - let mut filename_text_style = style.text.clone(); - filename_text_style.color = details - .git_status - .as_ref() - .map(|status| match status { - GitFileStatus::Added => style.status.git.inserted, - GitFileStatus::Modified => style.status.git.modified, - GitFileStatus::Conflict => style.status.git.conflict, - }) - .unwrap_or(style.text.color); - Flex::row() .with_child( if kind == EntryKind::Dir { @@ -1117,12 +1106,16 @@ impl ProjectPanel { .flex(1.0, true) .into_any() } else { - Label::new(details.filename.clone(), filename_text_style) - .contained() - .with_margin_left(style.icon_spacing) - .aligned() - .left() - .into_any() + ComponentHost::new(FileName::new( + details.filename.clone(), + details.git_status, + FileName::style(style.text.clone(), &theme::current(cx)), + )) + .contained() + .with_margin_left(style.icon_spacing) + .aligned() + .left() + .into_any() }) .constrained() .with_height(style.height) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index cd2bf90b7e..eb404cdaad 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -446,19 +446,6 @@ pub struct ProjectPanelEntry { pub icon_color: Color, pub icon_size: f32, pub icon_spacing: f32, - pub status: EntryStatus, -} - -#[derive(Clone, Debug, Deserialize, Default)] -pub struct EntryStatus { - pub git: GitProjectStatus, -} - -#[derive(Clone, Debug, Deserialize, Default)] -pub struct GitProjectStatus { - pub modified: Color, - pub inserted: Color, - pub conflict: Color, } #[derive(Clone, Debug, Deserialize, Default)] @@ -683,14 +670,6 @@ pub struct Scrollbar { pub thumb: ContainerStyle, pub width: f32, pub min_height_factor: f32, - pub git: GitDiffColors, -} - -#[derive(Clone, Deserialize, Default)] -pub struct GitDiffColors { - pub inserted: Color, - pub modified: Color, - pub deleted: Color, } #[derive(Clone, Deserialize, Default)] diff --git a/crates/theme/src/ui.rs b/crates/theme/src/ui.rs index b86bfca8c4..e4df24c89f 100644 --- a/crates/theme/src/ui.rs +++ b/crates/theme/src/ui.rs @@ -1,9 +1,10 @@ use std::borrow::Cow; +use fs::repository::GitFileStatus; use gpui::{ color::Color, elements::{ - ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label, + ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label, LabelStyle, MouseEventHandler, ParentElement, Stack, Svg, }, fonts::TextStyle, @@ -11,11 +12,11 @@ use gpui::{ platform, platform::MouseButton, scene::MouseClick, - Action, Element, EventContext, MouseState, View, ViewContext, + Action, AnyElement, Element, EventContext, MouseState, View, ViewContext, }; use serde::Deserialize; -use crate::{ContainedText, Interactive}; +use crate::{ContainedText, Interactive, Theme}; #[derive(Clone, Deserialize, Default)] pub struct CheckboxStyle { @@ -252,3 +253,53 @@ where .constrained() .with_height(style.dimensions().y()) } + +pub struct FileName { + filename: String, + git_status: Option, + style: FileNameStyle, +} + +pub struct FileNameStyle { + template_style: LabelStyle, + git_inserted: Color, + git_modified: Color, + git_deleted: Color, +} + +impl FileName { + pub fn new(filename: String, git_status: Option, style: FileNameStyle) -> Self { + FileName { + filename, + git_status, + style, + } + } + + pub fn style>(style: I, theme: &Theme) -> FileNameStyle { + FileNameStyle { + template_style: style.into(), + git_inserted: theme.editor.diff.inserted, + git_modified: theme.editor.diff.modified, + git_deleted: theme.editor.diff.deleted, + } + } +} + +impl gpui::elements::Component for FileName { + fn render(&self, _: &mut V, _: &mut ViewContext) -> AnyElement { + // Prepare colors for git statuses + let mut filename_text_style = self.style.template_style.text.clone(); + filename_text_style.color = self + .git_status + .as_ref() + .map(|status| match status { + GitFileStatus::Added => self.style.git_inserted, + GitFileStatus::Modified => self.style.git_modified, + GitFileStatus::Conflict => self.style.git_deleted, + }) + .unwrap_or(self.style.template_style.text.color); + + Label::new(self.filename.clone(), filename_text_style).into_any() + } +} diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index 91c1318359..7caa8b1c67 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -3,10 +3,9 @@ import { ColorScheme, Layer, StyleSets } from "../themes/common/colorScheme" import { background, border, borderColor, foreground, text } from "./components" import hoverPopover from "./hoverPopover" -import { buildSyntax } from "../themes/common/syntax" +import { SyntaxHighlightStyle, buildSyntax } from "../themes/common/syntax" export default function editor(colorScheme: ColorScheme) { - const { isLight } = colorScheme let layer = colorScheme.highest const autocompleteItem = { @@ -98,18 +97,12 @@ export default function editor(colorScheme: ColorScheme) { foldBackground: foreground(layer, "variant"), }, diff: { - deleted: isLight - ? colorScheme.ramps.red(0.5).hex() - : colorScheme.ramps.red(0.4).hex(), - modified: isLight - ? colorScheme.ramps.yellow(0.3).hex() - : colorScheme.ramps.yellow(0.5).hex(), - inserted: isLight - ? colorScheme.ramps.green(0.4).hex() - : colorScheme.ramps.green(0.5).hex(), + deleted: foreground(layer, "negative"), + modified: foreground(layer, "warning"), + inserted: foreground(layer, "positive"), removedWidthEm: 0.275, - widthEm: 0.15, - cornerRadius: 0.05, + widthEm: 0.22, + cornerRadius: 0.2, }, /** Highlights matching occurences of what is under the cursor * as well as matched brackets @@ -241,27 +234,12 @@ export default function editor(colorScheme: ColorScheme) { border: border(layer, "variant", { left: true }), }, thumb: { - background: withOpacity(background(layer, "inverted"), 0.3), + background: withOpacity(background(layer, "inverted"), 0.4), border: { width: 1, color: borderColor(layer, "variant"), - top: false, - right: true, - left: true, - bottom: false, }, }, - git: { - deleted: isLight - ? withOpacity(colorScheme.ramps.red(0.5).hex(), 0.8) - : withOpacity(colorScheme.ramps.red(0.4).hex(), 0.8), - modified: isLight - ? withOpacity(colorScheme.ramps.yellow(0.5).hex(), 0.8) - : withOpacity(colorScheme.ramps.yellow(0.4).hex(), 0.8), - inserted: isLight - ? withOpacity(colorScheme.ramps.green(0.5).hex(), 0.8) - : withOpacity(colorScheme.ramps.green(0.4).hex(), 0.8), - } }, compositionMark: { underline: { diff --git a/styles/src/styleTree/projectPanel.ts b/styles/src/styleTree/projectPanel.ts index 7af5952154..3d06a683ab 100644 --- a/styles/src/styleTree/projectPanel.ts +++ b/styles/src/styleTree/projectPanel.ts @@ -3,7 +3,6 @@ import { withOpacity } from "../utils/color" import { background, border, foreground, text } from "./components" export default function projectPanel(colorScheme: ColorScheme) { - const { isLight } = colorScheme let layer = colorScheme.middle let baseEntry = { @@ -13,20 +12,6 @@ export default function projectPanel(colorScheme: ColorScheme) { iconSpacing: 8, } - let status = { - git: { - modified: isLight - ? colorScheme.ramps.yellow(0.6).hex() - : colorScheme.ramps.yellow(0.5).hex(), - inserted: isLight - ? colorScheme.ramps.green(0.45).hex() - : colorScheme.ramps.green(0.5).hex(), - conflict: isLight - ? colorScheme.ramps.red(0.6).hex() - : colorScheme.ramps.red(0.5).hex() - } - } - let entry = { ...baseEntry, text: text(layer, "mono", "variant", { size: "sm" }), @@ -43,7 +28,6 @@ export default function projectPanel(colorScheme: ColorScheme) { background: background(layer, "active"), text: text(layer, "mono", "active", { size: "sm" }), }, - status } return { @@ -75,7 +59,6 @@ export default function projectPanel(colorScheme: ColorScheme) { entry, draggedEntry: { ...baseEntry, - status, text: text(layer, "mono", "on", { size: "sm" }), background: withOpacity(background(layer, "on"), 0.9), border: border(layer), @@ -96,6 +79,6 @@ export default function projectPanel(colorScheme: ColorScheme) { background: background(layer, "on"), text: text(layer, "mono", "on", { size: "sm" }), selection: colorScheme.players[0], - } + }, } } From 5c31c84c90768cd2c1af3dc797e27a20718fe9cc Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 15:51:31 -0700 Subject: [PATCH 111/131] Fixed contrast in project panel and scrollbar co-authored-by: nate --- crates/diagnostics/src/diagnostics.rs | 5 +- crates/editor/src/editor.rs | 1 - crates/project_panel/src/project_panel.rs | 33 +++++++------ crates/theme/src/theme.rs | 23 ++++++++- crates/theme/src/ui.rs | 57 ++--------------------- styles/src/styleTree/editor.ts | 41 ++++++++++++---- styles/src/styleTree/projectPanel.ts | 18 +++++++ 7 files changed, 97 insertions(+), 81 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 29ea1273e2..182efdfdd6 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -105,9 +105,8 @@ impl View for ProjectDiagnosticsEditor { } fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - dbg!("Focus in"); - if dbg!(cx.is_self_focused()) && dbg!(!self.path_states.is_empty()) { - dbg!(cx.focus(&self.editor)); + if cx.is_self_focused() && !self.path_states.is_empty() { + cx.focus(&self.editor); } } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 57d5ec6e95..ce67a59bd3 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7253,7 +7253,6 @@ impl View for Editor { } fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - dbg!("Editor Focus in"); if cx.is_self_focused() { let focused_event = EditorFocused(cx.handle()); cx.emit(Event::Focused); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index ce0dd9e222..b67af5898e 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -6,8 +6,8 @@ use gpui::{ actions, anyhow::{anyhow, Result}, elements::{ - AnchorCorner, ChildView, ComponentHost, ContainerStyle, Empty, Flex, MouseEventHandler, - ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, + AnchorCorner, ChildView, ContainerStyle, Empty, Flex, MouseEventHandler, + ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, Label, }, geometry::vector::Vector2F, keymap_matcher::KeymapContext, @@ -28,7 +28,7 @@ use std::{ path::Path, sync::Arc, }; -use theme::{ui::FileName, ProjectPanelEntry}; +use theme::ProjectPanelEntry; use unicase::UniCase; use workspace::Workspace; @@ -1079,6 +1079,17 @@ impl ProjectPanel { let kind = details.kind; let show_editor = details.is_editing && !details.is_processing; + let mut filename_text_style = style.text.clone(); + filename_text_style.color = details + .git_status + .as_ref() + .map(|status| match status { + GitFileStatus::Added => style.status.git.inserted, + GitFileStatus::Modified => style.status.git.modified, + GitFileStatus::Conflict => style.status.git.conflict, + }) + .unwrap_or(style.text.color); + Flex::row() .with_child( if kind == EntryKind::Dir { @@ -1106,16 +1117,12 @@ impl ProjectPanel { .flex(1.0, true) .into_any() } else { - ComponentHost::new(FileName::new( - details.filename.clone(), - details.git_status, - FileName::style(style.text.clone(), &theme::current(cx)), - )) - .contained() - .with_margin_left(style.icon_spacing) - .aligned() - .left() - .into_any() + Label::new(details.filename.clone(), filename_text_style) + .contained() + .with_margin_left(style.icon_spacing) + .aligned() + .left() + .into_any() }) .constrained() .with_height(style.height) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index eb404cdaad..f23493c75d 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -446,7 +446,20 @@ pub struct ProjectPanelEntry { pub icon_color: Color, pub icon_size: f32, pub icon_spacing: f32, -} + pub status: EntryStatus, + } + + #[derive(Clone, Debug, Deserialize, Default)] + pub struct EntryStatus { + pub git: GitProjectStatus, + } + + #[derive(Clone, Debug, Deserialize, Default)] + pub struct GitProjectStatus { + pub modified: Color, + pub inserted: Color, + pub conflict: Color, + } #[derive(Clone, Debug, Deserialize, Default)] pub struct ContextMenu { @@ -670,6 +683,14 @@ pub struct Scrollbar { pub thumb: ContainerStyle, pub width: f32, pub min_height_factor: f32, + pub git: GitDiffColors, +} + +#[derive(Clone, Deserialize, Default)] +pub struct GitDiffColors { + pub inserted: Color, + pub modified: Color, + pub deleted: Color, } #[derive(Clone, Deserialize, Default)] diff --git a/crates/theme/src/ui.rs b/crates/theme/src/ui.rs index e4df24c89f..b86bfca8c4 100644 --- a/crates/theme/src/ui.rs +++ b/crates/theme/src/ui.rs @@ -1,10 +1,9 @@ use std::borrow::Cow; -use fs::repository::GitFileStatus; use gpui::{ color::Color, elements::{ - ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label, LabelStyle, + ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label, MouseEventHandler, ParentElement, Stack, Svg, }, fonts::TextStyle, @@ -12,11 +11,11 @@ use gpui::{ platform, platform::MouseButton, scene::MouseClick, - Action, AnyElement, Element, EventContext, MouseState, View, ViewContext, + Action, Element, EventContext, MouseState, View, ViewContext, }; use serde::Deserialize; -use crate::{ContainedText, Interactive, Theme}; +use crate::{ContainedText, Interactive}; #[derive(Clone, Deserialize, Default)] pub struct CheckboxStyle { @@ -253,53 +252,3 @@ where .constrained() .with_height(style.dimensions().y()) } - -pub struct FileName { - filename: String, - git_status: Option, - style: FileNameStyle, -} - -pub struct FileNameStyle { - template_style: LabelStyle, - git_inserted: Color, - git_modified: Color, - git_deleted: Color, -} - -impl FileName { - pub fn new(filename: String, git_status: Option, style: FileNameStyle) -> Self { - FileName { - filename, - git_status, - style, - } - } - - pub fn style>(style: I, theme: &Theme) -> FileNameStyle { - FileNameStyle { - template_style: style.into(), - git_inserted: theme.editor.diff.inserted, - git_modified: theme.editor.diff.modified, - git_deleted: theme.editor.diff.deleted, - } - } -} - -impl gpui::elements::Component for FileName { - fn render(&self, _: &mut V, _: &mut ViewContext) -> AnyElement { - // Prepare colors for git statuses - let mut filename_text_style = self.style.template_style.text.clone(); - filename_text_style.color = self - .git_status - .as_ref() - .map(|status| match status { - GitFileStatus::Added => self.style.git_inserted, - GitFileStatus::Modified => self.style.git_modified, - GitFileStatus::Conflict => self.style.git_deleted, - }) - .unwrap_or(self.style.template_style.text.color); - - Label::new(self.filename.clone(), filename_text_style).into_any() - } -} diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index 7caa8b1c67..deeff855ff 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -6,6 +6,8 @@ import hoverPopover from "./hoverPopover" import { SyntaxHighlightStyle, buildSyntax } from "../themes/common/syntax" export default function editor(colorScheme: ColorScheme) { + const { isLight } = colorScheme + let layer = colorScheme.highest const autocompleteItem = { @@ -97,12 +99,18 @@ export default function editor(colorScheme: ColorScheme) { foldBackground: foreground(layer, "variant"), }, diff: { - deleted: foreground(layer, "negative"), - modified: foreground(layer, "warning"), - inserted: foreground(layer, "positive"), + deleted: isLight + ? colorScheme.ramps.red(0.5).hex() + : colorScheme.ramps.red(0.4).hex(), + modified: isLight + ? colorScheme.ramps.yellow(0.3).hex() + : colorScheme.ramps.yellow(0.5).hex(), + inserted: isLight + ? colorScheme.ramps.green(0.4).hex() + : colorScheme.ramps.green(0.5).hex(), removedWidthEm: 0.275, - widthEm: 0.22, - cornerRadius: 0.2, + widthEm: 0.15, + cornerRadius: 0.05, }, /** Highlights matching occurences of what is under the cursor * as well as matched brackets @@ -234,12 +242,27 @@ export default function editor(colorScheme: ColorScheme) { border: border(layer, "variant", { left: true }), }, thumb: { - background: withOpacity(background(layer, "inverted"), 0.4), + background: withOpacity(background(layer, "inverted"), 0.3), border: { - width: 1, - color: borderColor(layer, "variant"), - }, + width: 1, + color: borderColor(layer, "variant"), + top: false, + right: true, + left: true, + bottom: false, + } }, + git: { + deleted: isLight + ? withOpacity(colorScheme.ramps.red(0.5).hex(), 0.8) + : withOpacity(colorScheme.ramps.red(0.4).hex(), 0.8), + modified: isLight + ? withOpacity(colorScheme.ramps.yellow(0.5).hex(), 0.8) + : withOpacity(colorScheme.ramps.yellow(0.4).hex(), 0.8), + inserted: isLight + ? withOpacity(colorScheme.ramps.green(0.5).hex(), 0.8) + : withOpacity(colorScheme.ramps.green(0.4).hex(), 0.8), + } }, compositionMark: { underline: { diff --git a/styles/src/styleTree/projectPanel.ts b/styles/src/styleTree/projectPanel.ts index 3d06a683ab..08117bf6b0 100644 --- a/styles/src/styleTree/projectPanel.ts +++ b/styles/src/styleTree/projectPanel.ts @@ -3,6 +3,8 @@ import { withOpacity } from "../utils/color" import { background, border, foreground, text } from "./components" export default function projectPanel(colorScheme: ColorScheme) { + const { isLight } = colorScheme + let layer = colorScheme.middle let baseEntry = { @@ -12,6 +14,20 @@ export default function projectPanel(colorScheme: ColorScheme) { iconSpacing: 8, } + let status = { + git: { + modified: isLight + ? colorScheme.ramps.yellow(0.6).hex() + : colorScheme.ramps.yellow(0.5).hex(), + inserted: isLight + ? colorScheme.ramps.green(0.45).hex() + : colorScheme.ramps.green(0.5).hex(), + conflict: isLight + ? colorScheme.ramps.red(0.6).hex() + : colorScheme.ramps.red(0.5).hex() + } + } + let entry = { ...baseEntry, text: text(layer, "mono", "variant", { size: "sm" }), @@ -28,6 +44,7 @@ export default function projectPanel(colorScheme: ColorScheme) { background: background(layer, "active"), text: text(layer, "mono", "active", { size: "sm" }), }, + status } return { @@ -62,6 +79,7 @@ export default function projectPanel(colorScheme: ColorScheme) { text: text(layer, "mono", "on", { size: "sm" }), background: withOpacity(background(layer, "on"), 0.9), border: border(layer), + status }, ignoredEntry: { ...entry, From e4454f1e7fa245f5dfae1fa80ec2aabd0f973ea8 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 15:53:29 -0700 Subject: [PATCH 112/131] fmt --- crates/project_panel/src/project_panel.rs | 4 ++-- crates/theme/src/theme.rs | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index b67af5898e..a7895172d5 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -6,8 +6,8 @@ use gpui::{ actions, anyhow::{anyhow, Result}, elements::{ - AnchorCorner, ChildView, ContainerStyle, Empty, Flex, MouseEventHandler, - ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, Label, + AnchorCorner, ChildView, ContainerStyle, Empty, Flex, Label, MouseEventHandler, + ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, }, geometry::vector::Vector2F, keymap_matcher::KeymapContext, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index f23493c75d..cd2bf90b7e 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -447,19 +447,19 @@ pub struct ProjectPanelEntry { pub icon_size: f32, pub icon_spacing: f32, pub status: EntryStatus, - } +} - #[derive(Clone, Debug, Deserialize, Default)] - pub struct EntryStatus { - pub git: GitProjectStatus, - } +#[derive(Clone, Debug, Deserialize, Default)] +pub struct EntryStatus { + pub git: GitProjectStatus, +} - #[derive(Clone, Debug, Deserialize, Default)] - pub struct GitProjectStatus { - pub modified: Color, - pub inserted: Color, - pub conflict: Color, - } +#[derive(Clone, Debug, Deserialize, Default)] +pub struct GitProjectStatus { + pub modified: Color, + pub inserted: Color, + pub conflict: Color, +} #[derive(Clone, Debug, Deserialize, Default)] pub struct ContextMenu { From fc8248912864fc0df1e8dd9f7dbb842ea41348d2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 16:23:36 -0700 Subject: [PATCH 113/131] Update scrollbar styling --- crates/editor/src/element.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 13a24bfc2c..4e5863407f 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1053,7 +1053,7 @@ impl EditorElement { }); if layout.is_singleton && settings::get::(cx).scrollbar.git_diff { - let diff_style = theme::current(cx).editor.diff.clone(); + let diff_style = theme::current(cx).editor.scrollbar.git.clone(); for hunk in layout .position_map .snapshot From 51d94f532b6bc6f1e4e207bb16d56b0a1d288079 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 18:26:33 -0700 Subject: [PATCH 114/131] Load diff base for buffers that are opening but not yet opened when repositories are discovered --- crates/project/src/project.rs | 120 +++++++++++++++++++++++++-------- crates/project/src/worktree.rs | 1 + 2 files changed, 92 insertions(+), 29 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5b5d903822..dd53c30d14 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -16,6 +16,7 @@ use copilot::Copilot; use futures::{ channel::mpsc::{self, UnboundedReceiver}, future::{try_join_all, Shared}, + stream::FuturesUnordered, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt, }; use globset::{Glob, GlobSet, GlobSetBuilder}; @@ -1374,7 +1375,7 @@ impl Project { return Task::ready(Ok(existing_buffer)); } - let mut loading_watch = match self.loading_buffers_by_path.entry(project_path.clone()) { + let loading_watch = match self.loading_buffers_by_path.entry(project_path.clone()) { // If the given path is already being loaded, then wait for that existing // task to complete and return the same buffer. hash_map::Entry::Occupied(e) => e.get().clone(), @@ -1405,15 +1406,9 @@ impl Project { }; cx.foreground().spawn(async move { - loop { - if let Some(result) = loading_watch.borrow().as_ref() { - match result { - Ok(buffer) => return Ok(buffer.clone()), - Err(error) => return Err(anyhow!("{}", error)), - } - } - loading_watch.next().await; - } + pump_loading_buffer_reciever(loading_watch) + .await + .map_err(|error| anyhow!("{}", error)) }) } @@ -4822,6 +4817,51 @@ impl Project { ) { debug_assert!(worktree_handle.read(cx).is_local()); + // Setup the pending buffers + let future_buffers = self + .loading_buffers_by_path + .iter() + .filter_map(|(path, receiver)| { + let path = &path.path; + let (work_directory, repo) = repos + .iter() + .find(|(work_directory, _)| path.starts_with(work_directory))?; + + let repo_relative_path = path.strip_prefix(work_directory).log_err()?; + + let receiver = receiver.clone(); + let repo_ptr = repo.repo_ptr.clone(); + let repo_relative_path = repo_relative_path.to_owned(); + Some(async move { + pump_loading_buffer_reciever(receiver) + .await + .ok() + .map(|buffer| (buffer, repo_relative_path, repo_ptr)) + }) + }) + .collect::>() + .filter_map(|result| async move { + let (buffer_handle, repo_relative_path, repo_ptr) = result?; + + let lock = repo_ptr.lock(); + lock.load_index_text(&repo_relative_path) + .map(|diff_base| (diff_base, buffer_handle)) + }); + + let update_diff_base_fn = update_diff_base(self); + cx.spawn(|_, mut cx| async move { + let diff_base_tasks = cx + .background() + .spawn(future_buffers.collect::>()) + .await; + + for (diff_base, buffer) in diff_base_tasks.into_iter() { + update_diff_base_fn(Some(diff_base), buffer, &mut cx); + } + }) + .detach(); + + // And the current buffers for (_, buffer) in &self.opened_buffers { if let Some(buffer) = buffer.upgrade(cx) { let file = match File::from_dyn(buffer.read(cx).file()) { @@ -4841,18 +4881,17 @@ impl Project { .find(|(work_directory, _)| path.starts_with(work_directory)) { Some(repo) => repo.clone(), - None => return, + None => continue, }; let relative_repo = match path.strip_prefix(work_directory).log_err() { Some(relative_repo) => relative_repo.to_owned(), - None => return, + None => continue, }; drop(worktree); - let remote_id = self.remote_id(); - let client = self.client.clone(); + let update_diff_base_fn = update_diff_base(self); let git_ptr = repo.repo_ptr.clone(); let diff_base_task = cx .background() @@ -4860,21 +4899,7 @@ impl Project { cx.spawn(|_, mut cx| async move { let diff_base = diff_base_task.await; - - let buffer_id = buffer.update(&mut cx, |buffer, cx| { - buffer.set_diff_base(diff_base.clone(), cx); - buffer.remote_id() - }); - - if let Some(project_id) = remote_id { - client - .send(proto::UpdateDiffBase { - project_id, - buffer_id: buffer_id as u64, - diff_base, - }) - .log_err(); - } + update_diff_base_fn(diff_base, buffer, &mut cx); }) .detach(); } @@ -6764,3 +6789,40 @@ impl Item for Buffer { }) } } + +async fn pump_loading_buffer_reciever( + mut receiver: postage::watch::Receiver, Arc>>>, +) -> Result, Arc> { + loop { + if let Some(result) = receiver.borrow().as_ref() { + match result { + Ok(buffer) => return Ok(buffer.to_owned()), + Err(e) => return Err(e.to_owned()), + } + } + receiver.next().await; + } +} + +fn update_diff_base( + project: &Project, +) -> impl Fn(Option, ModelHandle, &mut AsyncAppContext) { + let remote_id = project.remote_id(); + let client = project.client().clone(); + move |diff_base, buffer, cx| { + let buffer_id = buffer.update(cx, |buffer, cx| { + buffer.set_diff_base(diff_base.clone(), cx); + buffer.remote_id() + }); + + if let Some(project_id) = remote_id { + client + .send(proto::UpdateDiffBase { + project_id, + buffer_id: buffer_id as u64, + diff_base, + }) + .log_err(); + } + } +} diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 2e10594378..d2c035f916 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -839,6 +839,7 @@ impl LocalWorktree { fn set_snapshot(&mut self, new_snapshot: LocalSnapshot, cx: &mut ModelContext) { let updated_repos = self.changed_repos(&self.git_repositories, &new_snapshot.git_repositories); + self.snapshot = new_snapshot; if let Some(share) = self.share.as_mut() { From 9307719a4b4f9419347314c9f79ab14107eebee8 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 18:40:12 -0700 Subject: [PATCH 115/131] Add run until parked for test --- crates/collab/src/tests/integration_tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 807510d705..439ee0786a 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -2688,6 +2688,7 @@ async fn test_git_branch_name( }); let project_remote_c = client_c.build_remote_project(project_id, cx_c).await; + deterministic.run_until_parked(); project_remote_c.read_with(cx_c, |project, cx| { assert_branch(Some("branch-2"), project, cx) }); From 049b72e3ac7d606840346b9ff01e0e988384dcc5 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Mon, 22 May 2023 21:45:58 -0400 Subject: [PATCH 116/131] Add architecture to clickhouse event request body --- crates/client/src/telemetry.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index b3bdc72c91..cf47700dfe 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -10,6 +10,7 @@ use parking_lot::Mutex; use serde::Serialize; use serde_json::json; use std::{ + env, io::Write, mem, path::PathBuf, @@ -33,8 +34,9 @@ struct TelemetryState { installation_id: Option>, // Per app installation app_version: Option>, release_channel: Option<&'static str>, - os_version: Option>, os_name: &'static str, + os_version: Option>, + architecture: &'static str, mixpanel_events_queue: Vec, clickhouse_events_queue: Vec, next_mixpanel_event_id: usize, @@ -63,6 +65,7 @@ struct ClickhouseEventRequestBody { app_version: Option>, os_name: &'static str, os_version: Option>, + architecture: &'static str, release_channel: Option<&'static str>, events: Vec, } @@ -153,12 +156,14 @@ impl Telemetry { } else { None }; + // TODO: Replace all hardware stuff with nested SystemSpecs json let this = Arc::new(Self { http_client: client, executor: cx.background().clone(), state: Mutex::new(TelemetryState { - os_version: platform.os_version().ok().map(|v| v.to_string().into()), os_name: platform.os_name().into(), + os_version: platform.os_version().ok().map(|v| v.to_string().into()), + architecture: env::consts::ARCH, app_version: platform.app_version().ok().map(|v| v.to_string().into()), release_channel, installation_id: None, @@ -451,6 +456,8 @@ impl Telemetry { app_version: state.app_version.clone(), os_name: state.os_name, os_version: state.os_version.clone(), + architecture: state.architecture, + release_channel: state.release_channel, events, }, From 7be8dead07b86683a627a1fa2969b3947fc44f1c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 20:23:07 -0700 Subject: [PATCH 117/131] Add initial project panel settings --- Cargo.lock | 4 +++ assets/settings/default.json | 4 +++ crates/project_panel/Cargo.toml | 6 ++++ crates/project_panel/src/project_panel.rs | 7 ++++- .../src/project_panel_settings.rs | 29 +++++++++++++++++++ 5 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 crates/project_panel/src/project_panel_settings.rs diff --git a/Cargo.lock b/Cargo.lock index 3ae96f4751..981005f3f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4886,6 +4886,7 @@ dependencies = [ name = "project_panel" version = "0.1.0" dependencies = [ + "anyhow", "client", "context_menu", "drag_and_drop", @@ -4896,6 +4897,9 @@ dependencies = [ "menu", "postage", "project", + "schemars", + "serde", + "serde_derive", "serde_json", "settings", "theme", diff --git a/assets/settings/default.json b/assets/settings/default.json index e4e7a8c522..38d0ce820b 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -70,6 +70,10 @@ // Whether to show git diff indicators in the scrollbar. "git_diff": true }, + "project_panel": { + // Whether to show the git status in the project panel. + "git_status": true + }, // Whether the screen sharing icon is shown in the os status bar. "show_call_status_icon": true, // Whether to use language servers to provide code intelligence. diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml index 6fcdf06d2c..01d1be53b9 100644 --- a/crates/project_panel/Cargo.toml +++ b/crates/project_panel/Cargo.toml @@ -21,6 +21,12 @@ util = { path = "../util" } workspace = { path = "../workspace" } postage.workspace = true futures.workspace = true +serde.workspace = true +serde_derive.workspace = true +serde_json.workspace = true +anyhow.workspace = true +schemars.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 a7895172d5..d4cd007454 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1,3 +1,5 @@ +mod project_panel_settings; + use context_menu::{ContextMenu, ContextMenuItem}; use drag_and_drop::{DragAndDrop, Draggable}; use editor::{Cancel, Editor}; @@ -20,6 +22,7 @@ use project::{ repository::GitFileStatus, Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId, }; +use project_panel_settings::ProjectPanelSettings; use std::{ cmp::Ordering, collections::{hash_map, HashMap}, @@ -111,6 +114,7 @@ actions!( ); pub fn init(cx: &mut AppContext) { + settings::register::(cx); cx.add_action(ProjectPanel::expand_selected_entry); cx.add_action(ProjectPanel::collapse_selected_entry); cx.add_action(ProjectPanel::select_prev); @@ -1000,6 +1004,7 @@ impl ProjectPanel { } let end_ix = range.end.min(ix + visible_worktree_entries.len()); + let git_status_setting = settings::get::(cx).git_status; if let Some(worktree) = self.project.read(cx).worktree_for_id(*worktree_id, cx) { let snapshot = worktree.read(cx).snapshot(); let root_name = OsStr::new(snapshot.root_name()); @@ -1013,7 +1018,7 @@ impl ProjectPanel { for (entry, repo) in snapshot.entries_with_repositories(visible_worktree_entries[entry_range].iter()) { - let status = (entry.path.parent().is_some() && !entry.is_ignored) + let status = (git_status_setting && entry.path.parent().is_some() && !entry.is_ignored) .then(|| repo.and_then(|repo| repo.status_for_path(&snapshot, &entry.path))) .flatten(); diff --git a/crates/project_panel/src/project_panel_settings.rs b/crates/project_panel/src/project_panel_settings.rs new file mode 100644 index 0000000000..01a15d089b --- /dev/null +++ b/crates/project_panel/src/project_panel_settings.rs @@ -0,0 +1,29 @@ +use serde_derive::{Deserialize, Serialize}; +use anyhow; +use settings::Setting; +use schemars::JsonSchema; + + +#[derive(Deserialize, Debug)] +pub struct ProjectPanelSettings { + pub git_status: bool, +} + +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +pub struct ProjectPanelSettingsContent { + pub git_status: Option, +} + +impl Setting for ProjectPanelSettings { + const KEY: Option<&'static str> = Some("project_panel"); + + type FileContent = ProjectPanelSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &gpui::AppContext, + ) -> anyhow::Result { + Self::load_via_json_merge(default_value, user_values) + } +} From da96802bf03644d4f056ff4bd8f8be97fd3e069b Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 20:25:27 -0700 Subject: [PATCH 118/131] fmt --- crates/project_panel/src/project_panel.rs | 4 +++- crates/project_panel/src/project_panel_settings.rs | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index d4cd007454..66f0fe7718 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1018,7 +1018,9 @@ impl ProjectPanel { for (entry, repo) in snapshot.entries_with_repositories(visible_worktree_entries[entry_range].iter()) { - let status = (git_status_setting && entry.path.parent().is_some() && !entry.is_ignored) + let status = (git_status_setting + && entry.path.parent().is_some() + && !entry.is_ignored) .then(|| repo.and_then(|repo| repo.status_for_path(&snapshot, &entry.path))) .flatten(); diff --git a/crates/project_panel/src/project_panel_settings.rs b/crates/project_panel/src/project_panel_settings.rs index 01a15d089b..7786f1b438 100644 --- a/crates/project_panel/src/project_panel_settings.rs +++ b/crates/project_panel/src/project_panel_settings.rs @@ -1,8 +1,7 @@ -use serde_derive::{Deserialize, Serialize}; use anyhow; -use settings::Setting; use schemars::JsonSchema; - +use serde_derive::{Deserialize, Serialize}; +use settings::Setting; #[derive(Deserialize, Debug)] pub struct ProjectPanelSettings { From b19a712799a042596d8e75654c9419a70b946532 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 20:33:18 -0700 Subject: [PATCH 119/131] Add init_settings to tests --- crates/project_panel/src/project_panel.rs | 7 ++++++- crates/zed/src/zed.rs | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 66f0fe7718..40ba0b7e67 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -113,8 +113,12 @@ actions!( ] ); -pub fn init(cx: &mut AppContext) { +pub fn init_settings(cx: &mut AppContext) { settings::register::(cx); +} + +pub fn init(cx: &mut AppContext) { + init_settings(cx); cx.add_action(ProjectPanel::expand_selected_entry); cx.add_action(ProjectPanel::collapse_selected_entry); cx.add_action(ProjectPanel::select_prev); @@ -2051,6 +2055,7 @@ mod tests { cx.foreground().forbid_parking(); cx.update(|cx| { cx.set_global(SettingsStore::test(cx)); + init_settings(cx); theme::init((), cx); language::init(cx); editor::init_settings(cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 376ecfc06b..95d09e82ec 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2068,6 +2068,7 @@ mod tests { workspace::init(app_state.clone(), cx); language::init(cx); editor::init(cx); + project_panel::init_settings(cx); pane::init(cx); app_state }) From a37b1b6a45c97f99cf236ffb71d3e27e42f72f4b Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 May 2023 20:38:30 -0700 Subject: [PATCH 120/131] Add other setting init --- crates/project_panel/src/project_panel.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 40ba0b7e67..69d8b8be4d 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -2068,6 +2068,7 @@ mod tests { cx.update(|cx| { let app_state = AppState::test(cx); theme::init((), cx); + init_settings(cx); language::init(cx); editor::init(cx); pane::init(cx); From 19b817e48aa372329b0dde3597651d9a760ea3b0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 23 May 2023 09:02:45 +0200 Subject: [PATCH 121/131] Improve styling of bottom and right docks --- crates/theme/src/theme.rs | 6 +++--- crates/workspace/src/dock.rs | 17 +++++++++++++---- styles/src/styleTree/workspace.ts | 11 +++++++++-- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 31a9f5cc03..b1c9e9c215 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -402,9 +402,9 @@ pub struct StatusBarLspStatus { #[derive(Deserialize, Default)] pub struct Dock { - pub initial_size: f32, - #[serde(flatten)] - pub container: ContainerStyle, + pub left: ContainerStyle, + pub bottom: ContainerStyle, + pub right: ContainerStyle, } #[derive(Clone, Deserialize, Default)] diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 7e6868f9b0..6ca78cd935 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -372,11 +372,10 @@ impl Dock { pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement { if let Some(active_entry) = self.active_entry() { - let style = &settings::get::(cx).theme.workspace.dock; Empty::new() .into_any() .contained() - .with_style(style.container) + .with_style(self.style(cx)) .resizable( self.position.to_resize_handle_side(), active_entry.panel.size(cx), @@ -387,6 +386,16 @@ impl Dock { Empty::new().into_any() } } + + fn style(&self, cx: &WindowContext) -> ContainerStyle { + let theme = &settings::get::(cx).theme; + let style = match self.position { + DockPosition::Left => theme.workspace.dock.left, + DockPosition::Bottom => theme.workspace.dock.bottom, + DockPosition::Right => theme.workspace.dock.right, + }; + style + } } impl Entity for Dock { @@ -400,10 +409,10 @@ impl View for Dock { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(active_entry) = self.active_entry() { - let style = &settings::get::(cx).theme.workspace.dock; + let style = self.style(cx); ChildView::new(active_entry.panel.as_any(), cx) .contained() - .with_style(style.container) + .with_style(style) .resizable( self.position.to_resize_handle_side(), active_entry.panel.size(cx), diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 501debc876..737d225784 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -128,8 +128,15 @@ export default function workspace(colorScheme: ColorScheme) { border: border(colorScheme.highest, { overlay: true }), }, dock: { - initialSize: 240, - border: border(layer, { left: true, right: true }), + left: { + border: border(layer, { right: true }), + }, + bottom: { + border: border(layer, { top: true }), + }, + right: { + border: border(layer, { left: true }), + } }, paneDivider: { color: borderColor(layer), From 774530dd05494f44f66a218c64508dc5a9139324 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 23 May 2023 15:32:55 +0200 Subject: [PATCH 122/131] Update LiveKit client SDK to 1.0.12 Co-Authored-By: Julia Risley --- .../LiveKitBridge/Package.resolved | 20 +++++++++---------- .../LiveKitBridge/Package.swift | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/live_kit_client/LiveKitBridge/Package.resolved b/crates/live_kit_client/LiveKitBridge/Package.resolved index 9318cc0184..85ae088565 100644 --- a/crates/live_kit_client/LiveKitBridge/Package.resolved +++ b/crates/live_kit_client/LiveKitBridge/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/livekit/client-sdk-swift.git", "state": { "branch": null, - "revision": "f6ca534eb334e99acb8e82cc99b491717df28d8a", - "version": null + "revision": "7331b813a5ab8a95cfb81fb2b4ed10519428b9ff", + "version": "1.0.12" } }, { @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/google/promises.git", "state": { "branch": null, - "revision": "3e4e743631e86c8c70dbc6efdc7beaa6e90fd3bb", - "version": "2.1.1" + "revision": "ec957ccddbcc710ccc64c9dcbd4c7006fcf8b73a", + "version": "2.2.0" } }, { @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/webrtc-sdk/Specs.git", "state": { "branch": null, - "revision": "38ac06261e62f980652278c69b70284324c769e0", - "version": "104.5112.5" + "revision": "2f6bab30c8df0fe59ab3e58bc99097f757f85f65", + "version": "104.5112.17" } }, { @@ -33,8 +33,8 @@ "repositoryURL": "https://github.com/apple/swift-log.git", "state": { "branch": null, - "revision": "6fe203dc33195667ce1759bf0182975e4653ba1c", - "version": "1.4.4" + "revision": "32e8d724467f8fe623624570367e3d50c5638e46", + "version": "1.5.2" } }, { @@ -42,8 +42,8 @@ "repositoryURL": "https://github.com/apple/swift-protobuf.git", "state": { "branch": null, - "revision": "88c7d15e1242fdb6ecbafbc7926426a19be1e98a", - "version": "1.20.2" + "revision": "0af9125c4eae12a4973fb66574c53a54962a9e1e", + "version": "1.21.0" } } ] diff --git a/crates/live_kit_client/LiveKitBridge/Package.swift b/crates/live_kit_client/LiveKitBridge/Package.swift index bdd664c6fb..d7b5c271b9 100644 --- a/crates/live_kit_client/LiveKitBridge/Package.swift +++ b/crates/live_kit_client/LiveKitBridge/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["LiveKitBridge"]), ], dependencies: [ - .package(url: "https://github.com/livekit/client-sdk-swift.git", revision: "f6ca534eb334e99acb8e82cc99b491717df28d8a"), + .package(url: "https://github.com/livekit/client-sdk-swift.git", .exact("1.0.12")), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. From 2457c55fe7a03633bc6dc3949ae2059eb622ce81 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 23 May 2023 13:53:56 -0400 Subject: [PATCH 123/131] add toggle right and bottom dock actions --- crates/workspace/src/workspace.rs | 8 ++++++++ crates/zed/src/menus.rs | 2 ++ 2 files changed, 10 insertions(+) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 495989c1be..40d170b0de 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -119,6 +119,8 @@ actions!( ActivateNextPane, FollowNextCollaborator, ToggleLeftDock, + ToggleRightDock, + ToggleBottomDock, NewTerminal, ToggleTerminalFocus, NewSearch, @@ -250,6 +252,12 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| { workspace.toggle_dock(DockPosition::Left, cx); }); + cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| { + workspace.toggle_dock(DockPosition::Right, cx); + }); + cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| { + workspace.toggle_dock(DockPosition::Bottom, cx); + }); cx.add_action(Workspace::activate_pane_at_index); cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| { diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index a98147c51b..37e835c13d 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -90,6 +90,8 @@ pub fn menus() -> Vec> { MenuItem::action("Reset Zoom", super::ResetBufferFontSize), MenuItem::separator(), MenuItem::action("Toggle Left Dock", workspace::ToggleLeftDock), + MenuItem::action("Toggle Right Dock", workspace::ToggleRightDock), + MenuItem::action("Toggle Bottom Dock", workspace::ToggleBottomDock), MenuItem::submenu(Menu { name: "Editor Layout", items: vec![ From e6857866d4ea53a80c80044458ae6d19c02250a7 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 23 May 2023 22:23:38 -0400 Subject: [PATCH 124/131] Update terminal panel tooltip --- crates/terminal_view/src/terminal_panel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 9c8073eada..f265c53552 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -347,7 +347,7 @@ impl Panel for TerminalPanel { } fn icon_tooltip(&self) -> String { - "Terminals".to_string() + "Terminal Panel".into() } fn icon_label(&self, cx: &WindowContext) -> Option { From 168ff9992798b05c4fe392a3c7dbb1381fbb12da Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 24 May 2023 10:34:56 +0200 Subject: [PATCH 125/131] Prevent empty panes from being zoomed --- crates/workspace/src/pane.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index e1079f201b..a2b13eb0cc 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -683,7 +683,7 @@ impl Pane { pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { if self.zoomed { cx.emit(Event::ZoomOut); - } else { + } else if !self.items.is_empty() { cx.emit(Event::ZoomIn); } } @@ -983,6 +983,10 @@ impl Pane { .remove(&item.id()); } + if self.items.is_empty() && self.zoomed { + cx.emit(Event::ZoomOut); + } + cx.notify(); } From 1a353ad25d6e3600bf23bac304cfa23dbb96d1f6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 24 May 2023 11:00:12 +0200 Subject: [PATCH 126/131] Show maximize/minimize icon for panes and terminal panel --- crates/terminal_view/src/terminal_panel.rs | 47 ++++++++++++++-------- crates/workspace/src/pane.rs | 16 +++++++- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index f265c53552..90334f9449 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -62,24 +62,37 @@ impl TerminalPanel { item.handle.act_as::(cx).is_some() }) }); - pane.set_render_tab_bar_buttons(cx, move |_, cx| { + pane.set_render_tab_bar_buttons(cx, move |pane, cx| { let this = weak_self.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(&Default::default(), cx); - }); - } - }) - }, - None, - ) + Flex::row() + .with_child(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(&Default::default(), cx); + }); + } + }) + }, + None, + )) + .with_child(Pane::render_tab_bar_button( + 1, + if pane.is_zoomed() { + "icons/minimize_8.svg" + } else { + "icons/maximize_8.svg" + }, + cx, + move |pane, cx| pane.toggle_zoom(&Default::default(), cx), + None, + )) + .into_any() }); pane }); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index a2b13eb0cc..4302c6b390 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -291,13 +291,24 @@ impl Pane { .handle_if_kind(TabBarContextMenuKind::New), )) .with_child(Self::render_tab_bar_button( - 2, + 1, "icons/split_12.svg", cx, |pane, cx| pane.deploy_split_menu(cx), pane.tab_bar_context_menu .handle_if_kind(TabBarContextMenuKind::Split), )) + .with_child(Pane::render_tab_bar_button( + 2, + if pane.is_zoomed() { + "icons/minimize_8.svg" + } else { + "icons/maximize_8.svg" + }, + cx, + move |pane, cx| pane.toggle_zoom(&Default::default(), cx), + None, + )) .into_any() }), } @@ -684,6 +695,9 @@ impl Pane { if self.zoomed { cx.emit(Event::ZoomOut); } else if !self.items.is_empty() { + if !self.has_focus { + cx.focus_self(); + } cx.emit(Event::ZoomIn); } } From e5fd953b4f78f604d3bb39d88d32d43644fc3dfc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 24 May 2023 11:18:30 +0200 Subject: [PATCH 127/131] Provide tooltips for pane buttons --- crates/terminal_view/src/terminal_panel.rs | 5 +++ crates/workspace/src/pane.rs | 43 ++++++++++++++-------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 90334f9449..766736d70e 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -68,6 +68,10 @@ impl TerminalPanel { .with_child(Pane::render_tab_bar_button( 0, "icons/plus_12.svg", + Some(( + "New Terminal".into(), + Some(Box::new(workspace::NewTerminal)), + )), cx, move |_, cx| { let this = this.clone(); @@ -88,6 +92,7 @@ impl TerminalPanel { } else { "icons/maximize_8.svg" }, + Some(("Toggle Zoom".into(), Some(Box::new(workspace::ToggleZoom)))), cx, move |pane, cx| pane.toggle_zoom(&Default::default(), cx), None, diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 4302c6b390..26f3d17453 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -285,6 +285,7 @@ impl Pane { .with_child(Self::render_tab_bar_button( 0, "icons/plus_12.svg", + Some(("New...".into(), None)), cx, |pane, cx| pane.deploy_new_menu(cx), pane.tab_bar_context_menu @@ -293,6 +294,7 @@ impl Pane { .with_child(Self::render_tab_bar_button( 1, "icons/split_12.svg", + Some(("Split Pane".into(), None)), cx, |pane, cx| pane.deploy_split_menu(cx), pane.tab_bar_context_menu @@ -305,6 +307,7 @@ impl Pane { } else { "icons/maximize_8.svg" }, + Some(("Toggle Zoom".into(), Some(Box::new(ToggleZoom)))), cx, move |pane, cx| pane.toggle_zoom(&Default::default(), cx), None, @@ -1589,29 +1592,37 @@ impl Pane { pub fn render_tab_bar_button)>( index: usize, icon: &'static str, + tooltip: Option<(String, Option>)>, cx: &mut ViewContext, on_click: F, context_menu: Option>, ) -> AnyElement { enum TabBarButton {} + let mut button = MouseEventHandler::::new(index, cx, |mouse_state, cx| { + let theme = &settings::get::(cx).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)) + .into_any(); + if let Some((tooltip, action)) = tooltip { + let tooltip_style = settings::get::(cx).theme.tooltip.clone(); + button = button + .with_tooltip::(index, tooltip, action, tooltip_style, cx) + .into_any(); + } + Stack::new() - .with_child( - MouseEventHandler::::new(index, cx, |mouse_state, cx| { - let theme = &settings::get::(cx).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_child(button) .with_children( context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()), ) From 2a8e0824a629e3553ce06b33039dabed9704a5a0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 24 May 2023 14:59:57 +0200 Subject: [PATCH 128/131] Fix opening excerpt when the cursor is at the end of a multi-buffer This was caused by seeking with a right bias, which in the case of the last excerpt in the buffer would seek past the end of the buffer. This commit changes the behavior to move the cursor back to the previous excerpt if we overshoot, to ensure we always land on an excerpt. --- crates/editor/src/multi_buffer.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 6a61775697..6b1ad6c5b2 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1140,6 +1140,10 @@ impl MultiBuffer { let mut result = Vec::new(); let mut cursor = snapshot.excerpts.cursor::(); cursor.seek(&start, Bias::Right, &()); + if cursor.item().is_none() { + cursor.prev(&()); + } + while let Some(excerpt) = cursor.item() { if *cursor.start() > end { break; @@ -5072,16 +5076,19 @@ mod tests { .read(cx) .range_to_buffer_ranges(start_ix..end_ix, cx); let excerpted_buffers_text = excerpted_buffer_ranges - .into_iter() + .iter() .map(|(buffer, buffer_range)| { buffer .read(cx) - .text_for_range(buffer_range) + .text_for_range(buffer_range.clone()) .collect::() }) .collect::>() .join("\n"); assert_eq!(excerpted_buffers_text, text_for_range); + if !expected_excerpts.is_empty() { + assert!(!excerpted_buffer_ranges.is_empty()); + } let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]); assert_eq!( From 50cfe5eec3f8fba29867c68ede5643a12e0f6c9c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 24 May 2023 15:57:26 +0200 Subject: [PATCH 129/131] Use cmd-b/cmd-r/cmd-j to toggle left/right/bottom dock and focus when opening Also, bind the same keys with shift to toggle the dock without focusing. Co-Authored-By: Nathan Sobo --- assets/keymaps/default.json | 25 +++++++++++++- crates/welcome/src/welcome.rs | 2 +- crates/workspace/src/dock.rs | 10 ++++++ crates/workspace/src/workspace.rs | 57 ++++++++++++++++++++++--------- crates/zed/src/menus.rs | 15 ++++++-- crates/zed/src/zed.rs | 2 +- 6 files changed, 89 insertions(+), 22 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 987c6cf105..88b27fd2f9 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -367,7 +367,30 @@ "workspace::ActivatePane", 8 ], - "cmd-b": "workspace::ToggleLeftDock", + "cmd-b": [ + "workspace::ToggleLeftDock", + { "focus": true } + ], + "cmd-shift-b": [ + "workspace::ToggleLeftDock", + { "focus": false } + ], + "cmd-r": [ + "workspace::ToggleRightDock", + { "focus": true } + ], + "cmd-shift-r": [ + "workspace::ToggleRightDock", + { "focus": false } + ], + "cmd-j": [ + "workspace::ToggleBottomDock", + { "focus": true } + ], + "cmd-shift-j": [ + "workspace::ToggleBottomDock", + { "focus": false } + ], "cmd-shift-f": "workspace::NewSearch", "cmd-k cmd-t": "theme_selector::Toggle", "cmd-k cmd-s": "zed::OpenKeymap", diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index b7460c4c46..cef6f53a6e 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -32,7 +32,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_dock(DockPosition::Left, cx); + workspace.toggle_dock(DockPosition::Left, false, 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/dock.rs b/crates/workspace/src/dock.rs index 6ca78cd935..7256ee8704 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -423,6 +423,16 @@ impl View for Dock { Empty::new().into_any() } } + + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + if cx.is_self_focused() { + if let Some(active_entry) = self.active_entry() { + cx.focus(active_entry.panel.as_any()); + } else { + cx.focus_parent(); + } + } + } } impl PanelButtons { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 40d170b0de..39ecce2ca5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -103,6 +103,21 @@ pub trait Modal: View { #[derive(Clone, PartialEq)] pub struct RemoveWorktreeFromProject(pub WorktreeId); +#[derive(Copy, Clone, Default, Deserialize, PartialEq)] +pub struct ToggleLeftDock { + pub focus: bool, +} + +#[derive(Copy, Clone, Default, Deserialize, PartialEq)] +pub struct ToggleBottomDock { + pub focus: bool, +} + +#[derive(Copy, Clone, Default, Deserialize, PartialEq)] +pub struct ToggleRightDock { + pub focus: bool, +} + actions!( workspace, [ @@ -118,9 +133,6 @@ actions!( ActivatePreviousPane, ActivateNextPane, FollowNextCollaborator, - ToggleLeftDock, - ToggleRightDock, - ToggleBottomDock, NewTerminal, ToggleTerminalFocus, NewSearch, @@ -133,6 +145,11 @@ actions!( actions!(zed, [OpenSettings]); +impl_actions!( + workspace, + [ToggleLeftDock, ToggleBottomDock, ToggleRightDock] +); + #[derive(Clone, PartialEq)] pub struct OpenPaths { pub paths: Vec, @@ -249,14 +266,14 @@ 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, _: &ToggleLeftDock, cx| { - workspace.toggle_dock(DockPosition::Left, cx); + cx.add_action(|workspace: &mut Workspace, action: &ToggleLeftDock, cx| { + workspace.toggle_dock(DockPosition::Left, action.focus, cx); }); - cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| { - workspace.toggle_dock(DockPosition::Right, cx); + cx.add_action(|workspace: &mut Workspace, action: &ToggleRightDock, cx| { + workspace.toggle_dock(DockPosition::Right, action.focus, cx); }); - cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| { - workspace.toggle_dock(DockPosition::Bottom, cx); + cx.add_action(|workspace: &mut Workspace, action: &ToggleBottomDock, cx| { + workspace.toggle_dock(DockPosition::Bottom, action.focus, cx); }); cx.add_action(Workspace::activate_pane_at_index); @@ -1455,21 +1472,29 @@ impl Workspace { } } - pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { + pub fn toggle_dock( + &mut self, + dock_side: DockPosition, + focus: bool, + 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, + DockPosition::Left => &self.left_dock, + DockPosition::Bottom => &self.bottom_dock, + DockPosition::Right => &self.right_dock, }; dock.update(cx, |dock, cx| { let open = !dock.is_open(); dock.set_open(open, cx); }); - self.serialize_workspace(cx); - - cx.focus_self(); + if dock.read(cx).is_open() && focus { + cx.focus(dock); + } else { + cx.focus_self(); + } cx.notify(); + self.serialize_workspace(cx); } pub fn toggle_panel(&mut self, action: &TogglePanel, cx: &mut ViewContext) { diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 37e835c13d..b242b0f183 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -89,9 +89,18 @@ pub fn menus() -> Vec> { MenuItem::action("Zoom Out", super::DecreaseBufferFontSize), MenuItem::action("Reset Zoom", super::ResetBufferFontSize), MenuItem::separator(), - MenuItem::action("Toggle Left Dock", workspace::ToggleLeftDock), - MenuItem::action("Toggle Right Dock", workspace::ToggleRightDock), - MenuItem::action("Toggle Bottom Dock", workspace::ToggleBottomDock), + MenuItem::action( + "Toggle Left Dock", + workspace::ToggleLeftDock { focus: false }, + ), + MenuItem::action( + "Toggle Right Dock", + workspace::ToggleRightDock { focus: false }, + ), + MenuItem::action( + "Toggle Bottom Dock", + workspace::ToggleBottomDock { focus: false }, + ), MenuItem::submenu(Menu { name: "Editor Layout", items: vec![ diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index f9c0a1855e..1dfe9c24e5 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -354,7 +354,7 @@ pub fn initialize_workspace( .map_or(false, |entry| entry.is_dir()) }) { - workspace.toggle_dock(project_panel_position, cx); + workspace.toggle_dock(project_panel_position, false, cx); } workspace.add_panel(terminal_panel, cx) From 0cf1632d39aab4d81042fbb2a0e409e80dba9df4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 24 May 2023 16:04:02 +0200 Subject: [PATCH 130/131] Only focus new terminal if the panel contains focus Co-Authored-By: Nathan Sobo --- crates/terminal_view/src/terminal_panel.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 766736d70e..2062d2aff3 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -236,7 +236,8 @@ impl TerminalPanel { Box::new(cx.add_view(|cx| { TerminalView::new(terminal, workspace.database_id(), cx) })); - Pane::add_item(workspace, &pane, terminal, true, true, None, cx); + let focus = pane.read(cx).has_focus(); + Pane::add_item(workspace, &pane, terminal, true, focus, None, cx); } })?; this.update(&mut cx, |this, cx| this.serialize(cx))?; From f67a22828b5d3c35d204f3c1ff96fe9113c09219 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 24 May 2023 16:13:58 +0200 Subject: [PATCH 131/131] Show keystroke in panel tooltips Co-Authored-By: Nathan Sobo --- crates/project_panel/src/project_panel.rs | 8 +- crates/terminal_view/src/terminal_panel.rs | 6 +- crates/workspace/src/dock.rs | 188 ++++++++++----------- crates/workspace/src/workspace.rs | 16 +- 4 files changed, 102 insertions(+), 116 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 32a8148cbb..6604472d86 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -15,8 +15,8 @@ use gpui::{ geometry::vector::Vector2F, keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, - AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, ModelHandle, Task, - View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, + Action, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, ModelHandle, + Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{ @@ -1507,8 +1507,8 @@ impl workspace::dock::Panel for ProjectPanel { "icons/folder_tree_16.svg" } - fn icon_tooltip(&self) -> String { - "Project Panel".into() + fn icon_tooltip(&self) -> (String, Option>) { + ("Project Panel".into(), Some(Box::new(ToggleFocus))) } fn should_change_position_on_event(event: &Self::Event) -> bool { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 2062d2aff3..791a8b21c5 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use crate::TerminalView; use db::kvp::KEY_VALUE_STORE; use gpui::{ - actions, anyhow::Result, elements::*, serde_json, AppContext, AsyncAppContext, Entity, + actions, anyhow::Result, elements::*, serde_json, Action, AppContext, AsyncAppContext, Entity, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use project::Fs; @@ -365,8 +365,8 @@ impl Panel for TerminalPanel { "icons/terminal_12.svg" } - fn icon_tooltip(&self) -> String { - "Terminal Panel".into() + fn icon_tooltip(&self) -> (String, Option>) { + ("Terminal Panel".into(), Some(Box::new(ToggleFocus))) } fn icon_label(&self, cx: &WindowContext) -> Option { diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 7256ee8704..73d4f79399 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::*, platform::CursorStyle, platform::MouseButton, Action, AnyViewHandle, AppContext, + Axis, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use serde::Deserialize; use std::rc::Rc; @@ -16,7 +15,7 @@ pub trait Panel: View { 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_tooltip(&self) -> (String, Option>); fn icon_label(&self, _: &WindowContext) -> Option { None } @@ -43,7 +42,7 @@ pub trait PanelHandle { 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_tooltip(&self, cx: &WindowContext) -> (String, Option>); fn icon_label(&self, cx: &WindowContext) -> Option; fn has_focus(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; @@ -93,7 +92,7 @@ where self.read(cx).icon_path() } - fn icon_tooltip(&self, cx: &WindowContext) -> String { + fn icon_tooltip(&self, cx: &WindowContext) -> (String, Option>) { self.read(cx).icon_tooltip() } @@ -166,14 +165,6 @@ pub struct PanelButtons { workspace: WeakViewHandle, } -#[derive(Clone, Debug, Deserialize, PartialEq)] -pub struct TogglePanel { - pub dock_position: DockPosition, - pub panel_index: usize, -} - -impl_actions!(workspace, [TogglePanel]); - impl Dock { pub fn new(position: DockPosition) -> Self { Self { @@ -480,98 +471,89 @@ impl View for PanelButtons { .map(|item| (item.panel.clone(), item.context_menu.clone())) .collect::>(); Flex::row() - .with_children( - panels - .into_iter() - .enumerate() - .map(|(ix, (view, context_menu))| { - let action = TogglePanel { - dock_position, - panel_index: ix, - }; - - Stack::new() - .with_child( - MouseEventHandler::::new(ix, cx, |state, cx| { - let is_active = is_open && ix == active_ix; - let style = button_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) + .with_children(panels.into_iter().enumerate().map( + |(panel_ix, (view, context_menu))| { + let (tooltip, tooltip_action) = view.icon_tooltip(cx); + Stack::new() + .with_child( + MouseEventHandler::::new(panel_ix, cx, |state, cx| { + let is_active = is_open && panel_ix == active_ix; + let style = button_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) = 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) - }); + } else { + None + }) + .constrained() + .with_height(style.icon_size) + .contained() + .with_style(style.container) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, { + move |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + cx.window_context().defer(move |cx| { + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel(dock_position, panel_ix, 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, - ]; + } + }) + .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, - ), - ) - .with_child(ChildView::new(&context_menu, cx)) - }), - ) + 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::( + panel_ix, + tooltip, + tooltip_action, + tooltip_style.clone(), + cx, + ), + ) + .with_child(ChildView::new(&context_menu, cx)) + }, + )) .contained() .with_style(group_style) .into_any() @@ -682,8 +664,8 @@ pub(crate) mod test { "icons/test_panel.svg" } - fn icon_tooltip(&self) -> String { - "Test Panel".into() + fn icon_tooltip(&self) -> (String, Option>) { + ("Test Panel".into(), None) } fn should_change_position_on_event(event: &Self::Event) -> bool { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 39ecce2ca5..1c2b33214e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -64,7 +64,7 @@ use crate::{ DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace, }, }; -use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle, TogglePanel}; +use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle}; use lazy_static::lazy_static; use notifications::{NotificationHandle, NotifyResultExt}; pub use pane::*; @@ -259,7 +259,6 @@ 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_panel); cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| { workspace.activate_previous_pane(cx) }); @@ -1497,19 +1496,24 @@ impl Workspace { self.serialize_workspace(cx); } - pub fn toggle_panel(&mut self, action: &TogglePanel, cx: &mut ViewContext) { - let dock = match action.dock_position { + pub fn toggle_panel( + &mut self, + position: DockPosition, + panel_index: usize, + cx: &mut ViewContext, + ) { + let dock = match 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| { - if dock.is_open() && dock.active_panel_index() == action.panel_index { + if dock.is_open() && dock.active_panel_index() == panel_index { dock.set_open(false, cx); None } else { dock.set_open(true, cx); - dock.activate_panel(action.panel_index, cx); + dock.activate_panel(panel_index, cx); dock.active_panel().cloned() } });