Git UI papercuts (#26316)
Release Notes: - Git Beta: added `git:Add` as an alias for the existing `git::Diff` - Git Beta: Fixed a bug where the 'generate commit message' keybinding wasn't working. - Git Beta: Made the empty project diff state a little more helpful with a button to push, and a button to close the item.
This commit is contained in:
parent
450d727a04
commit
cb543f9546
4 changed files with 460 additions and 373 deletions
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use crate::branch_picker::{self, BranchList};
|
use crate::branch_picker::{self, BranchList};
|
||||||
use crate::git_panel::{commit_message_editor, GitPanel};
|
use crate::git_panel::{commit_message_editor, GitPanel};
|
||||||
use git::Commit;
|
use git::{Commit, GenerateCommitMessage};
|
||||||
use panel::{panel_button, panel_editor_style, panel_filled_button};
|
use panel::{panel_button, panel_editor_style, panel_filled_button};
|
||||||
use ui::{prelude::*, KeybindingHint, PopoverMenu, Tooltip};
|
use ui::{prelude::*, KeybindingHint, PopoverMenu, Tooltip};
|
||||||
|
|
||||||
|
@ -372,6 +372,11 @@ impl Render for CommitModal {
|
||||||
.key_context("GitCommit")
|
.key_context("GitCommit")
|
||||||
.on_action(cx.listener(Self::dismiss))
|
.on_action(cx.listener(Self::dismiss))
|
||||||
.on_action(cx.listener(Self::commit))
|
.on_action(cx.listener(Self::commit))
|
||||||
|
.on_action(cx.listener(|this, _: &GenerateCommitMessage, _, cx| {
|
||||||
|
this.git_panel.update(cx, |panel, cx| {
|
||||||
|
panel.generate_commit_message(cx);
|
||||||
|
})
|
||||||
|
}))
|
||||||
.on_action(
|
.on_action(
|
||||||
cx.listener(|this, _: &zed_actions::git::Branch, window, cx| {
|
cx.listener(|this, _: &zed_actions::git::Branch, window, cx| {
|
||||||
toggle_branch_picker(this, window, cx);
|
toggle_branch_picker(this, window, cx);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::askpass_modal::AskPassModal;
|
use crate::askpass_modal::AskPassModal;
|
||||||
use crate::branch_picker;
|
|
||||||
use crate::commit_modal::CommitModal;
|
use crate::commit_modal::CommitModal;
|
||||||
use crate::git_panel_settings::StatusStyle;
|
use crate::git_panel_settings::StatusStyle;
|
||||||
use crate::remote_output_toast::{RemoteAction, RemoteOutputToast};
|
use crate::remote_output_toast::{RemoteAction, RemoteOutputToast};
|
||||||
use crate::repository_selector::filtered_repository_entries;
|
use crate::repository_selector::filtered_repository_entries;
|
||||||
|
use crate::{branch_picker, render_remote_button};
|
||||||
use crate::{
|
use crate::{
|
||||||
git_panel_settings::GitPanelSettings, git_status_icon, repository_selector::RepositorySelector,
|
git_panel_settings::GitPanelSettings, git_status_icon, repository_selector::RepositorySelector,
|
||||||
};
|
};
|
||||||
|
@ -26,12 +26,11 @@ use git::status::StageStatus;
|
||||||
use git::{repository::RepoPath, status::FileStatus, Commit, ToggleStaged};
|
use git::{repository::RepoPath, status::FileStatus, Commit, ToggleStaged};
|
||||||
use git::{ExpandCommitEditor, RestoreTrackedFiles, StageAll, TrashUntrackedFiles, UnstageAll};
|
use git::{ExpandCommitEditor, RestoreTrackedFiles, StageAll, TrashUntrackedFiles, UnstageAll};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, anchored, deferred, hsla, percentage, point, uniform_list, Action, Animation,
|
actions, anchored, deferred, percentage, uniform_list, Action, Animation, AnimationExt as _,
|
||||||
AnimationExt as _, AnyView, BoxShadow, ClickEvent, Corner, DismissEvent, Entity, EventEmitter,
|
ClickEvent, Corner, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, KeyContext,
|
||||||
FocusHandle, Focusable, KeyContext, ListHorizontalSizingBehavior, ListSizingBehavior,
|
ListHorizontalSizingBehavior, ListSizingBehavior, Modifiers, ModifiersChangedEvent,
|
||||||
Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, Point, PromptLevel,
|
MouseButton, MouseDownEvent, Point, PromptLevel, ScrollStrategy, Stateful, Subscription, Task,
|
||||||
ScrollStrategy, Stateful, Subscription, Task, Transformation, UniformListScrollHandle,
|
Transformation, UniformListScrollHandle, WeakEntity,
|
||||||
WeakEntity,
|
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::{Buffer, File};
|
use language::{Buffer, File};
|
||||||
|
@ -49,7 +48,6 @@ use project::{
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Settings as _;
|
use settings::Settings as _;
|
||||||
use smallvec::smallvec;
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
@ -58,8 +56,8 @@ use std::{collections::HashSet, sync::Arc, time::Duration, usize};
|
||||||
use strum::{IntoEnumIterator, VariantNames};
|
use strum::{IntoEnumIterator, VariantNames};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use ui::{
|
use ui::{
|
||||||
prelude::*, ButtonLike, Checkbox, ContextMenu, ElevationIndex, PopoverMenu, Scrollbar,
|
prelude::*, Checkbox, ContextMenu, ElevationIndex, PopoverMenu, Scrollbar, ScrollbarState,
|
||||||
ScrollbarState, Tooltip,
|
Tooltip,
|
||||||
};
|
};
|
||||||
use util::{maybe, post_inc, ResultExt, TryFutureExt};
|
use util::{maybe, post_inc, ResultExt, TryFutureExt};
|
||||||
use workspace::{AppState, OpenOptions, OpenVisible};
|
use workspace::{AppState, OpenOptions, OpenVisible};
|
||||||
|
@ -1748,7 +1746,7 @@ impl GitPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_push_and_pull(&self, cx: &App) -> bool {
|
fn can_push_and_pull(&self, cx: &App) -> bool {
|
||||||
!self.project.read(cx).is_via_collab()
|
crate::can_push_and_pull(&self.project, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_current_remote(
|
fn get_current_remote(
|
||||||
|
@ -3313,159 +3311,6 @@ impl Render for GitPanelMessageTooltip {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn git_action_tooltip(
|
|
||||||
label: impl Into<SharedString>,
|
|
||||||
action: &dyn Action,
|
|
||||||
command: impl Into<SharedString>,
|
|
||||||
focus_handle: Option<FocusHandle>,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> AnyView {
|
|
||||||
let label = label.into();
|
|
||||||
let command = command.into();
|
|
||||||
|
|
||||||
if let Some(handle) = focus_handle {
|
|
||||||
Tooltip::with_meta_in(
|
|
||||||
label.clone(),
|
|
||||||
Some(action),
|
|
||||||
command.clone(),
|
|
||||||
&handle,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Tooltip::with_meta(label.clone(), Some(action), command.clone(), window, cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
struct SplitButton {
|
|
||||||
pub left: ButtonLike,
|
|
||||||
pub right: AnyElement,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SplitButton {
|
|
||||||
fn new(
|
|
||||||
id: impl Into<SharedString>,
|
|
||||||
left_label: impl Into<SharedString>,
|
|
||||||
ahead_count: usize,
|
|
||||||
behind_count: usize,
|
|
||||||
left_icon: Option<IconName>,
|
|
||||||
left_on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
|
||||||
tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
|
|
||||||
) -> Self {
|
|
||||||
let id = id.into();
|
|
||||||
|
|
||||||
fn count(count: usize) -> impl IntoElement {
|
|
||||||
h_flex()
|
|
||||||
.ml_neg_px()
|
|
||||||
.h(rems(0.875))
|
|
||||||
.items_center()
|
|
||||||
.overflow_hidden()
|
|
||||||
.px_0p5()
|
|
||||||
.child(
|
|
||||||
Label::new(count.to_string())
|
|
||||||
.size(LabelSize::XSmall)
|
|
||||||
.line_height_style(LineHeightStyle::UiLabel),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let should_render_counts = left_icon.is_none() && (ahead_count > 0 || behind_count > 0);
|
|
||||||
|
|
||||||
let left = ui::ButtonLike::new_rounded_left(ElementId::Name(
|
|
||||||
format!("split-button-left-{}", id).into(),
|
|
||||||
))
|
|
||||||
.layer(ui::ElevationIndex::ModalSurface)
|
|
||||||
.size(ui::ButtonSize::Compact)
|
|
||||||
.when(should_render_counts, |this| {
|
|
||||||
this.child(
|
|
||||||
h_flex()
|
|
||||||
.ml_neg_0p5()
|
|
||||||
.mr_1()
|
|
||||||
.when(behind_count > 0, |this| {
|
|
||||||
this.child(Icon::new(IconName::ArrowDown).size(IconSize::XSmall))
|
|
||||||
.child(count(behind_count))
|
|
||||||
})
|
|
||||||
.when(ahead_count > 0, |this| {
|
|
||||||
this.child(Icon::new(IconName::ArrowUp).size(IconSize::XSmall))
|
|
||||||
.child(count(ahead_count))
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.when_some(left_icon, |this, left_icon| {
|
|
||||||
this.child(
|
|
||||||
h_flex()
|
|
||||||
.ml_neg_0p5()
|
|
||||||
.mr_1()
|
|
||||||
.child(Icon::new(left_icon).size(IconSize::XSmall)),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.child(Label::new(left_label).size(LabelSize::Small))
|
|
||||||
.mr_0p5(),
|
|
||||||
)
|
|
||||||
.on_click(left_on_click)
|
|
||||||
.tooltip(tooltip);
|
|
||||||
|
|
||||||
let right =
|
|
||||||
render_git_action_menu(ElementId::Name(format!("split-button-right-{}", id).into()))
|
|
||||||
.into_any_element();
|
|
||||||
// .on_click(right_on_click);
|
|
||||||
|
|
||||||
Self { left, right }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for SplitButton {
|
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
||||||
h_flex()
|
|
||||||
.rounded_sm()
|
|
||||||
.border_1()
|
|
||||||
.border_color(cx.theme().colors().text_muted.alpha(0.12))
|
|
||||||
.child(self.left)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.h_full()
|
|
||||||
.w_px()
|
|
||||||
.bg(cx.theme().colors().text_muted.alpha(0.16)),
|
|
||||||
)
|
|
||||||
.child(self.right)
|
|
||||||
.bg(ElevationIndex::Surface.on_elevation_bg(cx))
|
|
||||||
.shadow(smallvec![BoxShadow {
|
|
||||||
color: hsla(0.0, 0.0, 0.0, 0.16),
|
|
||||||
offset: point(px(0.), px(1.)),
|
|
||||||
blur_radius: px(0.),
|
|
||||||
spread_radius: px(0.),
|
|
||||||
}])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_git_action_menu(id: impl Into<ElementId>) -> impl IntoElement {
|
|
||||||
PopoverMenu::new(id.into())
|
|
||||||
.trigger(
|
|
||||||
ui::ButtonLike::new_rounded_right("split-button-right")
|
|
||||||
.layer(ui::ElevationIndex::ModalSurface)
|
|
||||||
.size(ui::ButtonSize::None)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.px_1()
|
|
||||||
.child(Icon::new(IconName::ChevronDownSmall).size(IconSize::XSmall)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.menu(move |window, cx| {
|
|
||||||
Some(ContextMenu::build(window, cx, |context_menu, _, _| {
|
|
||||||
context_menu
|
|
||||||
.action("Fetch", git::Fetch.boxed_clone())
|
|
||||||
.action("Pull", git::Pull.boxed_clone())
|
|
||||||
.separator()
|
|
||||||
.action("Push", git::Push.boxed_clone())
|
|
||||||
.action("Force Push", git::ForcePush.boxed_clone())
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
.anchor(Corner::TopRight)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(IntoElement, IntoComponent)]
|
#[derive(IntoElement, IntoComponent)]
|
||||||
#[component(scope = "Version Control")]
|
#[component(scope = "Version Control")]
|
||||||
pub struct PanelRepoFooter {
|
pub struct PanelRepoFooter {
|
||||||
|
@ -3516,200 +3361,6 @@ impl PanelRepoFooter {
|
||||||
.menu(move |window, cx| Some(git_panel_context_menu(window, cx)))
|
.menu(move |window, cx| Some(git_panel_context_menu(window, cx)))
|
||||||
.anchor(Corner::TopRight)
|
.anchor(Corner::TopRight)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn panel_focus_handle(&self, cx: &App) -> Option<FocusHandle> {
|
|
||||||
if let Some(git_panel) = self.git_panel.clone() {
|
|
||||||
Some(git_panel.focus_handle(cx))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_push_button(&self, id: SharedString, ahead: u32, cx: &mut App) -> SplitButton {
|
|
||||||
let panel = self.git_panel.clone();
|
|
||||||
let panel_focus_handle = self.panel_focus_handle(cx);
|
|
||||||
|
|
||||||
SplitButton::new(
|
|
||||||
id,
|
|
||||||
"Push",
|
|
||||||
ahead as usize,
|
|
||||||
0,
|
|
||||||
None,
|
|
||||||
move |_, window, cx| {
|
|
||||||
if let Some(panel) = panel.as_ref() {
|
|
||||||
panel.update(cx, |panel, cx| {
|
|
||||||
panel.push(false, window, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move |window, cx| {
|
|
||||||
git_action_tooltip(
|
|
||||||
"Push committed changes to remote",
|
|
||||||
&git::Push,
|
|
||||||
"git push",
|
|
||||||
panel_focus_handle.clone(),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_pull_button(
|
|
||||||
&self,
|
|
||||||
id: SharedString,
|
|
||||||
ahead: u32,
|
|
||||||
behind: u32,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> SplitButton {
|
|
||||||
let panel = self.git_panel.clone();
|
|
||||||
let panel_focus_handle = self.panel_focus_handle(cx);
|
|
||||||
|
|
||||||
SplitButton::new(
|
|
||||||
id,
|
|
||||||
"Pull",
|
|
||||||
ahead as usize,
|
|
||||||
behind as usize,
|
|
||||||
None,
|
|
||||||
move |_, window, cx| {
|
|
||||||
if let Some(panel) = panel.as_ref() {
|
|
||||||
panel.update(cx, |panel, cx| {
|
|
||||||
panel.pull(window, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move |window, cx| {
|
|
||||||
git_action_tooltip(
|
|
||||||
"Pull",
|
|
||||||
&git::Pull,
|
|
||||||
"git pull",
|
|
||||||
panel_focus_handle.clone(),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_fetch_button(&self, id: SharedString, cx: &mut App) -> SplitButton {
|
|
||||||
let panel = self.git_panel.clone();
|
|
||||||
let panel_focus_handle = self.panel_focus_handle(cx);
|
|
||||||
|
|
||||||
SplitButton::new(
|
|
||||||
id,
|
|
||||||
"Fetch",
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
Some(IconName::ArrowCircle),
|
|
||||||
move |_, window, cx| {
|
|
||||||
if let Some(panel) = panel.as_ref() {
|
|
||||||
panel.update(cx, |panel, cx| {
|
|
||||||
panel.fetch(window, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move |window, cx| {
|
|
||||||
git_action_tooltip(
|
|
||||||
"Fetch updates from remote",
|
|
||||||
&git::Fetch,
|
|
||||||
"git fetch",
|
|
||||||
panel_focus_handle.clone(),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_publish_button(&self, id: SharedString, cx: &mut App) -> SplitButton {
|
|
||||||
let panel = self.git_panel.clone();
|
|
||||||
let panel_focus_handle = self.panel_focus_handle(cx);
|
|
||||||
|
|
||||||
SplitButton::new(
|
|
||||||
id,
|
|
||||||
"Publish",
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
Some(IconName::ArrowUpFromLine),
|
|
||||||
move |_, window, cx| {
|
|
||||||
if let Some(panel) = panel.as_ref() {
|
|
||||||
panel.update(cx, |panel, cx| {
|
|
||||||
panel.push(false, window, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move |window, cx| {
|
|
||||||
git_action_tooltip(
|
|
||||||
"Publish branch to remote",
|
|
||||||
&git::Push,
|
|
||||||
"git push --set-upstream",
|
|
||||||
panel_focus_handle.clone(),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_republish_button(&self, id: SharedString, cx: &mut App) -> SplitButton {
|
|
||||||
let panel = self.git_panel.clone();
|
|
||||||
let panel_focus_handle = self.panel_focus_handle(cx);
|
|
||||||
|
|
||||||
SplitButton::new(
|
|
||||||
id,
|
|
||||||
"Republish",
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
Some(IconName::ArrowUpFromLine),
|
|
||||||
move |_, window, cx| {
|
|
||||||
if let Some(panel) = panel.as_ref() {
|
|
||||||
panel.update(cx, |panel, cx| {
|
|
||||||
panel.push(false, window, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move |window, cx| {
|
|
||||||
git_action_tooltip(
|
|
||||||
"Re-publish branch to remote",
|
|
||||||
&git::Push,
|
|
||||||
"git push --set-upstream",
|
|
||||||
panel_focus_handle.clone(),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_relevant_button(
|
|
||||||
&self,
|
|
||||||
id: impl Into<SharedString>,
|
|
||||||
branch: &Branch,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> Option<impl IntoElement> {
|
|
||||||
if let Some(git_panel) = self.git_panel.as_ref() {
|
|
||||||
if !git_panel.read(cx).can_push_and_pull(cx) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let id = id.into();
|
|
||||||
let upstream = branch.upstream.as_ref();
|
|
||||||
Some(match upstream {
|
|
||||||
Some(Upstream {
|
|
||||||
tracking: UpstreamTracking::Tracked(UpstreamTrackingStatus { ahead, behind }),
|
|
||||||
..
|
|
||||||
}) => match (*ahead, *behind) {
|
|
||||||
(0, 0) => self.render_fetch_button(id, cx),
|
|
||||||
(ahead, 0) => self.render_push_button(id, ahead, cx),
|
|
||||||
(ahead, behind) => self.render_pull_button(id, ahead, behind, cx),
|
|
||||||
},
|
|
||||||
Some(Upstream {
|
|
||||||
tracking: UpstreamTracking::Gone,
|
|
||||||
..
|
|
||||||
}) => self.render_republish_button(id, cx),
|
|
||||||
None => self.render_publish_button(id, cx),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for PanelRepoFooter {
|
impl RenderOnce for PanelRepoFooter {
|
||||||
|
@ -3825,8 +3476,20 @@ impl RenderOnce for PanelRepoFooter {
|
||||||
.children(spinner)
|
.children(spinner)
|
||||||
.child(self.render_overflow_menu(overflow_menu_id))
|
.child(self.render_overflow_menu(overflow_menu_id))
|
||||||
.when_some(branch, |this, branch| {
|
.when_some(branch, |this, branch| {
|
||||||
let button = self.render_relevant_button(self.id.clone(), &branch, cx);
|
let mut focus_handle = None;
|
||||||
this.children(button)
|
if let Some(git_panel) = self.git_panel.as_ref() {
|
||||||
|
if !git_panel.read(cx).can_push_and_pull(cx) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
focus_handle = Some(git_panel.focus_handle(cx));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.children(render_remote_button(
|
||||||
|
self.id.clone(),
|
||||||
|
&branch,
|
||||||
|
focus_handle,
|
||||||
|
true,
|
||||||
|
))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
use ::settings::Settings;
|
use ::settings::Settings;
|
||||||
use git::status::FileStatus;
|
use git::{
|
||||||
|
repository::{Branch, Upstream, UpstreamTracking, UpstreamTrackingStatus},
|
||||||
|
status::FileStatus,
|
||||||
|
};
|
||||||
use git_panel_settings::GitPanelSettings;
|
use git_panel_settings::GitPanelSettings;
|
||||||
use gpui::App;
|
use gpui::{App, Entity, FocusHandle};
|
||||||
|
use project::Project;
|
||||||
use project_diff::ProjectDiff;
|
use project_diff::ProjectDiff;
|
||||||
use ui::{ActiveTheme, Color, Icon, IconName, IntoElement};
|
use ui::{ActiveTheme, Color, Icon, IconName, IntoElement, SharedString};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
mod askpass_modal;
|
mod askpass_modal;
|
||||||
|
@ -89,3 +93,343 @@ pub fn git_status_icon(status: FileStatus, cx: &App) -> impl IntoElement {
|
||||||
};
|
};
|
||||||
Icon::new(icon_name).color(Color::Custom(color))
|
Icon::new(icon_name).color(Color::Custom(color))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_push_and_pull(project: &Entity<Project>, cx: &App) -> bool {
|
||||||
|
!project.read(cx).is_via_collab()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_remote_button(
|
||||||
|
id: impl Into<SharedString>,
|
||||||
|
branch: &Branch,
|
||||||
|
keybinding_target: Option<FocusHandle>,
|
||||||
|
show_fetch_button: bool,
|
||||||
|
) -> Option<impl IntoElement> {
|
||||||
|
let id = id.into();
|
||||||
|
let upstream = branch.upstream.as_ref();
|
||||||
|
match upstream {
|
||||||
|
Some(Upstream {
|
||||||
|
tracking: UpstreamTracking::Tracked(UpstreamTrackingStatus { ahead, behind }),
|
||||||
|
..
|
||||||
|
}) => match (*ahead, *behind) {
|
||||||
|
(0, 0) if show_fetch_button => {
|
||||||
|
Some(remote_button::render_fetch_button(keybinding_target, id))
|
||||||
|
}
|
||||||
|
(0, 0) => None,
|
||||||
|
(ahead, 0) => Some(remote_button::render_push_button(
|
||||||
|
keybinding_target.clone(),
|
||||||
|
id,
|
||||||
|
ahead,
|
||||||
|
)),
|
||||||
|
(ahead, behind) => Some(remote_button::render_pull_button(
|
||||||
|
keybinding_target.clone(),
|
||||||
|
id,
|
||||||
|
ahead,
|
||||||
|
behind,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Some(Upstream {
|
||||||
|
tracking: UpstreamTracking::Gone,
|
||||||
|
..
|
||||||
|
}) => Some(remote_button::render_republish_button(
|
||||||
|
keybinding_target,
|
||||||
|
id,
|
||||||
|
)),
|
||||||
|
None => Some(remote_button::render_publish_button(keybinding_target, id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod remote_button {
|
||||||
|
use gpui::{hsla, point, Action, AnyView, BoxShadow, ClickEvent, Corner, FocusHandle};
|
||||||
|
use ui::{
|
||||||
|
div, h_flex, px, rems, ActiveTheme, AnyElement, App, ButtonCommon, ButtonLike, Clickable,
|
||||||
|
ContextMenu, ElementId, ElevationIndex, FluentBuilder, Icon, IconName, IconSize,
|
||||||
|
IntoElement, Label, LabelCommon, LabelSize, LineHeightStyle, ParentElement, PopoverMenu,
|
||||||
|
RenderOnce, SharedString, Styled, Tooltip, Window,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn render_fetch_button(
|
||||||
|
keybinding_target: Option<FocusHandle>,
|
||||||
|
id: SharedString,
|
||||||
|
) -> SplitButton {
|
||||||
|
SplitButton::new(
|
||||||
|
id,
|
||||||
|
"Fetch",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
Some(IconName::ArrowCircle),
|
||||||
|
move |_, window, cx| {
|
||||||
|
window.dispatch_action(Box::new(git::Fetch), cx);
|
||||||
|
},
|
||||||
|
move |window, cx| {
|
||||||
|
git_action_tooltip(
|
||||||
|
"Fetch updates from remote",
|
||||||
|
&git::Fetch,
|
||||||
|
"git fetch",
|
||||||
|
keybinding_target.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_push_button(
|
||||||
|
keybinding_target: Option<FocusHandle>,
|
||||||
|
id: SharedString,
|
||||||
|
ahead: u32,
|
||||||
|
) -> SplitButton {
|
||||||
|
SplitButton::new(
|
||||||
|
id,
|
||||||
|
"Push",
|
||||||
|
ahead as usize,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
move |_, window, cx| {
|
||||||
|
window.dispatch_action(Box::new(git::Push), cx);
|
||||||
|
},
|
||||||
|
move |window, cx| {
|
||||||
|
git_action_tooltip(
|
||||||
|
"Push committed changes to remote",
|
||||||
|
&git::Push,
|
||||||
|
"git push",
|
||||||
|
keybinding_target.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_pull_button(
|
||||||
|
keybinding_target: Option<FocusHandle>,
|
||||||
|
id: SharedString,
|
||||||
|
ahead: u32,
|
||||||
|
behind: u32,
|
||||||
|
) -> SplitButton {
|
||||||
|
SplitButton::new(
|
||||||
|
id,
|
||||||
|
"Pull",
|
||||||
|
ahead as usize,
|
||||||
|
behind as usize,
|
||||||
|
None,
|
||||||
|
move |_, window, cx| {
|
||||||
|
window.dispatch_action(Box::new(git::Pull), cx);
|
||||||
|
},
|
||||||
|
move |window, cx| {
|
||||||
|
git_action_tooltip(
|
||||||
|
"Pull",
|
||||||
|
&git::Pull,
|
||||||
|
"git pull",
|
||||||
|
keybinding_target.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_publish_button(
|
||||||
|
keybinding_target: Option<FocusHandle>,
|
||||||
|
id: SharedString,
|
||||||
|
) -> SplitButton {
|
||||||
|
SplitButton::new(
|
||||||
|
id,
|
||||||
|
"Publish",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
Some(IconName::ArrowUpFromLine),
|
||||||
|
move |_, window, cx| {
|
||||||
|
window.dispatch_action(Box::new(git::Push), cx);
|
||||||
|
},
|
||||||
|
move |window, cx| {
|
||||||
|
git_action_tooltip(
|
||||||
|
"Publish branch to remote",
|
||||||
|
&git::Push,
|
||||||
|
"git push --set-upstream",
|
||||||
|
keybinding_target.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_republish_button(
|
||||||
|
keybinding_target: Option<FocusHandle>,
|
||||||
|
id: SharedString,
|
||||||
|
) -> SplitButton {
|
||||||
|
SplitButton::new(
|
||||||
|
id,
|
||||||
|
"Republish",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
Some(IconName::ArrowUpFromLine),
|
||||||
|
move |_, window, cx| {
|
||||||
|
window.dispatch_action(Box::new(git::Push), cx);
|
||||||
|
},
|
||||||
|
move |window, cx| {
|
||||||
|
git_action_tooltip(
|
||||||
|
"Re-publish branch to remote",
|
||||||
|
&git::Push,
|
||||||
|
"git push --set-upstream",
|
||||||
|
keybinding_target.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn git_action_tooltip(
|
||||||
|
label: impl Into<SharedString>,
|
||||||
|
action: &dyn Action,
|
||||||
|
command: impl Into<SharedString>,
|
||||||
|
focus_handle: Option<FocusHandle>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> AnyView {
|
||||||
|
let label = label.into();
|
||||||
|
let command = command.into();
|
||||||
|
|
||||||
|
if let Some(handle) = focus_handle {
|
||||||
|
Tooltip::with_meta_in(
|
||||||
|
label.clone(),
|
||||||
|
Some(action),
|
||||||
|
command.clone(),
|
||||||
|
&handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Tooltip::with_meta(label.clone(), Some(action), command.clone(), window, cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_git_action_menu(id: impl Into<ElementId>) -> impl IntoElement {
|
||||||
|
PopoverMenu::new(id.into())
|
||||||
|
.trigger(
|
||||||
|
ui::ButtonLike::new_rounded_right("split-button-right")
|
||||||
|
.layer(ui::ElevationIndex::ModalSurface)
|
||||||
|
.size(ui::ButtonSize::None)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.px_1()
|
||||||
|
.child(Icon::new(IconName::ChevronDownSmall).size(IconSize::XSmall)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.menu(move |window, cx| {
|
||||||
|
Some(ContextMenu::build(window, cx, |context_menu, _, _| {
|
||||||
|
context_menu
|
||||||
|
.action("Fetch", git::Fetch.boxed_clone())
|
||||||
|
.action("Pull", git::Pull.boxed_clone())
|
||||||
|
.separator()
|
||||||
|
.action("Push", git::Push.boxed_clone())
|
||||||
|
.action("Force Push", git::ForcePush.boxed_clone())
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.anchor(Corner::TopRight)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct SplitButton {
|
||||||
|
pub left: ButtonLike,
|
||||||
|
pub right: AnyElement,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SplitButton {
|
||||||
|
fn new(
|
||||||
|
id: impl Into<SharedString>,
|
||||||
|
left_label: impl Into<SharedString>,
|
||||||
|
ahead_count: usize,
|
||||||
|
behind_count: usize,
|
||||||
|
left_icon: Option<IconName>,
|
||||||
|
left_on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||||
|
tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||||
|
) -> Self {
|
||||||
|
let id = id.into();
|
||||||
|
|
||||||
|
fn count(count: usize) -> impl IntoElement {
|
||||||
|
h_flex()
|
||||||
|
.ml_neg_px()
|
||||||
|
.h(rems(0.875))
|
||||||
|
.items_center()
|
||||||
|
.overflow_hidden()
|
||||||
|
.px_0p5()
|
||||||
|
.child(
|
||||||
|
Label::new(count.to_string())
|
||||||
|
.size(LabelSize::XSmall)
|
||||||
|
.line_height_style(LineHeightStyle::UiLabel),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let should_render_counts = left_icon.is_none() && (ahead_count > 0 || behind_count > 0);
|
||||||
|
|
||||||
|
let left = ui::ButtonLike::new_rounded_left(ElementId::Name(
|
||||||
|
format!("split-button-left-{}", id).into(),
|
||||||
|
))
|
||||||
|
.layer(ui::ElevationIndex::ModalSurface)
|
||||||
|
.size(ui::ButtonSize::Compact)
|
||||||
|
.when(should_render_counts, |this| {
|
||||||
|
this.child(
|
||||||
|
h_flex()
|
||||||
|
.ml_neg_0p5()
|
||||||
|
.mr_1()
|
||||||
|
.when(behind_count > 0, |this| {
|
||||||
|
this.child(Icon::new(IconName::ArrowDown).size(IconSize::XSmall))
|
||||||
|
.child(count(behind_count))
|
||||||
|
})
|
||||||
|
.when(ahead_count > 0, |this| {
|
||||||
|
this.child(Icon::new(IconName::ArrowUp).size(IconSize::XSmall))
|
||||||
|
.child(count(ahead_count))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when_some(left_icon, |this, left_icon| {
|
||||||
|
this.child(
|
||||||
|
h_flex()
|
||||||
|
.ml_neg_0p5()
|
||||||
|
.mr_1()
|
||||||
|
.child(Icon::new(left_icon).size(IconSize::XSmall)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.child(Label::new(left_label).size(LabelSize::Small))
|
||||||
|
.mr_0p5(),
|
||||||
|
)
|
||||||
|
.on_click(left_on_click)
|
||||||
|
.tooltip(tooltip);
|
||||||
|
|
||||||
|
let right = render_git_action_menu(ElementId::Name(
|
||||||
|
format!("split-button-right-{}", id).into(),
|
||||||
|
))
|
||||||
|
.into_any_element();
|
||||||
|
|
||||||
|
Self { left, right }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for SplitButton {
|
||||||
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
h_flex()
|
||||||
|
.rounded_sm()
|
||||||
|
.border_1()
|
||||||
|
.border_color(cx.theme().colors().text_muted.alpha(0.12))
|
||||||
|
.child(div().flex_grow().child(self.left))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.h_full()
|
||||||
|
.w_px()
|
||||||
|
.bg(cx.theme().colors().text_muted.alpha(0.16)),
|
||||||
|
)
|
||||||
|
.child(self.right)
|
||||||
|
.bg(ElevationIndex::Surface.on_elevation_bg(cx))
|
||||||
|
.shadow(smallvec::smallvec![BoxShadow {
|
||||||
|
color: hsla(0.0, 0.0, 0.0, 0.16),
|
||||||
|
offset: point(px(0.), px(1.)),
|
||||||
|
blur_radius: px(0.),
|
||||||
|
spread_radius: px(0.),
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ use editor::{
|
||||||
use feature_flags::FeatureFlagViewExt;
|
use feature_flags::FeatureFlagViewExt;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use git::{
|
use git::{
|
||||||
status::FileStatus, Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll, UnstageAndNext,
|
repository::Branch, status::FileStatus, Commit, StageAll, StageAndNext, ToggleStaged,
|
||||||
|
UnstageAll, UnstageAndNext,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, Action, AnyElement, AnyView, App, AppContext as _, AsyncWindowContext, Entity,
|
actions, Action, AnyElement, AnyView, App, AppContext as _, AsyncWindowContext, Entity,
|
||||||
|
@ -24,27 +25,27 @@ use project::{
|
||||||
};
|
};
|
||||||
use std::any::{Any, TypeId};
|
use std::any::{Any, TypeId};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::{prelude::*, vertical_divider, Tooltip};
|
use ui::{prelude::*, vertical_divider, KeyBinding, Tooltip};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{BreadcrumbText, Item, ItemEvent, ItemHandle, TabContentParams},
|
item::{BreadcrumbText, Item, ItemEvent, ItemHandle, TabContentParams},
|
||||||
searchable::SearchableItemHandle,
|
searchable::SearchableItemHandle,
|
||||||
ItemNavHistory, SerializableItem, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
|
CloseActiveItem, ItemNavHistory, SerializableItem, ToolbarItemEvent, ToolbarItemLocation,
|
||||||
Workspace,
|
ToolbarItemView, Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(git, [Diff]);
|
actions!(git, [Diff, Add]);
|
||||||
|
|
||||||
pub struct ProjectDiff {
|
pub struct ProjectDiff {
|
||||||
|
project: Entity<Project>,
|
||||||
multibuffer: Entity<MultiBuffer>,
|
multibuffer: Entity<MultiBuffer>,
|
||||||
editor: Entity<Editor>,
|
editor: Entity<Editor>,
|
||||||
project: Entity<Project>,
|
|
||||||
git_store: Entity<GitStore>,
|
git_store: Entity<GitStore>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
update_needed: postage::watch::Sender<()>,
|
update_needed: postage::watch::Sender<()>,
|
||||||
pending_scroll: Option<PathKey>,
|
pending_scroll: Option<PathKey>,
|
||||||
|
current_branch: Option<Branch>,
|
||||||
_task: Task<Result<()>>,
|
_task: Task<Result<()>>,
|
||||||
_subscription: Subscription,
|
_subscription: Subscription,
|
||||||
}
|
}
|
||||||
|
@ -70,6 +71,9 @@ impl ProjectDiff {
|
||||||
let Some(window) = window else { return };
|
let Some(window) = window else { return };
|
||||||
cx.when_flag_enabled::<feature_flags::GitUiFeatureFlag>(window, |workspace, _, _cx| {
|
cx.when_flag_enabled::<feature_flags::GitUiFeatureFlag>(window, |workspace, _, _cx| {
|
||||||
workspace.register_action(Self::deploy);
|
workspace.register_action(Self::deploy);
|
||||||
|
workspace.register_action(|workspace, _: &Add, window, cx| {
|
||||||
|
Self::deploy(workspace, &Diff, window, cx);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
workspace::register_serializable_item::<ProjectDiff>(cx);
|
workspace::register_serializable_item::<ProjectDiff>(cx);
|
||||||
|
@ -179,6 +183,7 @@ impl ProjectDiff {
|
||||||
multibuffer,
|
multibuffer,
|
||||||
pending_scroll: None,
|
pending_scroll: None,
|
||||||
update_needed: send,
|
update_needed: send,
|
||||||
|
current_branch: None,
|
||||||
_task: worker,
|
_task: worker,
|
||||||
_subscription: git_store_subscription,
|
_subscription: git_store_subscription,
|
||||||
}
|
}
|
||||||
|
@ -444,6 +449,20 @@ impl ProjectDiff {
|
||||||
mut cx: AsyncWindowContext,
|
mut cx: AsyncWindowContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
while let Some(_) = recv.next().await {
|
while let Some(_) = recv.next().await {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
let new_branch =
|
||||||
|
this.git_store
|
||||||
|
.read(cx)
|
||||||
|
.active_repository()
|
||||||
|
.and_then(|active_repository| {
|
||||||
|
active_repository.read(cx).current_branch().cloned()
|
||||||
|
});
|
||||||
|
if new_branch != this.current_branch {
|
||||||
|
this.current_branch = new_branch;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
let buffers_to_load = this.update(&mut cx, |this, cx| this.load_buffers(cx))?;
|
let buffers_to_load = this.update(&mut cx, |this, cx| this.load_buffers(cx))?;
|
||||||
for buffer_to_load in buffers_to_load {
|
for buffer_to_load in buffers_to_load {
|
||||||
if let Some(buffer) = buffer_to_load.await.log_err() {
|
if let Some(buffer) = buffer_to_load.await.log_err() {
|
||||||
|
@ -642,9 +661,11 @@ impl Item for ProjectDiff {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for ProjectDiff {
|
impl Render for ProjectDiff {
|
||||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let is_empty = self.multibuffer.read(cx).is_empty();
|
let is_empty = self.multibuffer.read(cx).is_empty();
|
||||||
|
|
||||||
|
let can_push_and_pull = crate::can_push_and_pull(&self.project, cx);
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.key_context(if is_empty { "EmptyPane" } else { "GitDiff" })
|
.key_context(if is_empty { "EmptyPane" } else { "GitDiff" })
|
||||||
|
@ -654,7 +675,61 @@ impl Render for ProjectDiff {
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.size_full()
|
.size_full()
|
||||||
.when(is_empty, |el| {
|
.when(is_empty, |el| {
|
||||||
el.child(Label::new("No uncommitted changes"))
|
el.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.justify_around()
|
||||||
|
.child(Label::new("No uncommitted changes")),
|
||||||
|
)
|
||||||
|
.when(can_push_and_pull, |this_div| {
|
||||||
|
let keybinding_focus_handle = self.focus_handle(cx);
|
||||||
|
|
||||||
|
this_div.when_some(self.current_branch.as_ref(), |this_div, branch| {
|
||||||
|
let remote_button = crate::render_remote_button(
|
||||||
|
"project-diff-remote-button",
|
||||||
|
branch,
|
||||||
|
Some(keybinding_focus_handle.clone()),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
match remote_button {
|
||||||
|
Some(button) => {
|
||||||
|
this_div.child(h_flex().justify_around().child(button))
|
||||||
|
}
|
||||||
|
None => this_div.child(
|
||||||
|
h_flex()
|
||||||
|
.justify_around()
|
||||||
|
.child(Label::new("Remote up to date")),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map(|this| {
|
||||||
|
let keybinding_focus_handle = self.focus_handle(cx).clone();
|
||||||
|
|
||||||
|
this.child(
|
||||||
|
h_flex().justify_around().mt_1().child(
|
||||||
|
Button::new("project-diff-close-button", "Close")
|
||||||
|
// .style(ButtonStyle::Transparent)
|
||||||
|
.key_binding(KeyBinding::for_action_in(
|
||||||
|
&CloseActiveItem::default(),
|
||||||
|
&keybinding_focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
.on_click(move |_, window, cx| {
|
||||||
|
window.focus(&keybinding_focus_handle);
|
||||||
|
window.dispatch_action(
|
||||||
|
Box::new(CloseActiveItem::default()),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.when(!is_empty, |el| el.child(self.editor.clone()))
|
.when(!is_empty, |el| el.child(self.editor.clone()))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue