Add an undo button to the git panel (#24593)
Also prep infrastructure for pushing a commit Release Notes: - N/A --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com> Co-authored-by: Conrad <conrad@zed.dev> Co-authored-by: Nate Butler <iamnbutler@gmail.com>
This commit is contained in:
parent
df8adc8b11
commit
b014afa938
41 changed files with 1437 additions and 738 deletions
|
@ -1,28 +1,48 @@
|
|||
use futures::Future;
|
||||
use git::blame::BlameEntry;
|
||||
use git::Oid;
|
||||
use git::PullRequest;
|
||||
use gpui::{
|
||||
App, Asset, ClipboardItem, Element, ParentElement, Render, ScrollHandle,
|
||||
StatefulInteractiveElement, WeakEntity,
|
||||
};
|
||||
use language::ParsedMarkdown;
|
||||
use settings::Settings;
|
||||
use std::hash::Hash;
|
||||
use theme::ThemeSettings;
|
||||
use time::UtcOffset;
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
use time_format::format_local_timestamp;
|
||||
use ui::{prelude::*, tooltip_container, Avatar, Divider, IconButtonShape};
|
||||
use url::Url;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::git::blame::{CommitDetails, GitRemote};
|
||||
use crate::git::blame::GitRemote;
|
||||
use crate::EditorStyle;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CommitDetails {
|
||||
pub sha: SharedString,
|
||||
pub committer_name: SharedString,
|
||||
pub committer_email: SharedString,
|
||||
pub commit_time: OffsetDateTime,
|
||||
pub message: Option<ParsedCommitMessage>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ParsedCommitMessage {
|
||||
pub message: SharedString,
|
||||
pub parsed_message: ParsedMarkdown,
|
||||
pub permalink: Option<Url>,
|
||||
pub pull_request: Option<PullRequest>,
|
||||
pub remote: Option<GitRemote>,
|
||||
}
|
||||
|
||||
struct CommitAvatar<'a> {
|
||||
details: Option<&'a CommitDetails>,
|
||||
sha: Oid,
|
||||
commit: &'a CommitDetails,
|
||||
}
|
||||
|
||||
impl<'a> CommitAvatar<'a> {
|
||||
fn new(details: Option<&'a CommitDetails>, sha: Oid) -> Self {
|
||||
Self { details, sha }
|
||||
fn new(details: &'a CommitDetails) -> Self {
|
||||
Self { commit: details }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,14 +50,16 @@ impl<'a> CommitAvatar<'a> {
|
|||
fn render(
|
||||
&'a self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<BlameEntryTooltip>,
|
||||
cx: &mut Context<CommitTooltip>,
|
||||
) -> Option<impl IntoElement> {
|
||||
let remote = self
|
||||
.details
|
||||
.commit
|
||||
.message
|
||||
.as_ref()
|
||||
.and_then(|details| details.remote.as_ref())
|
||||
.filter(|remote| remote.host_supports_avatars())?;
|
||||
|
||||
let avatar_url = CommitAvatarAsset::new(remote.clone(), self.sha);
|
||||
let avatar_url = CommitAvatarAsset::new(remote.clone(), self.commit.sha.clone());
|
||||
|
||||
let element = match window.use_asset::<CommitAvatarAsset>(&avatar_url, cx) {
|
||||
// Loading or no avatar found
|
||||
|
@ -54,7 +76,7 @@ impl<'a> CommitAvatar<'a> {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
struct CommitAvatarAsset {
|
||||
sha: Oid,
|
||||
sha: SharedString,
|
||||
remote: GitRemote,
|
||||
}
|
||||
|
||||
|
@ -66,7 +88,7 @@ impl Hash for CommitAvatarAsset {
|
|||
}
|
||||
|
||||
impl CommitAvatarAsset {
|
||||
fn new(remote: GitRemote, sha: Oid) -> Self {
|
||||
fn new(remote: GitRemote, sha: SharedString) -> Self {
|
||||
Self { remote, sha }
|
||||
}
|
||||
}
|
||||
|
@ -91,50 +113,78 @@ impl Asset for CommitAvatarAsset {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) struct BlameEntryTooltip {
|
||||
blame_entry: BlameEntry,
|
||||
details: Option<CommitDetails>,
|
||||
pub struct CommitTooltip {
|
||||
commit: CommitDetails,
|
||||
editor_style: EditorStyle,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
scroll_handle: ScrollHandle,
|
||||
}
|
||||
|
||||
impl BlameEntryTooltip {
|
||||
pub(crate) fn new(
|
||||
blame_entry: BlameEntry,
|
||||
details: Option<CommitDetails>,
|
||||
style: &EditorStyle,
|
||||
impl CommitTooltip {
|
||||
pub fn blame_entry(
|
||||
blame: BlameEntry,
|
||||
details: Option<ParsedCommitMessage>,
|
||||
style: EditorStyle,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
) -> Self {
|
||||
let commit_time = blame
|
||||
.committer_time
|
||||
.and_then(|t| OffsetDateTime::from_unix_timestamp(t).ok())
|
||||
.unwrap_or(OffsetDateTime::now_utc());
|
||||
Self::new(
|
||||
CommitDetails {
|
||||
sha: blame.sha.to_string().into(),
|
||||
commit_time,
|
||||
committer_name: blame
|
||||
.committer_name
|
||||
.unwrap_or("<no name>".to_string())
|
||||
.into(),
|
||||
committer_email: blame.committer_email.unwrap_or("".to_string()).into(),
|
||||
message: details,
|
||||
},
|
||||
style,
|
||||
workspace,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
commit: CommitDetails,
|
||||
editor_style: EditorStyle,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
editor_style: style.clone(),
|
||||
blame_entry,
|
||||
details,
|
||||
editor_style,
|
||||
commit,
|
||||
workspace,
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for BlameEntryTooltip {
|
||||
impl Render for CommitTooltip {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let avatar =
|
||||
CommitAvatar::new(self.details.as_ref(), self.blame_entry.sha).render(window, cx);
|
||||
let avatar = CommitAvatar::new(&self.commit).render(window, cx);
|
||||
|
||||
let author = self
|
||||
.blame_entry
|
||||
.author
|
||||
.clone()
|
||||
.unwrap_or("<no name>".to_string());
|
||||
let author = self.commit.committer_name.clone();
|
||||
|
||||
let author_email = self.blame_entry.author_mail.clone();
|
||||
let author_email = self.commit.committer_email.clone();
|
||||
|
||||
let short_commit_id = self.blame_entry.sha.display_short();
|
||||
let full_sha = self.blame_entry.sha.to_string().clone();
|
||||
let absolute_timestamp = blame_entry_absolute_timestamp(&self.blame_entry);
|
||||
let short_commit_id = self
|
||||
.commit
|
||||
.sha
|
||||
.get(0..8)
|
||||
.map(|sha| sha.to_string().into())
|
||||
.unwrap_or_else(|| self.commit.sha.clone());
|
||||
let full_sha = self.commit.sha.to_string().clone();
|
||||
let absolute_timestamp = format_local_timestamp(
|
||||
self.commit.commit_time,
|
||||
OffsetDateTime::now_utc(),
|
||||
time_format::TimestampFormat::MediumAbsolute,
|
||||
);
|
||||
|
||||
let message = self
|
||||
.details
|
||||
.commit
|
||||
.message
|
||||
.as_ref()
|
||||
.map(|details| {
|
||||
crate::render_parsed_markdown(
|
||||
|
@ -149,7 +199,8 @@ impl Render for BlameEntryTooltip {
|
|||
.unwrap_or("<no commit message>".into_any());
|
||||
|
||||
let pull_request = self
|
||||
.details
|
||||
.commit
|
||||
.message
|
||||
.as_ref()
|
||||
.and_then(|details| details.pull_request.clone());
|
||||
|
||||
|
@ -171,7 +222,7 @@ impl Render for BlameEntryTooltip {
|
|||
.flex_wrap()
|
||||
.children(avatar)
|
||||
.child(author)
|
||||
.when_some(author_email, |this, author_email| {
|
||||
.when(!author_email.is_empty(), |this| {
|
||||
this.child(
|
||||
div()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
|
@ -231,12 +282,16 @@ impl Render for BlameEntryTooltip {
|
|||
.icon_color(Color::Muted)
|
||||
.icon_position(IconPosition::Start)
|
||||
.disabled(
|
||||
self.details.as_ref().map_or(true, |details| {
|
||||
details.permalink.is_none()
|
||||
}),
|
||||
self.commit
|
||||
.message
|
||||
.as_ref()
|
||||
.map_or(true, |details| {
|
||||
details.permalink.is_none()
|
||||
}),
|
||||
)
|
||||
.when_some(
|
||||
self.details
|
||||
self.commit
|
||||
.message
|
||||
.as_ref()
|
||||
.and_then(|details| details.permalink.clone()),
|
||||
|this, url| {
|
||||
|
@ -284,7 +339,3 @@ fn blame_entry_timestamp(blame_entry: &BlameEntry, format: time_format::Timestam
|
|||
pub fn blame_entry_relative_timestamp(blame_entry: &BlameEntry) -> String {
|
||||
blame_entry_timestamp(blame_entry, time_format::TimestampFormat::Relative)
|
||||
}
|
||||
|
||||
fn blame_entry_absolute_timestamp(blame_entry: &BlameEntry) -> String {
|
||||
blame_entry_timestamp(blame_entry, time_format::TimestampFormat::MediumAbsolute)
|
||||
}
|
|
@ -13,10 +13,10 @@
|
|||
//!
|
||||
//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
|
||||
pub mod actions;
|
||||
mod blame_entry_tooltip;
|
||||
mod blink_manager;
|
||||
mod clangd_ext;
|
||||
mod code_context_menus;
|
||||
pub mod commit_tooltip;
|
||||
pub mod display_map;
|
||||
mod editor_settings;
|
||||
mod editor_settings_controls;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
blame_entry_tooltip::{blame_entry_relative_timestamp, BlameEntryTooltip},
|
||||
code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP},
|
||||
commit_tooltip::{blame_entry_relative_timestamp, CommitTooltip, ParsedCommitMessage},
|
||||
display_map::{
|
||||
Block, BlockContext, BlockStyle, DisplaySnapshot, HighlightedChunk, ToDisplayPoint,
|
||||
},
|
||||
|
@ -8,7 +8,7 @@ use crate::{
|
|||
CurrentLineHighlight, DoubleClickInMultibuffer, MultiCursorModifier, ScrollBeyondLastLine,
|
||||
ScrollbarDiagnostics, ShowScrollbar,
|
||||
},
|
||||
git::blame::{CommitDetails, GitBlame},
|
||||
git::blame::GitBlame,
|
||||
hover_popover::{
|
||||
self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
|
||||
},
|
||||
|
@ -5939,7 +5939,8 @@ fn render_inline_blame_entry(
|
|||
|
||||
let details = blame.read(cx).details_for_entry(&blame_entry);
|
||||
|
||||
let tooltip = cx.new(|_| BlameEntryTooltip::new(blame_entry, details, style, workspace));
|
||||
let tooltip =
|
||||
cx.new(|_| CommitTooltip::blame_entry(blame_entry, details, style.clone(), workspace));
|
||||
|
||||
h_flex()
|
||||
.id("inline-blame")
|
||||
|
@ -5989,8 +5990,14 @@ fn render_blame_entry(
|
|||
|
||||
let workspace = editor.read(cx).workspace.as_ref().map(|(w, _)| w.clone());
|
||||
|
||||
let tooltip =
|
||||
cx.new(|_| BlameEntryTooltip::new(blame_entry.clone(), details.clone(), style, workspace));
|
||||
let tooltip = cx.new(|_| {
|
||||
CommitTooltip::blame_entry(
|
||||
blame_entry.clone(),
|
||||
details.clone(),
|
||||
style.clone(),
|
||||
workspace,
|
||||
)
|
||||
});
|
||||
|
||||
h_flex()
|
||||
.w_full()
|
||||
|
@ -6040,7 +6047,7 @@ fn render_blame_entry(
|
|||
|
||||
fn deploy_blame_entry_context_menu(
|
||||
blame_entry: &BlameEntry,
|
||||
details: Option<&CommitDetails>,
|
||||
details: Option<&ParsedCommitMessage>,
|
||||
editor: Entity<Editor>,
|
||||
position: gpui::Point<Pixels>,
|
||||
window: &mut Window,
|
||||
|
|
|
@ -2,7 +2,7 @@ use anyhow::Result;
|
|||
use collections::HashMap;
|
||||
use git::{
|
||||
blame::{Blame, BlameEntry},
|
||||
parse_git_remote_url, GitHostingProvider, GitHostingProviderRegistry, Oid, PullRequest,
|
||||
parse_git_remote_url, GitHostingProvider, GitHostingProviderRegistry, Oid,
|
||||
};
|
||||
use gpui::{App, Context, Entity, Subscription, Task};
|
||||
use http_client::HttpClient;
|
||||
|
@ -12,8 +12,11 @@ use project::{Project, ProjectItem};
|
|||
use smallvec::SmallVec;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use sum_tree::SumTree;
|
||||
use ui::SharedString;
|
||||
use url::Url;
|
||||
|
||||
use crate::commit_tooltip::ParsedCommitMessage;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct GitBlameEntry {
|
||||
pub rows: u32,
|
||||
|
@ -77,7 +80,11 @@ impl GitRemote {
|
|||
self.host.supports_avatars()
|
||||
}
|
||||
|
||||
pub async fn avatar_url(&self, commit: Oid, client: Arc<dyn HttpClient>) -> Option<Url> {
|
||||
pub async fn avatar_url(
|
||||
&self,
|
||||
commit: SharedString,
|
||||
client: Arc<dyn HttpClient>,
|
||||
) -> Option<Url> {
|
||||
self.host
|
||||
.commit_author_avatar_url(&self.owner, &self.repo, commit, client)
|
||||
.await
|
||||
|
@ -85,21 +92,11 @@ impl GitRemote {
|
|||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CommitDetails {
|
||||
pub message: String,
|
||||
pub parsed_message: ParsedMarkdown,
|
||||
pub permalink: Option<Url>,
|
||||
pub pull_request: Option<PullRequest>,
|
||||
pub remote: Option<GitRemote>,
|
||||
}
|
||||
|
||||
pub struct GitBlame {
|
||||
project: Entity<Project>,
|
||||
buffer: Entity<Buffer>,
|
||||
entries: SumTree<GitBlameEntry>,
|
||||
commit_details: HashMap<Oid, CommitDetails>,
|
||||
commit_details: HashMap<Oid, crate::commit_tooltip::ParsedCommitMessage>,
|
||||
buffer_snapshot: BufferSnapshot,
|
||||
buffer_edits: text::Subscription,
|
||||
task: Task<Result<()>>,
|
||||
|
@ -187,7 +184,7 @@ impl GitBlame {
|
|||
self.generated
|
||||
}
|
||||
|
||||
pub fn details_for_entry(&self, entry: &BlameEntry) -> Option<CommitDetails> {
|
||||
pub fn details_for_entry(&self, entry: &BlameEntry) -> Option<ParsedCommitMessage> {
|
||||
self.commit_details.get(&entry.sha).cloned()
|
||||
}
|
||||
|
||||
|
@ -480,7 +477,7 @@ async fn parse_commit_messages(
|
|||
deprecated_permalinks: &HashMap<Oid, Url>,
|
||||
provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
languages: &Arc<LanguageRegistry>,
|
||||
) -> HashMap<Oid, CommitDetails> {
|
||||
) -> HashMap<Oid, ParsedCommitMessage> {
|
||||
let mut commit_details = HashMap::default();
|
||||
|
||||
let parsed_remote_url = remote_url
|
||||
|
@ -519,8 +516,8 @@ async fn parse_commit_messages(
|
|||
|
||||
commit_details.insert(
|
||||
oid,
|
||||
CommitDetails {
|
||||
message,
|
||||
ParsedCommitMessage {
|
||||
message: message.into(),
|
||||
parsed_message,
|
||||
permalink,
|
||||
remote,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue