use super::id::{ChangeId, CommitId};
#[derive(Debug, Default, Clone)]
pub struct DiffContent {
pub commit_id: CommitId,
pub author: String,
pub timestamp: String,
pub description: String,
pub lines: Vec<DiffLine>,
}
impl DiffContent {
pub fn has_changes(&self) -> bool {
self.lines
.iter()
.any(|l| l.kind == DiffLineKind::FileHeader)
}
#[cfg(test)]
pub fn file_count(&self) -> usize {
self.lines
.iter()
.filter(|l| l.kind == DiffLineKind::FileHeader)
.count()
}
}
#[derive(Debug, Clone)]
pub struct DiffLine {
pub kind: DiffLineKind,
pub line_numbers: Option<(Option<usize>, Option<usize>)>,
pub content: String,
pub file_op: Option<FileOperation>,
}
impl DiffLine {
pub fn file_header(path: impl Into<String>) -> Self {
Self {
kind: DiffLineKind::FileHeader,
line_numbers: None,
content: path.into(),
file_op: None,
}
}
pub fn file_header_with_op(path: impl Into<String>, op: FileOperation) -> Self {
Self {
kind: DiffLineKind::FileHeader,
line_numbers: None,
content: path.into(),
file_op: Some(op),
}
}
pub fn separator() -> Self {
Self {
kind: DiffLineKind::Separator,
line_numbers: None,
content: String::new(),
file_op: None,
}
}
#[cfg(test)]
pub fn context(
old_line: Option<usize>,
new_line: Option<usize>,
content: impl Into<String>,
) -> Self {
Self {
kind: DiffLineKind::Context,
line_numbers: Some((old_line, new_line)),
content: content.into(),
file_op: None,
}
}
#[cfg(test)]
pub fn added(new_line: usize, content: impl Into<String>) -> Self {
Self {
kind: DiffLineKind::Added,
line_numbers: Some((None, Some(new_line))),
content: content.into(),
file_op: None,
}
}
#[cfg(test)]
pub fn deleted(old_line: usize, content: impl Into<String>) -> Self {
Self {
kind: DiffLineKind::Deleted,
line_numbers: Some((Some(old_line), None)),
content: content.into(),
file_op: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiffLineKind {
FileHeader,
Context,
Added,
Deleted,
Separator,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileOperation {
Added,
Modified,
Deleted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DiffDisplayFormat {
#[default]
ColorWords,
Stat,
Git,
}
impl DiffDisplayFormat {
pub fn next(self) -> Self {
match self {
Self::ColorWords => Self::Stat,
Self::Stat => Self::Git,
Self::Git => Self::ColorWords,
}
}
pub fn label(&self) -> &'static str {
match self {
Self::ColorWords => "color-words",
Self::Stat => "stat",
Self::Git => "git",
}
}
pub fn position(&self) -> usize {
match self {
Self::ColorWords => 1,
Self::Stat => 2,
Self::Git => 3,
}
}
pub const COUNT: usize = 3;
}
#[derive(Debug, Clone)]
pub struct CompareRevisionInfo {
pub change_id: ChangeId,
pub commit_id: CommitId,
pub bookmarks: Vec<String>,
pub author: String,
pub timestamp: String,
pub description: String,
}
#[derive(Debug, Clone)]
pub struct CompareInfo {
pub from: CompareRevisionInfo,
pub to: CompareRevisionInfo,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_diff_content_default() {
let content = DiffContent::default();
assert!(content.commit_id.is_empty());
assert!(content.lines.is_empty());
assert!(!content.has_changes());
assert_eq!(content.file_count(), 0);
}
#[test]
fn test_diff_content_has_changes() {
let mut content = DiffContent::default();
assert!(!content.has_changes());
content.lines.push(DiffLine::file_header("src/main.rs"));
assert!(content.has_changes());
}
#[test]
fn test_diff_content_file_count() {
let mut content = DiffContent::default();
assert_eq!(content.file_count(), 0);
content.lines.push(DiffLine::file_header("src/main.rs"));
content.lines.push(DiffLine::added(1, "fn main() {}"));
content.lines.push(DiffLine::separator());
content.lines.push(DiffLine::file_header("src/lib.rs"));
content.lines.push(DiffLine::added(1, "pub fn hello() {}"));
assert_eq!(content.file_count(), 2);
}
#[test]
fn test_diff_line_file_header() {
let line = DiffLine::file_header("src/main.rs");
assert_eq!(line.kind, DiffLineKind::FileHeader);
assert!(line.line_numbers.is_none());
assert_eq!(line.content, "src/main.rs");
}
#[test]
fn test_diff_line_separator() {
let line = DiffLine::separator();
assert_eq!(line.kind, DiffLineKind::Separator);
assert!(line.line_numbers.is_none());
assert!(line.content.is_empty());
}
#[test]
fn test_diff_line_context() {
let line = DiffLine::context(Some(10), Some(10), " fn main() {");
assert_eq!(line.kind, DiffLineKind::Context);
assert_eq!(line.line_numbers, Some((Some(10), Some(10))));
assert_eq!(line.content, " fn main() {");
}
#[test]
fn test_diff_line_added() {
let line = DiffLine::added(11, " println!(\"new\");");
assert_eq!(line.kind, DiffLineKind::Added);
assert_eq!(line.line_numbers, Some((None, Some(11))));
assert_eq!(line.content, " println!(\"new\");");
}
#[test]
fn test_diff_line_deleted() {
let line = DiffLine::deleted(11, " println!(\"old\");");
assert_eq!(line.kind, DiffLineKind::Deleted);
assert_eq!(line.line_numbers, Some((Some(11), None)));
assert_eq!(line.content, " println!(\"old\");");
}
#[test]
fn test_diff_line_kind_equality() {
assert_eq!(DiffLineKind::FileHeader, DiffLineKind::FileHeader);
assert_ne!(DiffLineKind::FileHeader, DiffLineKind::Added);
assert_ne!(DiffLineKind::Added, DiffLineKind::Deleted);
}
#[test]
fn test_display_format_default() {
let fmt = DiffDisplayFormat::default();
assert_eq!(fmt, DiffDisplayFormat::ColorWords);
}
#[test]
fn test_display_format_cycle() {
let fmt = DiffDisplayFormat::ColorWords;
assert_eq!(fmt.next(), DiffDisplayFormat::Stat);
assert_eq!(fmt.next().next(), DiffDisplayFormat::Git);
assert_eq!(fmt.next().next().next(), DiffDisplayFormat::ColorWords);
}
#[test]
fn test_display_format_labels() {
assert_eq!(DiffDisplayFormat::ColorWords.label(), "color-words");
assert_eq!(DiffDisplayFormat::Stat.label(), "stat");
assert_eq!(DiffDisplayFormat::Git.label(), "git");
}
#[test]
fn test_display_format_positions() {
assert_eq!(DiffDisplayFormat::ColorWords.position(), 1);
assert_eq!(DiffDisplayFormat::Stat.position(), 2);
assert_eq!(DiffDisplayFormat::Git.position(), 3);
assert_eq!(DiffDisplayFormat::COUNT, 3);
}
#[test]
fn test_diff_line_file_header_with_op() {
let line = DiffLine::file_header_with_op("src/main.rs", FileOperation::Modified);
assert_eq!(line.kind, DiffLineKind::FileHeader);
assert_eq!(line.content, "src/main.rs");
assert_eq!(line.file_op, Some(FileOperation::Modified));
}
#[test]
fn test_diff_line_file_header_no_op() {
let line = DiffLine::file_header("src/main.rs");
assert_eq!(line.kind, DiffLineKind::FileHeader);
assert_eq!(line.content, "src/main.rs");
assert_eq!(line.file_op, None);
}
}