Fancy promptline with git status details
I’ve been using powerline-shell for quite a while and like it a lot. I get aware of that every time I use a terminal which does not tell me which branch I’m on.
Some days ago I stumbled upon promptline.vim and as I’m also using vim-airline I gave it a try. Promptline.vim exports a shell script from the current airline settings.
After sourcing it into a shell you should immediately see the updated promptline (if there are random characters instead of fancy symbols, you need to install powerline symbols first):
Automatically load the promptline when a shell is opened:
[ -f ~/.shell_prompt.sh ] && source ~/.shell_prompt.sh
Vim source
I use the light solarized theme created by Ethan Schoonover and invoked the export from vim looking like this:
Result
I tweaked the result a bit. Originally it only indicates whether, and if any, which changes have been made. I added coloring for the git slice, red for pending changes (both staged and unstaged):
- +3 indicates three files with unstaged chanegs
- •2 tells about two files with staged changes
- … indicates that there are untracked files
In a clean working directory or if there are only untracked files, the slice is green:
Customization
My .shell_prompt.sh
(download) looks like this now:
- #
- # This shell prompt config file was created by promptline.vim
- #
- function __promptline_last_exit_code {
- [[ $last_exit_code -gt 0 ]] || return 1;
- printf "%s" "$last_exit_code"
- }
- function __promptline_ps1 {
- local slice_prefix slice_empty_prefix slice_joiner slice_suffix is_prompt_empty=1
- # section "aa" header
- slice_prefix="${aa_bg}${sep}${aa_fg}${aa_bg}${space}" slice_suffix="$space${aa_sep_fg}" slice_joiner="${aa_fg}${aa_bg}${alt_sep}${space}" slice_empty_prefix="${aa_fg}${aa_bg}${space}"
- [ $is_prompt_empty -eq 1 ] && slice_prefix="$slice_empty_prefix"
- # section "a" slices
- __promptline_wrapper "$(if [[ -n ${ZSH_VERSION-} ]]; then print %m; elif [[ -n ${FISH_VERSION-} ]]; then hostname -s; else printf "%s" \\A; fi )" "$slice_prefix" "$slice_suffix" && { slice_prefix="$slice_joiner"; is_prompt_empty=0; }
- # section "a" header
- slice_prefix="${a_bg}${sep}${a_fg}${a_bg}${space}" slice_suffix="$space${a_sep_fg}" slice_joiner="${a_fg}${a_bg}${alt_sep}${space}" slice_empty_prefix="${a_fg}${a_bg}${space}"
- [ $is_prompt_empty -eq 1 ] && slice_prefix="$slice_empty_prefix"
- # section "a" slices
- __promptline_wrapper "$(if [[ -n ${ZSH_VERSION-} ]]; then print %m; elif [[ -n ${FISH_VERSION-} ]]; then hostname -s; else printf "%s" \\h; fi )" "$slice_prefix" "$slice_suffix" && { slice_prefix="$slice_joiner"; is_prompt_empty=0; }
- # section "b" header
- slice_prefix="${b_bg}${sep}${b_fg}${b_bg}${space}" slice_suffix="$space${b_sep_fg}" slice_joiner="${b_fg}${b_bg}${alt_sep}${space}" slice_empty_prefix="${b_fg}${b_bg}${space}"
- [ $is_prompt_empty -eq 1 ] && slice_prefix="$slice_empty_prefix"
- # section "b" slices
- __promptline_wrapper "$USER" "$slice_prefix" "$slice_suffix" && { slice_prefix="$slice_joiner"; is_prompt_empty=0; }
- # section "c" header
- slice_prefix="${c_bg}${sep}${c_fg}${c_bg}${space}" slice_suffix="$space${c_sep_fg}" slice_joiner="${c_fg}${c_bg}${alt_sep}${space}" slice_empty_prefix="${c_fg}${c_bg}${space}"
- [ $is_prompt_empty -eq 1 ] && slice_prefix="$slice_empty_prefix"
- # section "c" slices
- __promptline_wrapper "$(__promptline_cwd)" "$slice_prefix" "$slice_suffix" && { slice_prefix="$slice_joiner"; is_prompt_empty=0; }
- # this will prepare the variables for __promptline_git_status and adjust the bg coloring for section "y"
- __determine_git_colors
- # section "y" header
- slice_prefix="${y_bg}${sep}${y_fg}${y_bg}${space}" slice_suffix="$space${y_sep_fg}" slice_joiner="${y_fg}${y_bg}${alt_sep}${space}" slice_empty_prefix="${y_fg}${y_bg}${space}"
- [ $is_prompt_empty -eq 1 ] && slice_prefix="$slice_empty_prefix"
- # section "y" slices
- __promptline_wrapper "$(__promptline_vcs_branch)" "$slice_prefix" "$slice_suffix" && { slice_prefix="$slice_joiner"; is_prompt_empty=0; }
- __promptline_wrapper "$(__promptline_git_status)" "$slice_prefix" "$slice_suffix" && { slice_prefix="$slice_joiner"; is_prompt_empty=0; }
- # section "warn" header
- slice_prefix="${warn_bg}${sep}${warn_fg}${warn_bg}${space}" slice_suffix="$space${warn_sep_fg}" slice_joiner="${warn_fg}${warn_bg}${alt_sep}${space}" slice_empty_prefix="${warn_fg}${warn_bg}${space}"
- [ $is_prompt_empty -eq 1 ] && slice_prefix="$slice_empty_prefix"
- # section "warn" slices
- __promptline_wrapper "$(__promptline_last_exit_code)" "$slice_prefix" "$slice_suffix" && { slice_prefix="$slice_joiner"; is_prompt_empty=0; }
- # close sections
- printf "%s" "${reset_bg}${sep}$reset$space"
- }
- function __promptline_vcs_branch {
- local branch
- local branch_symbol=" "
- # git
- if hash git 2>/dev/null; then
- if branch=$( { git symbolic-ref --quiet HEAD || git rev-parse --short HEAD; } 2>/dev/null ); then
- branch=${branch##*/}
- printf "%s" "${branch_symbol}${branch:-unknown}"
- return
- fi
- fi
- return 1
- }
- function __determine_git_colors {
- [[ $(git rev-parse --is-inside-work-tree 2>/dev/null) == true ]] || return 1
- __promptline_git_unmerged_count=0 __promptline_git_modified_count=0 __promptline_git_has_untracked_files=0 __promptline_git_added_count=0 __promptline_git_is_clean=""
- set -- $(git rev-list --left-right --count @{upstream}...HEAD 2>/dev/null)
- __promptline_git_behind_count=$1
- __promptline_git_ahead_count=$2
- # Added (A), Copied (C), Deleted (D), Modified (M), Renamed (R), changed (T), Unmerged (U), Unknown (X), Broken (B)
- while read line; do
- case "$line" in
- M*) __promptline_git_modified_count=$(( $__promptline_git_modified_count + 1 )) ;;
- U*) __promptline_git_unmerged_count=$(( $__promptline_git_unmerged_count + 1 )) ;;
- esac
- done < <(git diff --name-status)
- while read line; do
- case "$line" in
- *) __promptline_git_added_count=$(( $__promptline_git_added_count + 1 )) ;;
- esac
- done < <(git diff --name-status --cached)
- if [ -n "$(git ls-files --others --exclude-standard)" ]; then
- __promptline_git_has_untracked_files=1
- fi
- if [ $(( __promptline_git_unmerged_count + __promptline_git_modified_count + __promptline_git_has_untracked_files + __promptline_git_added_count )) -eq 0 ]; then
- __promptline_git_is_clean=1
- fi
- y_fg="${wrap}38;5;246${end_wrap}"
- # set green background for the branch info if there are no changes or only untracked files
- if [[ $((__promptline_git_is_clean + __promptline_git_has_untracked_files)) -gt 0 ]]; then
- y_bg="${wrap}48;5;194${end_wrap}"
- y_sep_fg="${wrap}38;5;194${end_wrap}"
- fi
- # set red background for the branch info if there are unstaged or staged (but not yet committed) changes
- if [[ $((__promptline_git_modified_count + __promptline_git_added_count)) -gt 0 ]]; then
- y_bg="${wrap}48;5;224${end_wrap}"
- y_sep_fg="${wrap}38;5;224${end_wrap}"
- #y_bg="${wrap}48;5;217${end_wrap}"
- #y_sep_fg="${wrap}38;5;217${end_wrap}"
- fi
- }
- function __promptline_git_status {
- [[ $(git rev-parse --is-inside-work-tree 2>/dev/null) == true ]] || return 1
- local added_symbol="●"
- local unmerged_symbol="✖"
- local modified_symbol="✚"
- local clean_symbol="✔"
- local has_untracked_files_symbol="…"
- local ahead_symbol="↑"
- local behind_symbol="↓"
- local leading_whitespace=""
- [[ $__promptline_git_ahead_count -gt 0 ]] && { printf "%s" "$leading_whitespace$ahead_symbol$__promptline_git_ahead_count"; leading_whitespace=" "; }
- [[ $__promptline_git_behind_count -gt 0 ]] && { printf "%s" "$leading_whitespace$behind_symbol$__promptline_git_behind_count"; leading_whitespace=" "; }
- [[ $__promptline_git_modified_count -gt 0 ]] && { printf "%s" "$leading_whitespace$modified_symbol$__promptline_git_modified_count"; leading_whitespace=" "; }
- [[ $__promptline_git_unmerged_count -gt 0 ]] && { printf "%s" "$leading_whitespace$unmerged_symbol$__promptline_git_unmerged_count"; leading_whitespace=" "; }
- [[ $__promptline_git_added_count -gt 0 ]] && { printf "%s" "$leading_whitespace$added_symbol$__promptline_git_added_count"; leading_whitespace=" "; }
- [[ $__promptline_git_has_untracked_files -gt 0 ]] && { printf "%s" "$leading_whitespace$has_untracked_files_symbol"; leading_whitespace=" "; }
- [[ $__promptline_git_is_clean -gt 0 ]] && { printf "%s" "$leading_whitespace$clean_symbol"; leading_whitespace=" "; }
- }
- function __promptline_cwd {
- local dir_limit="3"
- local truncation="⋯"
- local first_char
- local part_count=0
- local formatted_cwd=""
- local dir_sep=" "
- local tilde="~"
- local cwd="${PWD/#$HOME/$tilde}"
- # get first char of the path, i.e. tilde or slash
- [[ -n ${ZSH_VERSION-} ]] && first_char=$cwd[1,1] || first_char=${cwd::1}
- # remove leading tilde
- cwd="${cwd#\~}"
- while [[ "$cwd" == */* && "$cwd" != "/" ]]; do
- # pop off last part of cwd
- local part="${cwd##*/}"
- cwd="${cwd%/*}"
- formatted_cwd="$dir_sep$part$formatted_cwd"
- part_count=$((part_count+1))
- [[ $part_count -eq $dir_limit ]] && first_char="$truncation" && break
- done
- printf "%s" "$first_char$formatted_cwd"
- }
- function __promptline_left_prompt {
- local slice_prefix slice_empty_prefix slice_joiner slice_suffix is_prompt_empty=1
- # section "a" header
- slice_prefix="${a_bg}${sep}${a_fg}${a_bg}${space}" slice_suffix="$space${a_sep_fg}" slice_joiner="${a_fg}${a_bg}${alt_sep}${space}" slice_empty_prefix="${a_fg}${a_bg}${space}"
- [ $is_prompt_empty -eq 1 ] && slice_prefix="$slice_empty_prefix"
- # section "a" slices
- __promptline_wrapper "$(if [[ -n ${ZSH_VERSION-} ]]; then print %m; elif [[ -n ${FISH_VERSION-} ]]; then hostname -s; else printf "%s" \\h; fi )" "$slice_prefix" "$slice_suffix" && { slice_prefix="$slice_joiner"; is_prompt_empty=0; }
- # section "b" header
- slice_prefix="${b_bg}${sep}${b_fg}${b_bg}${space}" slice_suffix="$space${b_sep_fg}" slice_joiner="${b_fg}${b_bg}${alt_sep}${space}" slice_empty_prefix="${b_fg}${b_bg}${space}"
- [ $is_prompt_empty -eq 1 ] && slice_prefix="$slice_empty_prefix"
- # section "b" slices
- __promptline_wrapper "$USER" "$slice_prefix" "$slice_suffix" && { slice_prefix="$slice_joiner"; is_prompt_empty=0; }
- # section "c" header
- slice_prefix="${c_bg}${sep}${c_fg}${c_bg}${space}" slice_suffix="$space${c_sep_fg}" slice_joiner="${c_fg}${c_bg}${alt_sep}${space}" slice_empty_prefix="${c_fg}${c_bg}${space}"
- [ $is_prompt_empty -eq 1 ] && slice_prefix="$slice_empty_prefix"
- # section "c" slices
- __promptline_wrapper "$(__promptline_cwd)" "$slice_prefix" "$slice_suffix" && { slice_prefix="$slice_joiner"; is_prompt_empty=0; }
- # close sections
- printf "%s" "${reset_bg}${sep}$reset$space"
- }
- function __promptline_wrapper {
- # wrap the text in $1 with $2 and $3, only if $1 is not empty
- # $2 and $3 typically contain non-content-text, like color escape codes and separators
- [[ -n "$1" ]] || return 1
- printf "%s" "${2}${1}${3}"
- }
- function __promptline_right_prompt {
- local slice_prefix slice_empty_prefix slice_joiner slice_suffix
- # section "warn" header
- slice_prefix="${warn_sep_fg}${rsep}${warn_fg}${warn_bg}${space}" slice_suffix="$space${warn_sep_fg}" slice_joiner="${warn_fg}${warn_bg}${alt_rsep}${space}" slice_empty_prefix=""
- # section "warn" slices
- __promptline_wrapper "$(__promptline_last_exit_code)" "$slice_prefix" "$slice_suffix" && { slice_prefix="$slice_joiner"; }
- # section "y" header
- slice_prefix="${y_sep_fg}${rsep}${y_fg}${y_bg}${space}" slice_suffix="$space${y_sep_fg}" slice_joiner="${y_fg}${y_bg}${alt_rsep}${space}" slice_empty_prefix=""
- # section "y" slices
- __promptline_wrapper "$(__promptline_vcs_branch)" "$slice_prefix" "$slice_suffix" && { slice_prefix="$slice_joiner"; }
- # close sections
- printf "%s" "$reset"
- }
- function __promptline {
- local last_exit_code="${PROMPTLINE_LAST_EXIT_CODE:-$?}"
- local esc=$'[' end_esc=m
- if [[ -n ${ZSH_VERSION-} ]]; then
- local noprint='%{' end_noprint='%}'
- elif [[ -n ${FISH_VERSION-} ]]; then
- local noprint='' end_noprint=''
- else
- local noprint='\[' end_noprint='\]'
- fi
- local wrap="$noprint$esc" end_wrap="$end_esc$end_noprint"
- local space=" "
- local sep=""
- local rsep=""
- local alt_sep=""
- local alt_rsep=""
- local reset="${wrap}0${end_wrap}"
- local reset_bg="${wrap}49${end_wrap}"
- local aa_fg="${wrap}38;5;7${end_wrap}"
- local aa_bg="${wrap}48;5;246${end_wrap}"
- local aa_sep_fg="${wrap}38;5;246${end_wrap}"
- local a_fg="${wrap}38;5;7${end_wrap}"
- local a_bg="${wrap}48;5;11${end_wrap}"
- local a_sep_fg="${wrap}38;5;11${end_wrap}"
- local b_fg="${wrap}38;5;11${end_wrap}"
- # set red background for root
- if [ $UID == 0 ]; then
- local b_bg="${wrap}48;5;210${end_wrap}"
- local b_sep_fg="${wrap}38;5;210${end_wrap}"
- # set light green background for anyone else
- else
- local b_bg="${wrap}48;5;187${end_wrap}"
- local b_sep_fg="${wrap}38;5;187${end_wrap}"
- fi
- local c_fg="${wrap}38;5;14${end_wrap}"
- local c_bg="${wrap}48;5;7${end_wrap}"
- local c_sep_fg="${wrap}38;5;7${end_wrap}"
- local warn_fg="${wrap}38;5;15${end_wrap}"
- local warn_bg="${wrap}48;5;9${end_wrap}"
- local warn_sep_fg="${wrap}38;5;9${end_wrap}"
- local y_fg="${wrap}38;5;14${end_wrap}"
- local y_bg="${wrap}48;5;14${end_wrap}"
- local y_sep_fg="${wrap}38;5;14${end_wrap}"
- if [[ -n ${ZSH_VERSION-} ]]; then
- PROMPT="$(__promptline_left_prompt)"
- RPROMPT="$(__promptline_right_prompt)"
- elif [[ -n ${FISH_VERSION-} ]]; then
- if [[ -n "$1" ]]; then
- [[ "$1" = "left" ]] && __promptline_left_prompt || __promptline_right_prompt
- else
- __promptline_ps1
- fi
- else
- PS1="$(__promptline_ps1)"
- fi
- }
- if [[ -n ${ZSH_VERSION-} ]]; then
- if [[ ! ${precmd_functions[(r)__promptline]} == __promptline ]]; then
- precmd_functions+=(__promptline)
- fi
- elif [[ -n ${FISH_VERSION-} ]]; then
- __promptline "$1"
- else
- if [[ ! "$PROMPT_COMMAND" == *__promptline* ]]; then
- PROMPT_COMMAND='__promptline;'$'\n'"$PROMPT_COMMAND"
- fi
- fi
Changes:
- line 14-16: Add slice with current time (only works in bash as is)
- line 71-137: Add functions to determine git status
- line 39: Determine git details and coloring for ‚y‘-section before rendering starts
- line 45: Call git status rendering
- line 234-259: Changed some colors, added ‚aa‘-colors, user name background depends on being root or not
I took the logic for the git status slice from promptline.vim and adjusted it so that the coloring for the slice is determined before its rendering starts.
Colors can be adjusted by changing the third value of the tuples, a nice cheat-sheet can be found here.
Sieht super aus. Ich habe mich ja zugegebenermassen etwas daran aufgehangen, dass das Dreieckszeichen nicht bei normalen Schriftarten dabei ist… Humpf!
Dann kann man es ja raus nehmen und hat kantige statt dreieckig Slice-Enden ;). Oder man findet einen anderen „dynamischeren“ Übergang im Satz der Standardzeichen? Die Symbole die für die git-Infos (Branch-Symbol zumindest mal, vermute ich) verwendet werden betrifft das ja eventuell auch.
Ordentliche Übergänge gibt es da nicht, da die meisten Zeichen in Unicode nicht die volle Blockhöhe haben. (Es gibt nur ein paar Zeichen zum Kistenmalen die von alten DOS-Zeichensätzen vermacht wurden, aber davon gibt es wohl keine neuen. Am ehesten haut noch der Übergang mit mehreren Schattierungen hin, aber es ist alles nicht der Weisheit letzter Schluss.)
Warum willst du die Zeichen eigentlich nicht nachinstallieren? Das hat bei mir (den wahrnehmbaren Effekten nach zumindest) völlig schmerzfrei geklappt.