use crate::{
app::Environment,
components::{
string_width_align, time_to_string, visibility_blocking,
CommandBlocking, CommandInfo, Component, DrawableComponent,
EventState, ScrollType,
},
keys::{key_match, SharedKeyConfig},
popups::{FileRevOpen, InspectCommitOpen},
queue::{InternalEvent, Queue, StackablePopupOpen},
string_utils::tabs_to_spaces,
strings,
ui::{self, style::SharedTheme, AsyncSyntaxJob, SyntaxText},
AsyncAppNotification, AsyncNotification, SyntaxHighlightProgress,
};
use anyhow::Result;
use asyncgit::{
asyncjob::AsyncSingleJob,
sync::{BlameHunk, CommitId, FileBlame, RepoPathRef},
AsyncBlame, AsyncGitNotification, BlameParams,
};
use crossbeam_channel::Sender;
use crossterm::event::Event;
use ratatui::{
backend::Backend,
layout::{Constraint, Rect},
symbols::line::VERTICAL,
text::{Span, Text},
widgets::{Block, Borders, Cell, Clear, Row, Table, TableState},
Frame,
};
use std::path::Path;
static NO_COMMIT_ID: &str = "0000000";
static NO_AUTHOR: &str = "<no author>";
static MIN_AUTHOR_WIDTH: usize = 3;
static MAX_AUTHOR_WIDTH: usize = 20;
struct SyntaxFileBlame {
pub file_blame: FileBlame,
pub styled_text: Option<SyntaxText>,
}
impl SyntaxFileBlame {
fn path(&self) -> &str {
&self.file_blame.path
}
fn commit_id(&self) -> &CommitId {
&self.file_blame.commit_id
}
fn lines(&self) -> &Vec<(Option<BlameHunk>, String)> {
&self.file_blame.lines
}
}
enum BlameProcess {
GettingBlame(AsyncBlame),
SyntaxHighlighting {
unstyled_file_blame: SyntaxFileBlame,
job: AsyncSingleJob<AsyncSyntaxJob>,
},
Result(SyntaxFileBlame),
}
impl BlameProcess {
fn result(&self) -> Option<&SyntaxFileBlame> {
match self {
Self::GettingBlame(_) => None,
Self::SyntaxHighlighting {
unstyled_file_blame,
..
} => Some(unstyled_file_blame),
Self::Result(ref file_blame) => Some(file_blame),
}
}
}
#[derive(Clone, Debug)]
pub struct BlameFileOpen {
pub file_path: String,
pub commit_id: Option<CommitId>,
pub selection: Option<usize>,
}
pub struct BlameFilePopup {
title: String,
theme: SharedTheme,
queue: Queue,
visible: bool,
open_request: Option<BlameFileOpen>,
params: Option<BlameParams>,
table_state: std::cell::Cell<TableState>,
key_config: SharedKeyConfig,
current_height: std::cell::Cell<usize>,
blame: Option<BlameProcess>,
app_sender: Sender<AsyncAppNotification>,
git_sender: Sender<AsyncGitNotification>,
repo: RepoPathRef,
}
impl DrawableComponent for BlameFilePopup {
fn draw<B: Backend>(
&self,
f: &mut Frame<B>,
area: Rect,
) -> Result<()> {
if self.is_visible() {
let title = self.get_title();
let rows = self.get_rows(area.width.into());
let author_width = get_author_width(area.width.into());
let constraints = [
// commit id
Constraint::Length(7),
// commit date
Constraint::Length(10),
// commit author
Constraint::Length(author_width.try_into()?),
// line number and vertical bar
Constraint::Length(
(self.get_line_number_width().saturating_add(1))
.try_into()?,
),
// the source code line
Constraint::Percentage(100),
];
let number_of_rows: usize = rows.len();
let syntax_highlight_progress = match self.blame {
Some(BlameProcess::SyntaxHighlighting {
ref job,
..
}) => job
.progress()
.map(|p| format!(" ({}%)", p.progress))
.unwrap_or_default(),
_ => String::new(),
};
let title_with_highlight_progress =
format!("{title}{syntax_highlight_progress}");
let table = Table::new(rows)
.widths(&constraints)
.column_spacing(1)
.highlight_style(self.theme.text(true, true))
.block(
Block::default()
.borders(Borders::ALL)
.title(Span::styled(
title_with_highlight_progress,
self.theme.title(true),
))
.border_style(self.theme.block(true)),
);
let mut table_state = self.table_state.take();
f.render_widget(Clear, area);
f.render_stateful_widget(table, area, &mut table_state);
ui::draw_scroll