pub(crate) mod view;
use crate::core::keymap::{
nav_bindings, search_bindings, ActionHelp, Keymap, NavAction, SearchAction,
};
use crate::core::pane::{self, Pane, PaneShared, SubPaneScroll};
use crate::git::domain::graph::{self, GraphRow};
use crate::git::domain::repository::{CommitFileChange, CommitInfo, Repo};
use crate::git::state::PaneEvent;
use crossterm::event::KeyCode;
use ratatui::{layout::Rect, Frame};
use crate::core::app::AppContext;
use crate::core::search::SearchMatch;
#[derive(Debug, Clone)]
pub enum GitLogAction {
Nav(NavAction),
YankHash,
YankUrl,
OpenGitHub,
FocusReflog,
Search(SearchAction),
Esc,
}
crate::impl_pane_action_from_str!(
GitLogAction, nav: Nav, search: Search, esc: Esc,
YankHash, YankUrl, OpenGitHub, FocusReflog
);
impl ActionHelp for GitLogAction {
fn label(&self) -> Option<&'static str> {
match self {
GitLogAction::Nav(nav) => nav.label(),
GitLogAction::YankHash => Some("Copy commit hash"),
GitLogAction::YankUrl => Some("Copy commit URL"),
GitLogAction::OpenGitHub => Some("Open in GitHub"),
GitLogAction::FocusReflog => Some("Focus reflog"),
GitLogAction::Search(sa) => sa.label(),
GitLogAction::Esc => Some("Clear search / Back"),
}
}
}
pub fn default_keymap() -> Keymap<GitLogAction> {
Keymap::new()
.bindings(nav_bindings(GitLogAction::Nav))
.bindings(search_bindings(GitLogAction::Search))
.key(KeyCode::Char('y'), GitLogAction::YankHash)
.key(KeyCode::Char('Y'), GitLogAction::YankUrl)
.key(KeyCode::Char('o'), GitLogAction::OpenGitHub)
.key(KeyCode::Char('h'), GitLogAction::FocusReflog)
.key(KeyCode::Esc, GitLogAction::Esc)
}
pub struct GitLogPane {
pub commits: Vec<CommitInfo>,
pub selected_idx: usize,
pub view_height: u16,
pub ref_name: String,
pub graph: Vec<GraphRow>,
pub detail: SubPaneScroll,
pub detail_view_height: u16,
pub detail_changed_files: Vec<CommitFileChange>,
keymap: Keymap<GitLogAction>,
pub pane_id: usize,
reflog_id: usize,
pub branch_list_id: usize,
}
impl GitLogPane {
pub fn new(pane_id: usize, reflog_id: usize, branch_list_id: usize) -> Self {
Self {
commits: Vec::new(),
selected_idx: 0,
view_height: 0,
ref_name: String::new(),
graph: Vec::new(),
detail: SubPaneScroll::default(),
detail_view_height: 0,
detail_changed_files: Vec::new(),
keymap: default_keymap(),
pane_id,
reflog_id,
branch_list_id,
}
}
pub fn set_keymap(&mut self, km: Keymap<GitLogAction>) {
self.keymap = km;
}
pub fn keymap(&self) -> &Keymap<GitLogAction> {
&self.keymap
}
pub fn load_for_ref(&mut self, repo: &Repo, ref_name: &str) {
self.ref_name = ref_name.to_string();
self.commits = repo.log_for_ref(ref_name, 100);
self.graph = graph::build_graph(&self.commits);
self.selected_idx = 0;
self.detail.reset();
self.detail_changed_files.clear();
self.load_detail(repo);
}
pub fn load_detail(&mut self, repo: &Repo) {
if let Some(commit) = self.commits.get(self.selected_idx) {
self.detail_changed_files = repo.commit_changed_files(&commit.full_hash);
self.detail.reset();
} else {
self.detail_changed_files.clear();
}
}
pub fn clear_log(&mut self) {
self.commits.clear();
self.graph.clear();
self.ref_name.clear();
self.detail_changed_files.clear();
}
fn execute(&mut self, shared: &PaneShared, action: GitLogAction) -> Vec<PaneEvent> {
if let Some(events) = pane::try_dispatch_search_esc(
&action,
shared,
self.pane_id,
vec![PaneEvent::SetFocus(shared.previous_pane)],
) {
return events;
}
match action {
GitLogAction::FocusReflog => {
return vec![PaneEvent::SetFocus(self.reflog_id)];
}
GitLogAction::Nav(nav) => {
return pane::execute_list_nav(
nav,
&mut self.selected_idx,
self.commits.len(),
Some(self.view_height),
);
}
GitLogAction::YankHash => {
if let Some(commit) = self.commits.get(self.selected_idx) {
return vec![PaneEvent::CopyToClipboard(commit.full_hash.clone())];
}
}
GitLogAction::YankUrl => {
if let Some(commit) = self.commits.get(self.selected_idx) {
return vec![crate::github::panes::gh_list::copy_url_event(
crate::github::domain::client::commit_url(&commit.full_hash),
)];
}
}
GitLogAction::OpenGitHub => {
if let Some(commit) = self.commits.get(self.selected_idx) {
let hash = commit.full_hash.clone();
if let Some(url) = crate::github::domain::client::commit_url(&hash) {
return vec![PaneEvent::OpenUrl(url)];
} else {
return vec![PaneEvent::StatusMessage(
"Could not determine GitHub repository".to_string(),
)];
}
}
}
_ => {}
}
vec![]
}
}
impl Pane<PaneEvent> for GitLogPane {
crate::impl_handle_key!(keymap);
fn render(&mut self, f: &mut Frame, _ctx: &AppContext, shared: &PaneShared, area: Rect) {
view::render(f, self, shared, area);
}
fn collect_search_matches(&self, _shared: &PaneShared, query: &str) -> Vec<SearchMatch> {
pane::collect_list_search_matches(&self.commits, query, |commit| {
format!(
"{} {} {} {}",
commit.short_hash, commit.author, commit.date, commit.message
)
})
}
crate::impl_list_pane_selection!();
}