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
This commit is contained in:
vipex 2025-06-09 13:21:36 +02:00 committed by GitHub
parent 387281fa5b
commit 0cb7dd2972
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 175 additions and 148 deletions

View file

@ -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<Workspace>,
project: Entity<Project>,
app_state: Arc<AppState>,
fn new(
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
cx: &mut Context<Workspace>,
) -> Entity<Self> {
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::<SettingsStore>(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::<SettingsStore>(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);
});
let scroll_handle = UniformListScrollHandle::new();
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,
};
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::<SettingsStore>(move |_, cx| {
if assistant_enabled != AgentSettings::get_global(cx).enabled {
assistant_enabled = AgentSettings::get_global(cx).enabled;
cx.notify();
}
});
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);
}
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 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
});
commit_editor.update(cx, |editor, cx| {
editor.clear(window, cx);
});
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);
}
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 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,
};
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::<SettingsStore>(move |_, cx| {
if assistant_enabled != AgentSettings::get_global(cx).enabled {
assistant_enabled = AgentSettings::get_global(cx).enabled;
cx.notify();
}
});
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<Workspace>,
mut cx: AsyncWindowContext,
) -> anyhow::Result<Entity<Self>> {
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::<SerializedGitPanel>(&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<Arc<dyn LanguageModel>> {
@ -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(()))