From 0cb7dd297224cf28c191ddf4dff4add079c506b2 Mon Sep 17 00:00:00 2001 From: vipex <101529155+vipexv@users.noreply.github.com> Date: Mon, 9 Jun 2025 13:21:36 +0200 Subject: [PATCH] git_panel: Persist dock size (#32111) Closes #32054 The dock size for the git panel wasn't being persisted across Zed restarts. This was because the git panel lacked the serialization pattern used by other panels. Please let me know if you have any sort of feedback or anything, as i'm still trying to learn :] Release Notes: - Fixed Git Panel dock size not being remembered across Zed restarts ## TODO - [x] Update/fix tests that may be broken by the GitPanel constructor changes --- crates/git_ui/src/git_panel.rs | 297 ++++++++++++++++++--------------- crates/zed/src/zed.rs | 10 +- 2 files changed, 167 insertions(+), 140 deletions(-) diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 0bcec87de3f4c8df451d015373bfdb0ac24ad59a..fd18f6e7bf6b5835ba762913520706bf10538076 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -27,11 +27,12 @@ use git::status::StageStatus; use git::{Amend, ToggleStaged, repository::RepoPath, status::FileStatus}; use git::{ExpandCommitEditor, RestoreTrackedFiles, StageAll, TrashUntrackedFiles, UnstageAll}; use gpui::{ - Action, Animation, AnimationExt as _, Axis, ClickEvent, Corner, DismissEvent, Entity, - EventEmitter, FocusHandle, Focusable, KeyContext, ListHorizontalSizingBehavior, - ListSizingBehavior, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, Point, - PromptLevel, ScrollStrategy, Subscription, Task, Transformation, UniformListScrollHandle, - WeakEntity, actions, anchored, deferred, percentage, uniform_list, + Action, Animation, AnimationExt as _, AsyncWindowContext, Axis, ClickEvent, Corner, + DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, KeyContext, + ListHorizontalSizingBehavior, ListSizingBehavior, Modifiers, ModifiersChangedEvent, + MouseButton, MouseDownEvent, Point, PromptLevel, ScrollStrategy, Subscription, Task, + Transformation, UniformListScrollHandle, WeakEntity, actions, anchored, deferred, percentage, + uniform_list, }; use itertools::Itertools; use language::{Buffer, File}; @@ -63,7 +64,7 @@ use ui::{ Tooltip, prelude::*, }; use util::{ResultExt, TryFutureExt, maybe}; -use workspace::AppState; + use workspace::{ Workspace, dock::{DockPosition, Panel, PanelEvent}, @@ -389,144 +390,148 @@ pub(crate) fn commit_message_editor( } impl GitPanel { - pub fn new( - workspace: Entity, - project: Entity, - app_state: Arc, + fn new( + workspace: &mut Workspace, window: &mut Window, - cx: &mut Context, - ) -> Self { + cx: &mut Context, + ) -> Entity { + let project = workspace.project().clone(); + let app_state = workspace.app_state().clone(); let fs = app_state.fs.clone(); let git_store = project.read(cx).git_store().clone(); let active_repository = project.read(cx).active_repository(cx); - let workspace = workspace.downgrade(); - let focus_handle = cx.focus_handle(); - cx.on_focus(&focus_handle, window, Self::focus_in).detach(); - cx.on_focus_out(&focus_handle, window, |this, _, window, cx| { - this.hide_scrollbars(window, cx); - }) - .detach(); + let git_panel = cx.new(|cx| { + let focus_handle = cx.focus_handle(); + cx.on_focus(&focus_handle, window, Self::focus_in).detach(); + cx.on_focus_out(&focus_handle, window, |this, _, window, cx| { + this.hide_scrollbars(window, cx); + }) + .detach(); - let mut was_sort_by_path = GitPanelSettings::get_global(cx).sort_by_path; - cx.observe_global::(move |this, cx| { - let is_sort_by_path = GitPanelSettings::get_global(cx).sort_by_path; - if is_sort_by_path != was_sort_by_path { - this.update_visible_entries(cx); - } - was_sort_by_path = is_sort_by_path - }) - .detach(); + let mut was_sort_by_path = GitPanelSettings::get_global(cx).sort_by_path; + cx.observe_global::(move |this, cx| { + let is_sort_by_path = GitPanelSettings::get_global(cx).sort_by_path; + if is_sort_by_path != was_sort_by_path { + this.update_visible_entries(cx); + } + was_sort_by_path = is_sort_by_path + }) + .detach(); - // just to let us render a placeholder editor. - // Once the active git repo is set, this buffer will be replaced. - let temporary_buffer = cx.new(|cx| Buffer::local("", cx)); - let commit_editor = cx.new(|cx| { - commit_message_editor(temporary_buffer, None, project.clone(), true, window, cx) - }); + // just to let us render a placeholder editor. + // Once the active git repo is set, this buffer will be replaced. + let temporary_buffer = cx.new(|cx| Buffer::local("", cx)); + let commit_editor = cx.new(|cx| { + commit_message_editor(temporary_buffer, None, project.clone(), true, window, cx) + }); - commit_editor.update(cx, |editor, cx| { - editor.clear(window, cx); - }); + commit_editor.update(cx, |editor, cx| { + editor.clear(window, cx); + }); - let scroll_handle = UniformListScrollHandle::new(); + let scroll_handle = UniformListScrollHandle::new(); - cx.subscribe_in( - &git_store, - window, - move |this, git_store, event, window, cx| match event { - GitStoreEvent::ActiveRepositoryChanged(_) => { - this.active_repository = git_store.read(cx).active_repository(); - this.schedule_update(true, window, cx); - } - GitStoreEvent::RepositoryUpdated( - _, - RepositoryEvent::Updated { full_scan }, - true, - ) => { - this.schedule_update(*full_scan, window, cx); - } + let vertical_scrollbar = ScrollbarProperties { + axis: Axis::Vertical, + state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()), + show_scrollbar: false, + show_track: false, + auto_hide: false, + hide_task: None, + }; - GitStoreEvent::RepositoryAdded(_) | GitStoreEvent::RepositoryRemoved(_) => { - this.schedule_update(false, window, cx); - } - GitStoreEvent::IndexWriteError(error) => { - this.workspace - .update(cx, |workspace, cx| { - workspace.show_error(error, cx); - }) - .ok(); + let horizontal_scrollbar = ScrollbarProperties { + axis: Axis::Horizontal, + state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()), + show_scrollbar: false, + show_track: false, + auto_hide: false, + hide_task: None, + }; + + let mut assistant_enabled = AgentSettings::get_global(cx).enabled; + let _settings_subscription = cx.observe_global::(move |_, cx| { + if assistant_enabled != AgentSettings::get_global(cx).enabled { + assistant_enabled = AgentSettings::get_global(cx).enabled; + cx.notify(); } - GitStoreEvent::RepositoryUpdated(_, _, _) => {} - GitStoreEvent::JobsUpdated | GitStoreEvent::ConflictsUpdated => {} - }, - ) - .detach(); + }); - let vertical_scrollbar = ScrollbarProperties { - axis: Axis::Vertical, - state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()), - show_scrollbar: false, - show_track: false, - auto_hide: false, - hide_task: None, - }; + cx.subscribe_in( + &git_store, + window, + move |this, _git_store, event, window, cx| match event { + GitStoreEvent::ActiveRepositoryChanged(_) => { + this.active_repository = this.project.read(cx).active_repository(cx); + this.schedule_update(true, window, cx); + } + GitStoreEvent::RepositoryUpdated( + _, + RepositoryEvent::Updated { full_scan }, + true, + ) => { + this.schedule_update(*full_scan, window, cx); + } - let horizontal_scrollbar = ScrollbarProperties { - axis: Axis::Horizontal, - state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()), - show_scrollbar: false, - show_track: false, - auto_hide: false, - hide_task: None, - }; + GitStoreEvent::RepositoryAdded(_) | GitStoreEvent::RepositoryRemoved(_) => { + this.schedule_update(false, window, cx); + } + GitStoreEvent::IndexWriteError(error) => { + this.workspace + .update(cx, |workspace, cx| { + workspace.show_error(error, cx); + }) + .ok(); + } + GitStoreEvent::RepositoryUpdated(_, _, _) => {} + GitStoreEvent::JobsUpdated | GitStoreEvent::ConflictsUpdated => {} + }, + ) + .detach(); - let mut assistant_enabled = AgentSettings::get_global(cx).enabled; - let _settings_subscription = cx.observe_global::(move |_, cx| { - if assistant_enabled != AgentSettings::get_global(cx).enabled { - assistant_enabled = AgentSettings::get_global(cx).enabled; - cx.notify(); - } + let mut this = Self { + active_repository, + commit_editor, + conflicted_count: 0, + conflicted_staged_count: 0, + current_modifiers: window.modifiers(), + add_coauthors: true, + generate_commit_message_task: None, + entries: Vec::new(), + focus_handle: cx.focus_handle(), + fs, + new_count: 0, + new_staged_count: 0, + pending: Vec::new(), + pending_commit: None, + amend_pending: false, + pending_serialization: Task::ready(None), + single_staged_entry: None, + single_tracked_entry: None, + project, + scroll_handle, + max_width_item_index: None, + selected_entry: None, + marked_entries: Vec::new(), + tracked_count: 0, + tracked_staged_count: 0, + update_visible_entries_task: Task::ready(()), + width: None, + show_placeholders: false, + context_menu: None, + workspace: workspace.weak_handle(), + modal_open: false, + entry_count: 0, + horizontal_scrollbar, + vertical_scrollbar, + _settings_subscription, + }; + + this.schedule_update(false, window, cx); + this }); - let mut git_panel = Self { - active_repository, - commit_editor, - conflicted_count: 0, - conflicted_staged_count: 0, - current_modifiers: window.modifiers(), - add_coauthors: true, - generate_commit_message_task: None, - entries: Vec::new(), - focus_handle: cx.focus_handle(), - fs, - new_count: 0, - new_staged_count: 0, - pending: Vec::new(), - pending_commit: None, - amend_pending: false, - pending_serialization: Task::ready(None), - single_staged_entry: None, - single_tracked_entry: None, - project, - scroll_handle, - max_width_item_index: None, - selected_entry: None, - marked_entries: Vec::new(), - tracked_count: 0, - tracked_staged_count: 0, - update_visible_entries_task: Task::ready(()), - width: None, - show_placeholders: false, - context_menu: None, - workspace, - modal_open: false, - entry_count: 0, - horizontal_scrollbar, - vertical_scrollbar, - _settings_subscription, - }; - git_panel.schedule_update(false, window, cx); git_panel } @@ -4141,6 +4146,32 @@ impl GitPanel { self.amend_pending = value; cx.notify(); } + + pub async fn load( + workspace: WeakEntity, + mut cx: AsyncWindowContext, + ) -> anyhow::Result> { + let serialized_panel = cx + .background_spawn(async move { KEY_VALUE_STORE.read_kvp(&GIT_PANEL_KEY) }) + .await + .context("loading git panel") + .log_err() + .flatten() + .and_then(|panel| serde_json::from_str::(&panel).log_err()); + + workspace.update_in(&mut cx, |workspace, window, cx| { + let panel = GitPanel::new(workspace, window, cx); + + if let Some(serialized_panel) = serialized_panel { + panel.update(cx, |panel, cx| { + panel.width = serialized_panel.width; + cx.notify(); + }) + } + + panel + }) + } } fn current_language_model(cx: &Context<'_, GitPanel>) -> Option> { @@ -4852,7 +4883,7 @@ impl Component for PanelRepoFooter { #[cfg(test)] mod tests { use git::status::StatusCode; - use gpui::TestAppContext; + use gpui::{TestAppContext, VisualTestContext}; use project::{FakeFs, WorktreeSettings}; use serde_json::json; use settings::SettingsStore; @@ -4916,8 +4947,9 @@ mod tests { let project = Project::test(fs.clone(), [path!("/root/zed/crates/gpui").as_ref()], cx).await; - let (workspace, cx) = - cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); + let workspace = + cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx)); + let cx = &mut VisualTestContext::from_window(*workspace, cx); cx.read(|cx| { project @@ -4934,10 +4966,7 @@ mod tests { cx.executor().run_until_parked(); - let app_state = workspace.read_with(cx, |workspace, _| workspace.app_state().clone()); - let panel = cx.new_window_entity(|window, cx| { - GitPanel::new(workspace.clone(), project.clone(), app_state, window, cx) - }); + let panel = workspace.update(cx, GitPanel::new).unwrap(); let handle = cx.update_window_entity(&panel, |panel, _, _| { std::mem::replace(&mut panel.update_visible_entries_task, Task::ready(())) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 22a03ea9bc15e8f147b6a77240d50b743a7982bd..c9784d6f15e3b9fa282dfc53093d547cb29cbd5c 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -472,6 +472,7 @@ fn initialize_panels( let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone()); let outline_panel = OutlinePanel::load(workspace_handle.clone(), cx.clone()); let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone()); + let git_panel = GitPanel::load(workspace_handle.clone(), cx.clone()); let channels_panel = collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone()); let chat_panel = @@ -485,12 +486,14 @@ fn initialize_panels( project_panel, outline_panel, terminal_panel, + git_panel, channels_panel, chat_panel, notification_panel, ) = futures::try_join!( project_panel, outline_panel, + git_panel, terminal_panel, channels_panel, chat_panel, @@ -501,6 +504,7 @@ fn initialize_panels( workspace.add_panel(project_panel, window, cx); workspace.add_panel(outline_panel, window, cx); workspace.add_panel(terminal_panel, window, cx); + workspace.add_panel(git_panel, window, cx); workspace.add_panel(channels_panel, window, cx); workspace.add_panel(chat_panel, window, cx); workspace.add_panel(notification_panel, window, cx); @@ -518,12 +522,6 @@ fn initialize_panels( ) .detach() }); - - let entity = cx.entity(); - let project = workspace.project().clone(); - let app_state = workspace.app_state().clone(); - let git_panel = cx.new(|cx| GitPanel::new(entity, project, app_state, window, cx)); - workspace.add_panel(git_panel, window, cx); })?; let is_assistant2_enabled = !cfg!(test);