@@ -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<Vec<BorrowedFormatItem<'static>>> = 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::<GitGraph>(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<Project>,
+ workspace: WeakEntity<Workspace>,
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
row_height: Pixels,
table_interaction_state: Entity<TableInteractionState>,
@@ -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<Project>, window: &mut Window, cx: &mut Context<Self>) -> Self {
+ pub fn new(
+ project: Entity<Project>,
+ workspace: WeakEntity<Workspace>,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> 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<Self>) {
+ 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<Self>,
+ ) {
+ 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<Project>,
- _: WeakEntity<Workspace>,
+ workspace: WeakEntity<Workspace>,
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")))
@@ -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<T>(
msg: &str,
detail: Option<&str>,
@@ -4448,7 +4457,11 @@ impl GitPanel {
)
}
- fn render_previous_commit(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> {
+ fn render_previous_commit(
+ &self,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> Option<impl IntoElement> {
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(),
)