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 | 180 +--- crates/workspace/src/persistence/model.rs | 84 +- crates/workspace/src/workspace.rs | 137 +-- crates/zed/src/main.rs | 4 +- crates/zed/src/zed.rs | 1 - 18 files changed, 98 insertions(+), 1467 deletions(-) delete mode 100644 crates/workspace/src/dock.rs delete mode 100644 crates/workspace/src/dock/toggle_dock_button.rs diff --git a/assets/keymaps/atom.json b/assets/keymaps/atom.json index 99aae2638fb206e3caf648fc55c6075cb12154a6..a1e8cf6d9d189c02bc3a5b6cb1823563fa8739ca 100644 --- a/assets/keymaps/atom.json +++ b/assets/keymaps/atom.json @@ -62,9 +62,5 @@ "ctrl-f": "project_panel::ExpandSelectedEntry", "ctrl-shift-c": "project_panel::CopyPath" } - }, - { - "context": "Dock", - "bindings": {} } ] diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index b89940a751256d3fd461f7d7b6e818a8fa25c543..6e8b5193138415606210f6b34a4263f6d0fbafe9 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -443,32 +443,6 @@ "cmd-enter": "project_search::SearchInNew" } }, - { - "context": "Workspace", - "bindings": { - "shift-escape": "dock::FocusDock" - } - }, - { - "bindings": { - "cmd-shift-k cmd-shift-right": "dock::AnchorDockRight", - "cmd-shift-k cmd-shift-down": "dock::AnchorDockBottom", - "cmd-shift-k cmd-shift-up": "dock::ExpandDock" - } - }, - { - "context": "Pane", - "bindings": { - "cmd-escape": "dock::AddTabToDock" - } - }, - { - "context": "Pane && docked", - "bindings": { - "shift-escape": "dock::HideDock", - "cmd-escape": "dock::RemoveTabFromDock" - } - }, { "context": "ProjectPanel", "bindings": { diff --git a/assets/keymaps/jetbrains.json b/assets/keymaps/jetbrains.json index 59e069e7f779f56bd1789fdab54d4737102837fa..0dafe58d32112e11f212978a1f48b9d598b6aa02 100644 --- a/assets/keymaps/jetbrains.json +++ b/assets/keymaps/jetbrains.json @@ -66,14 +66,7 @@ "cmd-shift-a": "command_palette::Toggle", "cmd-alt-o": "project_symbols::Toggle", "cmd-1": "workspace::ToggleLeftSidebar", - "cmd-6": "diagnostics::Deploy", - "alt-f12": "dock::FocusDock" - } - }, - { - "context": "Dock", - "bindings": { - "alt-f12": "dock::HideDock" + "cmd-6": "diagnostics::Deploy" } } ] diff --git a/assets/keymaps/sublime_text.json b/assets/keymaps/sublime_text.json index 373f3f4d3a7c0dd978b34ad535dff30b14671fbe..7a285ee824d0fe58699dab65fe149cea16295e5f 100644 --- a/assets/keymaps/sublime_text.json +++ b/assets/keymaps/sublime_text.json @@ -45,18 +45,11 @@ { "context": "Workspace", "bindings": { - "ctrl-`": "dock::FocusDock", "cmd-k cmd-b": "workspace::ToggleLeftSidebar", "cmd-t": "file_finder::Toggle", "shift-cmd-r": "project_symbols::Toggle", // Currently busted: https://github.com/zed-industries/feedback/issues/898 "ctrl-0": "project_panel::ToggleFocus" } - }, - { - "context": "Dock", - "bindings": { - "ctrl-`": "dock::HideDock" - } } ] diff --git a/assets/keymaps/textmate.json b/assets/keymaps/textmate.json index 1abcaa376cdd62fe9d02f064000dc23127c873f2..738b6603ff8718dcc944dd9642133eb701c30491 100644 --- a/assets/keymaps/textmate.json +++ b/assets/keymaps/textmate.json @@ -83,9 +83,5 @@ { "context": "ProjectPanel", "bindings": {} - }, - { - "context": "Dock", - "bindings": {} } ] diff --git a/assets/settings/default.json b/assets/settings/default.json index 20d07acae05fc26fcb2da87fb39e57cf3c1fcd18..58fb28bc62577b6219fbfb59475b76b08a9d9b0e 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -59,16 +59,6 @@ // 4. Save when idle for a certain amount of time: // "autosave": { "after_delay": {"milliseconds": 500} }, "autosave": "off", - // Where to place the dock by default. This setting can take three - // values: - // - // 1. Position the dock attached to the bottom of the workspace - // "default_dock_anchor": "bottom" - // 2. Position the dock to the right of the workspace like a side panel - // "default_dock_anchor": "right" - // 3. Position the dock full screen over the entire workspace" - // "default_dock_anchor": "expanded" - "default_dock_anchor": "bottom", // Whether or not to remove any trailing whitespace from lines of a buffer // before saving it. "remove_trailing_whitespace_on_save": true, diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index 64768d1a4795679cfd54a8f7231d2c79488f01f7..aeb26231d4651909f535549f1837e5bb9989a03e 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -195,7 +195,6 @@ impl TestServer { fs: fs.clone(), build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _| unimplemented!(), - dock_default_item_factory: |_, _| None, background_actions: || &[], }); diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 7ba52fcb5e52e64e1c82f6a0e9f465f1bf8ecc9b..fde7f516a7e6ea4f86ca1c7f82155c20a3ddce6c 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -2,7 +2,7 @@ mod keymap_file; pub mod settings_file; pub mod watched_json; -use anyhow::{bail, Result}; +use anyhow::Result; use gpui::{ font_cache::{FamilyId, FontCache}, fonts, AssetSource, @@ -15,10 +15,6 @@ use schemars::{ }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; -use sqlez::{ - bindable::{Bind, Column, StaticColumnCount}, - statement::Statement, -}; use std::{ borrow::Cow, collections::HashMap, num::NonZeroU32, ops::Range, path::Path, str, sync::Arc, }; @@ -48,7 +44,6 @@ pub struct Settings { pub show_call_status_icon: bool, pub vim_mode: bool, pub autosave: Autosave, - pub default_dock_anchor: DockAnchor, pub editor_defaults: EditorSettings, pub editor_overrides: EditorSettings, pub git: GitSettings, @@ -340,43 +335,6 @@ impl TerminalSettings { } } -#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum DockAnchor { - #[default] - Bottom, - Right, - Expanded, -} - -impl StaticColumnCount for DockAnchor {} -impl Bind for DockAnchor { - fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { - match self { - DockAnchor::Bottom => "Bottom", - DockAnchor::Right => "Right", - DockAnchor::Expanded => "Expanded", - } - .bind(statement, start_index) - } -} - -impl Column for DockAnchor { - fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> { - String::column(statement, start_index).and_then(|(anchor_text, next_index)| { - Ok(( - match anchor_text.as_ref() { - "Bottom" => DockAnchor::Bottom, - "Right" => DockAnchor::Right, - "Expanded" => DockAnchor::Expanded, - _ => bail!("Stored dock anchor is incorrect"), - }, - next_index, - )) - }) - } -} - #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct SettingsFileContent { #[serde(default)] @@ -403,8 +361,6 @@ pub struct SettingsFileContent { pub vim_mode: Option, #[serde(default)] pub autosave: Option, - #[serde(default)] - pub default_dock_anchor: Option, #[serde(flatten)] pub editor: EditorSettings, #[serde(default)] @@ -501,7 +457,6 @@ impl Settings { show_call_status_icon: defaults.show_call_status_icon.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), - default_dock_anchor: defaults.default_dock_anchor.unwrap(), editor_defaults: EditorSettings { tab_size: required(defaults.editor.tab_size), hard_tabs: required(defaults.editor.hard_tabs), @@ -596,7 +551,6 @@ impl Settings { ); merge(&mut self.vim_mode, data.vim_mode); merge(&mut self.autosave, data.autosave); - merge(&mut self.default_dock_anchor, data.default_dock_anchor); merge(&mut self.base_keymap, data.base_keymap); merge(&mut self.features.copilot, data.features.copilot); @@ -796,7 +750,6 @@ impl Settings { show_call_status_icon: true, vim_mode: false, autosave: Autosave::Off, - default_dock_anchor: DockAnchor::Bottom, editor_defaults: EditorSettings { tab_size: Some(4.try_into().unwrap()), hard_tabs: Some(false), diff --git a/crates/terminal_view/src/terminal_button.rs b/crates/terminal_view/src/terminal_button.rs index a92f7285b51d4bad489af8ef929cb8a88ef91074..7b0da416ffcc865978fcaf292913d26c5f3603f7 100644 --- a/crates/terminal_view/src/terminal_button.rs +++ b/crates/terminal_view/src/terminal_button.rs @@ -8,7 +8,6 @@ use gpui::{ use settings::Settings; use std::any::TypeId; use workspace::{ - dock::{Dock, FocusDock}, item::ItemHandle, NewTerminal, StatusItemView, Workspace, }; @@ -85,8 +84,8 @@ impl View for TerminalButton { } else { if !active { if let Some(workspace) = this.workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - Dock::focus_dock(workspace, &Default::default(), cx) + workspace.update(cx, |_workspace, _cx| { + todo!() }) } } @@ -95,7 +94,7 @@ impl View for TerminalButton { .with_tooltip::( 0, "Show Terminal".into(), - Some(Box::new(FocusDock)), + None, // TODO! We need a new action here. theme.tooltip.clone(), cx, ), diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 0bd23a0b87598c02d41a2d5ae79aaaefa5ba2a14..8c77295748eb7bcad94740ece4dc1c1c8c350b4a 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -73,7 +73,6 @@ pub struct Workspace { pub joining_project_avatar: ImageStyle, pub joining_project_message: ContainedText, pub external_location_message: ContainedText, - pub dock: Dock, pub drop_target_overlay_color: Color, } @@ -296,15 +295,6 @@ pub struct Toolbar { pub nav_button: Interactive, } -#[derive(Clone, Deserialize, Default)] -pub struct Dock { - pub initial_size_right: f32, - pub initial_size_bottom: f32, - pub wash_color: Color, - pub panel: ContainerStyle, - pub maximized: ContainerStyle, -} - #[derive(Clone, Deserialize, Default)] pub struct Notifications { #[serde(flatten)] diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs deleted file mode 100644 index 7efcb7f9d31dae6c2dc665bfcd6243843e3f443e..0000000000000000000000000000000000000000 --- a/crates/workspace/src/dock.rs +++ /dev/null @@ -1,816 +0,0 @@ -mod toggle_dock_button; - -use collections::HashMap; -use gpui::{ - actions, - elements::{ChildView, Empty, MouseEventHandler, ParentElement, Side, Stack}, - geometry::vector::Vector2F, - platform::{CursorStyle, MouseButton}, - AnyElement, AppContext, Border, Element, SizeConstraint, ViewContext, ViewHandle, -}; -use settings::{DockAnchor, Settings}; -use theme::Theme; - -use crate::{sidebar::SidebarSide, BackgroundActions, ItemHandle, Pane, Workspace}; -pub use toggle_dock_button::ToggleDockButton; - -actions!( - dock, - [ - FocusDock, - HideDock, - AnchorDockRight, - AnchorDockBottom, - ExpandDock, - AddTabToDock, - RemoveTabFromDock, - ] -); - -pub fn init(cx: &mut AppContext) { - cx.add_action(Dock::focus_dock); - cx.add_action(Dock::hide_dock); - cx.add_action( - |workspace: &mut Workspace, _: &AnchorDockRight, cx: &mut ViewContext| { - Dock::move_dock(workspace, DockAnchor::Right, true, cx); - }, - ); - cx.add_action( - |workspace: &mut Workspace, _: &AnchorDockBottom, cx: &mut ViewContext| { - Dock::move_dock(workspace, DockAnchor::Bottom, true, cx) - }, - ); - cx.add_action( - |workspace: &mut Workspace, _: &ExpandDock, cx: &mut ViewContext| { - Dock::move_dock(workspace, DockAnchor::Expanded, true, cx) - }, - ); - cx.add_action( - |workspace: &mut Workspace, _: &AddTabToDock, cx: &mut ViewContext| { - if let Some(active_item) = workspace.active_item(cx) { - let item_id = active_item.id(); - - let from = workspace.active_pane(); - let to = workspace.dock_pane(); - if from.id() == to.id() { - return; - } - - let destination_index = to.read(cx).items_len() + 1; - - Pane::move_item( - workspace, - from.clone(), - to.clone(), - item_id, - destination_index, - cx, - ); - } - }, - ); - cx.add_action( - |workspace: &mut Workspace, _: &RemoveTabFromDock, cx: &mut ViewContext| { - if let Some(active_item) = workspace.active_item(cx) { - let item_id = active_item.id(); - - let from = workspace.dock_pane(); - let to = workspace - .last_active_center_pane - .as_ref() - .and_then(|pane| pane.upgrade(cx)) - .unwrap_or_else(|| { - workspace - .panes - .first() - .expect("There must be a pane") - .clone() - }); - - if from.id() == to.id() { - return; - } - - let destination_index = to.read(cx).items_len() + 1; - - Pane::move_item( - workspace, - from.clone(), - to.clone(), - item_id, - destination_index, - cx, - ); - } - }, - ); -} - -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum DockPosition { - Shown(DockAnchor), - Hidden(DockAnchor), -} - -impl Default for DockPosition { - fn default() -> Self { - DockPosition::Hidden(Default::default()) - } -} - -pub fn icon_for_dock_anchor(anchor: DockAnchor) -> &'static str { - match anchor { - DockAnchor::Right => "icons/dock_right_12.svg", - DockAnchor::Bottom => "icons/dock_bottom_12.svg", - DockAnchor::Expanded => "icons/dock_modal_12.svg", - } -} - -impl DockPosition { - pub fn is_visible(&self) -> bool { - match self { - DockPosition::Shown(_) => true, - DockPosition::Hidden(_) => false, - } - } - - pub fn anchor(&self) -> DockAnchor { - match self { - DockPosition::Shown(anchor) | DockPosition::Hidden(anchor) => *anchor, - } - } - - fn hide(self) -> Self { - match self { - DockPosition::Shown(anchor) => DockPosition::Hidden(anchor), - DockPosition::Hidden(_) => self, - } - } - - fn show(self) -> Self { - match self { - DockPosition::Hidden(anchor) => DockPosition::Shown(anchor), - DockPosition::Shown(_) => self, - } - } -} - -pub type DockDefaultItemFactory = - fn(workspace: &mut Workspace, cx: &mut ViewContext) -> Option>; - -pub struct Dock { - position: DockPosition, - panel_sizes: HashMap, - pane: ViewHandle, - default_item_factory: DockDefaultItemFactory, -} - -impl Dock { - pub fn new( - default_item_factory: DockDefaultItemFactory, - background_actions: BackgroundActions, - cx: &mut ViewContext, - ) -> Self { - let position = DockPosition::Hidden(cx.global::().default_dock_anchor); - let workspace = cx.weak_handle(); - let pane = - cx.add_view(|cx| Pane::new(workspace, Some(position.anchor()), background_actions, cx)); - pane.update(cx, |pane, cx| { - pane.set_active(false, cx); - }); - cx.subscribe(&pane, Workspace::handle_pane_event).detach(); - - Self { - pane, - panel_sizes: Default::default(), - position, - default_item_factory, - } - } - - pub fn pane(&self) -> &ViewHandle { - &self.pane - } - - pub fn visible_pane(&self) -> Option<&ViewHandle> { - self.position.is_visible().then(|| self.pane()) - } - - pub fn is_anchored_at(&self, anchor: DockAnchor) -> bool { - self.position.is_visible() && self.position.anchor() == anchor - } - - pub(crate) fn set_dock_position( - workspace: &mut Workspace, - new_position: DockPosition, - focus: bool, - cx: &mut ViewContext, - ) { - workspace.dock.position = new_position; - // Tell the pane about the new anchor position - workspace.dock.pane.update(cx, |pane, cx| { - pane.set_docked(Some(new_position.anchor()), cx) - }); - - if workspace.dock.position.is_visible() { - // Close the right sidebar if the dock is on the right side and the right sidebar is open - if workspace.dock.position.anchor() == DockAnchor::Right { - if workspace.right_sidebar().read(cx).is_open() { - workspace.toggle_sidebar(SidebarSide::Right, cx); - } - } - - // Ensure that the pane has at least one item or construct a default item to put in it - let pane = workspace.dock.pane.clone(); - if pane.read(cx).items().next().is_none() { - if let Some(item_to_add) = (workspace.dock.default_item_factory)(workspace, cx) { - Pane::add_item(workspace, &pane, item_to_add, focus, focus, None, cx); - } else { - workspace.dock.position = workspace.dock.position.hide(); - } - } else { - if focus { - cx.focus(&pane); - } - } - } else if let Some(last_active_center_pane) = workspace - .last_active_center_pane - .as_ref() - .and_then(|pane| pane.upgrade(cx)) - { - if focus { - cx.focus(&last_active_center_pane); - } - } - cx.emit(crate::Event::DockAnchorChanged); - workspace.serialize_workspace(cx); - cx.notify(); - } - - pub fn hide(workspace: &mut Workspace, cx: &mut ViewContext) { - Self::set_dock_position(workspace, workspace.dock.position.hide(), true, cx); - } - - pub fn show(workspace: &mut Workspace, focus: bool, cx: &mut ViewContext) { - Self::set_dock_position(workspace, workspace.dock.position.show(), focus, cx); - } - - pub fn hide_on_sidebar_shown( - workspace: &mut Workspace, - sidebar_side: SidebarSide, - cx: &mut ViewContext, - ) { - if (sidebar_side == SidebarSide::Right && workspace.dock.is_anchored_at(DockAnchor::Right)) - || workspace.dock.is_anchored_at(DockAnchor::Expanded) - { - Self::hide(workspace, cx); - } - } - - pub fn focus_dock(workspace: &mut Workspace, _: &FocusDock, cx: &mut ViewContext) { - Self::set_dock_position(workspace, workspace.dock.position.show(), true, cx); - } - - pub fn hide_dock(workspace: &mut Workspace, _: &HideDock, cx: &mut ViewContext) { - Self::set_dock_position(workspace, workspace.dock.position.hide(), true, cx); - } - - pub fn move_dock( - workspace: &mut Workspace, - new_anchor: DockAnchor, - focus: bool, - cx: &mut ViewContext, - ) { - Self::set_dock_position(workspace, DockPosition::Shown(new_anchor), focus, cx); - } - - pub fn render( - &self, - theme: &Theme, - anchor: DockAnchor, - cx: &mut ViewContext, - ) -> Option> { - let style = &theme.workspace.dock; - - self.position - .is_visible() - .then(|| self.position.anchor()) - .filter(|current_anchor| *current_anchor == anchor) - .map(|anchor| match anchor { - DockAnchor::Bottom | DockAnchor::Right => { - let mut panel_style = style.panel.clone(); - let (resize_side, initial_size) = if anchor == DockAnchor::Bottom { - panel_style.border = Border { - top: true, - bottom: false, - left: false, - right: false, - ..panel_style.border - }; - - (Side::Top, style.initial_size_bottom) - } else { - panel_style.border = Border { - top: false, - bottom: false, - left: true, - right: false, - ..panel_style.border - }; - (Side::Left, style.initial_size_right) - }; - - enum DockResizeHandle {} - - let resizable = ChildView::new(&self.pane, cx) - .contained() - .with_style(panel_style) - .with_resize_handle::( - resize_side as usize, - resize_side, - 4., - self.panel_sizes - .get(&anchor) - .copied() - .unwrap_or(initial_size), - cx, - ); - - let size = resizable.current_size(); - cx.defer(move |workspace, _| { - workspace.dock.panel_sizes.insert(anchor, size); - }); - - if anchor == DockAnchor::Right { - resizable.constrained().dynamically(|constraint, _, cx| { - SizeConstraint::new( - Vector2F::new(20., constraint.min.y()), - Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()), - ) - }) - } else { - resizable.constrained().dynamically(|constraint, _, cx| { - SizeConstraint::new( - Vector2F::new(constraint.min.x(), 50.), - Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8), - ) - }) - } - .into_any() - } - DockAnchor::Expanded => { - enum ExpandedDockWash {} - enum ExpandedDockPane {} - Stack::new() - .with_child( - // Render wash under the dock which when clicked hides it - MouseEventHandler::::new(0, cx, |_, _| { - Empty::new() - .contained() - .with_background_color(style.wash_color) - }) - .capture_all() - .on_down(MouseButton::Left, |_, workspace, cx| { - Dock::hide_dock(workspace, &Default::default(), cx) - }) - .with_cursor_style(CursorStyle::Arrow), - ) - .with_child( - MouseEventHandler::::new(0, cx, |_state, cx| { - ChildView::new(&self.pane, cx) - }) - // Make sure all events directly under the dock pane - // are captured - .capture_all() - .contained() - .with_style(style.maximized), - ) - .into_any() - } - }) - } - - pub fn position(&self) -> DockPosition { - self.position - } -} - -#[cfg(test)] -mod tests { - use std::{ - ops::{Deref, DerefMut}, - path::PathBuf, - sync::Arc, - }; - - use gpui::{AppContext, BorrowWindowContext, TestAppContext, ViewContext, WindowContext}; - use project::{FakeFs, Project}; - use settings::Settings; - use theme::ThemeRegistry; - - use super::*; - use crate::{ - dock, - item::{self, test::TestItem}, - persistence::model::{ - SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace, - }, - register_deserializable_item, - sidebar::Sidebar, - AppState, ItemHandle, Workspace, - }; - - pub fn default_item_factory( - _workspace: &mut Workspace, - cx: &mut ViewContext, - ) -> Option> { - Some(Box::new(cx.add_view(|_| TestItem::new()))) - } - - #[gpui::test] - async fn test_dock_workspace_infinite_loop(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); - - cx.update(|cx| { - register_deserializable_item::(cx); - }); - - let serialized_workspace = SerializedWorkspace { - id: 0, - location: Vec::::new().into(), - dock_position: dock::DockPosition::Shown(DockAnchor::Expanded), - center_group: SerializedPaneGroup::Pane(SerializedPane { - active: false, - children: vec![], - }), - dock_pane: SerializedPane { - active: true, - children: vec![SerializedItem { - active: true, - item_id: 0, - kind: "TestItem".into(), - }], - }, - left_sidebar_open: false, - bounds: Default::default(), - display: Default::default(), - }; - - let fs = FakeFs::new(cx.background()); - let project = Project::test(fs, [], cx).await; - - let (_, _workspace) = cx.add_window(|cx| { - Workspace::new( - Some(serialized_workspace), - 0, - project.clone(), - Arc::new(AppState { - languages: project.read(cx).languages().clone(), - themes: ThemeRegistry::new((), cx.font_cache().clone()), - client: project.read(cx).client(), - user_store: project.read(cx).user_store(), - fs: project.read(cx).fs().clone(), - build_window_options: |_, _, _| Default::default(), - initialize_workspace: |_, _, _| {}, - dock_default_item_factory: default_item_factory, - background_actions: || &[], - }), - cx, - ) - }); - - cx.foreground().run_until_parked(); - //Should terminate - } - - #[gpui::test] - async fn test_dock_hides_when_pane_empty(cx: &mut TestAppContext) { - let mut cx = DockTestContext::new(cx).await; - - // Closing the last item in the dock hides the dock - cx.move_dock(DockAnchor::Right); - let old_items = cx.dock_items(); - assert!(!old_items.is_empty()); - cx.close_dock_items().await; - cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Right)); - - // Reopening the dock adds a new item - cx.move_dock(DockAnchor::Right); - let new_items = cx.dock_items(); - assert!(!new_items.is_empty()); - assert!(new_items - .into_iter() - .all(|new_item| !old_items.contains(&new_item))); - } - - #[gpui::test] - async fn test_dock_panel_collisions(cx: &mut TestAppContext) { - let mut cx = DockTestContext::new(cx).await; - - // Dock closes when expanded for either panel - cx.move_dock(DockAnchor::Expanded); - cx.open_sidebar(SidebarSide::Left); - cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Expanded)); - cx.close_sidebar(SidebarSide::Left); - cx.move_dock(DockAnchor::Expanded); - cx.open_sidebar(SidebarSide::Right); - cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Expanded)); - - // Dock closes in the right position if the right sidebar is opened - cx.move_dock(DockAnchor::Right); - cx.open_sidebar(SidebarSide::Left); - cx.assert_dock_position(DockPosition::Shown(DockAnchor::Right)); - cx.open_sidebar(SidebarSide::Right); - cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Right)); - cx.close_sidebar(SidebarSide::Right); - - // Dock in bottom position ignores sidebars - cx.move_dock(DockAnchor::Bottom); - cx.open_sidebar(SidebarSide::Left); - cx.open_sidebar(SidebarSide::Right); - cx.assert_dock_position(DockPosition::Shown(DockAnchor::Bottom)); - - // Opening the dock in the right position closes the right sidebar - cx.move_dock(DockAnchor::Right); - cx.assert_sidebar_closed(SidebarSide::Right); - } - - #[gpui::test] - async fn test_focusing_panes_shows_and_hides_dock(cx: &mut TestAppContext) { - let mut cx = DockTestContext::new(cx).await; - - // Focusing an item not in the dock when expanded hides the dock - let center_item = cx.add_item_to_center_pane(); - cx.move_dock(DockAnchor::Expanded); - let dock_item = cx - .dock_items() - .get(0) - .cloned() - .expect("Dock should have an item at this point"); - center_item.update(&mut cx, |_, cx| cx.focus_self()); - cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Expanded)); - - // Focusing an item not in the dock when not expanded, leaves the dock open but inactive - cx.move_dock(DockAnchor::Right); - center_item.update(&mut cx, |_, cx| cx.focus_self()); - cx.assert_dock_position(DockPosition::Shown(DockAnchor::Right)); - cx.assert_dock_pane_inactive(); - cx.assert_workspace_pane_active(); - - // Focusing an item in the dock activates it's pane - dock_item.update(&mut cx, |_, cx| cx.focus_self()); - cx.assert_dock_position(DockPosition::Shown(DockAnchor::Right)); - cx.assert_dock_pane_active(); - cx.assert_workspace_pane_inactive(); - } - - #[gpui::test] - async fn test_toggle_dock_focus(cx: &mut TestAppContext) { - let mut cx = DockTestContext::new(cx).await; - - cx.move_dock(DockAnchor::Right); - cx.assert_dock_pane_active(); - cx.hide_dock(); - cx.move_dock(DockAnchor::Right); - cx.assert_dock_pane_active(); - } - - #[gpui::test] - async fn test_activate_next_and_prev_pane(cx: &mut TestAppContext) { - let mut cx = DockTestContext::new(cx).await; - - cx.move_dock(DockAnchor::Right); - cx.assert_dock_pane_active(); - - cx.update_workspace(|workspace, cx| workspace.activate_next_pane(cx)); - cx.assert_dock_pane_active(); - - cx.update_workspace(|workspace, cx| workspace.activate_previous_pane(cx)); - cx.assert_dock_pane_active(); - } - - struct DockTestContext<'a> { - pub cx: &'a mut TestAppContext, - pub window_id: usize, - pub workspace: ViewHandle, - } - - impl<'a> DockTestContext<'a> { - pub async fn new(cx: &'a mut TestAppContext) -> DockTestContext<'a> { - Settings::test_async(cx); - let fs = FakeFs::new(cx.background()); - - cx.update(|cx| init(cx)); - let project = Project::test(fs, [], cx).await; - let (window_id, workspace) = cx.add_window(|cx| { - Workspace::new( - None, - 0, - project.clone(), - Arc::new(AppState { - languages: project.read(cx).languages().clone(), - themes: ThemeRegistry::new((), cx.font_cache().clone()), - client: project.read(cx).client(), - user_store: project.read(cx).user_store(), - fs: project.read(cx).fs().clone(), - build_window_options: |_, _, _| Default::default(), - initialize_workspace: |_, _, _| {}, - dock_default_item_factory: default_item_factory, - background_actions: || &[], - }), - cx, - ) - }); - - workspace.update(cx, |workspace, cx| { - let left_panel = cx.add_view(|_| TestItem::new()); - workspace.left_sidebar().update(cx, |sidebar, cx| { - sidebar.add_item( - "icons/folder_tree_16.svg", - "Left Test Panel".to_string(), - left_panel.clone(), - cx, - ); - }); - - let right_panel = cx.add_view(|_| TestItem::new()); - workspace.right_sidebar().update(cx, |sidebar, cx| { - sidebar.add_item( - "icons/folder_tree_16.svg", - "Right Test Panel".to_string(), - right_panel.clone(), - cx, - ); - }); - }); - - Self { - cx, - window_id, - workspace, - } - } - - pub fn workspace(&self, read: F) -> T - where - F: FnOnce(&Workspace, &ViewContext) -> T, - { - self.workspace.read_with(self.cx, read) - } - - pub fn update_workspace(&mut self, update: F) -> T - where - F: FnOnce(&mut Workspace, &mut ViewContext) -> T, - { - self.workspace.update(self.cx, update) - } - - pub fn sidebar(&self, sidebar_side: SidebarSide, read: F) -> T - where - F: FnOnce(&Sidebar, &AppContext) -> T, - { - self.workspace(|workspace, cx| { - let sidebar = match sidebar_side { - SidebarSide::Left => workspace.left_sidebar(), - SidebarSide::Right => workspace.right_sidebar(), - } - .read(cx); - - read(sidebar, cx) - }) - } - - pub fn center_pane_handle(&self) -> ViewHandle { - self.workspace(|workspace, cx| { - workspace - .last_active_center_pane - .clone() - .and_then(|pane| pane.upgrade(cx)) - .unwrap_or_else(|| workspace.center.panes()[0].clone()) - }) - } - - pub fn add_item_to_center_pane(&mut self) -> ViewHandle { - self.update_workspace(|workspace, cx| { - let item = cx.add_view(|_| TestItem::new()); - let pane = workspace - .last_active_center_pane - .clone() - .and_then(|pane| pane.upgrade(cx)) - .unwrap_or_else(|| workspace.center.panes()[0].clone()); - Pane::add_item( - workspace, - &pane, - Box::new(item.clone()), - true, - true, - None, - cx, - ); - item - }) - } - - pub fn dock_pane(&self, read: F) -> T - where - F: FnOnce(&Pane, &AppContext) -> T, - { - self.workspace(|workspace, cx| { - let dock_pane = workspace.dock_pane().read(cx); - read(dock_pane, cx) - }) - } - - pub fn move_dock(&mut self, anchor: DockAnchor) { - self.update_workspace(|workspace, cx| Dock::move_dock(workspace, anchor, true, cx)); - } - - pub fn hide_dock(&mut self) { - self.cx.dispatch_action(self.window_id, HideDock); - } - - pub fn open_sidebar(&mut self, sidebar_side: SidebarSide) { - if !self.sidebar(sidebar_side, |sidebar, _| sidebar.is_open()) { - self.update_workspace(|workspace, cx| workspace.toggle_sidebar(sidebar_side, cx)); - } - } - - pub fn close_sidebar(&mut self, sidebar_side: SidebarSide) { - if self.sidebar(sidebar_side, |sidebar, _| sidebar.is_open()) { - self.update_workspace(|workspace, cx| workspace.toggle_sidebar(sidebar_side, cx)); - } - } - - pub fn dock_items(&self) -> Vec> { - self.dock_pane(|pane, cx| { - pane.items() - .map(|item| { - item.act_as::(cx) - .expect("Dock Test Context uses TestItems in the dock") - }) - .collect() - }) - } - - pub async fn close_dock_items(&mut self) { - self.update_workspace(|workspace, cx| { - Pane::close_items(workspace, workspace.dock_pane().clone(), cx, |_| true) - }) - .await - .expect("Could not close dock items") - } - - pub fn assert_dock_position(&self, expected_position: DockPosition) { - self.workspace(|workspace, _| assert_eq!(workspace.dock.position, expected_position)); - } - - pub fn assert_sidebar_closed(&self, sidebar_side: SidebarSide) { - assert!(!self.sidebar(sidebar_side, |sidebar, _| sidebar.is_open())); - } - - pub fn assert_workspace_pane_active(&self) { - assert!(self - .center_pane_handle() - .read_with(self.cx, |pane, _| pane.is_active())); - } - - pub fn assert_workspace_pane_inactive(&self) { - assert!(!self - .center_pane_handle() - .read_with(self.cx, |pane, _| pane.is_active())); - } - - pub fn assert_dock_pane_active(&self) { - assert!(self.dock_pane(|pane, _| pane.is_active())) - } - - pub fn assert_dock_pane_inactive(&self) { - assert!(!self.dock_pane(|pane, _| pane.is_active())) - } - } - - impl<'a> Deref for DockTestContext<'a> { - type Target = gpui::TestAppContext; - - fn deref(&self) -> &Self::Target { - self.cx - } - } - - impl<'a> DerefMut for DockTestContext<'a> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.cx - } - } - - impl BorrowWindowContext for DockTestContext<'_> { - fn read_with T>(&self, window_id: usize, f: F) -> T { - BorrowWindowContext::read_with(self.cx, window_id, f) - } - - fn update T>(&mut self, window_id: usize, f: F) -> T { - BorrowWindowContext::update(self.cx, window_id, f) - } - } -} diff --git a/crates/workspace/src/dock/toggle_dock_button.rs b/crates/workspace/src/dock/toggle_dock_button.rs deleted file mode 100644 index 1fda55b7834967207a0f4463e548c8ad219bf84e..0000000000000000000000000000000000000000 --- a/crates/workspace/src/dock/toggle_dock_button.rs +++ /dev/null @@ -1,126 +0,0 @@ -use super::{icon_for_dock_anchor, Dock, FocusDock, HideDock}; -use crate::{handle_dropped_item, StatusItemView, Workspace}; -use gpui::{ - elements::{Empty, MouseEventHandler, Svg}, - platform::CursorStyle, - platform::MouseButton, - AnyElement, Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle, -}; -use settings::Settings; - -pub struct ToggleDockButton { - workspace: WeakViewHandle, -} - -impl ToggleDockButton { - pub fn new(workspace: ViewHandle, cx: &mut ViewContext) -> Self { - // When dock moves, redraw so that the icon and toggle status matches. - cx.subscribe(&workspace, |_, _, _, cx| cx.notify()).detach(); - - Self { - workspace: workspace.downgrade(), - } - } -} - -impl Entity for ToggleDockButton { - type Event = (); -} - -impl View for ToggleDockButton { - fn ui_name() -> &'static str { - "Dock Toggle" - } - - fn render(&mut self, cx: &mut gpui::ViewContext) -> AnyElement { - let workspace = self.workspace.upgrade(cx); - - if workspace.is_none() { - return Empty::new().into_any(); - } - - let workspace = workspace.unwrap(); - let dock_position = workspace.read(cx).dock.position; - let dock_pane = workspace.read(cx).dock_pane().clone(); - - let theme = cx.global::().theme.clone(); - - let button = MouseEventHandler::::new(0, cx, { - let theme = theme.clone(); - move |state, _| { - let style = theme - .workspace - .status_bar - .sidebar_buttons - .item - .style_for(state, dock_position.is_visible()); - - Svg::new(icon_for_dock_anchor(dock_position.anchor())) - .with_color(style.icon_color) - .constrained() - .with_width(style.icon_size) - .with_height(style.icon_size) - .contained() - .with_style(style.container) - } - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_up(MouseButton::Left, move |event, this, cx| { - let drop_index = dock_pane.read(cx).items_len() + 1; - handle_dropped_item( - event, - this.workspace.clone(), - &dock_pane.downgrade(), - drop_index, - false, - None, - cx, - ); - }); - - if dock_position.is_visible() { - button - .on_click(MouseButton::Left, |_, this, cx| { - if let Some(workspace) = this.workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - Dock::hide_dock(workspace, &Default::default(), cx) - }) - } - }) - .with_tooltip::( - 0, - "Hide Dock".into(), - Some(Box::new(HideDock)), - theme.tooltip.clone(), - cx, - ) - } else { - button - .on_click(MouseButton::Left, |_, this, cx| { - if let Some(workspace) = this.workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - Dock::focus_dock(workspace, &Default::default(), cx) - }) - } - }) - .with_tooltip::( - 0, - "Focus Dock".into(), - Some(Box::new(FocusDock)), - theme.tooltip.clone(), - cx, - ) - } - .into_any() - } -} - -impl StatusItemView for ToggleDockButton { - fn set_active_pane_item( - &mut self, - _active_pane_item: Option<&dyn crate::ItemHandle>, - _cx: &mut ViewContext, - ) { - //Not applicable - } -} diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 96320a4baf5c5460030e73efd8276f26c7c6f804..1aed1c011e7af69be58a1bff3045001657fc440a 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2,7 +2,6 @@ mod dragged_item_receiver; use super::{ItemHandle, SplitDirection}; use crate::{ - dock::{icon_for_dock_anchor, AnchorDockBottom, AnchorDockRight, Dock, ExpandDock}, item::WeakItemHandle, toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, Workspace, @@ -29,7 +28,7 @@ use gpui::{ }; use project::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; -use settings::{Autosave, DockAnchor, Settings}; +use settings::{Autosave, Settings}; use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc}; use theme::Theme; use util::ResultExt; @@ -148,7 +147,6 @@ pub struct Pane { toolbar: ViewHandle, tab_bar_context_menu: TabBarContextMenu, tab_context_menu: ViewHandle, - docked: Option, _background_actions: BackgroundActions, workspace: WeakViewHandle, has_focus: bool, @@ -204,7 +202,6 @@ pub enum ReorderBehavior { enum TabBarContextMenuKind { New, Split, - Dock, } struct TabBarContextMenu { @@ -224,7 +221,6 @@ impl TabBarContextMenu { impl Pane { pub fn new( workspace: WeakViewHandle, - docked: Option, background_actions: BackgroundActions, cx: &mut ViewContext, ) -> Self { @@ -256,7 +252,6 @@ impl Pane { handle: context_menu, }, tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)), - docked, _background_actions: background_actions, workspace, has_focus: false, @@ -280,11 +275,6 @@ impl Pane { self.has_focus } - pub fn set_docked(&mut self, docked: Option, cx: &mut ViewContext) { - self.docked = docked; - cx.notify(); - } - pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { ItemNavHistory { history: self.nav_history.clone(), @@ -1157,23 +1147,6 @@ impl Pane { self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split; } - fn deploy_dock_menu(&mut self, cx: &mut ViewContext) { - self.tab_bar_context_menu.handle.update(cx, |menu, cx| { - menu.show( - Default::default(), - AnchorCorner::TopRight, - vec![ - ContextMenuItem::action("Anchor Dock Right", AnchorDockRight), - ContextMenuItem::action("Anchor Dock Bottom", AnchorDockBottom), - ContextMenuItem::action("Expand Dock", ExpandDock), - ], - cx, - ); - }); - - self.tab_bar_context_menu.kind = TabBarContextMenuKind::Dock; - } - fn deploy_new_menu(&mut self, cx: &mut ViewContext) { self.tab_bar_context_menu.handle.update(cx, |menu, cx| { menu.show( @@ -1616,51 +1589,13 @@ impl Pane { self.tab_bar_context_menu .handle_if_kind(TabBarContextMenuKind::New), )) - .with_child( - self.docked - .map(|anchor| { - // Add the dock menu button if this pane is a dock - let dock_icon = icon_for_dock_anchor(anchor); - - render_tab_bar_button( - 1, - dock_icon, - cx, - |pane, cx| pane.deploy_dock_menu(cx), - self.tab_bar_context_menu - .handle_if_kind(TabBarContextMenuKind::Dock), - ) - }) - .unwrap_or_else(|| { - // Add the split menu if this pane is not a dock - render_tab_bar_button( - 2, - "icons/split_12.svg", - cx, - |pane, cx| pane.deploy_split_menu(cx), - self.tab_bar_context_menu - .handle_if_kind(TabBarContextMenuKind::Split), - ) - }), - ) - // Add the close dock button if this pane is a dock - .with_children(self.docked.map(|_| { - render_tab_bar_button( - 3, - "icons/x_mark_8.svg", - cx, - |this, cx| { - if let Some(workspace) = this.workspace.upgrade(cx) { - cx.window_context().defer(move |cx| { - workspace.update(cx, |workspace, cx| { - Dock::hide_dock(workspace, &Default::default(), cx) - }) - }); - } - }, - None, - ) - })) + .with_child(render_tab_bar_button( + 2, + "icons/split_12.svg", + cx, + |pane, cx| pane.deploy_split_menu(cx), + self.tab_bar_context_menu.handle_if_kind(TabBarContextMenuKind::Split), + )) .contained() .with_style(theme.workspace.tab_bar.pane_button_container) .flex(1., false) @@ -1737,11 +1672,7 @@ impl View for Pane { 0, self.active_item_index + 1, false, - if self.docked.is_some() { - None - } else { - Some(100.) - }, + Some(100.), cx, { let toolbar = self.toolbar.clone(); @@ -1843,9 +1774,6 @@ impl View for Pane { fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { Self::reset_to_default_keymap_context(keymap); - if self.docked.is_some() { - keymap.add_identifier("docked"); - } } } diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index d5d79c3ddd3368ed5960747908a6c88d4edd8094..3b7d0ecb8701c4b974781f9ef0bb9e8a123156d3 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -11,7 +11,6 @@ use gpui::{platform::WindowBounds, Axis}; use util::{unzip_option, ResultExt}; use uuid::Uuid; -use crate::dock::DockPosition; use crate::WorkspaceId; use model::{ @@ -25,9 +24,9 @@ define_connection! { // workspaces( // workspace_id: usize, // Primary key for workspaces // workspace_location: Bincode>, - // dock_visible: bool, - // dock_anchor: DockAnchor, // 'Bottom' / 'Right' / 'Expanded' - // dock_pane: Option, // PaneId + // dock_visible: bool, // Deprecated + // dock_anchor: DockAnchor, // Deprecated + // dock_pane: Option, // Deprecated // left_sidebar_open: boolean, // timestamp: String, // UTC YYYY-MM-DD HH:MM:SS // window_state: String, // WindowBounds Discriminant @@ -71,10 +70,10 @@ define_connection! { CREATE TABLE workspaces( workspace_id INTEGER PRIMARY KEY, workspace_location BLOB UNIQUE, - dock_visible INTEGER, // Boolean - dock_anchor TEXT, // Enum: 'Bottom' / 'Right' / 'Expanded' - dock_pane INTEGER, // NULL indicates that we don't have a dock pane yet - left_sidebar_open INTEGER, //Boolean + dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed. + dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed. + dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed. + left_sidebar_open INTEGER, // Boolean timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, FOREIGN KEY(dock_pane) REFERENCES panes(pane_id) ) STRICT; @@ -146,11 +145,10 @@ impl WorkspaceDb { // Note that we re-assign the workspace_id here in case it's empty // and we've grabbed the most recent workspace - let (workspace_id, workspace_location, left_sidebar_open, dock_position, bounds, display): ( + let (workspace_id, workspace_location, left_sidebar_open, bounds, display): ( WorkspaceId, WorkspaceLocation, bool, - DockPosition, Option, Option, ) = self @@ -159,8 +157,6 @@ impl WorkspaceDb { workspace_id, workspace_location, left_sidebar_open, - dock_visible, - dock_anchor, window_state, window_x, window_y, @@ -178,15 +174,10 @@ impl WorkspaceDb { Some(SerializedWorkspace { id: workspace_id, location: workspace_location.clone(), - dock_pane: self - .get_dock_pane(workspace_id) - .context("Getting dock pane") - .log_err()?, center_group: self .get_center_pane_group(workspace_id) .context("Getting center group") .log_err()?, - dock_position, left_sidebar_open, bounds, display, @@ -200,7 +191,6 @@ impl WorkspaceDb { conn.with_savepoint("update_worktrees", || { // Clear out panes and pane_groups conn.exec_bound(sql!( - UPDATE workspaces SET dock_pane = NULL WHERE workspace_id = ?1; DELETE FROM pane_groups WHERE workspace_id = ?1; DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id) .expect("Clearing old panes"); @@ -216,41 +206,24 @@ impl WorkspaceDb { workspace_id, workspace_location, left_sidebar_open, - dock_visible, - dock_anchor, timestamp ) - VALUES (?1, ?2, ?3, ?4, ?5, CURRENT_TIMESTAMP) + VALUES (?1, ?2, ?3, CURRENT_TIMESTAMP) ON CONFLICT DO UPDATE SET workspace_location = ?2, left_sidebar_open = ?3, - dock_visible = ?4, - dock_anchor = ?5, timestamp = CURRENT_TIMESTAMP ))?(( workspace.id, &workspace.location, - workspace.left_sidebar_open, - workspace.dock_position, )) .context("Updating workspace")?; - // Save center pane group and dock pane + // Save center pane group Self::save_pane_group(conn, workspace.id, &workspace.center_group, None) .context("save pane group in save workspace")?; - let dock_id = Self::save_pane(conn, workspace.id, &workspace.dock_pane, None, true) - .context("save pane in save workspace")?; - - // Complete workspace initialization - conn.exec_bound(sql!( - UPDATE workspaces - SET dock_pane = ? - WHERE workspace_id = ? - ))?((dock_id, workspace.id)) - .context("Finishing initialization with dock pane")?; - Ok(()) }) .log_err(); @@ -402,32 +375,17 @@ impl WorkspaceDb { Ok(()) } SerializedPaneGroup::Pane(pane) => { - Self::save_pane(conn, workspace_id, &pane, parent, false)?; + Self::save_pane(conn, workspace_id, &pane, parent)?; Ok(()) } } } - fn get_dock_pane(&self, workspace_id: WorkspaceId) -> Result { - let (pane_id, active) = self.select_row_bound(sql!( - SELECT pane_id, active - FROM panes - WHERE pane_id = (SELECT dock_pane FROM workspaces WHERE workspace_id = ?) - ))?(workspace_id)? - .context("No dock pane for workspace")?; - - Ok(SerializedPane::new( - self.get_items(pane_id).context("Reading items")?, - active, - )) - } - fn save_pane( conn: &Connection, workspace_id: WorkspaceId, pane: &SerializedPane, - parent: Option<(GroupId, usize)>, // None indicates BOTH dock pane AND center_pane - dock: bool, + parent: Option<(GroupId, usize)>, ) -> Result { let pane_id = conn.select_row_bound::<_, i64>(sql!( INSERT INTO panes(workspace_id, active) @@ -436,13 +394,11 @@ impl WorkspaceDb { ))?((workspace_id, pane.active))? .ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?; - if !dock { - let (parent_id, order) = unzip_option(parent); - conn.exec_bound(sql!( - INSERT INTO center_panes(pane_id, parent_group_id, position) - VALUES (?, ?, ?) - ))?((pane_id, parent_id, order))?; - } + let (parent_id, order) = unzip_option(parent); + conn.exec_bound(sql!( + INSERT INTO center_panes(pane_id, parent_group_id, position) + VALUES (?, ?, ?) + ))?((pane_id, parent_id, order))?; Self::save_items(conn, workspace_id, pane_id, &pane.children).context("Saving items")?; @@ -498,10 +454,8 @@ impl WorkspaceDb { #[cfg(test)] mod tests { - use std::sync::Arc; use db::open_test_db; - use settings::DockAnchor; use super::*; @@ -578,20 +532,16 @@ mod tests { let mut workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), - dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom), center_group: Default::default(), - dock_pane: Default::default(), left_sidebar_open: true, bounds: Default::default(), display: Default::default(), }; - let mut workspace_2 = SerializedWorkspace { + let mut _workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), - dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded), center_group: Default::default(), - dock_pane: Default::default(), left_sidebar_open: false, bounds: Default::default(), display: Default::default(), @@ -606,7 +556,7 @@ mod tests { }) .await; - db.save_workspace(workspace_2.clone()).await; + db.save_workspace(_workspace_2.clone()).await; db.write(|conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) @@ -619,26 +569,27 @@ mod tests { db.save_workspace(workspace_1.clone()).await; db.save_workspace(workspace_1).await; - workspace_2.dock_pane.children.push(SerializedItem { - kind: Arc::from("Test"), - item_id: 10, - active: true, - }); - db.save_workspace(workspace_2).await; - - let test_text_2 = db - .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) - .unwrap()(2) - .unwrap() - .unwrap(); - assert_eq!(test_text_2, "test-text-2"); - - let test_text_1 = db - .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) - .unwrap()(1) - .unwrap() - .unwrap(); - assert_eq!(test_text_1, "test-text-1"); + todo!(); + // workspace_2.dock_pane.children.push(SerializedItem { + // kind: Arc::from("Test"), + // item_id: 10, + // active: true, + // }); + // db.save_workspace(workspace_2).await; + + // let test_text_2 = db + // .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) + // .unwrap()(2) + // .unwrap() + // .unwrap(); + // assert_eq!(test_text_2, "test-text-2"); + + // let test_text_1 = db + // .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) + // .unwrap()(1) + // .unwrap() + // .unwrap(); + // assert_eq!(test_text_1, "test-text-1"); } #[gpui::test] @@ -647,16 +598,6 @@ mod tests { let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await); - let dock_pane = crate::persistence::model::SerializedPane { - children: vec![ - SerializedItem::new("Terminal", 1, false), - SerializedItem::new("Terminal", 2, false), - SerializedItem::new("Terminal", 3, true), - SerializedItem::new("Terminal", 4, false), - ], - active: false, - }; - // ----------------- // | 1,2 | 5,6 | // | - - - | | @@ -697,9 +638,7 @@ mod tests { let workspace = SerializedWorkspace { id: 5, location: (["/tmp", "/tmp2"]).into(), - dock_position: DockPosition::Shown(DockAnchor::Bottom), center_group, - dock_pane, left_sidebar_open: true, bounds: Default::default(), display: Default::default(), @@ -727,9 +666,7 @@ mod tests { let workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), - dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom), center_group: Default::default(), - dock_pane: Default::default(), left_sidebar_open: true, bounds: Default::default(), display: Default::default(), @@ -738,9 +675,7 @@ mod tests { let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), - dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded), center_group: Default::default(), - dock_pane: Default::default(), left_sidebar_open: false, bounds: Default::default(), display: Default::default(), @@ -776,9 +711,7 @@ mod tests { let mut workspace_3 = SerializedWorkspace { id: 3, location: (&["/tmp", "/tmp2"]).into(), - dock_position: DockPosition::Shown(DockAnchor::Right), center_group: Default::default(), - dock_pane: Default::default(), left_sidebar_open: false, bounds: Default::default(), display: Default::default(), @@ -801,52 +734,23 @@ mod tests { ); } - use crate::dock::DockPosition; use crate::persistence::model::SerializedWorkspace; use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup}; fn default_workspace>( workspace_id: &[P], - dock_pane: SerializedPane, center_group: &SerializedPaneGroup, ) -> SerializedWorkspace { SerializedWorkspace { id: 4, location: workspace_id.into(), - dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Right), center_group: center_group.clone(), - dock_pane, left_sidebar_open: true, bounds: Default::default(), display: Default::default(), } } - #[gpui::test] - async fn test_basic_dock_pane() { - env_logger::try_init().ok(); - - let db = WorkspaceDb(open_test_db("basic_dock_pane").await); - - let dock_pane = crate::persistence::model::SerializedPane::new( - vec![ - SerializedItem::new("Terminal", 1, false), - SerializedItem::new("Terminal", 4, false), - SerializedItem::new("Terminal", 2, false), - SerializedItem::new("Terminal", 3, true), - ], - false, - ); - - let workspace = default_workspace(&["/tmp"], dock_pane, &Default::default()); - - db.save_workspace(workspace.clone()).await; - - let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); - - assert_eq!(workspace.dock_pane, new_workspace.dock_pane); - } - #[gpui::test] async fn test_simple_split() { env_logger::try_init().ok(); @@ -890,7 +794,7 @@ mod tests { ], }; - let workspace = default_workspace(&["/tmp"], Default::default(), ¢er_pane); + let workspace = default_workspace(&["/tmp"], ¢er_pane); db.save_workspace(workspace.clone()).await; @@ -939,7 +843,7 @@ mod tests { let id = &["/tmp"]; - let mut workspace = default_workspace(id, Default::default(), ¢er_pane); + let mut workspace = default_workspace(id, ¢er_pane); db.save_workspace(workspace.clone()).await; diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index e838d22f0d746c7a717dbcac4804dcc3f593a00a..3a348d610562f15931203b335e3bb5e03387a622 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -1,5 +1,5 @@ use crate::{ - dock::DockPosition, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, + ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, }; use anyhow::{anyhow, Context, Result}; use async_recursion::async_recursion; @@ -11,7 +11,6 @@ use gpui::{ platform::WindowBounds, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WeakViewHandle, }; use project::Project; -use settings::DockAnchor; use std::{ path::{Path, PathBuf}, sync::Arc, @@ -62,9 +61,7 @@ impl Column for WorkspaceLocation { pub struct SerializedWorkspace { pub id: WorkspaceId, pub location: WorkspaceLocation, - pub dock_position: DockPosition, pub center_group: SerializedPaneGroup, - pub dock_pane: SerializedPane, pub left_sidebar_open: bool, pub bounds: Option, pub display: Option, @@ -278,64 +275,39 @@ impl Column for SerializedItem { } } -impl StaticColumnCount for DockPosition { - fn column_count() -> usize { - 2 - } -} -impl Bind for DockPosition { - fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let next_index = statement.bind(self.is_visible(), start_index)?; - statement.bind(self.anchor(), next_index) - } -} - -impl Column for DockPosition { - fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { - let (visible, next_index) = bool::column(statement, start_index)?; - let (dock_anchor, next_index) = DockAnchor::column(statement, next_index)?; - let position = if visible { - DockPosition::Shown(dock_anchor) - } else { - DockPosition::Hidden(dock_anchor) - }; - Ok((position, next_index)) - } -} - #[cfg(test)] mod tests { use db::sqlez::connection::Connection; - use settings::DockAnchor; - use super::WorkspaceLocation; + // use super::WorkspaceLocation; #[test] fn test_workspace_round_trips() { - let db = Connection::open_memory(Some("workspace_id_round_trips")); - - db.exec(indoc::indoc! {" - CREATE TABLE workspace_id_test( - workspace_id INTEGER, - dock_anchor TEXT - );"}) - .unwrap()() - .unwrap(); - - let workspace_id: WorkspaceLocation = WorkspaceLocation::from(&["\test2", "\test1"]); - - db.exec_bound("INSERT INTO workspace_id_test(workspace_id, dock_anchor) VALUES (?,?)") - .unwrap()((&workspace_id, DockAnchor::Bottom)) - .unwrap(); - - assert_eq!( - db.select_row("SELECT workspace_id, dock_anchor FROM workspace_id_test LIMIT 1") - .unwrap()() - .unwrap(), - Some(( - WorkspaceLocation::from(&["\test1", "\test2"]), - DockAnchor::Bottom - )) - ); + let _db = Connection::open_memory(Some("workspace_id_round_trips")); + + todo!(); + // db.exec(indoc::indoc! {" + // CREATE TABLE workspace_id_test( + // workspace_id INTEGER, + // dock_anchor TEXT + // );"}) + // .unwrap()() + // .unwrap(); + + // let workspace_id: WorkspaceLocation = WorkspaceLocation::from(&["\test2", "\test1"]); + + // db.exec_bound("INSERT INTO workspace_id_test(workspace_id, dock_anchor) VALUES (?,?)") + // .unwrap()((&workspace_id, DockAnchor::Bottom)) + // .unwrap(); + + // assert_eq!( + // db.select_row("SELECT workspace_id, dock_anchor FROM workspace_id_test LIMIT 1") + // .unwrap()() + // .unwrap(), + // Some(( + // WorkspaceLocation::from(&["\test1", "\test2"]), + // DockAnchor::Bottom + // )) + // ); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a57cea526bd950c4be5c0bfc1f7d9a5eecb39cc2..84d1832df73bcaca91ac0b90d8f4bf5f0971163c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2,7 +2,6 @@ /// /// This may cause issues when you're trying to write tests that use workspace focus to add items at /// specific locations. -pub mod dock; pub mod item; pub mod notifications; pub mod pane; @@ -22,7 +21,6 @@ use client::{ Client, TypedEnvelope, UserStore, }; use collections::{hash_map, HashMap, HashSet}; -use dock::{Dock, DockDefaultItemFactory, ToggleDockButton}; use drag_and_drop::DragAndDrop; use futures::{ channel::{mpsc, oneshot}, @@ -63,7 +61,6 @@ use crate::{ persistence::model::{SerializedPane, SerializedPaneGroup, SerializedWorkspace}, }; use lazy_static::lazy_static; -use log::warn; use notifications::{NotificationHandle, NotifyResultExt}; pub use pane::*; pub use pane_group::*; @@ -75,7 +72,7 @@ pub use persistence::{ use postage::prelude::Stream; use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use serde::Deserialize; -use settings::{Autosave, DockAnchor, Settings}; +use settings::{Autosave, Settings}; use shared_screen::SharedScreen; use sidebar::{Sidebar, SidebarButtons, SidebarSide, ToggleSidebarItem}; use status_bar::StatusBar; @@ -185,7 +182,6 @@ impl_actions!(workspace, [ActivatePane]); pub fn init(app_state: Arc, cx: &mut AppContext) { pane::init(cx); - dock::init(cx); notifications::init(cx); cx.add_global_action({ @@ -362,7 +358,6 @@ pub struct AppState { pub build_window_options: fn(Option, Option, &dyn Platform) -> WindowOptions<'static>, pub initialize_workspace: fn(&mut Workspace, &Arc, &mut ViewContext), - pub dock_default_item_factory: DockDefaultItemFactory, pub background_actions: BackgroundActions, } @@ -386,7 +381,6 @@ impl AppState { user_store, initialize_workspace: |_, _, _| {}, build_window_options: |_, _, _| Default::default(), - dock_default_item_factory: |_, _| None, background_actions: || &[], }) } @@ -439,7 +433,6 @@ impl DelayedDebouncedEditAction { } pub enum Event { - DockAnchorChanged, PaneAdded(ViewHandle), ContactRequestedJoin(u64), } @@ -457,7 +450,6 @@ pub struct Workspace { last_active_center_pane: Option>, status_bar: ViewHandle, titlebar_item: Option, - dock: Dock, notifications: Vec<(TypeId, usize, Box)>, project: ModelHandle, leader_state: LeaderState, @@ -535,16 +527,10 @@ impl Workspace { let weak_handle = cx.weak_handle(); let center_pane = cx - .add_view(|cx| Pane::new(weak_handle.clone(), None, app_state.background_actions, cx)); + .add_view(|cx| Pane::new(weak_handle.clone(), app_state.background_actions, cx)); cx.subscribe(¢er_pane, Self::handle_pane_event).detach(); cx.focus(¢er_pane); cx.emit(Event::PaneAdded(center_pane.clone())); - let dock = Dock::new( - app_state.dock_default_item_factory, - app_state.background_actions, - cx, - ); - let dock_pane = dock.pane().clone(); let mut current_user = app_state.user_store.read(cx).watch_current_user(); let mut connection_status = app_state.client.status(); @@ -559,7 +545,6 @@ impl Workspace { } anyhow::Ok(()) }); - let handle = cx.handle(); // All leader updates are enqueued and then processed in a single task, so // that each asynchronous operation can be run in order. @@ -581,14 +566,12 @@ impl Workspace { let right_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Right)); let left_sidebar_buttons = cx.add_view(|cx| SidebarButtons::new(left_sidebar.clone(), weak_handle.clone(), cx)); - let toggle_dock = cx.add_view(|cx| ToggleDockButton::new(handle, cx)); let right_sidebar_buttons = cx.add_view(|cx| SidebarButtons::new(right_sidebar.clone(), weak_handle.clone(), cx)); let status_bar = cx.add_view(|cx| { let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); status_bar.add_left_item(left_sidebar_buttons, cx); status_bar.add_right_item(right_sidebar_buttons, cx); - status_bar.add_right_item(toggle_dock, cx); status_bar }); @@ -630,11 +613,7 @@ impl Workspace { modal: None, weak_self: weak_handle.clone(), center: PaneGroup::new(center_pane.clone()), - dock, - // When removing an item, the last element remaining in this array - // is used to find where focus should fallback to. As such, the order - // of these two variables is important. - panes: vec![dock_pane.clone(), center_pane.clone()], + panes: vec![center_pane.clone()], panes_by_item: Default::default(), active_pane: center_pane.clone(), last_active_center_pane: Some(center_pane.downgrade()), @@ -664,10 +643,6 @@ impl Workspace { cx.defer(move |_, cx| { Self::load_from_serialized_workspace(weak_handle, serialized_workspace, cx) }); - } else if project.read(cx).is_local() { - if cx.global::().default_dock_anchor != DockAnchor::Expanded { - Dock::show(&mut this, false, cx); - } } this @@ -1336,16 +1311,11 @@ impl Workspace { SidebarSide::Left => &mut self.left_sidebar, SidebarSide::Right => &mut self.right_sidebar, }; - let open = sidebar.update(cx, |sidebar, cx| { + sidebar.update(cx, |sidebar, cx| { let open = !sidebar.is_open(); sidebar.set_open(open, cx); - open }); - if open { - Dock::hide_on_sidebar_shown(self, sidebar_side, cx); - } - self.serialize_workspace(cx); cx.focus_self(); @@ -1369,8 +1339,6 @@ impl Workspace { }); if let Some(active_item) = active_item { - Dock::hide_on_sidebar_shown(self, action.sidebar_side, cx); - if active_item.is_focused(cx) { cx.focus_self(); } else { @@ -1401,8 +1369,6 @@ impl Workspace { sidebar.active_item().cloned() }); if let Some(active_item) = active_item { - Dock::hide_on_sidebar_shown(self, sidebar_side, cx); - if active_item.is_focused(cx) { cx.focus_self(); } else { @@ -1424,7 +1390,6 @@ impl Workspace { let pane = cx.add_view(|cx| { Pane::new( self.weak_handle(), - None, self.app_state.background_actions, cx, ) @@ -1466,16 +1431,12 @@ impl Workspace { cx: &mut ViewContext, ) -> Task, anyhow::Error>> { let pane = pane.unwrap_or_else(|| { - if !self.dock_active() { - self.active_pane().downgrade() - } else { - self.last_active_center_pane.clone().unwrap_or_else(|| { - self.panes - .first() - .expect("There must be an active pane") - .downgrade() - }) - } + self.last_active_center_pane.clone().unwrap_or_else(|| { + self.panes + .first() + .expect("There must be an active pane") + .downgrade() + }) }); let task = self.load_path(path.into(), cx); @@ -1599,15 +1560,7 @@ impl Workspace { status_bar.set_active_pane(&self.active_pane, cx); }); self.active_item_path_changed(cx); - - if &pane == self.dock_pane() { - Dock::show(self, true, cx); - } else { self.last_active_center_pane = Some(pane.downgrade()); - if self.dock.is_anchored_at(DockAnchor::Expanded) { - Dock::hide(self, cx); - } - } cx.notify(); } @@ -1630,13 +1583,11 @@ impl Workspace { event: &pane::Event, cx: &mut ViewContext, ) { - let is_dock = &pane == self.dock.pane(); match event { - pane::Event::Split(direction) if !is_dock => { + pane::Event::Split(direction) => { self.split_pane(pane, *direction, cx); } - pane::Event::Remove if !is_dock => self.remove_pane(pane, cx), - pane::Event::Remove if is_dock => Dock::hide(self, cx), + pane::Event::Remove => self.remove_pane(pane, cx), pane::Event::ActivateItem { local } => { if *local { self.unfollow(&pane, cx); @@ -1662,7 +1613,6 @@ impl Workspace { pane::Event::Focus => { self.handle_pane_focused(pane.clone(), cx); } - _ => {} } self.serialize_workspace(cx); @@ -1674,11 +1624,6 @@ impl Workspace { direction: SplitDirection, cx: &mut ViewContext, ) -> Option> { - if &pane == self.dock_pane() { - warn!("Can't split dock pane."); - return None; - } - let item = pane.read(cx).active_item()?; let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) { let new_pane = self.add_pane(cx); @@ -1702,10 +1647,6 @@ impl Workspace { ) { let Some(pane_to_split) = pane_to_split.upgrade(cx) else { return; }; let Some(from) = from.upgrade(cx) else { return; }; - if &pane_to_split == self.dock_pane() { - warn!("Can't split dock pane."); - return; - } let new_pane = self.add_pane(cx); Pane::move_item(self, from.clone(), new_pane.clone(), item_id_to_move, 0, cx); @@ -1723,11 +1664,6 @@ impl Workspace { cx: &mut ViewContext, ) -> Option>> { let pane_to_split = pane_to_split.upgrade(cx)?; - if &pane_to_split == self.dock_pane() { - warn!("Can't split dock pane."); - return None; - } - let new_pane = self.add_pane(cx); self.center .split(&pane_to_split, &new_pane, split_direction) @@ -1764,14 +1700,6 @@ impl Workspace { &self.active_pane } - pub fn dock_pane(&self) -> &ViewHandle { - self.dock.pane() - } - - fn dock_active(&self) -> bool { - &self.active_pane == self.dock.pane() - } - fn project_remote_id_changed(&mut self, remote_id: Option, cx: &mut ViewContext) { if let Some(remote_id) = remote_id { self.remote_entity_subscription = Some( @@ -2518,14 +2446,11 @@ impl Workspace { // - with_local_workspace() relies on this to not have other stuff open // when you open your log if !location.paths().is_empty() { - let dock_pane = serialize_pane_handle(self.dock.pane(), cx); let center_group = build_serialized_pane_group(&self.center.root, cx); let serialized_workspace = SerializedWorkspace { id: self.database_id, location, - dock_position: self.dock.position(), - dock_pane, center_group, left_sidebar_open: self.left_sidebar.read(cx).is_open(), bounds: Default::default(), @@ -2545,26 +2470,14 @@ impl Workspace { cx: &mut AppContext, ) { cx.spawn(|mut cx| async move { - let (project, dock_pane_handle, old_center_pane) = + let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| { ( workspace.project().clone(), - workspace.dock_pane().downgrade(), workspace.last_active_center_pane.clone(), ) })?; - serialized_workspace - .dock_pane - .deserialize_to( - &project, - &dock_pane_handle, - serialized_workspace.id, - &workspace, - &mut cx, - ) - .await?; - // Traverse the splits tree and add to things let center_group = serialized_workspace .center_group @@ -2602,18 +2515,6 @@ impl Workspace { workspace.toggle_sidebar(SidebarSide::Left, cx); } - // Note that without after_window, the focus_self() and - // the focus the dock generates start generating alternating - // focus due to the deferred execution each triggering each other - cx.after_window_update(move |workspace, cx| { - Dock::set_dock_position( - workspace, - serialized_workspace.dock_position, - true, - cx, - ); - }); - cx.notify(); })?; @@ -2634,7 +2535,6 @@ impl Workspace { fs: project.read(cx).fs().clone(), build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _| {}, - dock_default_item_factory: |_, _| None, background_actions: || &[], }); Self::new(None, 0, project, app_state, cx) @@ -2729,15 +2629,9 @@ impl View for Workspace { )) .flex(1., true), ) - .with_children(self.dock.render( - &theme, - DockAnchor::Bottom, - cx, - )), ) .flex(1., true), ) - .with_children(self.dock.render(&theme, DockAnchor::Right, cx)) .with_children( if self.right_sidebar.read(cx).active_item().is_some() { Some( @@ -2760,11 +2654,6 @@ impl View for Workspace { }) .with_child(Overlay::new( Stack::new() - .with_children(self.dock.render( - &theme, - DockAnchor::Expanded, - cx, - )) .with_children(self.modal.as_ref().map(|modal| { ChildView::new(modal, cx) .contained() diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f498078b520f5fe497fb87f8a7f04a0ca4025460..ff969a8c7597d8accae7ceb93cffa0751012405e 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -52,7 +52,7 @@ use staff_mode::StaffMode; use theme::ThemeRegistry; use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use workspace::{ - dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, + item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, Workspace, }; use zed::{self, build_window_options, initialize_workspace, languages, menus}; @@ -205,7 +205,6 @@ fn main() { fs, build_window_options, initialize_workspace, - dock_default_item_factory, background_actions, }); cx.set_global(Arc::downgrade(&app_state)); @@ -776,7 +775,6 @@ pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] { &[ ("Go to file", &file_finder::Toggle), ("Open command palette", &command_palette::Toggle), - ("Focus the dock", &FocusDock), ("Open recent projects", &recent_projects::OpenRecent), ("Change your settings", &OpenSettings), ] diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index f687237bd22dd599a3d313bf3d79bdfa0aa64399..46443eb77d110f0056802dd22c288f3c8500dc73 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -306,7 +306,6 @@ pub fn initialize_workspace( .detach(); cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone())); - cx.emit(workspace::Event::PaneAdded(workspace.dock_pane().clone())); let collab_titlebar_item = cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); From 03f8c1206a3c1f0d9abcbb8dd2745694f32b90f7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 May 2023 16:04:36 -0600 Subject: [PATCH 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 a1e8cf6d9d189c02bc3a5b6cb1823563fa8739ca..634aed322abde3333cef56f352a752d03e68005f 100644 --- a/assets/keymaps/atom.json +++ b/assets/keymaps/atom.json @@ -39,8 +39,8 @@ { "context": "Workspace", "bindings": { - "cmd-\\": "workspace::ToggleLeftSidebar", - "cmd-k cmd-b": "workspace::ToggleLeftSidebar", + "cmd-\\": "workspace::ToggleLeftDock", + "cmd-k cmd-b": "workspace::ToggleLeftDock", "cmd-t": "file_finder::Toggle", "cmd-shift-r": "project_symbols::Toggle" } diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 6e8b5193138415606210f6b34a4263f6d0fbafe9..cc36f79e9f1bcf6b82ead7c5f19dfdb33d31a94c 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -349,7 +349,7 @@ "workspace::ActivatePane", 8 ], - "cmd-b": "workspace::ToggleLeftSidebar", + "cmd-b": "workspace::ToggleLeftDock", "cmd-shift-f": "workspace::NewSearch", "cmd-k cmd-t": "theme_selector::Toggle", "cmd-k cmd-s": "zed::OpenKeymap", diff --git a/assets/keymaps/jetbrains.json b/assets/keymaps/jetbrains.json index 0dafe58d32112e11f212978a1f48b9d598b6aa02..69a583e1e96522f26b935a1aff2424bfdfeedc33 100644 --- a/assets/keymaps/jetbrains.json +++ b/assets/keymaps/jetbrains.json @@ -65,7 +65,7 @@ "bindings": { "cmd-shift-a": "command_palette::Toggle", "cmd-alt-o": "project_symbols::Toggle", - "cmd-1": "workspace::ToggleLeftSidebar", + "cmd-1": "workspace::ToggleLeftDock", "cmd-6": "diagnostics::Deploy" } } diff --git a/assets/keymaps/sublime_text.json b/assets/keymaps/sublime_text.json index 7a285ee824d0fe58699dab65fe149cea16295e5f..2d32b77d58a91b0a508a8da03754ce5eada9d64b 100644 --- a/assets/keymaps/sublime_text.json +++ b/assets/keymaps/sublime_text.json @@ -45,7 +45,7 @@ { "context": "Workspace", "bindings": { - "cmd-k cmd-b": "workspace::ToggleLeftSidebar", + "cmd-k cmd-b": "workspace::ToggleLeftDock", "cmd-t": "file_finder::Toggle", "shift-cmd-r": "project_symbols::Toggle", // Currently busted: https://github.com/zed-industries/feedback/issues/898 diff --git a/assets/keymaps/textmate.json b/assets/keymaps/textmate.json index 738b6603ff8718dcc944dd9642133eb701c30491..06be72742950a320ae483df70c37b2db8a4bcf02 100644 --- a/assets/keymaps/textmate.json +++ b/assets/keymaps/textmate.json @@ -68,7 +68,7 @@ { "context": "Workspace", "bindings": { - "cmd-alt-ctrl-d": "workspace::ToggleLeftSidebar", + "cmd-alt-ctrl-d": "workspace::ToggleLeftDock", "cmd-t": "file_finder::Toggle", "cmd-shift-t": "project_symbols::Toggle" } diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 4b0c9b494a1dc4f6a05455522f95e0301b88b794..0d6bcc115cb3d402d0d452c295d11c9be997c5b0 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -64,7 +64,7 @@ impl View for CopilotButton { let style = theme .workspace .status_bar - .sidebar_buttons + .panel_buttons .item .style_for(state, active); diff --git a/crates/feedback/src/deploy_feedback_button.rs b/crates/feedback/src/deploy_feedback_button.rs index b464d0088707e73be0f71a1c2a333823ce76f13d..5fdb378eb628021c4657b8056a6d3cc36055586a 100644 --- a/crates/feedback/src/deploy_feedback_button.rs +++ b/crates/feedback/src/deploy_feedback_button.rs @@ -40,7 +40,7 @@ impl View for DeployFeedbackButton { let style = &theme .workspace .status_bar - .sidebar_buttons + .panel_buttons .item .style_for(state, active); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 7602ff7db8fefa640a8b0763656140f8f2e28be5..4a1d488eaf6b62a6ca8c92100bef0288fcf5748e 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1327,7 +1327,7 @@ impl Entity for ProjectPanel { type Event = Event; } -impl workspace::sidebar::SidebarItem for ProjectPanel { +impl workspace::dock::DockItem for ProjectPanel { fn should_show_badge(&self, _: &AppContext) -> bool { false } diff --git a/crates/terminal_view/src/terminal_button.rs b/crates/terminal_view/src/terminal_button.rs index 7b0da416ffcc865978fcaf292913d26c5f3603f7..cacd21c73306a21714a640b5e6c1e86b38e11bb3 100644 --- a/crates/terminal_view/src/terminal_button.rs +++ b/crates/terminal_view/src/terminal_button.rs @@ -52,7 +52,7 @@ impl View for TerminalButton { let style = theme .workspace .status_bar - .sidebar_buttons + .panel_buttons .item .style_for(state, active); diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 8c77295748eb7bcad94740ece4dc1c1c8c350b4a..c239e0ef40cad02ff419c3ecdf88303ea26df4ce 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -61,7 +61,7 @@ pub struct Workspace { pub pane_divider: Border, pub leader_border_opacity: f32, pub leader_border_width: f32, - pub sidebar: Sidebar, + pub dock: Dock, pub status_bar: StatusBar, pub toolbar: Toolbar, pub breadcrumb_height: f32, @@ -335,16 +335,16 @@ pub struct StatusBar { pub auto_update_progress_message: TextStyle, pub auto_update_done_message: TextStyle, pub lsp_status: Interactive, - pub sidebar_buttons: StatusBarSidebarButtons, + pub panel_buttons: StatusBarPanelButtons, pub diagnostic_summary: Interactive, pub diagnostic_message: Interactive, } #[derive(Deserialize, Default)] -pub struct StatusBarSidebarButtons { +pub struct StatusBarPanelButtons { pub group_left: ContainerStyle, pub group_right: ContainerStyle, - pub item: Interactive, + pub item: Interactive, pub badge: ContainerStyle, } @@ -375,14 +375,14 @@ pub struct StatusBarLspStatus { } #[derive(Deserialize, Default)] -pub struct Sidebar { +pub struct Dock { pub initial_size: f32, #[serde(flatten)] pub container: ContainerStyle, } #[derive(Clone, Deserialize, Default)] -pub struct SidebarItem { +pub struct DockItem { #[serde(flatten)] pub container: ContainerStyle, pub icon_color: Color, diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index a3d91adc910f658ab2bfd3b9af063ae4bc81142b..6adcf27bd1aa18f6f96c1f2433861cdce8e38e09 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -10,7 +10,7 @@ use gpui::{ use settings::{settings_file::SettingsFile, Settings}; use workspace::{ - item::Item, open_new, sidebar::SidebarSide, AppState, PaneBackdrop, Welcome, Workspace, + item::Item, open_new, dock::DockPosition, AppState, PaneBackdrop, Welcome, Workspace, WorkspaceId, }; @@ -29,7 +29,7 @@ pub fn init(cx: &mut AppContext) { pub fn show_welcome_experience(app_state: &Arc, cx: &mut AppContext) { open_new(&app_state, cx, |workspace, cx| { - workspace.toggle_sidebar(SidebarSide::Left, cx); + workspace.toggle_dock(DockPosition::Left, cx); let welcome_page = cx.add_view(|cx| WelcomePage::new(workspace, cx)); workspace.add_item_to_center(Box::new(welcome_page.clone()), cx); cx.focus(&welcome_page); diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/dock.rs similarity index 81% rename from crates/workspace/src/sidebar.rs rename to crates/workspace/src/dock.rs index 6463ab7d24b9ea6e44ababa2c3768dc7d36b663a..88fc9576bdb6472696129ae6a3e606a4eab28976 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/dock.rs @@ -7,7 +7,7 @@ use serde::Deserialize; use settings::Settings; use std::rc::Rc; -pub trait SidebarItem: View { +pub trait DockItem: View { fn should_activate_item_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { false } @@ -19,16 +19,16 @@ pub trait SidebarItem: View { } } -pub trait SidebarItemHandle { +pub trait DockItemHandle { fn id(&self) -> usize; fn should_show_badge(&self, cx: &WindowContext) -> bool; fn is_focused(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; } -impl SidebarItemHandle for ViewHandle +impl DockItemHandle for ViewHandle where - T: SidebarItem, + T: DockItem, { fn id(&self) -> usize { self.id() @@ -47,26 +47,26 @@ where } } -impl From<&dyn SidebarItemHandle> for AnyViewHandle { - fn from(val: &dyn SidebarItemHandle) -> Self { +impl From<&dyn DockItemHandle> for AnyViewHandle { + fn from(val: &dyn DockItemHandle) -> Self { val.as_any().clone() } } -pub struct Sidebar { - sidebar_side: SidebarSide, +pub struct Dock { + position: DockPosition, items: Vec, is_open: bool, active_item_ix: usize, } #[derive(Clone, Copy, Debug, Deserialize, PartialEq)] -pub enum SidebarSide { +pub enum DockPosition { Left, Right, } -impl SidebarSide { +impl DockPosition { fn to_resizable_side(self) -> Side { match self { Self::Left => Side::Right, @@ -78,27 +78,27 @@ impl SidebarSide { struct Item { icon_path: &'static str, tooltip: String, - view: Rc, + view: Rc, _subscriptions: [Subscription; 2], } -pub struct SidebarButtons { - sidebar: ViewHandle, +pub struct PanelButtons { + dock: ViewHandle, workspace: WeakViewHandle, } #[derive(Clone, Debug, Deserialize, PartialEq)] -pub struct ToggleSidebarItem { - pub sidebar_side: SidebarSide, +pub struct ToggleDockItem { + pub dock_position: DockPosition, pub item_index: usize, } -impl_actions!(workspace, [ToggleSidebarItem]); +impl_actions!(workspace, [ToggleDockItem]); -impl Sidebar { - pub fn new(sidebar_side: SidebarSide) -> Self { +impl Dock { + pub fn new(position: DockPosition) -> Self { Self { - sidebar_side, + position, items: Default::default(), active_item_ix: 0, is_open: false, @@ -126,7 +126,7 @@ impl Sidebar { cx.notify(); } - pub fn add_item( + pub fn add_item( &mut self, icon_path: &'static str, tooltip: String, @@ -171,7 +171,7 @@ impl Sidebar { cx.notify(); } - pub fn active_item(&self) -> Option<&Rc> { + pub fn active_item(&self) -> Option<&Rc> { if self.is_open { self.items.get(self.active_item_ix).map(|item| &item.view) } else { @@ -180,25 +180,25 @@ impl Sidebar { } } -impl Entity for Sidebar { +impl Entity for Dock { type Event = (); } -impl View for Sidebar { +impl View for Dock { fn ui_name() -> &'static str { - "Sidebar" + "Dock" } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(active_item) = self.active_item() { enum ResizeHandleTag {} - let style = &cx.global::().theme.workspace.sidebar; + let style = &cx.global::().theme.workspace.dock; ChildView::new(active_item.as_any(), cx) .contained() .with_style(style.container) .with_resize_handle::( - self.sidebar_side as usize, - self.sidebar_side.to_resizable_side(), + self.position as usize, + self.position.to_resizable_side(), 4., style.initial_size, cx, @@ -210,43 +210,43 @@ impl View for Sidebar { } } -impl SidebarButtons { +impl PanelButtons { pub fn new( - sidebar: ViewHandle, + dock: ViewHandle, workspace: WeakViewHandle, cx: &mut ViewContext, ) -> Self { - cx.observe(&sidebar, |_, _, cx| cx.notify()).detach(); - Self { sidebar, workspace } + cx.observe(&dock, |_, _, cx| cx.notify()).detach(); + Self { dock, workspace } } } -impl Entity for SidebarButtons { +impl Entity for PanelButtons { type Event = (); } -impl View for SidebarButtons { +impl View for PanelButtons { fn ui_name() -> &'static str { - "SidebarToggleButton" + "DockToggleButton" } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { let theme = &cx.global::().theme; let tooltip_style = theme.tooltip.clone(); - let theme = &theme.workspace.status_bar.sidebar_buttons; - let sidebar = self.sidebar.read(cx); + let theme = &theme.workspace.status_bar.panel_buttons; + let dock = self.dock.read(cx); let item_style = theme.item.clone(); let badge_style = theme.badge; - let active_ix = sidebar.active_item_ix; - let is_open = sidebar.is_open; - let sidebar_side = sidebar.sidebar_side; - let group_style = match sidebar_side { - SidebarSide::Left => theme.group_left, - SidebarSide::Right => theme.group_right, + let active_ix = dock.active_item_ix; + let is_open = dock.is_open; + let dock_position = dock.position; + let group_style = match dock_position { + DockPosition::Left => theme.group_left, + DockPosition::Right => theme.group_right, }; #[allow(clippy::needless_collect)] - let items = sidebar + let items = dock .items .iter() .map(|item| (item.icon_path, item.tooltip.clone(), item.view.clone())) @@ -255,8 +255,8 @@ impl View for SidebarButtons { Flex::row() .with_children(items.into_iter().enumerate().map( |(ix, (icon_path, tooltip, item_view))| { - let action = ToggleSidebarItem { - sidebar_side, + let action = ToggleDockItem { + dock_position, item_index: ix, }; MouseEventHandler::::new(ix, cx, |state, cx| { @@ -291,7 +291,7 @@ impl View for SidebarButtons { let action = action.clone(); cx.window_context().defer(move |cx| { workspace.update(cx, |workspace, cx| { - workspace.toggle_sidebar_item(&action, cx) + workspace.toggle_panel(&action, cx) }); }); } @@ -312,7 +312,7 @@ impl View for SidebarButtons { } } -impl StatusItemView for SidebarButtons { +impl StatusItemView for PanelButtons { fn set_active_pane_item( &mut self, _: Option<&dyn crate::ItemHandle>, diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 43c4544611e9f174db5d8d2350501020eaa6d84e..49fd882b65d033cc2b73807ffdf4ff6aba057e9b 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -766,7 +766,7 @@ impl FollowableItemHandle for ViewHandle { #[cfg(test)] pub(crate) mod test { use super::{Item, ItemEvent}; - use crate::{sidebar::SidebarItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; + use crate::{dock::DockItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; use gpui::{ elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -1060,5 +1060,5 @@ pub(crate) mod test { } } - impl SidebarItem for TestItem {} + impl DockItem for TestItem {} } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 84d1832df73bcaca91ac0b90d8f4bf5f0971163c..4ab9e0752619ca1ef4ac71cd9405813df1a44d21 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -9,7 +9,7 @@ pub mod pane_group; mod persistence; pub mod searchable; pub mod shared_screen; -pub mod sidebar; +pub mod dock; mod status_bar; mod toolbar; @@ -74,7 +74,7 @@ use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use serde::Deserialize; use settings::{Autosave, Settings}; use shared_screen::SharedScreen; -use sidebar::{Sidebar, SidebarButtons, SidebarSide, ToggleSidebarItem}; +use dock::{Dock, PanelButtons, DockPosition, ToggleDockItem}; use status_bar::StatusBar; pub use status_bar::StatusItemView; use theme::{Theme, ThemeRegistry}; @@ -114,7 +114,7 @@ actions!( ActivatePreviousPane, ActivateNextPane, FollowNextCollaborator, - ToggleLeftSidebar, + ToggleLeftDock, NewTerminal, NewSearch, Feedback, @@ -229,7 +229,7 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { workspace.save_active_item(true, cx).detach_and_log_err(cx); }, ); - cx.add_action(Workspace::toggle_sidebar_item); + cx.add_action(Workspace::toggle_panel); cx.add_action(Workspace::focus_center); cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| { workspace.activate_previous_pane(cx) @@ -237,8 +237,8 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| { workspace.activate_next_pane(cx) }); - cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftSidebar, cx| { - workspace.toggle_sidebar(SidebarSide::Left, cx); + cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| { + workspace.toggle_dock(DockPosition::Left, cx); }); cx.add_action(Workspace::activate_pane_at_index); @@ -442,8 +442,8 @@ pub struct Workspace { remote_entity_subscription: Option, modal: Option, center: PaneGroup, - left_sidebar: ViewHandle, - right_sidebar: ViewHandle, + left_dock: ViewHandle, + right_dock: ViewHandle, panes: Vec>, panes_by_item: HashMap>, active_pane: ViewHandle, @@ -562,16 +562,16 @@ impl Workspace { cx.emit_global(WorkspaceCreated(weak_handle.clone())); - let left_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Left)); - let right_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Right)); - let left_sidebar_buttons = - cx.add_view(|cx| SidebarButtons::new(left_sidebar.clone(), weak_handle.clone(), cx)); - let right_sidebar_buttons = - cx.add_view(|cx| SidebarButtons::new(right_sidebar.clone(), weak_handle.clone(), cx)); + let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left)); + let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right)); + let left_dock_buttons = + cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx)); + let right_dock_buttons = + cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); let status_bar = cx.add_view(|cx| { let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); - status_bar.add_left_item(left_sidebar_buttons, cx); - status_bar.add_right_item(right_sidebar_buttons, cx); + status_bar.add_left_item(left_dock_buttons, cx); + status_bar.add_right_item(right_dock_buttons, cx); status_bar }); @@ -621,8 +621,8 @@ impl Workspace { titlebar_item: None, notifications: Default::default(), remote_entity_subscription: None, - left_sidebar, - right_sidebar, + left_dock: left_dock, + right_dock: right_dock, project: project.clone(), leader_state: Default::default(), follower_states_by_leader: Default::default(), @@ -813,12 +813,12 @@ impl Workspace { self.weak_self.clone() } - pub fn left_sidebar(&self) -> &ViewHandle { - &self.left_sidebar + pub fn left_dock(&self) -> &ViewHandle { + &self.left_dock } - pub fn right_sidebar(&self) -> &ViewHandle { - &self.right_sidebar + pub fn right_dock(&self) -> &ViewHandle { + &self.right_dock } pub fn status_bar(&self) -> &ViewHandle { @@ -1306,14 +1306,14 @@ impl Workspace { } } - pub fn toggle_sidebar(&mut self, sidebar_side: SidebarSide, cx: &mut ViewContext) { - let sidebar = match sidebar_side { - SidebarSide::Left => &mut self.left_sidebar, - SidebarSide::Right => &mut self.right_sidebar, + pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { + let dock = match dock_side { + DockPosition::Left => &mut self.left_dock, + DockPosition::Right => &mut self.right_dock, }; - sidebar.update(cx, |sidebar, cx| { - let open = !sidebar.is_open(); - sidebar.set_open(open, cx); + dock.update(cx, |dock, cx| { + let open = !dock.is_open(); + dock.set_open(open, cx); }); self.serialize_workspace(cx); @@ -1322,19 +1322,19 @@ impl Workspace { cx.notify(); } - pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext) { - let sidebar = match action.sidebar_side { - SidebarSide::Left => &mut self.left_sidebar, - SidebarSide::Right => &mut self.right_sidebar, + pub fn toggle_panel(&mut self, action: &ToggleDockItem, cx: &mut ViewContext) { + let dock = match action.dock_position { + DockPosition::Left => &mut self.left_dock, + DockPosition::Right => &mut self.right_dock, }; - let active_item = sidebar.update(cx, move |sidebar, cx| { - if sidebar.is_open() && sidebar.active_item_ix() == action.item_index { - sidebar.set_open(false, cx); + let active_item = dock.update(cx, move |dock, cx| { + if dock.is_open() && dock.active_item_ix() == action.item_index { + dock.set_open(false, cx); None } else { - sidebar.set_open(true, cx); - sidebar.activate_item(action.item_index, cx); - sidebar.active_item().cloned() + dock.set_open(true, cx); + dock.activate_item(action.item_index, cx); + dock.active_item().cloned() } }); @@ -1353,20 +1353,20 @@ impl Workspace { cx.notify(); } - pub fn toggle_sidebar_item_focus( + pub fn toggle_panel_focus( &mut self, - sidebar_side: SidebarSide, - item_index: usize, + dock_position: DockPosition, + panel_index: usize, cx: &mut ViewContext, ) { - let sidebar = match sidebar_side { - SidebarSide::Left => &mut self.left_sidebar, - SidebarSide::Right => &mut self.right_sidebar, + let dock = match dock_position { + DockPosition::Left => &mut self.left_dock, + DockPosition::Right => &mut self.right_dock, }; - let active_item = sidebar.update(cx, |sidebar, cx| { - sidebar.set_open(true, cx); - sidebar.activate_item(item_index, cx); - sidebar.active_item().cloned() + let active_item = dock.update(cx, |dock, cx| { + dock.set_open(true, cx); + dock.activate_item(panel_index, cx); + dock.active_item().cloned() }); if let Some(active_item) = active_item { if active_item.is_focused(cx) { @@ -2452,7 +2452,7 @@ impl Workspace { id: self.database_id, location, center_group, - left_sidebar_open: self.left_sidebar.read(cx).is_open(), + left_sidebar_open: self.left_dock.read(cx).is_open(), bounds: Default::default(), display: Default::default(), }; @@ -2509,10 +2509,10 @@ impl Workspace { } } - if workspace.left_sidebar().read(cx).is_open() + if workspace.left_dock().read(cx).is_open() != serialized_workspace.left_sidebar_open { - workspace.toggle_sidebar(SidebarSide::Left, cx); + workspace.toggle_dock(DockPosition::Left, cx); } cx.notify(); @@ -2596,9 +2596,9 @@ impl View for Workspace { let project = self.project.clone(); Flex::row() .with_children( - if self.left_sidebar.read(cx).active_item().is_some() { + if self.left_dock.read(cx).active_item().is_some() { Some( - ChildView::new(&self.left_sidebar, cx) + ChildView::new(&self.left_dock, cx) .constrained() .dynamically(|constraint, _, cx| { SizeConstraint::new( @@ -2633,9 +2633,9 @@ impl View for Workspace { .flex(1., true), ) .with_children( - if self.right_sidebar.read(cx).active_item().is_some() { + if self.right_dock.read(cx).active_item().is_some() { Some( - ChildView::new(&self.right_sidebar, cx) + ChildView::new(&self.right_dock, cx) .constrained() .dynamically(|constraint, _, cx| { SizeConstraint::new( @@ -2803,7 +2803,7 @@ pub fn open_paths( workspace.update(&mut cx, |workspace, cx| { if contains_directory { - workspace.toggle_sidebar(SidebarSide::Left, cx); + workspace.toggle_dock(DockPosition::Left, cx); } })?; diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 741b61f323d13cf3682a1c5c9460fb2f97a16661..a98147c51b09ff4e06b8583c68e1206c37e799c6 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -89,7 +89,7 @@ pub fn menus() -> Vec> { MenuItem::action("Zoom Out", super::DecreaseBufferFontSize), MenuItem::action("Reset Zoom", super::ResetBufferFontSize), MenuItem::separator(), - MenuItem::action("Toggle Left Sidebar", workspace::ToggleLeftSidebar), + MenuItem::action("Toggle Left Dock", workspace::ToggleLeftDock), MenuItem::submenu(Menu { name: "Editor Layout", items: vec![ diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 46443eb77d110f0056802dd22c288f3c8500dc73..b3968e45fcc1a5946a86ded208f9f20b78a8d4a8 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -36,7 +36,7 @@ use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; pub use workspace; use workspace::{ - create_and_open_local_file, open_new, sidebar::SidebarSide, AppState, NewFile, NewWindow, + create_and_open_local_file, open_new, dock::DockPosition, AppState, NewFile, NewWindow, Workspace, }; @@ -242,7 +242,7 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { |workspace: &mut Workspace, _: &project_panel::ToggleFocus, cx: &mut ViewContext| { - workspace.toggle_sidebar_item_focus(SidebarSide::Left, 0, cx); + workspace.toggle_panel_focus(DockPosition::Left, 0, cx); }, ); cx.add_global_action({ @@ -312,8 +312,8 @@ pub fn initialize_workspace( workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); let project_panel = ProjectPanel::new(workspace, cx); - workspace.left_sidebar().update(cx, |sidebar, cx| { - sidebar.add_item( + workspace.left_dock().update(cx, |dock, cx| { + dock.add_item( "icons/folder_tree_16.svg", "Project Panel".to_string(), project_panel, @@ -658,7 +658,7 @@ mod tests { .unwrap(); workspace_1.update(cx, |workspace, cx| { assert_eq!(workspace.worktrees(cx).count(), 2); - assert!(workspace.left_sidebar().read(cx).is_open()); + assert!(workspace.left_dock().read(cx).is_open()); assert!(workspace.active_pane().is_focused(cx)); }); @@ -701,7 +701,7 @@ mod tests { .collect::>(), &[Path::new("/root/c").into(), Path::new("/root/d").into()] ); - assert!(workspace.left_sidebar().read(cx).is_open()); + assert!(workspace.left_dock().read(cx).is_open()); assert!(workspace.active_pane().is_focused(cx)); }); } diff --git a/styles/src/styleTree/statusBar.ts b/styles/src/styleTree/statusBar.ts index c55160c336275c841b04258836d3ed47f04704ea..8a711992ec589ab652a8b3c4b3d308a4aaf70b10 100644 --- a/styles/src/styleTree/statusBar.ts +++ b/styles/src/styleTree/statusBar.ts @@ -93,7 +93,7 @@ export default function statusBar(colorScheme: ColorScheme) { }, }, }, - sidebarButtons: { + panelButtons: { groupLeft: {}, groupRight: {}, item: { diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 9d0c4de9f78eef989a71ee33c1b9da3fcc8f8a50..fe359b0501b1549c22671e5513dcdf428ae71322 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -118,7 +118,7 @@ export default function workspace(colorScheme: ColorScheme) { }, cursor: "Arrow", }, - sidebar: { + dock: { initialSize: 240, border: border(layer, { left: true, right: true }), }, @@ -310,19 +310,6 @@ export default function workspace(colorScheme: ColorScheme) { width: 400, margin: { right: 10, bottom: 10 }, }, - dock: { - initialSizeRight: 640, - initialSizeBottom: 304, - wash_color: withOpacity(background(colorScheme.highest), 0.5), - panel: { - border: border(colorScheme.middle), - }, - maximized: { - margin: 32, - border: border(colorScheme.highest, { overlay: true }), - shadow: colorScheme.modalShadow, - }, - }, dropTargetOverlayColor: withOpacity(foreground(layer, "variant"), 0.5), } } From 1ddbda509535fc9e17bdabcbfe204537de472a2c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 May 2023 16:13:36 -0600 Subject: [PATCH 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 0d6bcc115cb3d402d0d452c295d11c9be997c5b0..884d1358f739b3d7a7189d5e7c2d341612032c71 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -65,7 +65,7 @@ impl View for CopilotButton { .workspace .status_bar .panel_buttons - .item + .button .style_for(state, active); Flex::row() diff --git a/crates/feedback/src/deploy_feedback_button.rs b/crates/feedback/src/deploy_feedback_button.rs index 5fdb378eb628021c4657b8056a6d3cc36055586a..fc90b38750efef6ea34790b5adb2fc996b9045eb 100644 --- a/crates/feedback/src/deploy_feedback_button.rs +++ b/crates/feedback/src/deploy_feedback_button.rs @@ -41,7 +41,7 @@ impl View for DeployFeedbackButton { .workspace .status_bar .panel_buttons - .item + .button .style_for(state, active); Svg::new("icons/feedback_16.svg") diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 4a1d488eaf6b62a6ca8c92100bef0288fcf5748e..35ab314f3ada6892c7a047b57f649a6737c40a1b 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1327,7 +1327,7 @@ impl Entity for ProjectPanel { type Event = Event; } -impl workspace::dock::DockItem for ProjectPanel { +impl workspace::dock::Panel for ProjectPanel { fn should_show_badge(&self, _: &AppContext) -> bool { false } diff --git a/crates/terminal_view/src/terminal_button.rs b/crates/terminal_view/src/terminal_button.rs index cacd21c73306a21714a640b5e6c1e86b38e11bb3..0747f86d2d0c99f1d150e507bb3c3e872ca5a761 100644 --- a/crates/terminal_view/src/terminal_button.rs +++ b/crates/terminal_view/src/terminal_button.rs @@ -53,7 +53,7 @@ impl View for TerminalButton { .workspace .status_bar .panel_buttons - .item + .button .style_for(state, active); Flex::row() diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index c239e0ef40cad02ff419c3ecdf88303ea26df4ce..bc6d63944739f40e3570fd07be7daf8af33a59a4 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -344,7 +344,7 @@ pub struct StatusBar { pub struct StatusBarPanelButtons { pub group_left: ContainerStyle, pub group_right: ContainerStyle, - pub item: Interactive, + pub button: Interactive, pub badge: ContainerStyle, } @@ -382,7 +382,7 @@ pub struct Dock { } #[derive(Clone, Deserialize, Default)] -pub struct DockItem { +pub struct PanelButton { #[serde(flatten)] pub container: ContainerStyle, pub icon_color: Color, diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 88fc9576bdb6472696129ae6a3e606a4eab28976..432ca646c8010674522fc3436ac755efe2f76fe0 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -7,7 +7,7 @@ use serde::Deserialize; use settings::Settings; use std::rc::Rc; -pub trait DockItem: View { +pub trait Panel: View { fn should_activate_item_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { false } @@ -19,16 +19,17 @@ pub trait DockItem: View { } } -pub trait DockItemHandle { +pub trait PanelHandle { + fn id(&self) -> usize; fn should_show_badge(&self, cx: &WindowContext) -> bool; fn is_focused(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; } -impl DockItemHandle for ViewHandle +impl PanelHandle for ViewHandle where - T: DockItem, + T: Panel, { fn id(&self) -> usize { self.id() @@ -47,8 +48,8 @@ where } } -impl From<&dyn DockItemHandle> for AnyViewHandle { - fn from(val: &dyn DockItemHandle) -> Self { +impl From<&dyn PanelHandle> for AnyViewHandle { + fn from(val: &dyn PanelHandle) -> Self { val.as_any().clone() } } @@ -78,7 +79,7 @@ impl DockPosition { struct Item { icon_path: &'static str, tooltip: String, - view: Rc, + view: Rc, _subscriptions: [Subscription; 2], } @@ -88,12 +89,12 @@ pub struct PanelButtons { } #[derive(Clone, Debug, Deserialize, PartialEq)] -pub struct ToggleDockItem { +pub struct TogglePanel { pub dock_position: DockPosition, pub item_index: usize, } -impl_actions!(workspace, [ToggleDockItem]); +impl_actions!(workspace, [TogglePanel]); impl Dock { pub fn new(position: DockPosition) -> Self { @@ -126,7 +127,7 @@ impl Dock { cx.notify(); } - pub fn add_item( + pub fn add_item( &mut self, icon_path: &'static str, tooltip: String, @@ -171,7 +172,7 @@ impl Dock { cx.notify(); } - pub fn active_item(&self) -> Option<&Rc> { + pub fn active_item(&self) -> Option<&Rc> { if self.is_open { self.items.get(self.active_item_ix).map(|item| &item.view) } else { @@ -235,7 +236,7 @@ impl View for PanelButtons { let tooltip_style = theme.tooltip.clone(); let theme = &theme.workspace.status_bar.panel_buttons; let dock = self.dock.read(cx); - let item_style = theme.item.clone(); + let item_style = theme.button.clone(); let badge_style = theme.badge; let active_ix = dock.active_item_ix; let is_open = dock.is_open; @@ -255,7 +256,7 @@ impl View for PanelButtons { Flex::row() .with_children(items.into_iter().enumerate().map( |(ix, (icon_path, tooltip, item_view))| { - let action = ToggleDockItem { + let action = TogglePanel { dock_position, item_index: ix, }; diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 49fd882b65d033cc2b73807ffdf4ff6aba057e9b..e7571c1bf6c8c5a028d20c92e0af9e772d0fa473 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -766,7 +766,7 @@ impl FollowableItemHandle for ViewHandle { #[cfg(test)] pub(crate) mod test { use super::{Item, ItemEvent}; - use crate::{dock::DockItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; + use crate::{dock::Panel, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; use gpui::{ elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -1060,5 +1060,5 @@ pub(crate) mod test { } } - impl DockItem for TestItem {} + impl Panel for TestItem {} } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 4ab9e0752619ca1ef4ac71cd9405813df1a44d21..f1bcb3928fb3418a4cacc829abd3c3dd2ae36d78 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -74,7 +74,7 @@ use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use serde::Deserialize; use settings::{Autosave, Settings}; use shared_screen::SharedScreen; -use dock::{Dock, PanelButtons, DockPosition, ToggleDockItem}; +use dock::{Dock, PanelButtons, DockPosition, TogglePanel}; use status_bar::StatusBar; pub use status_bar::StatusItemView; use theme::{Theme, ThemeRegistry}; @@ -1322,7 +1322,7 @@ impl Workspace { cx.notify(); } - pub fn toggle_panel(&mut self, action: &ToggleDockItem, cx: &mut ViewContext) { + pub fn toggle_panel(&mut self, action: &TogglePanel, cx: &mut ViewContext) { let dock = match action.dock_position { DockPosition::Left => &mut self.left_dock, DockPosition::Right => &mut self.right_dock, diff --git a/styles/src/styleTree/statusBar.ts b/styles/src/styleTree/statusBar.ts index 8a711992ec589ab652a8b3c4b3d308a4aaf70b10..614b6ab34d1e0f7c6775737d84c7aed75de70274 100644 --- a/styles/src/styleTree/statusBar.ts +++ b/styles/src/styleTree/statusBar.ts @@ -96,7 +96,7 @@ export default function statusBar(colorScheme: ColorScheme) { panelButtons: { groupLeft: {}, groupRight: {}, - item: { + button: { ...statusContainer, iconSize: 16, iconColor: foreground(layer, "variant"), From 6ddc610fa21905f366d5032a381e2f857a400754 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 8 May 2023 18:31:08 +0200 Subject: [PATCH 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 0000000000000000000000000000000000000000..19b34209a3e82fcf2c8bdcc78110a70b6baa357b --- /dev/null +++ b/crates/terminal_view/src/terminal_panel.rs @@ -0,0 +1,76 @@ +use gpui::{elements::*, Entity, ModelHandle, View, ViewContext, ViewHandle, WeakViewHandle}; +use project::Project; +use settings::{Settings, WorkingDirectory}; +use util::ResultExt; +use workspace::{dock::Panel, Pane, Workspace}; + +use crate::TerminalView; + +pub struct TerminalPanel { + project: ModelHandle, + pane: ViewHandle, + workspace: WeakViewHandle, +} + +impl TerminalPanel { + pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + Self { + project: workspace.project().clone(), + pane: cx.add_view(|cx| { + Pane::new( + workspace.weak_handle(), + workspace.app_state().background_actions, + cx, + ) + }), + workspace: workspace.weak_handle(), + } + } +} + +impl Entity for TerminalPanel { + type Event = (); +} + +impl View for TerminalPanel { + fn ui_name() -> &'static str { + "TerminalPanel" + } + + fn render(&mut self, cx: &mut ViewContext) -> gpui::AnyElement { + ChildView::new(&self.pane, cx).into_any() + } + + fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { + if self.pane.read(cx).items_len() == 0 { + if let Some(workspace) = self.workspace.upgrade(cx) { + let working_directory_strategy = cx + .global::() + .terminal_overrides + .working_directory + .clone() + .unwrap_or(WorkingDirectory::CurrentProjectDirectory); + let working_directory = crate::get_working_directory( + workspace.read(cx), + cx, + working_directory_strategy, + ); + let window_id = cx.window_id(); + if let Some(terminal) = self.project.update(cx, |project, cx| { + project + .create_terminal(working_directory, window_id, cx) + .log_err() + }) { + workspace.update(cx, |workspace, cx| { + let terminal = Box::new(cx.add_view(|cx| { + TerminalView::new(terminal, workspace.database_id(), cx) + })); + Pane::add_item(workspace, &self.pane, terminal, true, true, None, cx); + }); + } + } + } + } +} + +impl Panel for TerminalPanel {} diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index dfb2334dc554d6dabc1ea7b5ac0d71d6726daf86..e3a5153ef309fa4505576e2d202046c59eb3ae91 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1,6 +1,7 @@ mod persistence; pub mod terminal_button; pub mod terminal_element; +pub mod terminal_panel; use context_menu::{ContextMenu, ContextMenuItem}; use dirs::home_dir; diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index bc6d63944739f40e3570fd07be7daf8af33a59a4..67d07a3998d69366441ff698ebe0e506d847bb36 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -343,6 +343,7 @@ pub struct StatusBar { #[derive(Deserialize, Default)] pub struct StatusBarPanelButtons { pub group_left: ContainerStyle, + pub group_bottom: ContainerStyle, pub group_right: ContainerStyle, pub button: Interactive, pub badge: ContainerStyle, diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 432ca646c8010674522fc3436ac755efe2f76fe0..70708837e692e5f59874d113a4adec4f4ec00474 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -20,7 +20,6 @@ pub trait Panel: View { } pub trait PanelHandle { - fn id(&self) -> usize; fn should_show_badge(&self, cx: &WindowContext) -> bool; fn is_focused(&self, cx: &WindowContext) -> bool; @@ -64,6 +63,7 @@ pub struct Dock { #[derive(Clone, Copy, Debug, Deserialize, PartialEq)] pub enum DockPosition { Left, + Bottom, Right, } @@ -71,6 +71,7 @@ impl DockPosition { fn to_resizable_side(self) -> Side { match self { Self::Left => Side::Right, + Self::Bottom => Side::Bottom, Self::Right => Side::Left, } } @@ -243,6 +244,7 @@ impl View for PanelButtons { let dock_position = dock.position; let group_style = match dock_position { DockPosition::Left => theme.group_left, + DockPosition::Bottom => theme.group_bottom, DockPosition::Right => theme.group_right, }; diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 1aed1c011e7af69be58a1bff3045001657fc440a..2597b4af811b09ab83bac94d562ea2f8bd2eb7c9 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2,9 +2,7 @@ mod dragged_item_receiver; use super::{ItemHandle, SplitDirection}; use crate::{ - item::WeakItemHandle, - toolbar::Toolbar, - Item, NewFile, NewSearch, NewTerminal, Workspace, + item::WeakItemHandle, toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, Workspace, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -485,7 +483,7 @@ impl Pane { } } - pub(crate) fn add_item( + pub fn add_item( workspace: &mut Workspace, pane: &ViewHandle, item: Box, @@ -1594,7 +1592,8 @@ impl Pane { "icons/split_12.svg", cx, |pane, cx| pane.deploy_split_menu(cx), - self.tab_bar_context_menu.handle_if_kind(TabBarContextMenuKind::Split), + self.tab_bar_context_menu + .handle_if_kind(TabBarContextMenuKind::Split), )) .contained() .with_style(theme.workspace.tab_bar.pane_button_container) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f1bcb3928fb3418a4cacc829abd3c3dd2ae36d78..ee02a97439a2823f000fa6d8ec1d3d102c011268 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1,3 +1,4 @@ +pub mod dock; /// NOTE: Focus only 'takes' after an update has flushed_effects. /// /// This may cause issues when you're trying to write tests that use workspace focus to add items at @@ -9,7 +10,6 @@ pub mod pane_group; mod persistence; pub mod searchable; pub mod shared_screen; -pub mod dock; mod status_bar; mod toolbar; @@ -60,6 +60,7 @@ use crate::{ notifications::simple_message_notification::MessageNotification, persistence::model::{SerializedPane, SerializedPaneGroup, SerializedWorkspace}, }; +use dock::{Dock, DockPosition, PanelButtons, TogglePanel}; use lazy_static::lazy_static; use notifications::{NotificationHandle, NotifyResultExt}; pub use pane::*; @@ -74,7 +75,6 @@ use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use serde::Deserialize; use settings::{Autosave, Settings}; use shared_screen::SharedScreen; -use dock::{Dock, PanelButtons, DockPosition, TogglePanel}; use status_bar::StatusBar; pub use status_bar::StatusItemView; use theme::{Theme, ThemeRegistry}; @@ -443,6 +443,7 @@ pub struct Workspace { modal: Option, center: PaneGroup, left_dock: ViewHandle, + bottom_dock: ViewHandle, right_dock: ViewHandle, panes: Vec>, panes_by_item: HashMap>, @@ -526,8 +527,8 @@ impl Workspace { let weak_handle = cx.weak_handle(); - let center_pane = cx - .add_view(|cx| Pane::new(weak_handle.clone(), app_state.background_actions, cx)); + let center_pane = + cx.add_view(|cx| Pane::new(weak_handle.clone(), app_state.background_actions, cx)); cx.subscribe(¢er_pane, Self::handle_pane_event).detach(); cx.focus(¢er_pane); cx.emit(Event::PaneAdded(center_pane.clone())); @@ -563,14 +564,18 @@ impl Workspace { cx.emit_global(WorkspaceCreated(weak_handle.clone())); let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left)); + let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom)); let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right)); let left_dock_buttons = cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx)); + let bottom_dock_buttons = + cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx)); let right_dock_buttons = cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); let status_bar = cx.add_view(|cx| { let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); status_bar.add_left_item(left_dock_buttons, cx); + status_bar.add_right_item(bottom_dock_buttons, cx); status_bar.add_right_item(right_dock_buttons, cx); status_bar }); @@ -621,8 +626,9 @@ impl Workspace { titlebar_item: None, notifications: Default::default(), remote_entity_subscription: None, - left_dock: left_dock, - right_dock: right_dock, + left_dock, + bottom_dock, + right_dock, project: project.clone(), leader_state: Default::default(), follower_states_by_leader: Default::default(), @@ -817,6 +823,10 @@ impl Workspace { &self.left_dock } + pub fn bottom_dock(&self) -> &ViewHandle { + &self.bottom_dock + } + pub fn right_dock(&self) -> &ViewHandle { &self.right_dock } @@ -1309,6 +1319,7 @@ impl Workspace { pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { let dock = match dock_side { DockPosition::Left => &mut self.left_dock, + DockPosition::Bottom => &mut self.bottom_dock, DockPosition::Right => &mut self.right_dock, }; dock.update(cx, |dock, cx| { @@ -1325,6 +1336,7 @@ impl Workspace { pub fn toggle_panel(&mut self, action: &TogglePanel, cx: &mut ViewContext) { let dock = match action.dock_position { DockPosition::Left => &mut self.left_dock, + DockPosition::Bottom => &mut self.bottom_dock, DockPosition::Right => &mut self.right_dock, }; let active_item = dock.update(cx, move |dock, cx| { @@ -1361,6 +1373,7 @@ impl Workspace { ) { let dock = match dock_position { DockPosition::Left => &mut self.left_dock, + DockPosition::Bottom => &mut self.bottom_dock, DockPosition::Right => &mut self.right_dock, }; let active_item = dock.update(cx, |dock, cx| { @@ -1387,13 +1400,8 @@ impl Workspace { } fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { - let pane = cx.add_view(|cx| { - Pane::new( - self.weak_handle(), - self.app_state.background_actions, - cx, - ) - }); + let pane = + cx.add_view(|cx| Pane::new(self.weak_handle(), self.app_state.background_actions, cx)); cx.subscribe(&pane, Self::handle_pane_event).detach(); self.panes.push(pane.clone()); cx.focus(&pane); @@ -1560,7 +1568,7 @@ impl Workspace { status_bar.set_active_pane(&self.active_pane, cx); }); self.active_item_path_changed(cx); - self.last_active_center_pane = Some(pane.downgrade()); + self.last_active_center_pane = Some(pane.downgrade()); cx.notify(); } @@ -2470,13 +2478,12 @@ impl Workspace { cx: &mut AppContext, ) { cx.spawn(|mut cx| async move { - let (project, old_center_pane) = - workspace.read_with(&cx, |workspace, _| { - ( - workspace.project().clone(), - workspace.last_active_center_pane.clone(), - ) - })?; + let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| { + ( + workspace.project().clone(), + workspace.last_active_center_pane.clone(), + ) + })?; // Traverse the splits tree and add to things let center_group = serialized_workspace @@ -2615,22 +2622,28 @@ impl View for Workspace { }, ) .with_child( - FlexItem::new( - Flex::column() - .with_child( - FlexItem::new(self.center.render( - &project, - &theme, - &self.follower_states_by_leader, - self.active_call(), - self.active_pane(), - &self.app_state, - cx, - )) - .flex(1., true), - ) - ) - .flex(1., true), + Flex::column() + .with_child( + FlexItem::new(self.center.render( + &project, + &theme, + &self.follower_states_by_leader, + self.active_call(), + self.active_pane(), + &self.app_state, + cx, + )) + .flex(1., true), + ) + .with_children( + if self.bottom_dock.read(cx).active_item().is_some() + { + Some(ChildView::new(&self.bottom_dock, cx)) + } else { + None + }, + ) + .flex(1., true), ) .with_children( if self.right_dock.read(cx).active_item().is_some() { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index b3968e45fcc1a5946a86ded208f9f20b78a8d4a8..52b0ad08980ab2c90a53ddcac1ccf5ebdf9fd6a8 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -31,12 +31,12 @@ use serde::Deserialize; use serde_json::to_string_pretty; use settings::{Settings, DEFAULT_SETTINGS_ASSET_PATH}; use std::{borrow::Cow, str, sync::Arc}; -use terminal_view::terminal_button::TerminalButton; +use terminal_view::terminal_panel::TerminalPanel; use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; pub use workspace; use workspace::{ - create_and_open_local_file, open_new, dock::DockPosition, AppState, NewFile, NewWindow, + create_and_open_local_file, dock::DockPosition, open_new, AppState, NewFile, NewWindow, Workspace, }; @@ -318,10 +318,19 @@ pub fn initialize_workspace( "Project Panel".to_string(), project_panel, cx, - ) + ); + }); + + let terminal_panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); + workspace.bottom_dock().update(cx, |dock, cx| { + dock.add_item( + "icons/terminal_12.svg", + "Terminals".to_string(), + terminal_panel, + cx, + ); }); - let toggle_terminal = cx.add_view(|cx| TerminalButton::new(workspace_handle.clone(), cx)); let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx)); let diagnostic_summary = cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); @@ -335,7 +344,6 @@ pub fn initialize_workspace( workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(activity_indicator, cx); - status_bar.add_right_item(toggle_terminal, cx); status_bar.add_right_item(feedback_button, cx); status_bar.add_right_item(copilot, cx); status_bar.add_right_item(active_buffer_language, cx); diff --git a/styles/src/styleTree/statusBar.ts b/styles/src/styleTree/statusBar.ts index 614b6ab34d1e0f7c6775737d84c7aed75de70274..eca537c150d566e7fe454d7f32b5a652cc5c6328 100644 --- a/styles/src/styleTree/statusBar.ts +++ b/styles/src/styleTree/statusBar.ts @@ -95,6 +95,7 @@ export default function statusBar(colorScheme: ColorScheme) { }, panelButtons: { groupLeft: {}, + groupBottom: {}, groupRight: {}, button: { ...statusContainer, From 916612caf1fd12aaa9e782b61f36bb3f2579cc73 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 09:56:22 +0200 Subject: [PATCH 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 19b34209a3e82fcf2c8bdcc78110a70b6baa357b..ddc53537b75e5c575992cdd85c8477216c0995ea 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -2,7 +2,7 @@ use gpui::{elements::*, Entity, ModelHandle, View, ViewContext, ViewHandle, Weak use project::Project; use settings::{Settings, WorkingDirectory}; use util::ResultExt; -use workspace::{dock::Panel, Pane, Workspace}; +use workspace::{dock::Panel, DraggedItem, Pane, Workspace}; use crate::TerminalView; @@ -17,11 +17,20 @@ impl TerminalPanel { Self { project: workspace.project().clone(), pane: cx.add_view(|cx| { - Pane::new( + let window_id = cx.window_id(); + let mut pane = Pane::new( workspace.weak_handle(), workspace.app_state().background_actions, cx, - ) + ); + pane.on_can_drop(move |drag_and_drop, cx| { + drag_and_drop + .currently_dragged::(window_id) + .map_or(false, |(_, item)| { + item.handle.act_as::(cx).is_some() + }) + }); + pane }), workspace: workspace.weak_handle(), } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 2597b4af811b09ab83bac94d562ea2f8bd2eb7c9..fc3fda8e6a7fe1de348b49fbff37443d26f117ab 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -7,8 +7,8 @@ use crate::{ use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; use context_menu::{ContextMenu, ContextMenuItem}; -use drag_and_drop::Draggable; -pub use dragged_item_receiver::{dragged_item_receiver, handle_dropped_item}; +use drag_and_drop::{DragAndDrop, Draggable}; +use dragged_item_receiver::dragged_item_receiver; use futures::StreamExt; use gpui::{ actions, @@ -148,6 +148,7 @@ pub struct Pane { _background_actions: BackgroundActions, workspace: WeakViewHandle, has_focus: bool, + can_drop: Rc, &WindowContext) -> bool>, } pub struct ItemNavHistory { @@ -185,9 +186,9 @@ pub struct NavigationEntry { pub data: Option>, } -struct DraggedItem { - item: Box, - pane: WeakViewHandle, +pub struct DraggedItem { + pub handle: Box, + pub pane: WeakViewHandle, } pub enum ReorderBehavior { @@ -253,6 +254,7 @@ impl Pane { _background_actions: background_actions, workspace, has_focus: false, + can_drop: Rc::new(|_, _| true), } } @@ -273,6 +275,13 @@ impl Pane { self.has_focus } + pub fn on_can_drop(&mut self, can_drop: F) + where + F: 'static + Fn(&DragAndDrop, &WindowContext) -> bool, + { + self.can_drop = Rc::new(can_drop); + } + pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { ItemNavHistory { history: self.nav_history.clone(), @@ -1293,7 +1302,7 @@ impl Pane { row.add_child({ enum TabDragReceiver {} let mut receiver = - dragged_item_receiver::(ix, ix, true, None, cx, { + dragged_item_receiver::(self, ix, ix, true, None, cx, { let item = item.clone(); let pane = pane.clone(); let detail = detail.clone(); @@ -1372,7 +1381,7 @@ impl Pane { receiver.as_draggable( DraggedItem { - item, + handle: item, pane: pane.clone(), }, { @@ -1382,7 +1391,7 @@ impl Pane { move |dragged_item: &DraggedItem, cx: &mut ViewContext| { let tab_style = &theme.workspace.tab_bar.dragged_tab; Self::render_dragged_tab( - &dragged_item.item, + &dragged_item.handle, dragged_item.pane.clone(), false, detail, @@ -1402,7 +1411,7 @@ impl Pane { let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false); enum Filler {} row.add_child( - dragged_item_receiver::(0, filler_index, true, None, cx, |_, _| { + dragged_item_receiver::(self, 0, filler_index, true, None, cx, |_, _| { Empty::new() .contained() .with_style(filler_style.container) @@ -1601,11 +1610,7 @@ impl Pane { .into_any() } - fn render_blank_pane( - &mut self, - theme: &Theme, - _cx: &mut ViewContext, - ) -> AnyElement { + fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext) -> AnyElement { let background = theme.workspace.background; Empty::new() .contained() @@ -1668,6 +1673,7 @@ impl View for Pane { .with_child({ enum PaneContentTabDropTarget {} dragged_item_receiver::( + self, 0, self.active_item_index + 1, false, @@ -1696,7 +1702,7 @@ impl View for Pane { enum EmptyPane {} let theme = cx.global::().theme.clone(); - dragged_item_receiver::(0, 0, false, None, cx, |_, cx| { + dragged_item_receiver::(self, 0, 0, false, None, cx, |_, cx| { self.render_blank_pane(&theme, cx) }) .on_down(MouseButton::Left, |_, _, cx| { diff --git a/crates/workspace/src/pane/dragged_item_receiver.rs b/crates/workspace/src/pane/dragged_item_receiver.rs index 961205b9ee72e67a32e152bb18cbad58a7c7a64f..003cf1e4efc54ed7ae54526b5e62b03bef0515e1 100644 --- a/crates/workspace/src/pane/dragged_item_receiver.rs +++ b/crates/workspace/src/pane/dragged_item_receiver.rs @@ -15,6 +15,7 @@ use crate::{Pane, SplitDirection, Workspace}; use super::DraggedItem; pub fn dragged_item_receiver( + pane: &Pane, region_id: usize, drop_index: usize, allow_same_pane: bool, @@ -27,22 +28,24 @@ where D: Element, F: FnOnce(&mut MouseState, &mut ViewContext) -> D, { - MouseEventHandler::::above(region_id, cx, |state, cx| { + let drag_and_drop = cx.global::>(); + let drag_position = if (pane.can_drop)(drag_and_drop, cx) { + drag_and_drop + .currently_dragged::(cx.window_id()) + .map(|(drag_position, _)| drag_position) + .or_else(|| { + drag_and_drop + .currently_dragged::(cx.window_id()) + .map(|(drag_position, _)| drag_position) + }) + } else { + None + }; + + let mut handler = MouseEventHandler::::above(region_id, cx, |state, cx| { // Observing hovered will cause a render when the mouse enters regardless // of if mouse position was accessed before - let drag_position = if state.hovered() { - cx.global::>() - .currently_dragged::(cx.window_id()) - .map(|(drag_position, _)| drag_position) - .or_else(|| { - cx.global::>() - .currently_dragged::(cx.window_id()) - .map(|(drag_position, _)| drag_position) - }) - } else { - None - }; - + let drag_position = if state.hovered() { drag_position } else { None }; Stack::new() .with_child(render_child(state, cx)) .with_children(drag_position.map(|drag_position| { @@ -67,38 +70,44 @@ where } }) })) - }) - .on_up(MouseButton::Left, { - move |event, pane, cx| { - let workspace = pane.workspace.clone(); - let pane = cx.weak_handle(); - handle_dropped_item( - event, - workspace, - &pane, - drop_index, - allow_same_pane, - split_margin, - cx, - ); - cx.notify(); - } - }) - .on_move(|_, _, cx| { - let drag_and_drop = cx.global::>(); + }); - if drag_and_drop - .currently_dragged::(cx.window_id()) - .is_some() - || drag_and_drop - .currently_dragged::(cx.window_id()) - .is_some() - { - cx.notify(); - } else { - cx.propagate_event(); - } - }) + if drag_position.is_some() { + handler = handler + .on_up(MouseButton::Left, { + move |event, pane, cx| { + let workspace = pane.workspace.clone(); + let pane = cx.weak_handle(); + handle_dropped_item( + event, + workspace, + &pane, + drop_index, + allow_same_pane, + split_margin, + cx, + ); + cx.notify(); + } + }) + .on_move(|_, _, cx| { + let drag_and_drop = cx.global::>(); + + if drag_and_drop + .currently_dragged::(cx.window_id()) + .is_some() + || drag_and_drop + .currently_dragged::(cx.window_id()) + .is_some() + { + cx.notify(); + } else { + cx.propagate_event(); + } + }) + } + + handler } pub fn handle_dropped_item( @@ -118,7 +127,7 @@ pub fn handle_dropped_item( let action = if let Some((_, dragged_item)) = drag_and_drop.currently_dragged::(cx.window_id()) { - Action::Move(dragged_item.pane.clone(), dragged_item.item.id()) + Action::Move(dragged_item.pane.clone(), dragged_item.handle.id()) } else if let Some((_, project_entry)) = drag_and_drop.currently_dragged::(cx.window_id()) { From 506f978c41b100c5355730a0c40b07210f25782a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 10:17:35 +0200 Subject: [PATCH 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 ddc53537b75e5c575992cdd85c8477216c0995ea..7d715019c364f3c2c0644b3a02d533cdb551425d 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1,44 +1,66 @@ -use gpui::{elements::*, Entity, ModelHandle, View, ViewContext, ViewHandle, WeakViewHandle}; +use crate::TerminalView; +use gpui::{ + elements::*, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle, + WeakViewHandle, +}; use project::Project; use settings::{Settings, WorkingDirectory}; use util::ResultExt; -use workspace::{dock::Panel, DraggedItem, Pane, Workspace}; +use workspace::{dock::Panel, pane, DraggedItem, Pane, Workspace}; -use crate::TerminalView; +pub enum Event { + Close, +} pub struct TerminalPanel { project: ModelHandle, pane: ViewHandle, workspace: WeakViewHandle, + _subscription: Subscription, } impl TerminalPanel { pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + let pane = cx.add_view(|cx| { + let window_id = cx.window_id(); + let mut pane = Pane::new( + workspace.weak_handle(), + workspace.app_state().background_actions, + cx, + ); + pane.on_can_drop(move |drag_and_drop, cx| { + drag_and_drop + .currently_dragged::(window_id) + .map_or(false, |(_, item)| { + item.handle.act_as::(cx).is_some() + }) + }); + pane + }); + let subscription = cx.subscribe(&pane, Self::handle_pane_event); Self { project: workspace.project().clone(), - pane: cx.add_view(|cx| { - let window_id = cx.window_id(); - let mut pane = Pane::new( - workspace.weak_handle(), - workspace.app_state().background_actions, - cx, - ); - pane.on_can_drop(move |drag_and_drop, cx| { - drag_and_drop - .currently_dragged::(window_id) - .map_or(false, |(_, item)| { - item.handle.act_as::(cx).is_some() - }) - }); - pane - }), + pane, workspace: workspace.weak_handle(), + _subscription: subscription, + } + } + + fn handle_pane_event( + &mut self, + _pane: ViewHandle, + event: &pane::Event, + cx: &mut ViewContext, + ) { + match event { + pane::Event::Remove => cx.emit(Event::Close), + _ => {} } } } impl Entity for TerminalPanel { - type Event = (); + type Event = Event; } impl View for TerminalPanel { @@ -82,4 +104,8 @@ impl View for TerminalPanel { } } -impl Panel for TerminalPanel {} +impl Panel for TerminalPanel { + fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { + matches!(event, Event::Close) + } +} diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 70708837e692e5f59874d113a4adec4f4ec00474..3acda4429764429000b8525f9778995975d22ba2 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -8,7 +8,10 @@ use settings::Settings; use std::rc::Rc; pub trait Panel: View { - fn should_activate_item_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + false + } + fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { false } fn should_show_badge(&self, _: &AppContext) -> bool { @@ -53,6 +56,10 @@ impl From<&dyn PanelHandle> for AnyViewHandle { } } +pub enum Event { + Close, +} + pub struct Dock { position: DockPosition, items: Vec, @@ -138,7 +145,7 @@ impl Dock { let subscriptions = [ cx.observe(&view, |_, _, cx| cx.notify()), cx.subscribe(&view, |this, view, event, cx| { - if view.read(cx).should_activate_item_on_event(event, cx) { + if view.read(cx).should_activate_on_event(event, cx) { if let Some(ix) = this .items .iter() @@ -146,6 +153,8 @@ impl Dock { { this.activate_item(ix, cx); } + } else if view.read(cx).should_close_on_event(event, cx) { + cx.emit(Event::Close); } }), ]; @@ -183,7 +192,7 @@ impl Dock { } impl Entity for Dock { - type Event = (); + type Event = Event; } impl View for Dock { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ee02a97439a2823f000fa6d8ec1d3d102c011268..e79f071267e3f25f1c686605effac66c5053f419 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -461,7 +461,7 @@ pub struct Workspace { leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: WorkspaceId, app_state: Arc, - _window_subscriptions: [Subscription; 3], + _subscriptions: Vec, _apply_leader_updates: Task>, _observe_current_user: Task>, } @@ -592,7 +592,7 @@ impl Workspace { active_call = Some((call, subscriptions)); } - let subscriptions = [ + let subscriptions = vec![ cx.observe_fullscreen(|_, _, cx| cx.notify()), cx.observe_window_activation(Self::on_window_activation_changed), cx.observe_window_bounds(move |_, mut bounds, display, cx| { @@ -612,6 +612,9 @@ impl Workspace { .spawn(DB.set_window_bounds(workspace_id, bounds, display)) .detach_and_log_err(cx); }), + Self::register_dock(&left_dock, cx), + Self::register_dock(&bottom_dock, cx), + Self::register_dock(&right_dock, cx), ]; let mut this = Workspace { @@ -640,7 +643,7 @@ impl Workspace { _observe_current_user, _apply_leader_updates, leader_updates_tx, - _window_subscriptions: subscriptions, + _subscriptions: subscriptions, }; this.project_remote_id_changed(project.read(cx).remote_id(), cx); cx.defer(|this, cx| this.update_window_title(cx)); @@ -1316,6 +1319,26 @@ impl Workspace { } } + fn register_dock(dock: &ViewHandle, cx: &mut ViewContext) -> Subscription { + cx.subscribe(dock, Self::handle_dock_event) + } + + fn handle_dock_event( + &mut self, + dock: ViewHandle, + event: &dock::Event, + cx: &mut ViewContext, + ) { + match event { + dock::Event::Close => { + dock.update(cx, |dock, cx| dock.set_open(false, cx)); + self.serialize_workspace(cx); + cx.focus_self(); + cx.notify(); + } + } + } + pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { let dock = match dock_side { DockPosition::Left => &mut self.left_dock, From 02066afb0e801f8226f8a155682bf60706286e79 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 11:45:39 +0200 Subject: [PATCH 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 e7571c1bf6c8c5a028d20c92e0af9e772d0fa473..0ae9e4f0eac92c50b98d9247fe1ee30e6dc459c0 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -437,7 +437,7 @@ impl ItemHandle for ViewHandle { for item_event in T::to_item_events(event).into_iter() { match item_event { ItemEvent::CloseItem => { - Pane::close_item_by_id(workspace, pane, item.id(), cx) + pane.update(cx, |pane, cx| pane.close_item_by_id(item.id(), cx)) .detach_and_log_err(cx); return; } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index fc3fda8e6a7fe1de348b49fbff37443d26f117ab..c02c5d7ef4bc719ca7d602a9b8fe2681f794bdcf 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -681,187 +681,108 @@ impl Pane { } pub fn close_active_item( - workspace: &mut Workspace, + &mut self, _: &CloseActiveItem, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - let pane = pane_handle.read(cx); - - if pane.items.is_empty() { + if self.items.is_empty() { return None; } - let active_item_id = pane.items[pane.active_item_index].id(); - - let task = Self::close_item_by_id(workspace, pane_handle, active_item_id, cx); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + let active_item_id = self.items[self.active_item_index].id(); + Some(self.close_item_by_id(active_item_id, cx)) } pub fn close_item_by_id( - workspace: &mut Workspace, - pane: ViewHandle, + &mut self, item_id_to_close: usize, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Task> { - Self::close_items(workspace, pane, cx, move |view_id| { - view_id == item_id_to_close - }) + self.close_items(cx, move |view_id| view_id == item_id_to_close) } pub fn close_inactive_items( - workspace: &mut Workspace, + &mut self, _: &CloseInactiveItems, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - let pane = pane_handle.read(cx); - let active_item_id = pane.items[pane.active_item_index].id(); - - let task = Self::close_items(workspace, pane_handle, cx, move |item_id| { - item_id != active_item_id - }); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + let active_item_id = self.items[self.active_item_index].id(); + Some(self.close_items(cx, move |item_id| item_id != active_item_id)) } pub fn close_clean_items( - workspace: &mut Workspace, + &mut self, _: &CloseCleanItems, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - let pane = pane_handle.read(cx); - - let item_ids: Vec<_> = pane + let item_ids: Vec<_> = self .items() .filter(|item| !item.is_dirty(cx)) .map(|item| item.id()) .collect(); - - let task = Self::close_items(workspace, pane_handle, cx, move |item_id| { - item_ids.contains(&item_id) - }); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + Some(self.close_items(cx, move |item_id| item_ids.contains(&item_id))) } pub fn close_items_to_the_left( - workspace: &mut Workspace, + &mut self, _: &CloseItemsToTheLeft, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - let pane = pane_handle.read(cx); - let active_item_id = pane.items[pane.active_item_index].id(); - - let task = Self::close_items_to_the_left_by_id(workspace, pane_handle, active_item_id, cx); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + let active_item_id = self.items[self.active_item_index].id(); + Some(self.close_items_to_the_left_by_id(active_item_id, cx)) } pub fn close_items_to_the_left_by_id( - workspace: &mut Workspace, - pane: ViewHandle, + &mut self, item_id: usize, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Task> { - let item_ids: Vec<_> = pane - .read(cx) + let item_ids: Vec<_> = self .items() .take_while(|item| item.id() != item_id) .map(|item| item.id()) .collect(); - - let task = Self::close_items(workspace, pane, cx, move |item_id| { - item_ids.contains(&item_id) - }); - - cx.foreground().spawn(async move { - task.await?; - Ok(()) - }) + self.close_items(cx, move |item_id| item_ids.contains(&item_id)) } pub fn close_items_to_the_right( - workspace: &mut Workspace, + &mut self, _: &CloseItemsToTheRight, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - let pane = pane_handle.read(cx); - let active_item_id = pane.items[pane.active_item_index].id(); - - let task = Self::close_items_to_the_right_by_id(workspace, pane_handle, active_item_id, cx); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + let active_item_id = self.items[self.active_item_index].id(); + Some(self.close_items_to_the_right_by_id(active_item_id, cx)) } pub fn close_items_to_the_right_by_id( - workspace: &mut Workspace, - pane: ViewHandle, + &mut self, item_id: usize, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Task> { - let item_ids: Vec<_> = pane - .read(cx) + let item_ids: Vec<_> = self .items() .rev() .take_while(|item| item.id() != item_id) .map(|item| item.id()) .collect(); - - let task = Self::close_items(workspace, pane, cx, move |item_id| { - item_ids.contains(&item_id) - }); - - cx.foreground().spawn(async move { - task.await?; - Ok(()) - }) + self.close_items(cx, move |item_id| item_ids.contains(&item_id)) } pub fn close_all_items( - workspace: &mut Workspace, + &mut self, _: &CloseAllItems, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let pane_handle = workspace.active_pane().clone(); - - let task = Self::close_items(workspace, pane_handle, cx, move |_| true); - - Some(cx.foreground().spawn(async move { - task.await?; - Ok(()) - })) + Some(self.close_items(cx, move |_| true)) } pub fn close_items( - workspace: &mut Workspace, - pane: ViewHandle, - cx: &mut ViewContext, + &mut self, + cx: &mut ViewContext, should_close: impl 'static + Fn(usize) -> bool, ) -> Task> { - let project = workspace.project().clone(); - // Find the items to close. let mut items_to_close = Vec::new(); - for item in &pane.read(cx).items { + for item in &self.items { if should_close(item.id()) { items_to_close.push(item.boxed_clone()); } @@ -873,8 +794,8 @@ impl Pane { // of what content they would be saving. items_to_close.sort_by_key(|item| !item.is_singleton(cx)); - let pane = pane.downgrade(); - cx.spawn(|workspace, mut cx| async move { + let workspace = self.workspace.clone(); + cx.spawn(|pane, mut cx| async move { let mut saved_project_items_ids = HashSet::default(); for item in items_to_close.clone() { // Find the item's current index and its set of project item models. Avoid @@ -892,7 +813,7 @@ impl Pane { // Check if this view has any project items that are not open anywhere else // in the workspace, AND that the user has not already been prompted to save. // If there are any such project entries, prompt the user to save this item. - workspace.read_with(&cx, |workspace, cx| { + let project = workspace.read_with(&cx, |workspace, cx| { for item in workspace.items(cx) { if !items_to_close .iter() @@ -902,6 +823,7 @@ impl Pane { project_item_ids.retain(|id| !other_project_item_ids.contains(id)); } } + workspace.project().clone() })?; let should_save = project_item_ids .iter() @@ -1200,14 +1122,11 @@ impl Pane { // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command. vec![ ContextMenuItem::handler("Close Inactive Item", { - let workspace = self.workspace.clone(); let pane = target_pane.clone(); move |cx| { - if let Some((workspace, pane)) = - workspace.upgrade(cx).zip(pane.upgrade(cx)) - { - workspace.update(cx, |workspace, cx| { - Self::close_item_by_id(workspace, pane, target_item_id, cx) + if let Some(pane) = pane.upgrade(cx) { + pane.update(cx, |pane, cx| { + pane.close_item_by_id(target_item_id, cx) .detach_and_log_err(cx); }) } @@ -1216,39 +1135,23 @@ impl Pane { ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), ContextMenuItem::action("Close Clean Items", CloseCleanItems), ContextMenuItem::handler("Close Items To The Left", { - let workspace = self.workspace.clone(); let pane = target_pane.clone(); move |cx| { - if let Some((workspace, pane)) = - workspace.upgrade(cx).zip(pane.upgrade(cx)) - { - workspace.update(cx, |workspace, cx| { - Self::close_items_to_the_left_by_id( - workspace, - pane, - target_item_id, - cx, - ) - .detach_and_log_err(cx); + if let Some(pane) = pane.upgrade(cx) { + pane.update(cx, |pane, cx| { + pane.close_items_to_the_left_by_id(target_item_id, cx) + .detach_and_log_err(cx); }) } } }), ContextMenuItem::handler("Close Items To The Right", { - let workspace = self.workspace.clone(); let pane = target_pane.clone(); move |cx| { - if let Some((workspace, pane)) = - workspace.upgrade(cx).zip(pane.upgrade(cx)) - { - workspace.update(cx, |workspace, cx| { - Self::close_items_to_the_right_by_id( - workspace, - pane, - target_item_id, - cx, - ) - .detach_and_log_err(cx); + if let Some(pane) = pane.upgrade(cx) { + pane.update(cx, |pane, cx| { + pane.close_items_to_the_right_by_id(target_item_id, cx) + .detach_and_log_err(cx); }) } } @@ -1336,20 +1239,7 @@ impl Pane { .on_click(MouseButton::Middle, { let item_id = item.id(); move |_, pane, cx| { - let workspace = pane.workspace.clone(); - let pane = cx.weak_handle(); - cx.window_context().defer(move |cx| { - if let Some((workspace, pane)) = - workspace.upgrade(cx).zip(pane.upgrade(cx)) - { - workspace.update(cx, |workspace, cx| { - Self::close_item_by_id( - workspace, pane, item_id, cx, - ) - .detach_and_log_err(cx); - }); - } - }); + pane.close_item_by_id(item_id, cx).detach_and_log_err(cx); } }) .on_down( @@ -1556,12 +1446,9 @@ impl Pane { let pane = pane.clone(); cx.window_context().defer(move |cx| { if let Some(pane) = pane.upgrade(cx) { - if let Some(workspace) = pane.read(cx).workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - Self::close_item_by_id(workspace, pane, item_id, cx) - .detach_and_log_err(cx); - }); - } + pane.update(cx, |pane, cx| { + pane.close_item_by_id(item_id, cx).detach_and_log_err(cx); + }); } }); } @@ -2028,9 +1915,10 @@ mod tests { let project = Project::test(fs, None, cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - workspace.update(cx, |workspace, cx| { - assert!(Pane::close_active_item(workspace, &CloseActiveItem, cx).is_none()) + pane.update(cx, |pane, cx| { + assert!(pane.close_active_item(&CloseActiveItem, cx).is_none()) }); } @@ -2327,30 +2215,22 @@ mod tests { add_labeled_item(&workspace, &pane, "1", false, cx); assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_active_item(workspace, &CloseActiveItem, cx); - }); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, ["A", "B*", "C", "D"], cx); pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx)); assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_active_item(workspace, &CloseActiveItem, cx); - }); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, ["A", "B*", "C"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_active_item(workspace, &CloseActiveItem, cx); - }); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, ["A", "C*"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_active_item(workspace, &CloseActiveItem, cx); - }); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, ["A*"], cx); } @@ -2366,8 +2246,8 @@ mod tests { set_labeled_items(&workspace, &pane, ["A", "B", "C*", "D", "E"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_inactive_items(workspace, &CloseInactiveItems, cx); + pane.update(cx, |pane, cx| { + pane.close_inactive_items(&CloseInactiveItems, cx) }); deterministic.run_until_parked(); @@ -2390,9 +2270,7 @@ mod tests { add_labeled_item(&workspace, &pane, "E", false, cx); assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_clean_items(workspace, &CloseCleanItems, cx); - }); + pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, ["A^", "C*^"], cx); @@ -2412,8 +2290,8 @@ mod tests { set_labeled_items(&workspace, &pane, ["A", "B", "C*", "D", "E"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_items_to_the_left(workspace, &CloseItemsToTheLeft, cx); + pane.update(cx, |pane, cx| { + pane.close_items_to_the_left(&CloseItemsToTheLeft, cx) }); deterministic.run_until_parked(); @@ -2434,8 +2312,8 @@ mod tests { set_labeled_items(&workspace, &pane, ["A", "B", "C*", "D", "E"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_items_to_the_right(workspace, &CloseItemsToTheRight, cx); + pane.update(cx, |pane, cx| { + pane.close_items_to_the_right(&CloseItemsToTheRight, cx) }); deterministic.run_until_parked(); @@ -2456,9 +2334,7 @@ mod tests { add_labeled_item(&workspace, &pane, "C", false, cx); assert_item_labels(&pane, ["A", "B", "C*"], cx); - workspace.update(cx, |workspace, cx| { - Pane::close_all_items(workspace, &CloseAllItems, cx); - }); + pane.update(cx, |pane, cx| pane.close_all_items(&CloseAllItems, cx)); deterministic.run_until_parked(); assert_item_labels(&pane, [], cx); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e79f071267e3f25f1c686605effac66c5053f419..59a8e97f3d29a892657282114e007d699093b8a0 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3125,6 +3125,7 @@ mod tests { let project = Project::test(fs, ["root1".as_ref()], cx).await; let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let worktree_id = project.read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() }); @@ -3167,12 +3168,11 @@ mod tests { }); // Close the active item - workspace - .update(cx, |workspace, cx| { - Pane::close_active_item(workspace, &Default::default(), cx).unwrap() - }) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_active_item(&Default::default(), cx).unwrap() + }) + .await + .unwrap(); assert_eq!( cx.current_window_title(window_id).as_deref(), Some("one.txt — root1") @@ -3281,18 +3281,13 @@ mod tests { workspace.active_pane().clone() }); - let close_items = workspace.update(cx, |workspace, cx| { - pane.update(cx, |pane, cx| { - pane.activate_item(1, true, true, cx); - assert_eq!(pane.active_item().unwrap().id(), item2.id()); - }); - + let close_items = pane.update(cx, |pane, cx| { + pane.activate_item(1, true, true, cx); + assert_eq!(pane.active_item().unwrap().id(), item2.id()); let item1_id = item1.id(); let item3_id = item3.id(); let item4_id = item4.id(); - Pane::close_items(workspace, pane.clone(), cx, move |id| { - [item1_id, item3_id, item4_id].contains(&id) - }) + pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id)) }); cx.foreground().run_until_parked(); @@ -3426,10 +3421,7 @@ mod tests { // once for project entry 0, and once for project entry 2. After those two // prompts, the task should complete. - let close = workspace.update(cx, |workspace, cx| { - Pane::close_items(workspace, left_pane.clone(), cx, |_| true) - }); - + let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true)); cx.foreground().run_until_parked(); left_pane.read_with(cx, |pane, cx| { assert_eq!( @@ -3464,6 +3456,7 @@ mod tests { let project = Project::test(fs, [], cx).await; let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let item = cx.add_view(window_id, |cx| { TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) @@ -3536,11 +3529,7 @@ mod tests { item.is_dirty = true; }); - workspace - .update(cx, |workspace, cx| { - let pane = workspace.active_pane().clone(); - Pane::close_items(workspace, pane, cx, move |id| id == item_id) - }) + pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id)) .await .unwrap(); assert!(!cx.has_pending_prompt(window_id)); @@ -3561,10 +3550,8 @@ mod tests { item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); // Ensure autosave is prevented for deleted files also when closing the buffer. - let _close_items = workspace.update(cx, |workspace, cx| { - let pane = workspace.active_pane().clone(); - Pane::close_items(workspace, pane, cx, move |id| id == item_id) - }); + let _close_items = + pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id)); deterministic.run_until_parked(); assert!(cx.has_pending_prompt(window_id)); item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 52b0ad08980ab2c90a53ddcac1ccf5ebdf9fd6a8..662638301dce4bbb9b9cdae7fcb857b7e16842f4 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -734,6 +734,7 @@ mod tests { .unwrap() .downcast::() .unwrap(); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let editor = workspace.read_with(cx, |workspace, cx| { workspace .active_item(cx) @@ -756,9 +757,9 @@ mod tests { assert!(cx.is_window_edited(workspace.window_id())); // Closing the item restores the window's edited state. - let close = workspace.update(cx, |workspace, cx| { + let close = pane.update(cx, |pane, cx| { drop(editor); - Pane::close_active_item(workspace, &Default::default(), cx).unwrap() + pane.close_active_item(&Default::default(), cx).unwrap() }); executor.run_until_parked(); cx.simulate_prompt_answer(workspace.window_id(), 1); @@ -1384,6 +1385,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let entries = cx.read(|cx| workspace.file_project_paths(cx)); let file1 = entries[0].clone(); @@ -1501,14 +1503,13 @@ mod tests { // Go forward to an item that has been closed, ensuring it gets re-opened at the same // location. - workspace - .update(cx, |workspace, cx| { - let editor3_id = editor3.id(); - drop(editor3); - Pane::close_item_by_id(workspace, workspace.active_pane().clone(), editor3_id, cx) - }) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + let editor3_id = editor3.id(); + drop(editor3); + pane.close_item_by_id(editor3_id, cx) + }) + .await + .unwrap(); workspace .update(cx, |w, cx| Pane::go_forward(w, None, cx)) .await @@ -1537,14 +1538,13 @@ mod tests { ); // Go back to an item that has been closed and removed from disk, ensuring it gets skipped. - workspace - .update(cx, |workspace, cx| { - let editor2_id = editor2.id(); - drop(editor2); - Pane::close_item_by_id(workspace, workspace.active_pane().clone(), editor2_id, cx) - }) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + let editor2_id = editor2.id(); + drop(editor2); + pane.close_item_by_id(editor2_id, cx) + }) + .await + .unwrap(); app_state .fs .remove_file(Path::new("/root/a/file2"), Default::default()) @@ -1693,34 +1693,22 @@ mod tests { assert_eq!(active_path(&workspace, cx), Some(file4.clone())); // Close all the pane items in some arbitrary order. - workspace - .update(cx, |workspace, cx| { - Pane::close_item_by_id(workspace, pane.clone(), file1_item_id, cx) - }) + pane.update(cx, |pane, cx| pane.close_item_by_id(file1_item_id, cx)) .await .unwrap(); assert_eq!(active_path(&workspace, cx), Some(file4.clone())); - workspace - .update(cx, |workspace, cx| { - Pane::close_item_by_id(workspace, pane.clone(), file4_item_id, cx) - }) + pane.update(cx, |pane, cx| pane.close_item_by_id(file4_item_id, cx)) .await .unwrap(); assert_eq!(active_path(&workspace, cx), Some(file3.clone())); - workspace - .update(cx, |workspace, cx| { - Pane::close_item_by_id(workspace, pane.clone(), file2_item_id, cx) - }) + pane.update(cx, |pane, cx| pane.close_item_by_id(file2_item_id, cx)) .await .unwrap(); assert_eq!(active_path(&workspace, cx), Some(file3.clone())); - workspace - .update(cx, |workspace, cx| { - Pane::close_item_by_id(workspace, pane.clone(), file3_item_id, cx) - }) + pane.update(cx, |pane, cx| pane.close_item_by_id(file3_item_id, cx)) .await .unwrap(); assert_eq!(active_path(&workspace, cx), None); From e6be35c9a54d498e0c21cc2a54fc9f7076cc1e20 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 12:21:35 +0200 Subject: [PATCH 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 35ab314f3ada6892c7a047b57f649a6737c40a1b..cecf6c114f27d567fd30564f91f05c37716a14bb 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1327,11 +1327,7 @@ impl Entity for ProjectPanel { type Event = Event; } -impl workspace::dock::Panel for ProjectPanel { - fn should_show_badge(&self, _: &AppContext) -> bool { - false - } -} +impl workspace::dock::Panel for ProjectPanel {} impl ClipboardEntry { fn is_cut(&self) -> bool { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 7d715019c364f3c2c0644b3a02d533cdb551425d..ce70f083911eb336cd9e41d9778398ffa3729e10 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -16,7 +16,7 @@ pub struct TerminalPanel { project: ModelHandle, pane: ViewHandle, workspace: WeakViewHandle, - _subscription: Subscription, + _subscriptions: Vec, } impl TerminalPanel { @@ -37,12 +37,15 @@ impl TerminalPanel { }); pane }); - let subscription = cx.subscribe(&pane, Self::handle_pane_event); + let subscriptions = vec![ + cx.observe(&pane, |_, _, cx| cx.notify()), + cx.subscribe(&pane, Self::handle_pane_event), + ]; Self { project: workspace.project().clone(), pane, workspace: workspace.weak_handle(), - _subscription: subscription, + _subscriptions: subscriptions, } } @@ -108,4 +111,13 @@ impl Panel for TerminalPanel { fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { matches!(event, Event::Close) } + + fn label(&self, cx: &AppContext) -> Option { + let count = self.pane.read(cx).items_len(); + if count == 0 { + None + } else { + Some(count.to_string()) + } + } } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 67d07a3998d69366441ff698ebe0e506d847bb36..2d4c5201270aa8ada60345b5da52ea302c6f3482 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -346,7 +346,6 @@ pub struct StatusBarPanelButtons { pub group_bottom: ContainerStyle, pub group_right: ContainerStyle, pub button: Interactive, - pub badge: ContainerStyle, } #[derive(Deserialize, Default)] diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 3acda4429764429000b8525f9778995975d22ba2..57d18fd3686a3e91d3c83d55cd7b1cca8ed71a5e 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -14,8 +14,8 @@ pub trait Panel: View { fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { false } - fn should_show_badge(&self, _: &AppContext) -> bool { - false + fn label(&self, _: &AppContext) -> Option { + None } fn contains_focused_view(&self, _: &AppContext) -> bool { false @@ -24,7 +24,7 @@ pub trait Panel: View { pub trait PanelHandle { fn id(&self) -> usize; - fn should_show_badge(&self, cx: &WindowContext) -> bool; + fn label(&self, cx: &WindowContext) -> Option; fn is_focused(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; } @@ -37,8 +37,8 @@ where self.id() } - fn should_show_badge(&self, cx: &WindowContext) -> bool { - self.read(cx).should_show_badge(cx) + fn label(&self, cx: &WindowContext) -> Option { + self.read(cx).label(cx) } fn is_focused(&self, cx: &WindowContext) -> bool { @@ -247,7 +247,6 @@ impl View for PanelButtons { let theme = &theme.workspace.status_bar.panel_buttons; let dock = self.dock.read(cx); let item_style = theme.button.clone(); - let badge_style = theme.badge; let active_ix = dock.active_item_ix; let is_open = dock.is_open; let dock_position = dock.position; @@ -274,23 +273,25 @@ impl View for PanelButtons { MouseEventHandler::::new(ix, cx, |state, cx| { let is_active = is_open && ix == active_ix; let style = item_style.style_for(state, is_active); - Stack::new() - .with_child(Svg::new(icon_path).with_color(style.icon_color)) - .with_children(if !is_active && item_view.should_show_badge(cx) { + Flex::row() + .with_child( + Svg::new(icon_path) + .with_color(style.icon_color) + .constrained() + .with_width(style.icon_size) + .aligned(), + ) + .with_children(if let Some(label) = item_view.label(cx) { Some( - Empty::new() - .collapsed() + Label::new(label, style.label.text.clone()) .contained() - .with_style(badge_style) - .aligned() - .bottom() - .right(), + .with_style(style.label.container) + .aligned(), ) } else { None }) .constrained() - .with_width(style.icon_size) .with_height(style.icon_size) .contained() .with_style(style.container) From 26fe7a81f34f69ec993f993eb1523dbe2d8cb480 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 12:29:16 +0200 Subject: [PATCH 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 ce70f083911eb336cd9e41d9778398ffa3729e10..5b1a7d20dfff1c6beb5d28e382525dd9959e7b79 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -28,6 +28,7 @@ impl TerminalPanel { workspace.app_state().background_actions, cx, ); + pane.set_can_split(false, cx); pane.on_can_drop(move |drag_and_drop, cx| { drag_and_drop .currently_dragged::(window_id) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index c02c5d7ef4bc719ca7d602a9b8fe2681f794bdcf..fd617e1685d06bd1b9d9628620e283d7625fc1dd 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -149,6 +149,7 @@ pub struct Pane { workspace: WeakViewHandle, has_focus: bool, can_drop: Rc, &WindowContext) -> bool>, + can_split: bool, } pub struct ItemNavHistory { @@ -255,6 +256,7 @@ impl Pane { workspace, has_focus: false, can_drop: Rc::new(|_, _| true), + can_split: true, } } @@ -282,6 +284,11 @@ impl Pane { self.can_drop = Rc::new(can_drop); } + pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext) { + self.can_split = can_split; + cx.notify(); + } + pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { ItemNavHistory { history: self.nav_history.clone(), @@ -1563,8 +1570,8 @@ impl View for Pane { self, 0, self.active_item_index + 1, - false, - Some(100.), + !self.can_split, + if self.can_split { Some(100.) } else { None }, cx, { let toolbar = self.toolbar.clone(); From ad7f32d7d2c2fc9e3a1dba920f6796ce8402297f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 14:46:15 +0200 Subject: [PATCH 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 57d18fd3686a3e91d3c83d55cd7b1cca8ed71a5e..2b23ceb7f308bc6cc0cb83073757d161eeb5796f 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -78,7 +78,7 @@ impl DockPosition { fn to_resizable_side(self) -> Side { match self { Self::Left => Side::Right, - Self::Bottom => Side::Bottom, + Self::Bottom => Side::Top, Self::Right => Side::Left, } } From 634b699281a3ab66a690fa93cead04e78d94aa9c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 15:05:29 +0200 Subject: [PATCH 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 5b1a7d20dfff1c6beb5d28e382525dd9959e7b79..90072ea6528cdf20ab56b74040ef6bde5b00f131 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -21,6 +21,7 @@ pub struct TerminalPanel { impl TerminalPanel { pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + let this = cx.weak_handle(); let pane = cx.add_view(|cx| { let window_id = cx.window_id(); let mut pane = Pane::new( @@ -36,6 +37,23 @@ impl TerminalPanel { item.handle.act_as::(cx).is_some() }) }); + pane.set_render_tab_bar_buttons(cx, move |_, cx| { + let this = this.clone(); + Pane::render_tab_bar_button( + 0, + "icons/plus_12.svg", + cx, + move |_, cx| { + let this = this.clone(); + cx.window_context().defer(move |cx| { + if let Some(this) = this.upgrade(cx) { + this.update(cx, |this, cx| this.add_terminal(cx)); + } + }) + }, + None, + ) + }); pane }); let subscriptions = vec![ @@ -61,6 +79,33 @@ impl TerminalPanel { _ => {} } } + + fn add_terminal(&mut self, cx: &mut ViewContext) { + if let Some(workspace) = self.workspace.upgrade(cx) { + let working_directory_strategy = cx + .global::() + .terminal_overrides + .working_directory + .clone() + .unwrap_or(WorkingDirectory::CurrentProjectDirectory); + let working_directory = + crate::get_working_directory(workspace.read(cx), cx, working_directory_strategy); + let window_id = cx.window_id(); + if let Some(terminal) = self.project.update(cx, |project, cx| { + project + .create_terminal(working_directory, window_id, cx) + .log_err() + }) { + workspace.update(cx, |workspace, cx| { + let terminal = + Box::new(cx.add_view(|cx| { + TerminalView::new(terminal, workspace.database_id(), cx) + })); + Pane::add_item(workspace, &self.pane, terminal, true, true, None, cx); + }); + } + } + } } impl Entity for TerminalPanel { @@ -78,32 +123,7 @@ impl View for TerminalPanel { fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { if self.pane.read(cx).items_len() == 0 { - if let Some(workspace) = self.workspace.upgrade(cx) { - let working_directory_strategy = cx - .global::() - .terminal_overrides - .working_directory - .clone() - .unwrap_or(WorkingDirectory::CurrentProjectDirectory); - let working_directory = crate::get_working_directory( - workspace.read(cx), - cx, - working_directory_strategy, - ); - let window_id = cx.window_id(); - if let Some(terminal) = self.project.update(cx, |project, cx| { - project - .create_terminal(working_directory, window_id, cx) - .log_err() - }) { - workspace.update(cx, |workspace, cx| { - let terminal = Box::new(cx.add_view(|cx| { - TerminalView::new(terminal, workspace.database_id(), cx) - })); - Pane::add_item(workspace, &self.pane, terminal, true, true, None, cx); - }); - } - } + self.add_terminal(cx) } } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index fd617e1685d06bd1b9d9628620e283d7625fc1dd..0523f082ea5a8d56014a5fe315b2263a66c966f7 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -150,6 +150,7 @@ pub struct Pane { has_focus: bool, can_drop: Rc, &WindowContext) -> bool>, can_split: bool, + render_tab_bar_buttons: Rc) -> AnyElement>, } pub struct ItemNavHistory { @@ -257,6 +258,27 @@ impl Pane { has_focus: false, can_drop: Rc::new(|_, _| true), can_split: true, + render_tab_bar_buttons: Rc::new(|pane, cx| { + Flex::row() + // New menu + .with_child(Self::render_tab_bar_button( + 0, + "icons/plus_12.svg", + cx, + |pane, cx| pane.deploy_new_menu(cx), + pane.tab_bar_context_menu + .handle_if_kind(TabBarContextMenuKind::New), + )) + .with_child(Self::render_tab_bar_button( + 2, + "icons/split_12.svg", + cx, + |pane, cx| pane.deploy_split_menu(cx), + pane.tab_bar_context_menu + .handle_if_kind(TabBarContextMenuKind::Split), + )) + .into_any() + }), } } @@ -289,6 +311,14 @@ impl Pane { cx.notify(); } + pub fn set_render_tab_bar_buttons(&mut self, cx: &mut ViewContext, render: F) + where + F: 'static + Fn(&mut Pane, &mut ViewContext) -> AnyElement, + { + self.render_tab_bar_buttons = Rc::new(render); + cx.notify(); + } + pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { ItemNavHistory { history: self.nav_history.clone(), @@ -1475,33 +1505,37 @@ impl Pane { .into_any() } - fn render_tab_bar_buttons( - &mut self, - theme: &Theme, - cx: &mut ViewContext, - ) -> AnyElement { - Flex::row() - // New menu - .with_child(render_tab_bar_button( - 0, - "icons/plus_12.svg", - cx, - |pane, cx| pane.deploy_new_menu(cx), - self.tab_bar_context_menu - .handle_if_kind(TabBarContextMenuKind::New), - )) - .with_child(render_tab_bar_button( - 2, - "icons/split_12.svg", - cx, - |pane, cx| pane.deploy_split_menu(cx), - self.tab_bar_context_menu - .handle_if_kind(TabBarContextMenuKind::Split), - )) - .contained() - .with_style(theme.workspace.tab_bar.pane_button_container) + pub fn render_tab_bar_button)>( + index: usize, + icon: &'static str, + cx: &mut ViewContext, + on_click: F, + context_menu: Option>, + ) -> AnyElement { + enum TabBarButton {} + + Stack::new() + .with_child( + MouseEventHandler::::new(index, cx, |mouse_state, cx| { + let theme = &cx.global::().theme.workspace.tab_bar; + let style = theme.pane_button.style_for(mouse_state, false); + Svg::new(icon) + .with_color(style.color) + .constrained() + .with_width(style.icon_width) + .aligned() + .constrained() + .with_width(style.button_width) + .with_height(style.button_width) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx)), + ) + .with_children( + context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()), + ) .flex(1., false) - .into_any() + .into_any_named("tab bar button") } fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext) -> AnyElement { @@ -1554,7 +1588,14 @@ impl View for Pane { .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs")); if self.is_active { - tab_row.add_child(self.render_tab_bar_buttons(&theme, cx)) + let render_tab_bar_buttons = self.render_tab_bar_buttons.clone(); + tab_row.add_child( + (render_tab_bar_buttons)(self, cx) + .contained() + .with_style(theme.workspace.tab_bar.pane_button_container) + .flex(1., false) + .into_any(), + ) } stack.add_child(tab_row); @@ -1676,39 +1717,6 @@ impl View for Pane { } } -fn render_tab_bar_button)>( - index: usize, - icon: &'static str, - cx: &mut ViewContext, - on_click: F, - context_menu: Option>, -) -> AnyElement { - enum TabBarButton {} - - Stack::new() - .with_child( - MouseEventHandler::::new(index, cx, |mouse_state, cx| { - let theme = &cx.global::().theme.workspace.tab_bar; - let style = theme.pane_button.style_for(mouse_state, false); - Svg::new(icon) - .with_color(style.color) - .constrained() - .with_width(style.icon_width) - .aligned() - .constrained() - .with_width(style.button_width) - .with_height(style.button_width) - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx)), - ) - .with_children( - context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()), - ) - .flex(1., false) - .into_any_named("tab bar button") -} - impl ItemNavHistory { pub fn push(&self, data: Option, cx: &mut WindowContext) { self.history.borrow_mut().push(data, self.item.clone(), cx); From 641f5d1107b282127115d812368704b161748cca Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 17:38:18 +0200 Subject: [PATCH 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 90072ea6528cdf20ab56b74040ef6bde5b00f131..d963025f89bccd36efb29cff8f65da9e91b4a431 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -8,6 +8,10 @@ use settings::{Settings, WorkingDirectory}; use util::ResultExt; use workspace::{dock::Panel, pane, DraggedItem, Pane, Workspace}; +pub fn init(cx: &mut AppContext) { + cx.add_action(TerminalPanel::add_terminal); +} + pub enum Event { Close, } @@ -47,7 +51,9 @@ impl TerminalPanel { let this = this.clone(); cx.window_context().defer(move |cx| { if let Some(this) = this.upgrade(cx) { - this.update(cx, |this, cx| this.add_terminal(cx)); + this.update(cx, |this, cx| { + this.add_terminal(&Default::default(), cx); + }); } }) }, @@ -80,7 +86,7 @@ impl TerminalPanel { } } - fn add_terminal(&mut self, cx: &mut ViewContext) { + fn add_terminal(&mut self, _: &workspace::NewTerminal, cx: &mut ViewContext) { if let Some(workspace) = self.workspace.upgrade(cx) { let working_directory_strategy = cx .global::() @@ -123,7 +129,7 @@ impl View for TerminalPanel { fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { if self.pane.read(cx).items_len() == 0 { - self.add_terminal(cx) + self.add_terminal(&Default::default(), cx) } } } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index e3a5153ef309fa4505576e2d202046c59eb3ae91..b318b572b5635cbede7ccf6687958b1532f1dea5 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -64,6 +64,7 @@ actions!( impl_actions!(terminal, [SendText, SendKeystroke]); pub fn init(cx: &mut AppContext) { + terminal_panel::init(cx); cx.add_action(TerminalView::deploy); register_deserializable_item::(cx); From 37d3ed5f5f6358d44d0546e24d709b8d3e804787 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 17:38:54 +0200 Subject: [PATCH 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 0523f082ea5a8d56014a5fe315b2263a66c966f7..30e782c7501ee19d80a92931c3300fa4b9dc529c 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -903,7 +903,8 @@ impl Pane { // to activating the item to the left .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1)); - self.activate_item(index_to_activate, activate_pane, activate_pane, cx); + let should_activate = activate_pane || self.has_focus; + self.activate_item(index_to_activate, should_activate, should_activate, cx); } let item = self.items.remove(item_index); From 45df09245b867a37475f405c0a7ee9e9a2222ac5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 18:06:59 +0200 Subject: [PATCH 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 2b23ceb7f308bc6cc0cb83073757d161eeb5796f..492f61c86fbdd37131668d1f9b3220e106964ca8 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -17,9 +17,6 @@ pub trait Panel: View { fn label(&self, _: &AppContext) -> Option { None } - fn contains_focused_view(&self, _: &AppContext) -> bool { - false - } } pub trait PanelHandle { @@ -42,7 +39,7 @@ where } fn is_focused(&self, cx: &WindowContext) -> bool { - ViewHandle::is_focused(self, cx) || self.read(cx).contains_focused_view(cx) + ViewHandle::is_focused(self, cx) } fn as_any(&self) -> &AnyViewHandle { From 8f12489937ce86d997b1f6fc73783f7231995a79 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 18:57:25 +0200 Subject: [PATCH 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 cecf6c114f27d567fd30564f91f05c37716a14bb..f326d665d00615052fb021cd2e588f3ccbc5dab3 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -28,7 +28,7 @@ use std::{ }; use theme::ProjectPanelEntry; use unicase::UniCase; -use workspace::Workspace; +use workspace::{dock::DockPosition, Workspace}; const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX; @@ -1327,7 +1327,35 @@ impl Entity for ProjectPanel { type Event = Event; } -impl workspace::dock::Panel for ProjectPanel {} +impl workspace::dock::Panel for ProjectPanel { + fn position(&self, cx: &gpui::WindowContext) -> DockPosition { + todo!() + } + + fn position_is_valid(&self, position: DockPosition) -> bool { + matches!(position, DockPosition::Left | DockPosition::Right) + } + + fn icon_path(&self) -> &'static str { + "icons/folder_tree_16.svg" + } + + fn icon_tooltip(&self) -> String { + "Project Panel".into() + } + + fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + todo!() + } + + fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + false + } + + fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + false + } +} impl ClipboardEntry { fn is_cut(&self) -> bool { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index d963025f89bccd36efb29cff8f65da9e91b4a431..be3c3de9d038ccb2b0f31f364f8c018e6443efb1 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -139,7 +139,7 @@ impl Panel for TerminalPanel { matches!(event, Event::Close) } - fn label(&self, cx: &AppContext) -> Option { + fn icon_label(&self, cx: &AppContext) -> Option { let count = self.pane.read(cx).items_len(); if count == 0 { None diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 492f61c86fbdd37131668d1f9b3220e106964ca8..a378e00ec34399d40d66f73e7d74d59726f07671 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -8,20 +8,25 @@ use settings::Settings; use std::rc::Rc; pub trait Panel: View { - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - false - } - fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - false - } - fn label(&self, _: &AppContext) -> Option { + fn position(&self, cx: &WindowContext) -> DockPosition; + fn position_is_valid(&self, position: DockPosition) -> bool; + fn icon_path(&self) -> &'static str; + fn icon_tooltip(&self) -> String; + fn icon_label(&self, _: &AppContext) -> Option { None } + fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; + fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; + fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; } pub trait PanelHandle { fn id(&self) -> usize; - fn label(&self, cx: &WindowContext) -> Option; + fn position(&self, cx: &WindowContext) -> DockPosition; + fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; + fn icon_path(&self, cx: &WindowContext) -> &'static str; + fn icon_tooltip(&self, cx: &WindowContext) -> String; + fn icon_label(&self, cx: &WindowContext) -> Option; fn is_focused(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; } @@ -34,8 +39,24 @@ where self.id() } - fn label(&self, cx: &WindowContext) -> Option { - self.read(cx).label(cx) + fn position(&self, cx: &WindowContext) -> DockPosition { + self.read(cx).position(cx) + } + + fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool { + self.read(cx).position_is_valid(position) + } + + fn icon_path(&self, cx: &WindowContext) -> &'static str { + self.read(cx).icon_path() + } + + fn icon_tooltip(&self, cx: &WindowContext) -> String { + self.read(cx).icon_tooltip() + } + + fn icon_label(&self, cx: &WindowContext) -> Option { + self.read(cx).icon_label(cx) } fn is_focused(&self, cx: &WindowContext) -> bool { @@ -82,8 +103,6 @@ impl DockPosition { } struct Item { - icon_path: &'static str, - tooltip: String, view: Rc, _subscriptions: [Subscription; 2], } @@ -132,13 +151,7 @@ impl Dock { cx.notify(); } - pub fn add_item( - &mut self, - icon_path: &'static str, - tooltip: String, - view: ViewHandle, - cx: &mut ViewContext, - ) { + pub fn add_panel(&mut self, view: ViewHandle, cx: &mut ViewContext) { let subscriptions = [ cx.observe(&view, |_, _, cx| cx.notify()), cx.subscribe(&view, |this, view, event, cx| { @@ -157,8 +170,6 @@ impl Dock { ]; self.items.push(Item { - icon_path, - tooltip, view: Rc::new(view), _subscriptions: subscriptions, }); @@ -235,15 +246,15 @@ impl Entity for PanelButtons { impl View for PanelButtons { fn ui_name() -> &'static str { - "DockToggleButton" + "PanelButtons" } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { let theme = &cx.global::().theme; let tooltip_style = theme.tooltip.clone(); let theme = &theme.workspace.status_bar.panel_buttons; - let dock = self.dock.read(cx); let item_style = theme.button.clone(); + let dock = self.dock.read(cx); let active_ix = dock.active_item_ix; let is_open = dock.is_open; let dock_position = dock.position; @@ -253,69 +264,65 @@ impl View for PanelButtons { DockPosition::Right => theme.group_right, }; - #[allow(clippy::needless_collect)] let items = dock .items .iter() - .map(|item| (item.icon_path, item.tooltip.clone(), item.view.clone())) + .map(|item| item.view.clone()) .collect::>(); - Flex::row() - .with_children(items.into_iter().enumerate().map( - |(ix, (icon_path, tooltip, item_view))| { - let action = TogglePanel { - dock_position, - item_index: ix, - }; - MouseEventHandler::::new(ix, cx, |state, cx| { - let is_active = is_open && ix == active_ix; - let style = item_style.style_for(state, is_active); - Flex::row() - .with_child( - Svg::new(icon_path) - .with_color(style.icon_color) - .constrained() - .with_width(style.icon_size) + .with_children(items.into_iter().enumerate().map(|(ix, view)| { + let action = TogglePanel { + dock_position, + item_index: ix, + }; + MouseEventHandler::::new(ix, cx, |state, cx| { + let is_active = is_open && ix == active_ix; + let style = item_style.style_for(state, is_active); + Flex::row() + .with_child( + Svg::new(view.icon_path(cx)) + .with_color(style.icon_color) + .constrained() + .with_width(style.icon_size) + .aligned(), + ) + .with_children(if let Some(label) = view.icon_label(cx) { + Some( + Label::new(label, style.label.text.clone()) + .contained() + .with_style(style.label.container) .aligned(), ) - .with_children(if let Some(label) = item_view.label(cx) { - Some( - Label::new(label, style.label.text.clone()) - .contained() - .with_style(style.label.container) - .aligned(), - ) - } else { - None - }) - .constrained() - .with_height(style.icon_size) - .contained() - .with_style(style.container) - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, { - let action = action.clone(); - move |_, this, cx| { - if let Some(workspace) = this.workspace.upgrade(cx) { - let action = action.clone(); - cx.window_context().defer(move |cx| { - workspace.update(cx, |workspace, cx| { - workspace.toggle_panel(&action, cx) - }); + } else { + None + }) + .constrained() + .with_height(style.icon_size) + .contained() + .with_style(style.container) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, { + let action = action.clone(); + move |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + let action = action.clone(); + cx.window_context().defer(move |cx| { + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel(&action, cx) }); - } + }); } - }) - .with_tooltip::( - ix, - tooltip, - Some(Box::new(action)), - tooltip_style.clone(), - cx, - ) - }, - )) + } + }) + .with_tooltip::( + ix, + view.icon_tooltip(cx), + Some(Box::new(action)), + tooltip_style.clone(), + cx, + ) + })) .contained() .with_style(group_style) .into_any() diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 0ae9e4f0eac92c50b98d9247fe1ee30e6dc459c0..ddee068d4645f712547047b8a5124c0f51c7ec10 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -1060,5 +1060,33 @@ pub(crate) mod test { } } - impl Panel for TestItem {} + impl Panel for TestItem { + fn position(&self, cx: &gpui::WindowContext) -> crate::dock::DockPosition { + unimplemented!() + } + + fn position_is_valid(&self, position: crate::dock::DockPosition) -> bool { + unimplemented!() + } + + fn icon_path(&self) -> &'static str { + unimplemented!() + } + + fn icon_tooltip(&self) -> String { + unimplemented!() + } + + fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + unimplemented!() + } + + fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + unimplemented!() + } + + fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + unimplemented!() + } + } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 59a8e97f3d29a892657282114e007d699093b8a0..d86b25b7463e3e704ce573e7e187bec139959088 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -60,7 +60,7 @@ use crate::{ notifications::simple_message_notification::MessageNotification, persistence::model::{SerializedPane, SerializedPaneGroup, SerializedWorkspace}, }; -use dock::{Dock, DockPosition, PanelButtons, TogglePanel}; +use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle, TogglePanel}; use lazy_static::lazy_static; use notifications::{NotificationHandle, NotifyResultExt}; pub use pane::*; @@ -834,6 +834,15 @@ impl Workspace { &self.right_dock } + pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) { + let dock = match panel.position(cx) { + DockPosition::Left => &mut self.left_dock, + DockPosition::Bottom => &mut self.bottom_dock, + DockPosition::Right => &mut self.right_dock, + }; + dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); + } + pub fn status_bar(&self) -> &ViewHandle { &self.status_bar } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 662638301dce4bbb9b9cdae7fcb857b7e16842f4..394661da418cb97ad016bf16b0a5792503f68a42 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -311,19 +311,12 @@ pub fn initialize_workspace( cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); - let project_panel = ProjectPanel::new(workspace, cx); - workspace.left_dock().update(cx, |dock, cx| { - dock.add_item( - "icons/folder_tree_16.svg", - "Project Panel".to_string(), - project_panel, - cx, - ); - }); + let project_panel = cx.add_view(|cx| ProjectPanel::new(workspace, cx)); + workspace.add_panel(panel, cx); let terminal_panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); workspace.bottom_dock().update(cx, |dock, cx| { - dock.add_item( + dock.add_panel( "icons/terminal_12.svg", "Terminals".to_string(), terminal_panel, From 6645323f1bee4bfc09c6f1256f5e2b83d568fda1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 13:28:34 -0600 Subject: [PATCH 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 f326d665d00615052fb021cd2e588f3ccbc5dab3..a595ec62255c6564600d3c402823a2652deb424d 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1329,7 +1329,7 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { - todo!() + cx.global::().project_panel_overrides.dock.into() } fn position_is_valid(&self, position: DockPosition) -> bool { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index fde7f516a7e6ea4f86ca1c7f82155c20a3ddce6c..528812af878febfbd8f2e184c42a5d0b73a028ff 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -44,6 +44,8 @@ pub struct Settings { pub show_call_status_icon: bool, pub vim_mode: bool, pub autosave: Autosave, + pub project_panel_defaults: ProjectPanelSettings, + pub project_panel_overrides: ProjectPanelSettings, pub editor_defaults: EditorSettings, pub editor_overrides: EditorSettings, pub git: GitSettings, @@ -129,6 +131,14 @@ impl TelemetrySettings { } } +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all="lowercase")] +pub enum DockPosition { + Left, + Right, + Bottom, +} + #[derive(Clone, Debug, Default)] pub struct CopilotSettings { pub disabled_globs: Vec, @@ -156,6 +166,19 @@ pub enum GitGutter { pub struct GitGutterConfig {} +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct ProjectPanelSettings { + pub dock: DockPosition +} + +impl Default for ProjectPanelSettings { + fn default() -> Self { + Self { + dock: DockPosition::Left + } + } +} + #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct EditorSettings { pub tab_size: Option, @@ -237,7 +260,7 @@ impl Default for HourFormat { } } -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct TerminalSettings { pub shell: Option, pub working_directory: Option, @@ -250,8 +273,29 @@ pub struct TerminalSettings { pub alternate_scroll: Option, pub option_as_meta: Option, pub copy_on_select: Option, + pub dock: DockPosition, +} + +impl Default for TerminalSettings { + fn default() -> Self { + Self { + shell:Default::default(), + working_directory:Default::default(), + font_size:Default::default(), + font_family:Default::default(), + line_height:Default::default(), + font_features:Default::default(), + env:Default::default(), + blinking:Default::default(), + alternate_scroll:Default::default(), + option_as_meta:Default::default(), + copy_on_select:Default::default(), + dock: DockPosition::Bottom, + } + } } + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] #[serde(rename_all = "snake_case")] pub enum TerminalLineHeight { @@ -457,6 +501,8 @@ impl Settings { show_call_status_icon: defaults.show_call_status_icon.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), + project_panel_defaults: Default::default(), + project_panel_overrides: Default::default(), editor_defaults: EditorSettings { tab_size: required(defaults.editor.tab_size), hard_tabs: required(defaults.editor.hard_tabs), @@ -750,6 +796,8 @@ impl Settings { show_call_status_icon: true, vim_mode: false, autosave: Autosave::Off, + project_panel_defaults: Default::default(), + project_panel_overrides: Default::default(), editor_defaults: EditorSettings { tab_size: Some(4.try_into().unwrap()), hard_tabs: Some(false), diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index be3c3de9d038ccb2b0f31f364f8c018e6443efb1..e9cdb5a1ea58431ec649b40ac112fce52b328b32 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -6,7 +6,7 @@ use gpui::{ use project::Project; use settings::{Settings, WorkingDirectory}; use util::ResultExt; -use workspace::{dock::Panel, pane, DraggedItem, Pane, Workspace}; +use workspace::{dock::{Panel, DockPosition}, pane, DraggedItem, Pane, Workspace}; pub fn init(cx: &mut AppContext) { cx.add_action(TerminalPanel::add_terminal); @@ -135,8 +135,20 @@ impl View for TerminalPanel { } impl Panel for TerminalPanel { - fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { - matches!(event, Event::Close) + fn position(&self, cx: &gpui::WindowContext) -> DockPosition { + cx.global::().terminal_overrides.dock.into() + } + + fn position_is_valid(&self, _: DockPosition) -> bool { + true + } + + fn icon_path(&self) -> &'static str { + "icons/terminal_12.svg" + } + + fn icon_tooltip(&self) -> String { + "Terminals".to_string() } fn icon_label(&self, cx: &AppContext) -> Option { @@ -147,4 +159,16 @@ impl Panel for TerminalPanel { Some(count.to_string()) } } + + fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + todo!() + } + + fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + todo!() + } + + fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { + matches!(event, Event::Close) + } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index a378e00ec34399d40d66f73e7d74d59726f07671..100b8e8661e59338e30acb01a1009e9b73629eb7 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -92,6 +92,16 @@ pub enum DockPosition { Right, } +impl From for DockPosition { + fn from(value: settings::DockPosition) -> Self { + match value { + settings::DockPosition::Left => Self::Left, + settings::DockPosition::Bottom => Self::Bottom, + settings::DockPosition::Right => Self::Right, + } + } +} + impl DockPosition { fn to_resizable_side(self) -> Side { match self { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index ddee068d4645f712547047b8a5124c0f51c7ec10..a4e034d7412406f5a37543a02222ecaf2b9a994c 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -1061,11 +1061,11 @@ pub(crate) mod test { } impl Panel for TestItem { - fn position(&self, cx: &gpui::WindowContext) -> crate::dock::DockPosition { + fn position(&self, _cx: &gpui::WindowContext) -> crate::dock::DockPosition { unimplemented!() } - fn position_is_valid(&self, position: crate::dock::DockPosition) -> bool { + fn position_is_valid(&self, _position: crate::dock::DockPosition) -> bool { unimplemented!() } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 394661da418cb97ad016bf16b0a5792503f68a42..8a40a5d1d3a6e43313458d7cdc32c4b89130fe39 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -311,18 +311,11 @@ pub fn initialize_workspace( cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); - let project_panel = cx.add_view(|cx| ProjectPanel::new(workspace, cx)); - workspace.add_panel(panel, cx); + let project_panel = ProjectPanel::new(workspace, cx); + workspace.add_panel(project_panel, cx); let terminal_panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); - workspace.bottom_dock().update(cx, |dock, cx| { - dock.add_panel( - "icons/terminal_12.svg", - "Terminals".to_string(), - terminal_panel, - cx, - ); - }); + workspace.add_panel(terminal_panel, cx); let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx)); let diagnostic_summary = From 0d78266ddb15772d946922068d557dbfb7e7e5a4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 16:37:10 -0600 Subject: [PATCH 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 e971af943aba42c94b7050c1ef0fcd5baeca9bae..fc7c1cd9686ebbbf66d4915d5e4a8ed4de9429e4 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1222,27 +1222,27 @@ mod tests { } fn as_local(&self) -> Option<&dyn language::LocalFile> { - todo!() + unimplemented!() } fn mtime(&self) -> SystemTime { - todo!() + unimplemented!() } fn file_name<'a>(&'a self, _: &'a gpui::AppContext) -> &'a std::ffi::OsStr { - todo!() + unimplemented!() } fn is_deleted(&self) -> bool { - todo!() + unimplemented!() } fn as_any(&self) -> &dyn std::any::Any { - todo!() + unimplemented!() } fn to_proto(&self) -> rpc::proto::File { - todo!() + unimplemented!() } } } diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index ca73196c8bb04a1e3dba879c23e667ecba1b7e22..1cf8cc986f08afe5325b3fe8f756192969487e99 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -990,7 +990,7 @@ mod tests { _: &mut V, _: &mut ViewContext, ) { - todo!() + unimplemented!() } fn rect_for_text_range( @@ -1003,7 +1003,7 @@ mod tests { _: &V, _: &ViewContext, ) -> Option { - todo!() + unimplemented!() } fn debug(&self, _: RectF, _: &(), _: &(), _: &V, _: &ViewContext) -> serde_json::Value { From 6a7feb4c4c0fea7ef5ab9c01a5029f43930519ed Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 17:26:54 -0600 Subject: [PATCH 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 58fb28bc62577b6219fbfb59475b76b08a9d9b0e..5d85e751d482445c7bce29cda0ba08cb89e1fdfa 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -107,6 +107,9 @@ // Automatically update Zed "auto_update": true, // Git gutter behavior configuration. + "project_panel": { + "dock": "left" + }, "git": { // Control whether the git gutter is shown. May take 2 values: // 1. Show the gutter @@ -149,6 +152,8 @@ // } // } "shell": "system", + // Where to dock terminals panel. Can be 'left', 'right', 'bottom'. + "dock": "bottom", // What working directory to use when launching the terminal. // May take 4 values: // 1. Use the current file's project directory. Will Fallback to the diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index a595ec62255c6564600d3c402823a2652deb424d..8ee701c3cc56ae68267cb47bfa50d54ecb8a934c 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -135,12 +135,25 @@ pub enum Event { entry_id: ProjectEntryId, focus_opened_item: bool, }, + DockPositionChanged, } impl ProjectPanel { pub fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> ViewHandle { let project = workspace.project().clone(); let project_panel = cx.add_view(|cx: &mut ViewContext| { + // Update the dock position when the setting changes. + let mut old_dock_position = cx.global::().project_panel_overrides.dock; + dbg!(old_dock_position); + cx.observe_global::(move |_, cx| { + let new_dock_position = cx.global::().project_panel_overrides.dock; + dbg!(new_dock_position); + if new_dock_position != old_dock_position { + old_dock_position = new_dock_position; + cx.emit(Event::DockPositionChanged); + } + }).detach(); + cx.observe(&project, |this, _, cx| { this.update_visible_entries(None, cx); cx.notify(); @@ -242,7 +255,8 @@ impl ProjectPanel { } } } - } + }, + Event::DockPositionChanged => {}, } }) .detach(); @@ -1344,8 +1358,8 @@ impl workspace::dock::Panel for ProjectPanel { "Project Panel".into() } - fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - todo!() + fn should_change_position_on_event(event: &Self::Event) -> bool { + matches!(event, Event::DockPositionChanged) } fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 528812af878febfbd8f2e184c42a5d0b73a028ff..0eb9eb9e5db0b1cb4d799531465d3b117ffcec3a 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -131,7 +131,7 @@ impl TelemetrySettings { } } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq)] #[serde(rename_all="lowercase")] pub enum DockPosition { Left, @@ -407,6 +407,7 @@ pub struct SettingsFileContent { pub autosave: Option, #[serde(flatten)] pub editor: EditorSettings, + pub project_panel: ProjectPanelSettings, #[serde(default)] pub journal: JournalSettings, #[serde(default)] @@ -609,6 +610,7 @@ impl Settings { } } self.editor_overrides = data.editor; + self.project_panel_overrides = data.project_panel; self.git_overrides = data.git.unwrap_or_default(); self.journal_overrides = data.journal; self.terminal_defaults.font_size = data.terminal.font_size; diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index e9cdb5a1ea58431ec649b40ac112fce52b328b32..e2bf2c9e32d9c7239ddbf223709b09640e21e01a 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -160,7 +160,7 @@ impl Panel for TerminalPanel { } } - fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_change_position_on_event(_: &Self::Event) -> bool { todo!() } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 100b8e8661e59338e30acb01a1009e9b73629eb7..0e2d84e626738bc2c7064c17aec77c22ad79001e 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -15,7 +15,7 @@ pub trait Panel: View { fn icon_label(&self, _: &AppContext) -> Option { None } - fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; + fn should_change_position_on_event(_: &Self::Event) -> bool; fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; } @@ -80,7 +80,7 @@ pub enum Event { pub struct Dock { position: DockPosition, - items: Vec, + panels: Vec, is_open: bool, active_item_ix: usize, } @@ -112,8 +112,8 @@ impl DockPosition { } } -struct Item { - view: Rc, +struct PanelEntry { + panel: Rc, _subscriptions: [Subscription; 2], } @@ -134,7 +134,7 @@ impl Dock { pub fn new(position: DockPosition) -> Self { Self { position, - items: Default::default(), + panels: Default::default(), active_item_ix: 0, is_open: false, } @@ -161,15 +161,15 @@ impl Dock { cx.notify(); } - pub fn add_panel(&mut self, view: ViewHandle, cx: &mut ViewContext) { + pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) { let subscriptions = [ - cx.observe(&view, |_, _, cx| cx.notify()), - cx.subscribe(&view, |this, view, event, cx| { + cx.observe(&panel, |_, _, cx| cx.notify()), + cx.subscribe(&panel, |this, view, event, cx| { if view.read(cx).should_activate_on_event(event, cx) { if let Some(ix) = this - .items + .panels .iter() - .position(|item| item.view.id() == view.id()) + .position(|item| item.panel.id() == view.id()) { this.activate_item(ix, cx); } @@ -179,13 +179,18 @@ impl Dock { }), ]; - self.items.push(Item { - view: Rc::new(view), + self.panels.push(PanelEntry { + panel: Rc::new(panel), _subscriptions: subscriptions, }); cx.notify() } + pub fn remove_panel(&mut self, panel: &ViewHandle, cx: &mut ViewContext) { + self.panels.retain(|entry| entry.panel.id() != panel.id()); + cx.notify(); + } + pub fn activate_item(&mut self, item_ix: usize, cx: &mut ViewContext) { self.active_item_ix = item_ix; cx.notify(); @@ -202,7 +207,7 @@ impl Dock { pub fn active_item(&self) -> Option<&Rc> { if self.is_open { - self.items.get(self.active_item_ix).map(|item| &item.view) + self.panels.get(self.active_item_ix).map(|item| &item.panel) } else { None } @@ -275,9 +280,9 @@ impl View for PanelButtons { }; let items = dock - .items + .panels .iter() - .map(|item| item.view.clone()) + .map(|item| item.panel.clone()) .collect::>(); Flex::row() .with_children(items.into_iter().enumerate().map(|(ix, view)| { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index a4e034d7412406f5a37543a02222ecaf2b9a994c..d2b9251a6294349780368789c17232ad2ac2437d 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -1077,7 +1077,7 @@ pub(crate) mod test { unimplemented!() } - fn should_change_position_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_change_position_on_event(_: &Self::Event) -> bool { unimplemented!() } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index d86b25b7463e3e704ce573e7e187bec139959088..1708555f50ce0e9aa78fdf091ac06f300f31697a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -836,10 +836,26 @@ impl Workspace { pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) { let dock = match panel.position(cx) { - DockPosition::Left => &mut self.left_dock, - DockPosition::Bottom => &mut self.bottom_dock, - DockPosition::Right => &mut self.right_dock, + DockPosition::Left => &self.left_dock, + DockPosition::Bottom => &self.bottom_dock, + DockPosition::Right => &self.right_dock, }; + + cx.subscribe(&panel, { + let mut dock = dock.clone(); + move |this, panel, event, cx| { + if T::should_change_position_on_event(event) { + dock.update(cx, |dock, cx| dock.remove_panel(&panel, cx)); + dock = match panel.read(cx).position(cx) { + DockPosition::Left => &this.left_dock, + DockPosition::Bottom => &this.bottom_dock, + DockPosition::Right => &this.right_dock, + }.clone(); + dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); + } + } + }).detach(); + dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); } From ba248244ec94724af99a41c9ec791ac437cb78e4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 17:36:49 -0600 Subject: [PATCH 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 8ee701c3cc56ae68267cb47bfa50d54ecb8a934c..c22e297a020d40fc5669ffd6c3720b5b4b46d550 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -144,10 +144,8 @@ impl ProjectPanel { let project_panel = cx.add_view(|cx: &mut ViewContext| { // Update the dock position when the setting changes. let mut old_dock_position = cx.global::().project_panel_overrides.dock; - dbg!(old_dock_position); cx.observe_global::(move |_, cx| { let new_dock_position = cx.global::().project_panel_overrides.dock; - dbg!(new_dock_position); if new_dock_position != old_dock_position { old_dock_position = new_dock_position; cx.emit(Event::DockPositionChanged); diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index e2bf2c9e32d9c7239ddbf223709b09640e21e01a..58deda923684e055943d7c2e8256fa09b183c054 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -14,6 +14,7 @@ pub fn init(cx: &mut AppContext) { pub enum Event { Close, + DockPositionChanged, } pub struct TerminalPanel { @@ -25,6 +26,15 @@ pub struct TerminalPanel { impl TerminalPanel { pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + let mut old_dock_position = cx.global::().terminal_overrides.dock; + cx.observe_global::(move |_, cx| { + let new_dock_position = cx.global::().terminal_overrides.dock; + if new_dock_position != old_dock_position { + old_dock_position = new_dock_position; + cx.emit(Event::DockPositionChanged); + } + }).detach(); + let this = cx.weak_handle(); let pane = cx.add_view(|cx| { let window_id = cx.window_id(); @@ -160,12 +170,12 @@ impl Panel for TerminalPanel { } } - fn should_change_position_on_event(_: &Self::Event) -> bool { - todo!() + fn should_change_position_on_event(event: &Self::Event) -> bool { + matches!(event, Event::DockPositionChanged) } fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - todo!() + false } fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { From f554877ff47ccb11a5c9b4fb1971241ab3407f3a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 17:41:24 -0600 Subject: [PATCH 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 1708555f50ce0e9aa78fdf091ac06f300f31697a..bfc0d8228f1c80feef85e0180446b9a9e295c1ab 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -575,8 +575,8 @@ impl Workspace { let status_bar = cx.add_view(|cx| { let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); status_bar.add_left_item(left_dock_buttons, cx); - status_bar.add_right_item(bottom_dock_buttons, cx); status_bar.add_right_item(right_dock_buttons, cx); + status_bar.add_right_item(bottom_dock_buttons, cx); status_bar }); From 9173916ab800959e0a7a46e463e4f1c801027e9d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 May 2023 20:24:37 -0600 Subject: [PATCH 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 0747f86d2d0c99f1d150e507bb3c3e872ca5a761..0000000000000000000000000000000000000000 --- a/crates/terminal_view/src/terminal_button.rs +++ /dev/null @@ -1,173 +0,0 @@ -use crate::TerminalView; -use context_menu::{ContextMenu, ContextMenuItem}; -use gpui::{ - elements::*, - platform::{CursorStyle, MouseButton}, - AnyElement, Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle, -}; -use settings::Settings; -use std::any::TypeId; -use workspace::{ - item::ItemHandle, - NewTerminal, StatusItemView, Workspace, -}; - -pub struct TerminalButton { - workspace: WeakViewHandle, - popup_menu: ViewHandle, -} - -impl Entity for TerminalButton { - type Event = (); -} - -impl View for TerminalButton { - fn ui_name() -> &'static str { - "TerminalButton" - } - - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let workspace = self.workspace.upgrade(cx); - let project = match workspace { - Some(workspace) => workspace.read(cx).project().read(cx), - None => return Empty::new().into_any(), - }; - - let focused_view = cx.focused_view_id(); - let active = focused_view - .map(|view_id| { - cx.view_type_id(cx.window_id(), view_id) == Some(TypeId::of::()) - }) - .unwrap_or(false); - - let has_terminals = !project.local_terminal_handles().is_empty(); - let terminal_count = project.local_terminal_handles().len() as i32; - let theme = cx.global::().theme.clone(); - - Stack::new() - .with_child( - MouseEventHandler::::new(0, cx, { - let theme = theme.clone(); - move |state, _cx| { - let style = theme - .workspace - .status_bar - .panel_buttons - .button - .style_for(state, active); - - Flex::row() - .with_child( - Svg::new("icons/terminal_12.svg") - .with_color(style.icon_color) - .constrained() - .with_width(style.icon_size) - .aligned() - .into_any_named("terminals-icon"), - ) - .with_children(has_terminals.then(|| { - Label::new(terminal_count.to_string(), style.label.text.clone()) - .contained() - .with_style(style.label.container) - .aligned() - })) - .constrained() - .with_height(style.icon_size) - .contained() - .with_style(style.container) - } - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, this, cx| { - if has_terminals { - this.deploy_terminal_menu(cx); - } else { - if !active { - if let Some(workspace) = this.workspace.upgrade(cx) { - workspace.update(cx, |_workspace, _cx| { - todo!() - }) - } - } - }; - }) - .with_tooltip::( - 0, - "Show Terminal".into(), - None, // TODO! We need a new action here. - theme.tooltip.clone(), - cx, - ), - ) - .with_child(ChildView::new(&self.popup_menu, cx).aligned().top().right()) - .into_any_named("terminal button") - } -} - -impl TerminalButton { - pub fn new(workspace: ViewHandle, cx: &mut ViewContext) -> Self { - let button_view_id = cx.view_id(); - cx.observe(&workspace, |_, _, cx| cx.notify()).detach(); - Self { - workspace: workspace.downgrade(), - popup_menu: cx.add_view(|cx| { - let mut menu = ContextMenu::new(button_view_id, cx); - menu.set_position_mode(OverlayPositionMode::Local); - menu - }), - } - } - - pub fn deploy_terminal_menu(&mut self, cx: &mut ViewContext) { - let mut menu_options = vec![ContextMenuItem::action("New Terminal", NewTerminal)]; - - if let Some(workspace) = self.workspace.upgrade(cx) { - let project = workspace.read(cx).project().read(cx); - let local_terminal_handles = project.local_terminal_handles(); - - if !local_terminal_handles.is_empty() { - menu_options.push(ContextMenuItem::Separator) - } - - for local_terminal_handle in local_terminal_handles { - if let Some(terminal) = local_terminal_handle.upgrade(cx) { - let workspace = self.workspace.clone(); - let local_terminal_handle = local_terminal_handle.clone(); - menu_options.push(ContextMenuItem::handler( - terminal.read(cx).title(), - move |cx| { - if let Some(workspace) = workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - let terminal = workspace - .items_of_type::(cx) - .find(|terminal| { - terminal.read(cx).model().downgrade() - == local_terminal_handle - }); - if let Some(terminal) = terminal { - workspace.activate_item(&terminal, cx); - } - }); - } - }, - )) - } - } - } - - self.popup_menu.update(cx, |menu, cx| { - menu.show( - Default::default(), - AnchorCorner::BottomRight, - menu_options, - cx, - ); - }); - } -} - -impl StatusItemView for TerminalButton { - fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, cx: &mut ViewContext) { - cx.notify(); - } -} diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index b318b572b5635cbede7ccf6687958b1532f1dea5..2c33cc8f39d1c5e35da3b767148a0b963aa6c960 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1,5 +1,4 @@ mod persistence; -pub mod terminal_button; pub mod terminal_element; pub mod terminal_panel; From 6e3ce6332a2af20525dc928137237847dac212d7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 10:43:42 +0200 Subject: [PATCH 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 c22e297a020d40fc5669ffd6c3720b5b4b46d550..85694ec629eb943076f6c2381059457f4215c0ac 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -150,7 +150,8 @@ impl ProjectPanel { old_dock_position = new_dock_position; cx.emit(Event::DockPositionChanged); } - }).detach(); + }) + .detach(); cx.observe(&project, |this, _, cx| { this.update_visible_entries(None, cx); @@ -253,8 +254,8 @@ impl ProjectPanel { } } } - }, - Event::DockPositionChanged => {}, + } + Event::DockPositionChanged => {} } }) .detach(); @@ -1341,7 +1342,11 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { - cx.global::().project_panel_overrides.dock.into() + cx.global::() + .project_panel_overrides + .dock + .map(Into::into) + .unwrap_or(DockPosition::Left) } fn position_is_valid(&self, position: DockPosition) -> bool { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 0eb9eb9e5db0b1cb4d799531465d3b117ffcec3a..a49d84c6a874fb0dce6acc9167a7e95849ca32d9 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -132,7 +132,7 @@ impl TelemetrySettings { } #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq)] -#[serde(rename_all="lowercase")] +#[serde(rename_all = "lowercase")] pub enum DockPosition { Left, Right, @@ -166,17 +166,9 @@ pub enum GitGutter { pub struct GitGutterConfig {} -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct ProjectPanelSettings { - pub dock: DockPosition -} - -impl Default for ProjectPanelSettings { - fn default() -> Self { - Self { - dock: DockPosition::Left - } - } + pub dock: Option, } #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] @@ -279,23 +271,22 @@ pub struct TerminalSettings { impl Default for TerminalSettings { fn default() -> Self { Self { - shell:Default::default(), - working_directory:Default::default(), - font_size:Default::default(), - font_family:Default::default(), - line_height:Default::default(), - font_features:Default::default(), - env:Default::default(), - blinking:Default::default(), - alternate_scroll:Default::default(), - option_as_meta:Default::default(), - copy_on_select:Default::default(), + shell: Default::default(), + working_directory: Default::default(), + font_size: Default::default(), + font_family: Default::default(), + line_height: Default::default(), + font_features: Default::default(), + env: Default::default(), + blinking: Default::default(), + alternate_scroll: Default::default(), + option_as_meta: Default::default(), + copy_on_select: Default::default(), dock: DockPosition::Bottom, } } } - #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] #[serde(rename_all = "snake_case")] pub enum TerminalLineHeight { @@ -407,7 +398,7 @@ pub struct SettingsFileContent { pub autosave: Option, #[serde(flatten)] pub editor: EditorSettings, - pub project_panel: ProjectPanelSettings, + pub project_panel: Option, #[serde(default)] pub journal: JournalSettings, #[serde(default)] @@ -502,7 +493,7 @@ impl Settings { show_call_status_icon: defaults.show_call_status_icon.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), - project_panel_defaults: Default::default(), + project_panel_defaults: defaults.project_panel.unwrap(), project_panel_overrides: Default::default(), editor_defaults: EditorSettings { tab_size: required(defaults.editor.tab_size), @@ -610,7 +601,7 @@ impl Settings { } } self.editor_overrides = data.editor; - self.project_panel_overrides = data.project_panel; + self.project_panel_overrides = data.project_panel.unwrap_or_default(); self.git_overrides = data.git.unwrap_or_default(); self.journal_overrides = data.journal; self.terminal_defaults.font_size = data.terminal.font_size; From 8fa379bbc5c09c7e4e57b4aa0b59f0dfc35ca8d2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 11:06:37 +0200 Subject: [PATCH 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 0e2d84e626738bc2c7064c17aec77c22ad79001e..3d5c4b6c38abf529a44a8856cb22a5f96e63b256 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -187,8 +187,22 @@ impl Dock { } pub fn remove_panel(&mut self, panel: &ViewHandle, cx: &mut ViewContext) { - self.panels.retain(|entry| entry.panel.id() != panel.id()); - cx.notify(); + if let Some(panel_ix) = self + .panels + .iter() + .position(|item| item.panel.id() == panel.id()) + { + if panel_ix == self.active_item_ix { + self.active_item_ix = 0; + cx.emit(Event::Close); + } + self.panels.remove(panel_ix); + cx.notify(); + } + } + + pub fn panels_len(&self) -> usize { + self.panels.len() } pub fn activate_item(&mut self, item_ix: usize, cx: &mut ViewContext) { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index bfc0d8228f1c80feef85e0180446b9a9e295c1ab..d147557103e94550958dd7e55ea066dd47e5cd68 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -845,16 +845,31 @@ impl Workspace { let mut dock = dock.clone(); move |this, panel, event, cx| { if T::should_change_position_on_event(event) { - dock.update(cx, |dock, cx| dock.remove_panel(&panel, cx)); + let mut was_visible = false; + dock.update(cx, |dock, cx| { + was_visible = dock.is_open() + && dock + .active_item() + .map_or(false, |item| item.as_any().is::()); + dock.remove_panel(&panel, cx); + }); dock = match panel.read(cx).position(cx) { DockPosition::Left => &this.left_dock, DockPosition::Bottom => &this.bottom_dock, DockPosition::Right => &this.right_dock, - }.clone(); - dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); + } + .clone(); + dock.update(cx, |dock, cx| { + dock.add_panel(panel, cx); + if was_visible { + dock.set_open(true, cx); + dock.activate_item(dock.panels_len() - 1, cx); + } + }); } } - }).detach(); + }) + .detach(); dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); } From f28eee88b64a69ed436763c856105d29dc7705c0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 14:05:48 +0200 Subject: [PATCH 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 85694ec629eb943076f6c2381059457f4215c0ac..4c75035c839cdd776febb33691373d6ede568ea4 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -17,7 +17,7 @@ use gpui::{ }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; -use settings::Settings; +use settings::{settings_file::SettingsFile, Settings}; use std::{ cmp::Ordering, collections::{hash_map, HashMap}, @@ -28,7 +28,10 @@ use std::{ }; use theme::ProjectPanelEntry; use unicase::UniCase; -use workspace::{dock::DockPosition, Workspace}; +use workspace::{ + dock::{DockPosition, Panel}, + Workspace, +}; const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX; @@ -142,17 +145,6 @@ impl ProjectPanel { pub fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> ViewHandle { let project = workspace.project().clone(); let project_panel = cx.add_view(|cx: &mut ViewContext| { - // Update the dock position when the setting changes. - let mut old_dock_position = cx.global::().project_panel_overrides.dock; - cx.observe_global::(move |_, cx| { - let new_dock_position = cx.global::().project_panel_overrides.dock; - if new_dock_position != old_dock_position { - old_dock_position = new_dock_position; - cx.emit(Event::DockPositionChanged); - } - }) - .detach(); - cx.observe(&project, |this, _, cx| { this.update_visible_entries(None, cx); cx.notify(); @@ -224,6 +216,18 @@ impl ProjectPanel { workspace: workspace.weak_handle(), }; this.update_visible_entries(None, cx); + + // Update the dock position when the setting changes. + let mut old_dock_position = this.position(cx); + cx.observe_global::(move |this, cx| { + let new_dock_position = this.position(cx); + if new_dock_position != old_dock_position { + old_dock_position = new_dock_position; + cx.emit(Event::DockPositionChanged); + } + }) + .detach(); + this }); @@ -1342,17 +1346,25 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { - cx.global::() + let settings = cx.global::(); + settings .project_panel_overrides .dock - .map(Into::into) - .unwrap_or(DockPosition::Left) + .or(settings.project_panel_defaults.dock) + .unwrap() + .into() } fn position_is_valid(&self, position: DockPosition) -> bool { matches!(position, DockPosition::Left | DockPosition::Right) } + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { + SettingsFile::update(cx, move |settings| { + settings.project_panel.dock = Some(position.into()) + }) + } + fn icon_path(&self) -> &'static str { "icons/folder_tree_16.svg" } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index a49d84c6a874fb0dce6acc9167a7e95849ca32d9..3bd37860715c6c00d1a88954d99efb75065f6712 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -252,7 +252,7 @@ impl Default for HourFormat { } } -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct TerminalSettings { pub shell: Option, pub working_directory: Option, @@ -265,26 +265,7 @@ pub struct TerminalSettings { pub alternate_scroll: Option, pub option_as_meta: Option, pub copy_on_select: Option, - pub dock: DockPosition, -} - -impl Default for TerminalSettings { - fn default() -> Self { - Self { - shell: Default::default(), - working_directory: Default::default(), - font_size: Default::default(), - font_family: Default::default(), - line_height: Default::default(), - font_features: Default::default(), - env: Default::default(), - blinking: Default::default(), - alternate_scroll: Default::default(), - option_as_meta: Default::default(), - copy_on_select: Default::default(), - dock: DockPosition::Bottom, - } - } + pub dock: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] @@ -398,7 +379,8 @@ pub struct SettingsFileContent { pub autosave: Option, #[serde(flatten)] pub editor: EditorSettings, - pub project_panel: Option, + #[serde(default)] + pub project_panel: ProjectPanelSettings, #[serde(default)] pub journal: JournalSettings, #[serde(default)] @@ -493,7 +475,7 @@ impl Settings { show_call_status_icon: defaults.show_call_status_icon.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), - project_panel_defaults: defaults.project_panel.unwrap(), + project_panel_defaults: defaults.project_panel, project_panel_overrides: Default::default(), editor_defaults: EditorSettings { tab_size: required(defaults.editor.tab_size), @@ -601,7 +583,7 @@ impl Settings { } } self.editor_overrides = data.editor; - self.project_panel_overrides = data.project_panel.unwrap_or_default(); + self.project_panel_overrides = data.project_panel; self.git_overrides = data.git.unwrap_or_default(); self.journal_overrides = data.journal; self.terminal_defaults.font_size = data.terminal.font_size; diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 58deda923684e055943d7c2e8256fa09b183c054..4e90a8fec45ee757e0b321a3cb60724e6e126c95 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -4,9 +4,12 @@ use gpui::{ WeakViewHandle, }; use project::Project; -use settings::{Settings, WorkingDirectory}; +use settings::{settings_file::SettingsFile, Settings, WorkingDirectory}; use util::ResultExt; -use workspace::{dock::{Panel, DockPosition}, pane, DraggedItem, Pane, Workspace}; +use workspace::{ + dock::{DockPosition, Panel}, + pane, DraggedItem, Pane, Workspace, +}; pub fn init(cx: &mut AppContext) { cx.add_action(TerminalPanel::add_terminal); @@ -33,7 +36,8 @@ impl TerminalPanel { old_dock_position = new_dock_position; cx.emit(Event::DockPositionChanged); } - }).detach(); + }) + .detach(); let this = cx.weak_handle(); let pane = cx.add_view(|cx| { @@ -146,13 +150,25 @@ impl View for TerminalPanel { impl Panel for TerminalPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { - cx.global::().terminal_overrides.dock.into() + let settings = cx.global::(); + settings + .terminal_overrides + .dock + .or(settings.terminal_defaults.dock) + .unwrap() + .into() } fn position_is_valid(&self, _: DockPosition) -> bool { true } + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { + SettingsFile::update(cx, move |settings| { + settings.terminal.dock = Some(position.into()); + }); + } + fn icon_path(&self) -> &'static str { "icons/terminal_12.svg" } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 3d5c4b6c38abf529a44a8856cb22a5f96e63b256..6f7f6b1c12a9c6c31ab9c265fd0cf8053059fa86 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,4 +1,5 @@ use crate::{StatusItemView, Workspace}; +use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, @@ -10,6 +11,7 @@ use std::rc::Rc; pub trait Panel: View { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; fn icon_label(&self, _: &AppContext) -> Option { @@ -24,6 +26,7 @@ pub trait PanelHandle { fn id(&self) -> usize; fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; + fn set_position(&self, position: DockPosition, cx: &mut WindowContext); fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -47,6 +50,10 @@ where self.read(cx).position_is_valid(position) } + fn set_position(&self, position: DockPosition, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.set_position(position, cx)) + } + fn icon_path(&self, cx: &WindowContext) -> &'static str { self.read(cx).icon_path() } @@ -102,7 +109,25 @@ impl From for DockPosition { } } +impl From for settings::DockPosition { + fn from(value: DockPosition) -> settings::DockPosition { + match value { + DockPosition::Left => settings::DockPosition::Left, + DockPosition::Bottom => settings::DockPosition::Bottom, + DockPosition::Right => settings::DockPosition::Right, + } + } +} + impl DockPosition { + fn to_label(&self) -> &'static str { + match self { + Self::Left => "left", + Self::Bottom => "bottom", + Self::Right => "right", + } + } + fn to_resizable_side(self) -> Side { match self { Self::Left => Side::Right, @@ -114,6 +139,7 @@ impl DockPosition { struct PanelEntry { panel: Rc, + context_menu: ViewHandle, _subscriptions: [Subscription; 2], } @@ -179,8 +205,14 @@ impl Dock { }), ]; + let dock_view_id = cx.view_id(); self.panels.push(PanelEntry { panel: Rc::new(panel), + context_menu: cx.add_view(|cx| { + let mut menu = ContextMenu::new(dock_view_id, cx); + menu.set_position_mode(OverlayPositionMode::Local); + menu + }), _subscriptions: subscriptions, }); cx.notify() @@ -292,66 +324,109 @@ impl View for PanelButtons { DockPosition::Bottom => theme.group_bottom, DockPosition::Right => theme.group_right, }; + let menu_corner = match dock_position { + DockPosition::Left => AnchorCorner::BottomLeft, + DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight, + }; let items = dock .panels .iter() - .map(|item| item.panel.clone()) + .map(|item| (item.panel.clone(), item.context_menu.clone())) .collect::>(); Flex::row() - .with_children(items.into_iter().enumerate().map(|(ix, view)| { - let action = TogglePanel { - dock_position, - item_index: ix, - }; - MouseEventHandler::::new(ix, cx, |state, cx| { - let is_active = is_open && ix == active_ix; - let style = item_style.style_for(state, is_active); - Flex::row() - .with_child( - Svg::new(view.icon_path(cx)) - .with_color(style.icon_color) - .constrained() - .with_width(style.icon_size) - .aligned(), - ) - .with_children(if let Some(label) = view.icon_label(cx) { - Some( - Label::new(label, style.label.text.clone()) - .contained() - .with_style(style.label.container) - .aligned(), + .with_children( + items + .into_iter() + .enumerate() + .map(|(ix, (view, context_menu))| { + let action = TogglePanel { + dock_position, + item_index: ix, + }; + + Stack::new() + .with_child( + MouseEventHandler::::new(ix, cx, |state, cx| { + let is_active = is_open && ix == active_ix; + let style = item_style.style_for(state, is_active); + Flex::row() + .with_child( + Svg::new(view.icon_path(cx)) + .with_color(style.icon_color) + .constrained() + .with_width(style.icon_size) + .aligned(), + ) + .with_children(if let Some(label) = view.icon_label(cx) { + Some( + Label::new(label, style.label.text.clone()) + .contained() + .with_style(style.label.container) + .aligned(), + ) + } else { + None + }) + .constrained() + .with_height(style.icon_size) + .contained() + .with_style(style.container) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, { + let action = action.clone(); + move |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + let action = action.clone(); + cx.window_context().defer(move |cx| { + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel(&action, cx) + }); + }); + } + } + }) + .on_click(MouseButton::Right, { + let view = view.clone(); + let menu = context_menu.clone(); + move |_, _, cx| { + const POSITIONS: [DockPosition; 3] = [ + DockPosition::Left, + DockPosition::Right, + DockPosition::Bottom, + ]; + + menu.update(cx, |menu, cx| { + let items = POSITIONS + .into_iter() + .filter(|position| { + *position != dock_position + && view.position_is_valid(*position, cx) + }) + .map(|position| { + let view = view.clone(); + ContextMenuItem::handler( + format!("Dock {}", position.to_label()), + move |cx| view.set_position(position, cx), + ) + }) + .collect(); + menu.show(Default::default(), menu_corner, items, cx); + }) + } + }) + .with_tooltip::( + ix, + view.icon_tooltip(cx), + Some(Box::new(action)), + tooltip_style.clone(), + cx, + ), ) - } else { - None - }) - .constrained() - .with_height(style.icon_size) - .contained() - .with_style(style.container) - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, { - let action = action.clone(); - move |_, this, cx| { - if let Some(workspace) = this.workspace.upgrade(cx) { - let action = action.clone(); - cx.window_context().defer(move |cx| { - workspace.update(cx, |workspace, cx| { - workspace.toggle_panel(&action, cx) - }); - }); - } - } - }) - .with_tooltip::( - ix, - view.icon_tooltip(cx), - Some(Box::new(action)), - tooltip_style.clone(), - cx, - ) - })) + .with_child(ChildView::new(&context_menu, cx)) + }), + ) .contained() .with_style(group_style) .into_any() diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index d2b9251a6294349780368789c17232ad2ac2437d..dfad53c3a370dc99289666db54c017b8393bed86 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -1069,6 +1069,14 @@ pub(crate) mod test { unimplemented!() } + fn set_position( + &mut self, + _position: crate::dock::DockPosition, + _cx: &mut ViewContext, + ) { + unimplemented!() + } + fn icon_path(&self) -> &'static str { unimplemented!() } From 9b9d53fcf8247fea9fca3323c301dd5667a2bd20 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 15:40:46 +0200 Subject: [PATCH 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 d5a7a2f3ab158fc3b91ece1a558231627026399d..488f55d0c6e91f1d0b19b75c7acf4f1f79948331 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1451,27 +1451,13 @@ impl AppContext { self.views_metadata.remove(&(window_id, view_id)); let mut view = self.views.remove(&(window_id, view_id)).unwrap(); view.release(self); - let change_focus_to = self.windows.get_mut(&window_id).and_then(|window| { + if let Some(window) = self.windows.get_mut(&window_id) { window.parents.remove(&view_id); window .invalidation .get_or_insert_with(Default::default) .removed .push(view_id); - if window.focused_view_id == Some(view_id) { - Some(window.root_view().id()) - } else { - None - } - }); - - if let Some(view_id) = change_focus_to { - self.pending_effects - .push_back(Effect::Focus(FocusEffect::View { - window_id, - view_id: Some(view_id), - is_forced: false, - })); } self.pending_effects @@ -1710,6 +1696,25 @@ impl AppContext { cx.invalidate(invalidation, appearance); if cx.layout(refreshing).log_err().is_some() { updated_windows.insert(window_id); + + // When the previously-focused view isn't rendered and + // there isn't any pending focus, focus the root view. + if let Some(focused_view_id) = cx.focused_view_id() { + let root_view_id = cx.window.root_view().id(); + if focused_view_id != root_view_id + && !cx.window.parents.contains_key(&focused_view_id) + && !focus_effects.contains_key(&window_id) + { + focus_effects.insert( + window_id, + FocusEffect::View { + window_id, + view_id: Some(root_view_id), + is_forced: false, + }, + ); + } + } } } }); @@ -1886,9 +1891,27 @@ impl AppContext { fn handle_focus_effect(&mut self, effect: FocusEffect) { let window_id = effect.window_id(); self.update_window(window_id, |cx| { + // Ensure the newly-focused view has been rendered, otherwise focus + // the root view instead. let focused_id = match effect { - FocusEffect::View { view_id, .. } => view_id, - FocusEffect::ViewParent { view_id, .. } => cx.ancestors(view_id).skip(1).next(), + FocusEffect::View { view_id, .. } => { + if let Some(view_id) = view_id { + if cx.window.parents.contains_key(&view_id) { + Some(view_id) + } else { + Some(cx.root_view().id()) + } + } else { + None + } + } + FocusEffect::ViewParent { view_id, .. } => Some( + cx.window + .parents + .get(&view_id) + .copied() + .unwrap_or(cx.root_view().id()), + ), }; let focus_changed = cx.window.focused_view_id != focused_id; From e507eadb4b08d284d04a906ddd80517193104ceb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 15:43:44 +0200 Subject: [PATCH 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 6f7f6b1c12a9c6c31ab9c265fd0cf8053059fa86..0948140e8e5b2bf03c526f2714cb6d67a2d3bca1 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -81,10 +81,6 @@ impl From<&dyn PanelHandle> for AnyViewHandle { } } -pub enum Event { - Close, -} - pub struct Dock { position: DockPosition, panels: Vec, @@ -182,7 +178,6 @@ impl Dock { } pub fn toggle_open(&mut self, cx: &mut ViewContext) { - if self.is_open {} self.is_open = !self.is_open; cx.notify(); } @@ -200,7 +195,7 @@ impl Dock { this.activate_item(ix, cx); } } else if view.read(cx).should_close_on_event(event, cx) { - cx.emit(Event::Close); + this.set_open(false, cx); } }), ]; @@ -226,7 +221,7 @@ impl Dock { { if panel_ix == self.active_item_ix { self.active_item_ix = 0; - cx.emit(Event::Close); + self.set_open(false, cx); } self.panels.remove(panel_ix); cx.notify(); @@ -261,7 +256,7 @@ impl Dock { } impl Entity for Dock { - type Event = Event; + type Event = (); } impl View for Dock { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index d147557103e94550958dd7e55ea066dd47e5cd68..497b28cff10425da6c6a0bf6324b5594a9e9d479 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -612,9 +612,18 @@ impl Workspace { .spawn(DB.set_window_bounds(workspace_id, bounds, display)) .detach_and_log_err(cx); }), - Self::register_dock(&left_dock, cx), - Self::register_dock(&bottom_dock, cx), - Self::register_dock(&right_dock, cx), + cx.observe(&left_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), + cx.observe(&bottom_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), + cx.observe(&right_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), ]; let mut this = Workspace { @@ -1359,26 +1368,6 @@ impl Workspace { } } - fn register_dock(dock: &ViewHandle, cx: &mut ViewContext) -> Subscription { - cx.subscribe(dock, Self::handle_dock_event) - } - - fn handle_dock_event( - &mut self, - dock: ViewHandle, - event: &dock::Event, - cx: &mut ViewContext, - ) { - match event { - dock::Event::Close => { - dock.update(cx, |dock, cx| dock.set_open(false, cx)); - self.serialize_workspace(cx); - cx.focus_self(); - cx.notify(); - } - } - } - pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { let dock = match dock_side { DockPosition::Left => &mut self.left_dock, From 0ccb4a50e6b952ccf2b25e506521fc6c028889f2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 May 2023 16:52:10 +0200 Subject: [PATCH 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 4c75035c839cdd776febb33691373d6ede568ea4..14779f9b6fa0f58d437e75c865584c5e7ef78ef7 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1347,12 +1347,15 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { let settings = cx.global::(); - settings + let dock = settings .project_panel_overrides .dock .or(settings.project_panel_defaults.dock) - .unwrap() - .into() + .unwrap(); + match dock { + settings::ProjectPanelDockPosition::Left => DockPosition::Left, + settings::ProjectPanelDockPosition::Right => DockPosition::Right, + } } fn position_is_valid(&self, position: DockPosition) -> bool { @@ -1361,7 +1364,13 @@ impl workspace::dock::Panel for ProjectPanel { fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { SettingsFile::update(cx, move |settings| { - settings.project_panel.dock = Some(position.into()) + let dock = match position { + DockPosition::Left | DockPosition::Bottom => { + settings::ProjectPanelDockPosition::Left + } + DockPosition::Right => settings::ProjectPanelDockPosition::Right, + }; + settings.project_panel.dock = Some(dock); }) } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 3bd37860715c6c00d1a88954d99efb75065f6712..67acd12939a3a429c14e0064bdc3c2495108e30c 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -131,14 +131,6 @@ impl TelemetrySettings { } } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum DockPosition { - Left, - Right, - Bottom, -} - #[derive(Clone, Debug, Default)] pub struct CopilotSettings { pub disabled_globs: Vec, @@ -168,7 +160,14 @@ pub struct GitGutterConfig {} #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct ProjectPanelSettings { - pub dock: Option, + pub dock: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum ProjectPanelDockPosition { + Left, + Right, } #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] @@ -265,7 +264,15 @@ pub struct TerminalSettings { pub alternate_scroll: Option, pub option_as_meta: Option, pub copy_on_select: Option, - pub dock: Option, + pub dock: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum TerminalDockPosition { + Left, + Bottom, + Right, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 4e90a8fec45ee757e0b321a3cb60724e6e126c95..5d4099c446fc6ee1d696126afec5b65a13c670c0 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -4,7 +4,7 @@ use gpui::{ WeakViewHandle, }; use project::Project; -use settings::{settings_file::SettingsFile, Settings, WorkingDirectory}; +use settings::{settings_file::SettingsFile, Settings, TerminalDockPosition, WorkingDirectory}; use util::ResultExt; use workspace::{ dock::{DockPosition, Panel}, @@ -151,12 +151,17 @@ impl View for TerminalPanel { impl Panel for TerminalPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { let settings = cx.global::(); - settings + let dock = settings .terminal_overrides .dock .or(settings.terminal_defaults.dock) .unwrap() - .into() + .into(); + match dock { + settings::TerminalDockPosition::Left => DockPosition::Left, + settings::TerminalDockPosition::Bottom => DockPosition::Bottom, + settings::TerminalDockPosition::Right => DockPosition::Right, + } } fn position_is_valid(&self, _: DockPosition) -> bool { @@ -165,7 +170,12 @@ impl Panel for TerminalPanel { fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { SettingsFile::update(cx, move |settings| { - settings.terminal.dock = Some(position.into()); + let dock = match position { + DockPosition::Left => TerminalDockPosition::Left, + DockPosition::Bottom => TerminalDockPosition::Bottom, + DockPosition::Right => TerminalDockPosition::Right, + }; + settings.terminal.dock = Some(dock); }); } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 0948140e8e5b2bf03c526f2714cb6d67a2d3bca1..74132098744e5a7f32389ee5cfbbab7208485a1d 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -95,26 +95,6 @@ pub enum DockPosition { Right, } -impl From for DockPosition { - fn from(value: settings::DockPosition) -> Self { - match value { - settings::DockPosition::Left => Self::Left, - settings::DockPosition::Bottom => Self::Bottom, - settings::DockPosition::Right => Self::Right, - } - } -} - -impl From for settings::DockPosition { - fn from(value: DockPosition) -> settings::DockPosition { - match value { - DockPosition::Left => settings::DockPosition::Left, - DockPosition::Bottom => settings::DockPosition::Bottom, - DockPosition::Right => settings::DockPosition::Right, - } - } -} - impl DockPosition { fn to_label(&self) -> &'static str { match self { From cc21421ea86914e036ffef810dfa62bfc566c940 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 10 May 2023 09:54:20 -0600 Subject: [PATCH 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 74132098744e5a7f32389ee5cfbbab7208485a1d..82d8d404259bf73d88e7d0f9fd6a0fbd59449bc4 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -83,9 +83,9 @@ impl From<&dyn PanelHandle> for AnyViewHandle { pub struct Dock { position: DockPosition, - panels: Vec, + panel_entries: Vec, is_open: bool, - active_item_ix: usize, + active_panel_index: usize, } #[derive(Clone, Copy, Debug, Deserialize, PartialEq)] @@ -127,7 +127,7 @@ pub struct PanelButtons { #[derive(Clone, Debug, Deserialize, PartialEq)] pub struct TogglePanel { pub dock_position: DockPosition, - pub item_index: usize, + pub panel_index: usize, } impl_actions!(workspace, [TogglePanel]); @@ -136,8 +136,8 @@ impl Dock { pub fn new(position: DockPosition) -> Self { Self { position, - panels: Default::default(), - active_item_ix: 0, + panel_entries: Default::default(), + active_panel_index: 0, is_open: false, } } @@ -146,8 +146,8 @@ impl Dock { self.is_open } - pub fn active_item_ix(&self) -> usize { - self.active_item_ix + pub fn active_panel_index(&self) -> usize { + self.active_panel_index } pub fn set_open(&mut self, open: bool, cx: &mut ViewContext) { @@ -168,11 +168,11 @@ impl Dock { cx.subscribe(&panel, |this, view, event, cx| { if view.read(cx).should_activate_on_event(event, cx) { if let Some(ix) = this - .panels + .panel_entries .iter() - .position(|item| item.panel.id() == view.id()) + .position(|entry| entry.panel.id() == view.id()) { - this.activate_item(ix, cx); + this.activate_panel(ix, cx); } } else if view.read(cx).should_close_on_event(event, cx) { this.set_open(false, cx); @@ -181,7 +181,7 @@ impl Dock { ]; let dock_view_id = cx.view_id(); - self.panels.push(PanelEntry { + self.panel_entries.push(PanelEntry { panel: Rc::new(panel), context_menu: cx.add_view(|cx| { let mut menu = ContextMenu::new(dock_view_id, cx); @@ -195,40 +195,40 @@ impl Dock { pub fn remove_panel(&mut self, panel: &ViewHandle, cx: &mut ViewContext) { if let Some(panel_ix) = self - .panels + .panel_entries .iter() - .position(|item| item.panel.id() == panel.id()) + .position(|entry| entry.panel.id() == panel.id()) { - if panel_ix == self.active_item_ix { - self.active_item_ix = 0; + if panel_ix == self.active_panel_index { + self.active_panel_index = 0; self.set_open(false, cx); } - self.panels.remove(panel_ix); + self.panel_entries.remove(panel_ix); cx.notify(); } } pub fn panels_len(&self) -> usize { - self.panels.len() + self.panel_entries.len() } - pub fn activate_item(&mut self, item_ix: usize, cx: &mut ViewContext) { - self.active_item_ix = item_ix; + pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { + self.active_panel_index = panel_ix; cx.notify(); } - pub fn toggle_item(&mut self, item_ix: usize, cx: &mut ViewContext) { - if self.active_item_ix == item_ix { + pub fn toggle_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { + if self.active_panel_index == panel_ix { self.is_open = false; } else { - self.active_item_ix = item_ix; + self.active_panel_index = panel_ix; } cx.notify(); } - pub fn active_item(&self) -> Option<&Rc> { + pub fn active_panel(&self) -> Option<&Rc> { if self.is_open { - self.panels.get(self.active_item_ix).map(|item| &item.panel) + self.panel_entries.get(self.active_panel_index).map(|entry| &entry.panel) } else { None } @@ -245,10 +245,10 @@ impl View for Dock { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - if let Some(active_item) = self.active_item() { + if let Some(active_panel) = self.active_panel() { enum ResizeHandleTag {} let style = &cx.global::().theme.workspace.dock; - ChildView::new(active_item.as_any(), cx) + ChildView::new(active_panel.as_any(), cx) .contained() .with_style(style.container) .with_resize_handle::( @@ -289,9 +289,9 @@ impl View for PanelButtons { let theme = &cx.global::().theme; let tooltip_style = theme.tooltip.clone(); let theme = &theme.workspace.status_bar.panel_buttons; - let item_style = theme.button.clone(); + let button_style = theme.button.clone(); let dock = self.dock.read(cx); - let active_ix = dock.active_item_ix; + let active_ix = dock.active_panel_index; let is_open = dock.is_open; let dock_position = dock.position; let group_style = match dock_position { @@ -304,27 +304,27 @@ impl View for PanelButtons { DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight, }; - let items = dock - .panels + let panels = dock + .panel_entries .iter() .map(|item| (item.panel.clone(), item.context_menu.clone())) .collect::>(); Flex::row() .with_children( - items + panels .into_iter() .enumerate() .map(|(ix, (view, context_menu))| { let action = TogglePanel { dock_position, - item_index: ix, + panel_index: ix, }; Stack::new() .with_child( MouseEventHandler::::new(ix, cx, |state, cx| { let is_active = is_open && ix == active_ix; - let style = item_style.style_for(state, is_active); + let style = button_style.style_for(state, is_active); Flex::row() .with_child( Svg::new(view.icon_path(cx)) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 497b28cff10425da6c6a0bf6324b5594a9e9d479..cf89ed43c75c3e648f86ded942d4d803e4ab521f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -858,7 +858,7 @@ impl Workspace { dock.update(cx, |dock, cx| { was_visible = dock.is_open() && dock - .active_item() + .active_panel() .map_or(false, |item| item.as_any().is::()); dock.remove_panel(&panel, cx); }); @@ -872,7 +872,7 @@ impl Workspace { dock.add_panel(panel, cx); if was_visible { dock.set_open(true, cx); - dock.activate_item(dock.panels_len() - 1, cx); + dock.activate_panel(dock.panels_len() - 1, cx); } }); } @@ -1392,13 +1392,13 @@ impl Workspace { DockPosition::Right => &mut self.right_dock, }; let active_item = dock.update(cx, move |dock, cx| { - if dock.is_open() && dock.active_item_ix() == action.item_index { + if dock.is_open() && dock.active_panel_index() == action.panel_index { dock.set_open(false, cx); None } else { dock.set_open(true, cx); - dock.activate_item(action.item_index, cx); - dock.active_item().cloned() + dock.activate_panel(action.panel_index, cx); + dock.active_panel().cloned() } }); @@ -1430,8 +1430,8 @@ impl Workspace { }; let active_item = dock.update(cx, |dock, cx| { dock.set_open(true, cx); - dock.activate_item(panel_index, cx); - dock.active_item().cloned() + dock.activate_panel(panel_index, cx); + dock.active_panel().cloned() }); if let Some(active_item) = active_item { if active_item.is_focused(cx) { @@ -2655,7 +2655,7 @@ impl View for Workspace { let project = self.project.clone(); Flex::row() .with_children( - if self.left_dock.read(cx).active_item().is_some() { + if self.left_dock.read(cx).active_panel().is_some() { Some( ChildView::new(&self.left_dock, cx) .constrained() @@ -2688,7 +2688,7 @@ impl View for Workspace { .flex(1., true), ) .with_children( - if self.bottom_dock.read(cx).active_item().is_some() + if self.bottom_dock.read(cx).active_panel().is_some() { Some(ChildView::new(&self.bottom_dock, cx)) } else { @@ -2698,7 +2698,7 @@ impl View for Workspace { .flex(1., true), ) .with_children( - if self.right_dock.read(cx).active_item().is_some() { + if self.right_dock.read(cx).active_panel().is_some() { Some( ChildView::new(&self.right_dock, cx) .constrained() From 55496693166f9b17a4371a1497bcfeb01a91dbfd Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 10 May 2023 10:41:03 -0600 Subject: [PATCH 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 6adcf27bd1aa18f6f96c1f2433861cdce8e38e09..d78b230dd91b2cf5dc0d16cc59311745b27d61b1 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -10,7 +10,7 @@ use gpui::{ use settings::{settings_file::SettingsFile, Settings}; use workspace::{ - item::Item, open_new, dock::DockPosition, AppState, PaneBackdrop, Welcome, Workspace, + dock::DockPosition, item::Item, open_new, AppState, PaneBackdrop, Welcome, Workspace, WorkspaceId, }; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 82d8d404259bf73d88e7d0f9fd6a0fbd59449bc4..5320a4c652cd05b99a82a243828f0434fb5f5044 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -202,6 +202,8 @@ impl Dock { if panel_ix == self.active_panel_index { self.active_panel_index = 0; self.set_open(false, cx); + } else if panel_ix < self.active_panel_index { + self.active_panel_index -= 1; } self.panel_entries.remove(panel_ix); cx.notify(); @@ -228,7 +230,9 @@ impl Dock { pub fn active_panel(&self) -> Option<&Rc> { if self.is_open { - self.panel_entries.get(self.active_panel_index).map(|entry| &entry.panel) + self.panel_entries + .get(self.active_panel_index) + .map(|entry| &entry.panel) } else { None } @@ -416,3 +420,68 @@ impl StatusItemView for PanelButtons { ) { } } + +#[cfg(test)] +pub(crate) mod test { + use super::*; + use gpui::Entity; + + pub enum TestPanelEvent { + PositionChanged, + Activated, + Closed, + } + + pub struct TestPanel { + pub position: DockPosition, + } + + impl Entity for TestPanel { + type Event = TestPanelEvent; + } + + impl View for TestPanel { + fn ui_name() -> &'static str { + "TestPanel" + } + + fn render(&mut self, _: &mut ViewContext<'_, '_, Self>) -> AnyElement { + Empty::new().into_any() + } + } + + impl Panel for TestPanel { + fn position(&self, _: &gpui::WindowContext) -> super::DockPosition { + self.position + } + + fn position_is_valid(&self, _: super::DockPosition) -> bool { + true + } + + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { + self.position = position; + cx.emit(TestPanelEvent::PositionChanged); + } + + fn icon_path(&self) -> &'static str { + "icons/test_panel.svg" + } + + fn icon_tooltip(&self) -> String { + "Test Panel".into() + } + + fn should_change_position_on_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::PositionChanged) + } + + fn should_activate_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool { + matches!(event, TestPanelEvent::Activated) + } + + fn should_close_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool { + matches!(event, TestPanelEvent::Closed) + } + } +} diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 3b7d0ecb8701c4b974781f9ef0bb9e8a123156d3..2ea99581d219b95e331430740af161bbd641056c 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -214,10 +214,7 @@ impl WorkspaceDb { workspace_location = ?2, left_sidebar_open = ?3, timestamp = CURRENT_TIMESTAMP - ))?(( - workspace.id, - &workspace.location, - )) + ))?((workspace.id, &workspace.location)) .context("Updating workspace")?; // Save center pane group @@ -454,7 +451,6 @@ impl WorkspaceDb { #[cfg(test)] mod tests { - use db::open_test_db; use super::*; diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index f946ebf9de81eab14ca9d5e51c2f773c9ed79c97..19da782fb6840a9a39d500db2355ca666154c7a7 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -1,6 +1,4 @@ -use crate::{ - ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, -}; +use crate::{ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId}; use anyhow::{anyhow, Context, Result}; use async_recursion::async_recursion; use db::sqlez::{ diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index cf89ed43c75c3e648f86ded942d4d803e4ab521f..791d0d0a44c319abe9db1f87eea139efbf82ccc1 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -859,7 +859,7 @@ impl Workspace { was_visible = dock.is_open() && dock .active_panel() - .map_or(false, |item| item.as_any().is::()); + .map_or(false, |active_panel| active_panel.id() == panel.id()); dock.remove_panel(&panel, cx); }); dock = match panel.read(cx).position(cx) { @@ -2688,7 +2688,11 @@ impl View for Workspace { .flex(1., true), ) .with_children( - if self.bottom_dock.read(cx).active_panel().is_some() + if self + .bottom_dock + .read(cx) + .active_panel() + .is_some() { Some(ChildView::new(&self.bottom_dock, cx)) } else { @@ -3074,7 +3078,10 @@ fn parse_pixel_position_env_var(value: &str) -> Option { mod tests { use std::{cell::RefCell, rc::Rc}; - use crate::item::test::{TestItem, TestItemEvent, TestProjectItem}; + use crate::{ + dock::test::TestPanel, + item::test::{TestItem, TestItemEvent, TestProjectItem}, + }; use super::*; use fs::FakeFs; @@ -3644,4 +3651,89 @@ mod tests { assert!(pane.can_navigate_forward()); }); } + + #[gpui::test] + async fn test_panels(deterministic: Arc, cx: &mut gpui::TestAppContext) { + deterministic.forbid_parking(); + Settings::test_async(cx); + let fs = FakeFs::new(cx.background()); + + let project = Project::test(fs, [], cx).await; + let (_window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + + let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { + // Add panel_1 on the left, panel_2 on the right. + let panel_1 = cx.add_view(|_| TestPanel { + position: DockPosition::Left, + }); + workspace.add_panel(panel_1.clone(), cx); + workspace + .left_dock() + .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); + let panel_2 = cx.add_view(|_| TestPanel { + position: DockPosition::Right, + }); + workspace.add_panel(panel_2.clone(), cx); + workspace + .right_dock() + .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + + assert_eq!( + workspace.left_dock().read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + assert_eq!( + workspace.right_dock().read(cx).active_panel().unwrap().id(), + panel_2.id() + ); + + (panel_1, panel_2) + }); + + // Move panel_1 to the right + panel_1.update(cx, |panel_1, cx| { + panel_1.set_position(DockPosition::Right, cx) + }); + + workspace.update(cx, |workspace, cx| { + // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right. + // Since it was the only panel on the left, the left dock should now be closed. + assert!(!workspace.left_dock().read(cx).is_open()); + assert!(workspace.left_dock().read(cx).active_panel().is_none()); + assert_eq!( + workspace.right_dock().read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + + // Now we move panel_2 to the left + panel_2.set_position(DockPosition::Left, cx); + }); + + workspace.update(cx, |workspace, cx| { + // Since panel_2 was not visible on the right, we don't open the left dock. + assert!(!workspace.left_dock().read(cx).is_open()); + // And the right dock is unaffected in it's displaying of panel_1 + assert!(workspace.right_dock().read(cx).is_open()); + assert_eq!( + workspace.right_dock().read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + }); + + // Move panel_1 back to the left + panel_1.update(cx, |panel_1, cx| { + panel_1.set_position(DockPosition::Left, cx) + }); + + workspace.update(cx, |workspace, cx| { + // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. + assert!(workspace.left_dock().read(cx).is_open()); + assert_eq!( + workspace.left_dock().read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + // And right the dock should be closed as it no longer has any panels. + assert!(!workspace.right_dock().read(cx).is_open()); + }); + } } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index ff969a8c7597d8accae7ceb93cffa0751012405e..362b714a9274cdc6d0448b2d1320b7482f23bb40 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -52,8 +52,7 @@ use staff_mode::StaffMode; use theme::ThemeRegistry; use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use workspace::{ - item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, - Workspace, + item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, Workspace, }; use zed::{self, build_window_options, initialize_workspace, languages, menus}; From 214354b4da5aa7ac3595dde4cd9d374b66c38f34 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 11 May 2023 23:25:05 -0600 Subject: [PATCH 030/131] Make panels independently resizable --- assets/settings/default.json | 3 +- crates/gpui/src/elements.rs | 20 ++-- crates/gpui/src/elements/resizable.rs | 122 +++++++++------------ crates/project_panel/src/project_panel.rs | 13 ++- crates/settings/src/settings.rs | 31 ++++-- crates/terminal_view/src/terminal_panel.rs | 8 ++ crates/workspace/src/dock.rs | 50 +++++++-- crates/workspace/src/item.rs | 40 +------ 8 files changed, 134 insertions(+), 153 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 5d85e751d482445c7bce29cda0ba08cb89e1fdfa..d1a64996551b60db95e4d97d574216744b59569a 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -108,7 +108,8 @@ "auto_update": true, // Git gutter behavior configuration. "project_panel": { - "dock": "left" + "dock": "left", + "default_width": 240 }, "git": { // Control whether the git gutter is shown. May take 2 values: diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index e2c4af143c5fba1a0404389231af5f0c3ec1e171..3caa91a3b8127a30f66c613e85382af2b7285d5b 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -187,25 +187,21 @@ pub trait Element: 'static { Tooltip::new::(id, text, action, style, self.into_any(), cx) } - fn with_resize_handle( + fn resizable( self, - element_id: usize, - side: Side, - handle_size: f32, - initial_size: f32, - cx: &mut ViewContext, + side: HandleSide, + size: f32, + on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext), ) -> Resizable where Self: 'static + Sized, { - Resizable::new::( + Resizable::new( self.into_any(), - element_id, side, - handle_size, - initial_size, - cx, - ) + size, + on_resize + ) } } diff --git a/crates/gpui/src/elements/resizable.rs b/crates/gpui/src/elements/resizable.rs index 0e78cc07fbce76fa73f6c5106ea218b774042281..a3d95893f47b82bc46a84dcc6714f46462887c42 100644 --- a/crates/gpui/src/elements/resizable.rs +++ b/crates/gpui/src/elements/resizable.rs @@ -1,4 +1,4 @@ -use std::{cell::Cell, rc::Rc}; +use std::{cell::RefCell, rc::Rc}; use pathfinder_geometry::vector::{vec2f, Vector2F}; use serde_json::json; @@ -7,25 +7,23 @@ use crate::{ geometry::rect::RectF, platform::{CursorStyle, MouseButton}, scene::MouseDrag, - AnyElement, Axis, Element, ElementStateHandle, LayoutContext, MouseRegion, SceneBuilder, View, - ViewContext, + AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, View, + ViewContext, SizeConstraint, }; -use super::{ConstrainedBox, Hook}; - #[derive(Copy, Clone, Debug)] -pub enum Side { +pub enum HandleSide { Top, Bottom, Left, Right, } -impl Side { +impl HandleSide { fn axis(&self) -> Axis { match self { - Side::Left | Side::Right => Axis::Horizontal, - Side::Top | Side::Bottom => Axis::Vertical, + HandleSide::Left | HandleSide::Right => Axis::Horizontal, + HandleSide::Top | HandleSide::Bottom => Axis::Vertical, } } @@ -33,8 +31,8 @@ impl Side { /// then top-to-bottom fn before_content(self) -> bool { match self { - Side::Left | Side::Top => true, - Side::Right | Side::Bottom => false, + HandleSide::Left | HandleSide::Top => true, + HandleSide::Right | HandleSide::Bottom => false, } } @@ -55,14 +53,14 @@ impl Side { fn of_rect(&self, bounds: RectF, handle_size: f32) -> RectF { match self { - Side::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)), - Side::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())), - Side::Bottom => { + HandleSide::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)), + HandleSide::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())), + HandleSide::Bottom => { let mut origin = bounds.lower_left(); origin.set_y(origin.y() - handle_size); RectF::new(origin, vec2f(bounds.width(), handle_size)) } - Side::Right => { + HandleSide::Right => { let mut origin = bounds.upper_right(); origin.set_x(origin.x() - handle_size); RectF::new(origin, vec2f(handle_size, bounds.height())) @@ -71,69 +69,44 @@ impl Side { } } -struct ResizeHandleState { - actual_dimension: Cell, - custom_dimension: Cell, -} - pub struct Resizable { - side: Side, - handle_size: f32, child: AnyElement, - state: Rc, - _state_handle: ElementStateHandle>, + handle_side: HandleSide, + handle_size: f32, + on_resize: Rc)>> } +const DEFAULT_HANDLE_SIZE: f32 = 4.0; + impl Resizable { - pub fn new( + pub fn new( child: AnyElement, - element_id: usize, - side: Side, - handle_size: f32, - initial_size: f32, - cx: &mut ViewContext, + handle_side: HandleSide, + size: f32, + on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext) ) -> Self { - let state_handle = cx.element_state::>( - element_id, - Rc::new(ResizeHandleState { - actual_dimension: Cell::new(initial_size), - custom_dimension: Cell::new(initial_size), - }), - ); - - let state = state_handle.read(cx).clone(); - - let child = Hook::new({ - let constrained = ConstrainedBox::new(child); - match side.axis() { - Axis::Horizontal => constrained.with_max_width(state.custom_dimension.get()), - Axis::Vertical => constrained.with_max_height(state.custom_dimension.get()), - } - }) - .on_after_layout({ - let state = state.clone(); - move |size, _| { - state.actual_dimension.set(side.relevant_component(size)); - } - }) + let child = match handle_side.axis() { + Axis::Horizontal => child.constrained().with_max_width(size), + Axis::Vertical => child.constrained().with_max_height(size), + } .into_any(); Self { - side, child, - handle_size, - state, - _state_handle: state_handle, + handle_side, + handle_size: DEFAULT_HANDLE_SIZE, + on_resize: Rc::new(RefCell::new(on_resize)), } } - pub fn current_size(&self) -> f32 { - self.state.actual_dimension.get() + pub fn with_handle_size(mut self, handle_size: f32) -> Self { + self.handle_size = handle_size; + self } } impl Element for Resizable { - type LayoutState = (); + type LayoutState = SizeConstraint; type PaintState = (); fn layout( @@ -142,7 +115,7 @@ impl Element for Resizable { view: &mut V, cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { - (self.child.layout(constraint, view, cx), ()) + (self.child.layout(constraint, view, cx), constraint) } fn paint( @@ -150,34 +123,37 @@ impl Element for Resizable { scene: &mut SceneBuilder, bounds: pathfinder_geometry::rect::RectF, visible_bounds: pathfinder_geometry::rect::RectF, - _child_size: &mut Self::LayoutState, + constraint: &mut SizeConstraint, view: &mut V, cx: &mut ViewContext, ) -> Self::PaintState { scene.push_stacking_context(None, None); - let handle_region = self.side.of_rect(bounds, self.handle_size); + let handle_region = self.handle_side.of_rect(bounds, self.handle_size); enum ResizeHandle {} scene.push_mouse_region( - MouseRegion::new::(cx.view_id(), self.side as usize, handle_region) + MouseRegion::new::(cx.view_id(), self.handle_side as usize, handle_region) .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere .on_drag(MouseButton::Left, { - let state = self.state.clone(); - let side = self.side; - move |e, _: &mut V, cx| { - let prev_width = state.actual_dimension.get(); - state - .custom_dimension - .set(0f32.max(prev_width + side.compute_delta(e)).round()); - cx.notify(); + let bounds = bounds.clone(); + let side = self.handle_side; + let prev_size = side.relevant_component(bounds.size()); + let min_size = side.relevant_component(constraint.min); + let max_size = side.relevant_component(constraint.max); + let on_resize = self.on_resize.clone(); + move |event, view: &mut V, cx| { + let new_size = min_size.max(prev_size + side.compute_delta(event)).min(max_size).round(); + if new_size != prev_size { + on_resize.borrow_mut()(view, new_size, cx); + } } }), ); scene.push_cursor_region(crate::CursorRegion { bounds: handle_region, - style: match self.side.axis() { + style: match self.handle_side.axis() { Axis::Horizontal => CursorStyle::ResizeLeftRight, Axis::Vertical => CursorStyle::ResizeUpDown, }, diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 14779f9b6fa0f58d437e75c865584c5e7ef78ef7..ac0df343ae4183f1627ab8e34a11b3aa48a2b351 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1347,12 +1347,9 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { let settings = cx.global::(); - let dock = settings - .project_panel_overrides - .dock - .or(settings.project_panel_defaults.dock) - .unwrap(); - match dock { + match settings + .project_panel + .dock { settings::ProjectPanelDockPosition::Left => DockPosition::Left, settings::ProjectPanelDockPosition::Right => DockPosition::Right, } @@ -1374,6 +1371,10 @@ impl workspace::dock::Panel for ProjectPanel { }) } + fn default_size(&self, cx: &gpui::WindowContext) -> f32 { + cx.global::().project_panel.default_width + } + fn icon_path(&self) -> &'static str { "icons/folder_tree_16.svg" } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 67acd12939a3a429c14e0064bdc3c2495108e30c..df03d4c1f6d72b31e5e9b8d0896a4329b9942a29 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -44,8 +44,7 @@ pub struct Settings { pub show_call_status_icon: bool, pub vim_mode: bool, pub autosave: Autosave, - pub project_panel_defaults: ProjectPanelSettings, - pub project_panel_overrides: ProjectPanelSettings, + pub project_panel: ProjectPanelSettings, pub editor_defaults: EditorSettings, pub editor_overrides: EditorSettings, pub git: GitSettings, @@ -158,9 +157,15 @@ pub enum GitGutter { pub struct GitGutterConfig {} -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct ProjectPanelSettings { + pub dock: ProjectPanelDockPosition, + pub default_width: f32, +} +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct ProjectPanelSettingsContent { pub dock: Option, + pub default_width: Option, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)] @@ -253,6 +258,8 @@ impl Default for HourFormat { #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct TerminalSettings { + pub default_width: Option, + pub default_height: Option, pub shell: Option, pub working_directory: Option, pub font_size: Option, @@ -387,7 +394,7 @@ pub struct SettingsFileContent { #[serde(flatten)] pub editor: EditorSettings, #[serde(default)] - pub project_panel: ProjectPanelSettings, + pub project_panel: ProjectPanelSettingsContent, #[serde(default)] pub journal: JournalSettings, #[serde(default)] @@ -423,7 +430,6 @@ pub struct Features { } #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] pub struct FeaturesContent { pub copilot: Option, } @@ -482,8 +488,10 @@ impl Settings { show_call_status_icon: defaults.show_call_status_icon.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), - project_panel_defaults: defaults.project_panel, - project_panel_overrides: Default::default(), + project_panel: ProjectPanelSettings { + dock: defaults.project_panel.dock.unwrap(), + default_width: defaults.project_panel.default_width.unwrap(), + }, editor_defaults: EditorSettings { tab_size: required(defaults.editor.tab_size), hard_tabs: required(defaults.editor.hard_tabs), @@ -590,7 +598,8 @@ impl Settings { } } self.editor_overrides = data.editor; - self.project_panel_overrides = data.project_panel; + merge(&mut self.project_panel.dock, data.project_panel.dock); + merge(&mut self.project_panel.default_width, data.project_panel.default_width); self.git_overrides = data.git.unwrap_or_default(); self.journal_overrides = data.journal; self.terminal_defaults.font_size = data.terminal.font_size; @@ -778,8 +787,10 @@ impl Settings { show_call_status_icon: true, vim_mode: false, autosave: Autosave::Off, - project_panel_defaults: Default::default(), - project_panel_overrides: Default::default(), + project_panel: ProjectPanelSettings { + dock: ProjectPanelDockPosition::Left, + default_width: 240., + }, editor_defaults: EditorSettings { tab_size: Some(4.try_into().unwrap()), hard_tabs: Some(false), diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 5d4099c446fc6ee1d696126afec5b65a13c670c0..995c8be50b273d4337be3a75453ae39f8b6d78be 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -179,6 +179,14 @@ impl Panel for TerminalPanel { }); } + fn default_size(&self, cx: &gpui::WindowContext) -> f32 { + let settings = &cx.global::().terminal_overrides; + match self.position(cx) { + DockPosition::Left | DockPosition::Right => settings.default_width.unwrap_or(640.), + DockPosition::Bottom => settings.default_height.unwrap_or(320.), + } + } + fn icon_path(&self) -> &'static str { "icons/terminal_12.svg" } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 5320a4c652cd05b99a82a243828f0434fb5f5044..6ce1e4f691fdbbda679a42f200bbb66506d53155 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -12,6 +12,7 @@ pub trait Panel: View { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); + fn default_size(&self, cx: &WindowContext) -> f32; fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; fn icon_label(&self, _: &AppContext) -> Option { @@ -27,6 +28,7 @@ pub trait PanelHandle { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; fn set_position(&self, position: DockPosition, cx: &mut WindowContext); + fn default_size(&self, cx: &WindowContext) -> f32; fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -54,6 +56,10 @@ where self.update(cx, |this, cx| this.set_position(position, cx)) } + fn default_size(&self, cx: &WindowContext) -> f32 { + self.read(cx).default_size(cx) + } + fn icon_path(&self, cx: &WindowContext) -> &'static str { self.read(cx).icon_path() } @@ -104,17 +110,18 @@ impl DockPosition { } } - fn to_resizable_side(self) -> Side { + fn to_resize_handle_side(self) -> HandleSide { match self { - Self::Left => Side::Right, - Self::Bottom => Side::Top, - Self::Right => Side::Left, + Self::Left => HandleSide::Right, + Self::Bottom => HandleSide::Top, + Self::Right => HandleSide::Left, } } } struct PanelEntry { panel: Rc, + size: f32, context_menu: ViewHandle, _subscriptions: [Subscription; 2], } @@ -181,8 +188,10 @@ impl Dock { ]; let dock_view_id = cx.view_id(); + let size = panel.default_size(cx); self.panel_entries.push(PanelEntry { panel: Rc::new(panel), + size, context_menu: cx.add_view(|cx| { let mut menu = ContextMenu::new(dock_view_id, cx); menu.set_position_mode(OverlayPositionMode::Local); @@ -237,6 +246,23 @@ impl Dock { None } } + + pub fn active_panel_size(&self) -> Option { + if self.is_open { + self.panel_entries + .get(self.active_panel_index) + .map(|entry| entry.size) + } else { + None + } + } + + pub fn resize_active_panel(&mut self, size: f32, cx: &mut ViewContext) { + if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) { + entry.size = size; + cx.notify(); + } + } } impl Entity for Dock { @@ -250,18 +276,14 @@ impl View for Dock { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(active_panel) = self.active_panel() { - enum ResizeHandleTag {} + let size = self.active_panel_size().unwrap(); let style = &cx.global::().theme.workspace.dock; ChildView::new(active_panel.as_any(), cx) .contained() .with_style(style.container) - .with_resize_handle::( - self.position as usize, - self.position.to_resizable_side(), - 4., - style.initial_size, - cx, - ) + .resizable(self.position.to_resize_handle_side(), size, |dock: &mut Self, size, cx| { + dock.resize_active_panel(size, cx); + }) .into_any() } else { Empty::new().into_any() @@ -464,6 +486,10 @@ pub(crate) mod test { cx.emit(TestPanelEvent::PositionChanged); } + fn default_size(&self, _: &WindowContext) -> f32 { + 300. + } + fn icon_path(&self) -> &'static str { "icons/test_panel.svg" } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index dfad53c3a370dc99289666db54c017b8393bed86..2adbff51fe956cee0cb9799f250b1f549f65aeec 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -766,7 +766,7 @@ impl FollowableItemHandle for ViewHandle { #[cfg(test)] pub(crate) mod test { use super::{Item, ItemEvent}; - use crate::{dock::Panel, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; + use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; use gpui::{ elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -1059,42 +1059,4 @@ pub(crate) mod test { Task::Ready(Some(anyhow::Ok(view))) } } - - impl Panel for TestItem { - fn position(&self, _cx: &gpui::WindowContext) -> crate::dock::DockPosition { - unimplemented!() - } - - fn position_is_valid(&self, _position: crate::dock::DockPosition) -> bool { - unimplemented!() - } - - fn set_position( - &mut self, - _position: crate::dock::DockPosition, - _cx: &mut ViewContext, - ) { - unimplemented!() - } - - fn icon_path(&self) -> &'static str { - unimplemented!() - } - - fn icon_tooltip(&self) -> String { - unimplemented!() - } - - fn should_change_position_on_event(_: &Self::Event) -> bool { - unimplemented!() - } - - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - unimplemented!() - } - - fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { - unimplemented!() - } - } } From bd795d7607f7ee03e67babc70049d2f7cfd61319 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 12 May 2023 15:15:11 -0600 Subject: [PATCH 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 3caa91a3b8127a30f66c613e85382af2b7285d5b..f593615ae74db2c5fb1e1d5045978a9b9dc08d9f 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -196,12 +196,7 @@ pub trait Element: 'static { where Self: 'static + Sized, { - Resizable::new( - self.into_any(), - side, - size, - on_resize - ) + Resizable::new(self.into_any(), side, size, on_resize) } } diff --git a/crates/gpui/src/elements/resizable.rs b/crates/gpui/src/elements/resizable.rs index a3d95893f47b82bc46a84dcc6714f46462887c42..da4b3473b3069ea343d7acdd0cc85c262e7a76cf 100644 --- a/crates/gpui/src/elements/resizable.rs +++ b/crates/gpui/src/elements/resizable.rs @@ -7,8 +7,8 @@ use crate::{ geometry::rect::RectF, platform::{CursorStyle, MouseButton}, scene::MouseDrag, - AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, View, - ViewContext, SizeConstraint, + AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View, + ViewContext, }; #[derive(Copy, Clone, Debug)] @@ -73,7 +73,7 @@ pub struct Resizable { child: AnyElement, handle_side: HandleSide, handle_size: f32, - on_resize: Rc)>> + on_resize: Rc)>>, } const DEFAULT_HANDLE_SIZE: f32 = 4.0; @@ -83,7 +83,7 @@ impl Resizable { child: AnyElement, handle_side: HandleSide, size: f32, - on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext) + on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext), ) -> Self { let child = match handle_side.axis() { Axis::Horizontal => child.constrained().with_max_width(size), @@ -133,22 +133,29 @@ impl Element for Resizable { enum ResizeHandle {} scene.push_mouse_region( - MouseRegion::new::(cx.view_id(), self.handle_side as usize, handle_region) - .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere - .on_drag(MouseButton::Left, { - let bounds = bounds.clone(); - let side = self.handle_side; - let prev_size = side.relevant_component(bounds.size()); - let min_size = side.relevant_component(constraint.min); - let max_size = side.relevant_component(constraint.max); - let on_resize = self.on_resize.clone(); - move |event, view: &mut V, cx| { - let new_size = min_size.max(prev_size + side.compute_delta(event)).min(max_size).round(); - if new_size != prev_size { - on_resize.borrow_mut()(view, new_size, cx); - } + MouseRegion::new::( + cx.view_id(), + self.handle_side as usize, + handle_region, + ) + .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere + .on_drag(MouseButton::Left, { + let bounds = bounds.clone(); + let side = self.handle_side; + let prev_size = side.relevant_component(bounds.size()); + let min_size = side.relevant_component(constraint.min); + let max_size = side.relevant_component(constraint.max); + let on_resize = self.on_resize.clone(); + move |event, view: &mut V, cx| { + let new_size = min_size + .max(prev_size + side.compute_delta(event)) + .min(max_size) + .round(); + if new_size != prev_size { + on_resize.borrow_mut()(view, new_size, cx); } - }), + } + }), ); scene.push_cursor_region(crate::CursorRegion { diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index ac0df343ae4183f1627ab8e34a11b3aa48a2b351..c76d2481efcb27ffd2b42799fd76132e1822bb46 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -12,8 +12,8 @@ use gpui::{ geometry::vector::Vector2F, keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, - AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext, - ViewHandle, WeakViewHandle, + AnyElement, AppContext, Axis, ClipboardItem, Element, Entity, ModelHandle, Task, View, + ViewContext, ViewHandle, WeakViewHandle, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; @@ -1347,9 +1347,7 @@ impl Entity for ProjectPanel { impl workspace::dock::Panel for ProjectPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { let settings = cx.global::(); - match settings - .project_panel - .dock { + match settings.project_panel.dock { settings::ProjectPanelDockPosition::Left => DockPosition::Left, settings::ProjectPanelDockPosition::Right => DockPosition::Right, } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index df03d4c1f6d72b31e5e9b8d0896a4329b9942a29..618ccb89ac812368a7d022054a57e5130e4971cd 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -599,7 +599,10 @@ impl Settings { } self.editor_overrides = data.editor; merge(&mut self.project_panel.dock, data.project_panel.dock); - merge(&mut self.project_panel.default_width, data.project_panel.default_width); + merge( + &mut self.project_panel.default_width, + data.project_panel.default_width, + ); self.git_overrides = data.git.unwrap_or_default(); self.journal_overrides = data.journal; self.terminal_defaults.font_size = data.terminal.font_size; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 6ce1e4f691fdbbda679a42f200bbb66506d53155..cafe8bd5dceedd411e71a32ca57a8f6f0bbf32d4 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -2,7 +2,8 @@ use crate::{StatusItemView, Workspace}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, - AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, + AppContext, Axis, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, + WindowContext, }; use serde::Deserialize; use settings::Settings; @@ -117,6 +118,13 @@ impl DockPosition { Self::Right => HandleSide::Left, } } + + pub fn axis(&self) -> Axis { + match self { + Self::Left | Self::Right => Axis::Horizontal, + Self::Bottom => Axis::Vertical, + } + } } struct PanelEntry { @@ -247,6 +255,23 @@ impl Dock { } } + pub fn panel_size(&self, panel: &dyn PanelHandle) -> Option { + self.panel_entries + .iter() + .find(|entry| entry.panel.id() == panel.id()) + .map(|entry| entry.size) + } + + pub fn resize_panel(&mut self, panel: &dyn PanelHandle, size: f32) { + let entry = self + .panel_entries + .iter_mut() + .find(|entry| entry.panel.id() == panel.id()); + if let Some(entry) = entry { + entry.size = size; + } + } + pub fn active_panel_size(&self) -> Option { if self.is_open { self.panel_entries @@ -281,9 +306,13 @@ impl View for Dock { ChildView::new(active_panel.as_any(), cx) .contained() .with_style(style.container) - .resizable(self.position.to_resize_handle_side(), size, |dock: &mut Self, size, cx| { - dock.resize_active_panel(size, cx); - }) + .resizable( + self.position.to_resize_handle_side(), + size, + |dock: &mut Self, size, cx| { + dock.resize_active_panel(size, cx); + }, + ) .into_any() } else { Empty::new().into_any() @@ -487,7 +516,10 @@ pub(crate) mod test { } fn default_size(&self, _: &WindowContext) -> f32 { - 300. + match self.position.axis() { + Axis::Horizontal => 300., + Axis::Vertical => 200., + } } fn icon_path(&self) -> &'static str { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 791d0d0a44c319abe9db1f87eea139efbf82ccc1..7bfe180d50640dc0798577574e4a0d5e6075ac03 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -852,10 +852,18 @@ impl Workspace { cx.subscribe(&panel, { let mut dock = dock.clone(); + let mut prev_position = panel.position(cx); move |this, panel, event, cx| { if T::should_change_position_on_event(event) { + let new_position = panel.read(cx).position(cx); let mut was_visible = false; + let mut size = None; dock.update(cx, |dock, cx| { + if new_position.axis() == prev_position.axis() { + size = dock.panel_size(&panel); + } + prev_position = new_position; + was_visible = dock.is_open() && dock .active_panel() @@ -869,7 +877,11 @@ impl Workspace { } .clone(); dock.update(cx, |dock, cx| { - dock.add_panel(panel, cx); + dock.add_panel(panel.clone(), cx); + if let Some(size) = size { + dock.resize_panel(&panel, size); + } + if was_visible { dock.set_open(true, cx); dock.activate_panel(dock.panels_len() - 1, cx); @@ -3678,10 +3690,17 @@ mod tests { .right_dock() .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + let left_dock = workspace.left_dock(); assert_eq!( - workspace.left_dock().read(cx).active_panel().unwrap().id(), + left_dock.read(cx).active_panel().unwrap().id(), panel_1.id() ); + assert_eq!( + left_dock.read(cx).active_panel_size().unwrap(), + panel_1.default_size(cx) + ); + + left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx)); assert_eq!( workspace.right_dock().read(cx).active_panel().unwrap().id(), panel_2.id() @@ -3700,10 +3719,12 @@ mod tests { // Since it was the only panel on the left, the left dock should now be closed. assert!(!workspace.left_dock().read(cx).is_open()); assert!(workspace.left_dock().read(cx).active_panel().is_none()); + let right_dock = workspace.right_dock(); assert_eq!( - workspace.right_dock().read(cx).active_panel().unwrap().id(), + right_dock.read(cx).active_panel().unwrap().id(), panel_1.id() ); + assert_eq!(right_dock.read(cx).active_panel_size().unwrap(), 1337.); // Now we move panel_2 to the left panel_2.set_position(DockPosition::Left, cx); @@ -3727,13 +3748,33 @@ mod tests { workspace.update(cx, |workspace, cx| { // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. - assert!(workspace.left_dock().read(cx).is_open()); + let left_dock = workspace.left_dock(); + assert!(left_dock.read(cx).is_open()); assert_eq!( - workspace.left_dock().read(cx).active_panel().unwrap().id(), + left_dock.read(cx).active_panel().unwrap().id(), panel_1.id() ); + assert_eq!(left_dock.read(cx).active_panel_size().unwrap(), 1337.); // And right the dock should be closed as it no longer has any panels. assert!(!workspace.right_dock().read(cx).is_open()); + + // Now we move panel_1 to the bottom + panel_1.set_position(DockPosition::Bottom, cx); + }); + + workspace.update(cx, |workspace, cx| { + // Since panel_1 was visible on the left, we close the left dock. + assert!(!workspace.left_dock().read(cx).is_open()); + // The bottom dock is sized based on the panel's default size, + // since the panel orientation changed from vertical to horizontal. + assert_eq!( + workspace + .bottom_dock() + .read(cx) + .active_panel_size() + .unwrap(), + panel_1.default_size(cx), + ); }); } } From 6c608538428820ddd3df182438bb03720498dcdd Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 12 May 2023 15:44:09 -0600 Subject: [PATCH 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 c76d2481efcb27ffd2b42799fd76132e1822bb46..e271c9970b7ba9f29772274e29bed58b6127b1ff 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -12,8 +12,8 @@ use gpui::{ geometry::vector::Vector2F, keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, - AnyElement, AppContext, Axis, ClipboardItem, Element, Entity, ModelHandle, Task, View, - ViewContext, ViewHandle, WeakViewHandle, + AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext, + ViewHandle, WeakViewHandle, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index cafe8bd5dceedd411e71a32ca57a8f6f0bbf32d4..86c2cc392932f24a5de77ed671ce11c827f2bbba 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -180,16 +180,20 @@ impl Dock { pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) { let subscriptions = [ cx.observe(&panel, |_, _, cx| cx.notify()), - cx.subscribe(&panel, |this, view, event, cx| { - if view.read(cx).should_activate_on_event(event, cx) { + cx.subscribe(&panel, |this, panel, event, cx| { + if panel.read(cx).should_activate_on_event(event, cx) { if let Some(ix) = this .panel_entries .iter() - .position(|entry| entry.panel.id() == view.id()) + .position(|entry| entry.panel.id() == panel.id()) { + this.set_open(true, cx); this.activate_panel(ix, cx); + cx.focus(&panel); } - } else if view.read(cx).should_close_on_event(event, cx) { + } else if panel.read(cx).should_close_on_event(event, cx) + && this.active_panel().map_or(false, |p| p.id() == panel.id()) + { this.set_open(false, cx); } }), diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 7bfe180d50640dc0798577574e4a0d5e6075ac03..ad717c362b0cdc1b4a973390655d1806ee9c5738 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3091,7 +3091,7 @@ mod tests { use std::{cell::RefCell, rc::Rc}; use crate::{ - dock::test::TestPanel, + dock::test::{TestPanel, TestPanelEvent}, item::test::{TestItem, TestItemEvent, TestProjectItem}, }; @@ -3767,14 +3767,50 @@ mod tests { assert!(!workspace.left_dock().read(cx).is_open()); // The bottom dock is sized based on the panel's default size, // since the panel orientation changed from vertical to horizontal. + let bottom_dock = workspace.bottom_dock(); assert_eq!( - workspace - .bottom_dock() - .read(cx) - .active_panel_size() - .unwrap(), + bottom_dock.read(cx).active_panel_size().unwrap(), panel_1.default_size(cx), ); + // Close bottom dock and move panel_1 back to the left. + bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); + panel_1.set_position(DockPosition::Left, cx); + }); + + // Emit activated event on panel 1 + panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated)); + + // Now the left dock is open and panel_1 is active and focused. + workspace.read_with(cx, |workspace, cx| { + let left_dock = workspace.left_dock(); + assert!(left_dock.read(cx).is_open()); + assert_eq!( + left_dock.read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + assert!(panel_1.is_focused(cx)); + }); + + // Emit closed event on panel 2, which is not active + panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed)); + + // Wo don't close the left dock, because panel_2 wasn't the active panel + workspace.read_with(cx, |workspace, cx| { + let left_dock = workspace.left_dock(); + assert!(left_dock.read(cx).is_open()); + assert_eq!( + left_dock.read(cx).active_panel().unwrap().id(), + panel_1.id() + ); + }); + + // Emit closed event on panel 1, which is active + panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed)); + + // Now the left dock is closed, because panel_1 was the active panel + workspace.read_with(cx, |workspace, cx| { + let left_dock = workspace.left_dock(); + assert!(!left_dock.read(cx).is_open()); }); } } From ba50b35de691f692c9624f11491d2f78d9a31ef0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 13 May 2023 14:34:09 -0600 Subject: [PATCH 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 2e4fa62e8ed1b7bdade780e366f3c7e92b445264..768e0c6ed1155fd6de2e131031954e4aacbdd5bd 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -1,534 +1,535 @@ [ - // Standard macOS bindings - { - "bindings": { - "up": "menu::SelectPrev", - "pageup": "menu::SelectFirst", - "shift-pageup": "menu::SelectFirst", - "ctrl-p": "menu::SelectPrev", - "down": "menu::SelectNext", - "pagedown": "menu::SelectLast", - "shift-pagedown": "menu::SelectFirst", - "ctrl-n": "menu::SelectNext", - "cmd-up": "menu::SelectFirst", - "cmd-down": "menu::SelectLast", - "enter": "menu::Confirm", - "escape": "menu::Cancel", - "ctrl-c": "menu::Cancel", - "cmd-{": "pane::ActivatePrevItem", - "cmd-}": "pane::ActivateNextItem", - "alt-cmd-left": "pane::ActivatePrevItem", - "alt-cmd-right": "pane::ActivateNextItem", - "cmd-w": "pane::CloseActiveItem", - "alt-cmd-t": "pane::CloseInactiveItems", - "cmd-k u": "pane::CloseCleanItems", - "cmd-k cmd-w": "pane::CloseAllItems", - "cmd-shift-w": "workspace::CloseWindow", - "cmd-s": "workspace::Save", - "cmd-shift-s": "workspace::SaveAs", - "cmd-=": "zed::IncreaseBufferFontSize", - "cmd--": "zed::DecreaseBufferFontSize", - "cmd-0": "zed::ResetBufferFontSize", - "cmd-,": "zed::OpenSettings", - "cmd-q": "zed::Quit", - "cmd-h": "zed::Hide", - "alt-cmd-h": "zed::HideOthers", - "cmd-m": "zed::Minimize", - "ctrl-cmd-f": "zed::ToggleFullScreen", - "cmd-n": "workspace::NewFile", - "cmd-shift-n": "workspace::NewWindow", - "cmd-o": "workspace::Open", - "alt-cmd-o": "projects::OpenRecent", - "ctrl-`": "workspace::NewTerminal" - } - }, - { - "context": "Editor", - "bindings": { - "escape": "editor::Cancel", - "backspace": "editor::Backspace", - "shift-backspace": "editor::Backspace", - "ctrl-h": "editor::Backspace", - "delete": "editor::Delete", - "ctrl-d": "editor::Delete", - "tab": "editor::Tab", - "shift-tab": "editor::TabPrev", - "ctrl-k": "editor::CutToEndOfLine", - "ctrl-t": "editor::Transpose", - "cmd-backspace": "editor::DeleteToBeginningOfLine", - "cmd-delete": "editor::DeleteToEndOfLine", - "alt-backspace": "editor::DeleteToPreviousWordStart", - "alt-delete": "editor::DeleteToNextWordEnd", - "alt-h": "editor::DeleteToPreviousWordStart", - "alt-d": "editor::DeleteToNextWordEnd", - "cmd-x": "editor::Cut", - "cmd-c": "editor::Copy", - "cmd-v": "editor::Paste", - "cmd-z": "editor::Undo", - "cmd-shift-z": "editor::Redo", - "up": "editor::MoveUp", - "pageup": "editor::PageUp", - "shift-pageup": "editor::MovePageUp", - "home": "editor::MoveToBeginningOfLine", - "down": "editor::MoveDown", - "pagedown": "editor::PageDown", - "shift-pagedown": "editor::MovePageDown", - "end": "editor::MoveToEndOfLine", - "left": "editor::MoveLeft", - "right": "editor::MoveRight", - "ctrl-p": "editor::MoveUp", - "ctrl-n": "editor::MoveDown", - "ctrl-b": "editor::MoveLeft", - "ctrl-f": "editor::MoveRight", - "ctrl-l": "editor::NextScreen", - "alt-left": "editor::MoveToPreviousWordStart", - "alt-b": "editor::MoveToPreviousWordStart", - "alt-right": "editor::MoveToNextWordEnd", - "alt-f": "editor::MoveToNextWordEnd", - "cmd-left": "editor::MoveToBeginningOfLine", - "ctrl-a": "editor::MoveToBeginningOfLine", - "cmd-right": "editor::MoveToEndOfLine", - "ctrl-e": "editor::MoveToEndOfLine", - "cmd-up": "editor::MoveToBeginning", - "cmd-down": "editor::MoveToEnd", - "shift-up": "editor::SelectUp", - "ctrl-shift-p": "editor::SelectUp", - "shift-down": "editor::SelectDown", - "ctrl-shift-n": "editor::SelectDown", - "shift-left": "editor::SelectLeft", - "ctrl-shift-b": "editor::SelectLeft", - "shift-right": "editor::SelectRight", - "ctrl-shift-f": "editor::SelectRight", - "alt-shift-left": "editor::SelectToPreviousWordStart", - "alt-shift-b": "editor::SelectToPreviousWordStart", - "alt-shift-right": "editor::SelectToNextWordEnd", - "alt-shift-f": "editor::SelectToNextWordEnd", - "cmd-shift-up": "editor::SelectToBeginning", - "cmd-shift-down": "editor::SelectToEnd", - "cmd-a": "editor::SelectAll", - "cmd-l": "editor::SelectLine", - "cmd-shift-i": "editor::Format", - "cmd-shift-left": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true + // Standard macOS bindings + { + "bindings": { + "up": "menu::SelectPrev", + "pageup": "menu::SelectFirst", + "shift-pageup": "menu::SelectFirst", + "ctrl-p": "menu::SelectPrev", + "down": "menu::SelectNext", + "pagedown": "menu::SelectLast", + "shift-pagedown": "menu::SelectFirst", + "ctrl-n": "menu::SelectNext", + "cmd-up": "menu::SelectFirst", + "cmd-down": "menu::SelectLast", + "enter": "menu::Confirm", + "escape": "menu::Cancel", + "ctrl-c": "menu::Cancel", + "cmd-{": "pane::ActivatePrevItem", + "cmd-}": "pane::ActivateNextItem", + "alt-cmd-left": "pane::ActivatePrevItem", + "alt-cmd-right": "pane::ActivateNextItem", + "cmd-w": "pane::CloseActiveItem", + "alt-cmd-t": "pane::CloseInactiveItems", + "cmd-k u": "pane::CloseCleanItems", + "cmd-k cmd-w": "pane::CloseAllItems", + "cmd-shift-w": "workspace::CloseWindow", + "cmd-s": "workspace::Save", + "cmd-shift-s": "workspace::SaveAs", + "cmd-=": "zed::IncreaseBufferFontSize", + "cmd--": "zed::DecreaseBufferFontSize", + "cmd-0": "zed::ResetBufferFontSize", + "cmd-,": "zed::OpenSettings", + "cmd-q": "zed::Quit", + "cmd-h": "zed::Hide", + "alt-cmd-h": "zed::HideOthers", + "cmd-m": "zed::Minimize", + "ctrl-cmd-f": "zed::ToggleFullScreen", + "cmd-n": "workspace::NewFile", + "cmd-shift-n": "workspace::NewWindow", + "cmd-o": "workspace::Open", + "alt-cmd-o": "projects::OpenRecent", + "ctrl-`": "workspace::NewTerminal" } - ], - "shift-home": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true + }, + { + "context": "Editor", + "bindings": { + "escape": "editor::Cancel", + "backspace": "editor::Backspace", + "shift-backspace": "editor::Backspace", + "ctrl-h": "editor::Backspace", + "delete": "editor::Delete", + "ctrl-d": "editor::Delete", + "tab": "editor::Tab", + "shift-tab": "editor::TabPrev", + "ctrl-k": "editor::CutToEndOfLine", + "ctrl-t": "editor::Transpose", + "cmd-backspace": "editor::DeleteToBeginningOfLine", + "cmd-delete": "editor::DeleteToEndOfLine", + "alt-backspace": "editor::DeleteToPreviousWordStart", + "alt-delete": "editor::DeleteToNextWordEnd", + "alt-h": "editor::DeleteToPreviousWordStart", + "alt-d": "editor::DeleteToNextWordEnd", + "cmd-x": "editor::Cut", + "cmd-c": "editor::Copy", + "cmd-v": "editor::Paste", + "cmd-z": "editor::Undo", + "cmd-shift-z": "editor::Redo", + "up": "editor::MoveUp", + "pageup": "editor::PageUp", + "shift-pageup": "editor::MovePageUp", + "home": "editor::MoveToBeginningOfLine", + "down": "editor::MoveDown", + "pagedown": "editor::PageDown", + "shift-pagedown": "editor::MovePageDown", + "end": "editor::MoveToEndOfLine", + "left": "editor::MoveLeft", + "right": "editor::MoveRight", + "ctrl-p": "editor::MoveUp", + "ctrl-n": "editor::MoveDown", + "ctrl-b": "editor::MoveLeft", + "ctrl-f": "editor::MoveRight", + "ctrl-l": "editor::NextScreen", + "alt-left": "editor::MoveToPreviousWordStart", + "alt-b": "editor::MoveToPreviousWordStart", + "alt-right": "editor::MoveToNextWordEnd", + "alt-f": "editor::MoveToNextWordEnd", + "cmd-left": "editor::MoveToBeginningOfLine", + "ctrl-a": "editor::MoveToBeginningOfLine", + "cmd-right": "editor::MoveToEndOfLine", + "ctrl-e": "editor::MoveToEndOfLine", + "cmd-up": "editor::MoveToBeginning", + "cmd-down": "editor::MoveToEnd", + "shift-up": "editor::SelectUp", + "ctrl-shift-p": "editor::SelectUp", + "shift-down": "editor::SelectDown", + "ctrl-shift-n": "editor::SelectDown", + "shift-left": "editor::SelectLeft", + "ctrl-shift-b": "editor::SelectLeft", + "shift-right": "editor::SelectRight", + "ctrl-shift-f": "editor::SelectRight", + "alt-shift-left": "editor::SelectToPreviousWordStart", + "alt-shift-b": "editor::SelectToPreviousWordStart", + "alt-shift-right": "editor::SelectToNextWordEnd", + "alt-shift-f": "editor::SelectToNextWordEnd", + "cmd-shift-up": "editor::SelectToBeginning", + "cmd-shift-down": "editor::SelectToEnd", + "cmd-a": "editor::SelectAll", + "cmd-l": "editor::SelectLine", + "cmd-shift-i": "editor::Format", + "cmd-shift-left": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true + } + ], + "shift-home": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true + } + ], + "ctrl-shift-a": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true + } + ], + "cmd-shift-right": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true + } + ], + "shift-end": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true + } + ], + "ctrl-shift-e": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true + } + ], + "ctrl-v": [ + "editor::MovePageDown", + { + "center_cursor": true + } + ], + "alt-v": [ + "editor::MovePageUp", + { + "center_cursor": true + } + ], + "ctrl-cmd-space": "editor::ShowCharacterPalette" } - ], - "ctrl-shift-a": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true + }, + { + "context": "Editor && mode == full", + "bindings": { + "enter": "editor::Newline", + "cmd-shift-enter": "editor::NewlineAbove", + "cmd-enter": "editor::NewlineBelow", + "alt-z": "editor::ToggleSoftWrap", + "cmd-f": [ + "buffer_search::Deploy", + { + "focus": true + } + ], + "cmd-e": [ + "buffer_search::Deploy", + { + "focus": false + } + ], + "alt-\\": "copilot::Suggest", + "alt-]": "copilot::NextSuggestion", + "alt-[": "copilot::PreviousSuggestion" } - ], - "cmd-shift-right": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true + }, + { + "context": "Editor && mode == auto_height", + "bindings": { + "alt-enter": "editor::Newline", + "cmd-alt-enter": "editor::NewlineBelow" } - ], - "shift-end": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true + }, + { + "context": "BufferSearchBar > Editor", + "bindings": { + "escape": "buffer_search::Dismiss", + "tab": "buffer_search::FocusEditor", + "enter": "search::SelectNextMatch", + "shift-enter": "search::SelectPrevMatch" } - ], - "ctrl-shift-e": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true + }, + { + "context": "ProjectSearchBar > Editor", + "bindings": { + "escape": "project_search::ToggleFocus" } - ], - "ctrl-v": [ - "editor::MovePageDown", - { - "center_cursor": true + }, + { + "context": "ProjectSearchView > Editor", + "bindings": { + "escape": "project_search::ToggleFocus" } - ], - "alt-v": [ - "editor::MovePageUp", - { - "center_cursor": true + }, + { + "context": "Pane", + "bindings": { + "cmd-f": "project_search::ToggleFocus", + "cmd-g": "search::SelectNextMatch", + "cmd-shift-g": "search::SelectPrevMatch", + "alt-cmd-c": "search::ToggleCaseSensitive", + "alt-cmd-w": "search::ToggleWholeWord", + "alt-cmd-r": "search::ToggleRegex", + "shift-escape": "pane::ToggleZoom" } - ], - "ctrl-cmd-space": "editor::ShowCharacterPalette" - } - }, - { - "context": "Editor && mode == full", - "bindings": { - "enter": "editor::Newline", - "cmd-shift-enter": "editor::NewlineAbove", - "cmd-enter": "editor::NewlineBelow", - "alt-z": "editor::ToggleSoftWrap", - "cmd-f": [ - "buffer_search::Deploy", - { - "focus": true + }, + // Bindings from VS Code + { + "context": "Editor", + "bindings": { + "cmd-[": "editor::Outdent", + "cmd-]": "editor::Indent", + "cmd-alt-up": "editor::AddSelectionAbove", + "cmd-ctrl-p": "editor::AddSelectionAbove", + "cmd-alt-down": "editor::AddSelectionBelow", + "cmd-ctrl-n": "editor::AddSelectionBelow", + "cmd-d": [ + "editor::SelectNext", + { + "replace_newest": false + } + ], + "cmd-k cmd-d": [ + "editor::SelectNext", + { + "replace_newest": true + } + ], + "cmd-k cmd-i": "editor::Hover", + "cmd-/": [ + "editor::ToggleComments", + { + "advance_downwards": false + } + ], + "alt-up": "editor::SelectLargerSyntaxNode", + "alt-down": "editor::SelectSmallerSyntaxNode", + "cmd-u": "editor::UndoSelection", + "cmd-shift-u": "editor::RedoSelection", + "f8": "editor::GoToDiagnostic", + "shift-f8": "editor::GoToPrevDiagnostic", + "f2": "editor::Rename", + "f12": "editor::GoToDefinition", + "cmd-f12": "editor::GoToTypeDefinition", + "alt-shift-f12": "editor::FindAllReferences", + "ctrl-m": "editor::MoveToEnclosingBracket", + "alt-cmd-[": "editor::Fold", + "alt-cmd-]": "editor::UnfoldLines", + "ctrl-space": "editor::ShowCompletions", + "cmd-.": "editor::ToggleCodeActions", + "alt-cmd-r": "editor::RevealInFinder" } - ], - "cmd-e": [ - "buffer_search::Deploy", - { - "focus": false + }, + { + "context": "Editor && mode == full", + "bindings": { + "cmd-shift-o": "outline::Toggle", + "ctrl-g": "go_to_line::Toggle" } - ], - "alt-\\": "copilot::Suggest", - "alt-]": "copilot::NextSuggestion", - "alt-[": "copilot::PreviousSuggestion" - } - }, - { - "context": "Editor && mode == auto_height", - "bindings": { - "alt-enter": "editor::Newline", - "cmd-alt-enter": "editor::NewlineBelow" - } - }, - { - "context": "BufferSearchBar > Editor", - "bindings": { - "escape": "buffer_search::Dismiss", - "tab": "buffer_search::FocusEditor", - "enter": "search::SelectNextMatch", - "shift-enter": "search::SelectPrevMatch" - } - }, - { - "context": "ProjectSearchBar > Editor", - "bindings": { - "escape": "project_search::ToggleFocus" - } - }, - { - "context": "ProjectSearchView > Editor", - "bindings": { - "escape": "project_search::ToggleFocus" - } - }, - { - "context": "Pane", - "bindings": { - "cmd-f": "project_search::ToggleFocus", - "cmd-g": "search::SelectNextMatch", - "cmd-shift-g": "search::SelectPrevMatch", - "alt-cmd-c": "search::ToggleCaseSensitive", - "alt-cmd-w": "search::ToggleWholeWord", - "alt-cmd-r": "search::ToggleRegex" - } - }, - // Bindings from VS Code - { - "context": "Editor", - "bindings": { - "cmd-[": "editor::Outdent", - "cmd-]": "editor::Indent", - "cmd-alt-up": "editor::AddSelectionAbove", - "cmd-ctrl-p": "editor::AddSelectionAbove", - "cmd-alt-down": "editor::AddSelectionBelow", - "cmd-ctrl-n": "editor::AddSelectionBelow", - "cmd-d": [ - "editor::SelectNext", - { - "replace_newest": false + }, + { + "context": "Pane", + "bindings": { + "ctrl-1": [ + "pane::ActivateItem", + 0 + ], + "ctrl-2": [ + "pane::ActivateItem", + 1 + ], + "ctrl-3": [ + "pane::ActivateItem", + 2 + ], + "ctrl-4": [ + "pane::ActivateItem", + 3 + ], + "ctrl-5": [ + "pane::ActivateItem", + 4 + ], + "ctrl-6": [ + "pane::ActivateItem", + 5 + ], + "ctrl-7": [ + "pane::ActivateItem", + 6 + ], + "ctrl-8": [ + "pane::ActivateItem", + 7 + ], + "ctrl-9": [ + "pane::ActivateItem", + 8 + ], + "ctrl-0": "pane::ActivateLastItem", + "ctrl--": "pane::GoBack", + "ctrl-_": "pane::GoForward", + "cmd-shift-t": "pane::ReopenClosedItem", + "cmd-shift-f": "project_search::ToggleFocus" } - ], - "cmd-k cmd-d": [ - "editor::SelectNext", - { - "replace_newest": true + }, + { + "context": "Workspace", + "bindings": { + "cmd-1": [ + "workspace::ActivatePane", + 0 + ], + "cmd-2": [ + "workspace::ActivatePane", + 1 + ], + "cmd-3": [ + "workspace::ActivatePane", + 2 + ], + "cmd-4": [ + "workspace::ActivatePane", + 3 + ], + "cmd-5": [ + "workspace::ActivatePane", + 4 + ], + "cmd-6": [ + "workspace::ActivatePane", + 5 + ], + "cmd-7": [ + "workspace::ActivatePane", + 6 + ], + "cmd-8": [ + "workspace::ActivatePane", + 7 + ], + "cmd-9": [ + "workspace::ActivatePane", + 8 + ], + "cmd-b": "workspace::ToggleLeftDock", + "cmd-shift-f": "workspace::NewSearch", + "cmd-k cmd-t": "theme_selector::Toggle", + "cmd-k cmd-s": "zed::OpenKeymap", + "cmd-t": "project_symbols::Toggle", + "cmd-p": "file_finder::Toggle", + "cmd-shift-p": "command_palette::Toggle", + "cmd-shift-m": "diagnostics::Deploy", + "cmd-shift-e": "project_panel::ToggleFocus", + "cmd-alt-s": "workspace::SaveAll", + "cmd-k m": "language_selector::Toggle" } - ], - "cmd-k cmd-i": "editor::Hover", - "cmd-/": [ - "editor::ToggleComments", - { - "advance_downwards": false + }, + // Bindings from Sublime Text + { + "context": "Editor", + "bindings": { + "ctrl-shift-k": "editor::DeleteLine", + "cmd-shift-d": "editor::DuplicateLine", + "cmd-shift-l": "editor::SplitSelectionIntoLines", + "ctrl-cmd-up": "editor::MoveLineUp", + "ctrl-cmd-down": "editor::MoveLineDown", + "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart", + "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", + "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", + "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", + "ctrl-alt-left": "editor::MoveToPreviousSubwordStart", + "ctrl-alt-b": "editor::MoveToPreviousSubwordStart", + "ctrl-alt-right": "editor::MoveToNextSubwordEnd", + "ctrl-alt-f": "editor::MoveToNextSubwordEnd", + "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart", + "ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart", + "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd", + "ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd" + } + }, + { + "bindings": { + "cmd-k cmd-left": "workspace::ActivatePreviousPane", + "cmd-k cmd-right": "workspace::ActivateNextPane" + } + }, + // Bindings from Atom + { + "context": "Pane", + "bindings": { + "cmd-k up": "pane::SplitUp", + "cmd-k down": "pane::SplitDown", + "cmd-k left": "pane::SplitLeft", + "cmd-k right": "pane::SplitRight" + } + }, + // Bindings that should be unified with bindings for more general actions + { + "context": "Editor && renaming", + "bindings": { + "enter": "editor::ConfirmRename" + } + }, + { + "context": "Editor && showing_completions", + "bindings": { + "enter": "editor::ConfirmCompletion", + "tab": "editor::ConfirmCompletion" + } + }, + { + "context": "Editor && showing_code_actions", + "bindings": { + "enter": "editor::ConfirmCodeAction" + } + }, + // Custom bindings + { + "bindings": { + "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator", + "cmd-shift-c": "collab::ToggleContactsMenu", + "cmd-alt-i": "zed::DebugElements" + } + }, + { + "context": "Editor", + "bindings": { + "alt-enter": "editor::OpenExcerpts", + "cmd-f8": "editor::GoToHunk", + "cmd-shift-f8": "editor::GoToPrevHunk" + } + }, + { + "context": "ProjectSearchBar", + "bindings": { + "cmd-enter": "project_search::SearchInNew" + } + }, + { + "context": "ProjectPanel", + "bindings": { + "left": "project_panel::CollapseSelectedEntry", + "right": "project_panel::ExpandSelectedEntry", + "cmd-x": "project_panel::Cut", + "cmd-c": "project_panel::Copy", + "cmd-v": "project_panel::Paste", + "cmd-alt-c": "project_panel::CopyPath", + "alt-cmd-shift-c": "project_panel::CopyRelativePath", + "f2": "project_panel::Rename", + "backspace": "project_panel::Delete", + "alt-cmd-r": "project_panel::RevealInFinder" + } + }, + { + "context": "Terminal", + "bindings": { + "ctrl-cmd-space": "terminal::ShowCharacterPalette", + "cmd-c": "terminal::Copy", + "cmd-v": "terminal::Paste", + "cmd-k": "terminal::Clear", + // Some nice conveniences + "cmd-backspace": [ + "terminal::SendText", + "\u0015" + ], + "cmd-right": [ + "terminal::SendText", + "\u0005" + ], + "cmd-left": [ + "terminal::SendText", + "\u0001" + ], + // Terminal.app compatability + "alt-left": [ + "terminal::SendText", + "\u001bb" + ], + "alt-right": [ + "terminal::SendText", + "\u001bf" + ], + // There are conflicting bindings for these keys in the global context. + // these bindings override them, remove at your own risk: + "up": [ + "terminal::SendKeystroke", + "up" + ], + "pageup": [ + "terminal::SendKeystroke", + "pageup" + ], + "down": [ + "terminal::SendKeystroke", + "down" + ], + "pagedown": [ + "terminal::SendKeystroke", + "pagedown" + ], + "escape": [ + "terminal::SendKeystroke", + "escape" + ], + "enter": [ + "terminal::SendKeystroke", + "enter" + ], + "ctrl-c": [ + "terminal::SendKeystroke", + "ctrl-c" + ] } - ], - "alt-up": "editor::SelectLargerSyntaxNode", - "alt-down": "editor::SelectSmallerSyntaxNode", - "cmd-u": "editor::UndoSelection", - "cmd-shift-u": "editor::RedoSelection", - "f8": "editor::GoToDiagnostic", - "shift-f8": "editor::GoToPrevDiagnostic", - "f2": "editor::Rename", - "f12": "editor::GoToDefinition", - "cmd-f12": "editor::GoToTypeDefinition", - "alt-shift-f12": "editor::FindAllReferences", - "ctrl-m": "editor::MoveToEnclosingBracket", - "alt-cmd-[": "editor::Fold", - "alt-cmd-]": "editor::UnfoldLines", - "ctrl-space": "editor::ShowCompletions", - "cmd-.": "editor::ToggleCodeActions", - "alt-cmd-r": "editor::RevealInFinder" - } - }, - { - "context": "Editor && mode == full", - "bindings": { - "cmd-shift-o": "outline::Toggle", - "ctrl-g": "go_to_line::Toggle" - } - }, - { - "context": "Pane", - "bindings": { - "ctrl-1": [ - "pane::ActivateItem", - 0 - ], - "ctrl-2": [ - "pane::ActivateItem", - 1 - ], - "ctrl-3": [ - "pane::ActivateItem", - 2 - ], - "ctrl-4": [ - "pane::ActivateItem", - 3 - ], - "ctrl-5": [ - "pane::ActivateItem", - 4 - ], - "ctrl-6": [ - "pane::ActivateItem", - 5 - ], - "ctrl-7": [ - "pane::ActivateItem", - 6 - ], - "ctrl-8": [ - "pane::ActivateItem", - 7 - ], - "ctrl-9": [ - "pane::ActivateItem", - 8 - ], - "ctrl-0": "pane::ActivateLastItem", - "ctrl--": "pane::GoBack", - "ctrl-_": "pane::GoForward", - "cmd-shift-t": "pane::ReopenClosedItem", - "cmd-shift-f": "project_search::ToggleFocus" - } - }, - { - "context": "Workspace", - "bindings": { - "cmd-1": [ - "workspace::ActivatePane", - 0 - ], - "cmd-2": [ - "workspace::ActivatePane", - 1 - ], - "cmd-3": [ - "workspace::ActivatePane", - 2 - ], - "cmd-4": [ - "workspace::ActivatePane", - 3 - ], - "cmd-5": [ - "workspace::ActivatePane", - 4 - ], - "cmd-6": [ - "workspace::ActivatePane", - 5 - ], - "cmd-7": [ - "workspace::ActivatePane", - 6 - ], - "cmd-8": [ - "workspace::ActivatePane", - 7 - ], - "cmd-9": [ - "workspace::ActivatePane", - 8 - ], - "cmd-b": "workspace::ToggleLeftDock", - "cmd-shift-f": "workspace::NewSearch", - "cmd-k cmd-t": "theme_selector::Toggle", - "cmd-k cmd-s": "zed::OpenKeymap", - "cmd-t": "project_symbols::Toggle", - "cmd-p": "file_finder::Toggle", - "cmd-shift-p": "command_palette::Toggle", - "cmd-shift-m": "diagnostics::Deploy", - "cmd-shift-e": "project_panel::ToggleFocus", - "cmd-alt-s": "workspace::SaveAll", - "cmd-k m": "language_selector::Toggle" - } - }, - // Bindings from Sublime Text - { - "context": "Editor", - "bindings": { - "ctrl-shift-k": "editor::DeleteLine", - "cmd-shift-d": "editor::DuplicateLine", - "cmd-shift-l": "editor::SplitSelectionIntoLines", - "ctrl-cmd-up": "editor::MoveLineUp", - "ctrl-cmd-down": "editor::MoveLineDown", - "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart", - "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", - "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", - "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", - "ctrl-alt-left": "editor::MoveToPreviousSubwordStart", - "ctrl-alt-b": "editor::MoveToPreviousSubwordStart", - "ctrl-alt-right": "editor::MoveToNextSubwordEnd", - "ctrl-alt-f": "editor::MoveToNextSubwordEnd", - "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart", - "ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart", - "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd", - "ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd" - } - }, - { - "bindings": { - "cmd-k cmd-left": "workspace::ActivatePreviousPane", - "cmd-k cmd-right": "workspace::ActivateNextPane" - } - }, - // Bindings from Atom - { - "context": "Pane", - "bindings": { - "cmd-k up": "pane::SplitUp", - "cmd-k down": "pane::SplitDown", - "cmd-k left": "pane::SplitLeft", - "cmd-k right": "pane::SplitRight" - } - }, - // Bindings that should be unified with bindings for more general actions - { - "context": "Editor && renaming", - "bindings": { - "enter": "editor::ConfirmRename" - } - }, - { - "context": "Editor && showing_completions", - "bindings": { - "enter": "editor::ConfirmCompletion", - "tab": "editor::ConfirmCompletion" - } - }, - { - "context": "Editor && showing_code_actions", - "bindings": { - "enter": "editor::ConfirmCodeAction" - } - }, - // Custom bindings - { - "bindings": { - "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator", - "cmd-shift-c": "collab::ToggleContactsMenu", - "cmd-alt-i": "zed::DebugElements" - } - }, - { - "context": "Editor", - "bindings": { - "alt-enter": "editor::OpenExcerpts", - "cmd-f8": "editor::GoToHunk", - "cmd-shift-f8": "editor::GoToPrevHunk" - } - }, - { - "context": "ProjectSearchBar", - "bindings": { - "cmd-enter": "project_search::SearchInNew" - } - }, - { - "context": "ProjectPanel", - "bindings": { - "left": "project_panel::CollapseSelectedEntry", - "right": "project_panel::ExpandSelectedEntry", - "cmd-x": "project_panel::Cut", - "cmd-c": "project_panel::Copy", - "cmd-v": "project_panel::Paste", - "cmd-alt-c": "project_panel::CopyPath", - "alt-cmd-shift-c": "project_panel::CopyRelativePath", - "f2": "project_panel::Rename", - "backspace": "project_panel::Delete", - "alt-cmd-r": "project_panel::RevealInFinder" - } - }, - { - "context": "Terminal", - "bindings": { - "ctrl-cmd-space": "terminal::ShowCharacterPalette", - "cmd-c": "terminal::Copy", - "cmd-v": "terminal::Paste", - "cmd-k": "terminal::Clear", - // Some nice conveniences - "cmd-backspace": [ - "terminal::SendText", - "\u0015" - ], - "cmd-right": [ - "terminal::SendText", - "\u0005" - ], - "cmd-left": [ - "terminal::SendText", - "\u0001" - ], - // Terminal.app compatability - "alt-left": [ - "terminal::SendText", - "\u001bb" - ], - "alt-right": [ - "terminal::SendText", - "\u001bf" - ], - // There are conflicting bindings for these keys in the global context. - // these bindings override them, remove at your own risk: - "up": [ - "terminal::SendKeystroke", - "up" - ], - "pageup": [ - "terminal::SendKeystroke", - "pageup" - ], - "down": [ - "terminal::SendKeystroke", - "down" - ], - "pagedown": [ - "terminal::SendKeystroke", - "pagedown" - ], - "escape": [ - "terminal::SendKeystroke", - "escape" - ], - "enter": [ - "terminal::SendKeystroke", - "enter" - ], - "ctrl-c": [ - "terminal::SendKeystroke", - "ctrl-c" - ] } - } ] diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 488f55d0c6e91f1d0b19b75c7acf4f1f79948331..65418dd99794acc754025b1a0c736ae9b878ecab 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3816,6 +3816,12 @@ impl PartialEq for ViewHandle { } } +impl PartialEq for ViewHandle { + fn eq(&self, other: &AnyViewHandle) -> bool { + self.window_id == other.window_id && self.view_id == other.view_id + } +} + impl PartialEq> for ViewHandle { fn eq(&self, other: &WeakViewHandle) -> bool { self.window_id == other.window_id && self.view_id == other.view_id diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index f593615ae74db2c5fb1e1d5045978a9b9dc08d9f..40b6a50816d155d0a4a2839e70b863306979bdd4 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -33,8 +33,11 @@ use crate::{ rect::RectF, vector::{vec2f, Vector2F}, }, - json, Action, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle, - WindowContext, + json, + platform::MouseButton, + scene::MouseDown, + Action, EventContext, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, + WeakViewHandle, WindowContext, }; use anyhow::{anyhow, Result}; use collections::HashMap; @@ -198,6 +201,13 @@ pub trait Element: 'static { { Resizable::new(self.into_any(), side, size, on_resize) } + + fn mouse(self, region_id: usize) -> MouseEventHandler + where + Self: Sized, + { + MouseEventHandler::for_child(self.into_any(), region_id) + } } trait AnyElementState { diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index ed624922d57362d0040a1d7e28a54ae9b3f28e9c..6f2762db66144f1099bd05cee01be43f399b0141 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -32,10 +32,25 @@ pub struct MouseEventHandler { /// Element which provides a render_child callback with a MouseState and paints a mouse /// region under (or above) it for easy mouse event handling. impl MouseEventHandler { - pub fn new(region_id: usize, cx: &mut ViewContext, render_child: F) -> Self + pub fn for_child(child: impl Element, region_id: usize) -> Self { + Self { + child: child.into_any(), + region_id, + cursor_style: None, + handlers: Default::default(), + notify_on_hover: false, + notify_on_click: false, + hoverable: false, + above: false, + padding: Default::default(), + _tag: PhantomData, + } + } + + pub fn new(region_id: usize, cx: &mut ViewContext, render_child: F) -> Self where - D: Element, - F: FnOnce(&mut MouseState, &mut ViewContext) -> D, + E: Element, + F: FnOnce(&mut MouseState, &mut ViewContext) -> E, { let mut mouse_state = cx.mouse_state::(region_id); let child = render_child(&mut mouse_state, cx).into_any(); diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 29bb51be684ef52be337fd06bb64a37cfe321684..50f4607d6669f97c43c57c698b42379e47008a4a 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -68,6 +68,8 @@ pub struct Workspace { pub breadcrumbs: Interactive, pub disconnected_overlay: ContainedText, pub modal: ContainerStyle, + pub zoomed_foreground: ContainerStyle, + pub zoomed_background: ContainerStyle, pub notification: ContainerStyle, pub notifications: Notifications, pub joining_project_avatar: ImageStyle, diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 30e782c7501ee19d80a92931c3300fa4b9dc529c..a216f6181cc734e1029503ffbe6c20590a21ce0a 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -69,6 +69,7 @@ actions!( SplitUp, SplitRight, SplitDown, + ToggleZoom, ] ); @@ -91,6 +92,7 @@ const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; pub type BackgroundActions = fn() -> &'static [(&'static str, &'static dyn Action)]; pub fn init(cx: &mut AppContext) { + cx.add_action(Pane::toggle_zoom); cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| { pane.activate_item(action.0, true, true, cx); }); @@ -132,12 +134,15 @@ pub enum Event { Split(SplitDirection), ChangeItemTitle, Focus, + ZoomIn, + ZoomOut, } pub struct Pane { items: Vec>, activation_history: Vec, is_active: bool, + zoomed: bool, active_item_index: usize, last_focused_view_by_item: HashMap, autoscroll: bool, @@ -236,6 +241,7 @@ impl Pane { items: Vec::new(), activation_history: Vec::new(), is_active: true, + zoomed: false, active_item_index: 0, last_focused_view_by_item: Default::default(), autoscroll: false, @@ -655,6 +661,14 @@ impl Pane { self.items.iter().position(|i| i.id() == item.id()) } + pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { + if self.zoomed { + cx.emit(Event::ZoomOut); + } else { + cx.emit(Event::ZoomIn); + } + } + pub fn activate_item( &mut self, index: usize, @@ -1546,6 +1560,15 @@ impl Pane { .with_background_color(background) .into_any() } + + pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { + self.zoomed = zoomed; + cx.notify(); + } + + pub fn is_zoomed(&self) -> bool { + self.zoomed + } } impl Entity for Pane { diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 55032b4bc1e79bf40e75c5be5a0a4b7e47f3e836..a1b35c1d65c58695cf2904729bc430898f8a9e5e 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -142,6 +142,12 @@ impl Member { match self { Member::Pane(pane) => { + let pane_element = if pane.read(cx).is_zoomed() { + Empty::new().into_any() + } else { + ChildView::new(pane, cx).into_any() + }; + let leader = follower_states .iter() .find_map(|(leader_id, follower_states)| { @@ -258,7 +264,7 @@ impl Member { }; Stack::new() - .with_child(ChildView::new(pane, cx).contained().with_border(border)) + .with_child(pane_element.contained().with_border(border)) .with_children(leader_status_box) .into_any() } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ad717c362b0cdc1b4a973390655d1806ee9c5738..f4ff8d5c917df637c56665a8e60100bd9437add5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -441,6 +441,7 @@ pub struct Workspace { weak_self: WeakViewHandle, remote_entity_subscription: Option, modal: Option, + zoomed: Option, center: PaneGroup, left_dock: ViewHandle, bottom_dock: ViewHandle, @@ -627,8 +628,9 @@ impl Workspace { ]; let mut this = Workspace { - modal: None, weak_self: weak_handle.clone(), + modal: None, + zoomed: None, center: PaneGroup::new(center_pane.clone()), panes: vec![center_pane.clone()], panes_by_item: Default::default(), @@ -1303,6 +1305,16 @@ impl Workspace { } } + pub fn zoom_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext) { + self.zoomed = Some(view); + cx.notify(); + } + + pub fn zoom_out(&mut self, cx: &mut ViewContext) { + self.zoomed.take(); + cx.notify(); + } + pub fn items<'a>( &'a self, cx: &'a AppContext, @@ -1685,6 +1697,16 @@ impl Workspace { pane::Event::Focus => { self.handle_pane_focused(pane.clone(), cx); } + pane::Event::ZoomIn => { + pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); + self.zoom_in(pane.into_any(), cx); + } + pane::Event::ZoomOut => { + if self.zoomed.as_ref().map_or(false, |zoomed| *zoomed == pane) { + pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); + self.zoom_out(cx); + } + } } self.serialize_workspace(cx); @@ -2735,6 +2757,21 @@ impl View for Workspace { }) .with_child(Overlay::new( Stack::new() + .with_children(self.zoomed.as_ref().map(|zoomed| { + enum ZoomBackground {} + + ChildView::new(zoomed, cx) + .contained() + .with_style(theme.workspace.zoomed_foreground) + .aligned() + .contained() + .with_style(theme.workspace.zoomed_background) + .mouse::(0) + .capture_all() + .on_down(MouseButton::Left, |_, this: &mut Self, cx| { + this.zoom_out(cx); + }) + })) .with_children(self.modal.as_ref().map(|modal| { ChildView::new(modal, cx) .contained() diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index fe359b0501b1549c22671e5513dcdf428ae71322..501debc876200e47edea06b44b5d70f342671135 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -118,6 +118,15 @@ export default function workspace(colorScheme: ColorScheme) { }, cursor: "Arrow", }, + zoomedBackground: { + padding: 10, + cursor: "Arrow", + background: withOpacity(background(colorScheme.lowest), 0.5) + }, + zoomedForeground: { + shadow: colorScheme.modalShadow, + border: border(colorScheme.highest, { overlay: true }), + }, dock: { initialSize: 240, border: border(layer, { left: true, right: true }), From c03e470fe6e0ef750f784d757ea31ddf689c3d05 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 May 2023 17:10:30 +0200 Subject: [PATCH 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 e271c9970b7ba9f29772274e29bed58b6127b1ff..b1f0740df08c5f5be602c314a1445e1715df8fb2 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1373,6 +1373,10 @@ impl workspace::dock::Panel for ProjectPanel { cx.global::().project_panel.default_width } + fn can_zoom(&self, _cx: &gpui::WindowContext) -> bool { + false + } + fn icon_path(&self) -> &'static str { "icons/folder_tree_16.svg" } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 995c8be50b273d4337be3a75453ae39f8b6d78be..f3bfbdd73061314ce63a041a8a860e1423aa8539 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1,7 +1,7 @@ use crate::TerminalView; use gpui::{ elements::*, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle, - WeakViewHandle, + WeakViewHandle, WindowContext, }; use project::Project; use settings::{settings_file::SettingsFile, Settings, TerminalDockPosition, WorkingDirectory}; @@ -149,7 +149,7 @@ impl View for TerminalPanel { } impl Panel for TerminalPanel { - fn position(&self, cx: &gpui::WindowContext) -> DockPosition { + fn position(&self, cx: &WindowContext) -> DockPosition { let settings = cx.global::(); let dock = settings .terminal_overrides @@ -179,7 +179,7 @@ impl Panel for TerminalPanel { }); } - fn default_size(&self, cx: &gpui::WindowContext) -> f32 { + fn default_size(&self, cx: &WindowContext) -> f32 { let settings = &cx.global::().terminal_overrides; match self.position(cx) { DockPosition::Left | DockPosition::Right => settings.default_width.unwrap_or(640.), @@ -187,6 +187,10 @@ impl Panel for TerminalPanel { } } + fn can_zoom(&self, _: &WindowContext) -> bool { + true + } + fn icon_path(&self) -> &'static str { "icons/terminal_12.svg" } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 86c2cc392932f24a5de77ed671ce11c827f2bbba..6d0b34dc27983cd746c5b2e4a3a27b6f5e55e535 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -9,11 +9,16 @@ use serde::Deserialize; use settings::Settings; use std::rc::Rc; +pub fn init(cx: &mut AppContext) { + cx.capture_action(Dock::toggle_zoom); +} + pub trait Panel: View { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); fn default_size(&self, cx: &WindowContext) -> f32; + fn can_zoom(&self, cx: &WindowContext) -> bool; fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; fn icon_label(&self, _: &AppContext) -> Option { @@ -30,6 +35,7 @@ pub trait PanelHandle { fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; fn set_position(&self, position: DockPosition, cx: &mut WindowContext); fn default_size(&self, cx: &WindowContext) -> f32; + fn can_zoom(&self, cx: &WindowContext) -> bool; fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -61,6 +67,10 @@ where self.read(cx).default_size(cx) } + fn can_zoom(&self, cx: &WindowContext) -> bool { + self.read(cx).can_zoom(cx) + } + fn icon_path(&self, cx: &WindowContext) -> &'static str { self.read(cx).icon_path() } @@ -313,9 +323,7 @@ impl View for Dock { .resizable( self.position.to_resize_handle_side(), size, - |dock: &mut Self, size, cx| { - dock.resize_active_panel(size, cx); - }, + |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), ) .into_any() } else { @@ -526,6 +534,10 @@ pub(crate) mod test { } } + fn can_zoom(&self, _cx: &WindowContext) -> bool { + unimplemented!() + } + fn icon_path(&self) -> &'static str { "icons/test_panel.svg" } From adf361b374b7a293881795aa24093bac8aae5751 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 16 May 2023 11:49:48 +0200 Subject: [PATCH 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 768e0c6ed1155fd6de2e131031954e4aacbdd5bd..37c010f0f93c78aae7a3cf3ec6c71f388f52d9bd 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -220,7 +220,7 @@ "alt-cmd-c": "search::ToggleCaseSensitive", "alt-cmd-w": "search::ToggleWholeWord", "alt-cmd-r": "search::ToggleRegex", - "shift-escape": "pane::ToggleZoom" + "shift-escape": "workspace::ToggleZoom" } }, // Bindings from VS Code diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index 40b6a50816d155d0a4a2839e70b863306979bdd4..a566751fd5de5cb2e0b842dee466dd55c93c708d 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -33,11 +33,8 @@ use crate::{ rect::RectF, vector::{vec2f, Vector2F}, }, - json, - platform::MouseButton, - scene::MouseDown, - Action, EventContext, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, - WeakViewHandle, WindowContext, + json, Action, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle, + WindowContext, }; use anyhow::{anyhow, Result}; use collections::HashMap; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 6d0b34dc27983cd746c5b2e4a3a27b6f5e55e535..2e4303e70fcead396e4da224e1ee711cc68d1008 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,4 +1,4 @@ -use crate::{StatusItemView, Workspace}; +use crate::{StatusItemView, ToggleZoom, Workspace}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, @@ -10,7 +10,7 @@ use settings::Settings; use std::rc::Rc; pub fn init(cx: &mut AppContext) { - cx.capture_action(Dock::toggle_zoom); + cx.add_action(Dock::toggle_zoom); } pub trait Panel: View { @@ -98,6 +98,10 @@ impl From<&dyn PanelHandle> for AnyViewHandle { } } +pub enum Event { + ZoomIn, +} + pub struct Dock { position: DockPosition, panel_entries: Vec, @@ -141,6 +145,7 @@ struct PanelEntry { panel: Rc, size: f32, context_menu: ViewHandle, + zoomed: bool, _subscriptions: [Subscription; 2], } @@ -187,6 +192,25 @@ impl Dock { cx.notify(); } + pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { + for (ix, entry) in self.panel_entries.iter_mut().enumerate() { + if ix == self.active_panel_index && entry.panel.can_zoom(cx) { + entry.zoomed = zoomed; + } else { + entry.zoomed = false; + } + } + + cx.notify(); + } + + pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { + cx.propagate_action(); + if !self.active_entry().map_or(false, |entry| entry.zoomed) { + cx.emit(Event::ZoomIn); + } + } + pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) { let subscriptions = [ cx.observe(&panel, |_, _, cx| cx.notify()), @@ -214,6 +238,7 @@ impl Dock { self.panel_entries.push(PanelEntry { panel: Rc::new(panel), size, + zoomed: false, context_menu: cx.add_view(|cx| { let mut menu = ContextMenu::new(dock_view_id, cx); menu.set_position_mode(OverlayPositionMode::Local); @@ -260,10 +285,22 @@ impl Dock { } pub fn active_panel(&self) -> Option<&Rc> { + let entry = self.active_entry()?; + Some(&entry.panel) + } + + fn active_entry(&self) -> Option<&PanelEntry> { if self.is_open { - self.panel_entries - .get(self.active_panel_index) - .map(|entry| &entry.panel) + self.panel_entries.get(self.active_panel_index) + } else { + None + } + } + + pub fn zoomed_panel(&self) -> Option { + let entry = self.active_entry()?; + if entry.zoomed { + Some(entry.panel.as_any().clone()) } else { None } @@ -305,7 +342,7 @@ impl Dock { } impl Entity for Dock { - type Event = (); + type Event = Event; } impl View for Dock { @@ -314,18 +351,22 @@ impl View for Dock { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - if let Some(active_panel) = self.active_panel() { - let size = self.active_panel_size().unwrap(); - let style = &cx.global::().theme.workspace.dock; - ChildView::new(active_panel.as_any(), cx) - .contained() - .with_style(style.container) - .resizable( - self.position.to_resize_handle_side(), - size, - |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), - ) - .into_any() + if let Some(active_entry) = self.active_entry() { + if active_entry.zoomed { + Empty::new().into_any() + } else { + let size = self.active_panel_size().unwrap(); + let style = &cx.global::().theme.workspace.dock; + ChildView::new(active_entry.panel.as_any(), cx) + .contained() + .with_style(style.container) + .resizable( + self.position.to_resize_handle_side(), + size, + |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), + ) + .into_any() + } } else { Empty::new().into_any() } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index a216f6181cc734e1029503ffbe6c20590a21ce0a..6ccd1573bc3c53d9efd643b2a7636b59eb8557ab 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2,7 +2,8 @@ mod dragged_item_receiver; use super::{ItemHandle, SplitDirection}; use crate::{ - item::WeakItemHandle, toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, Workspace, + item::WeakItemHandle, toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, ToggleZoom, + Workspace, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -69,7 +70,6 @@ actions!( SplitUp, SplitRight, SplitDown, - ToggleZoom, ] ); @@ -135,7 +135,6 @@ pub enum Event { ChangeItemTitle, Focus, ZoomIn, - ZoomOut, } pub struct Pane { @@ -662,9 +661,8 @@ impl Pane { } pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { - if self.zoomed { - cx.emit(Event::ZoomOut); - } else { + cx.propagate_action(); + if !self.zoomed { cx.emit(Event::ZoomIn); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f4ff8d5c917df637c56665a8e60100bd9437add5..af8c958db586c948ad99e8223814fd8bc3ecdc85 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -119,7 +119,8 @@ actions!( NewSearch, Feedback, Restart, - Welcome + Welcome, + ToggleZoom, ] ); @@ -181,6 +182,7 @@ pub type WorkspaceId = i64; impl_actions!(workspace, [ActivatePane]); pub fn init(app_state: Arc, cx: &mut AppContext) { + dock::init(cx); pane::init(cx); notifications::init(cx); @@ -230,6 +232,7 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { }, ); cx.add_action(Workspace::toggle_panel); + cx.add_action(Workspace::toggle_zoom); cx.add_action(Workspace::focus_center); cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| { workspace.activate_previous_pane(cx) @@ -441,7 +444,6 @@ pub struct Workspace { weak_self: WeakViewHandle, remote_entity_subscription: Option, modal: Option, - zoomed: Option, center: PaneGroup, left_dock: ViewHandle, bottom_dock: ViewHandle, @@ -593,7 +595,7 @@ impl Workspace { active_call = Some((call, subscriptions)); } - let subscriptions = vec![ + let mut subscriptions = vec![ cx.observe_fullscreen(|_, _, cx| cx.notify()), cx.observe_window_activation(Self::on_window_activation_changed), cx.observe_window_bounds(move |_, mut bounds, display, cx| { @@ -613,24 +615,14 @@ impl Workspace { .spawn(DB.set_window_bounds(workspace_id, bounds, display)) .detach_and_log_err(cx); }), - cx.observe(&left_dock, |this, _, cx| { - this.serialize_workspace(cx); - cx.notify(); - }), - cx.observe(&bottom_dock, |this, _, cx| { - this.serialize_workspace(cx); - cx.notify(); - }), - cx.observe(&right_dock, |this, _, cx| { - this.serialize_workspace(cx); - cx.notify(); - }), ]; + subscriptions.extend(Self::register_dock(&left_dock, cx)); + subscriptions.extend(Self::register_dock(&bottom_dock, cx)); + subscriptions.extend(Self::register_dock(&right_dock, cx)); let mut this = Workspace { weak_self: weak_handle.clone(), modal: None, - zoomed: None, center: PaneGroup::new(center_pane.clone()), panes: vec![center_pane.clone()], panes_by_item: Default::default(), @@ -1305,14 +1297,16 @@ impl Workspace { } } - pub fn zoom_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext) { - self.zoomed = Some(view); - cx.notify(); - } - - pub fn zoom_out(&mut self, cx: &mut ViewContext) { - self.zoomed.take(); - cx.notify(); + fn zoomed(&self, cx: &AppContext) -> Option { + self.left_dock + .read(cx) + .zoomed_panel() + .or(self.bottom_dock.read(cx).zoomed_panel()) + .or(self.right_dock.read(cx).zoomed_panel()) + .or_else(|| { + let pane = self.panes.iter().find(|pane| pane.read(cx).is_zoomed())?; + Some(pane.clone().into_any()) + }) } pub fn items<'a>( @@ -1470,11 +1464,46 @@ impl Workspace { cx.notify(); } + fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { + // Any time the zoom is toggled we will zoom out all panes and docks. Then, + // the dock or pane that was zoomed will emit an event to zoom itself back in. + self.zoom_out(cx); + } + + fn zoom_out(&mut self, cx: &mut ViewContext) { + for pane in &self.panes { + pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); + } + + self.left_dock + .update(cx, |dock, cx| dock.set_zoomed(false, cx)); + self.bottom_dock + .update(cx, |dock, cx| dock.set_zoomed(false, cx)); + self.right_dock + .update(cx, |dock, cx| dock.set_zoomed(false, cx)); + + cx.notify(); + } + pub fn focus_center(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { cx.focus_self(); cx.notify(); } + fn register_dock(dock: &ViewHandle, cx: &mut ViewContext) -> [Subscription; 2] { + [ + cx.observe(dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), + cx.subscribe(dock, |_, dock, event, cx| { + dock.update(cx, |dock, cx| match event { + dock::Event::ZoomIn => dock.set_zoomed(true, cx), + }) + }), + ] + } + fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { let pane = cx.add_view(|cx| Pane::new(self.weak_handle(), self.app_state.background_actions, cx)); @@ -1699,13 +1728,7 @@ impl Workspace { } pane::Event::ZoomIn => { pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); - self.zoom_in(pane.into_any(), cx); - } - pane::Event::ZoomOut => { - if self.zoomed.as_ref().map_or(false, |zoomed| *zoomed == pane) { - pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); - self.zoom_out(cx); - } + cx.notify(); } } @@ -2757,10 +2780,10 @@ impl View for Workspace { }) .with_child(Overlay::new( Stack::new() - .with_children(self.zoomed.as_ref().map(|zoomed| { + .with_children(self.zoomed(cx).map(|zoomed| { enum ZoomBackground {} - ChildView::new(zoomed, cx) + ChildView::new(&zoomed, cx) .contained() .with_style(theme.workspace.zoomed_foreground) .aligned() From f87ae6032e6eb0d11fcf039b64aa9b6c729000b4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 16 May 2023 18:01:18 +0200 Subject: [PATCH 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 65418dd99794acc754025b1a0c736ae9b878ecab..9cc1ad908a75af1c9a4709e0864417d188895aec 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3972,6 +3972,12 @@ impl Clone for AnyViewHandle { } } +impl PartialEq for AnyViewHandle { + fn eq(&self, other: &Self) -> bool { + self.window_id == other.window_id && self.view_id == other.view_id + } +} + impl PartialEq> for AnyViewHandle { fn eq(&self, other: &ViewHandle) -> bool { self.window_id == other.window_id && self.view_id == other.view_id diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index b1f0740df08c5f5be602c314a1445e1715df8fb2..c448ac4e43e577acc2cf3820de0461f5ac3ed98a 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1373,10 +1373,16 @@ impl workspace::dock::Panel for ProjectPanel { cx.global::().project_panel.default_width } - fn can_zoom(&self, _cx: &gpui::WindowContext) -> bool { + fn should_zoom_in_on_event(_: &Self::Event) -> bool { false } + fn should_zoom_out_on_event(_: &Self::Event) -> bool { + false + } + + fn set_zoomed(&mut self, _: bool, _: &mut ViewContext) {} + fn icon_path(&self) -> &'static str { "icons/folder_tree_16.svg" } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index f3bfbdd73061314ce63a041a8a860e1423aa8539..68783a5bc189e3fc05ad0a14cbad3a54bda8b56e 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -18,6 +18,8 @@ pub fn init(cx: &mut AppContext) { pub enum Event { Close, DockPositionChanged, + ZoomIn, + ZoomOut, } pub struct TerminalPanel { @@ -96,6 +98,8 @@ impl TerminalPanel { ) { match event { pane::Event::Remove => cx.emit(Event::Close), + pane::Event::ZoomIn => cx.emit(Event::ZoomIn), + pane::Event::ZoomOut => cx.emit(Event::ZoomOut), _ => {} } } @@ -187,8 +191,16 @@ impl Panel for TerminalPanel { } } - fn can_zoom(&self, _: &WindowContext) -> bool { - true + fn should_zoom_in_on_event(event: &Event) -> bool { + matches!(event, Event::ZoomIn) + } + + fn should_zoom_out_on_event(event: &Event) -> bool { + matches!(event, Event::ZoomOut) + } + + fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { + self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx)); } fn icon_path(&self) -> &'static str { diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 2e4303e70fcead396e4da224e1ee711cc68d1008..40e7288de0ae8431eacd5f9220b5a2c3820b4ac8 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,4 +1,4 @@ -use crate::{StatusItemView, ToggleZoom, Workspace}; +use crate::{StatusItemView, Workspace}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, @@ -9,22 +9,20 @@ use serde::Deserialize; use settings::Settings; use std::rc::Rc; -pub fn init(cx: &mut AppContext) { - cx.add_action(Dock::toggle_zoom); -} - pub trait Panel: View { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); fn default_size(&self, cx: &WindowContext) -> f32; - fn can_zoom(&self, cx: &WindowContext) -> bool; fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; fn icon_label(&self, _: &AppContext) -> Option { None } fn should_change_position_on_event(_: &Self::Event) -> bool; + fn should_zoom_in_on_event(_: &Self::Event) -> bool; + fn should_zoom_out_on_event(_: &Self::Event) -> bool; + fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext); fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; } @@ -34,8 +32,8 @@ pub trait PanelHandle { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; fn set_position(&self, position: DockPosition, cx: &mut WindowContext); + fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext); fn default_size(&self, cx: &WindowContext) -> f32; - fn can_zoom(&self, cx: &WindowContext) -> bool; fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -67,8 +65,8 @@ where self.read(cx).default_size(cx) } - fn can_zoom(&self, cx: &WindowContext) -> bool { - self.read(cx).can_zoom(cx) + fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.set_zoomed(zoomed, cx)) } fn icon_path(&self, cx: &WindowContext) -> &'static str { @@ -98,10 +96,6 @@ impl From<&dyn PanelHandle> for AnyViewHandle { } } -pub enum Event { - ZoomIn, -} - pub struct Dock { position: DockPosition, panel_entries: Vec, @@ -192,22 +186,33 @@ impl Dock { cx.notify(); } - pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { - for (ix, entry) in self.panel_entries.iter_mut().enumerate() { - if ix == self.active_panel_index && entry.panel.can_zoom(cx) { - entry.zoomed = zoomed; - } else { + pub fn set_panel_zoomed( + &mut self, + panel: &AnyViewHandle, + zoomed: bool, + cx: &mut ViewContext, + ) { + for entry in &mut self.panel_entries { + if entry.panel.as_any() == panel { + if zoomed != entry.zoomed { + entry.zoomed = zoomed; + entry.panel.set_zoomed(zoomed, cx); + } + } else if entry.zoomed { entry.zoomed = false; + entry.panel.set_zoomed(false, cx); } } cx.notify(); } - pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { - cx.propagate_action(); - if !self.active_entry().map_or(false, |entry| entry.zoomed) { - cx.emit(Event::ZoomIn); + pub fn zoom_out(&mut self, cx: &mut ViewContext) { + for entry in &mut self.panel_entries { + if entry.zoomed { + entry.zoomed = false; + entry.panel.set_zoomed(false, cx); + } } } @@ -342,7 +347,7 @@ impl Dock { } impl Entity for Dock { - type Event = Event; + type Event = (); } impl View for Dock { @@ -568,6 +573,10 @@ pub(crate) mod test { cx.emit(TestPanelEvent::PositionChanged); } + fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext) { + unimplemented!() + } + fn default_size(&self, _: &WindowContext) -> f32 { match self.position.axis() { Axis::Horizontal => 300., @@ -575,10 +584,6 @@ pub(crate) mod test { } } - fn can_zoom(&self, _cx: &WindowContext) -> bool { - unimplemented!() - } - fn icon_path(&self) -> &'static str { "icons/test_panel.svg" } @@ -591,6 +596,14 @@ pub(crate) mod test { matches!(event, TestPanelEvent::PositionChanged) } + fn should_zoom_in_on_event(_: &Self::Event) -> bool { + false + } + + fn should_zoom_out_on_event(_: &Self::Event) -> bool { + false + } + fn should_activate_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool { matches!(event, TestPanelEvent::Activated) } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 6ccd1573bc3c53d9efd643b2a7636b59eb8557ab..73738ab41322215e3d19f59d3baa7d3808115ef1 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -135,6 +135,7 @@ pub enum Event { ChangeItemTitle, Focus, ZoomIn, + ZoomOut, } pub struct Pane { @@ -661,8 +662,9 @@ impl Pane { } pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { - cx.propagate_action(); - if !self.zoomed { + if self.zoomed { + cx.emit(Event::ZoomOut); + } else { cx.emit(Event::ZoomIn); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index af8c958db586c948ad99e8223814fd8bc3ecdc85..06832e13859a7623b90bea3690af25b996d83683 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -182,7 +182,6 @@ pub type WorkspaceId = i64; impl_actions!(workspace, [ActivatePane]); pub fn init(app_state: Arc, cx: &mut AppContext) { - dock::init(cx); pane::init(cx); notifications::init(cx); @@ -232,7 +231,6 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { }, ); cx.add_action(Workspace::toggle_panel); - cx.add_action(Workspace::toggle_zoom); cx.add_action(Workspace::focus_center); cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| { workspace.activate_previous_pane(cx) @@ -595,7 +593,7 @@ impl Workspace { active_call = Some((call, subscriptions)); } - let mut subscriptions = vec![ + let subscriptions = vec![ cx.observe_fullscreen(|_, _, cx| cx.notify()), cx.observe_window_activation(Self::on_window_activation_changed), cx.observe_window_bounds(move |_, mut bounds, display, cx| { @@ -615,10 +613,19 @@ impl Workspace { .spawn(DB.set_window_bounds(workspace_id, bounds, display)) .detach_and_log_err(cx); }), + cx.observe(&left_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), + cx.observe(&bottom_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), + cx.observe(&right_dock, |this, _, cx| { + this.serialize_workspace(cx); + cx.notify(); + }), ]; - subscriptions.extend(Self::register_dock(&left_dock, cx)); - subscriptions.extend(Self::register_dock(&bottom_dock, cx)); - subscriptions.extend(Self::register_dock(&right_dock, cx)); let mut this = Workspace { weak_self: weak_handle.clone(), @@ -881,6 +888,11 @@ impl Workspace { dock.activate_panel(dock.panels_len() - 1, cx); } }); + } else if T::should_zoom_in_on_event(event) { + this.zoom_out(cx); + dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx)); + } else if T::should_zoom_out_on_event(event) { + this.zoom_out(cx); } } }) @@ -1464,23 +1476,14 @@ impl Workspace { cx.notify(); } - fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { - // Any time the zoom is toggled we will zoom out all panes and docks. Then, - // the dock or pane that was zoomed will emit an event to zoom itself back in. - self.zoom_out(cx); - } - fn zoom_out(&mut self, cx: &mut ViewContext) { for pane in &self.panes { pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); } - self.left_dock - .update(cx, |dock, cx| dock.set_zoomed(false, cx)); - self.bottom_dock - .update(cx, |dock, cx| dock.set_zoomed(false, cx)); - self.right_dock - .update(cx, |dock, cx| dock.set_zoomed(false, cx)); + self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx)); + self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx)); + self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx)); cx.notify(); } @@ -1490,20 +1493,6 @@ impl Workspace { cx.notify(); } - fn register_dock(dock: &ViewHandle, cx: &mut ViewContext) -> [Subscription; 2] { - [ - cx.observe(dock, |this, _, cx| { - this.serialize_workspace(cx); - cx.notify(); - }), - cx.subscribe(dock, |_, dock, event, cx| { - dock.update(cx, |dock, cx| match event { - dock::Event::ZoomIn => dock.set_zoomed(true, cx), - }) - }), - ] - } - fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { let pane = cx.add_view(|cx| Pane::new(self.weak_handle(), self.app_state.background_actions, cx)); @@ -1727,9 +1716,11 @@ impl Workspace { self.handle_pane_focused(pane.clone(), cx); } pane::Event::ZoomIn => { + self.zoom_out(cx); pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); cx.notify(); } + pane::Event::ZoomOut => self.zoom_out(cx), } self.serialize_workspace(cx); From 981129ef8ece66868ae17172542e82a837bded5c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 May 2023 15:06:58 +0200 Subject: [PATCH 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 9cc1ad908a75af1c9a4709e0864417d188895aec..5cb2e8d931df5a3ff1786b86b8aa84ff65fd8884 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -4224,7 +4224,7 @@ impl Hash for WeakViewHandle { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct AnyWeakViewHandle { window_id: usize, view_id: usize, diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index c448ac4e43e577acc2cf3820de0461f5ac3ed98a..63263b23c7304d1321b99a2c7462da1e906f6cb3 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -13,7 +13,7 @@ use gpui::{ keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext, - ViewHandle, WeakViewHandle, + ViewHandle, WeakViewHandle, WindowContext, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; @@ -48,6 +48,7 @@ pub struct ProjectPanel { context_menu: ViewHandle, dragged_entry_destination: Option>, workspace: WeakViewHandle, + has_focus: bool, } #[derive(Copy, Clone)] @@ -139,6 +140,7 @@ pub enum Event { focus_opened_item: bool, }, DockPositionChanged, + Focus, } impl ProjectPanel { @@ -214,6 +216,7 @@ impl ProjectPanel { context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)), dragged_entry_destination: None, workspace: workspace.weak_handle(), + has_focus: false, }; this.update_visible_entries(None, cx); @@ -259,7 +262,7 @@ impl ProjectPanel { } } } - Event::DockPositionChanged => {} + _ => {} } }) .detach(); @@ -1338,6 +1341,17 @@ impl View for ProjectPanel { Self::reset_to_default_keymap_context(keymap); keymap.add_identifier("menu"); } + + fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { + if !self.has_focus { + self.has_focus = true; + cx.emit(Event::Focus); + } + } + + fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext) { + self.has_focus = false; + } } impl Entity for ProjectPanel { @@ -1345,7 +1359,7 @@ impl Entity for ProjectPanel { } impl workspace::dock::Panel for ProjectPanel { - fn position(&self, cx: &gpui::WindowContext) -> DockPosition { + fn position(&self, cx: &WindowContext) -> DockPosition { let settings = cx.global::(); match settings.project_panel.dock { settings::ProjectPanelDockPosition::Left => DockPosition::Left, @@ -1369,7 +1383,7 @@ impl workspace::dock::Panel for ProjectPanel { }) } - fn default_size(&self, cx: &gpui::WindowContext) -> f32 { + fn default_size(&self, cx: &WindowContext) -> f32 { cx.global::().project_panel.default_width } @@ -1395,13 +1409,21 @@ impl workspace::dock::Panel for ProjectPanel { matches!(event, Event::DockPositionChanged) } - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_activate_on_event(_: &Self::Event) -> bool { false } - fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_close_on_event(_: &Self::Event) -> bool { false } + + fn has_focus(&self, _: &WindowContext) -> bool { + self.has_focus + } + + fn is_focus_event(event: &Self::Event) -> bool { + matches!(event, Event::Focus) + } } impl ClipboardEntry { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 68783a5bc189e3fc05ad0a14cbad3a54bda8b56e..25302d7df5afc0542174c200acac037096b70ef1 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -20,6 +20,7 @@ pub enum Event { DockPositionChanged, ZoomIn, ZoomOut, + Focus, } pub struct TerminalPanel { @@ -100,6 +101,7 @@ impl TerminalPanel { pane::Event::Remove => cx.emit(Event::Close), pane::Event::ZoomIn => cx.emit(Event::ZoomIn), pane::Event::ZoomOut => cx.emit(Event::ZoomOut), + pane::Event::Focus => cx.emit(Event::Focus), _ => {} } } @@ -149,6 +151,10 @@ impl View for TerminalPanel { if self.pane.read(cx).items_len() == 0 { self.add_terminal(&Default::default(), cx) } + + if cx.is_self_focused() { + cx.focus(&self.pane); + } } } @@ -211,7 +217,7 @@ impl Panel for TerminalPanel { "Terminals".to_string() } - fn icon_label(&self, cx: &AppContext) -> Option { + fn icon_label(&self, cx: &WindowContext) -> Option { let count = self.pane.read(cx).items_len(); if count == 0 { None @@ -224,11 +230,19 @@ impl Panel for TerminalPanel { matches!(event, Event::DockPositionChanged) } - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_activate_on_event(_: &Self::Event) -> bool { false } - fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { + fn should_close_on_event(event: &Event) -> bool { matches!(event, Event::Close) } + + fn has_focus(&self, cx: &WindowContext) -> bool { + self.pane.read(cx).has_focus() + } + + fn is_focus_event(event: &Self::Event) -> bool { + matches!(event, Event::Focus) + } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 40e7288de0ae8431eacd5f9220b5a2c3820b4ac8..edd6a5959f953dabee5e1155ef69fa7bde5125b3 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,9 +1,8 @@ use crate::{StatusItemView, Workspace}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ - elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, - AppContext, Axis, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, - WindowContext, + elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, Axis, + Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use serde::Deserialize; use settings::Settings; @@ -16,15 +15,17 @@ pub trait Panel: View { fn default_size(&self, cx: &WindowContext) -> f32; fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; - fn icon_label(&self, _: &AppContext) -> Option { + fn icon_label(&self, _: &WindowContext) -> Option { None } fn should_change_position_on_event(_: &Self::Event) -> bool; fn should_zoom_in_on_event(_: &Self::Event) -> bool; fn should_zoom_out_on_event(_: &Self::Event) -> bool; fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext); - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; - fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; + fn should_activate_on_event(_: &Self::Event) -> bool; + fn should_close_on_event(_: &Self::Event) -> bool; + fn has_focus(&self, cx: &WindowContext) -> bool; + fn is_focus_event(_: &Self::Event) -> bool; } pub trait PanelHandle { @@ -37,7 +38,7 @@ pub trait PanelHandle { fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; - fn is_focused(&self, cx: &WindowContext) -> bool; + fn has_focus(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; } @@ -81,8 +82,8 @@ where self.read(cx).icon_label(cx) } - fn is_focused(&self, cx: &WindowContext) -> bool { - ViewHandle::is_focused(self, cx) + fn has_focus(&self, cx: &WindowContext) -> bool { + self.read(cx).has_focus(cx) } fn as_any(&self) -> &AnyViewHandle { @@ -170,6 +171,11 @@ impl Dock { self.is_open } + pub fn has_focus(&self, cx: &WindowContext) -> bool { + self.active_panel() + .map_or(false, |panel| panel.has_focus(cx)) + } + pub fn active_panel_index(&self) -> usize { self.active_panel_index } @@ -220,7 +226,7 @@ impl Dock { let subscriptions = [ cx.observe(&panel, |_, _, cx| cx.notify()), cx.subscribe(&panel, |this, panel, event, cx| { - if panel.read(cx).should_activate_on_event(event, cx) { + if T::should_activate_on_event(event) { if let Some(ix) = this .panel_entries .iter() @@ -230,7 +236,7 @@ impl Dock { this.activate_panel(ix, cx); cx.focus(&panel); } - } else if panel.read(cx).should_close_on_event(event, cx) + } else if T::should_close_on_event(event) && this.active_panel().map_or(false, |p| p.id() == panel.id()) { this.set_open(false, cx); @@ -302,10 +308,10 @@ impl Dock { } } - pub fn zoomed_panel(&self) -> Option { + pub fn zoomed_panel(&self) -> Option> { let entry = self.active_entry()?; if entry.zoomed { - Some(entry.panel.as_any().clone()) + Some(entry.panel.clone()) } else { None } @@ -344,6 +350,24 @@ impl Dock { cx.notify(); } } + + pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement { + if let Some(active_entry) = self.active_entry() { + let style = &cx.global::().theme.workspace.dock; + Empty::new() + .into_any() + .contained() + .with_style(style.container) + .resizable( + self.position.to_resize_handle_side(), + active_entry.size, + |_, _, _| {}, + ) + .into_any() + } else { + Empty::new().into_any() + } + } } impl Entity for Dock { @@ -357,21 +381,16 @@ impl View for Dock { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(active_entry) = self.active_entry() { - if active_entry.zoomed { - Empty::new().into_any() - } else { - let size = self.active_panel_size().unwrap(); - let style = &cx.global::().theme.workspace.dock; - ChildView::new(active_entry.panel.as_any(), cx) - .contained() - .with_style(style.container) - .resizable( - self.position.to_resize_handle_side(), - size, - |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), - ) - .into_any() - } + let style = &cx.global::().theme.workspace.dock; + ChildView::new(active_entry.panel.as_any(), cx) + .contained() + .with_style(style.container) + .resizable( + self.position.to_resize_handle_side(), + active_entry.size, + |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), + ) + .into_any() } else { Empty::new().into_any() } @@ -604,12 +623,20 @@ pub(crate) mod test { false } - fn should_activate_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool { + fn should_activate_on_event(event: &Self::Event) -> bool { matches!(event, TestPanelEvent::Activated) } - fn should_close_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool { + fn should_close_on_event(event: &Self::Event) -> bool { matches!(event, TestPanelEvent::Closed) } + + fn has_focus(&self, _cx: &WindowContext) -> bool { + unimplemented!() + } + + fn is_focus_event(_: &Self::Event) -> bool { + unimplemented!() + } } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 73738ab41322215e3d19f59d3baa7d3808115ef1..681a7eda274df805c8a78c06ddebe6b2769c1932 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1699,7 +1699,11 @@ impl View for Pane { } fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { - self.has_focus = true; + if !self.has_focus { + self.has_focus = true; + cx.emit(Event::Focus); + } + self.toolbar.update(cx, |toolbar, cx| { toolbar.pane_focus_update(true, cx); }); @@ -1725,8 +1729,6 @@ impl View for Pane { .insert(active_item.id(), focused.downgrade()); } } - - cx.emit(Event::Focus); } fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index a1b35c1d65c58695cf2904729bc430898f8a9e5e..5f6d46aa46c36e709eb21b32b69ceec1a45df332 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -7,7 +7,7 @@ use gpui::{ elements::*, geometry::{rect::RectF, vector::Vector2F}, platform::{CursorStyle, MouseButton}, - Axis, Border, ModelHandle, ViewContext, ViewHandle, + AnyViewHandle, Axis, Border, ModelHandle, ViewContext, ViewHandle, }; use project::Project; use serde::Deserialize; @@ -72,6 +72,7 @@ impl PaneGroup { follower_states: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, active_pane: &ViewHandle, + zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { @@ -81,6 +82,7 @@ impl PaneGroup { follower_states, active_call, active_pane, + zoomed, app_state, cx, ) @@ -135,6 +137,7 @@ impl Member { follower_states: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, active_pane: &ViewHandle, + zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { @@ -142,7 +145,7 @@ impl Member { match self { Member::Pane(pane) => { - let pane_element = if pane.read(cx).is_zoomed() { + let pane_element = if Some(&**pane) == zoomed { Empty::new().into_any() } else { ChildView::new(pane, cx).into_any() @@ -274,6 +277,7 @@ impl Member { follower_states, active_call, active_pane, + zoomed, app_state, cx, ), @@ -378,6 +382,7 @@ impl PaneAxis { follower_state: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, active_pane: &ViewHandle, + zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { @@ -395,6 +400,7 @@ impl PaneAxis { follower_state, active_call, active_pane, + zoomed, app_state, cx, ); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 06832e13859a7623b90bea3690af25b996d83683..0fd961a768572ef97418f9a82bfb6c43f367c22a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -893,6 +893,8 @@ impl Workspace { dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx)); } else if T::should_zoom_out_on_event(event) { this.zoom_out(cx); + } else if T::is_focus_event(event) { + cx.notify(); } } }) @@ -1309,16 +1311,42 @@ impl Workspace { } } - fn zoomed(&self, cx: &AppContext) -> Option { - self.left_dock - .read(cx) - .zoomed_panel() - .or(self.bottom_dock.read(cx).zoomed_panel()) - .or(self.right_dock.read(cx).zoomed_panel()) - .or_else(|| { - let pane = self.panes.iter().find(|pane| pane.read(cx).is_zoomed())?; - Some(pane.clone().into_any()) - }) + fn zoomed(&self, cx: &WindowContext) -> Option { + self.zoomed_panel_for_dock(DockPosition::Left, cx) + .or_else(|| self.zoomed_panel_for_dock(DockPosition::Bottom, cx)) + .or_else(|| self.zoomed_panel_for_dock(DockPosition::Right, cx)) + .or_else(|| self.zoomed_pane(cx)) + } + + fn zoomed_panel_for_dock( + &self, + position: DockPosition, + cx: &WindowContext, + ) -> Option { + let (dock, other_docks) = match position { + DockPosition::Left => (&self.left_dock, [&self.bottom_dock, &self.right_dock]), + DockPosition::Bottom => (&self.bottom_dock, [&self.left_dock, &self.right_dock]), + DockPosition::Right => (&self.right_dock, [&self.left_dock, &self.bottom_dock]), + }; + + let zoomed_panel = dock.read(&cx).zoomed_panel()?; + if other_docks.iter().all(|dock| !dock.read(cx).has_focus(cx)) + && !self.active_pane.read(cx).has_focus() + { + Some(zoomed_panel.as_any().clone()) + } else { + None + } + } + + fn zoomed_pane(&self, cx: &WindowContext) -> Option { + let active_pane = self.active_pane.read(cx); + let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock]; + if active_pane.is_zoomed() && docks.iter().all(|dock| !dock.read(cx).has_focus(cx)) { + Some(self.active_pane.clone().into_any()) + } else { + None + } } pub fn items<'a>( @@ -1433,7 +1461,7 @@ impl Workspace { }); if let Some(active_item) = active_item { - if active_item.is_focused(cx) { + if active_item.has_focus(cx) { cx.focus_self(); } else { cx.focus(active_item.as_any()); @@ -1464,7 +1492,7 @@ impl Workspace { dock.active_panel().cloned() }); if let Some(active_item) = active_item { - if active_item.is_focused(cx) { + if active_item.has_focus(cx) { cx.focus_self(); } else { cx.focus(active_item.as_any()); @@ -1663,7 +1691,6 @@ impl Workspace { }); self.active_item_path_changed(cx); self.last_active_center_pane = Some(pane.downgrade()); - cx.notify(); } self.update_followers( @@ -1677,6 +1704,8 @@ impl Workspace { }), cx, ); + + cx.notify(); } fn handle_pane_event( @@ -1716,9 +1745,11 @@ impl Workspace { self.handle_pane_focused(pane.clone(), cx); } pane::Event::ZoomIn => { - self.zoom_out(cx); - pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); - cx.notify(); + if pane == self.active_pane { + self.zoom_out(cx); + pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); + cx.notify(); + } } pane::Event::ZoomOut => self.zoom_out(cx), } @@ -2646,6 +2677,33 @@ impl Workspace { }); Self::new(None, 0, project, app_state, cx) } + + fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option> { + let dock = match position { + DockPosition::Left => &self.left_dock, + DockPosition::Right => &self.right_dock, + DockPosition::Bottom => &self.bottom_dock, + }; + let active_panel = dock.read(cx).active_panel()?; + let element = if Some(active_panel.as_any()) == self.zoomed(cx).as_ref() { + dock.read(cx).render_placeholder(cx) + } else { + ChildView::new(dock, cx).into_any() + }; + + Some( + element + .constrained() + .dynamically(move |constraint, _, cx| match position { + DockPosition::Left | DockPosition::Right => SizeConstraint::new( + Vector2F::new(20., constraint.min.y()), + Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()), + ), + _ => constraint, + }) + .into_any(), + ) + } } fn notify_if_database_failed(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { @@ -2702,25 +2760,7 @@ impl View for Workspace { .with_child({ let project = self.project.clone(); Flex::row() - .with_children( - if self.left_dock.read(cx).active_panel().is_some() { - Some( - ChildView::new(&self.left_dock, cx) - .constrained() - .dynamically(|constraint, _, cx| { - SizeConstraint::new( - Vector2F::new(20., constraint.min.y()), - Vector2F::new( - cx.window_size().x() * 0.8, - constraint.max.y(), - ), - ) - }), - ) - } else { - None - }, - ) + .with_children(self.render_dock(DockPosition::Left, cx)) .with_child( Flex::column() .with_child( @@ -2730,44 +2770,18 @@ impl View for Workspace { &self.follower_states_by_leader, self.active_call(), self.active_pane(), + self.zoomed(cx).as_ref(), &self.app_state, cx, )) .flex(1., true), ) .with_children( - if self - .bottom_dock - .read(cx) - .active_panel() - .is_some() - { - Some(ChildView::new(&self.bottom_dock, cx)) - } else { - None - }, + self.render_dock(DockPosition::Bottom, cx), ) .flex(1., true), ) - .with_children( - if self.right_dock.read(cx).active_panel().is_some() { - Some( - ChildView::new(&self.right_dock, cx) - .constrained() - .dynamically(|constraint, _, cx| { - SizeConstraint::new( - Vector2F::new(20., constraint.min.y()), - Vector2F::new( - cx.window_size().x() * 0.8, - constraint.max.y(), - ), - ) - }), - ) - } else { - None - }, - ) + .with_children(self.render_dock(DockPosition::Right, cx)) }) .with_child(Overlay::new( Stack::new() @@ -2810,6 +2824,7 @@ impl View for Workspace { if cx.is_self_focused() { cx.focus(&self.active_pane); } + cx.notify(); } } From 747fbfadeb994e2d52bc42f36cc3baa557d735f0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 May 2023 17:12:12 +0200 Subject: [PATCH 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 5cb2e8d931df5a3ff1786b86b8aa84ff65fd8884..26963fa2dd9eec5879d93db5efb17a50470da4e3 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1694,12 +1694,54 @@ impl AppContext { if let Some(invalidation) = invalidation { let appearance = cx.window.platform_window.appearance(); cx.invalidate(invalidation, appearance); - if cx.layout(refreshing).log_err().is_some() { + if let Some(old_parents) = cx.layout(refreshing).log_err() { updated_windows.insert(window_id); - // When the previously-focused view isn't rendered and - // there isn't any pending focus, focus the root view. if let Some(focused_view_id) = cx.focused_view_id() { + let old_ancestors = std::iter::successors( + Some(focused_view_id), + |&view_id| old_parents.get(&view_id).copied(), + ) + .collect::>(); + let new_ancestors = + cx.ancestors(focused_view_id).collect::>(); + + // Notify the old ancestors of the focused view when they don't contain it anymore. + for old_ancestor in old_ancestors.iter().copied() { + if !new_ancestors.contains(&old_ancestor) { + if let Some(mut view) = + cx.views.remove(&(window_id, old_ancestor)) + { + view.focus_out( + focused_view_id, + cx, + old_ancestor, + ); + cx.views + .insert((window_id, old_ancestor), view); + } + } + } + + // Notify the new ancestors of the focused view if they contain it now. + for new_ancestor in new_ancestors.iter().copied() { + if !old_ancestors.contains(&new_ancestor) { + if let Some(mut view) = + cx.views.remove(&(window_id, new_ancestor)) + { + view.focus_in( + focused_view_id, + cx, + new_ancestor, + ); + cx.views + .insert((window_id, new_ancestor), view); + } + } + } + + // When the previously-focused view isn't rendered and + // there isn't any pending focus, focus the root view. let root_view_id = cx.window.root_view().id(); if focused_view_id != root_view_id && !cx.window.parents.contains_key(&focused_view_id) diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index bd9bd6d2db0842a3cf40a630f7f83779856ee143..24a36865635b895908bd9f647be9f3880e5ab2a0 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -29,6 +29,7 @@ use sqlez::{ }; use std::{ any::TypeId, + mem, ops::{Deref, DerefMut, Range}, }; use util::ResultExt; @@ -890,7 +891,7 @@ impl<'a> WindowContext<'a> { Ok(element) } - pub(crate) fn layout(&mut self, refreshing: bool) -> Result<()> { + pub(crate) fn layout(&mut self, refreshing: bool) -> Result> { let window_size = self.window.platform_window.content_size(); let root_view_id = self.window.root_view().id(); let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap(); @@ -923,11 +924,11 @@ impl<'a> WindowContext<'a> { } } - self.window.parents = new_parents; + let old_parents = mem::replace(&mut self.window.parents, new_parents); self.window .rendered_views .insert(root_view_id, rendered_root); - Ok(()) + Ok(old_parents) } pub(crate) fn paint(&mut self) -> Result { From f09744454699e0160d32c8a40ccca61e878480ea Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 May 2023 17:35:10 +0200 Subject: [PATCH 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 37c010f0f93c78aae7a3cf3ec6c71f388f52d9bd..246b6995587b906efd8583cce6f1b5dfe4b70a8f 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -39,7 +39,8 @@ "cmd-shift-n": "workspace::NewWindow", "cmd-o": "workspace::Open", "alt-cmd-o": "projects::OpenRecent", - "ctrl-`": "workspace::NewTerminal" + "ctrl-~": "workspace::NewTerminal", + "ctrl-`": "terminal_panel::ToggleFocus" } }, { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 25302d7df5afc0542174c200acac037096b70ef1..4e90121d590f7242168c518d21245b3db822d166 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1,7 +1,7 @@ use crate::TerminalView; use gpui::{ - elements::*, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle, - WeakViewHandle, WindowContext, + actions, elements::*, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, + ViewHandle, WeakViewHandle, WindowContext, }; use project::Project; use settings::{settings_file::SettingsFile, Settings, TerminalDockPosition, WorkingDirectory}; @@ -11,6 +11,8 @@ use workspace::{ pane, DraggedItem, Pane, Workspace, }; +actions!(terminal_panel, [ToggleFocus]); + pub fn init(cx: &mut AppContext) { cx.add_action(TerminalPanel::add_terminal); } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index edd6a5959f953dabee5e1155ef69fa7bde5125b3..a387f05e653ddf2d71fdeec515e6131b2970d462 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -176,6 +176,12 @@ impl Dock { .map_or(false, |panel| panel.has_focus(cx)) } + pub fn panel_index(&self) -> Option { + self.panel_entries + .iter() + .position(|entry| entry.panel.as_any().is::()) + } + pub fn active_panel_index(&self) -> usize { self.active_panel_index } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 0fd961a768572ef97418f9a82bfb6c43f367c22a..278ea9d87961cd20cffaec0c1b2a9b048ed6fa77 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -116,6 +116,7 @@ actions!( FollowNextCollaborator, ToggleLeftDock, NewTerminal, + ToggleTerminalFocus, NewSearch, Feedback, Restart, @@ -1475,33 +1476,27 @@ impl Workspace { cx.notify(); } - pub fn toggle_panel_focus( - &mut self, - dock_position: DockPosition, - panel_index: usize, - cx: &mut ViewContext, - ) { - let dock = match dock_position { - DockPosition::Left => &mut self.left_dock, - DockPosition::Bottom => &mut self.bottom_dock, - DockPosition::Right => &mut self.right_dock, - }; - let active_item = dock.update(cx, |dock, cx| { - dock.set_open(true, cx); - dock.activate_panel(panel_index, cx); - dock.active_panel().cloned() - }); - if let Some(active_item) = active_item { - if active_item.has_focus(cx) { - cx.focus_self(); - } else { - cx.focus(active_item.as_any()); + pub fn toggle_panel_focus(&mut self, cx: &mut ViewContext) { + for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { + if let Some(panel_index) = dock.read(cx).panel_index::() { + let active_item = dock.update(cx, |dock, cx| { + dock.set_open(true, cx); + dock.activate_panel(panel_index, cx); + dock.active_panel().cloned() + }); + if let Some(active_item) = active_item { + if active_item.has_focus(cx) { + cx.focus_self(); + } else { + cx.focus(active_item.as_any()); + } + } + + self.serialize_workspace(cx); + cx.notify(); + break; } } - - self.serialize_workspace(cx); - - cx.notify(); } fn zoom_out(&mut self, cx: &mut ViewContext) { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 8a40a5d1d3a6e43313458d7cdc32c4b89130fe39..fb7da693fc365ba12c4d8cf2202fd65fba0a8419 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -31,14 +31,11 @@ use serde::Deserialize; use serde_json::to_string_pretty; use settings::{Settings, DEFAULT_SETTINGS_ASSET_PATH}; use std::{borrow::Cow, str, sync::Arc}; -use terminal_view::terminal_panel::TerminalPanel; +use terminal_view::terminal_panel::{self, TerminalPanel}; use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; pub use workspace; -use workspace::{ - create_and_open_local_file, dock::DockPosition, open_new, AppState, NewFile, NewWindow, - Workspace, -}; +use workspace::{create_and_open_local_file, open_new, AppState, NewFile, NewWindow, Workspace}; #[derive(Deserialize, Clone, PartialEq)] pub struct OpenBrowser { @@ -242,7 +239,14 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { |workspace: &mut Workspace, _: &project_panel::ToggleFocus, cx: &mut ViewContext| { - workspace.toggle_panel_focus(DockPosition::Left, 0, cx); + workspace.toggle_panel_focus::(cx); + }, + ); + cx.add_action( + |workspace: &mut Workspace, + _: &terminal_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace.toggle_panel_focus::(cx); }, ); cx.add_global_action({ From 05fb0519241490856d62d6ce783dde88b44975ee Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 May 2023 17:51:11 +0200 Subject: [PATCH 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 63263b23c7304d1321b99a2c7462da1e906f6cb3..a89d5440749549e7cab9dbffd4394484a474e293 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1395,6 +1395,10 @@ impl workspace::dock::Panel for ProjectPanel { false } + fn is_zoomed(&self, _: &WindowContext) -> bool { + false + } + fn set_zoomed(&mut self, _: bool, _: &mut ViewContext) {} fn icon_path(&self) -> &'static str { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 4e90121d590f7242168c518d21245b3db822d166..4926a8775e84a1f3d1be262b1a819ef41e44bd30 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -207,6 +207,10 @@ impl Panel for TerminalPanel { matches!(event, Event::ZoomOut) } + fn is_zoomed(&self, cx: &WindowContext) -> bool { + self.pane.read(cx).is_zoomed() + } + fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx)); } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index a387f05e653ddf2d71fdeec515e6131b2970d462..5c501f64ad16a34d72ec5400dde972f6b359648d 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -21,6 +21,7 @@ pub trait Panel: View { fn should_change_position_on_event(_: &Self::Event) -> bool; fn should_zoom_in_on_event(_: &Self::Event) -> bool; fn should_zoom_out_on_event(_: &Self::Event) -> bool; + fn is_zoomed(&self, cx: &WindowContext) -> bool; fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext); fn should_activate_on_event(_: &Self::Event) -> bool; fn should_close_on_event(_: &Self::Event) -> bool; @@ -33,6 +34,7 @@ pub trait PanelHandle { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; fn set_position(&self, position: DockPosition, cx: &mut WindowContext); + fn is_zoomed(&self, cx: &WindowContext) -> bool; fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext); fn default_size(&self, cx: &WindowContext) -> f32; fn icon_path(&self, cx: &WindowContext) -> &'static str; @@ -66,6 +68,10 @@ where self.read(cx).default_size(cx) } + fn is_zoomed(&self, cx: &WindowContext) -> bool { + self.read(cx).is_zoomed(cx) + } + fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext) { self.update(cx, |this, cx| this.set_zoomed(zoomed, cx)) } @@ -140,7 +146,6 @@ struct PanelEntry { panel: Rc, size: f32, context_menu: ViewHandle, - zoomed: bool, _subscriptions: [Subscription; 2], } @@ -206,12 +211,10 @@ impl Dock { ) { for entry in &mut self.panel_entries { if entry.panel.as_any() == panel { - if zoomed != entry.zoomed { - entry.zoomed = zoomed; + if zoomed != entry.panel.is_zoomed(cx) { entry.panel.set_zoomed(zoomed, cx); } - } else if entry.zoomed { - entry.zoomed = false; + } else if entry.panel.is_zoomed(cx) { entry.panel.set_zoomed(false, cx); } } @@ -221,8 +224,7 @@ impl Dock { pub fn zoom_out(&mut self, cx: &mut ViewContext) { for entry in &mut self.panel_entries { - if entry.zoomed { - entry.zoomed = false; + if entry.panel.is_zoomed(cx) { entry.panel.set_zoomed(false, cx); } } @@ -255,7 +257,6 @@ impl Dock { self.panel_entries.push(PanelEntry { panel: Rc::new(panel), size, - zoomed: false, context_menu: cx.add_view(|cx| { let mut menu = ContextMenu::new(dock_view_id, cx); menu.set_position_mode(OverlayPositionMode::Local); @@ -314,9 +315,9 @@ impl Dock { } } - pub fn zoomed_panel(&self) -> Option> { + pub fn zoomed_panel(&self, cx: &WindowContext) -> Option> { let entry = self.active_entry()?; - if entry.zoomed { + if entry.panel.is_zoomed(cx) { Some(entry.panel.clone()) } else { None @@ -598,6 +599,10 @@ pub(crate) mod test { cx.emit(TestPanelEvent::PositionChanged); } + fn is_zoomed(&self, _: &WindowContext) -> bool { + unimplemented!() + } + fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext) { unimplemented!() } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 278ea9d87961cd20cffaec0c1b2a9b048ed6fa77..e9fc2866aadf10229e52f3b4444a99e6baaab4de 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1330,7 +1330,7 @@ impl Workspace { DockPosition::Right => (&self.right_dock, [&self.left_dock, &self.bottom_dock]), }; - let zoomed_panel = dock.read(&cx).zoomed_panel()?; + let zoomed_panel = dock.read(&cx).zoomed_panel(cx)?; if other_docks.iter().all(|dock| !dock.read(cx).has_focus(cx)) && !self.active_pane.read(cx).has_focus() { From 489841761704c4b3c5976c8eadc9730e405f4d8d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 May 2023 18:21:35 +0200 Subject: [PATCH 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 2ea99581d219b95e331430740af161bbd641056c..3621a4e0d9096db36b88c2d08640f7b91fc900ce 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -130,6 +130,27 @@ define_connection! { ALTER TABLE workspaces ADD COLUMN window_width REAL; ALTER TABLE workspaces ADD COLUMN window_height REAL; ALTER TABLE workspaces ADD COLUMN display BLOB; + ), + // Drop foreign key constraint from workspaces.dock_pane to panes table. + sql!( + CREATE TABLE workspaces_2( + workspace_id INTEGER PRIMARY KEY, + workspace_location BLOB UNIQUE, + dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed. + dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed. + dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed. + left_sidebar_open INTEGER, // Boolean + timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, + window_state TEXT, + window_x REAL, + window_y REAL, + window_width REAL, + window_height REAL, + display BLOB + ) STRICT; + INSERT INTO workspaces_2 SELECT * FROM workspaces; + DROP TABLE workspaces; + ALTER TABLE workspaces_2 RENAME TO workspaces; )]; } From 89d8bb1425be55627b038b05a7357946d3ddc9d5 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 17 May 2023 17:34:20 -0700 Subject: [PATCH 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 ec301939f77ffae8b615d8f96f9ada8590545c8d..8fbcda02a32be4ba7e41e9e8452e94d7a1014c9e 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -222,21 +222,21 @@ impl Bind for WindowBounds { fn bind(&self, statement: &Statement, start_index: i32) -> Result { let (region, next_index) = match self { WindowBounds::Fullscreen => { - let next_index = statement.bind("Fullscreen", start_index)?; + let next_index = statement.bind(&"Fullscreen", start_index)?; (None, next_index) } WindowBounds::Maximized => { - let next_index = statement.bind("Maximized", start_index)?; + let next_index = statement.bind(&"Maximized", start_index)?; (None, next_index) } WindowBounds::Fixed(region) => { - let next_index = statement.bind("Fixed", start_index)?; + let next_index = statement.bind(&"Fixed", start_index)?; (Some(*region), next_index) } }; statement.bind( - region.map(|region| { + ®ion.map(|region| { ( region.min_x(), region.min_y(), diff --git a/crates/sqlez/src/bindable.rs b/crates/sqlez/src/bindable.rs index 86d69afe5f8203224f5db5f7fd319b75ecb993c8..4c874c4585ebb6cc852704581485cff17d52c047 100644 --- a/crates/sqlez/src/bindable.rs +++ b/crates/sqlez/src/bindable.rs @@ -27,7 +27,7 @@ impl StaticColumnCount for bool {} impl Bind for bool { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement - .bind(self.then_some(1).unwrap_or(0), start_index) + .bind(&self.then_some(1).unwrap_or(0), start_index) .with_context(|| format!("Failed to bind bool at index {start_index}")) } } diff --git a/crates/sqlez/src/statement.rs b/crates/sqlez/src/statement.rs index 69d5685ba02ceadeeb5bec364366c76121aa12a9..de0ad626a535cca8f18c6455435664403bb170f5 100644 --- a/crates/sqlez/src/statement.rs +++ b/crates/sqlez/src/statement.rs @@ -236,7 +236,7 @@ impl<'a> Statement<'a> { Ok(str::from_utf8(slice)?) } - pub fn bind(&self, value: T, index: i32) -> Result { + pub fn bind(&self, value: &T, index: i32) -> Result { debug_assert!(index > 0); Ok(value.bind(self, index)?) } @@ -258,7 +258,7 @@ impl<'a> Statement<'a> { } } - pub fn with_bindings(&mut self, bindings: impl Bind) -> Result<&mut Self> { + pub fn with_bindings(&mut self, bindings: &impl Bind) -> Result<&mut Self> { self.bind(bindings, 1)?; Ok(self) } @@ -464,7 +464,7 @@ mod test { connection .exec(indoc! {" CREATE TABLE texts ( - text TEXT + text TEXT )"}) .unwrap()() .unwrap(); diff --git a/crates/sqlez/src/typed_statements.rs b/crates/sqlez/src/typed_statements.rs index 488ee27c0c155f4de4c34d259e0d07faa6f43411..d7f25cde5174b65a1c97acfd615314c3fe3b3382 100644 --- a/crates/sqlez/src/typed_statements.rs +++ b/crates/sqlez/src/typed_statements.rs @@ -29,7 +29,7 @@ impl Connection { query: &str, ) -> Result Result<()>> { let mut statement = Statement::prepare(self, query)?; - Ok(move |bindings| statement.with_bindings(bindings)?.exec()) + Ok(move |bindings| statement.with_bindings(&bindings)?.exec()) } /// Prepare a statement which has no bindings and returns a `Vec`. @@ -55,7 +55,7 @@ impl Connection { query: &str, ) -> Result Result>> { let mut statement = Statement::prepare(self, query)?; - Ok(move |bindings| statement.with_bindings(bindings)?.rows::()) + Ok(move |bindings| statement.with_bindings(&bindings)?.rows::()) } /// Prepare a statement that selects a single row from the database. @@ -87,7 +87,7 @@ impl Connection { let mut statement = Statement::prepare(self, query)?; Ok(move |bindings| { statement - .with_bindings(bindings) + .with_bindings(&bindings) .context("Bindings failed")? .maybe_row::() .context("Maybe row failed") diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 3621a4e0d9096db36b88c2d08640f7b91fc900ce..f5d8c56a466481cba38b0a9823dda570e5c53e17 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -18,6 +18,9 @@ use model::{ WorkspaceLocation, }; +use self::model::DockStructure; + + define_connection! { // Current schema shape using pseudo-rust syntax: // @@ -151,6 +154,15 @@ define_connection! { INSERT INTO workspaces_2 SELECT * FROM workspaces; DROP TABLE workspaces; ALTER TABLE workspaces_2 RENAME TO workspaces; + ), + // Add panels related information + sql!( + ALTER TABLE workspaces ADD COLUMN left_dock_visible INTEGER; //bool + ALTER TABLE workspaces ADD COLUMN left_dock_size REAL; + ALTER TABLE workspaces ADD COLUMN right_dock_visible INTEGER; //bool + ALTER TABLE workspaces ADD COLUMN right_dock_size REAL; + ALTER TABLE workspaces ADD COLUMN bottom_dock_visible INTEGER; //bool + ALTER TABLE workspaces ADD COLUMN bottom_dock_size REAL; )]; } @@ -166,12 +178,13 @@ impl WorkspaceDb { // Note that we re-assign the workspace_id here in case it's empty // and we've grabbed the most recent workspace - let (workspace_id, workspace_location, left_sidebar_open, bounds, display): ( + let (workspace_id, workspace_location, left_sidebar_open, bounds, display, docks): ( WorkspaceId, WorkspaceLocation, bool, Option, Option, + DockStructure ) = self .select_row_bound(sql! { SELECT @@ -183,7 +196,13 @@ impl WorkspaceDb { window_y, window_width, window_height, - display + display, + left_dock_visible, + left_dock_size, + right_dock_visible, + right_dock_size, + bottom_dock_visible, + bottom_dock_size FROM workspaces WHERE workspace_location = ? }) @@ -202,6 +221,7 @@ impl WorkspaceDb { left_sidebar_open, bounds, display, + docks }) } @@ -227,15 +247,27 @@ impl WorkspaceDb { workspace_id, workspace_location, left_sidebar_open, + left_dock_visible, + left_dock_size, + right_dock_visible, + right_dock_size, + bottom_dock_visible, + bottom_dock_size, timestamp ) - VALUES (?1, ?2, ?3, CURRENT_TIMESTAMP) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, CURRENT_TIMESTAMP) ON CONFLICT DO UPDATE SET workspace_location = ?2, left_sidebar_open = ?3, + left_dock_visible = ?4, + left_dock_size = ?5, + right_dock_visible = ?6, + right_dock_size = ?7, + bottom_dock_visible = ?8, + bottom_dock_size = ?9, timestamp = CURRENT_TIMESTAMP - ))?((workspace.id, &workspace.location)) + ))?((workspace.id, &workspace.location, workspace.left_sidebar_open, workspace.docks)) .context("Updating workspace")?; // Save center pane group @@ -549,19 +581,22 @@ mod tests { let mut workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), - center_group: Default::default(), left_sidebar_open: true, + center_group: Default::default(), bounds: Default::default(), display: Default::default(), + docks: Default::default() }; let mut _workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), - center_group: Default::default(), left_sidebar_open: false, + center_group: Default::default(), bounds: Default::default(), display: Default::default(), + docks: Default::default() + }; db.save_workspace(workspace_1.clone()).await; @@ -659,6 +694,7 @@ mod tests { left_sidebar_open: true, bounds: Default::default(), display: Default::default(), + docks: Default::default() }; db.save_workspace(workspace.clone()).await; @@ -687,6 +723,7 @@ mod tests { left_sidebar_open: true, bounds: Default::default(), display: Default::default(), + docks: Default::default() }; let mut workspace_2 = SerializedWorkspace { @@ -696,6 +733,7 @@ mod tests { left_sidebar_open: false, bounds: Default::default(), display: Default::default(), + docks: Default::default() }; db.save_workspace(workspace_1.clone()).await; @@ -732,6 +770,7 @@ mod tests { left_sidebar_open: false, bounds: Default::default(), display: Default::default(), + docks: Default::default() }; db.save_workspace(workspace_3.clone()).await; @@ -765,6 +804,7 @@ mod tests { left_sidebar_open: true, bounds: Default::default(), display: Default::default(), + docks: Default::default() } } diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index 19da782fb6840a9a39d500db2355ca666154c7a7..9d997fcfad1378d227dc844673c6ee3b0e431b53 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -63,6 +63,65 @@ pub struct SerializedWorkspace { pub left_sidebar_open: bool, pub bounds: Option, pub display: Option, + pub docks: DockStructure, +} + +#[derive(Debug, PartialEq, Clone, Default)] +pub struct DockStructure { + pub(crate) left: DockData, + pub(crate) right: DockData, + pub(crate) bottom: DockData, +} + +impl Column for DockStructure { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (left, next_index) = DockData::column(statement, start_index)?; + let (right, next_index) = DockData::column(statement, next_index)?; + let (bottom, next_index) = DockData::column(statement, next_index)?; + Ok(( + DockStructure { + left, + right, + bottom, + }, + next_index, + )) + } +} + +impl Bind for DockStructure { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + let next_index = statement.bind(&self.left, start_index)?; + let next_index = statement.bind(&self.right, next_index)?; + statement.bind(&self.bottom, next_index) + } +} + +#[derive(Debug, PartialEq, Clone, Default)] +pub struct DockData { + pub(crate) visible: bool, + pub(crate) size: Option, +} + +impl Column for DockData { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (visible, next_index) = Option::::column(statement, start_index)?; + let (size, next_index) = Option::::column(statement, next_index)?; + Ok(( + DockData { + visible: visible.unwrap_or(false), + size, + }, + next_index, + )) + } +} + +impl Bind for DockData { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + let next_index = statement.bind(&self.visible, start_index)?; + statement.bind(&self.size, next_index) + } } #[derive(Debug, PartialEq, Eq, Clone)] @@ -251,9 +310,9 @@ impl StaticColumnCount for SerializedItem { } impl Bind for &SerializedItem { fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let next_index = statement.bind(self.kind.clone(), start_index)?; - let next_index = statement.bind(self.item_id, next_index)?; - statement.bind(self.active, next_index) + let next_index = statement.bind(&self.kind, start_index)?; + let next_index = statement.bind(&self.item_id, next_index)?; + statement.bind(&self.active, next_index) } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e9fc2866aadf10229e52f3b4444a99e6baaab4de..6cb9f66edbb9cc5df220093a25d094fc649881f9 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -58,7 +58,9 @@ use std::{ use crate::{ notifications::simple_message_notification::MessageNotification, - persistence::model::{SerializedPane, SerializedPaneGroup, SerializedWorkspace}, + persistence::model::{ + DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace, + }, }; use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle, TogglePanel}; use lazy_static::lazy_static; @@ -2575,12 +2577,51 @@ impl Workspace { } } + fn build_serialized_docks(this: &Workspace, cx: &AppContext) -> DockStructure { + let left_dock = this.left_dock.read(cx); + let left_visible = left_dock.is_open(); + let left_size = left_dock + .active_panel() + .map(|panel| left_dock.panel_size(panel.as_ref())) + .flatten(); + + let right_dock = this.right_dock.read(cx); + let right_visible = right_dock.is_open(); + let right_size = right_dock + .active_panel() + .map(|panel| right_dock.panel_size(panel.as_ref())) + .flatten(); + + let bottom_dock = this.bottom_dock.read(cx); + let bottom_visible = bottom_dock.is_open(); + let bottom_size = bottom_dock + .active_panel() + .map(|panel| bottom_dock.panel_size(panel.as_ref())) + .flatten(); + + DockStructure { + left: DockData { + visible: left_visible, + size: left_size, + }, + right: DockData { + visible: right_visible, + size: right_size, + }, + bottom: DockData { + visible: bottom_visible, + size: bottom_size, + }, + } + } + if let Some(location) = self.location(cx) { // Load bearing special case: // - with_local_workspace() relies on this to not have other stuff open // when you open your log if !location.paths().is_empty() { let center_group = build_serialized_pane_group(&self.center.root, cx); + let docks = build_serialized_docks(self, cx); let serialized_workspace = SerializedWorkspace { id: self.database_id, @@ -2589,6 +2630,7 @@ impl Workspace { left_sidebar_open: self.left_dock.read(cx).is_open(), bounds: Default::default(), display: Default::default(), + docks, }; cx.background() @@ -2642,11 +2684,25 @@ impl Workspace { } } - if workspace.left_dock().read(cx).is_open() - != serialized_workspace.left_sidebar_open - { - workspace.toggle_dock(DockPosition::Left, cx); - } + let docks = serialized_workspace.docks; + workspace.left_dock.update(cx, |dock, cx| { + dock.set_open(docks.left.visible, cx); + if let Some(size) = docks.left.size { + dock.resize_active_panel(size, cx); + } + }); + workspace.right_dock.update(cx, |dock, cx| { + dock.set_open(docks.right.visible, cx); + if let Some(size) = docks.right.size { + dock.resize_active_panel(size, cx); + } + }); + workspace.bottom_dock.update(cx, |dock, cx| { + dock.set_open(docks.bottom.visible, cx); + if let Some(size) = docks.bottom.size { + dock.resize_active_panel(size, cx); + } + }); cx.notify(); })?; From f2ad17dbc0392c8b49c0054bd281da360feeccd2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 May 2023 15:35:46 +0200 Subject: [PATCH 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 f5d8c56a466481cba38b0a9823dda570e5c53e17..9cf13af681d226e2e25e4cb4fe03dc8c6440717a 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -20,7 +20,6 @@ use model::{ use self::model::DockStructure; - define_connection! { // Current schema shape using pseudo-rust syntax: // @@ -158,11 +157,11 @@ define_connection! { // Add panels related information sql!( ALTER TABLE workspaces ADD COLUMN left_dock_visible INTEGER; //bool - ALTER TABLE workspaces ADD COLUMN left_dock_size REAL; + ALTER TABLE workspaces ADD COLUMN left_dock_active_panel TEXT; ALTER TABLE workspaces ADD COLUMN right_dock_visible INTEGER; //bool - ALTER TABLE workspaces ADD COLUMN right_dock_size REAL; + ALTER TABLE workspaces ADD COLUMN right_dock_active_panel TEXT; ALTER TABLE workspaces ADD COLUMN bottom_dock_visible INTEGER; //bool - ALTER TABLE workspaces ADD COLUMN bottom_dock_size REAL; + ALTER TABLE workspaces ADD COLUMN bottom_dock_active_panel TEXT; )]; } @@ -178,19 +177,17 @@ impl WorkspaceDb { // Note that we re-assign the workspace_id here in case it's empty // and we've grabbed the most recent workspace - let (workspace_id, workspace_location, left_sidebar_open, bounds, display, docks): ( + let (workspace_id, workspace_location, bounds, display, docks): ( WorkspaceId, WorkspaceLocation, - bool, Option, Option, - DockStructure + DockStructure, ) = self .select_row_bound(sql! { SELECT workspace_id, workspace_location, - left_sidebar_open, window_state, window_x, window_y, @@ -198,11 +195,11 @@ impl WorkspaceDb { window_height, display, left_dock_visible, - left_dock_size, + left_dock_active_panel, right_dock_visible, - right_dock_size, + right_dock_active_panel, bottom_dock_visible, - bottom_dock_size + bottom_dock_active_panel FROM workspaces WHERE workspace_location = ? }) @@ -218,10 +215,9 @@ impl WorkspaceDb { .get_center_pane_group(workspace_id) .context("Getting center group") .log_err()?, - left_sidebar_open, bounds, display, - docks + docks, }) } @@ -246,28 +242,26 @@ impl WorkspaceDb { INSERT INTO workspaces( workspace_id, workspace_location, - left_sidebar_open, left_dock_visible, - left_dock_size, + left_dock_active_panel, right_dock_visible, - right_dock_size, + right_dock_active_panel, bottom_dock_visible, - bottom_dock_size, + bottom_dock_active_panel, timestamp ) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, CURRENT_TIMESTAMP) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, CURRENT_TIMESTAMP) ON CONFLICT DO UPDATE SET workspace_location = ?2, - left_sidebar_open = ?3, - left_dock_visible = ?4, - left_dock_size = ?5, - right_dock_visible = ?6, - right_dock_size = ?7, - bottom_dock_visible = ?8, - bottom_dock_size = ?9, + left_dock_visible = ?3, + left_dock_active_panel = ?4, + right_dock_visible = ?5, + right_dock_active_panel = ?6, + bottom_dock_visible = ?7, + bottom_dock_active_panel = ?8, timestamp = CURRENT_TIMESTAMP - ))?((workspace.id, &workspace.location, workspace.left_sidebar_open, workspace.docks)) + ))?((workspace.id, &workspace.location, workspace.docks)) .context("Updating workspace")?; // Save center pane group @@ -581,22 +575,19 @@ mod tests { let mut workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), - left_sidebar_open: true, center_group: Default::default(), bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), }; let mut _workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), - left_sidebar_open: false, center_group: Default::default(), bounds: Default::default(), display: Default::default(), - docks: Default::default() - + docks: Default::default(), }; db.save_workspace(workspace_1.clone()).await; @@ -691,10 +682,9 @@ mod tests { id: 5, location: (["/tmp", "/tmp2"]).into(), center_group, - left_sidebar_open: true, bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), }; db.save_workspace(workspace.clone()).await; @@ -720,20 +710,18 @@ mod tests { id: 1, location: (["/tmp", "/tmp2"]).into(), center_group: Default::default(), - left_sidebar_open: true, bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), }; let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), center_group: Default::default(), - left_sidebar_open: false, bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), }; db.save_workspace(workspace_1.clone()).await; @@ -767,10 +755,9 @@ mod tests { id: 3, location: (&["/tmp", "/tmp2"]).into(), center_group: Default::default(), - left_sidebar_open: false, bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), }; db.save_workspace(workspace_3.clone()).await; @@ -801,10 +788,9 @@ mod tests { id: 4, location: workspace_id.into(), center_group: center_group.clone(), - left_sidebar_open: true, bounds: Default::default(), display: Default::default(), - docks: Default::default() + docks: Default::default(), } } diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index 9d997fcfad1378d227dc844673c6ee3b0e431b53..bc10c931bb545223ae0fa16dc68757d1bd0cd2bb 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -60,7 +60,6 @@ pub struct SerializedWorkspace { pub id: WorkspaceId, pub location: WorkspaceLocation, pub center_group: SerializedPaneGroup, - pub left_sidebar_open: bool, pub bounds: Option, pub display: Option, pub docks: DockStructure, @@ -100,17 +99,17 @@ impl Bind for DockStructure { #[derive(Debug, PartialEq, Clone, Default)] pub struct DockData { pub(crate) visible: bool, - pub(crate) size: Option, + pub(crate) active_panel: Option, } impl Column for DockData { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { let (visible, next_index) = Option::::column(statement, start_index)?; - let (size, next_index) = Option::::column(statement, next_index)?; + let (active_panel, next_index) = Option::::column(statement, next_index)?; Ok(( DockData { visible: visible.unwrap_or(false), - size, + active_panel, }, next_index, )) @@ -120,7 +119,7 @@ impl Column for DockData { impl Bind for DockData { fn bind(&self, statement: &Statement, start_index: i32) -> Result { let next_index = statement.bind(&self.visible, start_index)?; - statement.bind(&self.size, next_index) + statement.bind(&self.active_panel, next_index) } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6cb9f66edbb9cc5df220093a25d094fc649881f9..30a2a2f4e3234d6d57bbb19902a114796be64d57 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2580,39 +2580,45 @@ impl Workspace { fn build_serialized_docks(this: &Workspace, cx: &AppContext) -> DockStructure { let left_dock = this.left_dock.read(cx); let left_visible = left_dock.is_open(); - let left_size = left_dock - .active_panel() - .map(|panel| left_dock.panel_size(panel.as_ref())) - .flatten(); + let left_active_panel = left_dock.active_panel().and_then(|panel| { + Some( + cx.view_ui_name(panel.as_any().window_id(), panel.id())? + .to_string(), + ) + }); let right_dock = this.right_dock.read(cx); let right_visible = right_dock.is_open(); - let right_size = right_dock - .active_panel() - .map(|panel| right_dock.panel_size(panel.as_ref())) - .flatten(); + let right_active_panel = right_dock.active_panel().and_then(|panel| { + Some( + cx.view_ui_name(panel.as_any().window_id(), panel.id())? + .to_string(), + ) + }); let bottom_dock = this.bottom_dock.read(cx); let bottom_visible = bottom_dock.is_open(); - let bottom_size = bottom_dock - .active_panel() - .map(|panel| bottom_dock.panel_size(panel.as_ref())) - .flatten(); + let bottom_active_panel = bottom_dock.active_panel().and_then(|panel| { + Some( + cx.view_ui_name(panel.as_any().window_id(), panel.id())? + .to_string(), + ) + }); - DockStructure { + dbg!(DockStructure { left: DockData { visible: left_visible, - size: left_size, + active_panel: left_active_panel, }, right: DockData { visible: right_visible, - size: right_size, + active_panel: right_active_panel, }, bottom: DockData { visible: bottom_visible, - size: bottom_size, + active_panel: bottom_active_panel, }, - } + }) } if let Some(location) = self.location(cx) { @@ -2627,7 +2633,6 @@ impl Workspace { id: self.database_id, location, center_group, - left_sidebar_open: self.left_dock.read(cx).is_open(), bounds: Default::default(), display: Default::default(), docks, @@ -2686,22 +2691,15 @@ impl Workspace { let docks = serialized_workspace.docks; workspace.left_dock.update(cx, |dock, cx| { + dbg!(docks.left.visible); dock.set_open(docks.left.visible, cx); - if let Some(size) = docks.left.size { - dock.resize_active_panel(size, cx); - } + dbg!(dock.is_open()); }); workspace.right_dock.update(cx, |dock, cx| { dock.set_open(docks.right.visible, cx); - if let Some(size) = docks.right.size { - dock.resize_active_panel(size, cx); - } }); workspace.bottom_dock.update(cx, |dock, cx| { dock.set_open(docks.bottom.visible, cx); - if let Some(size) = docks.bottom.size { - dock.resize_active_panel(size, cx); - } }); cx.notify(); From 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 daac344a0a8fb4396da802ddc6f7325ffd47ea9f..d4d661b6a17beb36739108b16bb3ad9c5575c924 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 13809622f9e3191cf4b12eaaa0347c3f98e0f050..23b4984e55ae0e568f29086660e9db474c858a89 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 5c501f64ad16a34d72ec5400dde972f6b359648d..e115e1d5739f4e85cf814c96d6174ca25fbbca02 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,8 +1,9 @@ use crate::{StatusItemView, Workspace}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ - elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, Axis, - Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, + elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, + AppContext, Axis, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, + WindowContext, }; use serde::Deserialize; use settings::Settings; @@ -181,12 +182,19 @@ impl Dock { .map_or(false, |panel| panel.has_focus(cx)) } - pub fn panel_index(&self) -> Option { + pub fn panel_index_for_type(&self) -> Option { self.panel_entries .iter() .position(|entry| entry.panel.as_any().is::()) } + pub fn panel_index_for_ui_name(&self, ui_name: &str, cx: &AppContext) -> Option { + self.panel_entries.iter().position(|entry| { + let panel = entry.panel.as_any(); + cx.view_ui_name(panel.window_id(), panel.id()) == Some(ui_name) + }) + } + pub fn active_panel_index(&self) -> usize { self.active_panel_index } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 30a2a2f4e3234d6d57bbb19902a114796be64d57..01ed898de4c0a41be626b5edcf314265f691ea56 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1480,7 +1480,7 @@ impl Workspace { pub fn toggle_panel_focus(&mut self, cx: &mut ViewContext) { for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { - if let Some(panel_index) = dock.read(cx).panel_index::() { + if let Some(panel_index) = dock.read(cx).panel_index_for_type::() { let active_item = dock.update(cx, |dock, cx| { dock.set_open(true, cx); dock.activate_panel(panel_index, cx); @@ -2605,7 +2605,7 @@ impl Workspace { ) }); - dbg!(DockStructure { + DockStructure { left: DockData { visible: left_visible, active_panel: left_active_panel, @@ -2618,7 +2618,7 @@ impl Workspace { visible: bottom_visible, active_panel: bottom_active_panel, }, - }) + } } if let Some(location) = self.location(cx) { @@ -2691,15 +2691,28 @@ impl Workspace { let docks = serialized_workspace.docks; workspace.left_dock.update(cx, |dock, cx| { - dbg!(docks.left.visible); dock.set_open(docks.left.visible, cx); - dbg!(dock.is_open()); + if let Some(active_panel) = docks.left.active_panel { + if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { + dock.activate_panel(ix, cx); + } + } }); workspace.right_dock.update(cx, |dock, cx| { dock.set_open(docks.right.visible, cx); + if let Some(active_panel) = docks.right.active_panel { + if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { + dock.activate_panel(ix, cx); + } + } }); workspace.bottom_dock.update(cx, |dock, cx| { dock.set_open(docks.bottom.visible, cx); + if let Some(active_panel) = docks.bottom.active_panel { + if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { + dock.activate_panel(ix, cx); + } + } }); cx.notify(); From 924ec961ffa1a41b7f7e86829b501e0a64f32d4f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 May 2023 15:27:18 +0200 Subject: [PATCH 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 aeb26231d4651909f535549f1837e5bb9989a03e..cbc4b3bd45cf4131ae13e817dda4fdadf06f2691 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -194,7 +194,7 @@ impl TestServer { themes: ThemeRegistry::new((), cx.font_cache()), fs: fs.clone(), build_window_options: |_, _, _| Default::default(), - initialize_workspace: |_, _, _| unimplemented!(), + initialize_workspace: |_, _, _, _| unimplemented!(), background_actions: || &[], }); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 01ed898de4c0a41be626b5edcf314265f691ea56..b26acf9b4dd409e703e3102f5f1ac007eef40df6 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -361,7 +361,7 @@ pub struct AppState { pub fs: Arc, pub build_window_options: fn(Option, Option, &dyn Platform) -> WindowOptions<'static>, - pub initialize_workspace: fn(&mut Workspace, &Arc, &mut ViewContext), + pub initialize_workspace: fn(&mut Workspace, bool, &Arc, &mut ViewContext), pub background_actions: BackgroundActions, } @@ -383,7 +383,7 @@ impl AppState { fs, languages, user_store, - initialize_workspace: |_, _, _| {}, + initialize_workspace: |_, _, _, _| {}, build_window_options: |_, _, _| Default::default(), background_actions: || &[], }) @@ -733,6 +733,7 @@ impl Workspace { let build_workspace = |cx: &mut ViewContext, serialized_workspace: Option| { + let was_deserialized = serialized_workspace.is_some(); let mut workspace = Workspace::new( serialized_workspace, workspace_id, @@ -740,7 +741,12 @@ impl Workspace { app_state.clone(), cx, ); - (app_state.initialize_workspace)(&mut workspace, &app_state, cx); + (app_state.initialize_workspace)( + &mut workspace, + was_deserialized, + &app_state, + cx, + ); workspace }; @@ -2734,7 +2740,7 @@ impl Workspace { user_store: project.read(cx).user_store(), fs: project.read(cx).fs().clone(), build_window_options: |_, _, _| Default::default(), - initialize_workspace: |_, _, _| {}, + initialize_workspace: |_, _, _, _| {}, background_actions: || &[], }); Self::new(None, 0, project, app_state, cx) @@ -2998,28 +3004,11 @@ pub fn open_paths( .await, )) } else { - let contains_directory = - futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path))) - .await - .contains(&false); - - cx.update(|cx| { - let task = - Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx); - - cx.spawn(|mut cx| async move { - let (workspace, items) = task.await; - - workspace.update(&mut cx, |workspace, cx| { - if contains_directory { - workspace.toggle_dock(DockPosition::Left, cx); - } - })?; - - anyhow::Ok((workspace, items)) + Ok(cx + .update(|cx| { + Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx) }) - }) - .await + .await) } }) } @@ -3109,9 +3098,8 @@ pub fn join_remote_project( let (_, workspace) = cx.add_window( (app_state.build_window_options)(None, None, cx.platform().as_ref()), |cx| { - let mut workspace = - Workspace::new(Default::default(), 0, project, app_state.clone(), cx); - (app_state.initialize_workspace)(&mut workspace, &app_state, cx); + let mut workspace = Workspace::new(None, 0, project, app_state.clone(), cx); + (app_state.initialize_workspace)(&mut workspace, false, &app_state, cx); workspace }, ); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index fb7da693fc365ba12c4d8cf2202fd65fba0a8419..35a430f63f2008f88840c17f17bbdfe7e17f9ef7 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -35,7 +35,10 @@ use terminal_view::terminal_panel::{self, TerminalPanel}; use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; pub use workspace; -use workspace::{create_and_open_local_file, open_new, AppState, NewFile, NewWindow, Workspace}; +use workspace::{ + create_and_open_local_file, dock::PanelHandle, open_new, AppState, NewFile, NewWindow, + Workspace, +}; #[derive(Deserialize, Clone, PartialEq)] pub struct OpenBrowser { @@ -279,6 +282,7 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { pub fn initialize_workspace( workspace: &mut Workspace, + was_deserialized: bool, app_state: &Arc, cx: &mut ViewContext, ) { @@ -316,7 +320,21 @@ pub fn initialize_workspace( workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); let project_panel = ProjectPanel::new(workspace, cx); + let project_panel_position = project_panel.position(cx); workspace.add_panel(project_panel, cx); + if !was_deserialized + && workspace + .project() + .read(cx) + .visible_worktrees(cx) + .any(|tree| { + tree.read(cx) + .root_entry() + .map_or(false, |entry| entry.is_dir()) + }) + { + workspace.toggle_dock(project_panel_position, cx); + } let terminal_panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); workspace.add_panel(terminal_panel, cx); From e49281699c690897e7c96d8e7c6253b51259c717 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 May 2023 16:07:47 +0200 Subject: [PATCH 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 a89d5440749549e7cab9dbffd4394484a474e293..3476a6a155fefe895ba301d2fee4e47ddc7eeacd 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1401,6 +1401,8 @@ impl workspace::dock::Panel for ProjectPanel { fn set_zoomed(&mut self, _: bool, _: &mut ViewContext) {} + fn set_active(&mut self, _: bool, _: &mut ViewContext) {} + fn icon_path(&self) -> &'static str { "icons/folder_tree_16.svg" } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 4926a8775e84a1f3d1be262b1a819ef41e44bd30..4652b58072519b677df181fc85609b2ca28d9159 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1,9 +1,8 @@ use crate::TerminalView; use gpui::{ - actions, elements::*, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, - ViewHandle, WeakViewHandle, WindowContext, + actions, anyhow, elements::*, AppContext, Entity, Subscription, View, ViewContext, ViewHandle, + WeakViewHandle, WindowContext, }; -use project::Project; use settings::{settings_file::SettingsFile, Settings, TerminalDockPosition, WorkingDirectory}; use util::ResultExt; use workspace::{ @@ -26,7 +25,6 @@ pub enum Event { } pub struct TerminalPanel { - project: ModelHandle, pane: ViewHandle, workspace: WeakViewHandle, _subscriptions: Vec, @@ -86,7 +84,6 @@ impl TerminalPanel { cx.subscribe(&pane, Self::handle_pane_event), ]; Self { - project: workspace.project().clone(), pane, workspace: workspace.weak_handle(), _subscriptions: subscriptions, @@ -109,30 +106,34 @@ impl TerminalPanel { } fn add_terminal(&mut self, _: &workspace::NewTerminal, cx: &mut ViewContext) { - if let Some(workspace) = self.workspace.upgrade(cx) { - let working_directory_strategy = cx - .global::() - .terminal_overrides - .working_directory - .clone() - .unwrap_or(WorkingDirectory::CurrentProjectDirectory); - let working_directory = - crate::get_working_directory(workspace.read(cx), cx, working_directory_strategy); - let window_id = cx.window_id(); - if let Some(terminal) = self.project.update(cx, |project, cx| { - project - .create_terminal(working_directory, window_id, cx) - .log_err() - }) { - workspace.update(cx, |workspace, cx| { + let workspace = self.workspace.clone(); + cx.spawn(|this, mut cx| async move { + let pane = this.read_with(&cx, |this, _| this.pane.clone())?; + workspace.update(&mut cx, |workspace, cx| { + let working_directory_strategy = cx + .global::() + .terminal_overrides + .working_directory + .clone() + .unwrap_or(WorkingDirectory::CurrentProjectDirectory); + let working_directory = + crate::get_working_directory(workspace, cx, working_directory_strategy); + let window_id = cx.window_id(); + if let Some(terminal) = workspace.project().update(cx, |project, cx| { + project + .create_terminal(working_directory, window_id, cx) + .log_err() + }) { let terminal = Box::new(cx.add_view(|cx| { TerminalView::new(terminal, workspace.database_id(), cx) })); - Pane::add_item(workspace, &self.pane, terminal, true, true, None, cx); - }); - } - } + Pane::add_item(workspace, &pane, terminal, true, true, None, cx); + } + })?; + anyhow::Ok(()) + }) + .detach_and_log_err(cx); } } @@ -150,10 +151,6 @@ impl View for TerminalPanel { } fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { - if self.pane.read(cx).items_len() == 0 { - self.add_terminal(&Default::default(), cx) - } - if cx.is_self_focused() { cx.focus(&self.pane); } @@ -215,6 +212,12 @@ impl Panel for TerminalPanel { self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx)); } + fn set_active(&mut self, active: bool, cx: &mut ViewContext) { + if active && self.pane.read(cx).items_len() == 0 { + self.add_terminal(&Default::default(), cx) + } + } + fn icon_path(&self) -> &'static str { "icons/terminal_12.svg" } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index e115e1d5739f4e85cf814c96d6174ca25fbbca02..83bb54d3be3f554463279311281aedb2b8a1ea07 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -24,6 +24,7 @@ pub trait Panel: View { fn should_zoom_out_on_event(_: &Self::Event) -> bool; fn is_zoomed(&self, cx: &WindowContext) -> bool; fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext); + fn set_active(&mut self, active: bool, cx: &mut ViewContext); fn should_activate_on_event(_: &Self::Event) -> bool; fn should_close_on_event(_: &Self::Event) -> bool; fn has_focus(&self, cx: &WindowContext) -> bool; @@ -37,6 +38,7 @@ pub trait PanelHandle { fn set_position(&self, position: DockPosition, cx: &mut WindowContext); fn is_zoomed(&self, cx: &WindowContext) -> bool; fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext); + fn set_active(&self, active: bool, cx: &mut WindowContext); fn default_size(&self, cx: &WindowContext) -> f32; fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; @@ -77,6 +79,10 @@ where self.update(cx, |this, cx| this.set_zoomed(zoomed, cx)) } + fn set_active(&self, active: bool, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.set_active(active, cx)) + } + fn icon_path(&self, cx: &WindowContext) -> &'static str { self.read(cx).icon_path() } @@ -202,12 +208,16 @@ impl Dock { pub fn set_open(&mut self, open: bool, cx: &mut ViewContext) { if open != self.is_open { self.is_open = open; + if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + active_panel.panel.set_active(open, cx); + } + cx.notify(); } } pub fn toggle_open(&mut self, cx: &mut ViewContext) { - self.is_open = !self.is_open; + self.set_open(!self.is_open, cx); cx.notify(); } @@ -297,17 +307,18 @@ impl Dock { } pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { - self.active_panel_index = panel_ix; - cx.notify(); - } + if panel_ix != self.active_panel_index { + if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + active_panel.panel.set_active(false, cx); + } - pub fn toggle_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { - if self.active_panel_index == panel_ix { - self.is_open = false; - } else { self.active_panel_index = panel_ix; + if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + active_panel.panel.set_active(true, cx); + } + + cx.notify(); } - cx.notify(); } pub fn active_panel(&self) -> Option<&Rc> { @@ -615,6 +626,10 @@ pub(crate) mod test { unimplemented!() } + fn set_active(&mut self, _active: bool, _cx: &mut ViewContext) { + unimplemented!() + } + fn default_size(&self, _: &WindowContext) -> f32 { match self.position.axis() { Axis::Horizontal => 300., From 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 4af436a7b8d5eaad61d9599c46246b0f27152400..e956c4ca0d209276a07253388c497e67abcf7402 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 13809622f9e3191cf4b12eaaa0347c3f98e0f050..106dd4d81767d6a4bb956d5bd204cfe360523bcd 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 683ce8ad06671e2346628bcac46494dbfbfc3d7f..8606649398faf504c9a1e4eac9697a75b8a7daa0 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 368afcd16c8f134a0c9a40dc6e0e32a192cb7c39..c54f8d393b57988a8b357d207b37f4e0bf2874e3 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 28ad294798ee179952f03d5feb1b385e6ae2ac97..11703aba6ee0395af02e959646eaa95d27f78df9 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 d339ac32568c9f3ff3ee5941907ac41ac4409027..7129cc8b817e15f5903b82393432f4f19f91c656 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 f14e1c73552f766034ee1d98dd1a51fe3a00bf09..c31624b5fe2b8fc57ae7ccf9d184bc67bb8da656 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 190f1d96a8539ad41e224f346be35957547f570f..d6578c87ba6232461750de71091383618164f48e 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 d4d661b6a17beb36739108b16bb3ad9c5575c924..0000000000000000000000000000000000000000 --- 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 23b4984e55ae0e568f29086660e9db474c858a89..4b759c4240984da4b35b4f00dde9a48c61cef0c5 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 e7b1a84924eb2f5b25b519177ba9bcd5039eb000..69bcea8ce0426f7fe812618542336f3a018262f3 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 ed139c97d37032969af5c042e1ed8a27ad7ef7b7..4b4126fef2e1f8f4cb403c809ca62a136cff5afb 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 14e658e8f843bd71a915e774f9e6fc745c0b1e31..7ef388f7c087638c1ee3f5c2002ab3d2c3371dc7 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 17f86c153c9b51edba847f4fafb99a8efda9cbbb..d96d77eb005aa099642f01f6499db60363caaf6d 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 7129cc8b817e15f5903b82393432f4f19f91c656..3ae96f4751751fb06ce6250a31b138f93cd4cdf0 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 c31624b5fe2b8fc57ae7ccf9d184bc67bb8da656..4854be0c7bb1985770f699395a88fc1d3b89d9b3 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 73cd8f6a1d8ae4cbec8fe89b53d912a25026e759..fdd0da19700a408cc992f9f0d911112c036ebbbd 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 fc7bf4b8abad6732ab338e439db6f30ba2f49e83..325883b7c03fcbc8ed4d2c33df7f4ff00d4e3ef2 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 5a7644d98e6220d77bda66bcd6d6f35f895b67a6..7e81620e5cea9fc86e207394745aeadfd80982e4 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 b47982819a5a59dbc79791c04ec4918c7f99a00a..d877304f1d991eeb22a58022f6c9723be71ba1cd 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 2cb6637eadc876f375313f502d97ff89aaa3ee4f..1ec0ff4a635551e5877f302375e41d0b8ed0fb62 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 | 175 +++++++++++---------- 3 files changed, 246 insertions(+), 113 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 4652b58072519b677df181fc85609b2ca28d9159..1f4096880c9e88023ad57eea2be578ba28d52954 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1,15 +1,20 @@ use crate::TerminalView; +use db::kvp::KEY_VALUE_STORE; use gpui::{ - actions, anyhow, elements::*, AppContext, Entity, Subscription, View, ViewContext, ViewHandle, - WeakViewHandle, WindowContext, + actions, anyhow::Result, elements::*, serde_json, AppContext, AsyncAppContext, Entity, + Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; +use serde::{Deserialize, Serialize}; use settings::{settings_file::SettingsFile, Settings, TerminalDockPosition, WorkingDirectory}; -use util::ResultExt; +use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel}, + item::Item, pane, DraggedItem, Pane, Workspace, }; +const TERMINAL_PANEL_KEY: &'static str = "TerminalPanel"; + actions!(terminal_panel, [ToggleFocus]); pub fn init(cx: &mut AppContext) { @@ -27,6 +32,7 @@ pub enum Event { pub struct TerminalPanel { pane: ViewHandle, workspace: WeakViewHandle, + pending_serialization: Task>, _subscriptions: Vec, } @@ -86,10 +92,79 @@ impl TerminalPanel { Self { pane, workspace: workspace.weak_handle(), + pending_serialization: Task::ready(None), _subscriptions: subscriptions, } } + pub fn load( + workspace: WeakViewHandle, + cx: AsyncAppContext, + ) -> Task>> { + cx.spawn(|mut cx| async move { + let serialized_panel = if let Some(panel) = cx + .background() + .spawn(async move { KEY_VALUE_STORE.read_kvp(TERMINAL_PANEL_KEY) }) + .await? + { + Some(serde_json::from_str::(&panel)?) + } else { + None + }; + let (panel, pane, items) = workspace.update(&mut cx, |workspace, cx| { + let panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); + let items = if let Some(serialized_panel) = serialized_panel.as_ref() { + panel.update(cx, |panel, cx| { + panel.pane.update(cx, |_, cx| { + serialized_panel + .items + .iter() + .map(|item_id| { + TerminalView::deserialize( + workspace.project().clone(), + workspace.weak_handle(), + workspace.database_id(), + *item_id, + cx, + ) + }) + .collect::>() + }) + }) + } else { + Default::default() + }; + let pane = panel.read(cx).pane.clone(); + (panel, pane, items) + })?; + + let items = futures::future::join_all(items).await; + workspace.update(&mut cx, |workspace, cx| { + let active_item_id = serialized_panel + .as_ref() + .and_then(|panel| panel.active_item_id); + let mut active_ix = None; + for item in items { + if let Some(item) = item.log_err() { + let item_id = item.id(); + Pane::add_item(workspace, &pane, Box::new(item), false, false, None, cx); + if Some(item_id) == active_item_id { + active_ix = Some(pane.read(cx).items_len() - 1); + } + } + } + + if let Some(active_ix) = active_ix { + pane.update(cx, |pane, cx| { + pane.activate_item(active_ix, false, false, cx) + }); + } + })?; + + Ok(panel) + }) + } + fn handle_pane_event( &mut self, _pane: ViewHandle, @@ -97,6 +172,8 @@ impl TerminalPanel { cx: &mut ViewContext, ) { match event { + pane::Event::ActivateItem { .. } => self.serialize(cx), + pane::Event::RemoveItem { .. } => self.serialize(cx), pane::Event::Remove => cx.emit(Event::Close), pane::Event::ZoomIn => cx.emit(Event::ZoomIn), pane::Event::ZoomOut => cx.emit(Event::ZoomOut), @@ -131,10 +208,36 @@ impl TerminalPanel { Pane::add_item(workspace, &pane, terminal, true, true, None, cx); } })?; + this.update(&mut cx, |this, cx| this.serialize(cx))?; anyhow::Ok(()) }) .detach_and_log_err(cx); } + + fn serialize(&mut self, cx: &mut ViewContext) { + let items = self + .pane + .read(cx) + .items() + .map(|item| item.id()) + .collect::>(); + let active_item_id = self.pane.read(cx).active_item().map(|item| item.id()); + self.pending_serialization = cx.background().spawn( + async move { + KEY_VALUE_STORE + .write_kvp( + TERMINAL_PANEL_KEY.into(), + serde_json::to_string(&SerializedTerminalPanel { + items, + active_item_id, + })?, + ) + .await?; + anyhow::Ok(()) + } + .log_err(), + ); + } } impl Entity for TerminalPanel { @@ -255,3 +358,9 @@ impl Panel for TerminalPanel { matches!(event, Event::Focus) } } + +#[derive(Serialize, Deserialize)] +struct SerializedTerminalPanel { + items: Vec, + active_item_id: Option, +} diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index b26acf9b4dd409e703e3102f5f1ac007eef40df6..5dc7c066f7de1384a0ca339cd954a6d93427b8fe 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -361,7 +361,8 @@ pub struct AppState { pub fs: Arc, pub build_window_options: fn(Option, Option, &dyn Platform) -> WindowOptions<'static>, - pub initialize_workspace: fn(&mut Workspace, bool, &Arc, &mut ViewContext), + pub initialize_workspace: + fn(WeakViewHandle, bool, Arc, AsyncAppContext) -> Task>, pub background_actions: BackgroundActions, } @@ -383,7 +384,7 @@ impl AppState { fs, languages, user_store, - initialize_workspace: |_, _, _, _| {}, + initialize_workspace: |_, _, _, _| Task::ready(Ok(())), build_window_options: |_, _, _| Default::default(), background_actions: || &[], }) @@ -730,31 +731,19 @@ impl Workspace { )) }); - let build_workspace = - |cx: &mut ViewContext, - serialized_workspace: Option| { - let was_deserialized = serialized_workspace.is_some(); - let mut workspace = Workspace::new( - serialized_workspace, - workspace_id, - project_handle.clone(), - app_state.clone(), - cx, - ); - (app_state.initialize_workspace)( - &mut workspace, - was_deserialized, - &app_state, - cx, - ); - workspace - }; + let was_deserialized = serialized_workspace.is_some(); let workspace = requesting_window_id .and_then(|window_id| { cx.update(|cx| { cx.replace_root_view(window_id, |cx| { - build_workspace(cx, serialized_workspace.take()) + Workspace::new( + serialized_workspace.take(), + workspace_id, + project_handle.clone(), + app_state.clone(), + cx, + ) }) }) }) @@ -794,11 +783,28 @@ impl Workspace { // Use the serialized workspace to construct the new window cx.add_window( (app_state.build_window_options)(bounds, display, cx.platform().as_ref()), - |cx| build_workspace(cx, serialized_workspace), + |cx| { + Workspace::new( + serialized_workspace, + workspace_id, + project_handle.clone(), + app_state.clone(), + cx, + ) + }, ) .1 }); + (app_state.initialize_workspace)( + workspace.downgrade(), + was_deserialized, + app_state.clone(), + cx.clone(), + ) + .await + .log_err(); + let workspace = workspace.downgrade(); notify_if_database_failed(&workspace, &mut cx); @@ -2740,7 +2746,7 @@ impl Workspace { user_store: project.read(cx).user_store(), fs: project.read(cx).fs().clone(), build_window_options: |_, _, _| Default::default(), - initialize_workspace: |_, _, _, _| {}, + initialize_workspace: |_, _, _, _| Task::ready(Ok(())), background_actions: || &[], }); Self::new(None, 0, project, app_state, cx) @@ -3097,12 +3103,17 @@ pub fn join_remote_project( let (_, workspace) = cx.add_window( (app_state.build_window_options)(None, None, cx.platform().as_ref()), - |cx| { - let mut workspace = Workspace::new(None, 0, project, app_state.clone(), cx); - (app_state.initialize_workspace)(&mut workspace, false, &app_state, cx); - workspace - }, + |cx| Workspace::new(None, 0, project, app_state.clone(), cx), ); + (app_state.initialize_workspace)( + workspace.downgrade(), + false, + app_state.clone(), + cx.clone(), + ) + .await + .log_err(); + workspace.downgrade() }; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 35a430f63f2008f88840c17f17bbdfe7e17f9ef7..2608ea1ec41323a1aa77bef242742451d4c6b822 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -18,10 +18,11 @@ use feedback::{ use futures::StreamExt; use gpui::{ actions, + anyhow::{self, Result}, geometry::vector::vec2f, impl_actions, platform::{Platform, PromptLevel, TitlebarOptions, WindowBounds, WindowKind, WindowOptions}, - AppContext, ViewContext, + AppContext, AsyncAppContext, Task, ViewContext, WeakViewHandle, }; pub use lsp; pub use project; @@ -281,93 +282,105 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { } pub fn initialize_workspace( - workspace: &mut Workspace, + workspace_handle: WeakViewHandle, was_deserialized: bool, - app_state: &Arc, - cx: &mut ViewContext, -) { - let workspace_handle = cx.handle(); - cx.subscribe(&workspace_handle, { - move |workspace, _, event, cx| { - if let workspace::Event::PaneAdded(pane) = event { - pane.update(cx, |pane, cx| { - pane.toolbar().update(cx, |toolbar, cx| { - let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace)); - toolbar.add_item(breadcrumbs, cx); - let buffer_search_bar = cx.add_view(BufferSearchBar::new); - toolbar.add_item(buffer_search_bar, cx); - let project_search_bar = cx.add_view(|_| ProjectSearchBar::new()); - toolbar.add_item(project_search_bar, cx); - let submit_feedback_button = cx.add_view(|_| SubmitFeedbackButton::new()); - toolbar.add_item(submit_feedback_button, cx); - let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new()); - toolbar.add_item(feedback_info_text, cx); - let lsp_log_item = cx.add_view(|_| { - lsp_log::LspLogToolbarItemView::new(workspace.project().clone()) + app_state: Arc, + cx: AsyncAppContext, +) -> Task> { + cx.spawn(|mut cx| async move { + workspace_handle.update(&mut cx, |workspace, cx| { + let workspace_handle = cx.handle(); + cx.subscribe(&workspace_handle, { + move |workspace, _, event, cx| { + if let workspace::Event::PaneAdded(pane) = event { + pane.update(cx, |pane, cx| { + pane.toolbar().update(cx, |toolbar, cx| { + let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace)); + toolbar.add_item(breadcrumbs, cx); + let buffer_search_bar = cx.add_view(BufferSearchBar::new); + toolbar.add_item(buffer_search_bar, cx); + let project_search_bar = cx.add_view(|_| ProjectSearchBar::new()); + toolbar.add_item(project_search_bar, cx); + let submit_feedback_button = + cx.add_view(|_| SubmitFeedbackButton::new()); + toolbar.add_item(submit_feedback_button, cx); + let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new()); + toolbar.add_item(feedback_info_text, cx); + let lsp_log_item = cx.add_view(|_| { + lsp_log::LspLogToolbarItemView::new(workspace.project().clone()) + }); + toolbar.add_item(lsp_log_item, cx); + }) }); - toolbar.add_item(lsp_log_item, cx); - }) - }); - } - } - }) - .detach(); - - cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone())); - - let collab_titlebar_item = - cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); - workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); - - let project_panel = ProjectPanel::new(workspace, cx); - let project_panel_position = project_panel.position(cx); - workspace.add_panel(project_panel, cx); - if !was_deserialized - && workspace - .project() - .read(cx) - .visible_worktrees(cx) - .any(|tree| { - tree.read(cx) - .root_entry() - .map_or(false, |entry| entry.is_dir()) + } + } }) - { - workspace.toggle_dock(project_panel_position, cx); - } + .detach(); - let terminal_panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); - workspace.add_panel(terminal_panel, cx); - - let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx)); - let diagnostic_summary = - cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); - let activity_indicator = - activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx); - let active_buffer_language = - cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace)); - let feedback_button = - cx.add_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)); - let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); - workspace.status_bar().update(cx, |status_bar, cx| { - status_bar.add_left_item(diagnostic_summary, cx); - status_bar.add_left_item(activity_indicator, cx); - status_bar.add_right_item(feedback_button, cx); - status_bar.add_right_item(copilot, cx); - status_bar.add_right_item(active_buffer_language, cx); - status_bar.add_right_item(cursor_position, cx); - }); + cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone())); + + let collab_titlebar_item = + cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); + workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); + + let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx)); + let diagnostic_summary = + cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); + let activity_indicator = activity_indicator::ActivityIndicator::new( + workspace, + app_state.languages.clone(), + cx, + ); + let active_buffer_language = + cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace)); + let feedback_button = cx.add_view(|_| { + feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace) + }); + let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); + workspace.status_bar().update(cx, |status_bar, cx| { + status_bar.add_left_item(diagnostic_summary, cx); + status_bar.add_left_item(activity_indicator, cx); + status_bar.add_right_item(feedback_button, cx); + status_bar.add_right_item(copilot, cx); + status_bar.add_right_item(active_buffer_language, cx); + status_bar.add_right_item(cursor_position, cx); + }); - auto_update::notify_of_any_new_update(cx.weak_handle(), cx); + auto_update::notify_of_any_new_update(cx.weak_handle(), cx); - vim::observe_keystrokes(cx); + vim::observe_keystrokes(cx); - cx.on_window_should_close(|workspace, cx| { - if let Some(task) = workspace.close(&Default::default(), cx) { - task.detach_and_log_err(cx); - } - false - }); + cx.on_window_should_close(|workspace, cx| { + if let Some(task) = workspace.close(&Default::default(), cx) { + task.detach_and_log_err(cx); + } + false + }); + + let project_panel = ProjectPanel::new(workspace, cx); + let project_panel_position = project_panel.position(cx); + workspace.add_panel(project_panel, cx); + if !was_deserialized + && workspace + .project() + .read(cx) + .visible_worktrees(cx) + .any(|tree| { + tree.read(cx) + .root_entry() + .map_or(false, |entry| entry.is_dir()) + }) + { + workspace.toggle_dock(project_panel_position, cx); + } + })?; + + let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone()).await?; + workspace_handle.update(&mut cx, |workspace, cx| { + workspace.add_panel(terminal_panel, cx) + })?; + Ok(()) + }) } pub fn build_window_options( From 5a8fb18c20363da73179efc4e105648e7300a57e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 May 2023 19:35:00 +0200 Subject: [PATCH 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 35484b33090fb6eacc4cb41de8e233b92a19a16d..d8cf01d20ca6adf689c9f3464196b63eaf497f7b 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -42,6 +42,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { titlebar: None, center: false, focus: false, + show: true, kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index 8a41368276dded678f6df7b60732175eaf5c8fd0..fac2588e0b25a21cb282fd02221b9268e2cf5471 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -36,6 +36,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { titlebar: None, center: false, focus: false, + show: true, kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index da3c96956e613941f96d3e58077236f43876f25e..18211e4276dcdc61b58f9d2819b9b3de2d85735e 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -74,6 +74,7 @@ fn create_copilot_auth_window( titlebar: None, center: true, focus: true, + show: true, kind: WindowKind::Normal, is_movable: true, screen: None, diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 8fbcda02a32be4ba7e41e9e8452e94d7a1014c9e..9b4fd7ca510a49a12b439da29a6df8002f9dada0 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -173,6 +173,7 @@ pub struct WindowOptions<'a> { pub titlebar: Option>, pub center: bool, pub focus: bool, + pub show: bool, pub kind: WindowKind, pub is_movable: bool, pub screen: Option>, @@ -376,6 +377,7 @@ impl<'a> Default for WindowOptions<'a> { }), center: false, focus: true, + show: true, kind: WindowKind::Normal, is_movable: true, screen: None, diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index bcff08d0056a0c01ada1fa589a264ab910b3adc4..2d1de6df75b9db382eea9ce5f826580bb4e1b946 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -614,7 +614,7 @@ impl Window { } if options.focus { native_window.makeKeyAndOrderFront_(nil); - } else { + } else if options.show { native_window.orderFront_(nil); } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5dc7c066f7de1384a0ca339cd954a6d93427b8fe..dfb8560843ac34b1b9423bc18cb49872d6851a68 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -805,6 +805,8 @@ impl Workspace { .await .log_err(); + cx.update_window(workspace.window_id(), |cx| cx.activate_window()); + let workspace = workspace.downgrade(); notify_if_database_failed(&workspace, &mut cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 2608ea1ec41323a1aa77bef242742451d4c6b822..77a3bf350f130925f7fcf484036513fd6de24a7f 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -398,7 +398,8 @@ pub fn build_window_options( traffic_light_position: Some(vec2f(8., 8.)), }), center: false, - focus: true, + focus: false, + show: false, kind: WindowKind::Normal, is_movable: true, bounds, From 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 403d8934259e864a427e690fe497e2cd87f23ef6..61f12f7de971cfa566aaa28c11e3302f6eb6dcf6 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 403d8934259e864a427e690fe497e2cd87f23ef6..25470927a8fb8b2bb30c441e8f7d090f45e5dbc8 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 683ce8ad06671e2346628bcac46494dbfbfc3d7f..a599bac1108c86de51ad8e556f23b700aae312f5 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 61f12f7de971cfa566aaa28c11e3302f6eb6dcf6..f178d7162aa8d33a35aa858e032e762626d79579 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 c54f8d393b57988a8b357d207b37f4e0bf2874e3..39f1a9fd9ece29ed2128b6d2da483486e112fbc3 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 11703aba6ee0395af02e959646eaa95d27f78df9..8a5ce7a105d69ef7d5feb3e8a2cd9f9d39dbd28a 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 d96d77eb005aa099642f01f6499db60363caaf6d..915957401d440c40dd13d4b3364b0be76e09f3ef 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 26b52b848eaad6d982bc6a74e8c95c7c181f3e4b..7431199d28864942d1e8cc599122f86bd95c412e 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 7431199d28864942d1e8cc599122f86bd95c412e..24e8a004824c6fec4c99ddad5333de2fa8d71150 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 93c8f41cef67e56a5ba517954b606ed782d01186..ce0dd9e22212ad954e5398eff8c3a209b7ea4e78 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 39f1a9fd9ece29ed2128b6d2da483486e112fbc3..a196133f3ad8ce210d9fb1f31621c6b8d80693d6 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 | 239 ++++++++++++++++++--------------- 1 file changed, 134 insertions(+), 105 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 24e8a004824c6fec4c99ddad5333de2fa8d71150..b7cb82f628f8df7a02994c8128652946229f6a75 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 + 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, - snapshot: Snapshot, } #[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,6 +2007,46 @@ impl LocalSnapshot { Some(()) } + + fn ancestor_inodes_for_path(&self, path: &Path) -> TreeSet { + let mut inodes = TreeSet::default(); + for ancestor in path.ancestors().skip(1) { + if let Some(entry) = self.entry_for_path(ancestor) { + inodes.insert(entry.inode); + } + } + inodes + } + + fn ignore_stack_for_abs_path(&self, abs_path: &Path, is_dir: bool) -> Arc { + let mut new_ignores = Vec::new(); + for ancestor in abs_path.ancestors().skip(1) { + if let Some((ignore, _)) = self.ignores_by_parent_abs_path.get(ancestor) { + new_ignores.push((ancestor, Some(ignore.clone()))); + } else { + new_ignores.push((ancestor, None)); + } + } + + let mut ignore_stack = IgnoreStack::none(); + for (parent_abs_path, ignore) in new_ignores.into_iter().rev() { + if ignore_stack.is_abs_path_ignored(parent_abs_path, true) { + ignore_stack = IgnoreStack::all(); + break; + } else if let Some(ignore) = ignore { + ignore_stack = ignore_stack.append(parent_abs_path.into(), ignore); + } + } + + if ignore_stack.is_abs_path_ignored(abs_path, is_dir) { + ignore_stack = IgnoreStack::all(); + } + + ignore_stack + } +} + +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; @@ -2049,6 +2055,66 @@ impl LocalSnapshot { } } + 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; @@ -2081,43 +2147,6 @@ impl LocalSnapshot { } } } - - fn ancestor_inodes_for_path(&self, path: &Path) -> TreeSet { - let mut inodes = TreeSet::default(); - for ancestor in path.ancestors().skip(1) { - if let Some(entry) = self.entry_for_path(ancestor) { - inodes.insert(entry.inode); - } - } - inodes - } - - fn ignore_stack_for_abs_path(&self, abs_path: &Path, is_dir: bool) -> Arc { - let mut new_ignores = Vec::new(); - for ancestor in abs_path.ancestors().skip(1) { - if let Some((ignore, _)) = self.ignores_by_parent_abs_path.get(ancestor) { - new_ignores.push((ancestor, Some(ignore.clone()))); - } else { - new_ignores.push((ancestor, None)); - } - } - - let mut ignore_stack = IgnoreStack::none(); - for (parent_abs_path, ignore) in new_ignores.into_iter().rev() { - if ignore_stack.is_abs_path_ignored(parent_abs_path, true) { - ignore_stack = IgnoreStack::all(); - break; - } else if let Some(ignore) = ignore { - ignore_stack = ignore_stack.append(parent_abs_path.into(), ignore); - } - } - - if ignore_stack.is_abs_path_ignored(abs_path, is_dir) { - ignore_stack = IgnoreStack::all(); - } - - ignore_stack - } } async fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result { @@ -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 0a17fc8baf915fdfbf4663490b064649eeadabd8..35279913c5396bb6dc52c538ff992551a1690b83 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 b28af26f1679da459aaaab270233f8fabda00c2c..a2349649c54ebab22bad82fc212e306f546ded03 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 35279913c5396bb6dc52c538ff992551a1690b83..8a74a08c8623b2992b990b613e64431f55b93660 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 549d74a0b54b88a4a58399139f21d31ba33c9b09..5055cc6466620acdc09d4533c87fd0de93854464 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 8a74a08c8623b2992b990b613e64431f55b93660..7285db7366585a733173f0b9cad2ae13f14933be 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 5055cc6466620acdc09d4533c87fd0de93854464..345213812626e3d2eb93a739aaa4b593b9ef9f4f 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 b7cb82f628f8df7a02994c8128652946229f6a75..b4f188a2c33eab05d29926d0be8b273119eb750c 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 b4f188a2c33eab05d29926d0be8b273119eb750c..4d6853d2eed20b7edd6b27290e93fb918d15f117 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 15741a27f17ce202468680af1f9d9e12a996293d..f91cd999f9dd0068f98ae9f30fda3067469481d2 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 a2349649c54ebab22bad82fc212e306f546ded03..09a0d930c5d09c59a4c1a42f420be3f657c42787 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 15741a27f17ce202468680af1f9d9e12a996293d..f91cd999f9dd0068f98ae9f30fda3067469481d2 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 86bb7f4a26768260c1be6c74d7392d8aa6fe0236..278fd058feb2489a0050b994e90153fd68b9c5a8 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 311d9a2b8872cd3d63ad861cc986849d93d1e240..c9b83d805a4a87a65eda95f5b765c35841627d30 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 5108d2740875bd38dabea3c4ca06ca78784d1f35..a3f38a3dc096c6a4ce8365911313c31c147d06e7 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 d877304f1d991eeb22a58022f6c9723be71ba1cd..c98297c03648f7db9c307a592b4f7bf2dcfe279d 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 92e8cfcca79fc59f17a879c5c7ade0264a6b40bf..c542d1d13fd42c3cd2721c92981e74129556a554 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 dd81b05434af71680f85f18b644839d7a6b47875..329b290a3192c66545e7001635917e448acc1e81 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 41e47964910575a06fb0891c5e75d6ced13eefd7..4ec0a22b06800869bce8dcbd0ef13a5e646b2c8b 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 | 124 +++++++++++------- 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, 209 insertions(+), 97 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index d0625066d595c04d9fd99c51776b1018e8d05b6e..807510d70555ed446d9ecf9d9bc40a6aa652f162 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 0fb7a10a166b7dd31b7b5014b65d1454d33db479..7207e3c91c6da9f1e1ecfb37b94a560c875127d3 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) - } + let snapshot = self + .display_map + .update(cx, |display_map, cx| display_map.snapshot(cx)); + let selection = self.selections.newest::(cx); - fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext) { - self.go_to_hunk_impl(Direction::Prev, cx) + 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((wrapped_point.row + 1)..u32::MAX), + cx, + ); + } } - pub fn go_to_hunk_impl(&mut self, direction: Direction, cx: &mut ViewContext) { + 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); - 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 { - snapshot - .buffer_snapshot - .git_diff_hunks_in_range(initial_point.row..u32::MAX, false) - } else { + 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(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(); + .git_diff_hunks_in_range_rev(0..wrapped_point.row), + cx, + ); + } + } - 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]); - }); + 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(); - true - } else { - false - } - } + 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]); + }); - 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); + true + } else { + false } } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7285db7366585a733173f0b9cad2ae13f14933be..57dc3293f6f9dc322c1a7744f97b21000fb34c0c 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 f3e8fd7440dd3eb050d8efe7f3b738c0262ac485..1423473e1ad8c3d952d6bb47ef95d20286e0a46f 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 ced99a3f23407524df3baae4ed4b0f25fa84b3fd..e520562ebba1d410e58e9ae3a9438899701a62c5 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 09a0d930c5d09c59a4c1a42f420be3f657c42787..8704f850055aa4edb5f03109ecd1fa5d18d4aa5c 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 aee646091a407d327579e7a8497d588e17583ede..3a977024873baa82d39e50bae06a3e3e43b7f254 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 4d6853d2eed20b7edd6b27290e93fb918d15f117..4f898aa91d2558c606b39dda29b83ea6ff24a3d8 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 278fd058feb2489a0050b994e90153fd68b9c5a8..dcfaf818d1f97f98002d60fecf90533d50c2969b 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 917383234a322f1145e47c218ca0937cae8b339f..43d16e6b9bd56bba3fc60d96177aec4f655b631a 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 ad3febd68c6146bee0688bcdc32060bb00caeb65..50fbaa64ee49de6ab1e682856eaec3ed16cfb4bc 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 e74e14ff4ca8a35c8d270246052a5280404950eb..ddd6ab0009d6fa07925d5ce44f6fff36013e17c6 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 cae3fa25caf1907e28f7921689ee02407cfa1845..6f6be7427bd8054e9506fa72d1f4fb4d0fa367fe 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 441f7ef7e40fbc5ea4b7ac6c1965c33090b45b5d..b32b4aaf13f6d754cbea71198e0f5fd1642ea01a 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 c1d9bde89e19bccc1404b8a292543f29185eb87d..b7cbc62559f83c45adbc9447ca029c9c38462b03 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 e7b3d8d4bed3411d3a5145ea6b482087aa407760..f6e213f25fca361c71137c1bf80029c27e3b1cfd 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 8741f0a4cffbf9d1108a6611f5011d2f7325774b..6f47057b442df907c3dcc42fd78d8f104301f24b 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 95272b063e798c6cc8a2ea9aa3f7278ea9c1b755..f4e2b849fa6c5186bd4db076812cf522b021be2a 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 b723cd788cfbb30912595eedaff89eef21406982..54e4b15ad54ed809d22784a0fe37e76548236048 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 7e23e42b2633c4af743d2586fea0db3ebafcbe7b..85939634adbdd66e02d3f3dd8a3865dca50817c7 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 d9e7546f34464a3b611b1f4457a26efad9286e1a..14f8853c9c0aa9177365bb49b3f366d8fc3f2c0d 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 3a253178702fae6e7221febc2f6a599c5c71dc2c..a42d6c550ee82188c44f43403973c3f2e5de7af8 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 ac3a85d89a48a777cd852366b588bb8d05fa45a5..377f64aad6f1579dfe9ebb50fb0e8b9c683e0f01 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 65f5151584bea831d1c4744c150b994ea86fa72e..ea01f822a7b4e1ace0ce25dca5d6df3935d5a857 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 ac1dcb5b6ed6c10c8349ad972a43799d3ce51193..4690c88a3c59dd3e22dd31094f52bd276092785a 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 4690c88a3c59dd3e22dd31094f52bd276092785a..47623125f9c28fa12898a21d1a629c69fcf6158e 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 | 1040 +++++++++++++++++------------------ 1 file changed, 520 insertions(+), 520 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 246b6995587b906efd8583cce6f1b5dfe4b70a8f..1fca19a83f7d4033719a887a46a0c80c2ea24fa7 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -1,536 +1,536 @@ [ - // Standard macOS bindings - { - "bindings": { - "up": "menu::SelectPrev", - "pageup": "menu::SelectFirst", - "shift-pageup": "menu::SelectFirst", - "ctrl-p": "menu::SelectPrev", - "down": "menu::SelectNext", - "pagedown": "menu::SelectLast", - "shift-pagedown": "menu::SelectFirst", - "ctrl-n": "menu::SelectNext", - "cmd-up": "menu::SelectFirst", - "cmd-down": "menu::SelectLast", - "enter": "menu::Confirm", - "escape": "menu::Cancel", - "ctrl-c": "menu::Cancel", - "cmd-{": "pane::ActivatePrevItem", - "cmd-}": "pane::ActivateNextItem", - "alt-cmd-left": "pane::ActivatePrevItem", - "alt-cmd-right": "pane::ActivateNextItem", - "cmd-w": "pane::CloseActiveItem", - "alt-cmd-t": "pane::CloseInactiveItems", - "cmd-k u": "pane::CloseCleanItems", - "cmd-k cmd-w": "pane::CloseAllItems", - "cmd-shift-w": "workspace::CloseWindow", - "cmd-s": "workspace::Save", - "cmd-shift-s": "workspace::SaveAs", - "cmd-=": "zed::IncreaseBufferFontSize", - "cmd--": "zed::DecreaseBufferFontSize", - "cmd-0": "zed::ResetBufferFontSize", - "cmd-,": "zed::OpenSettings", - "cmd-q": "zed::Quit", - "cmd-h": "zed::Hide", - "alt-cmd-h": "zed::HideOthers", - "cmd-m": "zed::Minimize", - "ctrl-cmd-f": "zed::ToggleFullScreen", - "cmd-n": "workspace::NewFile", - "cmd-shift-n": "workspace::NewWindow", - "cmd-o": "workspace::Open", - "alt-cmd-o": "projects::OpenRecent", - "ctrl-~": "workspace::NewTerminal", - "ctrl-`": "terminal_panel::ToggleFocus" - } - }, - { - "context": "Editor", - "bindings": { - "escape": "editor::Cancel", - "backspace": "editor::Backspace", - "shift-backspace": "editor::Backspace", - "ctrl-h": "editor::Backspace", - "delete": "editor::Delete", - "ctrl-d": "editor::Delete", - "tab": "editor::Tab", - "shift-tab": "editor::TabPrev", - "ctrl-k": "editor::CutToEndOfLine", - "ctrl-t": "editor::Transpose", - "cmd-backspace": "editor::DeleteToBeginningOfLine", - "cmd-delete": "editor::DeleteToEndOfLine", - "alt-backspace": "editor::DeleteToPreviousWordStart", - "alt-delete": "editor::DeleteToNextWordEnd", - "alt-h": "editor::DeleteToPreviousWordStart", - "alt-d": "editor::DeleteToNextWordEnd", - "cmd-x": "editor::Cut", - "cmd-c": "editor::Copy", - "cmd-v": "editor::Paste", - "cmd-z": "editor::Undo", - "cmd-shift-z": "editor::Redo", - "up": "editor::MoveUp", - "pageup": "editor::PageUp", - "shift-pageup": "editor::MovePageUp", - "home": "editor::MoveToBeginningOfLine", - "down": "editor::MoveDown", - "pagedown": "editor::PageDown", - "shift-pagedown": "editor::MovePageDown", - "end": "editor::MoveToEndOfLine", - "left": "editor::MoveLeft", - "right": "editor::MoveRight", - "ctrl-p": "editor::MoveUp", - "ctrl-n": "editor::MoveDown", - "ctrl-b": "editor::MoveLeft", - "ctrl-f": "editor::MoveRight", - "ctrl-l": "editor::NextScreen", - "alt-left": "editor::MoveToPreviousWordStart", - "alt-b": "editor::MoveToPreviousWordStart", - "alt-right": "editor::MoveToNextWordEnd", - "alt-f": "editor::MoveToNextWordEnd", - "cmd-left": "editor::MoveToBeginningOfLine", - "ctrl-a": "editor::MoveToBeginningOfLine", - "cmd-right": "editor::MoveToEndOfLine", - "ctrl-e": "editor::MoveToEndOfLine", - "cmd-up": "editor::MoveToBeginning", - "cmd-down": "editor::MoveToEnd", - "shift-up": "editor::SelectUp", - "ctrl-shift-p": "editor::SelectUp", - "shift-down": "editor::SelectDown", - "ctrl-shift-n": "editor::SelectDown", - "shift-left": "editor::SelectLeft", - "ctrl-shift-b": "editor::SelectLeft", - "shift-right": "editor::SelectRight", - "ctrl-shift-f": "editor::SelectRight", - "alt-shift-left": "editor::SelectToPreviousWordStart", - "alt-shift-b": "editor::SelectToPreviousWordStart", - "alt-shift-right": "editor::SelectToNextWordEnd", - "alt-shift-f": "editor::SelectToNextWordEnd", - "cmd-shift-up": "editor::SelectToBeginning", - "cmd-shift-down": "editor::SelectToEnd", - "cmd-a": "editor::SelectAll", - "cmd-l": "editor::SelectLine", - "cmd-shift-i": "editor::Format", - "cmd-shift-left": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true - } - ], - "shift-home": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true - } - ], - "ctrl-shift-a": [ - "editor::SelectToBeginningOfLine", - { - "stop_at_soft_wraps": true - } - ], - "cmd-shift-right": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true - } - ], - "shift-end": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true - } - ], - "ctrl-shift-e": [ - "editor::SelectToEndOfLine", - { - "stop_at_soft_wraps": true - } - ], - "ctrl-v": [ - "editor::MovePageDown", - { - "center_cursor": true - } - ], - "alt-v": [ - "editor::MovePageUp", - { - "center_cursor": true - } - ], - "ctrl-cmd-space": "editor::ShowCharacterPalette" - } - }, - { - "context": "Editor && mode == full", - "bindings": { - "enter": "editor::Newline", - "cmd-shift-enter": "editor::NewlineAbove", - "cmd-enter": "editor::NewlineBelow", - "alt-z": "editor::ToggleSoftWrap", - "cmd-f": [ - "buffer_search::Deploy", - { - "focus": true - } - ], - "cmd-e": [ - "buffer_search::Deploy", - { - "focus": false - } - ], - "alt-\\": "copilot::Suggest", - "alt-]": "copilot::NextSuggestion", - "alt-[": "copilot::PreviousSuggestion" - } - }, - { - "context": "Editor && mode == auto_height", - "bindings": { - "alt-enter": "editor::Newline", - "cmd-alt-enter": "editor::NewlineBelow" - } - }, - { - "context": "BufferSearchBar > Editor", - "bindings": { - "escape": "buffer_search::Dismiss", - "tab": "buffer_search::FocusEditor", - "enter": "search::SelectNextMatch", - "shift-enter": "search::SelectPrevMatch" - } - }, - { - "context": "ProjectSearchBar > Editor", - "bindings": { - "escape": "project_search::ToggleFocus" - } - }, - { - "context": "ProjectSearchView > Editor", - "bindings": { - "escape": "project_search::ToggleFocus" - } - }, - { - "context": "Pane", - "bindings": { - "cmd-f": "project_search::ToggleFocus", - "cmd-g": "search::SelectNextMatch", - "cmd-shift-g": "search::SelectPrevMatch", - "alt-cmd-c": "search::ToggleCaseSensitive", - "alt-cmd-w": "search::ToggleWholeWord", - "alt-cmd-r": "search::ToggleRegex", - "shift-escape": "workspace::ToggleZoom" - } - }, - // Bindings from VS Code - { - "context": "Editor", - "bindings": { - "cmd-[": "editor::Outdent", - "cmd-]": "editor::Indent", - "cmd-alt-up": "editor::AddSelectionAbove", - "cmd-ctrl-p": "editor::AddSelectionAbove", - "cmd-alt-down": "editor::AddSelectionBelow", - "cmd-ctrl-n": "editor::AddSelectionBelow", - "cmd-d": [ - "editor::SelectNext", - { - "replace_newest": false - } - ], - "cmd-k cmd-d": [ - "editor::SelectNext", - { - "replace_newest": true - } - ], - "cmd-k cmd-i": "editor::Hover", - "cmd-/": [ - "editor::ToggleComments", - { - "advance_downwards": false - } - ], - "alt-up": "editor::SelectLargerSyntaxNode", - "alt-down": "editor::SelectSmallerSyntaxNode", - "cmd-u": "editor::UndoSelection", - "cmd-shift-u": "editor::RedoSelection", - "f8": "editor::GoToDiagnostic", - "shift-f8": "editor::GoToPrevDiagnostic", - "f2": "editor::Rename", - "f12": "editor::GoToDefinition", - "cmd-f12": "editor::GoToTypeDefinition", - "alt-shift-f12": "editor::FindAllReferences", - "ctrl-m": "editor::MoveToEnclosingBracket", - "alt-cmd-[": "editor::Fold", - "alt-cmd-]": "editor::UnfoldLines", - "ctrl-space": "editor::ShowCompletions", - "cmd-.": "editor::ToggleCodeActions", - "alt-cmd-r": "editor::RevealInFinder" - } - }, - { - "context": "Editor && mode == full", - "bindings": { - "cmd-shift-o": "outline::Toggle", - "ctrl-g": "go_to_line::Toggle" - } - }, - { - "context": "Pane", - "bindings": { - "ctrl-1": [ - "pane::ActivateItem", - 0 - ], - "ctrl-2": [ - "pane::ActivateItem", - 1 - ], - "ctrl-3": [ - "pane::ActivateItem", - 2 - ], - "ctrl-4": [ - "pane::ActivateItem", - 3 - ], - "ctrl-5": [ - "pane::ActivateItem", - 4 - ], - "ctrl-6": [ - "pane::ActivateItem", - 5 - ], - "ctrl-7": [ - "pane::ActivateItem", - 6 - ], - "ctrl-8": [ - "pane::ActivateItem", - 7 - ], - "ctrl-9": [ - "pane::ActivateItem", - 8 - ], - "ctrl-0": "pane::ActivateLastItem", - "ctrl--": "pane::GoBack", - "ctrl-_": "pane::GoForward", - "cmd-shift-t": "pane::ReopenClosedItem", - "cmd-shift-f": "project_search::ToggleFocus" + // Standard macOS bindings + { + "bindings": { + "up": "menu::SelectPrev", + "pageup": "menu::SelectFirst", + "shift-pageup": "menu::SelectFirst", + "ctrl-p": "menu::SelectPrev", + "down": "menu::SelectNext", + "pagedown": "menu::SelectLast", + "shift-pagedown": "menu::SelectFirst", + "ctrl-n": "menu::SelectNext", + "cmd-up": "menu::SelectFirst", + "cmd-down": "menu::SelectLast", + "enter": "menu::Confirm", + "escape": "menu::Cancel", + "ctrl-c": "menu::Cancel", + "cmd-{": "pane::ActivatePrevItem", + "cmd-}": "pane::ActivateNextItem", + "alt-cmd-left": "pane::ActivatePrevItem", + "alt-cmd-right": "pane::ActivateNextItem", + "cmd-w": "pane::CloseActiveItem", + "alt-cmd-t": "pane::CloseInactiveItems", + "cmd-k u": "pane::CloseCleanItems", + "cmd-k cmd-w": "pane::CloseAllItems", + "cmd-shift-w": "workspace::CloseWindow", + "cmd-s": "workspace::Save", + "cmd-shift-s": "workspace::SaveAs", + "cmd-=": "zed::IncreaseBufferFontSize", + "cmd--": "zed::DecreaseBufferFontSize", + "cmd-0": "zed::ResetBufferFontSize", + "cmd-,": "zed::OpenSettings", + "cmd-q": "zed::Quit", + "cmd-h": "zed::Hide", + "alt-cmd-h": "zed::HideOthers", + "cmd-m": "zed::Minimize", + "ctrl-cmd-f": "zed::ToggleFullScreen", + "cmd-n": "workspace::NewFile", + "cmd-shift-n": "workspace::NewWindow", + "cmd-o": "workspace::Open", + "alt-cmd-o": "projects::OpenRecent", + "ctrl-~": "workspace::NewTerminal", + "ctrl-`": "terminal_panel::ToggleFocus" + } + }, + { + "context": "Editor", + "bindings": { + "escape": "editor::Cancel", + "backspace": "editor::Backspace", + "shift-backspace": "editor::Backspace", + "ctrl-h": "editor::Backspace", + "delete": "editor::Delete", + "ctrl-d": "editor::Delete", + "tab": "editor::Tab", + "shift-tab": "editor::TabPrev", + "ctrl-k": "editor::CutToEndOfLine", + "ctrl-t": "editor::Transpose", + "cmd-backspace": "editor::DeleteToBeginningOfLine", + "cmd-delete": "editor::DeleteToEndOfLine", + "alt-backspace": "editor::DeleteToPreviousWordStart", + "alt-delete": "editor::DeleteToNextWordEnd", + "alt-h": "editor::DeleteToPreviousWordStart", + "alt-d": "editor::DeleteToNextWordEnd", + "cmd-x": "editor::Cut", + "cmd-c": "editor::Copy", + "cmd-v": "editor::Paste", + "cmd-z": "editor::Undo", + "cmd-shift-z": "editor::Redo", + "up": "editor::MoveUp", + "pageup": "editor::PageUp", + "shift-pageup": "editor::MovePageUp", + "home": "editor::MoveToBeginningOfLine", + "down": "editor::MoveDown", + "pagedown": "editor::PageDown", + "shift-pagedown": "editor::MovePageDown", + "end": "editor::MoveToEndOfLine", + "left": "editor::MoveLeft", + "right": "editor::MoveRight", + "ctrl-p": "editor::MoveUp", + "ctrl-n": "editor::MoveDown", + "ctrl-b": "editor::MoveLeft", + "ctrl-f": "editor::MoveRight", + "ctrl-l": "editor::NextScreen", + "alt-left": "editor::MoveToPreviousWordStart", + "alt-b": "editor::MoveToPreviousWordStart", + "alt-right": "editor::MoveToNextWordEnd", + "alt-f": "editor::MoveToNextWordEnd", + "cmd-left": "editor::MoveToBeginningOfLine", + "ctrl-a": "editor::MoveToBeginningOfLine", + "cmd-right": "editor::MoveToEndOfLine", + "ctrl-e": "editor::MoveToEndOfLine", + "cmd-up": "editor::MoveToBeginning", + "cmd-down": "editor::MoveToEnd", + "shift-up": "editor::SelectUp", + "ctrl-shift-p": "editor::SelectUp", + "shift-down": "editor::SelectDown", + "ctrl-shift-n": "editor::SelectDown", + "shift-left": "editor::SelectLeft", + "ctrl-shift-b": "editor::SelectLeft", + "shift-right": "editor::SelectRight", + "ctrl-shift-f": "editor::SelectRight", + "alt-shift-left": "editor::SelectToPreviousWordStart", + "alt-shift-b": "editor::SelectToPreviousWordStart", + "alt-shift-right": "editor::SelectToNextWordEnd", + "alt-shift-f": "editor::SelectToNextWordEnd", + "cmd-shift-up": "editor::SelectToBeginning", + "cmd-shift-down": "editor::SelectToEnd", + "cmd-a": "editor::SelectAll", + "cmd-l": "editor::SelectLine", + "cmd-shift-i": "editor::Format", + "cmd-shift-left": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true } - }, - { - "context": "Workspace", - "bindings": { - "cmd-1": [ - "workspace::ActivatePane", - 0 - ], - "cmd-2": [ - "workspace::ActivatePane", - 1 - ], - "cmd-3": [ - "workspace::ActivatePane", - 2 - ], - "cmd-4": [ - "workspace::ActivatePane", - 3 - ], - "cmd-5": [ - "workspace::ActivatePane", - 4 - ], - "cmd-6": [ - "workspace::ActivatePane", - 5 - ], - "cmd-7": [ - "workspace::ActivatePane", - 6 - ], - "cmd-8": [ - "workspace::ActivatePane", - 7 - ], - "cmd-9": [ - "workspace::ActivatePane", - 8 - ], - "cmd-b": "workspace::ToggleLeftDock", - "cmd-shift-f": "workspace::NewSearch", - "cmd-k cmd-t": "theme_selector::Toggle", - "cmd-k cmd-s": "zed::OpenKeymap", - "cmd-t": "project_symbols::Toggle", - "cmd-p": "file_finder::Toggle", - "cmd-shift-p": "command_palette::Toggle", - "cmd-shift-m": "diagnostics::Deploy", - "cmd-shift-e": "project_panel::ToggleFocus", - "cmd-alt-s": "workspace::SaveAll", - "cmd-k m": "language_selector::Toggle" + ], + "shift-home": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true } - }, - // Bindings from Sublime Text - { - "context": "Editor", - "bindings": { - "ctrl-shift-k": "editor::DeleteLine", - "cmd-shift-d": "editor::DuplicateLine", - "cmd-shift-l": "editor::SplitSelectionIntoLines", - "ctrl-cmd-up": "editor::MoveLineUp", - "ctrl-cmd-down": "editor::MoveLineDown", - "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart", - "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", - "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", - "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", - "ctrl-alt-left": "editor::MoveToPreviousSubwordStart", - "ctrl-alt-b": "editor::MoveToPreviousSubwordStart", - "ctrl-alt-right": "editor::MoveToNextSubwordEnd", - "ctrl-alt-f": "editor::MoveToNextSubwordEnd", - "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart", - "ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart", - "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd", - "ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd" + ], + "ctrl-shift-a": [ + "editor::SelectToBeginningOfLine", + { + "stop_at_soft_wraps": true } - }, - { - "bindings": { - "cmd-k cmd-left": "workspace::ActivatePreviousPane", - "cmd-k cmd-right": "workspace::ActivateNextPane" + ], + "cmd-shift-right": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true } - }, - // Bindings from Atom - { - "context": "Pane", - "bindings": { - "cmd-k up": "pane::SplitUp", - "cmd-k down": "pane::SplitDown", - "cmd-k left": "pane::SplitLeft", - "cmd-k right": "pane::SplitRight" + ], + "shift-end": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true } - }, - // Bindings that should be unified with bindings for more general actions - { - "context": "Editor && renaming", - "bindings": { - "enter": "editor::ConfirmRename" + ], + "ctrl-shift-e": [ + "editor::SelectToEndOfLine", + { + "stop_at_soft_wraps": true } - }, - { - "context": "Editor && showing_completions", - "bindings": { - "enter": "editor::ConfirmCompletion", - "tab": "editor::ConfirmCompletion" + ], + "ctrl-v": [ + "editor::MovePageDown", + { + "center_cursor": true } - }, - { - "context": "Editor && showing_code_actions", - "bindings": { - "enter": "editor::ConfirmCodeAction" + ], + "alt-v": [ + "editor::MovePageUp", + { + "center_cursor": true } - }, - // Custom bindings - { - "bindings": { - "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator", - "cmd-shift-c": "collab::ToggleContactsMenu", - "cmd-alt-i": "zed::DebugElements" + ], + "ctrl-cmd-space": "editor::ShowCharacterPalette" + } + }, + { + "context": "Editor && mode == full", + "bindings": { + "enter": "editor::Newline", + "cmd-shift-enter": "editor::NewlineAbove", + "cmd-enter": "editor::NewlineBelow", + "alt-z": "editor::ToggleSoftWrap", + "cmd-f": [ + "buffer_search::Deploy", + { + "focus": true } - }, - { - "context": "Editor", - "bindings": { - "alt-enter": "editor::OpenExcerpts", - "cmd-f8": "editor::GoToHunk", - "cmd-shift-f8": "editor::GoToPrevHunk" + ], + "cmd-e": [ + "buffer_search::Deploy", + { + "focus": false } - }, - { - "context": "ProjectSearchBar", - "bindings": { - "cmd-enter": "project_search::SearchInNew" + ], + "alt-\\": "copilot::Suggest", + "alt-]": "copilot::NextSuggestion", + "alt-[": "copilot::PreviousSuggestion" + } + }, + { + "context": "Editor && mode == auto_height", + "bindings": { + "alt-enter": "editor::Newline", + "cmd-alt-enter": "editor::NewlineBelow" + } + }, + { + "context": "BufferSearchBar > Editor", + "bindings": { + "escape": "buffer_search::Dismiss", + "tab": "buffer_search::FocusEditor", + "enter": "search::SelectNextMatch", + "shift-enter": "search::SelectPrevMatch" + } + }, + { + "context": "ProjectSearchBar > Editor", + "bindings": { + "escape": "project_search::ToggleFocus" + } + }, + { + "context": "ProjectSearchView > Editor", + "bindings": { + "escape": "project_search::ToggleFocus" + } + }, + { + "context": "Pane", + "bindings": { + "cmd-f": "project_search::ToggleFocus", + "cmd-g": "search::SelectNextMatch", + "cmd-shift-g": "search::SelectPrevMatch", + "alt-cmd-c": "search::ToggleCaseSensitive", + "alt-cmd-w": "search::ToggleWholeWord", + "alt-cmd-r": "search::ToggleRegex", + "shift-escape": "workspace::ToggleZoom" + } + }, + // Bindings from VS Code + { + "context": "Editor", + "bindings": { + "cmd-[": "editor::Outdent", + "cmd-]": "editor::Indent", + "cmd-alt-up": "editor::AddSelectionAbove", + "cmd-ctrl-p": "editor::AddSelectionAbove", + "cmd-alt-down": "editor::AddSelectionBelow", + "cmd-ctrl-n": "editor::AddSelectionBelow", + "cmd-d": [ + "editor::SelectNext", + { + "replace_newest": false } - }, - { - "context": "ProjectPanel", - "bindings": { - "left": "project_panel::CollapseSelectedEntry", - "right": "project_panel::ExpandSelectedEntry", - "cmd-x": "project_panel::Cut", - "cmd-c": "project_panel::Copy", - "cmd-v": "project_panel::Paste", - "cmd-alt-c": "project_panel::CopyPath", - "alt-cmd-shift-c": "project_panel::CopyRelativePath", - "f2": "project_panel::Rename", - "backspace": "project_panel::Delete", - "alt-cmd-r": "project_panel::RevealInFinder" + ], + "cmd-k cmd-d": [ + "editor::SelectNext", + { + "replace_newest": true } - }, - { - "context": "Terminal", - "bindings": { - "ctrl-cmd-space": "terminal::ShowCharacterPalette", - "cmd-c": "terminal::Copy", - "cmd-v": "terminal::Paste", - "cmd-k": "terminal::Clear", - // Some nice conveniences - "cmd-backspace": [ - "terminal::SendText", - "\u0015" - ], - "cmd-right": [ - "terminal::SendText", - "\u0005" - ], - "cmd-left": [ - "terminal::SendText", - "\u0001" - ], - // Terminal.app compatability - "alt-left": [ - "terminal::SendText", - "\u001bb" - ], - "alt-right": [ - "terminal::SendText", - "\u001bf" - ], - // There are conflicting bindings for these keys in the global context. - // these bindings override them, remove at your own risk: - "up": [ - "terminal::SendKeystroke", - "up" - ], - "pageup": [ - "terminal::SendKeystroke", - "pageup" - ], - "down": [ - "terminal::SendKeystroke", - "down" - ], - "pagedown": [ - "terminal::SendKeystroke", - "pagedown" - ], - "escape": [ - "terminal::SendKeystroke", - "escape" - ], - "enter": [ - "terminal::SendKeystroke", - "enter" - ], - "ctrl-c": [ - "terminal::SendKeystroke", - "ctrl-c" - ] + ], + "cmd-k cmd-i": "editor::Hover", + "cmd-/": [ + "editor::ToggleComments", + { + "advance_downwards": false } + ], + "alt-up": "editor::SelectLargerSyntaxNode", + "alt-down": "editor::SelectSmallerSyntaxNode", + "cmd-u": "editor::UndoSelection", + "cmd-shift-u": "editor::RedoSelection", + "f8": "editor::GoToDiagnostic", + "shift-f8": "editor::GoToPrevDiagnostic", + "f2": "editor::Rename", + "f12": "editor::GoToDefinition", + "cmd-f12": "editor::GoToTypeDefinition", + "alt-shift-f12": "editor::FindAllReferences", + "ctrl-m": "editor::MoveToEnclosingBracket", + "alt-cmd-[": "editor::Fold", + "alt-cmd-]": "editor::UnfoldLines", + "ctrl-space": "editor::ShowCompletions", + "cmd-.": "editor::ToggleCodeActions", + "alt-cmd-r": "editor::RevealInFinder" + } + }, + { + "context": "Editor && mode == full", + "bindings": { + "cmd-shift-o": "outline::Toggle", + "ctrl-g": "go_to_line::Toggle" + } + }, + { + "context": "Pane", + "bindings": { + "ctrl-1": [ + "pane::ActivateItem", + 0 + ], + "ctrl-2": [ + "pane::ActivateItem", + 1 + ], + "ctrl-3": [ + "pane::ActivateItem", + 2 + ], + "ctrl-4": [ + "pane::ActivateItem", + 3 + ], + "ctrl-5": [ + "pane::ActivateItem", + 4 + ], + "ctrl-6": [ + "pane::ActivateItem", + 5 + ], + "ctrl-7": [ + "pane::ActivateItem", + 6 + ], + "ctrl-8": [ + "pane::ActivateItem", + 7 + ], + "ctrl-9": [ + "pane::ActivateItem", + 8 + ], + "ctrl-0": "pane::ActivateLastItem", + "ctrl--": "pane::GoBack", + "ctrl-_": "pane::GoForward", + "cmd-shift-t": "pane::ReopenClosedItem", + "cmd-shift-f": "project_search::ToggleFocus" + } + }, + { + "context": "Workspace", + "bindings": { + "cmd-1": [ + "workspace::ActivatePane", + 0 + ], + "cmd-2": [ + "workspace::ActivatePane", + 1 + ], + "cmd-3": [ + "workspace::ActivatePane", + 2 + ], + "cmd-4": [ + "workspace::ActivatePane", + 3 + ], + "cmd-5": [ + "workspace::ActivatePane", + 4 + ], + "cmd-6": [ + "workspace::ActivatePane", + 5 + ], + "cmd-7": [ + "workspace::ActivatePane", + 6 + ], + "cmd-8": [ + "workspace::ActivatePane", + 7 + ], + "cmd-9": [ + "workspace::ActivatePane", + 8 + ], + "cmd-b": "workspace::ToggleLeftDock", + "cmd-shift-f": "workspace::NewSearch", + "cmd-k cmd-t": "theme_selector::Toggle", + "cmd-k cmd-s": "zed::OpenKeymap", + "cmd-t": "project_symbols::Toggle", + "cmd-p": "file_finder::Toggle", + "cmd-shift-p": "command_palette::Toggle", + "cmd-shift-m": "diagnostics::Deploy", + "cmd-shift-e": "project_panel::ToggleFocus", + "cmd-alt-s": "workspace::SaveAll", + "cmd-k m": "language_selector::Toggle" + } + }, + // Bindings from Sublime Text + { + "context": "Editor", + "bindings": { + "ctrl-shift-k": "editor::DeleteLine", + "cmd-shift-d": "editor::DuplicateLine", + "cmd-shift-l": "editor::SplitSelectionIntoLines", + "ctrl-cmd-up": "editor::MoveLineUp", + "ctrl-cmd-down": "editor::MoveLineDown", + "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart", + "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", + "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", + "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", + "ctrl-alt-left": "editor::MoveToPreviousSubwordStart", + "ctrl-alt-b": "editor::MoveToPreviousSubwordStart", + "ctrl-alt-right": "editor::MoveToNextSubwordEnd", + "ctrl-alt-f": "editor::MoveToNextSubwordEnd", + "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart", + "ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart", + "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd", + "ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd" + } + }, + { + "bindings": { + "cmd-k cmd-left": "workspace::ActivatePreviousPane", + "cmd-k cmd-right": "workspace::ActivateNextPane" + } + }, + // Bindings from Atom + { + "context": "Pane", + "bindings": { + "cmd-k up": "pane::SplitUp", + "cmd-k down": "pane::SplitDown", + "cmd-k left": "pane::SplitLeft", + "cmd-k right": "pane::SplitRight" + } + }, + // Bindings that should be unified with bindings for more general actions + { + "context": "Editor && renaming", + "bindings": { + "enter": "editor::ConfirmRename" + } + }, + { + "context": "Editor && showing_completions", + "bindings": { + "enter": "editor::ConfirmCompletion", + "tab": "editor::ConfirmCompletion" + } + }, + { + "context": "Editor && showing_code_actions", + "bindings": { + "enter": "editor::ConfirmCodeAction" + } + }, + // Custom bindings + { + "bindings": { + "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator", + "cmd-shift-c": "collab::ToggleContactsMenu", + "cmd-alt-i": "zed::DebugElements" + } + }, + { + "context": "Editor", + "bindings": { + "alt-enter": "editor::OpenExcerpts", + "cmd-f8": "editor::GoToHunk", + "cmd-shift-f8": "editor::GoToPrevHunk" + } + }, + { + "context": "ProjectSearchBar", + "bindings": { + "cmd-enter": "project_search::SearchInNew" + } + }, + { + "context": "ProjectPanel", + "bindings": { + "left": "project_panel::CollapseSelectedEntry", + "right": "project_panel::ExpandSelectedEntry", + "cmd-x": "project_panel::Cut", + "cmd-c": "project_panel::Copy", + "cmd-v": "project_panel::Paste", + "cmd-alt-c": "project_panel::CopyPath", + "alt-cmd-shift-c": "project_panel::CopyRelativePath", + "f2": "project_panel::Rename", + "backspace": "project_panel::Delete", + "alt-cmd-r": "project_panel::RevealInFinder" + } + }, + { + "context": "Terminal", + "bindings": { + "ctrl-cmd-space": "terminal::ShowCharacterPalette", + "cmd-c": "terminal::Copy", + "cmd-v": "terminal::Paste", + "cmd-k": "terminal::Clear", + // Some nice conveniences + "cmd-backspace": [ + "terminal::SendText", + "\u0015" + ], + "cmd-right": [ + "terminal::SendText", + "\u0005" + ], + "cmd-left": [ + "terminal::SendText", + "\u0001" + ], + // Terminal.app compatability + "alt-left": [ + "terminal::SendText", + "\u001bb" + ], + "alt-right": [ + "terminal::SendText", + "\u001bf" + ], + // There are conflicting bindings for these keys in the global context. + // these bindings override them, remove at your own risk: + "up": [ + "terminal::SendKeystroke", + "up" + ], + "pageup": [ + "terminal::SendKeystroke", + "pageup" + ], + "down": [ + "terminal::SendKeystroke", + "down" + ], + "pagedown": [ + "terminal::SendKeystroke", + "pagedown" + ], + "escape": [ + "terminal::SendKeystroke", + "escape" + ], + "enter": [ + "terminal::SendKeystroke", + "enter" + ], + "ctrl-c": [ + "terminal::SendKeystroke", + "ctrl-c" + ] } + } ] From 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 01a09e0cba8f54dc2b436914cf515fcc120d7eff..cab4333f74c30842e2f23927ece00c4c03a398f1 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 7207e3c91c6da9f1e1ecfb37b94a560c875127d3..cf614fccf729ae90ca9ea5f7d527e4979869ef9d 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 6c9bd6cb4fe1554d9a04a840db78ff8edc88b4c9..523a0af9640aa98b3f3e3d7b9fd980768f1e4f89 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 2aea69e90b7cdda61afabba7988d569189b36de2..b8b57e8b8dde611aae3689ae1d30dd0a9ec1567a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4888,6 +4888,7 @@ version = "0.1.0" dependencies = [ "client", "context_menu", + "db", "drag_and_drop", "editor", "futures 0.3.28", diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml index 8cd5a06675318ff3f48730cd360ac6deaa599469..aab5b8a41c2d00a5d445576c07cc952c0d020b73 100644 --- a/crates/project_panel/Cargo.toml +++ b/crates/project_panel/Cargo.toml @@ -10,6 +10,7 @@ doctest = false [dependencies] context_menu = { path = "../context_menu" } +db = { path = "../db" } drag_and_drop = { path = "../drag_and_drop" } editor = { path = "../editor" } gpui = { path = "../gpui" } @@ -23,6 +24,7 @@ postage.workspace = true futures.workspace = true schemars.workspace = true serde.workspace = true +serde_json.workspace = true unicase = "2.6" [dev-dependencies] diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 761d80308466632a8ea43fc93105ea92d0eda656..010a305f161cc966c43fb88c1970c3dcd06e966b 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1,10 +1,11 @@ use context_menu::{ContextMenu, ContextMenuItem}; +use db::kvp::KEY_VALUE_STORE; use drag_and_drop::{DragAndDrop, Draggable}; use editor::{Cancel, Editor}; use futures::stream::StreamExt; use gpui::{ actions, - anyhow::{anyhow, Result}, + anyhow::{self, anyhow, Result}, elements::{ AnchorCorner, ChildView, ComponentHost, ContainerStyle, Empty, Flex, MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, @@ -12,8 +13,8 @@ use gpui::{ geometry::vector::Vector2F, keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, - AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext, - ViewHandle, WeakViewHandle, WindowContext, + AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, ModelHandle, Task, + View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{ @@ -33,11 +34,13 @@ use std::{ }; use theme::{ui::FileName, ProjectPanelEntry}; use unicase::UniCase; +use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel}, Workspace, }; +const PROJECT_PANEL_KEY: &'static str = "ProjectPanel"; const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX; #[derive(Deserialize)] @@ -67,6 +70,7 @@ pub struct ProjectPanelSettingsContent { } #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] pub enum ProjectPanelDockPosition { Left, Right, @@ -87,6 +91,8 @@ pub struct ProjectPanel { dragged_entry_destination: Option>, workspace: WeakViewHandle, has_focus: bool, + width: Option, + pending_serialization: Task>, } #[derive(Copy, Clone)] @@ -183,8 +189,13 @@ pub enum Event { Focus, } +#[derive(Serialize, Deserialize)] +struct SerializedProjectPanel { + width: Option, +} + impl ProjectPanel { - pub fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> ViewHandle { + fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> ViewHandle { let project = workspace.project().clone(); let project_panel = cx.add_view(|cx: &mut ViewContext| { cx.observe(&project, |this, _, cx| { @@ -258,6 +269,8 @@ impl ProjectPanel { dragged_entry_destination: None, workspace: workspace.weak_handle(), has_focus: false, + width: None, + pending_serialization: Task::ready(None), }; this.update_visible_entries(None, cx); @@ -311,6 +324,51 @@ impl ProjectPanel { project_panel } + pub fn load( + workspace: WeakViewHandle, + cx: AsyncAppContext, + ) -> Task>> { + cx.spawn(|mut cx| async move { + let serialized_panel = if let Some(panel) = cx + .background() + .spawn(async move { KEY_VALUE_STORE.read_kvp(PROJECT_PANEL_KEY) }) + .await + .log_err() + .flatten() + { + Some(serde_json::from_str::(&panel)?) + } else { + None + }; + workspace.update(&mut cx, |workspace, cx| { + let panel = ProjectPanel::new(workspace, cx); + if let Some(serialized_panel) = serialized_panel { + panel.update(cx, |panel, cx| { + panel.width = serialized_panel.width; + cx.notify(); + }); + } + panel + }) + }) + } + + fn serialize(&mut self, cx: &mut ViewContext) { + let width = self.width; + self.pending_serialization = cx.background().spawn( + async move { + KEY_VALUE_STORE + .write_kvp( + PROJECT_PANEL_KEY.into(), + serde_json::to_string(&SerializedProjectPanel { width })?, + ) + .await?; + anyhow::Ok(()) + } + .log_err(), + ); + } + fn deploy_context_menu( &mut self, position: Vector2F, @@ -1435,8 +1493,15 @@ impl workspace::dock::Panel for ProjectPanel { ); } - fn default_size(&self, cx: &WindowContext) -> f32 { - settings::get::(cx).default_width + fn size(&self, cx: &WindowContext) -> f32 { + self.width + .unwrap_or_else(|| settings::get::(cx).default_width) + } + + fn set_size(&mut self, size: f32, cx: &mut ViewContext) { + self.width = Some(size); + self.serialize(cx); + cx.notify(); } fn should_zoom_in_on_event(_: &Self::Event) -> bool { diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 9cf3f529249e6c6725f499ed535ed779694d3fc7..576719526da0104cb92b6e6c05b2f768c042da84 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -120,6 +120,7 @@ pub fn init(cx: &mut AppContext) { } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] pub enum TerminalDockPosition { Left, Bottom, diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index afd564fb56d5b4cecb19e43cf22f52b1ff9eb7dd..9c8073eada819f26ca944140025394c93023be3f 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -37,12 +37,14 @@ pub struct TerminalPanel { pane: ViewHandle, fs: Arc, workspace: WeakViewHandle, + width: Option, + height: Option, pending_serialization: Task>, _subscriptions: Vec, } impl TerminalPanel { - pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { let weak_self = cx.weak_handle(); let pane = cx.add_view(|cx| { let window_id = cx.window_id(); @@ -90,6 +92,8 @@ impl TerminalPanel { fs: workspace.app_state().fs.clone(), workspace: workspace.weak_handle(), pending_serialization: Task::ready(None), + width: None, + height: None, _subscriptions: subscriptions, }; let mut old_dock_position = this.position(cx); @@ -112,7 +116,9 @@ impl TerminalPanel { let serialized_panel = if let Some(panel) = cx .background() .spawn(async move { KEY_VALUE_STORE.read_kvp(TERMINAL_PANEL_KEY) }) - .await? + .await + .log_err() + .flatten() { Some(serde_json::from_str::(&panel)?) } else { @@ -122,6 +128,9 @@ impl TerminalPanel { let panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); let items = if let Some(serialized_panel) = serialized_panel.as_ref() { panel.update(cx, |panel, cx| { + cx.notify(); + panel.height = serialized_panel.height; + panel.width = serialized_panel.width; panel.pane.update(cx, |_, cx| { serialized_panel .items @@ -226,6 +235,8 @@ impl TerminalPanel { .map(|item| item.id()) .collect::>(); let active_item_id = self.pane.read(cx).active_item().map(|item| item.id()); + let height = self.height; + let width = self.width; self.pending_serialization = cx.background().spawn( async move { KEY_VALUE_STORE @@ -234,6 +245,8 @@ impl TerminalPanel { serde_json::to_string(&SerializedTerminalPanel { items, active_item_id, + height, + width, })?, ) .await?; @@ -288,12 +301,23 @@ impl Panel for TerminalPanel { }); } - fn default_size(&self, cx: &WindowContext) -> f32 { + fn size(&self, cx: &WindowContext) -> f32 { let settings = settings::get::(cx); match self.position(cx) { - DockPosition::Left | DockPosition::Right => settings.default_width, - DockPosition::Bottom => settings.default_height, + DockPosition::Left | DockPosition::Right => { + self.width.unwrap_or_else(|| settings.default_width) + } + DockPosition::Bottom => self.height.unwrap_or_else(|| settings.default_height), + } + } + + fn set_size(&mut self, size: f32, cx: &mut ViewContext) { + match self.position(cx) { + DockPosition::Left | DockPosition::Right => self.width = Some(size), + DockPosition::Bottom => self.height = Some(size), } + self.serialize(cx); + cx.notify(); } fn should_zoom_in_on_event(event: &Event) -> bool { @@ -360,4 +384,6 @@ impl Panel for TerminalPanel { struct SerializedTerminalPanel { items: Vec, active_item_id: Option, + width: Option, + height: Option, } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index c327c4ded8a7e58f13ca1b436ce0dac3eeff2d03..c4693b340325994147b6da6d2d9ba67946efdbc5 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -13,7 +13,8 @@ pub trait Panel: View { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); - fn default_size(&self, cx: &WindowContext) -> f32; + fn size(&self, cx: &WindowContext) -> f32; + fn set_size(&mut self, size: f32, cx: &mut ViewContext); fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; fn icon_label(&self, _: &WindowContext) -> Option { @@ -39,7 +40,8 @@ pub trait PanelHandle { fn is_zoomed(&self, cx: &WindowContext) -> bool; fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext); fn set_active(&self, active: bool, cx: &mut WindowContext); - fn default_size(&self, cx: &WindowContext) -> f32; + fn size(&self, cx: &WindowContext) -> f32; + fn set_size(&self, size: f32, cx: &mut WindowContext); fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -67,8 +69,12 @@ where self.update(cx, |this, cx| this.set_position(position, cx)) } - fn default_size(&self, cx: &WindowContext) -> f32 { - self.read(cx).default_size(cx) + fn size(&self, cx: &WindowContext) -> f32 { + self.read(cx).size(cx) + } + + fn set_size(&self, size: f32, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.set_size(size, cx)) } fn is_zoomed(&self, cx: &WindowContext) -> bool { @@ -151,7 +157,6 @@ impl DockPosition { struct PanelEntry { panel: Rc, - size: f32, context_menu: ViewHandle, _subscriptions: [Subscription; 2], } @@ -271,10 +276,8 @@ impl Dock { ]; let dock_view_id = cx.view_id(); - let size = panel.default_size(cx); self.panel_entries.push(PanelEntry { panel: Rc::new(panel), - size, context_menu: cx.add_view(|cx| { let mut menu = ContextMenu::new(dock_view_id, cx); menu.set_position_mode(OverlayPositionMode::Local); @@ -343,28 +346,18 @@ impl Dock { } } - pub fn panel_size(&self, panel: &dyn PanelHandle) -> Option { + pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option { self.panel_entries .iter() .find(|entry| entry.panel.id() == panel.id()) - .map(|entry| entry.size) + .map(|entry| entry.panel.size(cx)) } - pub fn resize_panel(&mut self, panel: &dyn PanelHandle, size: f32) { - let entry = self - .panel_entries - .iter_mut() - .find(|entry| entry.panel.id() == panel.id()); - if let Some(entry) = entry { - entry.size = size; - } - } - - pub fn active_panel_size(&self) -> Option { + pub fn active_panel_size(&self, cx: &WindowContext) -> Option { if self.is_open { self.panel_entries .get(self.active_panel_index) - .map(|entry| entry.size) + .map(|entry| entry.panel.size(cx)) } else { None } @@ -372,7 +365,7 @@ impl Dock { pub fn resize_active_panel(&mut self, size: f32, cx: &mut ViewContext) { if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) { - entry.size = size; + entry.panel.set_size(size, cx); cx.notify(); } } @@ -386,7 +379,7 @@ impl Dock { .with_style(style.container) .resizable( self.position.to_resize_handle_side(), - active_entry.size, + active_entry.panel.size(cx), |_, _, _| {}, ) .into_any() @@ -413,7 +406,7 @@ impl View for Dock { .with_style(style.container) .resizable( self.position.to_resize_handle_side(), - active_entry.size, + active_entry.panel.size(cx), |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), ) .into_any() @@ -630,13 +623,17 @@ pub(crate) mod test { unimplemented!() } - fn default_size(&self, _: &WindowContext) -> f32 { + fn size(&self, _: &WindowContext) -> f32 { match self.position.axis() { Axis::Horizontal => 300., Axis::Vertical => 200., } } + fn set_size(&mut self, _: f32, _: &mut ViewContext) { + unimplemented!() + } + fn icon_path(&self) -> &'static str { "icons/test_panel.svg" } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 78900eb0021f0545bfb06199fd23efcce0dea52b..6c7d61fef9625e9a5030ca0505114d37ba981de3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -853,11 +853,7 @@ impl Workspace { if T::should_change_position_on_event(event) { let new_position = panel.read(cx).position(cx); let mut was_visible = false; - let mut size = None; dock.update(cx, |dock, cx| { - if new_position.axis() == prev_position.axis() { - size = dock.panel_size(&panel); - } prev_position = new_position; was_visible = dock.is_open() @@ -874,10 +870,6 @@ impl Workspace { .clone(); dock.update(cx, |dock, cx| { dock.add_panel(panel.clone(), cx); - if let Some(size) = size { - dock.resize_panel(&panel, size); - } - if was_visible { dock.set_open(true, cx); dock.activate_panel(dock.panels_len() - 1, cx); @@ -3961,8 +3953,8 @@ mod tests { panel_1.id() ); assert_eq!( - left_dock.read(cx).active_panel_size().unwrap(), - panel_1.default_size(cx) + left_dock.read(cx).active_panel_size(cx).unwrap(), + panel_1.size(cx) ); left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx)); @@ -3989,7 +3981,7 @@ mod tests { right_dock.read(cx).active_panel().unwrap().id(), panel_1.id() ); - assert_eq!(right_dock.read(cx).active_panel_size().unwrap(), 1337.); + assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); // Now we move panel_2 to the left panel_2.set_position(DockPosition::Left, cx); @@ -4019,7 +4011,7 @@ mod tests { left_dock.read(cx).active_panel().unwrap().id(), panel_1.id() ); - assert_eq!(left_dock.read(cx).active_panel_size().unwrap(), 1337.); + assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); // And right the dock should be closed as it no longer has any panels. assert!(!workspace.right_dock().read(cx).is_open()); @@ -4034,8 +4026,8 @@ mod tests { // since the panel orientation changed from vertical to horizontal. let bottom_dock = workspace.bottom_dock(); assert_eq!( - bottom_dock.read(cx).active_panel_size().unwrap(), - panel_1.default_size(cx), + bottom_dock.read(cx).active_panel_size(cx).unwrap(), + panel_1.size(cx), ); // Close bottom dock and move panel_1 back to the left. bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 0c4b63081c0ebb1fbb9343fa2d9f95fbc83d7c17..be0c566bb136aaf2ba6bc97f921f85f91fa8abef 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -335,8 +335,12 @@ pub fn initialize_workspace( } false }); + })?; - let project_panel = ProjectPanel::new(workspace, cx); + let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone()); + let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone()); + let (project_panel, terminal_panel) = futures::try_join!(project_panel, terminal_panel)?; + workspace_handle.update(&mut cx, |workspace, cx| { let project_panel_position = project_panel.position(cx); workspace.add_panel(project_panel, cx); if !was_deserialized @@ -352,10 +356,7 @@ pub fn initialize_workspace( { workspace.toggle_dock(project_panel_position, cx); } - })?; - let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone()).await?; - workspace_handle.update(&mut cx, |workspace, cx| { workspace.add_panel(terminal_panel, cx) })?; Ok(()) From 185a624b99a2cd5cd73bab32d2a43973b8c21339 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 16:18:53 +0200 Subject: [PATCH 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 010a305f161cc966c43fb88c1970c3dcd06e966b..9159e8fa98eb85006726d8e737161aea628afc35 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -2243,6 +2243,7 @@ mod tests { theme::init((), cx); language::init(cx); editor::init_settings(cx); + crate::init(cx); workspace::init_settings(cx); }); } @@ -2255,6 +2256,7 @@ mod tests { language::init(cx); editor::init(cx); pane::init(cx); + crate::init(cx); workspace::init(app_state.clone(), cx); }); } From 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 9a21429301b6906e025f87c14659a8594279c11d..180de155e998fff1492c22311502d87ebcf744bf 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 329b290a3192c66545e7001635917e448acc1e81..71b3cc635f4e03465d94cb498567c21bd36bd76a 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 bc895034594178622aec581da3cd4322c203488d..0fafe7694204b2500b203db5edc6489ed094bf40 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1749,11 +1749,11 @@ impl AppContext { } } - // When the previously-focused view isn't rendered and + // When the previously-focused view has been dropped and // there isn't any pending focus, focus the root view. let root_view_id = cx.window.root_view().id(); if focused_view_id != root_view_id - && !cx.window.parents.contains_key(&focused_view_id) + && !cx.views.contains_key(&(window_id, focused_view_id)) && !focus_effects.contains_key(&window_id) { focus_effects.insert( @@ -1942,12 +1942,12 @@ impl AppContext { fn handle_focus_effect(&mut self, effect: FocusEffect) { let window_id = effect.window_id(); self.update_window(window_id, |cx| { - // Ensure the newly-focused view has been rendered, otherwise focus + // Ensure the newly-focused view still exists, otherwise focus // the root view instead. let focused_id = match effect { FocusEffect::View { view_id, .. } => { if let Some(view_id) = view_id { - if cx.window.parents.contains_key(&view_id) { + if cx.views.contains_key(&(window_id, view_id)) { Some(view_id) } else { Some(cx.root_view().id()) From 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 7caa8b1c674996822d91a0a3b120b44be6e44cbc..2443717e4d54e2d1e6a38d30cc379865f79d767b 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 3d06a683abebf71d55311273f8e93f08876ea509..0c32ee7132c62e195a749d12e8bc1391490266fd 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 be0c566bb136aaf2ba6bc97f921f85f91fa8abef..4c4137b7f37979d4c12e2b89b71fae1f5804ea22 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1396,7 +1396,7 @@ mod tests { cx.foreground().run_until_parked(); workspace.read_with(cx, |workspace, _| { - assert_eq!(workspace.panes().len(), 2); //Center pane + Dock pane + assert_eq!(workspace.panes().len(), 1); assert_eq!(workspace.active_pane(), &pane_1); }); @@ -1406,7 +1406,7 @@ mod tests { cx.foreground().run_until_parked(); workspace.read_with(cx, |workspace, cx| { - assert_eq!(workspace.panes().len(), 2); + assert_eq!(workspace.panes().len(), 1); assert!(workspace.active_item(cx).is_none()); }); @@ -2088,6 +2088,8 @@ mod tests { language::init(cx); editor::init(cx); pane::init(cx); + project_panel::init(cx); + terminal_view::init(cx); app_state }) } From 5b7e852903c3b1e9b4c42505ef44f9d47187e16a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 18:28:30 +0200 Subject: [PATCH 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 5f698d89a5c7887f4596334694f11e805cd317c7..aae900c8615236591804870a845fb136d34b91fa 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2343,23 +2343,31 @@ mod tests { add_labeled_item(&workspace, &pane, "1", false, cx); assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "B*", "C", "D"], cx); pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx)); assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "B*", "C"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "C*"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)); - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A*"], cx); } @@ -2376,9 +2384,10 @@ mod tests { pane.update(cx, |pane, cx| { pane.close_inactive_items(&CloseInactiveItems, cx) - }); - - deterministic.run_until_parked(); + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["C*"], cx); } @@ -2398,9 +2407,10 @@ mod tests { add_labeled_item(&workspace, &pane, "E", false, cx); assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx); - pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)); - - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A^", "C*^"], cx); } @@ -2420,9 +2430,10 @@ mod tests { pane.update(cx, |pane, cx| { pane.close_items_to_the_left(&CloseItemsToTheLeft, cx) - }); - - deterministic.run_until_parked(); + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["C*", "D", "E"], cx); } @@ -2442,9 +2453,10 @@ mod tests { pane.update(cx, |pane, cx| { pane.close_items_to_the_right(&CloseItemsToTheRight, cx) - }); - - deterministic.run_until_parked(); + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "B", "C*"], cx); } @@ -2462,9 +2474,10 @@ mod tests { add_labeled_item(&workspace, &pane, "C", false, cx); assert_item_labels(&pane, ["A", "B", "C*"], cx); - pane.update(cx, |pane, cx| pane.close_all_items(&CloseAllItems, cx)); - - deterministic.run_until_parked(); + pane.update(cx, |pane, cx| pane.close_all_items(&CloseAllItems, cx)) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, [], cx); } From 75a0742c90028ae3e0128714ecea27b658a37280 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 18:32:28 +0200 Subject: [PATCH 084/131] Uncomment persistence test --- crates/workspace/src/persistence.rs | 39 ++++++++++++----------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index fbefd3984ef4eb9f161902e215cac487a4d15b37..76b66c5329ec9a133b8bd4b3402fe87c7f20cd8c 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -579,7 +579,7 @@ mod tests { docks: Default::default(), }; - let mut _workspace_2 = SerializedWorkspace { + let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), center_group: Default::default(), @@ -597,7 +597,7 @@ mod tests { }) .await; - db.save_workspace(_workspace_2.clone()).await; + db.save_workspace(workspace_2.clone()).await; db.write(|conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) @@ -609,28 +609,21 @@ mod tests { workspace_1.location = (["/tmp", "/tmp3"]).into(); db.save_workspace(workspace_1.clone()).await; db.save_workspace(workspace_1).await; + db.save_workspace(workspace_2).await; + + let test_text_2 = db + .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) + .unwrap()(2) + .unwrap() + .unwrap(); + assert_eq!(test_text_2, "test-text-2"); - todo!(); - // workspace_2.dock_pane.children.push(SerializedItem { - // kind: Arc::from("Test"), - // item_id: 10, - // active: true, - // }); - // db.save_workspace(workspace_2).await; - - // let test_text_2 = db - // .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) - // .unwrap()(2) - // .unwrap() - // .unwrap(); - // assert_eq!(test_text_2, "test-text-2"); - - // let test_text_1 = db - // .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) - // .unwrap()(1) - // .unwrap() - // .unwrap(); - // assert_eq!(test_text_1, "test-text-1"); + let test_text_1 = db + .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) + .unwrap()(1) + .unwrap() + .unwrap(); + assert_eq!(test_text_1, "test-text-1"); } #[gpui::test] From 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 ce0dd9e22212ad954e5398eff8c3a209b7ea4e78..dd28bdd73e2ec3231188690ca06309deaaa1bfab 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 eb404cdaad6ad5a8d5e7fa5c572e0ad709e6c4e4..5a799465893e61cda58d8f9a0ecd0cc69bd59591 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 e4df24c89fcf92842e663e8a50792709a19b7981..1799f37aedb85b87b736057813de0f168546248c 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 0c32ee7132c62e195a749d12e8bc1391490266fd..976cdede5cdab5f3760fd51a4343b04a771944b5 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 6c7d61fef9625e9a5030ca0505114d37ba981de3..bd50251cf1a56717814e2001f50c863213e6b2e1 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -477,7 +477,7 @@ pub struct Workspace { leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: WorkspaceId, app_state: Arc, - _subscriptions: Vec, + subscriptions: Vec, _apply_leader_updates: Task>, _observe_current_user: Task>, pane_history_timestamp: Arc, @@ -683,7 +683,7 @@ impl Workspace { _observe_current_user, _apply_leader_updates, leader_updates_tx, - _subscriptions: subscriptions, + subscriptions, pane_history_timestamp, }; this.project_remote_id_changed(project.read(cx).remote_id(), cx); @@ -846,7 +846,7 @@ impl Workspace { DockPosition::Right => &self.right_dock, }; - cx.subscribe(&panel, { + self.subscriptions.push(cx.subscribe(&panel, { let mut dock = dock.clone(); let mut prev_position = panel.position(cx); move |this, panel, event, cx| { @@ -884,8 +884,7 @@ impl Workspace { cx.notify(); } } - }) - .detach(); + })); dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); } From 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 57dc3293f6f9dc322c1a7744f97b21000fb34c0c..502975c144ff5ff7d187378c54d085edb3837f7c 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 5a799465893e61cda58d8f9a0ecd0cc69bd59591..c589137e73c5df2cc917959c3793999e8d05f78c 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 1799f37aedb85b87b736057813de0f168546248c..b86bfca8c42ae05900d76d86e19544531c245899 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 2443717e4d54e2d1e6a38d30cc379865f79d767b..a13ae49c7d89f1a1589fcf3677f51d9993d4c217 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 c589137e73c5df2cc917959c3793999e8d05f78c..b5ac126a690ca2ba7023e526297f57302934bb4a 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 976cdede5cdab5f3760fd51a4343b04a771944b5..81dded151e54a091ac91ceba130c27449946594d 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 dd28bdd73e2ec3231188690ca06309deaaa1bfab..22b6b3fd75b767a99c0a453dd531000fad4242f4 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 c4693b340325994147b6da6d2d9ba67946efdbc5..8f44419ddc58787310fe64df9d67f9ec803364f4 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -577,10 +577,29 @@ pub(crate) mod test { PositionChanged, Activated, Closed, + ZoomIn, + ZoomOut, + Focus, } pub struct TestPanel { pub position: DockPosition, + pub zoomed: bool, + pub active: bool, + pub has_focus: bool, + pub size: f32, + } + + impl TestPanel { + pub fn new(position: DockPosition) -> Self { + Self { + position, + zoomed: false, + active: false, + has_focus: false, + size: 300., + } + } } impl Entity for TestPanel { @@ -595,6 +614,14 @@ pub(crate) mod test { fn render(&mut self, _: &mut ViewContext<'_, '_, Self>) -> AnyElement { Empty::new().into_any() } + + fn focus_in(&mut self, _: AnyViewHandle, _: &mut ViewContext) { + self.has_focus = true; + } + + fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext) { + self.has_focus = false; + } } impl Panel for TestPanel { @@ -612,26 +639,23 @@ pub(crate) mod test { } fn is_zoomed(&self, _: &WindowContext) -> bool { - unimplemented!() + self.zoomed } - fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext) { - unimplemented!() + fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext) { + self.zoomed = zoomed; } - fn set_active(&mut self, _active: bool, _cx: &mut ViewContext) { - unimplemented!() + fn set_active(&mut self, active: bool, _cx: &mut ViewContext) { + self.active = active; } fn size(&self, _: &WindowContext) -> f32 { - match self.position.axis() { - Axis::Horizontal => 300., - Axis::Vertical => 200., - } + self.size } - fn set_size(&mut self, _: f32, _: &mut ViewContext) { - unimplemented!() + fn set_size(&mut self, size: f32, _: &mut ViewContext) { + self.size = size; } fn icon_path(&self) -> &'static str { @@ -646,12 +670,12 @@ pub(crate) mod test { matches!(event, TestPanelEvent::PositionChanged) } - fn should_zoom_in_on_event(_: &Self::Event) -> bool { - false + fn should_zoom_in_on_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::ZoomIn) } - fn should_zoom_out_on_event(_: &Self::Event) -> bool { - false + fn should_zoom_out_on_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::ZoomOut) } fn should_activate_on_event(event: &Self::Event) -> bool { @@ -663,11 +687,11 @@ pub(crate) mod test { } fn has_focus(&self, _cx: &WindowContext) -> bool { - unimplemented!() + self.has_focus } - fn is_focus_event(_: &Self::Event) -> bool { - unimplemented!() + fn is_focus_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::Focus) } } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index bd50251cf1a56717814e2001f50c863213e6b2e1..495989c1bed17e13084b73189dc7bac562cae2dd 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3931,16 +3931,12 @@ mod tests { let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { // Add panel_1 on the left, panel_2 on the right. - let panel_1 = cx.add_view(|_| TestPanel { - position: DockPosition::Left, - }); + let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left)); workspace.add_panel(panel_1.clone(), cx); workspace .left_dock() .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); - let panel_2 = cx.add_view(|_| TestPanel { - position: DockPosition::Right, - }); + let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right)); workspace.add_panel(panel_2.clone(), cx); workspace .right_dock() @@ -4060,6 +4056,30 @@ mod tests { ); }); + // Emitting a ZoomIn event shows the panel as zoomed. + panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn)); + workspace.read_with(cx, |workspace, cx| { + assert_eq!(workspace.zoomed(cx), Some(panel_1.clone().into_any())); + }); + + // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed. + workspace.update(cx, |_, cx| cx.focus_self()); + workspace.read_with(cx, |workspace, cx| { + assert_eq!(workspace.zoomed(cx), None); + }); + + // When focus is transferred back to the panel, it is zoomed again. + panel_1.update(cx, |_, cx| cx.focus_self()); + workspace.read_with(cx, |workspace, cx| { + assert_eq!(workspace.zoomed(cx), Some(panel_1.clone().into_any())); + }); + + // Emitting a ZoomOut event unzooms the panel. + panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut)); + workspace.read_with(cx, |workspace, cx| { + assert_eq!(workspace.zoomed(cx), None); + }); + // Emit closed event on panel 1, which is active panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed)); From 6f39d49b184a55d344d6d6920abea74197d5aa1b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 19:33:11 +0200 Subject: [PATCH 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 8f44419ddc58787310fe64df9d67f9ec803364f4..7e6868f9b095558532487da27904ae49d40a86fc 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -615,8 +615,9 @@ pub(crate) mod test { Empty::new().into_any() } - fn focus_in(&mut self, _: AnyViewHandle, _: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { self.has_focus = true; + cx.emit(TestPanelEvent::Focus); } fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext) { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index aae900c8615236591804870a845fb136d34b91fa..eb3dd12b33489f74392cc4d066e892d86832e4d3 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2325,7 +2325,7 @@ mod tests { } #[gpui::test] - async fn test_remove_item_ordering(deterministic: Arc, cx: &mut TestAppContext) { + async fn test_remove_item_ordering(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); @@ -2372,7 +2372,7 @@ mod tests { } #[gpui::test] - async fn test_close_inactive_items(deterministic: Arc, cx: &mut TestAppContext) { + async fn test_close_inactive_items(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); @@ -2392,7 +2392,7 @@ mod tests { } #[gpui::test] - async fn test_close_clean_items(deterministic: Arc, cx: &mut TestAppContext) { + async fn test_close_clean_items(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); @@ -2461,7 +2461,7 @@ mod tests { } #[gpui::test] - async fn test_close_all_items(deterministic: Arc, cx: &mut TestAppContext) { + async fn test_close_all_items(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 76b66c5329ec9a133b8bd4b3402fe87c7f20cd8c..d27818d2028eec866d17cc687bb393f37e9cb6e5 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -579,7 +579,7 @@ mod tests { docks: Default::default(), }; - let mut workspace_2 = SerializedWorkspace { + let workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), center_group: Default::default(), From 33f6c56b1465e5628d791f2b2f74307157064c47 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 19:36:01 +0200 Subject: [PATCH 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 eb3dd12b33489f74392cc4d066e892d86832e4d3..e07bd0c9cf8032666d1375d5a5a3e22bf559f3a6 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2415,10 +2415,7 @@ mod tests { } #[gpui::test] - async fn test_close_items_to_the_left( - deterministic: Arc, - cx: &mut TestAppContext, - ) { + async fn test_close_items_to_the_left(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); @@ -2438,10 +2435,7 @@ mod tests { } #[gpui::test] - async fn test_close_items_to_the_right( - deterministic: Arc, - cx: &mut TestAppContext, - ) { + async fn test_close_items_to_the_right(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background()); From f22067b236bdb0bebb3c58b0790ec78b0d4a549b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 May 2023 19:38:57 +0200 Subject: [PATCH 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 e07bd0c9cf8032666d1375d5a5a3e22bf559f3a6..e1079f201b676286590d8b272cc4658d9c4777df 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2028,11 +2028,9 @@ impl Element for PaneBackdrop { #[cfg(test)] mod tests { - use std::sync::Arc; - use super::*; use crate::item::test::{TestItem, TestProjectItem}; - use gpui::{executor::Deterministic, TestAppContext}; + use gpui::TestAppContext; use project::FakeFs; use settings::SettingsStore; From 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 7207e3c91c6da9f1e1ecfb37b94a560c875127d3..0a4204ba2a0b635aec36818bc68cefb7ceb9a2fa 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 57dc3293f6f9dc322c1a7744f97b21000fb34c0c..5a057390d58ac7c8f01138166b531085492948a7 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.; - } - 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, - }; + if end_y - start_y < 1. { + end_y = start_y + 1.; + } + let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y)); - scene.push_quad(Quad { - bounds, - background: Some(color), - border, - corner_radius: style.thumb.corner_radius, - }) + 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 1423473e1ad8c3d952d6bb47ef95d20286e0a46f..6a617756974ee5a2021c1377ba2edb00468aa304 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 8704f850055aa4edb5f03109ecd1fa5d18d4aa5c..8260dfc98d9834f8cefff6dfc274a4d7a9e237d7 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 0a4204ba2a0b635aec36818bc68cefb7ceb9a2fa..12f5b3b872f6c51fb5b33324c249e77659d3785e 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 a202a6082c0522901a349c9537e49a2fe7e2c6b8..27f75ea89680de1cc6fbe80d909edc33503dd690 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 47623125f9c28fa12898a21d1a629c69fcf6158e..5a0be3ed6669233bb50e001320259f09c626c288 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 27f75ea89680de1cc6fbe80d909edc33503dd690..182efdfdd6c520e30d285ed822f66f9a3f98a368 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 7207e3c91c6da9f1e1ecfb37b94a560c875127d3..94fa23939bcdcfe0958945cad8fdc726c1720693 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 182efdfdd6c520e30d285ed822f66f9a3f98a368..29ea1273e24f4cf62942ad9627448920467cb1b1 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 94fa23939bcdcfe0958945cad8fdc726c1720693..6a43e5a93b86284c3547bcc6656bad3b0eab1e55 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 8d16a59bc12852ec9f06f3efa7ff43e0d333c038..f66fdce683a1d8633e25710e172b2ec1d553503a 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 f66fdce683a1d8633e25710e172b2ec1d553503a..67a7ed31c9b5a4cfbf3ab6881c627b1a9db126c5 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 4f149edb1053d10dfb6ae4764b439978f5130825..84db3f8ea1be70aa1243664a5bb5de3b04abf25a 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 814435059ee9f9df5406a1ec1d786263a6971439..d8bf71e2f292490c4bc8a0f808194dbb1d48915a 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 a3f38a3dc096c6a4ce8365911313c31c147d06e7..05045ca49067727edc01468cc29517715a8ecc9f 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 5a057390d58ac7c8f01138166b531085492948a7..b5e09fc86c20e2897c1a45fbac5ee2efb10c0651 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 05045ca49067727edc01468cc29517715a8ecc9f..d97006e5839248960edc3e0b35dc61890613dcaa 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 84db3f8ea1be70aa1243664a5bb5de3b04abf25a..e4e7a8c522d3585dda3ef9ac05a01d37b72bcda5 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 d97006e5839248960edc3e0b35dc61890613dcaa..7f01834b161b8f1db75a40145694f1c80e473755 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 b5e09fc86c20e2897c1a45fbac5ee2efb10c0651..13a24bfc2c1f884dc2dc1d8cc916e6fc22855ea4 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 3a977024873baa82d39e50bae06a3e3e43b7f254..5539d1d941726148fc543a59652d57190e1d400d 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 948a7ee39471579a4cc9a8553b700ccf26c2b9ae..f269fce88d694c8efe2f255e302bbef7aed865ea 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 f91cd999f9dd0068f98ae9f30fda3067469481d2..5b5d9038224925f1882b544e280083f546238c77 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 69bcea8ce0426f7fe812618542336f3a018262f3..e48ce6258b34f38f438dd6035a533a99a0be5d0a 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 4f898aa91d2558c606b39dda29b83ea6ff24a3d8..7df99b577bfde01b0f405c0dc5a0bf9a1002e4c4 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 4f898aa91d2558c606b39dda29b83ea6ff24a3d8..8d0896d2d1b66001b538c2f33e909e80d9650b71 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 a13ae49c7d89f1a1589fcf3677f51d9993d4c217..786f01024248f93f7a35b1e7cb530a0bd627c3db 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 81dded151e54a091ac91ceba130c27449946594d..7af59521545dce8db9574f707d4a69afdd53d763 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 786f01024248f93f7a35b1e7cb530a0bd627c3db..91c131835913887b86fabe3eb5ef90a5094add8b 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 57d5ec6e95ac327e187a34f09a549f57f9d9e6bc..ce67a59bd39238f819fa38d3ceb807842e664a9b 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 ba9adfdd16bf7be0062aa14dd46e55207da8b2aa..13a24bfc2c1f884dc2dc1d8cc916e6fc22855ea4 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 22b6b3fd75b767a99c0a453dd531000fad4242f4..a7895172d56d02a0da84c890eafd774b9f3fa905 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 b5ac126a690ca2ba7023e526297f57302934bb4a..cd2bf90b7eb85fb51068e11c31fa7be6b0ef84ce 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 ce67a59bd39238f819fa38d3ceb807842e664a9b..57d5ec6e95ac327e187a34f09a549f57f9d9e6bc 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 a7895172d56d02a0da84c890eafd774b9f3fa905..ce0dd9e22212ad954e5398eff8c3a209b7ea4e78 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 cd2bf90b7eb85fb51068e11c31fa7be6b0ef84ce..eb404cdaad6ad5a8d5e7fa5c572e0ad709e6c4e4 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 b86bfca8c42ae05900d76d86e19544531c245899..e4df24c89fcf92842e663e8a50792709a19b7981 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 91c131835913887b86fabe3eb5ef90a5094add8b..7caa8b1c674996822d91a0a3b120b44be6e44cbc 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 7af59521545dce8db9574f707d4a69afdd53d763..3d06a683abebf71d55311273f8e93f08876ea509 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 29ea1273e24f4cf62942ad9627448920467cb1b1..182efdfdd6c520e30d285ed822f66f9a3f98a368 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 57d5ec6e95ac327e187a34f09a549f57f9d9e6bc..ce67a59bd39238f819fa38d3ceb807842e664a9b 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 ce0dd9e22212ad954e5398eff8c3a209b7ea4e78..b67af5898eae754815a12c3dfcff59abbde6fedd 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 eb404cdaad6ad5a8d5e7fa5c572e0ad709e6c4e4..f23493c75dffd3d77898eb25a81d69a2d64a22ab 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 e4df24c89fcf92842e663e8a50792709a19b7981..b86bfca8c42ae05900d76d86e19544531c245899 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 7caa8b1c674996822d91a0a3b120b44be6e44cbc..deeff855ff0f61dc8e51c9a8b8065d8a29b9c437 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 3d06a683abebf71d55311273f8e93f08876ea509..08117bf6b01d5fe7b14f2e0ace22dc680c5cefac 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 | 26 +++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index b67af5898eae754815a12c3dfcff59abbde6fedd..a7895172d56d02a0da84c890eafd774b9f3fa905 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 f23493c75dffd3d77898eb25a81d69a2d64a22ab..cd2bf90b7eb85fb51068e11c31fa7be6b0ef84ce 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 GitProjectStatus { - pub modified: Color, - pub inserted: Color, - pub conflict: Color, - } +} + +#[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 { 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 13a24bfc2c1f884dc2dc1d8cc916e6fc22855ea4..4e5863407f208ae0d4b0b84030d1b21521164ec6 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 5b5d9038224925f1882b544e280083f546238c77..dd53c30d140534c3b15ea5a562b6ebc3c92fe2d6 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 2e1059437871c9094152b6507a2f015e3f72824b..d2c035f91675a779b84a4facfc6331091b481910 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 807510d70555ed446d9ecf9d9bc40a6aa652f162..439ee0786a1b850bb68ffef539be1651f8ca0480 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 b3bdc72c91cc8e9f5bb09ded6034f2d55928e46c..cf47700dfe26936d44676c208fee941e9e4d9565 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 3ae96f4751751fb06ce6250a31b138f93cd4cdf0..981005f3f2b0e0de0364a67378903f41e5383b2b 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 e4e7a8c522d3585dda3ef9ac05a01d37b72bcda5..38d0ce820ba3eaf06281bb9992e5ed0c3a0b56ae 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 6fcdf06d2c736c63ea19cc5bd5ebdd79e7c36061..01d1be53b9eaeb572a69de15c655a750812f7df3 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 a7895172d56d02a0da84c890eafd774b9f3fa905..d4cd0074549fdd6bc86159319395ee25a46fd47f 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 0000000000000000000000000000000000000000..01a15d089bfed0b794777f8479580d98e097eccf --- /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 d4cd0074549fdd6bc86159319395ee25a46fd47f..66f0fe77182d0ef1a1493141be93483624392078 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 01a15d089bfed0b794777f8479580d98e097eccf..7786f1b4380b10956c8b3ea327f98b349c0e6ad3 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 66f0fe77182d0ef1a1493141be93483624392078..40ba0b7e67de51b3f2d0677257cb4fc7df11620f 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 376ecfc06bd43aeb2753345aa2301b8bd7d60cf7..95d09e82ec445ea245508f8e31441772b118ba6f 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 40ba0b7e67de51b3f2d0677257cb4fc7df11620f..69d8b8be4ddeca6f63c59368be4dda153c80965c 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 31a9f5cc03ed840af905f3652cf1ecc70830ddfc..b1c9e9c215324bbd6dcdf363d2aeb272ec86f057 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -402,9 +402,9 @@ pub struct StatusBarLspStatus { #[derive(Deserialize, Default)] pub struct Dock { - pub initial_size: f32, - #[serde(flatten)] - pub container: ContainerStyle, + pub left: ContainerStyle, + pub bottom: ContainerStyle, + pub right: ContainerStyle, } #[derive(Clone, Deserialize, Default)] diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 7e6868f9b095558532487da27904ae49d40a86fc..6ca78cd935b0c35aa3303ba683364fd07449c4d1 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -372,11 +372,10 @@ impl Dock { pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement { if let Some(active_entry) = self.active_entry() { - let style = &settings::get::(cx).theme.workspace.dock; Empty::new() .into_any() .contained() - .with_style(style.container) + .with_style(self.style(cx)) .resizable( self.position.to_resize_handle_side(), active_entry.panel.size(cx), @@ -387,6 +386,16 @@ impl Dock { Empty::new().into_any() } } + + fn style(&self, cx: &WindowContext) -> ContainerStyle { + let theme = &settings::get::(cx).theme; + let style = match self.position { + DockPosition::Left => theme.workspace.dock.left, + DockPosition::Bottom => theme.workspace.dock.bottom, + DockPosition::Right => theme.workspace.dock.right, + }; + style + } } impl Entity for Dock { @@ -400,10 +409,10 @@ impl View for Dock { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(active_entry) = self.active_entry() { - let style = &settings::get::(cx).theme.workspace.dock; + let style = self.style(cx); ChildView::new(active_entry.panel.as_any(), cx) .contained() - .with_style(style.container) + .with_style(style) .resizable( self.position.to_resize_handle_side(), active_entry.panel.size(cx), diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 501debc876200e47edea06b44b5d70f342671135..737d225784aeabe86edbdcf9a044748855ffc01f 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -128,8 +128,15 @@ export default function workspace(colorScheme: ColorScheme) { border: border(colorScheme.highest, { overlay: true }), }, dock: { - initialSize: 240, - border: border(layer, { left: true, right: true }), + left: { + border: border(layer, { right: true }), + }, + bottom: { + border: border(layer, { top: true }), + }, + right: { + border: border(layer, { left: true }), + } }, paneDivider: { color: borderColor(layer), 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 9318cc01847dd6d793b17dd869dc60d9f2aa55cd..85ae0885652d9054356ca19b9be6d5fd8642d16d 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 bdd664c6fbf0d07d2b4408a99209410f5d7a656a..d7b5c271b95496112b2fc368a851506d54b176b8 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 495989c1bed17e13084b73189dc7bac562cae2dd..40d170b0ded381a43b31252fcaffcde41d38ab93 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 a98147c51b09ff4e06b8583c68e1206c37e799c6..37e835c13de2c79f144d71d4e06089a0e658daa9 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 9c8073eada819f26ca944140025394c93023be3f..f265c535524352ac7de3284f8accdebef7bb7190 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 e1079f201b676286590d8b272cc4658d9c4777df..a2b13eb0ccce3b41bf08abfa3d5c45a7a9f6dbcf 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 f265c535524352ac7de3284f8accdebef7bb7190..90334f94490ab183e069d5328add7077a78236ca 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 a2b13eb0ccce3b41bf08abfa3d5c45a7a9f6dbcf..4302c6b3903777b3187d5155b34be023fc41ad9c 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 90334f94490ab183e069d5328add7077a78236ca..766736d70e4c6bdd5a713afd0aee29c3967a4250 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 4302c6b3903777b3187d5155b34be023fc41ad9c..26f3d17453c66566d5dfe64b5f711dc945796c39 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 6a617756974ee5a2021c1377ba2edb00468aa304..6b1ad6c5b2d6eb7c2e7bd9be9376b5fad04b8dd7 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 987c6cf1054fd58c3cb40086669df8b2d3fd1e50..88b27fd2f901375c0dd3dc22c1b64c26248d92e1 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 b7460c4c461f6a707bf77ee193145770418eeb50..cef6f53a6ecf50b376b93b460fbba82c96eb51de 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 6ca78cd935b0c35aa3303ba683364fd07449c4d1..7256ee870422113b6280f4f1c0fcae7a5f852395 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 40d170b0ded381a43b31252fcaffcde41d38ab93..39ecce2ca5f365bc2d77dc4dcce86941902f1377 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 37e835c13de2c79f144d71d4e06089a0e658daa9..b242b0f183767106f3b323a2c8cf52ce3900d9b1 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 f9c0a1855ee099ca7c4575ec4ca63c8462aabca9..1dfe9c24e54dd15f098d2a4e5d1df2f2c2e75f1e 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 766736d70e4c6bdd5a713afd0aee29c3967a4250..2062d2aff313f4c32eb5b926e5ff315e57cdbdcb 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 | 190 ++++++++++----------- crates/workspace/src/workspace.rs | 16 +- 4 files changed, 103 insertions(+), 117 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 32a8148cbb7562d36f4a3bb2c7a530f72b728363..6604472d863ac338b98fe7f088c37b68db122d54 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 2062d2aff313f4c32eb5b926e5ff315e57cdbdcb..791a8b21c56939a1c6c37f2ae6b461bbc44b3cf9 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 7256ee870422113b6280f4f1c0fcae7a5f852395..73d4f79399bd73bd52c9b0ecce04076e76b0407e 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, - ]; - - 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)) - }), - ) + } + }) + .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::( + 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 39ecce2ca5f365bc2d77dc4dcce86941902f1377..1c2b33214e26fd0e45f29aa97316049e28f80ec7 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() } });