use std::path::PathBuf;
use std::process::{Command, Stdio};
use crossterm::event::Event;
use crossterm::style::{style, ContentStyle, StyledContent};
use git_wrapper::Repository;
use crate::commit::Commit;
use crate::commit::Oid;
use crate::default_styles::{
DATE_STYLE, DEBUG_STYLE, DEFAULT_STYLE, ID_STYLE, MOD_STYLE, NAME_STYLE, REF_STYLE,
};
use crate::history_entry::HistoryEntry;
use crate::raw;
use crate::ui::base::data::StyledAreaAdapter;
use crate::ui::base::{Area, Drawable, HandleEvent, ListWidget, StyledArea, StyledLine};
use crate::ui::layouts::DetailsWidget;
pub struct DiffView(ListWidget<String>, Vec<PathBuf>, Repository);
impl DiffView {
pub fn new(repo: Repository, paths: Vec<PathBuf>) -> Self {
let adapter = StyledAreaAdapter {
content: vec![],
thread: None,
};
Self(ListWidget::new(Box::new(adapter)), paths, repo)
}
}
impl Drawable for DiffView {
fn render(&mut self, area: &Area) -> StyledArea<String> {
self.0.render(area)
}
fn on_event(&mut self, event: &Event) -> HandleEvent {
self.0.on_event(event)
}
}
impl DetailsWidget<HistoryEntry> for DiffView {
fn set_content(&mut self, content: &HistoryEntry) {
let commit = content.commit();
let mut data: StyledArea<String> = vec![
color_text("Commit: ", &commit.id().0, *ID_STYLE),
color_text(
"Parents: ",
&commit
.parents()
.iter()
.map(|p| format!("{:?}", p))
.collect::<Vec<String>>()
.join(" "),
*ID_STYLE,
),
color_text("Author: ", commit.author_name(), *NAME_STYLE),
color_text("Author Date: ", commit.author_date(), *DATE_STYLE),
];
if commit.author_name() != commit.committer_name() {
data.push(color_text(
"Committer: ",
commit.committer_name(),
*NAME_STYLE,
));
}
if commit.author_date() != commit.committer_date() {
data.push(color_text(
"Committer Date: ",
commit.committer_date(),
*DATE_STYLE,
));
}
if !content.subtrees().is_empty() {
let module_names: Vec<String> =
content.subtrees().iter().map(|e| e.id().clone()).collect();
data.push(color_text(
"Strees: ",
&module_names.join(", "),
*MOD_STYLE,
));
}
if !commit.references().is_empty() {
let references: Vec<&str> = content
.filtered_references()
.iter()
.map(|r| r.0.as_str())
.collect();
data.push(color_text(
"Refs: ",
&references.join(", "),
*REF_STYLE,
));
}
if *content.debug() {
add_debug_content(&mut data, content);
}
data.push(StyledLine::empty());
for subject_line in commit.subject().trim().lines() {
data.push(color_text(" ", subject_line, *DEFAULT_STYLE));
}
data.push(StyledLine::empty());
for body_line in commit.body().trim().lines() {
data.push(color_text(" ", body_line, *DEFAULT_STYLE));
}
data.push(StyledLine::empty());
data.push(StyledLine {
content: vec![style(
" ❦ ❦ ❦ ❦ ".to_owned(),
)],
});
data.push(StyledLine::empty());
for line in git_diff(&self.2, commit, self.1.as_ref()) {
data.push(line);
}
let adapter = StyledAreaAdapter {
content: data,
thread: None,
};
self.0 = ListWidget::new(Box::new(adapter));
}
}
fn add_debug_content(data: &mut Vec<StyledLine<String>>, content: &HistoryEntry) {
data.push(StyledLine {
content: vec![style(" DEBUG".to_owned())],
});
data.push(color_text(
"fork_point: ",
&format!("{:?}", content.fork_point()),
*DEBUG_STYLE,
));
data.push(color_text(
"level: ",
&content.level().to_string(),
*DEBUG_STYLE,
));
data.push(color_text(
"commit_link: ",
&content.is_link().to_string(),
*DEBUG_STYLE,
));
data.push(color_text(
"is_foldable: ",
&content.is_foldable().to_string(),
*DEBUG_STYLE,
));
if content.is_foldable() {
data.push(color_text(
"is_folded: ",
&content.is_folded().to_string(),
*DEBUG_STYLE,
));
if !content.is_folded() {
data.push(color_text(
"children: ",
&content.visible_children().to_string(),
*DEBUG_STYLE,
));
}
}
data.push(StyledLine {
content: vec![style(
" ❦ ❦ ❦ ❦ ".to_owned(),
)],
});
}
fn git_diff(repo: &Repository, commit: &Commit, paths: &[PathBuf]) -> Vec<StyledLine<String>> {
let empty_tree = Oid("4b825dc642cb6eb9a060e54bf8d69288fbee4904".to_owned());
let bellow = commit.parents().first().unwrap_or(&empty_tree);
let rev = format!("{}..{}", bellow.0, commit.id().0);
let mut cmd = repo.git();
cmd.args(&[
"diff",
"--color=always",
"--stat",
"-p",
"-M",
"--full-index",
&rev,
]);
if !paths.is_empty() {
cmd.arg("--");
cmd.args(paths);
}
if which::which("delta").is_ok() {
let proc = cmd.stdout(Stdio::piped()).spawn().unwrap();
let delta_p = Command::new("delta")
.arg("--paging=never")
.stdin(Stdio::from(proc.stdout.unwrap()))
.output()
.unwrap();
raw::parse_spans(delta_p.stdout)
} else {
let proc = cmd
.args(paths)
.output()
.expect("Failed to execute git-diff(1)");
raw::parse_spans(proc.stdout)
}
}
fn color_text(key: &str, value: &str, style: ContentStyle) -> StyledLine<String> {
let content = format!("{}{}", key, value);
StyledLine {
content: vec![StyledContent::new(style, content)],
}
}