Compare commits

...

1 Commits

Author SHA1 Message Date
Lukas Wirth
c5f3d7dfc6 git_ui: Color blame timestamps by their age 2025-09-30 09:36:05 +02:00
6 changed files with 53 additions and 11 deletions

View File

@@ -15,7 +15,7 @@ use markdown::{Markdown, MarkdownElement};
use project::{git_store::Repository, project_settings::ProjectSettings};
use settings::Settings as _;
use theme::ThemeSettings;
use time::OffsetDateTime;
use time::{OffsetDateTime, ext::NumericalDuration};
use time_format::format_local_timestamp;
use ui::{ContextMenu, Divider, IconButtonShape, prelude::*, tooltip_container};
use workspace::Workspace;
@@ -42,7 +42,7 @@ impl BlameRenderer for GitBlameRenderer {
window: &mut Window,
cx: &mut App,
) -> Option<AnyElement> {
let relative_timestamp = blame_entry_relative_timestamp(&blame_entry);
let (time_color, relative_timestamp) = blame_entry_relative_timestamp(cx, &blame_entry);
let short_commit_id = blame_entry.sha.display_short();
let author_name = blame_entry.author.as_deref().unwrap_or("<no name>");
let name = util::truncate_and_trailoff(author_name, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED);
@@ -80,7 +80,7 @@ impl BlameRenderer for GitBlameRenderer {
.children(avatar)
.child(name),
)
.child(relative_timestamp)
.child(div().text_color(time_color).child(relative_timestamp))
.hover(|style| style.bg(cx.theme().colors().element_hover))
.cursor_pointer()
.on_mouse_down(MouseButton::Right, {
@@ -150,7 +150,7 @@ impl BlameRenderer for GitBlameRenderer {
blame_entry: BlameEntry,
cx: &mut App,
) -> Option<AnyElement> {
let relative_timestamp = blame_entry_relative_timestamp(&blame_entry);
let (_, relative_timestamp) = blame_entry_relative_timestamp(cx, &blame_entry);
let author = blame_entry.author.as_deref().unwrap_or_default();
let summary_enabled = ProjectSettings::get_global(cx)
.git
@@ -415,17 +415,32 @@ fn deploy_blame_entry_context_menu(
});
}
fn blame_entry_relative_timestamp(blame_entry: &BlameEntry) -> String {
fn blame_entry_relative_timestamp(cx: &App, blame_entry: &BlameEntry) -> (Hsla, String) {
match blame_entry.author_offset_date_time() {
Ok(timestamp) => {
let mut color = cx.theme().colors().version_control_blame_age_new;
let local = chrono::Local::now().offset().local_minus_utc();
time_format::format_localized_timestamp(
timestamp,
time::OffsetDateTime::now_utc(),
time::UtcOffset::from_whole_seconds(local).unwrap(),
time_format::TimestampFormat::Relative,
let now = time::OffsetDateTime::now_utc();
// Gradient over ~2 years
let recency = ((now - timestamp - 2.hours()).as_seconds_f32()
/ 106.weeks().as_seconds_f32())
.clamp(0.0, 1.0);
// Sqrt to make more recent colors change more strongly
color.l *= 1. - 0.45 * recency.sqrt();
(
color,
time_format::format_localized_timestamp(
timestamp,
now,
time::UtcOffset::from_whole_seconds(local).unwrap(),
time_format::TimestampFormat::Relative,
),
)
}
Err(_) => "Error parsing date".to_string(),
Err(_) => (
cx.theme().colors().terminal_ansi_bright_red,
"Error parsing date".to_string(),
),
}
}

View File

@@ -777,6 +777,14 @@ pub struct ThemeColorsContent {
/// Deprecated in favor of `version_control_conflict_marker_theirs`.
#[deprecated]
pub version_control_conflict_theirs_background: Option<String>,
/// Blame gutter color for new revisions.
#[serde(rename = "version_control.blame.age_new")]
pub version_control_blame_age_new: Option<String>,
/// Blame gutter color for old revisions.
#[serde(rename = "version_control.blame.age_old")]
pub version_control_blame_age_old: Option<String>,
}
#[skip_serializing_none]

View File

@@ -154,6 +154,8 @@ impl ThemeColors {
version_control_ignored: gray().light().step_12(),
version_control_conflict_marker_ours: green().light().step_10().alpha(0.5),
version_control_conflict_marker_theirs: blue().light().step_10().alpha(0.5),
version_control_blame_age_new: orange().light().step_10(),
version_control_blame_age_old: blue().light().step_10(),
}
}
@@ -280,6 +282,8 @@ impl ThemeColors {
version_control_ignored: gray().dark().step_12(),
version_control_conflict_marker_ours: green().dark().step_10().alpha(0.5),
version_control_conflict_marker_theirs: blue().dark().step_10().alpha(0.5),
version_control_blame_age_new: orange().dark().step_10(),
version_control_blame_age_old: blue().dark().step_10(),
}
}
}

View File

@@ -233,6 +233,8 @@ pub(crate) fn zed_default_dark() -> Theme {
version_control_ignored: crate::gray().light().step_12(),
version_control_conflict_marker_ours: crate::green().light().step_12().alpha(0.5),
version_control_conflict_marker_theirs: crate::blue().light().step_12().alpha(0.5),
version_control_blame_age_new: crate::orange().dark().step_9(),
version_control_blame_age_old: crate::blue().dark().step_9(),
},
status: StatusColors {
conflict: yellow,

View File

@@ -756,6 +756,14 @@ pub fn theme_colors_refinement(
.as_ref()
.or(this.version_control_conflict_theirs_background.as_ref())
.and_then(|color| try_parse_color(color).ok()),
version_control_blame_age_new: this
.version_control_blame_age_new
.as_ref()
.and_then(|color| try_parse_color(color).ok()),
version_control_blame_age_old: this
.version_control_blame_age_old
.as_ref()
.and_then(|color| try_parse_color(color).ok()),
}
}

View File

@@ -286,6 +286,11 @@ pub struct ThemeColors {
pub version_control_conflict_marker_ours: Hsla,
/// Represents the "theirs" region of a merge conflict.
pub version_control_conflict_marker_theirs: Hsla,
/// Blame gutter color for new revisions.
pub version_control_blame_age_new: Hsla,
/// Blame gutter color for old revisions.
pub version_control_blame_age_old: Hsla,
}
#[derive(EnumIter, Debug, Clone, Copy, AsRefStr)]