mod show_commit_state;
mod util;
mod view_builder;
#[cfg(test)]
mod tests;
use std::sync::Arc;
use anyhow::{anyhow, Error};
use captur::capture;
use config::{Config, DiffIgnoreWhitespaceSetting, DiffShowWhitespaceSetting};
use git::{CommitDiff, CommitDiffLoaderOptions, Repository};
use input::{InputOptions, StandardEvent};
use parking_lot::Mutex;
use todo_file::TodoFile;
use view::{RenderContext, ViewData};
use self::{
show_commit_state::ShowCommitState,
util::get_show_commit_help_lines,
view_builder::{ViewBuilder, ViewBuilderOptions},
};
use crate::{
components::help::Help,
events::{Event, KeyBindings, MetaEvent},
module::{Module, State},
process::Results,
select,
util::handle_view_data_scroll,
};
const INPUT_OPTIONS: InputOptions = InputOptions::UNDO_REDO
.union(InputOptions::MOVEMENT)
.union(InputOptions::HELP);
pub(crate) struct ShowCommit {
commit_diff_loader_options: CommitDiffLoaderOptions,
diff: Option<CommitDiff>,
diff_view_data: ViewData,
help: Help,
overview_view_data: ViewData,
repository: Repository,
state: ShowCommitState,
todo_file: Arc<Mutex<TodoFile>>,
view_builder: ViewBuilder,
}
impl Module for ShowCommit {
fn activate(&mut self, _: State) -> Results {
let mut results = Results::new();
if let Some(selected_line) = self.todo_file.lock().get_selected_line() {
if let Some(diff) = self.diff.as_ref() {
if diff.commit().hash() == selected_line.get_hash() {
return results;
}
}
self.overview_view_data.update_view_data(|updater| {
updater.clear();
updater.reset_scroll_position();
});
self.diff_view_data.update_view_data(|updater| {
updater.clear();
updater.reset_scroll_position();
});
let new_diff = self
.repository
.load_commit_diff(selected_line.get_hash(), &self.commit_diff_loader_options);
match new_diff {
Ok(diff) => {
self.diff = Some(diff);
},
Err(e) => {
results.error_with_return(Error::from(e), State::List);
},
}
}
else {
results.error_with_return(anyhow!("No valid commit to show"), State::List);
}
results
}
fn build_view_data(&mut self, context: &RenderContext) -> &ViewData {
if self.help.is_active() {
return self.help.get_view_data();
}
let diff = self.diff.as_ref().unwrap(); let state = &self.state;
let view_builder = &self.view_builder;
let is_full_width = context.is_full_width();
match *state {
ShowCommitState::Overview => {
if self.overview_view_data.is_empty() {
self.overview_view_data.update_view_data(|updater| {
capture!(view_builder, diff);
view_builder.build_view_data_for_overview(updater, diff, is_full_width);
});
}
&self.overview_view_data
},
ShowCommitState::Diff => {
if self.diff_view_data.is_empty() {
self.diff_view_data.update_view_data(|updater| {
capture!(view_builder, diff);
view_builder.build_view_data_diff(updater, diff, is_full_width);
});
}
&self.diff_view_data
},
}
}
fn input_options(&self) -> &InputOptions {
select!(default || &INPUT_OPTIONS, || self.help.input_options())
}
fn read_event(&self, event: Event, key_bindings: &KeyBindings) -> Event {
select!(
default || {
key_bindings
.custom
.show_diff
.contains(&event)
.then(|| Event::from(MetaEvent::ShowDiff))
.unwrap_or(event)
},
|| { self.help.read_event(event) }
)
}
fn handle_event(&mut self, event: Event, view_state: &view::State) -> Results {
if self.help.is_active() {
self.help.handle_event(event, view_state);
return Results::new();
}
let mut results = Results::new();
let active_view_data = match self.state {
ShowCommitState::Overview => &mut self.overview_view_data,
ShowCommitState::Diff => &mut self.diff_view_data,
};
if handle_view_data_scroll(event, view_state).is_none() {
match event {
Event::MetaEvent(meta_event) if meta_event == MetaEvent::ShowDiff => {
active_view_data.update_view_data(|updater| updater.clear());
self.state = match self.state {
ShowCommitState::Overview => ShowCommitState::Diff,
ShowCommitState::Diff => ShowCommitState::Overview,
}
},
Event::Standard(standard_event) if standard_event == StandardEvent::Help => self.help.set_active(),
Event::Key(_) => {
active_view_data.update_view_data(|updater| updater.clear());
if self.state == ShowCommitState::Diff {
self.state = ShowCommitState::Overview;
}
else {
results.state(State::List);
}
},
Event::Resize(..) => active_view_data.update_view_data(|updater| updater.clear()),
_ => {},
}
}
results
}
}
impl ShowCommit {
pub(crate) fn new(config: &Config, repository: Repository, todo_file: Arc<Mutex<TodoFile>>) -> Self {
let overview_view_data = ViewData::new(|updater| {
updater.set_show_title(true);
updater.set_show_help(true);
});
let diff_view_data = ViewData::new(|updater| {
updater.set_show_title(true);
updater.set_show_help(true);
});
let view_builder_options = ViewBuilderOptions::new(
config.diff_tab_width as usize,
config.diff_tab_symbol.as_str(),
config.diff_space_symbol.as_str(),
config.diff_show_whitespace == DiffShowWhitespaceSetting::Both
|| config.diff_show_whitespace == DiffShowWhitespaceSetting::Leading,
config.diff_show_whitespace == DiffShowWhitespaceSetting::Both
|| config.diff_show_whitespace == DiffShowWhitespaceSetting::Trailing,
);
let commit_diff_loader_options = CommitDiffLoaderOptions::new()
.context_lines(config.git.diff_context)
.copies(config.git.diff_copies)
.ignore_whitespace(config.diff_ignore_whitespace == DiffIgnoreWhitespaceSetting::All)
.ignore_whitespace_change(config.diff_ignore_whitespace == DiffIgnoreWhitespaceSetting::Change)
.ignore_blank_lines(config.diff_ignore_blank_lines)
.interhunk_context(config.git.diff_interhunk_lines)
.renames(config.git.diff_renames, config.git.diff_rename_limit);
Self {
commit_diff_loader_options,
diff: None,
diff_view_data,
help: Help::new_from_keybindings(&get_show_commit_help_lines(&config.key_bindings)),
overview_view_data,
repository,
state: ShowCommitState::Overview,
todo_file,
view_builder: ViewBuilder::new(view_builder_options),
}
}
}