From ab67c7ca6f0191d2c47f2e07731ca084559f9c4f Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 27 Aug 2016 15:54:13 +1200 Subject: Port Bash prompt to Zsh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Somewhat naïvely; just quick searches of `man zshall`. I'm sure some of the stuff I removed has analogous features or that they can be implemented. --- zsh/zshrc | 7 +- zsh/zshrc.d/prompt.zsh | 214 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 zsh/zshrc.d/prompt.zsh (limited to 'zsh') diff --git a/zsh/zshrc b/zsh/zshrc index 014ac3fd..2376e568 100644 --- a/zsh/zshrc +++ b/zsh/zshrc @@ -7,5 +7,8 @@ HISTFILE=$HOME/.zsh_history SAVEHIST=$((1 << 12)) HISTSIZE=$((1 << 10)) -# Load POSIX shell functions -source "$ENV" +# Load POSIX shell startup files and then Bash-specific ones +for sh in "$ENV" "$HOME"/.zshrc.d/*.zsh ; do + [[ -e $sh ]] && source "$sh" +done +unset -v sh diff --git a/zsh/zshrc.d/prompt.zsh b/zsh/zshrc.d/prompt.zsh new file mode 100644 index 00000000..a17a5e68 --- /dev/null +++ b/zsh/zshrc.d/prompt.zsh @@ -0,0 +1,214 @@ +# Frontend to controlling prompt +prompt() { + + # What's done next depends on the first argument to the function + case $1 in + + # Turn complex, colored PS1 and debugging PS4 prompts on + on) + setopt promptsubst promptpercent + + # Declare the PROMPT_RETURN variable as an integer + declare -i PROMPT_RETURN + + # Set up pre-prompt command + precmd() { + PROMPT_RETURN=$? + } + + # Basic prompt shape depends on whether we're in SSH or not + PS1= + if [[ -n $SSH_CLIENT ]] || [[ -n $SSH_CONNECTION ]] ; then + PS1=$PS1'%n@%m:' + fi + PS1=$PS1'%~' + + # Add sub-commands; VCS, job, and return status checks + PS1=$PS1'$(prompt vcs)$(prompt job)$(prompt ret)' + + # Add prefix and suffix + PS1='${PROMPT_PREFIX}'$PS1'${PROMPT_SUFFIX}' + + # Add terminating "$" or "#" sign + PS1=$PS1'%#' + + # Bold and color the prompt if it looks like we can + if (( $({ tput colors || tput Co ; } 2>/dev/null) >= 8 )) ; then + PS1='%B%F{green}'$PS1'%f%b' + fi + + # Add a space and define the rest of the prompts + PS1=$PS1' ' + PS2='> ' + PS3='? ' + PS4='+<$?> $LINENO:' + ;; + + # Revert to simple inexpensive prompts + off) + unset -v precmd PROMPT_RETURN + PS1='\$ ' + PS2='> ' + PS3='? ' + PS4='+ ' + ;; + + # Git prompt function + git) + # Bail if we're not in a work tree--or, implicitly, if we don't + # have git(1). + local iswt + iswt=$(git rev-parse --is-inside-work-tree 2>/dev/null) + [[ $iswt = true ]] || return + + # Find a branch label, or a tag, or just show the short commit ID, + # in that order of preference; if none of that works, bail out. + local branch + branch=$( { + git symbolic-ref --quiet HEAD || + git describe --tags --exact-match HEAD || + git rev-parse --short HEAD + } 2>/dev/null ) + [[ -n $branch ]] || return + branch=${branch##*/} + + # Refresh index so e.g. git-diff-files(1) is accurate + git update-index --refresh >/dev/null + + # Check various files in .git to flag processes + local proc + [[ -d .git/rebase-merge || -d .git/rebase-apply ]] && + proc=${proc:+$proc,}'REBASE' + [[ -f .git/MERGE_HEAD ]] && + proc=${proc:+$proc,}'MERGE' + [[ -f .git/CHERRY_PICK_HEAD ]] && + proc=${proc:+$proc,}'PICK' + [[ -f .git/REVERT_HEAD ]] && + proc=${proc:+$proc,}'REVERT' + [[ -f .git/BISECT_LOG ]] && + proc=${proc:+$proc,}'BISECT' + + # Collect symbols representing repository state + local state + + # Upstream HEAD has commits after local HEAD; we're "behind" + local -i behind + behind=$(git rev-list --count 'HEAD..@{u}' 2>/dev/null) + ((behind)) && state=${state}'<' + + # Local HEAD has commits after upstream HEAD; we're "ahead" + local -i ahead + ahead=$(git rev-list --count '@{u}..HEAD' 2>/dev/null) + ((ahead)) && state=${state}'>' + + # Tracked files are modified + git diff-files --no-ext-diff --quiet || + state=${state}'!' + + # Changes are staged + git diff-index --cached --no-ext-diff --quiet HEAD 2>/dev/null || + state=${state}'+' + + # There are some untracked and unignored files + git ls-files --directory --error-unmatch --exclude-standard \ + --no-empty-directory --others -- ':/*' >/dev/null 2>&1 && + state=${state}'?' + + # There are stashed changes + git rev-parse --quiet --verify refs/stash >/dev/null && + state=${state}'^' + + # Print the status in brackets; add a git: prefix only if there + # might be another VCS prompt (because PROMPT_VCS is set) + printf '(%s%s%s%s)' \ + "${PROMPT_VCS:+git:}" "${branch:-unknown}" \ + "${proc:+:$proc}" "$state" + ;; + + # Subversion prompt function + svn) + # Determine the repository URL and root directory + local key value url root + while IFS=: read -r key value ; do + case $key in + 'URL') + url=${value## } + ;; + 'Repository Root') + root=${value## } + ;; + esac + done < <(svn info 2>/dev/null) + + # Exit if we couldn't get either--or, implicitly, if we don't have + # svn(1). + [[ -n $url ]] || return + [[ -n $root ]] || return + + # Remove the root from the URL to get what's hopefully the branch + # name, removing leading slashes and the 'branches' prefix, and any + # trailing content after a slash + local branch + branch=${url/"$root"} + branch=${branch#/} + branch=${branch#branches/} + branch=${branch%%/*} + + # Parse the output of svn status to determine working copy state + local symbol + local -i modified untracked + while read -r symbol _ ; do + if [[ $symbol == *'?'* ]] ; then + untracked=1 + else + modified=1 + fi + done < <(svn status 2>/dev/null) + + # Add appropriate state flags + local -a state + ((modified)) && state[${#state[@]}]='!' + ((untracked)) && state[${#state[@]}]='?' + + # Print the state in brackets with an svn: prefix + (IFS= ; printf '(svn:%s%s)' \ + "${branch:-unknown}" "${state[*]}") + ;; + + # VCS wrapper prompt function; print the first relevant prompt, if any + vcs) + local vcs + for vcs in "${PROMPT_VCS[@]:-git}" ; do + prompt "$vcs" && return + done + ;; + + # Show return status of previous command in angle brackets, if not zero + ret) + ((PROMPT_RETURN)) && printf '<%u>' "$PROMPT_RETURN" + ;; + + # Show the count of background jobs in curly brackets, if not zero + job) + local -i jobc + while read -r ; do + ((jobc++)) + done < <(jobs -p) + ((jobc)) && printf '{%u}' "$jobc" + ;; + + # No argument given, print prompt strings and vars + '') + declare -p PS1 PS2 PS3 PS4 + ;; + + # Print error + *) + printf 'prompt: Unknown command %s\n' "$1" >&2 + return 2 + ;; + esac +} + +# Start with full-fledged prompt +prompt on -- cgit v1.2.3