use once_cell::sync::OnceCell;
use regex::Regex;
use super::{Context, Module, RootModuleConfig};
use crate::configs::git_status::GitStatusConfig;
use crate::formatter::StringFormatter;
use crate::segment::Segment;
use std::sync::Arc;
const ALL_STATUS_FORMAT: &str = "$conflicted$stashed$deleted$renamed$modified$staged$untracked";
/// Creates a module with the Git branch in the current directory
///
/// Will display the branch name if the current directory is a git repo
/// By default, the following symbols will be used to represent the repo's status:
/// - `=` – This branch has merge conflicts
/// - `⇡` – This branch is ahead of the branch being tracked
/// - `⇣` – This branch is behind of the branch being tracked
/// - `⇕` – This branch has diverged from the branch being tracked
/// - `?` — There are untracked files in the working directory
/// - `$` — A stash exists for the local repository
/// - `!` — There are file modifications in the working directory
/// - `+` — A new file has been added to the staging area
/// - `»` — A renamed file has been added to the staging area
/// - `✘` — A file's deletion has been added to the staging area
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let info = Arc::new(GitStatusInfo::load(context));
let mut module = context.new_module("git_status");
let config: GitStatusConfig = GitStatusConfig::try_load(module.config);
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
formatter
.map_meta(|variable, _| match variable {
"all_status" => Some(ALL_STATUS_FORMAT),
_ => None,
})
.map_style(|variable: &str| match variable {
"style" => Some(Ok(config.style)),
_ => None,
})
.map_variables_to_segments(|variable: &str| {
let info = Arc::clone(&info);
let segments = match variable {
"stashed" => info.get_stashed().and_then(|count| {
format_count(config.stashed, "git_status.stashed", count)
}),
"ahead_behind" => info.get_ahead_behind().and_then(|(ahead, behind)| {
if ahead > 0 && behind > 0 {
format_text(config.diverged, "git_status.diverged", |variable| {
match variable {
"ahead_count" => Some(ahead.to_string()),
"behind_count" => Some(behind.to_string()),
_ => None,
}
})
} else if ahead > 0 && behind == 0 {
format_count(config.ahead, "git_status.ahead", ahead)
} else if behind > 0 && ahead == 0 {
format_count(config.behind, "git_status.behind", behind)
} else {
None
}
}),
"conflicted" => info.get_conflicted().and_then(|count| {
format_count(config.conflicted, "git_status.conflicted", count)
}),
"deleted" => info.get_deleted().and_then(|count| {
format_count(config.deleted, "git_status.deleted", count)
}),
"renamed" => info.get_renamed().and_then(|count| {
format_count(config.renamed, "git_status.renamed", count)
}),
"modified" => info.get_modified().and_then(|count| {
format_count(config.modified, "git_status.modified", count)
}),
"staged" => info
.get_staged()
.and_then(|count| format_count(config.staged, "git_status.staged", count)),
"untracked" => info.get_untracked().and_then(|count| {
format_count(config.untracked, "git_status.untracked", count)
}),
_ => None,
};
segments.map(Ok)
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => {
if segments.is_empty() {
return None;
} else {
segments
}
}
Err(error) => {
log::warn!("Error in module `git_status`:\n{}", error);
return None;
}
});
Some(module)
}
struct GitStatusInfo<'a> {
context: &'a Context<'a>,
repo_status: OnceCell<Option<RepoStatus>>,
stashed_count: OnceCell<Option<usize>>,
}
impl<'a>