diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 51207624..9fd2a5f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,7 +52,7 @@ We use [GitHub Actions](https://github.com/lusingander/serie/blob/master/.github Improvements to the commit graph are welcome. -Tests for the commit graph are conducted in [./tests/graph.rs](./tests/graph.rs). +Tests for the commit graph are conducted in [./src/tests/graph.rs](./src/tests/graph.rs). Running the tests will output images and the test repository to `./out/graph`. If you add new test cases, please add these images under `./tests/graph/`. diff --git a/src/git.rs b/src/git.rs index d83d98bb..02711614 100644 --- a/src/git.rs +++ b/src/git.rs @@ -29,13 +29,6 @@ impl From<&str> for CommitHash { } } -#[derive(Debug, Default, Clone)] -pub enum CommitType { - #[default] - Commit, - Stash, -} - #[derive(Debug, Default, Clone)] pub struct Commit { pub commit_hash: CommitHash, @@ -48,7 +41,6 @@ pub struct Commit { pub subject: String, pub body: String, pub parent_commit_hashes: Vec, - pub commit_type: CommitType, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -325,7 +317,6 @@ fn load_all_commits( subject: parts[7].into(), body: parts[8].into(), parent_commit_hashes: parse_parent_commit_hashes(parts[9]), - commit_type: CommitType::Commit, }; commits.push(commit); @@ -375,7 +366,6 @@ fn load_all_stashes(path: &Path, mailmap: bool) -> Vec { subject: parts[7].into(), body: parts[8].into(), parent_commit_hashes: parse_parent_commit_hashes(parts[9]), - commit_type: CommitType::Stash, }; commits.push(commit); diff --git a/src/graph/image.rs b/src/graph/image.rs index 0d3326da..be2678fd 100644 --- a/src/graph/image.rs +++ b/src/graph/image.rs @@ -109,11 +109,6 @@ impl<'a> GraphImageManager<'a> { } } -#[derive(Debug, Default)] -pub struct GraphImage { - pub images: FxHashMap, GraphRowImage>, -} - pub struct GraphRowImage { pub bytes: Vec, pub cell_count: usize, diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 57f762c4..00000000 --- a/src/lib.rs +++ /dev/null @@ -1,214 +0,0 @@ -pub mod color; -pub mod config; -pub mod git; -pub mod graph; -pub mod protocol; - -mod app; -mod check; -mod event; -mod external; -mod keybind; -mod view; -mod widget; - -use std::{path::Path, rc::Rc}; - -use app::{App, Ret}; -use clap::{Parser, ValueEnum}; -use graph::GraphImageManager; -use serde::Deserialize; - -/// Serie - A rich git commit graph in your terminal, like magic 📚 -#[derive(Parser)] -#[command(version)] -struct Args { - /// Maximum number of commits to render - #[arg(short = 'n', long, value_name = "NUMBER")] - max_count: Option, - - /// Image protocol to render graph [default: auto] - #[arg(short, long, value_name = "TYPE")] - protocol: Option, - - /// Commit ordering algorithm [default: chrono] - #[arg(short, long, value_name = "TYPE")] - order: Option, - - /// Commit graph image cell width [default: auto] - #[arg(short, long, value_name = "TYPE")] - graph_width: Option, - - /// Commit graph image edge style [default: rounded] - #[arg(short = 's', long, value_name = "TYPE")] - graph_style: Option, - - /// Initial selection of commit [default: latest] - #[arg(short, long, value_name = "TYPE")] - initial_selection: Option, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum ImageProtocolType { - Auto, - Iterm, - Kitty, - KittyUnicode, -} - -impl From> for protocol::ImageProtocol { - fn from(protocol: Option) -> Self { - match protocol { - Some(ImageProtocolType::Auto) => protocol::auto_detect(), - Some(ImageProtocolType::Iterm) => protocol::ImageProtocol::Iterm2, - Some(ImageProtocolType::Kitty) => protocol::ImageProtocol::Kitty, - Some(ImageProtocolType::KittyUnicode) => protocol::ImageProtocol::KittyUnicode { - tmux: protocol::detect_tmux(), - }, - None => protocol::auto_detect(), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum CommitOrderType { - Chrono, - Topo, -} - -impl From> for git::SortCommit { - fn from(order: Option) -> Self { - match order { - Some(CommitOrderType::Chrono) => git::SortCommit::Chronological, - Some(CommitOrderType::Topo) => git::SortCommit::Topological, - None => git::SortCommit::Chronological, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum GraphWidthType { - Auto, - Double, - Single, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum GraphStyle { - Rounded, - Angular, -} - -impl From> for graph::GraphStyle { - fn from(style: Option) -> Self { - match style { - Some(GraphStyle::Rounded) => graph::GraphStyle::Rounded, - Some(GraphStyle::Angular) => graph::GraphStyle::Angular, - None => graph::GraphStyle::Rounded, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum InitialSelection { - Latest, - Head, -} - -impl From> for app::InitialSelection { - fn from(selection: Option) -> Self { - match selection { - Some(InitialSelection::Latest) => app::InitialSelection::Latest, - Some(InitialSelection::Head) => app::InitialSelection::Head, - None => app::InitialSelection::Latest, - } - } -} - -pub type Result = std::result::Result>; - -pub fn run() -> Result<()> { - let args = Args::parse(); - let (core_config, ui_config, graph_config, color_theme, keybind_patch) = config::load()?; - let keybind = keybind::KeyBind::new(keybind_patch); - - let max_count = args.max_count; - let image_protocol = args.protocol.or(core_config.option.protocol).into(); - let order = args.order.or(core_config.option.order).into(); - let graph_width = args.graph_width.or(core_config.option.graph_width); - let graph_style = args.graph_style.or(core_config.option.graph_style).into(); - let graph_image_width_mode = graph_config.row_image_width; - let initial_selection = args - .initial_selection - .or(core_config.option.initial_selection) - .into(); - let mailmap = core_config.git.mailmap; - - let graph_color_set = color::GraphColorSet::new(&graph_config.color); - - let ctx = Rc::new(app::AppContext { - keybind, - core_config, - ui_config, - color_theme, - image_protocol, - }); - - let ec = event::EventController::init(); - let mut refresh_view_context = None; - let mut terminal = None; - - let ret = loop { - let repository = git::Repository::load(Path::new("."), order, max_count, mailmap)?; - - let graph = graph::calc_graph(&repository); - - let cell_width_type = check::decide_cell_width_type(&graph, graph_width)?; - - let graph_image_manager = GraphImageManager::new( - &graph, - &graph_color_set, - cell_width_type, - graph_style, - graph_image_width_mode, - image_protocol, - ); - - if terminal.is_none() { - terminal = Some(ratatui::init()); - } - - let mut app = App::new( - &repository, - graph_image_manager, - &graph, - &graph_color_set, - cell_width_type, - initial_selection, - ctx.clone(), - &ec, - refresh_view_context, - ); - - match app.run(terminal.as_mut().unwrap()) { - Ok(Ret::Quit) => { - break Ok(()); - } - Ok(Ret::Refresh(request)) => { - refresh_view_context = Some(request.context); - continue; - } - Err(e) => { - break Err(e); - } - } - }; - - ratatui::restore(); - ret.map_err(Into::into) -} diff --git a/src/main.rs b/src/main.rs index 5ab78634..31e3d0d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,221 @@ -fn main() -> serie::Result<()> { - serie::run() +mod app; +mod check; +mod color; +mod config; +mod event; +mod external; +mod git; +mod graph; +mod keybind; +mod protocol; +mod view; +mod widget; + +#[cfg(test)] +#[path = "tests/graph.rs"] +mod graph_tests; + +#[cfg(test)] +#[path = "tests/mailmap.rs"] +mod mailmap_tests; + +use std::{path::Path, rc::Rc}; + +use app::{App, Ret}; +use clap::{Parser, ValueEnum}; +use graph::GraphImageManager; +use serde::Deserialize; + +/// Serie - A rich git commit graph in your terminal, like magic 📚 +#[derive(Parser)] +#[command(version)] +struct Args { + /// Maximum number of commits to render + #[arg(short = 'n', long, value_name = "NUMBER")] + max_count: Option, + + /// Image protocol to render graph [default: auto] + #[arg(short, long, value_name = "TYPE")] + protocol: Option, + + /// Commit ordering algorithm [default: chrono] + #[arg(short, long, value_name = "TYPE")] + order: Option, + + /// Commit graph image cell width [default: auto] + #[arg(short, long, value_name = "TYPE")] + graph_width: Option, + + /// Commit graph image edge style [default: rounded] + #[arg(short = 's', long, value_name = "TYPE")] + graph_style: Option, + + /// Initial selection of commit [default: latest] + #[arg(short, long, value_name = "TYPE")] + initial_selection: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] +#[serde(rename_all = "kebab-case")] +enum ImageProtocolType { + Auto, + Iterm, + Kitty, + KittyUnicode, +} + +impl From> for protocol::ImageProtocol { + fn from(protocol: Option) -> Self { + match protocol { + Some(ImageProtocolType::Auto) => protocol::auto_detect(), + Some(ImageProtocolType::Iterm) => protocol::ImageProtocol::Iterm2, + Some(ImageProtocolType::Kitty) => protocol::ImageProtocol::Kitty, + Some(ImageProtocolType::KittyUnicode) => protocol::ImageProtocol::KittyUnicode { + tmux: protocol::detect_tmux(), + }, + None => protocol::auto_detect(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] +#[serde(rename_all = "lowercase")] +enum CommitOrderType { + Chrono, + Topo, +} + +impl From> for git::SortCommit { + fn from(order: Option) -> Self { + match order { + Some(CommitOrderType::Chrono) => git::SortCommit::Chronological, + Some(CommitOrderType::Topo) => git::SortCommit::Topological, + None => git::SortCommit::Chronological, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] +#[serde(rename_all = "lowercase")] +enum GraphWidthType { + Auto, + Double, + Single, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] +#[serde(rename_all = "lowercase")] +enum GraphStyle { + Rounded, + Angular, +} + +impl From> for graph::GraphStyle { + fn from(style: Option) -> Self { + match style { + Some(GraphStyle::Rounded) => graph::GraphStyle::Rounded, + Some(GraphStyle::Angular) => graph::GraphStyle::Angular, + None => graph::GraphStyle::Rounded, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] +#[serde(rename_all = "lowercase")] +enum InitialSelection { + Latest, + Head, +} + +impl From> for app::InitialSelection { + fn from(selection: Option) -> Self { + match selection { + Some(InitialSelection::Latest) => app::InitialSelection::Latest, + Some(InitialSelection::Head) => app::InitialSelection::Head, + None => app::InitialSelection::Latest, + } + } +} + +type Result = std::result::Result>; + +fn main() -> Result<()> { + let args = Args::parse(); + let (core_config, ui_config, graph_config, color_theme, keybind_patch) = config::load()?; + let keybind = keybind::KeyBind::new(keybind_patch); + + let max_count = args.max_count; + let image_protocol = args.protocol.or(core_config.option.protocol).into(); + let order = args.order.or(core_config.option.order).into(); + let graph_width = args.graph_width.or(core_config.option.graph_width); + let graph_style = args.graph_style.or(core_config.option.graph_style).into(); + let graph_image_width_mode = graph_config.row_image_width; + let initial_selection = args + .initial_selection + .or(core_config.option.initial_selection) + .into(); + let mailmap = core_config.git.mailmap; + + let graph_color_set = color::GraphColorSet::new(&graph_config.color); + + let ctx = Rc::new(app::AppContext { + keybind, + core_config, + ui_config, + color_theme, + image_protocol, + }); + + let ec = event::EventController::init(); + let mut refresh_view_context = None; + let mut terminal = None; + + let ret = loop { + let repository = git::Repository::load(Path::new("."), order, max_count, mailmap)?; + + let graph = graph::calc_graph(&repository); + + let cell_width_type = check::decide_cell_width_type(&graph, graph_width)?; + + let graph_image_manager = GraphImageManager::new( + &graph, + &graph_color_set, + cell_width_type, + graph_style, + graph_image_width_mode, + image_protocol, + ); + + if terminal.is_none() { + terminal = Some(ratatui::init()); + } + + let mut app = App::new( + &repository, + graph_image_manager, + &graph, + &graph_color_set, + cell_width_type, + initial_selection, + ctx.clone(), + &ec, + refresh_view_context, + ); + + match app.run(terminal.as_mut().unwrap()) { + Ok(Ret::Quit) => { + break Ok(()); + } + Ok(Ret::Refresh(request)) => { + refresh_view_context = Some(request.context); + continue; + } + Err(e) => { + break Err(e); + } + } + }; + + ratatui::restore(); + ret.map_err(Into::into) } diff --git a/src/protocol.rs b/src/protocol.rs index 3c13309d..bb6897ab 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -67,7 +67,6 @@ impl PreparedImageCell { #[derive(Debug, Clone)] pub struct PreparedImage { cells: Vec, - cell_width: usize, upload_data: Option, } @@ -76,10 +75,6 @@ impl PreparedImage { &self.cells } - pub fn cell_width(&self) -> usize { - self.cell_width - } - pub fn take_upload_data(&mut self) -> Option { self.upload_data.take() } @@ -109,7 +104,6 @@ impl ImageProtocol { } PreparedImage { cells, - cell_width, upload_data: None, } } @@ -516,7 +510,6 @@ fn kitty_unicode_prepare( PreparedImage { cells, - cell_width, upload_data: Some(upload_symbol), } } diff --git a/tests/graph.rs b/src/tests/graph.rs similarity index 98% rename from tests/graph.rs rename to src/tests/graph.rs index 04021cf2..d4549317 100644 --- a/tests/graph.rs +++ b/src/tests/graph.rs @@ -2,8 +2,12 @@ use std::{path::Path, process::Command}; use chrono::{DateTime, Days, NaiveDate, TimeZone, Utc}; use image::{GenericImage, GenericImageView}; -use rustc_hash::FxHashSet; -use serie::{color, config, git, graph}; +use rustc_hash::{FxHashMap, FxHashSet}; + +use crate::{ + color, config, git, + graph::{self, Edge, GraphRowImage}, +}; type TestResult = Result<(), Box>; @@ -1400,13 +1404,13 @@ fn generate_and_output_graph_image>(path: P, option: &GenerateGra // Create concatenated image let (width, height) = (50, 50); - let image_width = ((width * (graph.max_pos_x as usize + 1)) + (width * 7)) as u32; + let image_width = ((width * (graph.max_pos_x + 1)) + (width * 7)) as u32; let image_height = (height * graph.commits.len()) as u32; let mut img_buf: image::ImageBuffer, Vec> = image::ImageBuffer::new(image_width, image_height); let text_renderer = text_to_png::TextRenderer::default(); - let text_x = (width * (graph.max_pos_x as usize + 1)) as u32; + let text_x = (width * (graph.max_pos_x + 1)) as u32; for (i, edges) in graph.edges.iter().enumerate() { let y = (height * i) as u32; @@ -1455,12 +1459,17 @@ fn generate_and_output_graph_image>(path: P, option: &GenerateGra .unwrap(); } +#[derive(Debug, Default)] +pub struct GraphImage { + pub images: FxHashMap, GraphRowImage>, +} + fn build_graph_image( graph: &graph::Graph<'_>, image_params: &graph::ImageParams, drawing_pixels: &graph::DrawingPixels, graph_style: graph::GraphStyle, -) -> graph::GraphImage { +) -> GraphImage { let graph_row_sources: FxHashSet<(usize, &Vec)> = graph .commits .iter() @@ -1488,7 +1497,7 @@ fn build_graph_image( }) .collect(); - graph::GraphImage { images } + GraphImage { images } } fn create_output_dirs(path: &str) { diff --git a/tests/mailmap.rs b/src/tests/mailmap.rs similarity index 99% rename from tests/mailmap.rs rename to src/tests/mailmap.rs index eb1f6221..878f7a5b 100644 --- a/tests/mailmap.rs +++ b/src/tests/mailmap.rs @@ -1,6 +1,6 @@ use std::{fs, path::Path, process::Command}; -use serie::git::{self, Repository}; +use crate::git::{self, Repository}; type TestResult = Result<(), Box>;