diff --git a/crates/drag_and_drop/src/drag_and_drop.rs b/crates/drag_and_drop/src/drag_and_drop.rs index a34fa83a4c59a9d48b997beecf91ebbb4a7ea83b..b4881258f7f4ffbb863a79478ff73037a64b1459 100644 --- a/crates/drag_and_drop/src/drag_and_drop.rs +++ b/crates/drag_and_drop/src/drag_and_drop.rs @@ -139,9 +139,7 @@ impl DragAndDrop { region_offset, region, }) => { - if (dbg!(event.position) - (dbg!(region.origin() + region_offset))).length() - > DEAD_ZONE - { + if (event.position - (region.origin() + region_offset)).length() > DEAD_ZONE { this.currently_dragged = Some(State::Dragging { window_id, region_offset, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2cedc3e517c3cbbf4efadd5ef39aa1c6428c2635..ddfe0c00e0b70be02bac8e9e9e21b62ca8d7d00a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -184,7 +184,6 @@ actions!( Paste, Undo, Redo, - NextScreen, MoveUp, PageUp, MoveDown, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index f68720480511e1810dc6aa52448375b8bc7c7109..510a94c2f17180d4869fd15c2de816797695898f 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -4991,9 +4991,11 @@ async fn test_following(cx: &mut gpui::TestAppContext) { ) }); + let is_still_following = Rc::new(RefCell::new(true)); let pending_update = Rc::new(RefCell::new(None)); follower.update(cx, { let update = pending_update.clone(); + let is_still_following = is_still_following.clone(); |_, cx| { cx.subscribe(&leader, move |_, leader, event, cx| { leader @@ -5001,6 +5003,13 @@ async fn test_following(cx: &mut gpui::TestAppContext) { .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx); }) .detach(); + + cx.subscribe(&follower, move |_, _, event, cx| { + if Editor::should_unfollow_on_event(event, cx) { + *is_still_following.borrow_mut() = false; + } + }) + .detach(); } }); @@ -5017,6 +5026,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) { follower.read_with(cx, |follower, cx| { assert_eq!(follower.selections.ranges(cx), vec![1..1]); }); + assert_eq!(*is_still_following.borrow(), true); // Update the scroll position only leader.update(cx, |leader, cx| { @@ -5032,6 +5042,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) { follower.update(cx, |follower, cx| follower.scroll_position(cx)), vec2f(1.5, 3.5) ); + assert_eq!(*is_still_following.borrow(), true); // Update the selections and scroll position. The follower's scroll position is updated // via autoscroll, not via the leader's exact scroll position. @@ -5050,6 +5061,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) { assert_eq!(follower.scroll_position(cx), vec2f(1.5, 0.0)); assert_eq!(follower.selections.ranges(cx), vec![0..0]); }); + assert_eq!(*is_still_following.borrow(), true); // Creating a pending selection that precedes another selection leader.update(cx, |leader, cx| { @@ -5065,6 +5077,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) { follower.read_with(cx, |follower, cx| { assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]); }); + assert_eq!(*is_still_following.borrow(), true); // Extend the pending selection so that it surrounds another selection leader.update(cx, |leader, cx| { @@ -5079,6 +5092,19 @@ async fn test_following(cx: &mut gpui::TestAppContext) { follower.read_with(cx, |follower, cx| { assert_eq!(follower.selections.ranges(cx), vec![0..2]); }); + + // Scrolling locally breaks the follow + follower.update(cx, |follower, cx| { + let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0); + follower.set_scroll_anchor( + ScrollAnchor { + top_anchor, + offset: vec2f(0.0, 0.5), + }, + cx, + ); + }); + assert_eq!(*is_still_following.borrow(), false); } #[gpui::test] diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 49605a1d16caaabcc4163e50754406a14d1b9a99..cdabf7193bcf1b9b06f64969daa4e08fb9682675 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -138,7 +138,7 @@ impl FollowableItem for Editor { } if let Some(scroll_top_anchor) = scroll_top_anchor { - editor.set_scroll_anchor( + editor.set_scroll_anchor_remote( ScrollAnchor { top_anchor: scroll_top_anchor, offset: vec2f(state.scroll_x, state.scroll_y), @@ -363,7 +363,10 @@ impl FollowableItem for Editor { this.set_selections_from_remote(selections, cx); this.request_autoscroll_remotely(Autoscroll::newest(), cx); } else if let Some(anchor) = scroll_top_anchor { - this.set_scroll_anchor(ScrollAnchor {top_anchor: anchor, offset: vec2f(message.scroll_x, message.scroll_y) }, cx); + this.set_scroll_anchor_remote(ScrollAnchor { + top_anchor: anchor, + offset: vec2f(message.scroll_x, message.scroll_y) + }, cx); } }); Ok(()) diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index 5cb58e21e990bdcc3923a9603207d35eeb431838..eb89bfeec206d44af2a60602919309c353f3c7e1 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -284,17 +284,17 @@ impl Editor { } pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext) { - self.set_scroll_anchor_internal(scroll_anchor, true, cx); + hide_hover(self, cx); + self.scroll_manager.set_anchor(scroll_anchor, true, cx); } - pub(crate) fn set_scroll_anchor_internal( + pub(crate) fn set_scroll_anchor_remote( &mut self, scroll_anchor: ScrollAnchor, - local: bool, cx: &mut ViewContext, ) { hide_hover(self, cx); - self.scroll_manager.set_anchor(scroll_anchor, local, cx); + self.scroll_manager.set_anchor(scroll_anchor, false, cx); } pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext) { diff --git a/crates/editor/src/scroll/actions.rs b/crates/editor/src/scroll/actions.rs index 8e574025326c3b2abb28245cc473a294198926ea..fb3dec01291e40024b7882c0d00628aab9cb20e4 100644 --- a/crates/editor/src/scroll/actions.rs +++ b/crates/editor/src/scroll/actions.rs @@ -64,15 +64,15 @@ impl Editor { return None; } - self.context_menu.as_mut()?; + if self.mouse_context_menu.read(cx).visible() { + return None; + } if matches!(self.mode, EditorMode::SingleLine) { cx.propagate_action(); return None; } - self.request_autoscroll(Autoscroll::Next, cx); - Some(()) } diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 2d4ae919f95d4fcaeb8f0a7466e39098132e1003..9f957f6e18ba145bb8e16ebba15c2743d3197d92 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -8,7 +8,7 @@ use anyhow::{anyhow, bail, Context, Result}; use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql}; use gpui::Axis; -use util::{iife, unzip_option, ResultExt}; +use util::{ unzip_option, ResultExt}; use crate::dock::DockPosition; use crate::WorkspaceId; @@ -96,22 +96,16 @@ impl WorkspaceDb { WorkspaceLocation, bool, DockPosition, - ) = iife!({ - if worktree_roots.len() == 0 { - self.select_row(sql!( - SELECT workspace_id, workspace_location, left_sidebar_open, dock_visible, dock_anchor - FROM workspaces - ORDER BY timestamp DESC LIMIT 1))?()? - } else { - self.select_row_bound(sql!( - SELECT workspace_id, workspace_location, left_sidebar_open, dock_visible, dock_anchor - FROM workspaces - WHERE workspace_location = ?))?(&workspace_location)? - } + ) = + self.select_row_bound(sql!{ + SELECT workspace_id, workspace_location, left_sidebar_open, dock_visible, dock_anchor + FROM workspaces + WHERE workspace_location = ? + }) + .and_then(|mut prepared_statement| (prepared_statement)(&workspace_location)) .context("No workspaces found") - }) - .warn_on_err() - .flatten()?; + .warn_on_err() + .flatten()?; Some(SerializedWorkspace { id: workspace_id, @@ -205,11 +199,21 @@ impl WorkspaceDb { } } + query! { + pub fn last_workspace() -> Result> { + SELECT workspace_location + FROM workspaces + WHERE workspace_location IS NOT NULL + ORDER BY timestamp DESC + LIMIT 1 + } + } + fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result { - self.get_pane_group(workspace_id, None)? + Ok(self.get_pane_group(workspace_id, None)? .into_iter() .next() - .context("No center pane group") + .unwrap_or_else(|| SerializedPaneGroup::Pane(SerializedPane { active: true, children: vec![] }))) } fn get_pane_group( @@ -263,7 +267,7 @@ impl WorkspaceDb { // Filter out panes and pane groups which don't have any children or items .filter(|pane_group| match pane_group { Ok(SerializedPaneGroup::Group { children, .. }) => !children.is_empty(), - Ok(SerializedPaneGroup::Pane(pane)) => !pane.children.is_empty(), + Ok(SerializedPaneGroup::Pane(pane)) => !pane.children.is_empty(), _ => true, }) .collect::>() @@ -371,6 +375,15 @@ impl WorkspaceDb { Ok(()) } + + query!{ + pub async fn update_timestamp(workspace_id: WorkspaceId) -> Result<()> { + UPDATE workspaces + SET timestamp = CURRENT_TIMESTAMP + WHERE workspace_id = ? + } + } + } #[cfg(test)] diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index c75488561f50735ae98c0c4e08a256995a9cba55..b264114fb68820203f90cd8b139d2d85a4836864 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -106,7 +106,6 @@ impl SerializedPaneGroup { .await { members.push(new_member); - current_active_pane = current_active_pane.or(active_pane); } } @@ -115,6 +114,10 @@ impl SerializedPaneGroup { return None; } + if members.len() == 1 { + return Some((members.remove(0), current_active_pane)); + } + Some(( Member::Axis(PaneAxis { axis: *axis, @@ -130,9 +133,10 @@ impl SerializedPaneGroup { .deserialize_to(project, &pane, workspace_id, workspace, cx) .await; - if pane.read_with(cx, |pane, _| pane.items().next().is_some()) { + if pane.read_with(cx, |pane, _| pane.items_len() != 0) { Some((Member::Pane(pane.clone()), active.then(|| pane))) } else { + workspace.update(cx, |workspace, cx| workspace.remove_pane(pane, cx)); None } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 0d1564c396b911eb168dff03f13bc873bc56c5c0..ecc6a43fccd2d08be2f2c131cfbd112dc7138436 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -178,6 +178,7 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { } } }); + cx.add_global_action({ let app_state = Arc::downgrade(&app_state); move |_: &NewWindow, cx: &mut MutableAppContext| { @@ -2167,7 +2168,11 @@ impl Workspace { } pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext) { - if !active { + if active { + cx.background() + .spawn(persistence::DB.update_timestamp(self.database_id())) + .detach(); + } else { for pane in &self.panes { pane.update(cx, |pane, cx| { if let Some(item) = pane.active_item() { @@ -2281,6 +2286,9 @@ impl Workspace { } 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 dock_pane = serialize_pane_handle(self.dock.pane(), cx); let center_group = build_serialized_pane_group(&self.center.root, cx); @@ -2308,9 +2316,14 @@ impl Workspace { ) { cx.spawn(|mut cx| async move { if let Some(workspace) = workspace.upgrade(&cx) { - let (project, dock_pane_handle) = workspace.read_with(&cx, |workspace, _| { - (workspace.project().clone(), workspace.dock_pane().clone()) - }); + let (project, dock_pane_handle, old_center_pane) = + workspace.read_with(&cx, |workspace, _| { + ( + workspace.project().clone(), + workspace.dock_pane().clone(), + workspace.last_active_center_pane.clone(), + ) + }); serialized_workspace .dock_pane @@ -2346,11 +2359,14 @@ impl Workspace { cx.focus(workspace.panes.last().unwrap().clone()); } } else { - cx.focus_self(); + let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx)); + if let Some(old_center_handle) = old_center_handle { + cx.focus(old_center_handle) + } else { + cx.focus_self() + } } - // Note, if this is moved after 'set_dock_position' - // it causes an infinite loop. if workspace.left_sidebar().read(cx).is_open() != serialized_workspace.left_sidebar_open { @@ -2604,6 +2620,10 @@ pub fn activate_workspace_for_project( None } +pub fn last_opened_workspace_paths() -> Option { + DB.last_workspace().log_err().flatten() +} + #[allow(clippy::type_complexity)] pub fn open_paths( abs_paths: &[PathBuf], diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 09a20b566002158d6f9f4e3471fb9e39c8852a73..bffd567505b1973163f50ecc8afdd28886a48470 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -169,7 +169,7 @@ fn main() { cx.platform().activate(true); let paths = collect_path_args(); if paths.is_empty() { - cx.dispatch_global_action(NewFile); + restore_or_create_workspace(cx); } else { cx.dispatch_global_action(OpenPaths { paths }); } @@ -178,7 +178,7 @@ fn main() { cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) .detach(); } else { - cx.dispatch_global_action(NewFile); + restore_or_create_workspace(cx); } cx.spawn(|cx| async move { while let Some(connection) = cli_connections_rx.next().await { @@ -202,6 +202,16 @@ fn main() { }); } +fn restore_or_create_workspace(cx: &mut gpui::MutableAppContext) { + if let Some(location) = workspace::last_opened_workspace_paths() { + cx.dispatch_global_action(OpenPaths { + paths: location.paths().as_ref().clone(), + }) + } else { + cx.dispatch_global_action(NewFile); + } +} + fn init_paths() { std::fs::create_dir_all(&*util::paths::CONFIG_DIR).expect("could not create config path"); std::fs::create_dir_all(&*util::paths::LANGUAGES_DIR).expect("could not create languages path");