pub mod formatting;
pub mod grid;
pub mod grouping;
pub mod render;
pub mod types;
use std::{
collections::HashMap,
io::{self, Write}
};
use masterror::AppResult;
use owo_colors::OwoColorize;
use terminal_size::{Width, terminal_size};
pub use self::{
grid::{calculate_columns, render_grid},
render::render_file_block
};
use super::types::{DiffEntry, DiffResult};
use crate::error::IoError;
pub fn show_summary(result: &DiffResult, color: bool) {
if color {
println!("\n{}\n", "DIFF SUMMARY".bold());
} else {
println!("\nDIFF SUMMARY\n");
}
for file in &result.files {
if color {
println!("{}:", file.path.cyan().bold());
} else {
println!("{}:", file.path);
}
let mut analyzer_counts = HashMap::new();
for entry in &file.entries {
*analyzer_counts.entry(&entry.analyzer).or_insert(0) += 1;
}
for (analyzer, count) in analyzer_counts {
if color {
println!(
" {}: {} {}",
analyzer.green(),
count,
if count == 1 { "issue" } else { "issues" }
);
} else {
println!(
" {}: {} {}",
analyzer,
count,
if count == 1 { "issue" } else { "issues" }
);
}
}
println!();
}
let summary = format!(
"Total: {} changes in {} files",
result.total_changes(),
result.total_files()
);
if color {
println!("{}", summary.yellow().bold());
} else {
println!("{}", summary);
}
}
pub fn show_full(result: &DiffResult, color: bool) {
if color {
println!("\n{}\n", "DIFF OUTPUT".bold());
} else {
println!("\nDIFF OUTPUT\n");
}
let term_width = terminal_size()
.map(|(Width(w), _)| w as usize)
.unwrap_or(80);
let rendered: Vec<_> = result
.files
.iter()
.map(|f| render_file_block(f, color))
.collect();
let columns = calculate_columns(&rendered, term_width);
if columns > 1 {
let layout_info = format!(
"Layout: {} columns (terminal width: {})",
columns, term_width
);
if color {
println!("{}\n", layout_info.dimmed());
} else {
println!("{}\n", layout_info);
}
}
render_grid(&rendered, columns);
let summary = format!(
"Total: {} changes in {} files",
result.total_changes(),
result.total_files()
);
if color {
println!("{}", summary.yellow().bold());
} else {
println!("{}", summary);
}
}
pub fn show_interactive(result: &DiffResult, color: bool) -> AppResult<Vec<DiffEntry>> {
let mut selected = Vec::with_capacity(result.total_changes());
let mut apply_all = false;
if color {
println!("\n{}\n", "INTERACTIVE DIFF".bold());
println!("{}", "Commands: y=yes, n=no, a=all, q=quit\n".dimmed());
} else {
println!("\nINTERACTIVE DIFF\n");
println!("Commands: y=yes, n=no, a=all, q=quit\n");
}
for file in &result.files {
if color {
println!("{}", format!("File: {}", file.path).cyan().bold());
} else {
println!("File: {}", file.path);
}
println!();
for (idx, entry) in file.entries.iter().enumerate() {
if color {
println!(
"{} {}",
format!("[{}/{}]", idx + 1, file.entries.len()).yellow(),
entry.analyzer.green()
);
println!("{}", format!("Line {}:", entry.line).dimmed());
println!("{}", format!("- {}", entry.original).red());
if let Some(import) = &entry.import {
println!("{}", format!("+ {}", import).green());
}
println!("{}", format!("+ {}", entry.modified).green());
} else {
println!("[{}/{}] {}", idx + 1, file.entries.len(), entry.analyzer);
println!("Line {}:", entry.line);
println!("- {}", entry.original);
if let Some(import) = &entry.import {
println!("+ {}", import);
}
println!("+ {}", entry.modified);
}
println!();
if apply_all {
selected.push(entry.clone());
continue;
}
print!("{}", "Apply this fix? [y/n/a/q]: ".bold());
io::stdout().flush().map_err(IoError::from)?;
let mut input = String::new();
io::stdin().read_line(&mut input).map_err(IoError::from)?;
match input.trim().to_lowercase().as_str() {
"y" | "yes" => {
selected.push(entry.clone());
println!("{}", "Applied".green());
}
"n" | "no" => {
println!("{}", "Skipped".yellow());
}
"a" | "all" => {
apply_all = true;
selected.push(entry.clone());
println!("{}", "Applying all remaining changes".green().bold());
}
"q" | "quit" => {
println!("{}", "Quit".red());
break;
}
_ => {
println!("{}", "Invalid input, skipping".red());
}
}
println!();
}
}
println!(
"\n{}",
format!("Selected {} changes for application", selected.len())
.yellow()
.bold()
);
Ok(selected)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::differ::types::FileDiff;
#[test]
fn test_show_summary_empty() {
let result = DiffResult::new();
show_summary(&result, false);
}
#[test]
fn test_show_full_empty() {
let result = DiffResult::new();
show_full(&result, false);
}
#[test]
fn test_show_summary_with_data() {
let mut result = DiffResult::new();
let mut file = FileDiff::new("test.rs".to_string());
file.add_entry(DiffEntry {
line: 1,
analyzer: "test".to_string(),
original: "old".to_string(),
modified: "new".to_string(),
description: "desc".to_string(),
import: None
});
result.add_file(file);
show_summary(&result, false);
}
#[test]
fn test_show_full_with_data() {
let mut result = DiffResult::new();
let mut file = FileDiff::new("test.rs".to_string());
file.add_entry(DiffEntry {
line: 10,
analyzer: "test".to_string(),
original: "old".to_string(),
modified: "new".to_string(),
description: "desc".to_string(),
import: None
});
result.add_file(file);
show_full(&result, false);
}
}