use serde_json::{json, Value};
pub trait VcsRepository {
fn branch(&self) -> String;
fn stash(&self) -> Option<u64> {
None
}
fn tree_status(&self) -> Option<String> {
None
}
}
pub struct BranchSegment;
impl Default for BranchSegment {
fn default() -> Self {
Self::new()
}
}
impl BranchSegment {
pub const DIVIDER_HIGHLIGHT_GROUP: Option<&'static str> = None;
pub fn new() -> Self {
Self
}
pub fn get_directory<F>(getcwd: F) -> Option<String>
where
F: FnOnce() -> Option<String>,
{
getcwd()
}
pub fn call<R: VcsRepository>(
repo: Option<&R>,
status_colors: bool,
ignore_statuses: &[String],
) -> Option<Vec<Value>> {
let repo = repo?;
let branch = repo.branch();
let mut scol: Vec<String> = vec!["branch".to_string()];
if status_colors {
let status = repo.tree_status();
let effective = status.and_then(|s| {
let trimmed = s.trim().to_string();
if trimmed.is_empty() || ignore_statuses.iter().any(|i| i == &trimmed) {
None
} else {
Some(trimmed)
}
});
let group = if effective.is_some() {
"branch_dirty"
} else {
"branch_clean"
};
scol.insert(0, group.to_string());
}
Some(vec![json!({
"contents": branch,
"highlight_groups": scol,
"divider_highlight_group": Self::DIVIDER_HIGHLIGHT_GROUP,
})])
}
}
pub struct StashSegment;
impl Default for StashSegment {
fn default() -> Self {
Self::new()
}
}
impl StashSegment {
pub const DIVIDER_HIGHLIGHT_GROUP: Option<&'static str> = None;
pub fn new() -> Self {
Self
}
pub fn get_directory<F>(getcwd: F) -> Option<String>
where
F: FnOnce() -> Option<String>,
{
getcwd()
}
pub fn call<R: VcsRepository>(repo: Option<&R>) -> Option<Vec<Value>> {
let repo = repo?;
let stashes = repo.stash()?;
if stashes == 0 {
return None;
}
Some(vec![json!({
"contents": stashes.to_string(),
"highlight_groups": ["stash"],
"divider_highlight_group": Self::DIVIDER_HIGHLIGHT_GROUP,
})])
}
}
#[cfg(test)]
mod tests {
use super::*;
struct FakeRepo {
branch_name: String,
stash_count: Option<u64>,
status: Option<String>,
}
impl VcsRepository for FakeRepo {
fn branch(&self) -> String {
self.branch_name.clone()
}
fn stash(&self) -> Option<u64> {
self.stash_count
}
fn tree_status(&self) -> Option<String> {
self.status.clone()
}
}
fn repo(branch: &str) -> FakeRepo {
FakeRepo {
branch_name: branch.to_string(),
stash_count: None,
status: None,
}
}
#[test]
fn branch_segment_divider_highlight_group_is_none() {
assert_eq!(BranchSegment::DIVIDER_HIGHLIGHT_GROUP, None);
}
#[test]
fn branch_segment_get_directory_calls_getcwd_closure() {
let r = BranchSegment::get_directory(|| Some("/repo".to_string()));
assert_eq!(r, Some("/repo".to_string()));
}
#[test]
fn branch_segment_get_directory_none_when_getcwd_returns_none() {
let r = BranchSegment::get_directory(|| None);
assert!(r.is_none());
}
#[test]
fn branch_segment_no_repo_returns_none() {
let r: Option<&FakeRepo> = None;
let segments = BranchSegment::call(r, false, &[]);
assert!(segments.is_none());
}
#[test]
fn branch_segment_no_status_colors_returns_branch_highlight() {
let r = repo("main");
let segments = BranchSegment::call(Some(&r), false, &[]).unwrap();
assert_eq!(segments[0]["contents"], "main");
assert_eq!(segments[0]["highlight_groups"], json!(["branch"]));
}
#[test]
fn branch_segment_with_status_colors_clean_picks_branch_clean() {
let r = FakeRepo {
branch_name: "main".to_string(),
stash_count: None,
status: None,
};
let segments = BranchSegment::call(Some(&r), true, &[]).unwrap();
assert_eq!(segments[0]["highlight_groups"][0], "branch_clean");
assert_eq!(segments[0]["highlight_groups"][1], "branch");
}
#[test]
fn branch_segment_with_status_colors_dirty_picks_branch_dirty() {
let r = FakeRepo {
branch_name: "main".to_string(),
stash_count: None,
status: Some("D ".to_string()),
};
let segments = BranchSegment::call(Some(&r), true, &[]).unwrap();
assert_eq!(segments[0]["highlight_groups"][0], "branch_dirty");
}
#[test]
fn branch_segment_ignore_statuses_strips_to_clean() {
let r = FakeRepo {
branch_name: "main".to_string(),
stash_count: None,
status: Some(" U".to_string()),
};
let segments = BranchSegment::call(Some(&r), true, &["U".to_string()]).unwrap();
assert_eq!(segments[0]["highlight_groups"][0], "branch_clean");
}
#[test]
fn branch_segment_empty_status_is_clean() {
let r = FakeRepo {
branch_name: "main".to_string(),
stash_count: None,
status: Some(" ".to_string()),
};
let segments = BranchSegment::call(Some(&r), true, &[]).unwrap();
assert_eq!(segments[0]["highlight_groups"][0], "branch_clean");
}
#[test]
fn branch_segment_emits_divider_highlight_group_as_null() {
let r = repo("main");
let segments = BranchSegment::call(Some(&r), false, &[]).unwrap();
assert_eq!(segments[0]["divider_highlight_group"], Value::Null);
}
#[test]
fn stash_segment_divider_highlight_group_is_none() {
assert_eq!(StashSegment::DIVIDER_HIGHLIGHT_GROUP, None);
}
#[test]
fn stash_segment_no_repo_returns_none() {
let r: Option<&FakeRepo> = None;
assert!(StashSegment::call(r).is_none());
}
#[test]
fn stash_segment_no_stash_support_returns_none() {
let r = FakeRepo {
branch_name: String::new(),
stash_count: None,
status: None,
};
assert!(StashSegment::call(Some(&r)).is_none());
}
#[test]
fn stash_segment_zero_stashes_returns_none() {
let r = FakeRepo {
branch_name: String::new(),
stash_count: Some(0),
status: None,
};
assert!(StashSegment::call(Some(&r)).is_none());
}
#[test]
fn stash_segment_positive_count_returns_segment() {
let r = FakeRepo {
branch_name: String::new(),
stash_count: Some(3),
status: None,
};
let segments = StashSegment::call(Some(&r)).unwrap();
assert_eq!(segments[0]["contents"], "3");
assert_eq!(segments[0]["highlight_groups"], json!(["stash"]));
}
#[test]
fn stash_segment_get_directory_calls_closure() {
let r = StashSegment::get_directory(|| Some("/repo".to_string()));
assert_eq!(r, Some("/repo".to_string()));
}
}