use enum_dispatch::enum_dispatch;
const LOCAL_COMMIT_HASH_COLOR: &str = "\u{1b}[0;93;1m";
const REMOTE_COMMIT_HASH_COLOR: &str = "\u{1b}[0;33m";
#[derive(Debug)]
pub struct Commit {
lines: Vec<Vec<String>>,
pub selected: bool,
}
impl Commit {
pub fn new(parsed_lines: Vec<Vec<String>>, selected: bool) -> Self {
Self {
lines: parsed_lines,
selected,
}
}
pub fn hash(&self) -> Option<&str> {
let first_line = self.parsed_lines().first().unwrap();
let mut commit_hash_index = 0;
for (index, text) in first_line.iter().enumerate() {
if (text == REMOTE_COMMIT_HASH_COLOR) | (text == LOCAL_COMMIT_HASH_COLOR) {
commit_hash_index = index + 1;
break;
}
}
Some(&first_line[commit_hash_index])
}
pub fn select(&mut self) {
if self.selected {
return;
}
self.selected = true;
for line in self.lines.iter_mut() {
Self::add_selection_color(line);
}
}
pub fn deselect(&mut self) {
if !self.selected {
return;
}
self.selected = false;
for line in self.lines.iter_mut() {
Self::remove_selection_color(line);
}
}
fn add_selection_color(line: &mut Vec<String>) {
if line.len() > 4 {
line.insert(4, Self::selection_formatter());
} else if line.len() > 1 {
line.insert(1, Self::selection_formatter());
} else {
let text = line.pop().unwrap();
for block in text.splitn(2, ' ') {
line.push(block.to_string());
}
line.insert(1, " ".to_string());
line.insert(1, Self::selection_formatter());
}
line.push(Self::stop_formatter());
}
fn remove_selection_color(line: &mut Vec<String>) {
line.retain(|text| !text.contains(&Self::selection_formatter()));
}
fn selection_formatter() -> String {
"\u{1b}[0;35m".to_string()
}
fn stop_formatter() -> String {
"\u{1b}[0m".to_string()
}
}
#[derive(Debug)]
pub struct Glyph {
lines: Vec<Vec<String>>,
}
impl Glyph {
pub fn new(parsed_lines: Vec<Vec<String>>) -> Self {
Self {
lines: parsed_lines,
}
}
}
#[enum_dispatch(ItemType)]
pub trait Item {
fn parsed_lines(&self) -> &Vec<Vec<String>>;
fn add_parsed_line(&mut self, parsed_line: Vec<String>);
fn to_string_vec(&self) -> Vec<String> {
self.parsed_lines()
.iter()
.map(|line| line.join(""))
.collect()
}
}
impl Item for Commit {
fn parsed_lines(&self) -> &Vec<Vec<String>> {
&self.lines
}
fn add_parsed_line(&mut self, parsed_line: Vec<String>) {
self.lines.push(parsed_line);
}
}
impl Item for Glyph {
fn parsed_lines(&self) -> &Vec<Vec<String>> {
&self.lines
}
fn add_parsed_line(&mut self, parsed_line: Vec<String>) {
self.lines.push(parsed_line);
}
}
#[enum_dispatch]
#[derive(Debug)]
pub enum ItemType {
Commit,
Glyph,
}
#[cfg(test)]
mod tests {
use crate::parser::SmartLogParser;
use super::*;
const RAW_LINES: [&str; 15] = [
" @ \u{1b}[0;35m\u{1b}[0;93;1m1cee5d55e\u{1b}[0m\u{1b}[0;35m Dec 08 at 09:46 royrothenberg \u{1b}[0;36m#780 Closed\u{1b}[0m\u{1b}[0;35m \u{1b}[0;31m✗\u{1b}[0m",
" │ \u{1b}[0;35m[pr body update] update stack list without overwriting PR title and body\u{1b}[0m",
" │",
" o \u{1b}[0;93;1mc3bd9e5fa\u{1b}[0m Dec 08 at 09:46 royrothenberg \u{1b}[0;38;2;141;148;158m#779 Unreviewed\u{1b}[0m \u{1b}[0;31m✗\u{1b}[0m",
"╭─╯ [pr body update] fix reviewstack option breaking stack list detection",
"│",
"o \u{1b}[0;33mba27d4d13\u{1b}[0m Dec 07 at 22:20 \u{1b}[0;32mremote/main\u{1b}[0m",
"╷",
"╷ o \u{1b}[0;93;1m2f85065e7\u{1b}[0m Nov 28 at 11:49 royrothenberg \u{1b}[0;36m#781 Closed\u{1b}[0m \u{1b}[0;32m✓\u{1b}[0m",
"╭─╯ [isl] increase width of diff window in split stack edit panel",
"│",
"o \u{1b}[0;33m0e069ab09\u{1b}[0m Nov 21 at 13:16",
"│",
"~",
"",
];
#[test]
fn test_commit() {
let mut commit = Commit::new(vec![vec!["a".to_string()]], true);
commit.add_parsed_line(vec!["b".to_string()]);
assert_eq!(
commit.lines,
vec![vec!["a".to_string()], vec!["b".to_string()]]
);
assert!(commit.selected);
commit.selected = false;
assert!(!commit.selected);
}
#[test]
fn test_glyph() {
let mut glyph = Glyph::new(vec![vec!["a".to_string()]]);
glyph.add_parsed_line(vec!["b".to_string()]);
assert_eq!(
glyph.parsed_lines(),
&vec![vec!["a".to_string()], vec!["b".to_string()]]
);
}
#[test]
fn test_item_types() {
let mut items: Vec<ItemType> = vec![
Commit::new(vec![vec!["a".to_string()]], true).into(),
Glyph::new(vec![vec!["a".to_string()]]).into(),
];
for item in items.iter_mut() {
item.add_parsed_line(vec!["b".to_string()]);
match item {
ItemType::Commit(commit) => {
assert_eq!(
commit.parsed_lines(),
&vec![vec!["a".to_string()], vec!["b".to_string()]]
);
assert!(commit.selected);
commit.selected = false;
assert!(!commit.selected);
}
ItemType::Glyph(glyph) => {
assert_eq!(
glyph.parsed_lines(),
&vec![vec!["a".to_string()], vec!["b".to_string()]]
);
}
}
}
}
#[test]
fn test_select() {
let graph_items = &mut SmartLogParser::parse(&raw_lines()).unwrap();
let commit = &mut graph_items[2];
match commit {
ItemType::Commit(commit) => {
assert!(!commit.selected);
assert_eq!(
commit.parsed_lines()[0][4],
" Dec 08 at 09:46 royrothenberg "
);
assert_eq!(
commit.parsed_lines()[1][1],
" [pr body update] fix reviewstack option breaking stack list detection"
);
commit.select();
assert!(commit.selected);
assert_eq!(
commit.parsed_lines()[0][4],
"\u{1b}[0;35m", );
assert_eq!(
commit.parsed_lines()[1][1],
"\u{1b}[0;35m", );
}
_ => panic!("Expected GraphCommit"),
}
}
#[test]
fn test_deselect() {
let graph_items = &mut SmartLogParser::parse(&raw_lines()).unwrap();
let commit = &mut graph_items[0];
match commit {
ItemType::Commit(commit) => {
assert!(commit.selected);
assert!(commit.parsed_lines()[0].contains(&"\u{1b}[0;35m".to_string()));
assert!(commit.parsed_lines()[1].contains(&"\u{1b}[0;35m".to_string()));
commit.deselect();
assert!(!commit.selected);
assert!(!commit.parsed_lines()[0].contains(&"\u{1b}[0;35m".to_string()));
assert!(!commit.parsed_lines()[1].contains(&"\u{1b}[0;35m".to_string()));
}
_ => panic!("Expected GraphCommit"),
}
}
#[test]
fn test_hash() {
let graph_items = &mut SmartLogParser::parse(&raw_lines()).unwrap();
let local_commit = &mut graph_items[0];
match local_commit {
ItemType::Commit(commit) => {
assert_eq!(commit.hash().unwrap(), "1cee5d55e");
}
_ => panic!("Expected GraphCommit"),
}
let remote_commit = &mut graph_items[4];
match remote_commit {
ItemType::Commit(commit) => {
assert_eq!(commit.hash().unwrap(), "ba27d4d13");
}
_ => panic!("Expected GraphCommit"),
}
}
fn raw_lines() -> Vec<String> {
RAW_LINES.iter().map(|x| x.to_string()).collect()
}
}