feat: allow using custom commit format

This commit is contained in:
Nik Revenco 2025-03-19 16:16:15 +00:00
parent 452da44dc7
commit 8b559da256
5 changed files with 75 additions and 14 deletions

View file

@ -34,6 +34,7 @@ impl helix_event::AsyncHook for BlameHandler {
diff_providers,
removed_lines_count,
added_lines_count,
blame_format,
} = event;
self.cursor_line = cursor_line;
@ -49,7 +50,7 @@ impl helix_event::AsyncHook for BlameHandler {
added_lines_count,
removed_lines_count,
)
.map(|s| s.to_string())
.map(|s| s.parse_format(&blame_format))
});
self.worker = Some(worker);
Some(Instant::now() + Duration::from_millis(50))
@ -85,7 +86,8 @@ impl helix_event::AsyncHook for BlameHandler {
pub(super) fn register_hooks(handlers: &Handlers) {
let tx = handlers.blame.clone();
register_hook!(move |event: &mut PostCommand<'_, '_>| {
if !event.cx.editor.config().version_control.blame {
let version_control_config = &event.cx.editor.config().version_control;
if !version_control_config.inline_blame {
return Ok(());
}
@ -124,6 +126,8 @@ pub(super) fn register_hooks(handlers: &Handlers) {
added_lines_count,
// ok to clone because diff_providers is very small
diff_providers: event.cx.editor.diff_providers.clone(),
// ok to clone because blame_format is likely to be about 30 characters or less
blame_format: version_control_config.inline_blame_format.clone(),
},
);

View file

@ -201,7 +201,7 @@ impl EditorView {
inline_diagnostic_config,
config.end_of_line_diagnostics,
));
if config.version_control.blame {
if config.version_control.inline_blame {
if let Some(blame) = &doc.blame {
decorations.add_decoration(text_decorations::blame::EolBlame::new(
doc,

View file

@ -1,24 +1,68 @@
use anyhow::Context as _;
use core::fmt;
use gix::bstr::BStr;
use std::{ops::Range, path::Path};
use std::{collections::HashMap, ops::Range, path::Path};
use super::{get_repo_dir, open_repo};
pub struct BlameInformation {
pub commit_hash: String,
pub author_name: String,
pub author_email: String,
pub commit_date: String,
pub commit_message: String,
}
impl fmt::Display for BlameInformation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}, {} • {} • {}",
self.author_name, self.commit_date, self.commit_message, self.commit_hash
)
impl BlameInformation {
/// Parse the user's blame format
pub fn parse_format(&self, format: &str) -> String {
let mut formatted = String::with_capacity(format.len() * 2);
let variables = HashMap::from([
("commit", &self.commit_hash),
("author", &self.author_name),
("date", &self.commit_date),
("message", &self.commit_message),
("email", &self.author_email),
]);
let mut chars = format.chars().peekable();
while let Some(ch) = chars.next() {
// "{{" => '{'
if ch == '{' && chars.next_if_eq(&'{').is_some() {
formatted.push('{');
}
// "}}" => '}'
else if ch == '}' && chars.next_if_eq(&'}').is_some() {
formatted.push('}');
} else if ch == '{' {
let mut variable = String::new();
// eat all characters until the end
while let Some(ch) = chars.next_if(|ch| *ch != '}') {
variable.push(ch);
}
// eat the '}' if it was found
let has_closing = chars.next().is_some();
let res = variables
.get(variable.as_str())
.map(|s| s.to_string())
.unwrap_or_else(|| {
// Invalid variable. So just add whatever we parsed before
let mut result = String::with_capacity(variable.len() + 2);
result.push('{');
result.push_str(variable.as_str());
if has_closing {
result.push('}');
}
result
});
formatted.push_str(&res);
} else {
formatted.push(ch);
}
}
formatted
}
}
@ -87,6 +131,7 @@ pub fn blame(
Ok(BlameInformation {
commit_hash: commit.short_id()?.to_string(),
author_name: author.name.to_string(),
author_email: author.email.to_string(),
commit_date: author.time.format(gix::date::time::format::SHORT),
commit_message: commit.message()?.title.to_string(),
})

View file

@ -171,11 +171,21 @@ impl Default for GutterLineNumbersConfig {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct VersionControlConfig {
/// Whether to enable git blame
pub blame: bool,
pub inline_blame: bool,
pub inline_blame_format: String,
}
impl Default for VersionControlConfig {
fn default() -> Self {
Self {
inline_blame: false,
inline_blame_format: "{author}, {date} • {message} • {commit}".to_owned(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View file

@ -29,6 +29,8 @@ pub enum BlameEvent {
/// How many lines were added before cursor_line
added_lines_count: u32,
diff_providers: DiffProviderRegistry,
/// Format of the blame
blame_format: String,
},
}