diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index ff5d7533f412872908d52228590fa3afe45a02d0..d2f70b825b4efa3544e916589846e7944a91771a 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -1080,7 +1080,8 @@ { "context": "StashList || (StashList > Picker > Editor)", "bindings": { - "ctrl-shift-backspace": "stash_picker::DropStashItem" + "ctrl-shift-backspace": "stash_picker::DropStashItem", + "ctrl-shift-v": "stash_picker::ShowStashItem" } }, { @@ -1266,6 +1267,14 @@ "ctrl-pagedown": "settings_editor::FocusNextFile" } }, + { + "context": "StashDiff > Editor", + "bindings": { + "ctrl-space": "git::ApplyCurrentStash", + "ctrl-shift-space": "git::PopCurrentStash", + "ctrl-shift-backspace": "git::DropCurrentStash" + } + }, { "context": "SettingsWindow > NavigationMenu", "use_key_equivalents": true, diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 9b20c267feeed5068b05cb08e0755d3faab75c96..75dd84f7ae269f0883809c8ec1b2516b25f024c9 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -1153,7 +1153,8 @@ "context": "StashList || (StashList > Picker > Editor)", "use_key_equivalents": true, "bindings": { - "ctrl-shift-backspace": "stash_picker::DropStashItem" + "ctrl-shift-backspace": "stash_picker::DropStashItem", + "ctrl-shift-v": "stash_picker::ShowStashItem" } }, { @@ -1371,6 +1372,15 @@ "cmd-}": "settings_editor::FocusNextFile" } }, + { + "context": "StashDiff > Editor", + "use_key_equivalents": true, + "bindings": { + "ctrl-space": "git::ApplyCurrentStash", + "ctrl-shift-space": "git::PopCurrentStash", + "ctrl-shift-backspace": "git::DropCurrentStash" + } + }, { "context": "SettingsWindow > NavigationMenu", "use_key_equivalents": true, diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 87e1c350dc10c1f47ceb260b4ce2a03a032b0996..e38bca90e4394ec415df253a7d9668cadefceac1 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -1106,7 +1106,8 @@ "context": "StashList || (StashList > Picker > Editor)", "use_key_equivalents": true, "bindings": { - "ctrl-shift-backspace": "stash_picker::DropStashItem" + "ctrl-shift-backspace": "stash_picker::DropStashItem", + "ctrl-shift-v": "stash_picker::ShowStashItem" } }, { @@ -1294,6 +1295,15 @@ "ctrl-pagedown": "settings_editor::FocusNextFile" } }, + { + "context": "StashDiff > Editor", + "use_key_equivalents": true, + "bindings": { + "ctrl-space": "git::ApplyCurrentStash", + "ctrl-shift-space": "git::PopCurrentStash", + "ctrl-shift-backspace": "git::DropCurrentStash" + } + }, { "context": "SettingsWindow > NavigationMenu", "use_key_equivalents": true, diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index 2e132d4eaca55c9307bf3368c412f77ed6726df2..dc674fb3dd5cbe6a55837780f7ac10449e2efd41 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -693,10 +693,11 @@ impl GitRepository for RealGitRepository { .args([ "--no-optional-locks", "show", - "--format=%P", + "--format=", "-z", "--no-renames", "--name-status", + "--first-parent", ]) .arg(&commit) .stdin(Stdio::null()) @@ -707,9 +708,8 @@ impl GitRepository for RealGitRepository { .context("starting git show process")?; let show_stdout = String::from_utf8_lossy(&show_output.stdout); - let mut lines = show_stdout.split('\n'); - let parent_sha = lines.next().unwrap().trim().trim_end_matches('\0'); - let changes = parse_git_diff_name_status(lines.next().unwrap_or("")); + let changes = parse_git_diff_name_status(&show_stdout); + let parent_sha = format!("{}^", commit); let mut cat_file_process = util::command::new_smol_command(&git_binary_path) .current_dir(&working_directory) diff --git a/crates/git_ui/src/blame_ui.rs b/crates/git_ui/src/blame_ui.rs index 31726dc0b33c5274519c7b77b61c10609ac3bfcc..6059bc9e83b63e710815891165fe6e530a0efa1a 100644 --- a/crates/git_ui/src/blame_ui.rs +++ b/crates/git_ui/src/blame_ui.rs @@ -98,25 +98,10 @@ impl BlameRenderer for GitBlameRenderer { let workspace = workspace.clone(); move |_, window, cx| { CommitView::open( - CommitSummary { - sha: blame_entry.sha.to_string().into(), - subject: blame_entry - .summary - .clone() - .unwrap_or_default() - .into(), - commit_timestamp: blame_entry - .committer_time - .unwrap_or_default(), - author_name: blame_entry - .committer_name - .clone() - .unwrap_or_default() - .into(), - has_parent: true, - }, + blame_entry.sha.to_string(), repository.downgrade(), workspace.clone(), + None, window, cx, ) @@ -335,9 +320,10 @@ impl BlameRenderer for GitBlameRenderer { .icon_size(IconSize::Small) .on_click(move |_, window, cx| { CommitView::open( - commit_summary.clone(), + commit_summary.sha.clone().into(), repository.downgrade(), workspace.clone(), + None, window, cx, ); @@ -374,15 +360,10 @@ impl BlameRenderer for GitBlameRenderer { cx: &mut App, ) { CommitView::open( - CommitSummary { - sha: blame_entry.sha.to_string().into(), - subject: blame_entry.summary.clone().unwrap_or_default().into(), - commit_timestamp: blame_entry.committer_time.unwrap_or_default(), - author_name: blame_entry.committer_name.unwrap_or_default().into(), - has_parent: true, - }, + blame_entry.sha.to_string(), repository.downgrade(), workspace, + None, window, cx, ) diff --git a/crates/git_ui/src/commit_tooltip.rs b/crates/git_ui/src/commit_tooltip.rs index 84ecc0b3a9c0c708ec81a0af1234506ec0208cd0..97224840debcc4cfd8dcc74a56d448ef0d2826c1 100644 --- a/crates/git_ui/src/commit_tooltip.rs +++ b/crates/git_ui/src/commit_tooltip.rs @@ -318,9 +318,10 @@ impl Render for CommitTooltip { .on_click( move |_, window, cx| { CommitView::open( - commit_summary.clone(), + commit_summary.sha.to_string(), repo.downgrade(), workspace.clone(), + None, window, cx, ); diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index f89afb0a64b4377b235866c4da66e8255f2320d1..9738e13984a0b032b09a218990f3466052e9fa61 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -1,10 +1,11 @@ use anyhow::{Context as _, Result}; use buffer_diff::{BufferDiff, BufferDiffSnapshot}; use editor::{Editor, EditorEvent, MultiBuffer, SelectionEffects, multibuffer_context_lines}; -use git::repository::{CommitDetails, CommitDiff, CommitSummary, RepoPath}; +use git::repository::{CommitDetails, CommitDiff, RepoPath}; use gpui::{ - AnyElement, AnyView, App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, - FocusHandle, Focusable, IntoElement, Render, WeakEntity, Window, + Action, AnyElement, AnyView, App, AppContext as _, AsyncApp, AsyncWindowContext, Context, + Entity, EventEmitter, FocusHandle, Focusable, IntoElement, PromptLevel, Render, WeakEntity, + Window, actions, }; use language::{ Anchor, Buffer, Capability, DiskState, File, LanguageRegistry, LineEnding, OffsetRangeExt as _, @@ -18,17 +19,42 @@ use std::{ path::PathBuf, sync::Arc, }; -use ui::{Color, Icon, IconName, Label, LabelCommon as _, SharedString}; +use ui::{ + Button, Color, Icon, IconName, Label, LabelCommon as _, SharedString, Tooltip, prelude::*, +}; use util::{ResultExt, paths::PathStyle, rel_path::RelPath, truncate_and_trailoff}; use workspace::{ - Item, ItemHandle as _, ItemNavHistory, ToolbarItemLocation, Workspace, + Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, + Workspace, item::{BreadcrumbText, ItemEvent, TabContentParams}, + notifications::NotifyTaskExt, + pane::SaveIntent, searchable::SearchableItemHandle, }; +use crate::git_panel::GitPanel; + +actions!(git, [ApplyCurrentStash, PopCurrentStash, DropCurrentStash,]); + +pub fn init(cx: &mut App) { + cx.observe_new(|workspace: &mut Workspace, _window, _cx| { + register_workspace_action(workspace, |toolbar, _: &ApplyCurrentStash, window, cx| { + toolbar.apply_stash(window, cx); + }); + register_workspace_action(workspace, |toolbar, _: &DropCurrentStash, window, cx| { + toolbar.remove_stash(window, cx); + }); + register_workspace_action(workspace, |toolbar, _: &PopCurrentStash, window, cx| { + toolbar.pop_stash(window, cx); + }); + }) + .detach(); +} + pub struct CommitView { commit: CommitDetails, editor: Entity, + stash: Option, multibuffer: Entity, } @@ -48,17 +74,18 @@ const FILE_NAMESPACE_SORT_PREFIX: u64 = 1; impl CommitView { pub fn open( - commit: CommitSummary, + commit_sha: String, repo: WeakEntity, workspace: WeakEntity, + stash: Option, window: &mut Window, cx: &mut App, ) { let commit_diff = repo - .update(cx, |repo, _| repo.load_commit_diff(commit.sha.to_string())) + .update(cx, |repo, _| repo.load_commit_diff(commit_sha.clone())) .ok(); let commit_details = repo - .update(cx, |repo, _| repo.show(commit.sha.to_string())) + .update(cx, |repo, _| repo.show(commit_sha.clone())) .ok(); window @@ -77,6 +104,7 @@ impl CommitView { commit_diff, repo, project.clone(), + stash, window, cx, ) @@ -87,7 +115,7 @@ impl CommitView { let ix = pane.items().position(|item| { let commit_view = item.downcast::(); commit_view - .is_some_and(|view| view.read(cx).commit.sha == commit.sha) + .is_some_and(|view| view.read(cx).commit.sha == commit_sha) }); if let Some(ix) = ix { pane.activate_item(ix, true, true, window, cx); @@ -106,6 +134,7 @@ impl CommitView { commit_diff: CommitDiff, repository: Entity, project: Entity, + stash: Option, window: &mut Window, cx: &mut Context, ) -> Self { @@ -127,10 +156,13 @@ impl CommitView { let mut metadata_buffer_id = None; if let Some(worktree_id) = first_worktree_id { + let title = if let Some(stash) = stash { + format!("stash@{{{}}}", stash) + } else { + format!("commit {}", commit.sha) + }; let file = Arc::new(CommitMetadataFile { - title: RelPath::unix(&format!("commit {}", commit.sha)) - .unwrap() - .into(), + title: RelPath::unix(&title).unwrap().into(), worktree_id, }); let buffer = cx.new(|cx| { @@ -138,7 +170,7 @@ impl CommitView { ReplicaId::LOCAL, cx.entity_id().as_non_zero_u64().into(), LineEnding::default(), - format_commit(&commit).into(), + format_commit(&commit, stash.is_some()).into(), ); metadata_buffer_id = Some(buffer.remote_id()); Buffer::build(buffer, Some(file.clone()), Capability::ReadWrite) @@ -211,6 +243,7 @@ impl CommitView { commit, editor, multibuffer, + stash, } } } @@ -369,9 +402,13 @@ async fn build_buffer_diff( }) } -fn format_commit(commit: &CommitDetails) -> String { +fn format_commit(commit: &CommitDetails, is_stash: bool) -> String { let mut result = String::new(); - writeln!(&mut result, "commit {}", commit.sha).unwrap(); + if is_stash { + writeln!(&mut result, "stash commit {}", commit.sha).unwrap(); + } else { + writeln!(&mut result, "commit {}", commit.sha).unwrap(); + } writeln!( &mut result, "Author: {} <{}>", @@ -538,13 +575,296 @@ impl Item for CommitView { editor, multibuffer, commit: self.commit.clone(), + stash: self.stash, } })) } } impl Render for CommitView { - fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { - self.editor.clone() + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + let is_stash = self.stash.is_some(); + div() + .key_context(if is_stash { "StashDiff" } else { "CommitDiff" }) + .bg(cx.theme().colors().editor_background) + .flex() + .items_center() + .justify_center() + .size_full() + .child(self.editor.clone()) + } +} + +pub struct CommitViewToolbar { + commit_view: Option>, + workspace: WeakEntity, +} + +impl CommitViewToolbar { + pub fn new(workspace: &Workspace, _: &mut Context) -> Self { + Self { + commit_view: None, + workspace: workspace.weak_handle(), + } + } + + fn commit_view(&self, _: &App) -> Option> { + self.commit_view.as_ref()?.upgrade() + } + + async fn close_commit_view( + commit_view: Entity, + workspace: WeakEntity, + cx: &mut AsyncWindowContext, + ) -> anyhow::Result<()> { + workspace + .update_in(cx, |workspace, window, cx| { + let active_pane = workspace.active_pane(); + let commit_view_id = commit_view.entity_id(); + active_pane.update(cx, |pane, cx| { + pane.close_item_by_id(commit_view_id, SaveIntent::Skip, window, cx) + }) + })? + .await?; + anyhow::Ok(()) + } + + fn apply_stash(&mut self, window: &mut Window, cx: &mut Context) { + self.stash_action( + "Apply", + window, + cx, + async move |repository, sha, stash, commit_view, workspace, cx| { + let result = repository.update(cx, |repo, cx| { + if !stash_matches_index(&sha, stash, repo) { + return Err(anyhow::anyhow!("Stash has changed, not applying")); + } + Ok(repo.stash_apply(Some(stash), cx)) + })?; + + match result { + Ok(task) => task.await?, + Err(err) => { + Self::close_commit_view(commit_view, workspace, cx).await?; + return Err(err); + } + }; + Self::close_commit_view(commit_view, workspace, cx).await?; + anyhow::Ok(()) + }, + ); + } + + fn pop_stash(&mut self, window: &mut Window, cx: &mut Context) { + self.stash_action( + "Pop", + window, + cx, + async move |repository, sha, stash, commit_view, workspace, cx| { + let result = repository.update(cx, |repo, cx| { + if !stash_matches_index(&sha, stash, repo) { + return Err(anyhow::anyhow!("Stash has changed, pop aborted")); + } + Ok(repo.stash_pop(Some(stash), cx)) + })?; + + match result { + Ok(task) => task.await?, + Err(err) => { + Self::close_commit_view(commit_view, workspace, cx).await?; + return Err(err); + } + }; + Self::close_commit_view(commit_view, workspace, cx).await?; + anyhow::Ok(()) + }, + ); + } + + fn remove_stash(&mut self, window: &mut Window, cx: &mut Context) { + self.stash_action( + "Drop", + window, + cx, + async move |repository, sha, stash, commit_view, workspace, cx| { + let result = repository.update(cx, |repo, cx| { + if !stash_matches_index(&sha, stash, repo) { + return Err(anyhow::anyhow!("Stash has changed, drop aborted")); + } + Ok(repo.stash_drop(Some(stash), cx)) + })?; + + match result { + Ok(task) => task.await??, + Err(err) => { + Self::close_commit_view(commit_view, workspace, cx).await?; + return Err(err); + } + }; + Self::close_commit_view(commit_view, workspace, cx).await?; + anyhow::Ok(()) + }, + ); + } + + fn stash_action( + &mut self, + str_action: &str, + window: &mut Window, + cx: &mut Context, + callback: AsyncFn, + ) where + AsyncFn: AsyncFnOnce( + Entity, + &SharedString, + usize, + Entity, + WeakEntity, + &mut AsyncWindowContext, + ) -> anyhow::Result<()> + + 'static, + { + let Some(commit_view) = self.commit_view(cx) else { + return; + }; + let Some(stash) = commit_view.read(cx).stash else { + return; + }; + let sha = commit_view.read(cx).commit.sha.clone(); + let answer = window.prompt( + PromptLevel::Info, + &format!("{} stash@{{{}}}?", str_action, stash), + None, + &[str_action, "Cancel"], + cx, + ); + + let workspace = self.workspace.clone(); + cx.spawn_in(window, async move |_, cx| { + if answer.await != Ok(0) { + return anyhow::Ok(()); + } + let repo = workspace.update(cx, |workspace, cx| { + workspace + .panel::(cx) + .and_then(|p| p.read(cx).active_repository.clone()) + })?; + + let Some(repo) = repo else { + return Ok(()); + }; + callback(repo, &sha, stash, commit_view, workspace, cx).await?; + anyhow::Ok(()) + }) + .detach_and_notify_err(window, cx); + } +} + +impl EventEmitter for CommitViewToolbar {} + +impl ToolbarItemView for CommitViewToolbar { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn ItemHandle>, + _: &mut Window, + cx: &mut Context, + ) -> ToolbarItemLocation { + if let Some(entity) = active_pane_item.and_then(|i| i.act_as::(cx)) + && entity.read(cx).stash.is_some() + { + self.commit_view = Some(entity.downgrade()); + return ToolbarItemLocation::PrimaryRight; + } + ToolbarItemLocation::Hidden + } + + fn pane_focus_update( + &mut self, + _pane_focused: bool, + _window: &mut Window, + _cx: &mut Context, + ) { + } +} + +impl Render for CommitViewToolbar { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + let Some(commit_view) = self.commit_view(cx) else { + return div(); + }; + + let is_stash = commit_view.read(cx).stash.is_some(); + if !is_stash { + return div(); + } + + let focus_handle = commit_view.focus_handle(cx); + + h_group_xl().my_neg_1().py_1().items_center().child( + h_group_sm() + .child( + Button::new("apply-stash", "Apply") + .tooltip(Tooltip::for_action_title_in( + "Apply current stash", + &ApplyCurrentStash, + &focus_handle, + )) + .on_click(cx.listener(|this, _, window, cx| this.apply_stash(window, cx))), + ) + .child( + Button::new("pop-stash", "Pop") + .tooltip(Tooltip::for_action_title_in( + "Pop current stash", + &PopCurrentStash, + &focus_handle, + )) + .on_click(cx.listener(|this, _, window, cx| this.pop_stash(window, cx))), + ) + .child( + Button::new("remove-stash", "Remove") + .icon(IconName::Trash) + .tooltip(Tooltip::for_action_title_in( + "Remove current stash", + &DropCurrentStash, + &focus_handle, + )) + .on_click(cx.listener(|this, _, window, cx| this.remove_stash(window, cx))), + ), + ) + } +} + +fn register_workspace_action( + workspace: &mut Workspace, + callback: fn(&mut CommitViewToolbar, &A, &mut Window, &mut Context), +) { + workspace.register_action(move |workspace, action: &A, window, cx| { + if workspace.has_active_modal(window, cx) { + cx.propagate(); + return; + } + + workspace.active_pane().update(cx, |pane, cx| { + pane.toolbar().update(cx, move |workspace, cx| { + if let Some(toolbar) = workspace.item_of_type::() { + toolbar.update(cx, move |toolbar, cx| { + callback(toolbar, action, window, cx); + cx.notify(); + }); + } + }); + }) + }); +} + +fn stash_matches_index(sha: &str, index: usize, repo: &mut Repository) -> bool { + match repo + .cached_stash() + .entries + .iter() + .find(|entry| entry.index == index) + { + Some(entry) => entry.oid.to_string() == sha, + None => false, } } diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index ce6ddf43f6dbf1e4df32c45f7c96f7c08447df06..2e34c060f61c5dd9ed6718d379e16019c8e17b16 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -3611,9 +3611,10 @@ impl GitPanel { let repo = active_repository.downgrade(); move |_, window, cx| { CommitView::open( - commit.clone(), + commit.sha.to_string(), repo.clone(), workspace.clone(), + None, window, cx, ); diff --git a/crates/git_ui/src/git_ui.rs b/crates/git_ui/src/git_ui.rs index da2e2ca032aa005ad619eabf094ae6981975b050..303e23c959557efe859cb069c1e41ff8352923fe 100644 --- a/crates/git_ui/src/git_ui.rs +++ b/crates/git_ui/src/git_ui.rs @@ -34,7 +34,7 @@ mod askpass_modal; pub mod branch_picker; mod commit_modal; pub mod commit_tooltip; -mod commit_view; +pub mod commit_view; mod conflict_view; pub mod file_diff_view; pub mod git_panel; @@ -59,6 +59,7 @@ pub fn init(cx: &mut App) { GitPanelSettings::register(cx); editor::set_blame_renderer(blame_ui::GitBlameRenderer, cx); + commit_view::init(cx); cx.observe_new(|editor: &mut Editor, _, cx| { conflict_view::register_editor(editor, editor.buffer().clone(), cx); diff --git a/crates/git_ui/src/stash_picker.rs b/crates/git_ui/src/stash_picker.rs index d82498007d3d38e509e34e86044fa0a0e188c910..3f159035a0ada5a79d26dd0d1d8222678aed23b3 100644 --- a/crates/git_ui/src/stash_picker.rs +++ b/crates/git_ui/src/stash_picker.rs @@ -5,18 +5,21 @@ use git::stash::StashEntry; use gpui::{ Action, AnyElement, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement, Render, - SharedString, Styled, Subscription, Task, Window, actions, rems, + SharedString, Styled, Subscription, Task, WeakEntity, Window, actions, rems, svg, }; use picker::{Picker, PickerDelegate}; use project::git_store::{Repository, RepositoryEvent}; use std::sync::Arc; use time::{OffsetDateTime, UtcOffset}; use time_format; -use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*}; +use ui::{ + ButtonLike, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*, +}; use util::ResultExt; use workspace::notifications::DetachAndPromptErr; use workspace::{ModalView, Workspace}; +use crate::commit_view::CommitView; use crate::stash_picker; actions!( @@ -24,6 +27,8 @@ actions!( [ /// Drop the selected stash entry. DropStashItem, + /// Show the diff view of the selected stash entry. + ShowStashItem, ] ); @@ -38,8 +43,9 @@ pub fn open( cx: &mut Context, ) { let repository = workspace.project().read(cx).active_repository(cx); + let weak_workspace = workspace.weak_handle(); workspace.toggle_modal(window, cx, |window, cx| { - StashList::new(repository, rems(34.), window, cx) + StashList::new(repository, weak_workspace, rems(34.), window, cx) }) } @@ -53,6 +59,7 @@ pub struct StashList { impl StashList { fn new( repository: Option>, + workspace: WeakEntity, width: Rems, window: &mut Window, cx: &mut Context, @@ -98,7 +105,7 @@ impl StashList { }) .detach_and_log_err(cx); - let delegate = StashListDelegate::new(repository, window, cx); + let delegate = StashListDelegate::new(repository, workspace, window, cx); let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx)); let picker_focus_handle = picker.focus_handle(cx); picker.update(cx, |picker, _| { @@ -131,6 +138,20 @@ impl StashList { cx.notify(); } + fn handle_show_stash( + &mut self, + _: &ShowStashItem, + window: &mut Window, + cx: &mut Context, + ) { + self.picker.update(cx, |picker, cx| { + picker + .delegate + .show_stash_at(picker.delegate.selected_index(), window, cx); + }); + cx.notify(); + } + fn handle_modifiers_changed( &mut self, ev: &ModifiersChangedEvent, @@ -157,6 +178,7 @@ impl Render for StashList { .w(self.width) .on_modifiers_changed(cx.listener(Self::handle_modifiers_changed)) .on_action(cx.listener(Self::handle_drop_stash)) + .on_action(cx.listener(Self::handle_show_stash)) .child(self.picker.clone()) } } @@ -172,6 +194,7 @@ pub struct StashListDelegate { matches: Vec, all_stash_entries: Option>, repo: Option>, + workspace: WeakEntity, selected_index: usize, last_query: String, modifiers: Modifiers, @@ -182,6 +205,7 @@ pub struct StashListDelegate { impl StashListDelegate { fn new( repo: Option>, + workspace: WeakEntity, _window: &mut Window, cx: &mut Context, ) -> Self { @@ -192,6 +216,7 @@ impl StashListDelegate { Self { matches: vec![], repo, + workspace, all_stash_entries: None, selected_index: 0, last_query: Default::default(), @@ -235,6 +260,25 @@ impl StashListDelegate { }); } + fn show_stash_at(&self, ix: usize, window: &mut Window, cx: &mut Context>) { + let Some(entry_match) = self.matches.get(ix) else { + return; + }; + let stash_sha = entry_match.entry.oid.to_string(); + let stash_index = entry_match.entry.index; + let Some(repo) = self.repo.clone() else { + return; + }; + CommitView::open( + stash_sha, + repo.downgrade(), + self.workspace.clone(), + Some(stash_index), + window, + cx, + ); + } + fn pop_stash(&self, stash_index: usize, window: &mut Window, cx: &mut Context>) { let Some(repo) = self.repo.clone() else { return; @@ -390,7 +434,7 @@ impl PickerDelegate for StashListDelegate { ix: usize, selected: bool, _window: &mut Window, - _cx: &mut Context>, + cx: &mut Context>, ) -> Option { let entry_match = &self.matches[ix]; @@ -432,11 +476,35 @@ impl PickerDelegate for StashListDelegate { .size(LabelSize::Small), ); + let show_button = div() + .group("show-button-hover") + .child( + ButtonLike::new("show-button") + .child( + svg() + .size(IconSize::Medium.rems()) + .flex_none() + .path(IconName::Eye.path()) + .text_color(Color::Default.color(cx)) + .group_hover("show-button-hover", |this| { + this.text_color(Color::Accent.color(cx)) + }) + .hover(|this| this.text_color(Color::Accent.color(cx))), + ) + .tooltip(Tooltip::for_action_title("Show Stash", &ShowStashItem)) + .on_click(cx.listener(move |picker, _, window, cx| { + cx.stop_propagation(); + picker.delegate.show_stash_at(ix, window, cx); + })), + ) + .into_any_element(); + Some( ListItem::new(SharedString::from(format!("stash-{ix}"))) .inset(true) .spacing(ListItemSpacing::Sparse) .toggle_state(selected) + .end_slot(show_button) .child( v_flex() .w_full() diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index e2116415d4b882b66728c43cee90b251b3448013..a9b28229de20b8d24e481482e1441482e5d36212 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -25,6 +25,7 @@ use feature_flags::{FeatureFlagAppExt, PanicFeatureFlag}; use fs::Fs; use futures::future::Either; use futures::{StreamExt, channel::mpsc, select_biased}; +use git_ui::commit_view::CommitViewToolbar; use git_ui::git_panel::GitPanel; use git_ui::project_diff::ProjectDiffToolbar; use gpui::{ @@ -1049,6 +1050,8 @@ fn initialize_pane( toolbar.add_item(migration_banner, window, cx); let project_diff_toolbar = cx.new(|cx| ProjectDiffToolbar::new(workspace, cx)); toolbar.add_item(project_diff_toolbar, window, cx); + let commit_view_toolbar = cx.new(|cx| CommitViewToolbar::new(workspace, cx)); + toolbar.add_item(commit_view_toolbar, window, cx); let agent_diff_toolbar = cx.new(AgentDiffToolbar::new); toolbar.add_item(agent_diff_toolbar, window, cx); let basedpyright_banner = cx.new(|cx| BasedPyrightBanner::new(workspace, cx));