gitstack 5.3.0

Git history viewer with insights - Author stats, file heatmap, code ownership
Documentation
//! Graph layout definitions

use super::cell::GraphCell;
use crate::event::GitEvent;

/// Data for a single row of the graph
#[derive(Debug, Clone)]
pub struct GraphRow {
    /// Event information for this commit (None for connector rows)
    pub event: Option<GitEvent>,
    /// Column position of this commit
    pub column: usize,
    /// Color index
    pub color_idx: usize,
    /// Graph cell array (rendering content for each column from left to right)
    pub cells: Vec<GraphCell>,
    /// Whether this is HEAD
    pub is_head: bool,
    /// List of branch names
    pub branch_names: Vec<String>,
}

impl GraphRow {
    /// Create a new GraphRow
    pub fn new(
        event: Option<GitEvent>,
        column: usize,
        color_idx: usize,
        cells: Vec<GraphCell>,
    ) -> Self {
        Self {
            event,
            column,
            color_idx,
            cells,
            is_head: false,
            branch_names: Vec::new(),
        }
    }

    /// Set the HEAD flag
    pub fn with_head(mut self, is_head: bool) -> Self {
        self.is_head = is_head;
        self
    }

    /// Set branch names
    pub fn with_branches(mut self, names: Vec<String>) -> Self {
        self.branch_names = names;
        self
    }
}

/// Layout of the entire graph
#[derive(Debug, Clone)]
pub struct GraphLayout {
    /// Row data
    pub rows: Vec<GraphRow>,
    /// Maximum column count
    pub max_column: usize,
}

impl GraphLayout {
    /// Create an empty layout
    pub fn empty() -> Self {
        Self {
            rows: Vec::new(),
            max_column: 0,
        }
    }

    /// Get the number of rows
    pub fn len(&self) -> usize {
        self.rows.len()
    }

    /// Check if empty
    pub fn is_empty(&self) -> bool {
        self.rows.is_empty()
    }

    /// Get the row at the specified index
    pub fn get(&self, idx: usize) -> Option<&GraphRow> {
        self.rows.get(idx)
    }

    /// Get an iterator
    pub fn iter(&self) -> impl Iterator<Item = &GraphRow> {
        self.rows.iter()
    }
}

impl Default for GraphLayout {
    fn default() -> Self {
        Self::empty()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_graph_row_new() {
        let row = GraphRow::new(None, 0, 1, vec![GraphCell::Empty]);
        assert!(row.event.is_none());
        assert_eq!(row.column, 0);
        assert_eq!(row.color_idx, 1);
        assert!(!row.is_head);
    }

    #[test]
    fn test_graph_row_with_head() {
        let row = GraphRow::new(None, 0, 0, vec![]).with_head(true);
        assert!(row.is_head);
    }

    #[test]
    fn test_graph_row_with_branches() {
        let row = GraphRow::new(None, 0, 0, vec![]).with_branches(vec!["main".to_string()]);
        assert_eq!(row.branch_names, vec!["main"]);
    }

    #[test]
    fn test_graph_layout_empty() {
        let layout = GraphLayout::empty();
        assert!(layout.is_empty());
        assert_eq!(layout.len(), 0);
        assert_eq!(layout.max_column, 0);
    }

    #[test]
    fn test_graph_layout_get() {
        let mut layout = GraphLayout::empty();
        layout.rows.push(GraphRow::new(None, 0, 0, vec![]));
        assert!(layout.get(0).is_some());
        assert!(layout.get(1).is_none());
    }
}