GitBucket
4.21.2
Toggle navigation
Snippets
Sign in
Files
Branches
1
Releases
Issues
Pull requests
Labels
Priorities
Milestones
Wiki
Forks
nigel.stanger
/
zsh-rust-git-prompt
Browse code
Testing comment
master
1 parent
1ab16c0
commit
14bb24b97896cb2d3e59e7ae690b4d4b05092df3
Nigel Stanger
authored
on 20 Oct 2022
Patch
Showing
1 changed file
src/main.rs
Ignore Space
Show notes
View
src/main.rs
use anes::{Attribute, Color, ResetAttributes, SetAttribute, SetForegroundColor}; use std::io; use std::process::ExitCode; use regex::Regex; use std::num::NonZeroUsize; use substring::Substring; // Configuration. // What to print before and after the current branch name. const BRANCH_PREFIX: &str = "\u{e725} "; // Nerd Font nf-dev-git_branch const BRANCH_SUFFIX: &str = " "; // What to print before the number of commits ahead and behind remote. const AHEAD_PREFIX: &str = "\u{f55c}"; // Nerd Font nf-mdi-arrow_up const BEHIND_PREFIX: &str = "\u{f544}"; // Nerd Font nf-mdi-arrow_down // What to print between the current branch information and the file // status information. const STATUS_SEPARATOR: &str = " | "; // U+007C VERTICAL LINE // What to print before the number of stashes. const STASHED_PREFIX: &str = "\u{2691}"; // U+2691 BLACK FLAG // What to print before the number of staged files. const STAGED_PREFIX: &str = "+"; // U+002B PLUS SIGN // What to print before the number of renamed files. const RENAMED_PREFIX: &str = "~"; // U+007E TILDE // What to print before the number of changed files. const CHANGED_PREFIX: &str = "\u{2206}"; // U+2206 INCREMENT // What to print before the number of deleted files. const DELETED_PREFIX: &str = "\u{2212}"; // U+2212 MINUS SIGN // What to print before the number of conflicting files. const CONFLICTED_PREFIX: &str = "\u{203c}"; // U+203C DOUBLE EXCLAMATION MARK // What to print before the number of untracked files. const UNTRACKED_PREFIX: &str = "\u{f142}"; // Nerd Font nf-fa-ellipsis_v // What to print before the number of ignored files. const IGNORED_PREFIX: &str = "\u{eabc}"; // Nerd Font nf-cod-circle // What to print if the branch is clean. const CLEAN_INDICATOR: &str = "\u{f00c}"; // Nerd Font nf-fa-check // Styling. fn reset_style() -> String { return format!("%{{{}%}}", ResetAttributes); } fn branch_style() -> String { return format!("%{{{}{}{}%}}", SetForegroundColor(Color::Black), SetAttribute(Attribute::Bold), SetAttribute(Attribute::Underline)); } fn stashed_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::Blue)); } fn staged_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::Blue)); } fn renamed_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::Blue)); } fn changed_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::DarkRed)); } fn deleted_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::DarkRed)); } fn conflicted_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::DarkRed)); } fn untracked_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::Gray)); } fn ignored_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::Gray)); } fn clean_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::DarkGreen)); } fn main() -> ExitCode { let mut git_status = String::new(); io::stdin().read_line(&mut git_status).expect("Failed to read input"); if git_status.len() == 0 { print!(""); return ExitCode::SUCCESS; } // Current commit. // example: # branch.oid 825882679bbf52650f8115bfec8591d973d80b49 let hash = match Regex::new(r"# branch\.oid ([^\x00]+)").unwrap().captures(&git_status) { Some(caps) => caps.get(1).unwrap().as_str(), None => "???", }; // Current branch. // example: # branch.head master // example: # branch.head (detached) let branch = match Regex::new(r"# branch\.head ([^\x00]+)").unwrap().captures(&git_status) { Some(caps) => { let name = caps.get(1).unwrap().as_str(); if name == "(detached)" { // use the (shortened) commit hash format!("{BRANCH_PREFIX}:{}{}{}{BRANCH_SUFFIX}", branch_style(), hash.substring(0, 7), reset_style()) } else { format!("{BRANCH_PREFIX}{}{}{}{BRANCH_SUFFIX}", branch_style(), name, reset_style()) } }, None => "???".to_string(), }; // Commits ahead/behind of upstream branch. // example: # branch.ab +2 -5 let (ahead, behind) = match Regex::new(r"# branch\.ab \+(\d+) -(\d+)").unwrap().captures(&git_status) { Some(caps) => ( format!("{AHEAD_PREFIX}{}", caps.get(1).unwrap().as_str()), format!("{BEHIND_PREFIX}{}", caps.get(2).unwrap().as_str()) ), None => ( format!("{AHEAD_PREFIX}0"), format!("{BEHIND_PREFIX}0") ), }; // Number of stashed files. // example: # stash 4 let stashed = match Regex::new(r"# stash (\d+)").unwrap().captures(&git_status) { Some(caps) => format!("{}{STASHED_PREFIX}{}{} ", stashed_style(), caps.get(1).unwrap().as_str(), reset_style()), None => "".to_string(), }; // Number of staged files. // example: 1 A. N... 000000 100644 100644 0000000000000000000000000000000000000000 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file.staged let staged = match NonZeroUsize::new(Regex::new(r"1 (A.) [^\x00]+").unwrap().find_iter(&git_status).count()) { Some(n) => format!("{}{STAGED_PREFIX}{n}{} ", staged_style(), reset_style()), None => "".to_string(), }; // Number of renamed files. // example: 2 R. N... 100644 100644 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 R100 file.renamed.new.1 file.renamed.original.1 let renamed = match NonZeroUsize::new(Regex::new(r"2 (R.) [^\x00]+").unwrap().find_iter(&git_status).count()) { Some(n) => format!("{}{RENAMED_PREFIX}{n}{} ", renamed_style(), reset_style()), None => "".to_string(), }; // Number of changed files. // example: 1 .M N... 000000 100644 100644 0000000000000000000000000000000000000000 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file.changed let changed = match NonZeroUsize::new(Regex::new(r"1 (.[MT]) [^\x00]+").unwrap().find_iter(&git_status).count()) { Some(n) => format!("{}{CHANGED_PREFIX}{n}{} ", changed_style(), reset_style()), None => "".to_string(), }; // Number of deleted files. // example: 1 D. N... 100644 000000 000000 284f07612f65f8032a5f5871c2591da2af4bd184 0000000000000000000000000000000000000000 file.deleted // example: 1 .D N... 100644 100644 000000 cefc2d5fe77876ddd24e962031bd5829d6431470 cefc2d5fe77876ddd24e962031bd5829d6431470 file.deleted let deleted = match NonZeroUsize::new(Regex::new(r"1 (D[^D]|[^D]D) [^\x00]+").unwrap().find_iter(&git_status).count()) { Some(n) => format!("{}{DELETED_PREFIX}{n}{} ", deleted_style(), reset_style()), None => "".to_string(), }; // Number of conflicted files. // example: u UD N... 000000 100644 100644 0000000000000000000000000000000000000000 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file.conflicted let conflicted = match NonZeroUsize::new(Regex::new(r"u [ADU][ADU] ([^\x00]+)").unwrap().find_iter(&git_status).count()) { Some(n) => format!("{}{CONFLICTED_PREFIX}{n}{} ", conflicted_style(), reset_style()), None => "".to_string(), }; // Number of untracked files. // example: ? file.untracked let untracked = match NonZeroUsize::new(Regex::new(r"\? ([^\x00]+)").unwrap().find_iter(&git_status).count()) { Some(n) => format!("{}{UNTRACKED_PREFIX}{n}{} ", untracked_style(), reset_style()), None => "".to_string(), }; // Number of ignored files. // example: ! file.ignored let ignored = match NonZeroUsize::new(Regex::new(r"! ([^\x00]+)").unwrap().find_iter(&git_status).count()) { Some(n) => format!("{}{IGNORED_PREFIX}{n}{} ", ignored_style(), reset_style()), None => "".to_string(), }; let status_string = format!("{stashed}{staged}{renamed}{changed}{deleted}{conflicted}{untracked}{ignored}"); print!("{branch}{ahead}{behind}{STATUS_SEPARATOR}{}", if status_string.len() > 0 { status_string.trim().to_string() } else { format!("{}{CLEAN_INDICATOR}{}", clean_style(), reset_style()) } ); ExitCode::SUCCESS } // Testing: // RPROMPT=$'%B%{\Ue725%G%} %Umaster%u%b %{\Uf55c%G%}1%{\Uf544%G%}2%} | %{\U2691%G%}2 +2 ~4 %{\U2206%G%}1 %{\U2212%G%}5 %{\U203c%G%}2 %{\Uf142%G%}8 %{\Ueabc%G%}3 %{\Uf00c%G%}'
use anes::{Attribute, Color, ResetAttributes, SetAttribute, SetForegroundColor}; use std::io; use std::process::ExitCode; use regex::Regex; use std::num::NonZeroUsize; use substring::Substring; // Configuration. // What to print before and after the current branch name. const BRANCH_PREFIX: &str = "\u{e725} "; // Nerd Font nf-dev-git_branch const BRANCH_SUFFIX: &str = " "; // What to print before the number of commits ahead and behind remote. const AHEAD_PREFIX: &str = "\u{f55c}"; // Nerd Font nf-mdi-arrow_up const BEHIND_PREFIX: &str = "\u{f544}"; // Nerd Font nf-mdi-arrow_down // What to print between the current branch information and the file // status information. const STATUS_SEPARATOR: &str = " | "; // U+007C VERTICAL LINE // What to print before the number of stashes. const STASHED_PREFIX: &str = "\u{2691}"; // U+2691 BLACK FLAG // What to print before the number of staged files. const STAGED_PREFIX: &str = "+"; // U+002B PLUS SIGN // What to print before the number of renamed files. const RENAMED_PREFIX: &str = "~"; // U+007E TILDE // What to print before the number of changed files. const CHANGED_PREFIX: &str = "\u{2206}"; // U+2206 INCREMENT // What to print before the number of deleted files. const DELETED_PREFIX: &str = "\u{2212}"; // U+2212 MINUS SIGN // What to print before the number of conflicting files. const CONFLICTED_PREFIX: &str = "\u{203c}"; // U+203C DOUBLE EXCLAMATION MARK // What to print before the number of untracked files. const UNTRACKED_PREFIX: &str = "\u{f142}"; // Nerd Font nf-fa-ellipsis_v // What to print before the number of ignored files. const IGNORED_PREFIX: &str = "\u{eabc}"; // Nerd Font nf-cod-circle // What to print if the branch is clean. const CLEAN_INDICATOR: &str = "\u{f00c}"; // Nerd Font nf-fa-check // Styling. fn reset_style() -> String { return format!("%{{{}%}}", ResetAttributes); } fn branch_style() -> String { return format!("%{{{}{}{}%}}", SetForegroundColor(Color::Black), SetAttribute(Attribute::Bold), SetAttribute(Attribute::Underline)); } fn stashed_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::Blue)); } fn staged_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::Blue)); } fn renamed_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::Blue)); } fn changed_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::DarkRed)); } fn deleted_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::DarkRed)); } fn conflicted_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::DarkRed)); } fn untracked_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::Gray)); } fn ignored_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::Gray)); } fn clean_style() -> String { return format!("%{{{}%}}", SetForegroundColor(Color::DarkGreen)); } fn main() -> ExitCode { let mut git_status = String::new(); io::stdin().read_line(&mut git_status).expect("Failed to read input"); if git_status.len() == 0 { print!(""); return ExitCode::SUCCESS; } // Current commit. // example: # branch.oid 825882679bbf52650f8115bfec8591d973d80b49 let hash = match Regex::new(r"# branch\.oid ([^\x00]+)").unwrap().captures(&git_status) { Some(caps) => caps.get(1).unwrap().as_str(), None => "???", }; // Current branch. // example: # branch.head master // example: # branch.head (detached) let branch = match Regex::new(r"# branch\.head ([^\x00]+)").unwrap().captures(&git_status) { Some(caps) => { let name = caps.get(1).unwrap().as_str(); if name == "(detached)" { // use the (shortened) commit hash format!("{BRANCH_PREFIX}:{}{}{}{BRANCH_SUFFIX}", branch_style(), hash.substring(0, 7), reset_style()) } else { format!("{BRANCH_PREFIX}{}{}{}{BRANCH_SUFFIX}", branch_style(), name, reset_style()) } }, None => "???".to_string(), }; // Commits ahead/behind of upstream branch. // example: # branch.ab +2 -5 let (ahead, behind) = match Regex::new(r"# branch\.ab \+(\d+) -(\d+)").unwrap().captures(&git_status) { Some(caps) => ( format!("{AHEAD_PREFIX}{}", caps.get(1).unwrap().as_str()), format!("{BEHIND_PREFIX}{}", caps.get(2).unwrap().as_str()) ), None => ( format!("{AHEAD_PREFIX}0"), format!("{BEHIND_PREFIX}0") ), }; // Number of stashed files. // example: # stash 4 let stashed = match Regex::new(r"# stash (\d+)").unwrap().captures(&git_status) { Some(caps) => format!("{}{STASHED_PREFIX}{}{} ", stashed_style(), caps.get(1).unwrap().as_str(), reset_style()), None => "".to_string(), }; // Number of staged files. // example: 1 A. N... 000000 100644 100644 0000000000000000000000000000000000000000 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file.staged let staged = match NonZeroUsize::new(Regex::new(r"1 (A.) [^\x00]+").unwrap().find_iter(&git_status).count()) { Some(n) => format!("{}{STAGED_PREFIX}{n}{} ", staged_style(), reset_style()), None => "".to_string(), }; // Number of renamed files. // example: 2 R. N... 100644 100644 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 R100 file.renamed.new.1 file.renamed.original.1 let renamed = match NonZeroUsize::new(Regex::new(r"2 (R.) [^\x00]+").unwrap().find_iter(&git_status).count()) { Some(n) => format!("{}{RENAMED_PREFIX}{n}{} ", renamed_style(), reset_style()), None => "".to_string(), }; // Number of changed files. // example: 1 .M N... 000000 100644 100644 0000000000000000000000000000000000000000 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file.changed let changed = match NonZeroUsize::new(Regex::new(r"1 (.[MT]) [^\x00]+").unwrap().find_iter(&git_status).count()) { Some(n) => format!("{}{CHANGED_PREFIX}{n}{} ", changed_style(), reset_style()), None => "".to_string(), }; // Number of deleted files. // example: 1 D. N... 100644 000000 000000 284f07612f65f8032a5f5871c2591da2af4bd184 0000000000000000000000000000000000000000 file.deleted // example: 1 .D N... 100644 100644 000000 cefc2d5fe77876ddd24e962031bd5829d6431470 cefc2d5fe77876ddd24e962031bd5829d6431470 file.deleted let deleted = match NonZeroUsize::new(Regex::new(r"1 (D[^D]|[^D]D) [^\x00]+").unwrap().find_iter(&git_status).count()) { Some(n) => format!("{}{DELETED_PREFIX}{n}{} ", deleted_style(), reset_style()), None => "".to_string(), }; // Number of conflicted files. // example: u UD N... 000000 100644 100644 0000000000000000000000000000000000000000 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file.conflicted let conflicted = match NonZeroUsize::new(Regex::new(r"u [ADU][ADU] ([^\x00]+)").unwrap().find_iter(&git_status).count()) { Some(n) => format!("{}{CONFLICTED_PREFIX}{n}{} ", conflicted_style(), reset_style()), None => "".to_string(), }; // Number of untracked files. // example: ? file.untracked let untracked = match NonZeroUsize::new(Regex::new(r"\? ([^\x00]+)").unwrap().find_iter(&git_status).count()) { Some(n) => format!("{}{UNTRACKED_PREFIX}{n}{} ", untracked_style(), reset_style()), None => "".to_string(), }; // Number of ignored files. // example: ! file.ignored let ignored = match NonZeroUsize::new(Regex::new(r"! ([^\x00]+)").unwrap().find_iter(&git_status).count()) { Some(n) => format!("{}{IGNORED_PREFIX}{n}{} ", ignored_style(), reset_style()), None => "".to_string(), }; let status_string = format!("{stashed}{staged}{renamed}{changed}{deleted}{conflicted}{untracked}{ignored}"); print!("{branch}{ahead}{behind}{STATUS_SEPARATOR}{}", if status_string.len() > 0 { status_string.trim().to_string() } else { format!("{}{CLEAN_INDICATOR}{}", clean_style(), reset_style()) } ); ExitCode::SUCCESS }
Show line notes below