git: Add diff view for stash entries (#38280)
Continues the work from #35927 to add a git diff view for stash entries. [Screencast From 2025-09-17 19-46-01.webm](https://github.com/user-attachments/assets/ded33782-adef-4696-8e34-3665911c09c7) Stash entries are [represented as commits](https://git-scm.com/docs/git-stash#_discussion) except they have up to 3 parents: ``` .----W (this is the stash entry) / /| -----H----I | \| U ``` Where `H` is the `HEAD` commit, `I` is a commit that records the state of the index, and `U` is another commit that records untracked files (when using `git stash -u`). Given this, I modified the existing commit view struct to allow loading stash and commits entries with git sha identifier so that we can get a similar git diff view for both of them. The stash diff is generated by comparing the stash commit with its parent (`<commit>^` or `H` in the diagram) which generates the same diff as doing `git stash show -p <stash entry>`. This *can* be counter-intuitive since a user may expect the comparison to be made between the stash commit and the current commit (`HEAD`), but given that the default behavior in git cli is to compare with the stash parent, I went for that approach. Hoping to get some feedback from a Zed team member to see if they agree with this approach. Release Notes: - Add git diff view for stash entries - Add toolbar on git diff view for stash entries - Prompt before executing a destructive stash action on diff view - Fix commit view for merge commits (see #38289)
This commit is contained in:
@@ -1080,7 +1080,8 @@
|
|||||||
{
|
{
|
||||||
"context": "StashList || (StashList > Picker > Editor)",
|
"context": "StashList || (StashList > Picker > Editor)",
|
||||||
"bindings": {
|
"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"
|
"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",
|
"context": "SettingsWindow > NavigationMenu",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
|
|||||||
@@ -1153,7 +1153,8 @@
|
|||||||
"context": "StashList || (StashList > Picker > Editor)",
|
"context": "StashList || (StashList > Picker > Editor)",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"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"
|
"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",
|
"context": "SettingsWindow > NavigationMenu",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
|
|||||||
@@ -1106,7 +1106,8 @@
|
|||||||
"context": "StashList || (StashList > Picker > Editor)",
|
"context": "StashList || (StashList > Picker > Editor)",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"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"
|
"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",
|
"context": "SettingsWindow > NavigationMenu",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
|
|||||||
@@ -693,10 +693,11 @@ impl GitRepository for RealGitRepository {
|
|||||||
.args([
|
.args([
|
||||||
"--no-optional-locks",
|
"--no-optional-locks",
|
||||||
"show",
|
"show",
|
||||||
"--format=%P",
|
"--format=",
|
||||||
"-z",
|
"-z",
|
||||||
"--no-renames",
|
"--no-renames",
|
||||||
"--name-status",
|
"--name-status",
|
||||||
|
"--first-parent",
|
||||||
])
|
])
|
||||||
.arg(&commit)
|
.arg(&commit)
|
||||||
.stdin(Stdio::null())
|
.stdin(Stdio::null())
|
||||||
@@ -707,9 +708,8 @@ impl GitRepository for RealGitRepository {
|
|||||||
.context("starting git show process")?;
|
.context("starting git show process")?;
|
||||||
|
|
||||||
let show_stdout = String::from_utf8_lossy(&show_output.stdout);
|
let show_stdout = String::from_utf8_lossy(&show_output.stdout);
|
||||||
let mut lines = show_stdout.split('\n');
|
let changes = parse_git_diff_name_status(&show_stdout);
|
||||||
let parent_sha = lines.next().unwrap().trim().trim_end_matches('\0');
|
let parent_sha = format!("{}^", commit);
|
||||||
let changes = parse_git_diff_name_status(lines.next().unwrap_or(""));
|
|
||||||
|
|
||||||
let mut cat_file_process = util::command::new_smol_command(&git_binary_path)
|
let mut cat_file_process = util::command::new_smol_command(&git_binary_path)
|
||||||
.current_dir(&working_directory)
|
.current_dir(&working_directory)
|
||||||
|
|||||||
@@ -98,25 +98,10 @@ impl BlameRenderer for GitBlameRenderer {
|
|||||||
let workspace = workspace.clone();
|
let workspace = workspace.clone();
|
||||||
move |_, window, cx| {
|
move |_, window, cx| {
|
||||||
CommitView::open(
|
CommitView::open(
|
||||||
CommitSummary {
|
blame_entry.sha.to_string(),
|
||||||
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,
|
|
||||||
},
|
|
||||||
repository.downgrade(),
|
repository.downgrade(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
|
None,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@@ -335,9 +320,10 @@ impl BlameRenderer for GitBlameRenderer {
|
|||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.on_click(move |_, window, cx| {
|
.on_click(move |_, window, cx| {
|
||||||
CommitView::open(
|
CommitView::open(
|
||||||
commit_summary.clone(),
|
commit_summary.sha.clone().into(),
|
||||||
repository.downgrade(),
|
repository.downgrade(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
|
None,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
@@ -374,15 +360,10 @@ impl BlameRenderer for GitBlameRenderer {
|
|||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) {
|
||||||
CommitView::open(
|
CommitView::open(
|
||||||
CommitSummary {
|
blame_entry.sha.to_string(),
|
||||||
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,
|
|
||||||
},
|
|
||||||
repository.downgrade(),
|
repository.downgrade(),
|
||||||
workspace,
|
workspace,
|
||||||
|
None,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -318,9 +318,10 @@ impl Render for CommitTooltip {
|
|||||||
.on_click(
|
.on_click(
|
||||||
move |_, window, cx| {
|
move |_, window, cx| {
|
||||||
CommitView::open(
|
CommitView::open(
|
||||||
commit_summary.clone(),
|
commit_summary.sha.to_string(),
|
||||||
repo.downgrade(),
|
repo.downgrade(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
|
None,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
||||||
use editor::{Editor, EditorEvent, MultiBuffer, SelectionEffects, multibuffer_context_lines};
|
use editor::{Editor, EditorEvent, MultiBuffer, SelectionEffects, multibuffer_context_lines};
|
||||||
use git::repository::{CommitDetails, CommitDiff, CommitSummary, RepoPath};
|
use git::repository::{CommitDetails, CommitDiff, RepoPath};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, AnyView, App, AppContext as _, AsyncApp, Context, Entity, EventEmitter,
|
Action, AnyElement, AnyView, App, AppContext as _, AsyncApp, AsyncWindowContext, Context,
|
||||||
FocusHandle, Focusable, IntoElement, Render, WeakEntity, Window,
|
Entity, EventEmitter, FocusHandle, Focusable, IntoElement, PromptLevel, Render, WeakEntity,
|
||||||
|
Window, actions,
|
||||||
};
|
};
|
||||||
use language::{
|
use language::{
|
||||||
Anchor, Buffer, Capability, DiskState, File, LanguageRegistry, LineEnding, OffsetRangeExt as _,
|
Anchor, Buffer, Capability, DiskState, File, LanguageRegistry, LineEnding, OffsetRangeExt as _,
|
||||||
@@ -18,17 +19,42 @@ use std::{
|
|||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::Arc,
|
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 util::{ResultExt, paths::PathStyle, rel_path::RelPath, truncate_and_trailoff};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
Item, ItemHandle as _, ItemNavHistory, ToolbarItemLocation, Workspace,
|
Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
|
||||||
|
Workspace,
|
||||||
item::{BreadcrumbText, ItemEvent, TabContentParams},
|
item::{BreadcrumbText, ItemEvent, TabContentParams},
|
||||||
|
notifications::NotifyTaskExt,
|
||||||
|
pane::SaveIntent,
|
||||||
searchable::SearchableItemHandle,
|
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 {
|
pub struct CommitView {
|
||||||
commit: CommitDetails,
|
commit: CommitDetails,
|
||||||
editor: Entity<Editor>,
|
editor: Entity<Editor>,
|
||||||
|
stash: Option<usize>,
|
||||||
multibuffer: Entity<MultiBuffer>,
|
multibuffer: Entity<MultiBuffer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,17 +74,18 @@ const FILE_NAMESPACE_SORT_PREFIX: u64 = 1;
|
|||||||
|
|
||||||
impl CommitView {
|
impl CommitView {
|
||||||
pub fn open(
|
pub fn open(
|
||||||
commit: CommitSummary,
|
commit_sha: String,
|
||||||
repo: WeakEntity<Repository>,
|
repo: WeakEntity<Repository>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
|
stash: Option<usize>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) {
|
||||||
let commit_diff = repo
|
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();
|
.ok();
|
||||||
let commit_details = repo
|
let commit_details = repo
|
||||||
.update(cx, |repo, _| repo.show(commit.sha.to_string()))
|
.update(cx, |repo, _| repo.show(commit_sha.clone()))
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
window
|
window
|
||||||
@@ -77,6 +104,7 @@ impl CommitView {
|
|||||||
commit_diff,
|
commit_diff,
|
||||||
repo,
|
repo,
|
||||||
project.clone(),
|
project.clone(),
|
||||||
|
stash,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@@ -87,7 +115,7 @@ impl CommitView {
|
|||||||
let ix = pane.items().position(|item| {
|
let ix = pane.items().position(|item| {
|
||||||
let commit_view = item.downcast::<CommitView>();
|
let commit_view = item.downcast::<CommitView>();
|
||||||
commit_view
|
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 {
|
if let Some(ix) = ix {
|
||||||
pane.activate_item(ix, true, true, window, cx);
|
pane.activate_item(ix, true, true, window, cx);
|
||||||
@@ -106,6 +134,7 @@ impl CommitView {
|
|||||||
commit_diff: CommitDiff,
|
commit_diff: CommitDiff,
|
||||||
repository: Entity<Repository>,
|
repository: Entity<Repository>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
|
stash: Option<usize>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -127,10 +156,13 @@ impl CommitView {
|
|||||||
|
|
||||||
let mut metadata_buffer_id = None;
|
let mut metadata_buffer_id = None;
|
||||||
if let Some(worktree_id) = first_worktree_id {
|
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 {
|
let file = Arc::new(CommitMetadataFile {
|
||||||
title: RelPath::unix(&format!("commit {}", commit.sha))
|
title: RelPath::unix(&title).unwrap().into(),
|
||||||
.unwrap()
|
|
||||||
.into(),
|
|
||||||
worktree_id,
|
worktree_id,
|
||||||
});
|
});
|
||||||
let buffer = cx.new(|cx| {
|
let buffer = cx.new(|cx| {
|
||||||
@@ -138,7 +170,7 @@ impl CommitView {
|
|||||||
ReplicaId::LOCAL,
|
ReplicaId::LOCAL,
|
||||||
cx.entity_id().as_non_zero_u64().into(),
|
cx.entity_id().as_non_zero_u64().into(),
|
||||||
LineEnding::default(),
|
LineEnding::default(),
|
||||||
format_commit(&commit).into(),
|
format_commit(&commit, stash.is_some()).into(),
|
||||||
);
|
);
|
||||||
metadata_buffer_id = Some(buffer.remote_id());
|
metadata_buffer_id = Some(buffer.remote_id());
|
||||||
Buffer::build(buffer, Some(file.clone()), Capability::ReadWrite)
|
Buffer::build(buffer, Some(file.clone()), Capability::ReadWrite)
|
||||||
@@ -211,6 +243,7 @@ impl CommitView {
|
|||||||
commit,
|
commit,
|
||||||
editor,
|
editor,
|
||||||
multibuffer,
|
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();
|
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!(
|
writeln!(
|
||||||
&mut result,
|
&mut result,
|
||||||
"Author: {} <{}>",
|
"Author: {} <{}>",
|
||||||
@@ -538,13 +575,296 @@ impl Item for CommitView {
|
|||||||
editor,
|
editor,
|
||||||
multibuffer,
|
multibuffer,
|
||||||
commit: self.commit.clone(),
|
commit: self.commit.clone(),
|
||||||
|
stash: self.stash,
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for CommitView {
|
impl Render for CommitView {
|
||||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
self.editor.clone()
|
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<WeakEntity<CommitView>>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommitViewToolbar {
|
||||||
|
pub fn new(workspace: &Workspace, _: &mut Context<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
commit_view: None,
|
||||||
|
workspace: workspace.weak_handle(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commit_view(&self, _: &App) -> Option<Entity<CommitView>> {
|
||||||
|
self.commit_view.as_ref()?.upgrade()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn close_commit_view(
|
||||||
|
commit_view: Entity<CommitView>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
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>) {
|
||||||
|
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>) {
|
||||||
|
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>) {
|
||||||
|
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<AsyncFn>(
|
||||||
|
&mut self,
|
||||||
|
str_action: &str,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
callback: AsyncFn,
|
||||||
|
) where
|
||||||
|
AsyncFn: AsyncFnOnce(
|
||||||
|
Entity<Repository>,
|
||||||
|
&SharedString,
|
||||||
|
usize,
|
||||||
|
Entity<CommitView>,
|
||||||
|
WeakEntity<Workspace>,
|
||||||
|
&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::<GitPanel>(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<ToolbarItemEvent> for CommitViewToolbar {}
|
||||||
|
|
||||||
|
impl ToolbarItemView for CommitViewToolbar {
|
||||||
|
fn set_active_pane_item(
|
||||||
|
&mut self,
|
||||||
|
active_pane_item: Option<&dyn ItemHandle>,
|
||||||
|
_: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> ToolbarItemLocation {
|
||||||
|
if let Some(entity) = active_pane_item.and_then(|i| i.act_as::<CommitView>(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<Self>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for CommitViewToolbar {
|
||||||
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> 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<A: Action>(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
callback: fn(&mut CommitViewToolbar, &A, &mut Window, &mut Context<CommitViewToolbar>),
|
||||||
|
) {
|
||||||
|
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::<CommitViewToolbar>() {
|
||||||
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3611,9 +3611,10 @@ impl GitPanel {
|
|||||||
let repo = active_repository.downgrade();
|
let repo = active_repository.downgrade();
|
||||||
move |_, window, cx| {
|
move |_, window, cx| {
|
||||||
CommitView::open(
|
CommitView::open(
|
||||||
commit.clone(),
|
commit.sha.to_string(),
|
||||||
repo.clone(),
|
repo.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
|
None,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ mod askpass_modal;
|
|||||||
pub mod branch_picker;
|
pub mod branch_picker;
|
||||||
mod commit_modal;
|
mod commit_modal;
|
||||||
pub mod commit_tooltip;
|
pub mod commit_tooltip;
|
||||||
mod commit_view;
|
pub mod commit_view;
|
||||||
mod conflict_view;
|
mod conflict_view;
|
||||||
pub mod file_diff_view;
|
pub mod file_diff_view;
|
||||||
pub mod git_panel;
|
pub mod git_panel;
|
||||||
@@ -59,6 +59,7 @@ pub fn init(cx: &mut App) {
|
|||||||
GitPanelSettings::register(cx);
|
GitPanelSettings::register(cx);
|
||||||
|
|
||||||
editor::set_blame_renderer(blame_ui::GitBlameRenderer, cx);
|
editor::set_blame_renderer(blame_ui::GitBlameRenderer, cx);
|
||||||
|
commit_view::init(cx);
|
||||||
|
|
||||||
cx.observe_new(|editor: &mut Editor, _, cx| {
|
cx.observe_new(|editor: &mut Editor, _, cx| {
|
||||||
conflict_view::register_editor(editor, editor.buffer().clone(), cx);
|
conflict_view::register_editor(editor, editor.buffer().clone(), cx);
|
||||||
|
|||||||
@@ -5,18 +5,21 @@ use git::stash::StashEntry;
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
Action, AnyElement, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
Action, AnyElement, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement, Render,
|
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 picker::{Picker, PickerDelegate};
|
||||||
use project::git_store::{Repository, RepositoryEvent};
|
use project::git_store::{Repository, RepositoryEvent};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use time::{OffsetDateTime, UtcOffset};
|
use time::{OffsetDateTime, UtcOffset};
|
||||||
use time_format;
|
use time_format;
|
||||||
use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*};
|
use ui::{
|
||||||
|
ButtonLike, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*,
|
||||||
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::notifications::DetachAndPromptErr;
|
use workspace::notifications::DetachAndPromptErr;
|
||||||
use workspace::{ModalView, Workspace};
|
use workspace::{ModalView, Workspace};
|
||||||
|
|
||||||
|
use crate::commit_view::CommitView;
|
||||||
use crate::stash_picker;
|
use crate::stash_picker;
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
@@ -24,6 +27,8 @@ actions!(
|
|||||||
[
|
[
|
||||||
/// Drop the selected stash entry.
|
/// Drop the selected stash entry.
|
||||||
DropStashItem,
|
DropStashItem,
|
||||||
|
/// Show the diff view of the selected stash entry.
|
||||||
|
ShowStashItem,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -38,8 +43,9 @@ pub fn open(
|
|||||||
cx: &mut Context<Workspace>,
|
cx: &mut Context<Workspace>,
|
||||||
) {
|
) {
|
||||||
let repository = workspace.project().read(cx).active_repository(cx);
|
let repository = workspace.project().read(cx).active_repository(cx);
|
||||||
|
let weak_workspace = workspace.weak_handle();
|
||||||
workspace.toggle_modal(window, cx, |window, cx| {
|
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 {
|
impl StashList {
|
||||||
fn new(
|
fn new(
|
||||||
repository: Option<Entity<Repository>>,
|
repository: Option<Entity<Repository>>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
width: Rems,
|
width: Rems,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
@@ -98,7 +105,7 @@ impl StashList {
|
|||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.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 = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
||||||
let picker_focus_handle = picker.focus_handle(cx);
|
let picker_focus_handle = picker.focus_handle(cx);
|
||||||
picker.update(cx, |picker, _| {
|
picker.update(cx, |picker, _| {
|
||||||
@@ -131,6 +138,20 @@ impl StashList {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_show_stash(
|
||||||
|
&mut self,
|
||||||
|
_: &ShowStashItem,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
self.picker.update(cx, |picker, cx| {
|
||||||
|
picker
|
||||||
|
.delegate
|
||||||
|
.show_stash_at(picker.delegate.selected_index(), window, cx);
|
||||||
|
});
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_modifiers_changed(
|
fn handle_modifiers_changed(
|
||||||
&mut self,
|
&mut self,
|
||||||
ev: &ModifiersChangedEvent,
|
ev: &ModifiersChangedEvent,
|
||||||
@@ -157,6 +178,7 @@ impl Render for StashList {
|
|||||||
.w(self.width)
|
.w(self.width)
|
||||||
.on_modifiers_changed(cx.listener(Self::handle_modifiers_changed))
|
.on_modifiers_changed(cx.listener(Self::handle_modifiers_changed))
|
||||||
.on_action(cx.listener(Self::handle_drop_stash))
|
.on_action(cx.listener(Self::handle_drop_stash))
|
||||||
|
.on_action(cx.listener(Self::handle_show_stash))
|
||||||
.child(self.picker.clone())
|
.child(self.picker.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,6 +194,7 @@ pub struct StashListDelegate {
|
|||||||
matches: Vec<StashEntryMatch>,
|
matches: Vec<StashEntryMatch>,
|
||||||
all_stash_entries: Option<Vec<StashEntry>>,
|
all_stash_entries: Option<Vec<StashEntry>>,
|
||||||
repo: Option<Entity<Repository>>,
|
repo: Option<Entity<Repository>>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
last_query: String,
|
last_query: String,
|
||||||
modifiers: Modifiers,
|
modifiers: Modifiers,
|
||||||
@@ -182,6 +205,7 @@ pub struct StashListDelegate {
|
|||||||
impl StashListDelegate {
|
impl StashListDelegate {
|
||||||
fn new(
|
fn new(
|
||||||
repo: Option<Entity<Repository>>,
|
repo: Option<Entity<Repository>>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<StashList>,
|
cx: &mut Context<StashList>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -192,6 +216,7 @@ impl StashListDelegate {
|
|||||||
Self {
|
Self {
|
||||||
matches: vec![],
|
matches: vec![],
|
||||||
repo,
|
repo,
|
||||||
|
workspace,
|
||||||
all_stash_entries: None,
|
all_stash_entries: None,
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
last_query: Default::default(),
|
last_query: Default::default(),
|
||||||
@@ -235,6 +260,25 @@ impl StashListDelegate {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_stash_at(&self, ix: usize, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
|
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<Picker<Self>>) {
|
fn pop_stash(&self, stash_index: usize, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
let Some(repo) = self.repo.clone() else {
|
let Some(repo) = self.repo.clone() else {
|
||||||
return;
|
return;
|
||||||
@@ -390,7 +434,7 @@ impl PickerDelegate for StashListDelegate {
|
|||||||
ix: usize,
|
ix: usize,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
_cx: &mut Context<Picker<Self>>,
|
cx: &mut Context<Picker<Self>>,
|
||||||
) -> Option<Self::ListItem> {
|
) -> Option<Self::ListItem> {
|
||||||
let entry_match = &self.matches[ix];
|
let entry_match = &self.matches[ix];
|
||||||
|
|
||||||
@@ -432,11 +476,35 @@ impl PickerDelegate for StashListDelegate {
|
|||||||
.size(LabelSize::Small),
|
.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(
|
Some(
|
||||||
ListItem::new(SharedString::from(format!("stash-{ix}")))
|
ListItem::new(SharedString::from(format!("stash-{ix}")))
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Sparse)
|
||||||
.toggle_state(selected)
|
.toggle_state(selected)
|
||||||
|
.end_slot(show_button)
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ use feature_flags::{FeatureFlagAppExt, PanicFeatureFlag};
|
|||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::future::Either;
|
use futures::future::Either;
|
||||||
use futures::{StreamExt, channel::mpsc, select_biased};
|
use futures::{StreamExt, channel::mpsc, select_biased};
|
||||||
|
use git_ui::commit_view::CommitViewToolbar;
|
||||||
use git_ui::git_panel::GitPanel;
|
use git_ui::git_panel::GitPanel;
|
||||||
use git_ui::project_diff::ProjectDiffToolbar;
|
use git_ui::project_diff::ProjectDiffToolbar;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@@ -1049,6 +1050,8 @@ fn initialize_pane(
|
|||||||
toolbar.add_item(migration_banner, window, cx);
|
toolbar.add_item(migration_banner, window, cx);
|
||||||
let project_diff_toolbar = cx.new(|cx| ProjectDiffToolbar::new(workspace, cx));
|
let project_diff_toolbar = cx.new(|cx| ProjectDiffToolbar::new(workspace, cx));
|
||||||
toolbar.add_item(project_diff_toolbar, window, 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);
|
let agent_diff_toolbar = cx.new(AgentDiffToolbar::new);
|
||||||
toolbar.add_item(agent_diff_toolbar, window, cx);
|
toolbar.add_item(agent_diff_toolbar, window, cx);
|
||||||
let basedpyright_banner = cx.new(|cx| BasedPyrightBanner::new(workspace, cx));
|
let basedpyright_banner = cx.new(|cx| BasedPyrightBanner::new(workspace, cx));
|
||||||
|
|||||||
Reference in New Issue
Block a user