sl-up 0.1.1

An interactive TUI smartlog for Sapling SCM, like git-sl-up but written in Rust.
Documentation
use crate::{
    graph::{Item, ItemType},
    parser::SmartLogParser,
};

#[derive(Debug)]
pub struct SmartLog {
    pub items: Vec<ItemType>,
    selection_idx: usize,
}

impl SmartLog {
    pub fn new(raw_lines: &[String]) -> Self {
        let items = SmartLogParser::parse(raw_lines).unwrap();
        let selection_idx = Self::get_selected_item_index(&items).unwrap();
        Self {
            items,
            selection_idx,
        }
    }

    pub fn get_selected_commit_hash(&self) -> Option<&str> {
        let item = self.items.get(self.selection_idx).unwrap();
        if let ItemType::Commit(commit) = item {
            return commit.hash();
        }
        None
    }

    pub fn move_up(&mut self) {
        if self.selection_idx > 0 {
            let mut selection_candidate = self.selection_idx;
            for i in (0..self.selection_idx).rev() {
                if let ItemType::Commit(_) = self.items[i] {
                    selection_candidate = i;
                    break;
                }
            }
            if selection_candidate == self.selection_idx {
                return;
            }

            self.deselect_line_idx(self.selection_idx);
            self.select_line_index(selection_candidate);
        }
    }

    pub fn move_down(&mut self) {
        if self.selection_idx < self.items.len() - 1 {
            let mut selection_candidate = self.selection_idx;
            for i in (self.selection_idx + 1)..self.items.len() {
                if let ItemType::Commit(_) = self.items[i] {
                    selection_candidate = i;
                    break;
                }
            }
            if selection_candidate == self.selection_idx {
                return;
            }

            self.deselect_line_idx(self.selection_idx);
            self.select_line_index(selection_candidate);
        }
    }

    pub fn to_string_vec(&self) -> Vec<String> {
        self.items
            .iter()
            .flat_map(|item| item.to_string_vec())
            .collect()
    }

    pub fn select_line_index(&mut self, item_idx: usize) {
        let item = self.items.get_mut(item_idx).unwrap();
        if let ItemType::Commit(commit) = item {
            commit.select();
            self.selection_idx = item_idx;
        }
    }

    pub fn deselect_line_idx(&mut self, item_idx: usize) {
        let item = self.items.get_mut(item_idx).unwrap();
        if let ItemType::Commit(commit) = item {
            commit.deselect();
        }
    }

    fn get_selected_item_index(items: &[ItemType]) -> Option<usize> {
        for (idx, item) in items.iter().enumerate() {
            if let ItemType::Commit(commit) = item {
                if commit.selected {
                    return Some(idx);
                }
            }
        }
        None
    }
}