diff --git a/README.md b/README.md index 064ed07..ae75a25 100644 --- a/README.md +++ b/README.md @@ -95,11 +95,12 @@ Serie - A rich git commit graph in your terminal, like magic 📚 Usage: serie [OPTIONS] Options: - -p, --protocol Image protocol to render graph [default: auto] [possible values: auto, iterm, kitty] - -o, --order Commit ordering algorithm [default: chrono] [possible values: chrono, topo] - --preload Preload all graph images - -h, --help Print help - -V, --version Print version + -p, --protocol Image protocol to render graph [default: auto] [possible values: auto, iterm, kitty] + -o, --order Commit ordering algorithm [default: chrono] [possible values: chrono, topo] + -g, --graph-width Commit graph image cell width [possible values: double, single] + --preload Preload all graph images + -h, --help Print help + -V, --version Print version ``` #### -p, --protocol \ @@ -115,11 +116,39 @@ Refer to [Compatibility](#compatibility) for details. `--order chrono` will order commits by commit date if possible. - - `--order topo` will order commits on the same branch consecutively if possible. - +
+Screenshots + + + +`--order chrono` + + + +`--order topo` + +
+ +#### -g, --graph-width \ + +The character width that a graph image unit cell occupies. + +If not specified, `double` will be used automatically if there is enough width to display it, `single` otherwise. + +
+Screenshots + + + +`--graph-width double` + + + +`--graph-width single` + +
#### --preload diff --git a/img/graph-width-double.png b/img/graph-width-double.png new file mode 100644 index 0000000..988f54d Binary files /dev/null and b/img/graph-width-double.png differ diff --git a/img/graph-width-single.png b/img/graph-width-single.png new file mode 100644 index 0000000..1708fd5 Binary files /dev/null and b/img/graph-width-single.png differ diff --git a/img/order-chrono.png b/img/order-chrono.png index 2fc1b49..b08dec7 100644 Binary files a/img/order-chrono.png and b/img/order-chrono.png differ diff --git a/img/order-topo.png b/img/order-topo.png index be0920a..7a98922 100644 Binary files a/img/order-topo.png and b/img/order-topo.png differ diff --git a/src/app.rs b/src/app.rs index 6b3a75c..5b15a4c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -15,7 +15,7 @@ use crate::{ event::{AppEvent, Receiver, Sender, UserEvent}, external::copy_to_clipboard, git::Repository, - graph::{Graph, GraphImageManager}, + graph::{CellWidthType, Graph, GraphImageManager}, keybind::KeyBind, protocol::ImageProtocol, view::View, @@ -61,6 +61,7 @@ impl<'a> App<'a> { keybind: &'a KeyBind, ui_config: &'a UiConfig, color_set: &'a ColorSet, + cell_width_type: CellWidthType, image_protocol: ImageProtocol, tx: Sender, ) -> Self { @@ -79,7 +80,10 @@ impl<'a> App<'a> { CommitInfo::new(commit, refs, graph_color) }) .collect(); - let graph_cell_width = (graph.max_pos_x + 1) as u16 * 2; + let graph_cell_width = match cell_width_type { + CellWidthType::Double => (graph.max_pos_x + 1) as u16 * 2, + CellWidthType::Single => (graph.max_pos_x + 1) as u16, + }; let head = repository.head(); let commit_list_state = CommitListState::new( commits, diff --git a/src/check.rs b/src/check.rs index e9a2835..ed19a7a 100644 --- a/src/check.rs +++ b/src/check.rs @@ -1,13 +1,53 @@ use ratatui::crossterm::terminal; -use crate::graph::Graph; +use crate::graph::{CellWidthType, Graph}; -pub fn term_size(graph: &Graph) -> std::io::Result<()> { +pub fn decide_cell_width_type( + graph: &Graph, + cell_width_type: Option, +) -> std::io::Result { let (w, h) = terminal::size()?; - let image_cell_width = (graph.max_pos_x + 1) * 2; - let required_width = image_cell_width + 2; - if required_width > w as usize { - panic!("Terminal size {w}x{h} is too small. Required width is {required_width}."); + let cell_width_type = + decide_cell_width_type_from(graph.max_pos_x, w as usize, h as usize, cell_width_type); + Ok(cell_width_type) +} + +fn decide_cell_width_type_from( + max_pos_x: usize, + term_width: usize, + term_height: usize, + cell_width_type: Option, +) -> CellWidthType { + let single_image_cell_width = max_pos_x + 1; + let double_image_cell_width = single_image_cell_width * 2; + + match cell_width_type { + Some(CellWidthType::Double) => { + let required_width = double_image_cell_width + 2; + if required_width > term_width { + panic!("Terminal size {term_width}x{term_height} is too small. Required width is {required_width} (graph_width = double)."); + } + CellWidthType::Double + } + Some(CellWidthType::Single) => { + let required_width = single_image_cell_width + 2; + if required_width > term_width { + panic!("Terminal size {term_width}x{term_height} is too small. Required width is {required_width} (graph_width = single)."); + } + CellWidthType::Single + } + None => { + let double_required_width = double_image_cell_width + 2; + if double_required_width <= term_width { + return CellWidthType::Double; + } + let single_required_width = single_image_cell_width + 2; + if single_required_width <= term_width { + return CellWidthType::Single; + } + panic!( + "Terminal size {term_width}x{term_height} is too small. Required width is {single_required_width} (graph_width = single) or {double_required_width} (graph_width = double)." + ); + } } - Ok(()) } diff --git a/src/graph/image.rs b/src/graph/image.rs index 6e6a8b2..4bee140 100644 --- a/src/graph/image.rs +++ b/src/graph/image.rs @@ -18,6 +18,7 @@ pub struct GraphImageManager<'a> { encoded_image_map: FxHashMap, graph: &'a Graph<'a>, + cell_width_type: CellWidthType, image_params: ImageParams, drawing_pixels: DrawingPixels, image_protocol: ImageProtocol, @@ -27,10 +28,11 @@ impl<'a> GraphImageManager<'a> { pub fn new( graph: &'a Graph, options: GraphImageOptions, + cell_width_type: CellWidthType, image_protocol: ImageProtocol, preload: bool, ) -> Self { - let image_params = ImageParams::new(&options.color_set); + let image_params = ImageParams::new(&options.color_set, cell_width_type); let drawing_pixels = DrawingPixels::new(&image_params); let mut m = GraphImageManager { @@ -38,6 +40,7 @@ impl<'a> GraphImageManager<'a> { image_params, drawing_pixels, graph, + cell_width_type, image_protocol, }; if preload { @@ -59,7 +62,8 @@ impl<'a> GraphImageManager<'a> { .enumerate() .map(|(i, commit)| { let edges = &self.graph.edges[i]; - let image = graph_image.images[edges].encode(self.image_protocol); + let image = + graph_image.images[edges].encode(self.cell_width_type, self.image_protocol); (commit.commit_hash.clone(), image) }) .collect() @@ -75,7 +79,7 @@ impl<'a> GraphImageManager<'a> { &self.drawing_pixels, commit_hash, ); - let image = graph_row_image.encode(self.image_protocol); + let image = graph_row_image.encode(self.cell_width_type, self.image_protocol); self.encoded_image_map.insert(commit_hash.clone(), image); } } @@ -102,8 +106,11 @@ impl Debug for GraphRowImage { } impl GraphRowImage { - fn encode(&self, image_protocol: ImageProtocol) -> String { - let image_cell_width = self.cell_count * 2; + fn encode(&self, cell_width_type: CellWidthType, image_protocol: ImageProtocol) -> String { + let image_cell_width = match cell_width_type { + CellWidthType::Double => self.cell_count * 2, + CellWidthType::Single => self.cell_count, + }; image_protocol.encode(&self.bytes, image_cell_width) } } @@ -120,13 +127,19 @@ pub struct ImageParams { background_color: image::Rgba, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CellWidthType { + Double, // 2 cells + Single, +} + impl ImageParams { - pub fn new(color_set: &ColorSet) -> Self { - let width = 50; - let height = 50; - let line_width = 5; - let circle_inner_radius = 10; - let circle_outer_radius = 13; + pub fn new(color_set: &ColorSet, cell_width_type: CellWidthType) -> Self { + let (width, height, line_width, circle_inner_radius, circle_outer_radius) = + match cell_width_type { + CellWidthType::Double => (50, 50, 5, 10, 13), + CellWidthType::Single => (25, 50, 3, 7, 10), + }; let edge_colors = color_set .colors .iter() @@ -696,7 +709,8 @@ mod tests { let cell_count = 4; let graph_color_config = GraphColorConfig::default(); let color_set = ColorSet::new(&graph_color_config); - let image_params = ImageParams::new(&color_set); + let cell_width_type = CellWidthType::Double; + let image_params = ImageParams::new(&color_set, cell_width_type); let drawing_pixels = DrawingPixels::new(&image_params); let file_name = "default_params"; @@ -709,7 +723,8 @@ mod tests { let cell_count = 4; let graph_color_config = GraphColorConfig::default(); let color_set = ColorSet::new(&graph_color_config); - let mut image_params = ImageParams::new(&color_set); + let cell_width_type = CellWidthType::Double; + let mut image_params = ImageParams::new(&color_set, cell_width_type); image_params.width = 100; let drawing_pixels = DrawingPixels::new(&image_params); let file_name = "wide_image"; @@ -723,7 +738,8 @@ mod tests { let cell_count = 4; let graph_color_config = GraphColorConfig::default(); let color_set = ColorSet::new(&graph_color_config); - let mut image_params = ImageParams::new(&color_set); + let cell_width_type = CellWidthType::Double; + let mut image_params = ImageParams::new(&color_set, cell_width_type); image_params.height = 100; let drawing_pixels = DrawingPixels::new(&image_params); let file_name = "tall_image"; @@ -731,13 +747,28 @@ mod tests { test_calc_graph_row_image(params, cell_count, image_params, drawing_pixels, file_name); } + #[test] + fn test_calc_graph_row_image_single_cell_width() { + let params = simple_test_params(); + let cell_count = 4; + let graph_color_config = GraphColorConfig::default(); + let color_set = ColorSet::new(&graph_color_config); + let cell_width_type = CellWidthType::Single; + let image_params = ImageParams::new(&color_set, cell_width_type); + let drawing_pixels = DrawingPixels::new(&image_params); + let file_name = "single_cell_width"; + + test_calc_graph_row_image(params, cell_count, image_params, drawing_pixels, file_name); + } + #[test] fn test_calc_graph_row_image_circle_radius() { let params = straight_test_params(); let cell_count = 2; let graph_color_config = GraphColorConfig::default(); let color_set = ColorSet::new(&graph_color_config); - let mut image_params = ImageParams::new(&color_set); + let cell_width_type = CellWidthType::Double; + let mut image_params = ImageParams::new(&color_set, cell_width_type); image_params.circle_inner_radius = 5; image_params.circle_outer_radius = 12; let drawing_pixels = DrawingPixels::new(&image_params); @@ -752,7 +783,8 @@ mod tests { let cell_count = 2; let graph_color_config = GraphColorConfig::default(); let color_set = ColorSet::new(&graph_color_config); - let mut image_params = ImageParams::new(&color_set); + let cell_width_type = CellWidthType::Double; + let mut image_params = ImageParams::new(&color_set, cell_width_type); image_params.line_width = 1; let drawing_pixels = DrawingPixels::new(&image_params); let file_name = "line_width"; @@ -775,7 +807,8 @@ mod tests { background: "#00ff0070".into(), }; let color_set = ColorSet::new(&graph_color_config); - let image_params = ImageParams::new(&color_set); + let cell_width_type = CellWidthType::Double; + let image_params = ImageParams::new(&color_set, cell_width_type); let drawing_pixels = DrawingPixels::new(&image_params); let file_name = "color"; diff --git a/src/lib.rs b/src/lib.rs index 3a30455..c7fe44e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,10 @@ struct Args { #[arg(short, long, value_name = "TYPE", default_value = "chrono")] order: CommitOrderType, + /// Commit graph image cell width + #[arg(short, long, value_name = "TYPE")] + graph_width: Option, + /// Preload all graph images #[arg(long, default_value = "false")] preload: bool, @@ -68,6 +72,21 @@ impl From for graph::SortCommit { } } +#[derive(Debug, Clone, ValueEnum)] +enum GraphWidthType { + Double, + Single, +} + +impl From for graph::CellWidthType { + fn from(width: GraphWidthType) -> Self { + match width { + GraphWidthType::Double => graph::CellWidthType::Double, + GraphWidthType::Single => graph::CellWidthType::Single, + } + } +} + pub fn run() -> std::io::Result<()> { color_eyre::install().unwrap(); let args = Args::parse(); @@ -81,11 +100,17 @@ pub fn run() -> std::io::Result<()> { let graph = graph::calc_graph(&repository); - check::term_size(&graph)?; + let cell_width_type = + check::decide_cell_width_type(&graph, args.graph_width.map(|w| w.into()))?; let graph_image_options = graph::GraphImageOptions::new(color_set.clone()); - let graph_image_manager = - GraphImageManager::new(&graph, graph_image_options, image_protocol, args.preload); + let graph_image_manager = GraphImageManager::new( + &graph, + graph_image_options, + cell_width_type, + image_protocol, + args.preload, + ); let mut terminal = ratatui::init(); @@ -98,6 +123,7 @@ pub fn run() -> std::io::Result<()> { &key_bind, &ui_config, &color_set, + cell_width_type, image_protocol, tx, ); diff --git a/tests/graph.rs b/tests/graph.rs index 5a645b6..7f1d514 100644 --- a/tests/graph.rs +++ b/tests/graph.rs @@ -2,7 +2,7 @@ use std::{path::Path, process::Command}; use chrono::{DateTime, Days, NaiveDate, TimeZone, Utc}; use image::{GenericImage, GenericImageView}; -use serie::{color, config::GraphColorConfig, git, graph}; +use serie::{color, config, git, graph}; type TestResult = Result<(), Box>; @@ -1076,11 +1076,12 @@ fn generate_and_output_graph_images(repo_path: &Path, options: &[GenerateGraphOp fn generate_and_output_graph_image>(path: P, option: &GenerateGraphOption) { // Build graphs in the same way as application - let graph_color_config = GraphColorConfig::default(); + let graph_color_config = config::GraphColorConfig::default(); let color_set = color::ColorSet::new(&graph_color_config); + let cell_width_type = graph::CellWidthType::Double; let repository = git::Repository::load(path.as_ref(), option.sort); let graph = graph::calc_graph(&repository); - let image_params = graph::ImageParams::new(&color_set); + let image_params = graph::ImageParams::new(&color_set, cell_width_type); let drawing_pixels = graph::DrawingPixels::new(&image_params); let graph_image = graph::build_graph_image(&graph, &image_params, &drawing_pixels);