//! Git Interactive Rebase Tool - Todo File Module
//!
//! # Description
//! This module is used to handle working with the rebase todo file.
mod action;
mod edit_content;
pub(crate) mod errors;
mod history;
mod line;
mod line_parser;
mod search;
#[cfg(test)]
#[cfg(not(tarpaulin_include))]
pub(crate) mod testutil;
mod todo_file_options;
mod utils;
use std::{
fs::{read_to_string, File},
io::Write,
path::{Path, PathBuf},
slice::Iter,
};
pub(crate) use version_track::Version;
pub(crate) use self::{
action::Action,
edit_content::EditContext,
line::Line,
search::Search,
todo_file_options::TodoFileOptions,
};
use self::{
history::{History, HistoryItem},
utils::{remove_range, swap_range_down, swap_range_up},
};
use crate::todo_file::{
errors::{FileReadErrorCause, IoError},
history::Operation,
};
/// Represents a rebase file.
#[derive(Debug)]
pub(crate) struct TodoFile {
filepath: PathBuf,
history: History,
is_noop: bool,
lines: Vec<Line>,
options: TodoFileOptions,
selected_line_index: usize,
version: Version,
}
impl TodoFile {
/// Create a new instance.
#[must_use]
pub(crate) fn new<Path: AsRef<std::path::Path>>(path: Path, options: TodoFileOptions) -> Self {
let history = History::new(options.undo_limit);
Self {
filepath: PathBuf::from(path.as_ref()),
history,
is_noop: false,
lines: vec![],
options,
selected_line_index: 0,
version: Version::new(),
}
}
/// Set the rebase lines.
pub(crate) fn set_lines(&mut self, lines: Vec<Line>) {
self.is_noop = !lines.is_empty() && lines[0].get_action() == &Action::Noop;
self.lines = if self.is_noop {
vec![]
}
else {
lines.into_iter().filter(|l| l.get_action() != &Action::Noop).collect()
};
if self.selected_line_index >= self.lines.len() {
self.selected_line_index = if self.lines.is_empty() { 0 } else { self.lines.len() - 1 };
}
self.version.reset();
self.history.reset();
}
/// Load the rebase file from disk.
///
/// # Errors
///
/// Returns error if the file cannot be read.
pub(crate) fn load_file(&mut self) -> Result<(), IoError> {
let lines: Result<Vec<Line>, IoError> = read_to_string(self.filepath.as_path())
.map_err(|err| {
IoError::FileRead {
file: self.filepath.clone(),
cause: FileReadErrorCause::from(err),
}
})?
.lines()
.filter_map(|l| {
if l.starts_with(self.options.comment_prefix.as_str()) || l.is_empty() {
None
}
else {
Some(Line::parse(l).map_err(|err| {
IoError::FileRead {
file: self.filepath.clone(),
cause: FileReadErrorCause::from(err),
}
}))
}
})
.collect();
self.set_lines(lines?);
Ok(())
}
/// Write the rebase file to disk.
/// # Errors
///
/// Returns error if the file cannot be written.
pub(crate) fn write_file(&self) -> Result<(), IoError> {
let mut file = File::create(&self.filepath).map_err(|err| {
IoError::FileRead {
file: self.filepath.clone(),
cause: FileReadErrorCause::from(err),
}
})?;
let file_contents = if self.is_noop {
String::from("noop")
}
else {
self.lines
.iter()
.flat_map(|l| {
let mut