diff --git a/crates/git_graph/src/git_graph.rs b/crates/git_graph/src/git_graph.rs index 284d5758f1d6ed0ede9bb4dd6de9818980fe6c8d..d81e3e02c0e4b16a5d42fe2c71a04ed3bce8a304 100644 --- a/crates/git_graph/src/git_graph.rs +++ b/crates/git_graph/src/git_graph.rs @@ -5,7 +5,7 @@ use git::{ parse_git_remote_url, repository::{CommitDiff, InitialGraphCommitData, LogOrder, LogSource}, }; -use git_ui::commit_tooltip::CommitAvatar; +use git_ui::{commit_tooltip::CommitAvatar, commit_view::CommitView}; use gpui::{ AnyElement, App, Bounds, ClipboardItem, Context, Corner, DefiniteLength, ElementId, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Hsla, InteractiveElement, ParentElement, @@ -31,12 +31,6 @@ use workspace::{ item::{Item, ItemEvent, SerializableItem}, }; -pub struct GitGraphFeatureFlag; - -impl FeatureFlag for GitGraphFeatureFlag { - const NAME: &'static str = "git-graph"; -} - const COMMIT_CIRCLE_RADIUS: Pixels = px(4.5); const COMMIT_CIRCLE_STROKE_WIDTH: Pixels = px(1.5); const LANE_WIDTH: Pixels = px(16.0); @@ -46,13 +40,17 @@ const LINE_WIDTH: Pixels = px(1.5); actions!( git_graph, [ - /// Opens the Git Graph panel. - Open, /// Opens the commit view for the selected commit. OpenCommitView, ] ); +pub struct GitGraphFeatureFlag; + +impl FeatureFlag for GitGraphFeatureFlag { + const NAME: &'static str = "git-graph"; +} + fn timestamp_format() -> &'static [BorrowedFormatItem<'static>] { static FORMAT: OnceLock>> = OnceLock::new(); FORMAT.get_or_init(|| { @@ -509,11 +507,19 @@ pub fn init(cx: &mut App) { |div| { let workspace = workspace.weak_handle(); - div.on_action(move |_: &Open, window, cx| { + div.on_action(move |_: &git_ui::git_panel::Open, window, cx| { workspace .update(cx, |workspace, cx| { + let existing = workspace.items_of_type::(cx).next(); + if let Some(existing) = existing { + workspace.activate_item(&existing, true, true, window, cx); + return; + } + let project = workspace.project().clone(); - let git_graph = cx.new(|cx| GitGraph::new(project, window, cx)); + let workspace_handle = workspace.weak_handle(); + let git_graph = cx + .new(|cx| GitGraph::new(project, workspace_handle, window, cx)); workspace.add_item_to_active_pane( Box::new(git_graph), None, @@ -579,6 +585,7 @@ pub struct GitGraph { focus_handle: FocusHandle, graph_data: GraphData, project: Entity, + workspace: WeakEntity, context_menu: Option<(Entity, Point, Subscription)>, row_height: Pixels, table_interaction_state: Entity, @@ -604,7 +611,12 @@ impl GitGraph { (LANE_WIDTH * self.graph_data.max_lanes.min(8) as f32) + LEFT_PADDING * 2.0 } - pub fn new(project: Entity, window: &mut Window, cx: &mut Context) -> Self { + pub fn new( + project: Entity, + workspace: WeakEntity, + window: &mut Window, + cx: &mut Context, + ) -> Self { let focus_handle = cx.focus_handle(); cx.on_focus(&focus_handle, window, |_, _, cx| cx.notify()) .detach(); @@ -662,6 +674,7 @@ impl GitGraph { GitGraph { focus_handle, project, + workspace, graph_data: graph, _load_task: None, _commit_diff_task: None, @@ -903,6 +916,43 @@ impl GitGraph { cx.notify(); } + fn open_selected_commit_view(&mut self, window: &mut Window, cx: &mut Context) { + let Some(selected_entry_index) = self.selected_entry_idx else { + return; + }; + + self.open_commit_view(selected_entry_index, window, cx); + } + + fn open_commit_view( + &mut self, + entry_index: usize, + window: &mut Window, + cx: &mut Context, + ) { + let Some(commit_entry) = self.graph_data.commits.get(entry_index) else { + return; + }; + + let repository = self + .project + .read_with(cx, |project, cx| project.active_repository(cx)); + + let Some(repository) = repository else { + return; + }; + + CommitView::open( + commit_entry.data.sha.to_string(), + repository.downgrade(), + self.workspace.clone(), + None, + None, + window, + cx, + ); + } + fn get_remote( &self, repository: &Repository, @@ -1602,9 +1652,13 @@ impl Render for GitGraph { .when(is_selected, |row| { row.bg(cx.theme().colors().element_selected) }) - .on_click(move |_, _, cx| { + .on_click(move |event, window, cx| { + let click_count = event.click_count(); weak.update(cx, |this, cx| { this.select_entry(index, cx); + if click_count >= 2 { + this.open_commit_view(index, window, cx); + } }) .ok(); }) @@ -1627,6 +1681,9 @@ impl Render for GitGraph { .bg(cx.theme().colors().editor_background) .key_context("GitGraph") .track_focus(&self.focus_handle) + .on_action(cx.listener(|this, _: &OpenCommitView, window, cx| { + this.open_selected_commit_view(window, cx); + })) .on_action(cx.listener(Self::select_prev)) .on_action(cx.listener(Self::select_next)) .child(content) @@ -1688,7 +1745,7 @@ impl SerializableItem for GitGraph { fn deserialize( project: Entity, - _: WeakEntity, + workspace: WeakEntity, workspace_id: workspace::WorkspaceId, item_id: workspace::ItemId, window: &mut Window, @@ -1699,7 +1756,7 @@ impl SerializableItem for GitGraph { .ok() .is_some_and(|is_open| is_open) { - let git_graph = cx.new(|cx| GitGraph::new(project, window, cx)); + let git_graph = cx.new(|cx| GitGraph::new(project, workspace, window, cx)); Task::ready(Ok(git_graph)) } else { Task::ready(Err(anyhow::anyhow!("No git graph to deserialize"))) diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 0afbbaa2c3027d34394b19ae15d609b6279cc2ce..5e71b62e22b8b3f4bbfcdcbff3f93c9ea6abde91 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -78,6 +78,7 @@ use workspace::{ dock::{DockPosition, Panel, PanelEvent}, notifications::{DetachAndPromptErr, ErrorMessagePrompt, NotificationId, NotifyResultExt}, }; + actions!( git_panel, [ @@ -112,6 +113,14 @@ actions!( ] ); +actions!( + git_graph, + [ + /// Opens the Git Graph Tab. + Open, + ] +); + fn prompt( msg: &str, detail: Option<&str>, @@ -4448,7 +4457,11 @@ impl GitPanel { ) } - fn render_previous_commit(&self, cx: &mut Context) -> Option { + fn render_previous_commit( + &self, + window: &mut Window, + cx: &mut Context, + ) -> Option { let active_repository = self.active_repository.as_ref()?; let branch = active_repository.read(cx).branch.as_ref()?; let commit = branch.most_recent_commit.as_ref()?.clone(); @@ -4507,22 +4520,37 @@ impl GitPanel { .when(commit.has_parent, |this| { let has_unstaged = self.has_unstaged_changes(); this.pr_2().child( - panel_icon_button("undo", IconName::Undo) + h_flex().gap_1().child( + panel_icon_button("undo", IconName::Undo) + .icon_size(IconSize::XSmall) + .icon_color(Color::Muted) + .tooltip(move |_window, cx| { + Tooltip::with_meta( + "Uncommit", + Some(&git::Uncommit), + if has_unstaged { + "git reset HEAD^ --soft" + } else { + "git reset HEAD^" + }, + cx, + ) + }) + .on_click( + cx.listener(|this, _, window, cx| this.uncommit(window, cx)), + ), + ), + ) + }) + .when(window.is_action_available(&Open, cx), |this| { + this.child( + panel_icon_button("git-graph-button", IconName::ListTree) .icon_size(IconSize::XSmall) .icon_color(Color::Muted) - .tooltip(move |_window, cx| { - Tooltip::with_meta( - "Uncommit", - Some(&git::Uncommit), - if has_unstaged { - "git reset HEAD^ --soft" - } else { - "git reset HEAD^" - }, - cx, - ) - }) - .on_click(cx.listener(|this, _, window, cx| this.uncommit(window, cx))), + .tooltip(|_window, cx| Tooltip::for_action("Open Git Graph", &Open, cx)) + .on_click(|_, window, cx| { + window.dispatch_action(Open.boxed_clone(), cx) + }), ) }), ) @@ -5513,7 +5541,7 @@ impl Render for GitPanel { this.child(self.render_pending_amend(cx)) }) .when(!self.amend_pending, |this| { - this.children(self.render_previous_commit(cx)) + this.children(self.render_previous_commit(window, cx)) }) .into_any_element(), )