use std::borrow::Cow;
use askama::Template;
use icu_datetime::{DateTimeFormatter, DateTimeFormatterPreferences};
use jiff::tz::TimeZone;
use jiff_icu::ConvertFrom;
use l10n_embed::Localize;
use l10n_embed_derive::localize;
use libpijul::Base32;
use pijul_extension::FileSystemRepository;
use pijul_extension::author::AuthorSource;
use pijul_extension::file_system::changes::{CreditSource, HunkDiff};
use pijul_extension::file_system::open_file::OpenFile;
use crate::vscode_sys;
#[localize("l10n/**/inline_credit/hover.ftl")]
enum HoverHeading {
AbsoluteTimestamp,
ChangeId,
}
#[localize("l10n/**/inline_credit/hover.ftl")]
struct HunkHeading {
index: usize,
}
#[derive(askama::Template)]
#[template(path = "hover/diff.md", escape = "none")]
struct HunkHoverTemplate {
heading: String,
lines_added: Vec<String>,
lines_removed: Vec<String>,
}
#[derive(askama::Template)]
#[template(path = "hover/tracked.html")]
struct TrackedHoverTemplate<'change_header> {
title: &'change_header str,
authors: String,
relative_timestamp: String,
absolute_timestamp_header: String,
absolute_timestamp: String,
change_id_header: String,
change_id: String,
description: Option<&'change_header str>,
hunk_template: HunkHoverTemplate,
}
#[derive(askama::Template)]
#[template(path = "hover/untracked.html")]
struct UntrackedHoverTemplate {
hunk_template: HunkHoverTemplate,
}
pub fn render<'env>(
env: &'env napi::Env,
localization_context: &l10n_embed::Context,
repository: &FileSystemRepository,
open_file: &OpenFile,
credit_source: CreditSource,
) -> Result<vscode_sys::MarkdownString<'env>, napi::Error> {
let active_hunk = repository
.get_active_hunk(open_file, credit_source)
.map_err(|error| napi::Error::from_reason(format!("Unable to get active hunk: {error}")))?;
let mut hunk_heading_buffer = String::new();
let hunk_heading = HunkHeading {
index: active_hunk.index,
};
hunk_heading.localize(localization_context, &mut hunk_heading_buffer);
let hunk_template = match active_hunk.diff {
HunkDiff::TextChange {
lines_added,
lines_removed,
} => HunkHoverTemplate {
heading: hunk_heading_buffer,
lines_added,
lines_removed,
},
};
let rendered_html_result = match credit_source {
CreditSource::Tracked { vertex } => {
let change = repository.get_change(vertex.change).map_err(|error| {
napi::Error::from_reason(format!(
"Unable to get change for vertex {vertex:#?}: {error}"
))
})?;
let mut authors = String::new();
let authors_list = l10n_embed::list::AndList::new(
repository
.authors_for_change(&change.header)
.into_iter()
.map(|author_source| match author_source {
AuthorSource::Local(identity) | AuthorSource::Remote(identity) => {
Cow::from(format!(
"{} ({})",
identity.config.author.display_name,
identity.config.author.username
))
}
AuthorSource::Unknown(public_key_hash) => Cow::from(public_key_hash),
})
.collect(),
);
authors_list.localize(localization_context, &mut authors);
let mut relative_timestamp = String::new();
change
.header
.timestamp
.localize(localization_context, &mut relative_timestamp);
let mut absolute_timestamp_header = String::new();
let mut change_id_header = String::new();
HoverHeading::AbsoluteTimestamp
.localize(localization_context, &mut absolute_timestamp_header);
HoverHeading::ChangeId.localize(localization_context, &mut change_id_header);
let zoned_timestamp = change.header.timestamp.to_zoned(TimeZone::system());
let icu_timestamp = icu_time::ZonedDateTime::<
icu_calendar::Iso,
icu_time::TimeZoneInfo<icu_time::zone::models::Full>,
>::convert_from(&zoned_timestamp);
let absolute_timestamp_formatter = DateTimeFormatter::try_new(
DateTimeFormatterPreferences::from(&localization_context.locale),
icu_datetime::fieldsets::YMDT::medium()
.with_zone(icu_datetime::fieldsets::zone::SpecificShort),
)
.map_err(|error| {
napi::Error::from_reason(format!("Failed to create datetime formatter: {error}"))
})?;
let absolute_timestamp = absolute_timestamp_formatter
.format(&icu_timestamp)
.to_string();
let hover_template = TrackedHoverTemplate {
title: &change.header.message,
authors,
relative_timestamp,
absolute_timestamp_header,
absolute_timestamp,
change_id_header,
change_id: vertex.change.to_base32(),
description: change.header.description.as_deref(),
hunk_template,
};
hover_template.render()
}
CreditSource::Untracked { .. } => {
let hover_template = UntrackedHoverTemplate { hunk_template };
hover_template.render()
}
};
let rendered_html = rendered_html_result.map_err(|error| {
napi::Error::from_reason(format!("Failed to render hover message: {error}"))
})?;
let mut hover_message = vscode_sys::MarkdownString::new(env, &rendered_html, false)?;
hover_message.set_support_html(true)?;
Ok(hover_message)
}