seafly-prompt 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. #!/bin/bash
  2. # A modern, informative and configurable command prompt for the Bash shell.
  3. #
  4. # URL: github.com/bluz71/bash-seafly-prompt
  5. # License: MIT (https://opensource.org/licenses/MIT)
  6. # Non-interactive shells don't have a prompt, exit early.
  7. [[ $- =~ i ]] || return 0
  8. # Set a simple prompt for non-256color, non-alacritty and non-kitty terminals.
  9. if [[ $TERM != *-256color ]] && [[ $TERM != alacritty* ]] && [[ $TERM != *-kitty ]]; then
  10. PS1='\h \w > '
  11. return 0
  12. fi
  13. # Default colors used in the prompt.
  14. : ${SEAFLY_PREFIX_COLOR:=$(tput setaf 153)}
  15. : ${SEAFLY_NORMAL_COLOR:=$(tput setaf 111)}
  16. : ${SEAFLY_ALERT_COLOR:=$(tput setaf 203)}
  17. : ${SEAFLY_HOST_COLOR:=$(tput setaf 255)}
  18. : ${SEAFLY_GIT_COLOR:=$(tput setaf 147)}
  19. : ${SEAFLY_PATH_COLOR:=$(tput setaf 150)}
  20. : ${NOCOLOR:=$(tput sgr0)}
  21. # Shorten directory paths to a maximum of four components unless PROMPT_DIRTRIM
  22. # has already been set.
  23. : ${PROMPT_DIRTRIM:=4}
  24. # Default Git indicator values.
  25. : ${GIT_PS1_SHOWDIRTYSTATE:=1}
  26. : ${GIT_PS1_SHOWSTASHSTATE:=1}
  27. : ${GIT_PS1_SHOWUPSTREAM:=1}
  28. # Default layout settings.
  29. : ${SEAFLY_LAYOUT:=1}
  30. : ${SEAFLY_MULTILINE:=0}
  31. : ${SEAFLY_SHOW_USER:=0}
  32. : ${SEAFLY_SHOW_HOST:=1}
  33. # Default symbols used in the prompt.
  34. : ${SEAFLY_PROMPT_SYMBOL:="❯"}
  35. : ${SEAFLY_PS2_PROMPT_SYMBOL:="❯"}
  36. : ${SEAFLY_GIT_PREFIX:=""}
  37. : ${SEAFLY_GIT_SUFFIX:=""}
  38. : ${SEAFLY_GIT_DIRTY:="✗"}
  39. : ${SEAFLY_GIT_STAGED:="✓"}
  40. : ${SEAFLY_GIT_STASH:="⚑"}
  41. : ${SEAFLY_GIT_AHEAD:="↑"}
  42. : ${SEAFLY_GIT_BEHIND:="↓"}
  43. : ${SEAFLY_GIT_DIVERGED:="↕"}
  44. # Location of [gitstatus](https://github.com/romkatv/gitstatus).
  45. : ${SEAFLY_GITSTATUS_DIR:="$HOME/.gitstatus"}
  46. # Optional command to run before every prompt; output is ignored.
  47. : ${SEAFLY_PRE_COMMAND:=""}
  48. # Optional command that outputs as the prompt prefix.
  49. : ${SEAFLY_PROMPT_PREFIX:=""}
  50. # Collate Git details using the optimized
  51. # [gitstatus](https://github.com/romkatv/gitstatus) command.
  52. #
  53. _seafly_git_optimized() {
  54. local flags
  55. # Note, gitstatus will automatically set '-p' if the local repository has
  56. # set 'bash.showDirtyState' to false.
  57. [[ $GIT_PS1_SHOWDIRTYSTATE == 0 ]] && flags=-p # Avoid unnecessary work
  58. if ! hash gitstatus_query 2>/dev/null || ! gitstatus_query $flags; then
  59. # Either gitstatus_query does not exist or it failed, use fallback
  60. # instead.
  61. _seafly_git_fallback
  62. return
  63. fi
  64. [[ $VCS_STATUS_RESULT == ok-sync ]] || return
  65. # We are in a Git repository and gitstatus_query succeeded.
  66. local branch=$VCS_STATUS_LOCAL_BRANCH
  67. if [[ -z $branch ]]; then
  68. branch="detached*$(git rev-parse --short HEAD 2>/dev/null)"
  69. fi
  70. branch=${branch//\\/\\\\} # Escape backslashes
  71. branch=${branch//\$/\\\$} # Escape dollars
  72. local dirty
  73. local staged
  74. if [[ $GIT_PS1_SHOWDIRTYSTATE != 0 && $VCS_STATUS_HAS_UNSTAGED == 1 ]]; then
  75. dirty=$SEAFLY_GIT_DIRTY
  76. fi
  77. if [[ $GIT_PS1_SHOWDIRTYSTATE != 0 && $VCS_STATUS_HAS_STAGED == 1 ]]; then
  78. staged=$SEAFLY_GIT_STAGED
  79. fi
  80. local stash
  81. if [[ $GIT_PS1_SHOWSTASHSTATE != 0 && $VCS_STATUS_STASHES -gt 0 ]]; then
  82. stash=$SEAFLY_GIT_STASH
  83. fi
  84. local upstream
  85. if [[ $GIT_PS1_SHOWUPSTREAM != 0 ]]; then
  86. if [[ $VCS_STATUS_COMMITS_AHEAD -gt 0 &&
  87. $VCS_STATUS_COMMITS_BEHIND -gt 0 ]]; then
  88. upstream=$SEAFLY_GIT_DIVERGED
  89. elif [[ $VCS_STATUS_COMMITS_AHEAD -gt 0 ]]; then
  90. upstream=$SEAFLY_GIT_AHEAD
  91. elif [[ $VCS_STATUS_COMMITS_BEHIND -gt 0 ]]; then
  92. upstream=$SEAFLY_GIT_BEHIND
  93. elif [[ -n $VCS_STATUS_REMOTE_NAME ]]; then
  94. upstream="="
  95. fi
  96. fi
  97. local spacer
  98. if [[ -n $dirty || -n $staged || -n $stash || -n $upstream ]]; then
  99. spacer=" "
  100. fi
  101. _seafly_git=" $SEAFLY_GIT_PREFIX$branch$spacer\[$SEAFLY_ALERT_COLOR\]$dirty\[$SEAFLY_NORMAL_COLOR\]$staged$upstream\[$SEAFLY_GIT_COLOR\]$stash$SEAFLY_GIT_SUFFIX"
  102. }
  103. # Collate Git details using just the 'git' command.
  104. #
  105. _seafly_git_fallback() {
  106. local is_git_repo
  107. if [[ $(git rev-parse --is-inside-work-tree --is-bare-repository 2>/dev/null) =~ true ]]; then
  108. is_git_repo=1
  109. fi
  110. [[ $is_git_repo == 1 ]] || return
  111. # We are in a Git repository.
  112. local branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)"
  113. if [[ $branch == "HEAD" ]]; then
  114. branch="detached*$(git rev-parse --short HEAD 2>/dev/null)"
  115. fi
  116. branch=${branch//\\/\\\\} # Escape backslashes
  117. branch=${branch//\$/\\\$} # Escape dollars
  118. local dirty
  119. local staged
  120. if [[ $branch != "detached*" &&
  121. $GIT_PS1_SHOWDIRTYSTATE != 0 &&
  122. $(git config --bool bash.showDirtyState) != "false" ]]; then
  123. git diff --no-ext-diff --quiet --exit-code --ignore-submodules 2>/dev/null || dirty=$SEAFLY_GIT_DIRTY
  124. git diff --no-ext-diff --quiet --cached --exit-code --ignore-submodules 2>/dev/null || staged=$SEAFLY_GIT_STAGED
  125. fi
  126. local stash
  127. if [[ $GIT_PS1_SHOWSTASHSTATE != 0 ]]; then
  128. git rev-parse --verify --quiet refs/stash >/dev/null && stash=$SEAFLY_GIT_STASH
  129. fi
  130. local upstream
  131. if [[ $GIT_PS1_SHOWUPSTREAM != 0 ]]; then
  132. case "$(git rev-list --left-right --count HEAD...@'{u}' 2>/dev/null)" in
  133. "") # no upstream
  134. upstream="" ;;
  135. "0 0") # equal to upstream
  136. upstream="=" ;;
  137. "0 "*) # behind upstream
  138. upstream=$SEAFLY_GIT_BEHIND ;;
  139. *" 0") # ahead of upstream
  140. upstream=$SEAFLY_GIT_AHEAD ;;
  141. *) # diverged from upstream
  142. upstream=$SEAFLY_GIT_DIVERGED ;;
  143. esac
  144. fi
  145. local spacer
  146. if [[ -n $dirty || -n $staged || -n $stash || -n $upstream ]]; then
  147. spacer=" "
  148. fi
  149. _seafly_git=" $SEAFLY_GIT_PREFIX$branch$spacer\[$SEAFLY_ALERT_COLOR\]$dirty\[$SEAFLY_NORMAL_COLOR\]$staged$upstream\[$SEAFLY_GIT_COLOR\]$stash$SEAFLY_GIT_SUFFIX"
  150. }
  151. _seafly_command_prompt() {
  152. # QNET: Custom Prompting
  153. MSG="/tmp/prompt_msg"
  154. if [[ -f "$MSG" ]]; then # var exists
  155. if [[ -n "$MSG" ]]; then # not empty
  156. cat "$MSG"
  157. echo ''
  158. fi
  159. fi
  160. # Run the pre-command if set.
  161. eval $SEAFLY_PRE_COMMAND
  162. local prompt_prefix
  163. local prefix_value=$(eval $SEAFLY_PROMPT_PREFIX)
  164. if [[ -n $prefix_value ]]; then
  165. prompt_prefix="\[$SEAFLY_PREFIX_COLOR\]$prefix_value "
  166. fi
  167. if [[ $SEAFLY_MULTILINE = 1 ]]; then
  168. prompt_prefix="\n$prompt_prefix"
  169. fi
  170. local prompt_start
  171. if [[ $SEAFLY_SHOW_USER = 1 && $SEAFLY_SHOW_HOST = 1 ]]; then
  172. prompt_start="\[$SEAFLY_HOST_COLOR\]\u@\h"
  173. elif [[ $SEAFLY_SHOW_USER = 1 ]]; then
  174. prompt_start="\[$SEAFLY_HOST_COLOR\]\u"
  175. elif [[ $SEAFLY_SHOW_HOST = 1 ]]; then
  176. prompt_start="\[$SEAFLY_HOST_COLOR\]\h"
  177. fi
  178. # Collate Git details, if applicable, for the current directory.
  179. _seafly_git_optimized
  180. local prompt_middle
  181. if [[ $SEAFLY_LAYOUT = 1 ]]; then
  182. prompt_middle="\[$SEAFLY_GIT_COLOR\]$_seafly_git\[$SEAFLY_PATH_COLOR\] \w"
  183. else
  184. prompt_middle="\[$SEAFLY_PATH_COLOR\] \w\[$SEAFLY_GIT_COLOR\]$_seafly_git"
  185. fi
  186. unset _seafly_git
  187. # Normal prompt indicates that the last command ran successfully.
  188. # Alert prompt indicates that the last command failed.
  189. _seafly_colors=("$SEAFLY_ALERT_COLOR" "$SEAFLY_NORMAL_COLOR")
  190. local prompt_end="\[\${_seafly_colors[\$((!\$?))]}\] $SEAFLY_PROMPT_SYMBOL\[\$NOCOLOR\] "
  191. if [[ $SEAFLY_MULTILINE = 1 ]]; then
  192. prompt_end="\n$prompt_end"
  193. fi
  194. PS1="$prompt_prefix$prompt_start$prompt_middle$prompt_end"
  195. PS2="\[$SEAFLY_NORMAL_COLOR\]$SEAFLY_PS2_PROMPT_SYMBOL\[\$NOCOLOR\] "
  196. }
  197. # Use [gitstatus](https://github.com/romkatv/gitstatus) if it is available.
  198. if [[ -r $SEAFLY_GITSTATUS_DIR/gitstatus.plugin.sh ]]; then
  199. source "$SEAFLY_GITSTATUS_DIR"/gitstatus.plugin.sh
  200. gitstatus_stop && gitstatus_start -c 0 -d 0
  201. fi
  202. # Bind and call the '_seafly_command_prompt' function as the Bash prompt.
  203. PROMPT_COMMAND=_seafly_command_prompt