mod show_commit_state;
mod util;
mod view_builder;
#[cfg(test)]
mod tests;
use anyhow::anyhow;
use captur::capture;
use config::{Config, DiffIgnoreWhitespaceSetting, DiffShowWhitespaceSetting};
use git::{CommitDiff, CommitDiffLoaderOptions, Repository};
use input::InputOptions;
use todo_file::TodoFile;
use view::{RenderContext, ViewData, ViewSender};
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, ProcessResult, State},
select,
util::handle_view_data_scroll,
};
const INPUT_OPTIONS: InputOptions = InputOptions::UNDO_REDO.union(InputOptions::MOVEMENT);
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,
view_builder: ViewBuilder,
}
impl Module for ShowCommit {
fn activate(&mut self, rebase_todo: &TodoFile, _: State) -> ProcessResult {
if let Some(selected_line) = rebase_todo.get_selected_line() {
if let Some(diff) = self.diff.as_ref() {
if diff.commit().hash() == selected_line.get_hash() {
return ProcessResult::new();
}
}
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);
ProcessResult::new()
},
Err(e) => {
ProcessResult::new()
.error(e.context(anyhow!("Error loading commit")))
.state(State::List)
},
}
}
else {
ProcessResult::new()
.error(anyhow!("No valid commit to show"))
.state(State::List)
}
}
fn build_view_data(&mut self, context: &RenderContext, _: &TodoFile) -> &ViewData {
if self.help.is_active() {
return self.help.get_view_data();
}
let diff = self.diff.as_ref().expect("Diff ref unwrap failed"); 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, key_bindings) }
)
}
fn handle_event(&mut self, event: Event, view_sender: &ViewSender, _: &mut TodoFile) -> ProcessResult {
if self.help.is_active() {
self.help.handle_event(event, view_sender);
return ProcessResult::from(event);
}
let mut result = ProcessResult::from(event);
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_sender).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::MetaEvent(meta_event) if meta_event == MetaEvent::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 {
result = result.state(State::List);
}
},
Event::Resize(..) => active_view_data.update_view_data(|updater| updater.clear()),
_ => {},
}
}
result
}
}
impl ShowCommit {
pub(crate) fn new(config: &Config, repository: Repository) -> 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)
.interhunk_context(config.git.diff_interhunk_lines)
.renames(config.git.diff_renames, config.git.diff_rename_limit);
Self {
diff: None,
diff_view_data,
help: Help::new_from_keybindings(&get_show_commit_help_lines(&config.key_bindings)),
commit_diff_loader_options,
overview_view_data,
state: ShowCommitState::Overview,
view_builder: ViewBuilder::new(view_builder_options),
repository,
}
}
}