diff --git a/Cargo.lock b/Cargo.lock index def04c674e5f7ba6cbfb2e8db21c7374cbafdaa0..cfa2e72fdce561468e8770e4d89da5c33d6ffd39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5174,9 +5174,17 @@ dependencies = [ name = "git_ui" version = "0.1.0" dependencies = [ + "anyhow", + "db", "gpui", + "project", + "schemars", "serde", + "serde_derive", + "serde_json", + "settings", "ui", + "util", "windows 0.58.0", "workspace", ] diff --git a/assets/settings/default.json b/assets/settings/default.json index 3634f088f40c8de71086f632e0bcd84a421f497e..a5ebc0d7148cbb193ea8e0f2a0c7fb1fd867ce6e 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -471,6 +471,14 @@ // Default width of the chat panel. "default_width": 240 }, + "git_panel": { + // Whether to show the git panel button in the status bar. + "button": true, + // Where to the git panel. Can be 'left' or 'right'. + "dock": "left", + // Default width of the git panel. + "default_width": 360 + }, "message_editor": { // Whether to automatically replace emoji shortcodes with emoji characters. // For example: typing `:wave:` gets replaced with `👋`. diff --git a/crates/git_ui/Cargo.toml b/crates/git_ui/Cargo.toml index 4875b2afdf439d94d64db0d17f0d3f7c905b4d36..a7804af0bbd0e755295434ca5139683e5b902964 100644 --- a/crates/git_ui/Cargo.toml +++ b/crates/git_ui/Cargo.toml @@ -13,10 +13,18 @@ name = "git_ui" path = "src/git_ui.rs" [dependencies] +anyhow.workspace = true +db.workspace = true gpui.workspace = true +project.workspace = true +schemars.workspace = true serde.workspace = true -workspace.workspace = true +serde_derive.workspace = true +serde_json.workspace = true +settings.workspace = true ui.workspace = true +util.workspace = true +workspace.workspace = true [target.'cfg(windows)'.dependencies] windows.workspace = true diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index b901414d8c3e09727841ffaa64b36077f0a8ffab..e06787bd4a7c9ec8188ce9b5b6fd26c63566bae9 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -1,8 +1,19 @@ +use std::sync::Arc; +use util::TryFutureExt; + +use db::kvp::KEY_VALUE_STORE; use gpui::*; +use project::Fs; +use serde::{Deserialize, Serialize}; +use settings::Settings as _; use ui::{prelude::*, Checkbox, Divider, DividerColor, ElevationIndex}; use workspace::dock::{DockPosition, Panel, PanelEvent}; use workspace::Workspace; +use crate::settings::GitPanelSettings; + +const GIT_PANEL_KEY: &str = "GitPanel"; + pub fn init(cx: &mut AppContext) { cx.observe_new_views( |workspace: &mut Workspace, _cx: &mut ViewContext| { @@ -14,11 +25,17 @@ pub fn init(cx: &mut AppContext) { .detach(); } +#[derive(Serialize, Deserialize)] +struct SerializedGitPanel { + width: Option, +} + actions!(git_panel, [Deploy, ToggleFocus]); -#[derive(Clone)] pub struct GitPanel { _workspace: WeakView, + pending_serialization: Task>, + fs: Arc, focus_handle: FocusHandle, width: Option, } @@ -29,20 +46,39 @@ impl GitPanel { cx: AsyncWindowContext, ) -> Task>> { cx.spawn(|mut cx| async move { - workspace.update(&mut cx, |workspace, cx| { - let workspace_handle = workspace.weak_handle(); - - cx.new_view(|cx| Self::new(workspace_handle, cx)) - }) + // Clippy incorrectly classifies this as a redundant closure + #[allow(clippy::redundant_closure)] + workspace.update(&mut cx, |workspace, cx| Self::new(workspace, cx)) }) } - pub fn new(workspace: WeakView, cx: &mut ViewContext) -> Self { - Self { - _workspace: workspace, + pub fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> View { + let fs = workspace.app_state().fs.clone(); + let weak_workspace = workspace.weak_handle(); + + cx.new_view(|cx| Self { + fs, + _workspace: weak_workspace, + pending_serialization: Task::ready(None), focus_handle: cx.focus_handle(), width: Some(px(360.)), - } + }) + } + + fn serialize(&mut self, cx: &mut ViewContext) { + let width = self.width; + self.pending_serialization = cx.background_executor().spawn( + async move { + KEY_VALUE_STORE + .write_kvp( + GIT_PANEL_KEY.into(), + serde_json::to_string(&SerializedGitPanel { width })?, + ) + .await?; + anyhow::Ok(()) + } + .log_err(), + ); } pub fn render_panel_header(&self, cx: &mut ViewContext) -> impl IntoElement { @@ -53,14 +89,14 @@ impl GitPanel { .bg(ElevationIndex::Surface.bg(cx)) .child( h_flex() - .gap_1() + .gap_2() .child(Checkbox::new("all-changes", true.into()).disabled(true)) .child(div().text_buffer(cx).text_ui_sm(cx).child("0 changes")), ) .child(div().flex_grow()) .child( h_flex() - .gap_1() + .gap_2() .child( IconButton::new("discard-changes", IconName::Undo) .icon_size(IconSize::Small) @@ -104,6 +140,22 @@ impl GitPanel { .opacity(0.5), ) } + + fn render_empty_state(&self, cx: &ViewContext) -> impl IntoElement { + h_flex() + .h_full() + .flex_1() + .justify_center() + .items_center() + .child( + v_flex() + .gap_3() + .child("No changes to commit") + .text_ui_sm(cx) + .mx_auto() + .text_color(Color::Placeholder.color(cx)), + ) + } } impl Render for GitPanel { @@ -124,7 +176,7 @@ impl Render for GitPanel { .h(px(8.)) .child(Divider::horizontal_dashed().color(DividerColor::Border)), ) - .child(div().flex_1()) + .child(self.render_empty_state(cx)) .child( h_flex() .items_center() @@ -148,27 +200,35 @@ impl Panel for GitPanel { "GitPanel" } - fn position(&self, _cx: &gpui::WindowContext) -> DockPosition { - DockPosition::Left + fn position(&self, cx: &gpui::WindowContext) -> DockPosition { + GitPanelSettings::get_global(cx).dock } fn position_is_valid(&self, position: DockPosition) -> bool { matches!(position, DockPosition::Left | DockPosition::Right) } - fn set_position(&mut self, _position: DockPosition, _cx: &mut ViewContext) {} + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { + settings::update_settings_file::( + self.fs.clone(), + cx, + move |settings, _| settings.dock = Some(position), + ); + } - fn size(&self, _cx: &gpui::WindowContext) -> Pixels { - self.width.unwrap_or(px(360.)) + fn size(&self, cx: &gpui::WindowContext) -> Pixels { + self.width + .unwrap_or_else(|| GitPanelSettings::get_global(cx).default_width) } fn set_size(&mut self, size: Option, cx: &mut ViewContext) { self.width = size; + self.serialize(cx); cx.notify(); } - fn icon(&self, _cx: &gpui::WindowContext) -> Option { - Some(ui::IconName::GitBranch) + fn icon(&self, cx: &WindowContext) -> Option { + Some(ui::IconName::GitBranch).filter(|_| GitPanelSettings::get_global(cx).button) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { diff --git a/crates/git_ui/src/git_ui.rs b/crates/git_ui/src/git_ui.rs index b23171d722c2d1b5765543b967f16538f34266aa..a61733f8534717840b44a3e92e0b0b808b425f8a 100644 --- a/crates/git_ui/src/git_ui.rs +++ b/crates/git_ui/src/git_ui.rs @@ -1 +1,10 @@ +use ::settings::Settings; +use gpui::AppContext; +use settings::GitPanelSettings; + pub mod git_panel; +mod settings; + +pub fn init(cx: &mut AppContext) { + GitPanelSettings::register(cx); +} diff --git a/crates/git_ui/src/settings.rs b/crates/git_ui/src/settings.rs new file mode 100644 index 0000000000000000000000000000000000000000..fc2f4d51af4f12271292870af5258f768b160236 --- /dev/null +++ b/crates/git_ui/src/settings.rs @@ -0,0 +1,41 @@ +use gpui::Pixels; +use schemars::JsonSchema; +use serde_derive::{Deserialize, Serialize}; +use settings::{Settings, SettingsSources}; +use workspace::dock::DockPosition; + +#[derive(Deserialize, Debug)] +pub struct GitPanelSettings { + pub button: bool, + pub dock: DockPosition, + pub default_width: Pixels, +} + +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +pub struct PanelSettingsContent { + /// Whether to show the panel button in the status bar. + /// + /// Default: true + pub button: Option, + /// Where to dock the panel. + /// + /// Default: left + pub dock: Option, + /// Default width of the panel in pixels. + /// + /// Default: 360 + pub default_width: Option, +} + +impl Settings for GitPanelSettings { + const KEY: Option<&'static str> = Some("git_panel"); + + type FileContent = PanelSettingsContent; + + fn load( + sources: SettingsSources, + _: &mut gpui::AppContext, + ) -> anyhow::Result { + sources.json_merge() + } +} diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index ab90b8ec6b6e507e2a60f0990e7626fd70783d7e..062fff049d1c166f71a4ce7ef8034924ab6e5fe7 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -463,6 +463,7 @@ fn main() { call::init(app_state.client.clone(), app_state.user_store.clone(), cx); notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx); collab_ui::init(&app_state, cx); + git_ui::init(cx); vcs_menu::init(cx); feedback::init(cx); markdown_preview::init(cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index a024a098ae1160ab0a641de5cff3e419df0e57ae..4fc6ef944fe5e6d3650197c5a67e9aade4331633 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -3477,6 +3477,7 @@ mod tests { language::init(cx); editor::init(cx); collab_ui::init(&app_state, cx); + git_ui::init(cx); project_panel::init((), cx); outline_panel::init((), cx); terminal_view::init(cx);