diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/cli.zsh | 426 | ||||
-rw-r--r-- | lib/clipboard.zsh | 125 | ||||
-rw-r--r-- | lib/compfix.zsh | 2 | ||||
-rw-r--r-- | lib/completion.zsh | 22 | ||||
-rw-r--r-- | lib/diagnostics.zsh | 10 | ||||
-rw-r--r-- | lib/directories.zsh | 10 | ||||
-rw-r--r-- | lib/functions.zsh | 47 | ||||
-rw-r--r-- | lib/git.zsh | 253 | ||||
-rw-r--r-- | lib/grep.zsh | 57 | ||||
-rw-r--r-- | lib/history.zsh | 12 | ||||
-rw-r--r-- | lib/key-bindings.zsh | 107 | ||||
-rw-r--r-- | lib/misc.zsh | 34 | ||||
-rw-r--r-- | lib/nvm.zsh | 9 | ||||
-rw-r--r-- | lib/prompt_info_functions.zsh | 20 | ||||
-rw-r--r-- | lib/spectrum.zsh | 26 | ||||
-rw-r--r-- | lib/termsupport.zsh | 88 | ||||
-rw-r--r-- | lib/theme-and-appearance.zsh | 12 |
17 files changed, 946 insertions, 314 deletions
diff --git a/lib/cli.zsh b/lib/cli.zsh new file mode 100644 index 000000000..38e2f72f8 --- /dev/null +++ b/lib/cli.zsh @@ -0,0 +1,426 @@ +#!/usr/bin/env zsh + +function omz { + [[ $# -gt 0 ]] || { + _omz::help + return 1 + } + + local command="$1" + shift + + # Subcommand functions start with _ so that they don't + # appear as completion entries when looking for `omz` + (( $+functions[_omz::$command] )) || { + _omz::help + return 1 + } + + _omz::$command "$@" +} + +function _omz { + local -a cmds subcmds + cmds=( + 'changelog:Print the changelog' + 'help:Usage information' + 'plugin:Manage plugins' + 'pr:Manage Oh My Zsh Pull Requests' + 'theme:Manage themes' + 'update:Update Oh My Zsh' + ) + + if (( CURRENT == 2 )); then + _describe 'command' cmds + elif (( CURRENT == 3 )); then + case "$words[2]" in + changelog) local -a refs + refs=("${(@f)$(command git for-each-ref --format="%(refname:short):%(subject)" refs/heads refs/tags)}") + _describe 'command' refs ;; + plugin) subcmds=('info:Get plugin information' 'list:List plugins') + _describe 'command' subcmds ;; + pr) subcmds=('test:Test a Pull Request' 'clean:Delete all Pull Request branches') + _describe 'command' subcmds ;; + theme) subcmds=('use:Load a theme' 'list:List themes') + _describe 'command' subcmds ;; + esac + elif (( CURRENT == 4 )); then + case "$words[2]::$words[3]" in + plugin::info) compadd "$ZSH"/plugins/*/README.md(.N:h:t) \ + "$ZSH_CUSTOM"/plugins/*/README.md(.N:h:t) ;; + theme::use) compadd "$ZSH"/themes/*.zsh-theme(.N:t:r) \ + "$ZSH_CUSTOM"/**/*.zsh-theme(.N:r:gs:"$ZSH_CUSTOM"/themes/:::gs:"$ZSH_CUSTOM"/:::) ;; + esac + fi + + return 0 +} + +compdef _omz omz + +## Utility functions + +function _omz::confirm { + # If question supplied, ask it before reading the answer + # NOTE: uses the logname of the caller function + if [[ -n "$1" ]]; then + _omz::log prompt "$1" "${${functrace[1]#_}%:*}" + fi + + # Read one character + read -r -k 1 + + # If no newline entered, add a newline + if [[ "$REPLY" != $'\n' ]]; then + echo + fi +} + +function _omz::log { + # if promptsubst is set, a message with `` or $() + # will be run even if quoted due to `print -P` + setopt localoptions nopromptsubst + + # $1 = info|warn|error|debug + # $2 = text + # $3 = (optional) name of the logger + + local logtype=$1 + local logname=${3:-${${functrace[1]#_}%:*}} + + # Don't print anything if debug is not active + if [[ $logtype = debug && -z $_OMZ_DEBUG ]]; then + return + fi + + # Choose coloring based on log type + case "$logtype" in + prompt) print -Pn "%S%F{blue}$logname%f%s: $2" ;; + debug) print -P "%F{white}$logname%f: $2" ;; + info) print -P "%F{green}$logname%f: $2" ;; + warn) print -P "%S%F{yellow}$logname%f%s: $2" ;; + error) print -P "%S%F{red}$logname%f%s: $2" ;; + esac >&2 +} + +## User-facing commands + +function _omz::help { + cat <<EOF +Usage: omz <command> [options] + +Available commands: + + help Print this help message + changelog Print the changelog + plugin <command> Manage plugins + pr <command> Manage Oh My Zsh Pull Requests + theme <command> Manage themes + update Update Oh My Zsh + +EOF +} + +function _omz::changelog { + local version=${1:-HEAD} format=${3:-"--text"} + + if ! command git -C "$ZSH" show-ref --verify refs/heads/$version &>/dev/null && \ + ! command git -C "$ZSH" show-ref --verify refs/tags/$version &>/dev/null && \ + ! command git -C "$ZSH" rev-parse --verify "${version}^{commit}" &>/dev/null; then + cat <<EOF +Usage: omz changelog [version] + +NOTE: <version> must be a valid branch, tag or commit. +EOF + return 1 + fi + + "$ZSH/tools/changelog.sh" "$version" "${2:-}" "$format" +} + +function _omz::plugin { + (( $# > 0 && $+functions[_omz::plugin::$1] )) || { + cat <<EOF +Usage: omz plugin <command> [options] + +Available commands: + + info <plugin> Get information of a plugin + list List all available Oh My Zsh plugins + +EOF + return 1 + } + + local command="$1" + shift + + _omz::plugin::$command "$@" +} + +function _omz::plugin::info { + if [[ -z "$1" ]]; then + echo >&2 "Usage: omz plugin info <plugin>" + return 1 + fi + + local readme + for readme in "$ZSH_CUSTOM/plugins/$1/README.md" "$ZSH/plugins/$1/README.md"; do + if [[ -f "$readme" ]]; then + (( ${+commands[less]} )) && less "$readme" || cat "$readme" + return 0 + fi + done + + if [[ -d "$ZSH_CUSTOM/plugins/$1" || -d "$ZSH/plugins/$1" ]]; then + _omz::log error "the '$1' plugin doesn't have a README file" + else + _omz::log error "'$1' plugin not found" + fi + + return 1 +} + +function _omz::plugin::list { + local -a custom_plugins builtin_plugins + custom_plugins=("$ZSH_CUSTOM"/plugins/*(-/N:t)) + builtin_plugins=("$ZSH"/plugins/*(-/N:t)) + + # If the command is being piped, print all found line by line + if [[ ! -t 1 ]]; then + print -l ${(q-)custom_plugins} ${(q-)builtin_plugins} + return + fi + + if (( ${#custom_plugins} )); then + print -P "%U%BCustom plugins%b%u:" + print -l ${(q-)custom_plugins} | column + fi + + if (( ${#builtin_plugins} )); then + (( ${#custom_plugins} )) && echo # add a line of separation + + print -P "%U%BBuilt-in plugins%b%u:" + print -l ${(q-)builtin_plugins} | column + fi +} + +function _omz::pr { + (( $# > 0 && $+functions[_omz::pr::$1] )) || { + cat <<EOF +Usage: omz pr <command> [options] + +Available commands: + + clean Delete all PR branches (ohmyzsh/pull-*) + test <PR_number_or_URL> Fetch PR #NUMBER and rebase against master + +EOF + return 1 + } + + local command="$1" + shift + + _omz::pr::$command "$@" +} + +function _omz::pr::clean { + ( + set -e + builtin cd -q "$ZSH" + + # Check if there are PR branches + local fmt branches + fmt="%(color:bold blue)%(align:18,right)%(refname:short)%(end)%(color:reset) %(color:dim bold red)%(objectname:short)%(color:reset) %(color:yellow)%(contents:subject)" + branches="$(command git for-each-ref --sort=-committerdate --color --format="$fmt" "refs/heads/ohmyzsh/pull-*")" + + # Exit if there are no PR branches + if [[ -z "$branches" ]]; then + _omz::log info "there are no Pull Request branches to remove." + return + fi + + # Print found PR branches + echo "$branches\n" + # Confirm before removing the branches + _omz::confirm "do you want remove these Pull Request branches? [Y/n] " + # Only proceed if the answer is a valid yes option + [[ "$REPLY" != [yY$'\n'] ]] && return + + _omz::log info "removing all Oh My Zsh Pull Request branches..." + command git branch --list 'ohmyzsh/pull-*' | while read branch; do + command git branch -D "$branch" + done + ) +} + +function _omz::pr::test { + # Allow $1 to be a URL to the pull request + if [[ "$1" = https://* ]]; then + 1="${1:t}" + fi + + # Check the input + if ! [[ -n "$1" && "$1" =~ ^[[:digit:]]+$ ]]; then + echo >&2 "Usage: omz pr test <PR_NUMBER_or_URL>" + return 1 + fi + + # Save current git HEAD + local branch + branch=$(builtin cd -q "$ZSH"; git symbolic-ref --short HEAD) || { + _omz::log error "error when getting the current git branch. Aborting..." + return 1 + } + + + # Fetch PR onto ohmyzsh/pull-<PR_NUMBER> branch and rebase against master + # If any of these operations fail, undo the changes made + ( + set -e + builtin cd -q "$ZSH" + + # Get the ohmyzsh git remote + command git remote -v | while read remote url _; do + case "$url" in + https://github.com/ohmyzsh/ohmyzsh(|.git)) found=1; break ;; + git@github.com:ohmyzsh/ohmyzsh(|.git)) found=1; break ;; + esac + done + + (( $found )) || { + _omz::log error "could not found the ohmyzsh git remote. Aborting..." + return 1 + } + + # Fetch pull request head + _omz::log info "fetching PR #$1 to ohmyzsh/pull-$1..." + command git fetch -f "$remote" refs/pull/$1/head:ohmyzsh/pull-$1 || { + _omz::log error "error when trying to fetch PR #$1." + return 1 + } + + # Rebase pull request branch against the current master + _omz::log info "rebasing PR #$1..." + command git rebase master ohmyzsh/pull-$1 || { + command git rebase --abort &>/dev/null + _omz::log warn "could not rebase PR #$1 on top of master." + _omz::log warn "you might not see the latest stable changes." + _omz::log info "run \`zsh\` to test the changes." + return 1 + } + + _omz::log info "fetch of PR #${1} successful." + ) + + # If there was an error, abort running zsh to test the PR + [[ $? -eq 0 ]] || return 1 + + # Run zsh to test the changes + _omz::log info "running \`zsh\` to test the changes. Run \`exit\` to go back." + command zsh -l + + # After testing, go back to the previous HEAD if the user wants + _omz::confirm "do you want to go back to the previous branch? [Y/n] " + # Only proceed if the answer is a valid yes option + [[ "$REPLY" != [yY$'\n'] ]] && return + + ( + set -e + builtin cd -q "$ZSH" + + command git checkout "$branch" -- || { + _omz::log error "could not go back to the previous branch ('$branch')." + return 1 + } + ) +} + +function _omz::theme { + (( $# > 0 && $+functions[_omz::theme::$1] )) || { + cat <<EOF +Usage: omz theme <command> [options] + +Available commands: + + list List all available Oh My Zsh themes + use <theme> Load an Oh My Zsh theme + +EOF + return 1 + } + + local command="$1" + shift + + _omz::theme::$command "$@" +} + +function _omz::theme::list { + local -a custom_themes builtin_themes + custom_themes=("$ZSH_CUSTOM"/**/*.zsh-theme(-.N:r:gs:"$ZSH_CUSTOM"/themes/:::gs:"$ZSH_CUSTOM"/:::)) + builtin_themes=("$ZSH"/themes/*.zsh-theme(-.N:t:r)) + + # If the command is being piped, print all found line by line + if [[ ! -t 1 ]]; then + print -l ${(q-)custom_themes} ${(q-)builtin_themes} + return + fi + + if (( ${#custom_themes} )); then + print -P "%U%BCustom themes%b%u:" + print -l ${(q-)custom_themes} | column + fi + + if (( ${#builtin_themes} )); then + (( ${#custom_themes} )) && echo # add a line of separation + + print -P "%U%BBuilt-in themes%b%u:" + print -l ${(q-)builtin_themes} | column + fi +} + +function _omz::theme::use { + if [[ -z "$1" ]]; then + echo >&2 "Usage: omz theme use <theme>" + return 1 + fi + + # Respect compatibility with old lookup order + if [[ -f "$ZSH_CUSTOM/$1.zsh-theme" ]]; then + source "$ZSH_CUSTOM/$1.zsh-theme" + elif [[ -f "$ZSH_CUSTOM/themes/$1.zsh-theme" ]]; then + source "$ZSH_CUSTOM/themes/$1.zsh-theme" + elif [[ -f "$ZSH/themes/$1.zsh-theme" ]]; then + source "$ZSH/themes/$1.zsh-theme" + else + _omz::log error "theme '$1' not found" + return 1 + fi +} + +function _omz::update { + local last_commit=$(cd "$ZSH"; git rev-parse HEAD) + + # Run update script + if [[ "$1" != --unattended ]]; then + ZSH="$ZSH" zsh -f "$ZSH/tools/upgrade.sh" --interactive + else + ZSH="$ZSH" zsh -f "$ZSH/tools/upgrade.sh" + fi + + # Update last updated file + zmodload zsh/datetime + echo "LAST_EPOCH=$(( EPOCHSECONDS / 60 / 60 / 24 ))" >! "${ZSH_CACHE_DIR}/.zsh-update" + # Remove update lock if it exists + command rm -rf "$ZSH/log/update.lock" + + # Restart the zsh session if there were changes + if [[ "$1" != --unattended && "$(cd "$ZSH"; git rev-parse HEAD)" != "$last_commit" ]]; then + # Old zsh versions don't have ZSH_ARGZERO + local zsh="${ZSH_ARGZERO:-${functrace[-1]%:*}}" + # Check whether to run a login shell + [[ "$zsh" = -* || -o login ]] && exec -l "${zsh#-}" || exec "$zsh" + fi +} diff --git a/lib/clipboard.zsh b/lib/clipboard.zsh index 2c93d1bb5..122145f15 100644 --- a/lib/clipboard.zsh +++ b/lib/clipboard.zsh @@ -3,10 +3,23 @@ # This file has support for doing system clipboard copy and paste operations # from the command line in a generic cross-platform fashion. # -# On OS X and Windows, the main system clipboard or "pasteboard" is used. On other -# Unix-like OSes, this considers the X Windows CLIPBOARD selection to be the -# "system clipboard", and the X Windows `xclip` command must be installed. - +# This is uses essentially the same heuristic as neovim, with the additional +# special support for Cygwin. +# See: https://github.com/neovim/neovim/blob/e682d799fa3cf2e80a02d00c6ea874599d58f0e7/runtime/autoload/provider/clipboard.vim#L55-L121 +# +# - pbcopy, pbpaste (macOS) +# - cygwin (Windows running Cygwin) +# - wl-copy, wl-paste (if $WAYLAND_DISPLAY is set) +# - xclip (if $DISPLAY is set) +# - xsel (if $DISPLAY is set) +# - lemonade (for SSH) https://github.com/pocke/lemonade +# - doitclient (for SSH) http://www.chiark.greenend.org.uk/~sgtatham/doit/ +# - win32yank (Windows) +# - tmux (if $TMUX is set) +# +# Defines two functions, clipcopy and clippaste, based on the detected platform. +## +# # clipcopy - Copy data to clipboard # # Usage: @@ -15,41 +28,8 @@ # # clipcopy <file> - copies a file's contents to clipboard # -function clipcopy() { - emulate -L zsh - local file=$1 - if [[ $OSTYPE == darwin* ]]; then - if [[ -z $file ]]; then - pbcopy - else - cat $file | pbcopy - fi - elif [[ $OSTYPE == cygwin* ]]; then - if [[ -z $file ]]; then - cat > /dev/clipboard - else - cat $file > /dev/clipboard - fi - else - if (( $+commands[xclip] )); then - if [[ -z $file ]]; then - xclip -in -selection clipboard - else - xclip -in -selection clipboard $file - fi - elif (( $+commands[xsel] )); then - if [[ -z $file ]]; then - xsel --clipboard --input - else - cat "$file" | xsel --clipboard --input - fi - else - print "clipcopy: Platform $OSTYPE not supported or xclip/xsel not installed" >&2 - return 1 - fi - fi -} - +## +# # clippaste - "Paste" data from clipboard to stdout # # Usage: @@ -67,20 +47,61 @@ function clipcopy() { # # # Paste to a file # clippaste > file.txt -function clippaste() { +# +function detect-clipboard() { emulate -L zsh - if [[ $OSTYPE == darwin* ]]; then - pbpaste - elif [[ $OSTYPE == cygwin* ]]; then - cat /dev/clipboard + + if [[ "${OSTYPE}" == darwin* ]] && (( ${+commands[pbcopy]} )) && (( ${+commands[pbpaste]} )); then + function clipcopy() { pbcopy < "${1:-/dev/stdin}"; } + function clippaste() { pbpaste; } + elif [[ "${OSTYPE}" == (cygwin|msys)* ]]; then + function clipcopy() { cat "${1:-/dev/stdin}" > /dev/clipboard; } + function clippaste() { cat /dev/clipboard; } + elif [ -n "${WAYLAND_DISPLAY:-}" ] && (( ${+commands[wl-copy]} )) && (( ${+commands[wl-paste]} )); then + function clipcopy() { wl-copy < "${1:-/dev/stdin}"; } + function clippaste() { wl-paste; } + elif [ -n "${DISPLAY:-}" ] && (( ${+commands[xclip]} )); then + function clipcopy() { xclip -in -selection clipboard < "${1:-/dev/stdin}"; } + function clippaste() { xclip -out -selection clipboard; } + elif [ -n "${DISPLAY:-}" ] && (( ${+commands[xsel]} )); then + function clipcopy() { xsel --clipboard --input < "${1:-/dev/stdin}"; } + function clippaste() { xsel --clipboard --output; } + elif (( ${+commands[lemonade]} )); then + function clipcopy() { lemonade copy < "${1:-/dev/stdin}"; } + function clippaste() { lemonade paste; } + elif (( ${+commands[doitclient]} )); then + function clipcopy() { doitclient wclip < "${1:-/dev/stdin}"; } + function clippaste() { doitclient wclip -r; } + elif (( ${+commands[win32yank]} )); then + function clipcopy() { win32yank -i < "${1:-/dev/stdin}"; } + function clippaste() { win32yank -o; } + elif [[ $OSTYPE == linux-android* ]] && (( $+commands[termux-clipboard-set] )); then + function clipcopy() { termux-clipboard-set "${1:-/dev/stdin}"; } + function clippaste() { termux-clipboard-get; } + elif [ -n "${TMUX:-}" ] && (( ${+commands[tmux]} )); then + function clipcopy() { tmux load-buffer "${1:--}"; } + function clippaste() { tmux save-buffer -; } + elif [[ $(uname -r) = *icrosoft* ]]; then + function clipcopy() { clip.exe < "${1:-/dev/stdin}"; } + function clippaste() { powershell.exe -noprofile -command Get-Clipboard; } else - if (( $+commands[xclip] )); then - xclip -out -selection clipboard - elif (( $+commands[xsel] )); then - xsel --clipboard --output - else - print "clipcopy: Platform $OSTYPE not supported or xclip/xsel not installed" >&2 - return 1 - fi + function _retry_clipboard_detection_or_fail() { + local clipcmd="${1}"; shift + if detect-clipboard; then + "${clipcmd}" "$@" + else + print "${clipcmd}: Platform $OSTYPE not supported or xclip/xsel not installed" >&2 + return 1 + fi + } + function clipcopy() { _retry_clipboard_detection_or_fail clipcopy "$@"; } + function clippaste() { _retry_clipboard_detection_or_fail clippaste "$@"; } + return 1 fi } + +# Detect at startup. A non-zero exit here indicates that the dummy clipboards were set, +# which is not really an error. If the user calls them, they will attempt to redetect +# (for example, perhaps the user has now installed xclip) and then either print an error +# or proceed successfully. +detect-clipboard || true diff --git a/lib/compfix.zsh b/lib/compfix.zsh index 68decc1ed..b09b283f2 100644 --- a/lib/compfix.zsh +++ b/lib/compfix.zsh @@ -18,7 +18,7 @@ function handle_completion_insecurities() { insecure_dirs=( ${(f@):-"$(compaudit 2>/dev/null)"} ) # If no such directories exist, get us out of here. - (( ! ${#insecure_dirs} )) && return + [[ -z "${insecure_dirs}" ]] && return # List ownership and permissions of all insecure directories. print "[oh-my-zsh] Insecure completion-dependent directories detected:" diff --git a/lib/completion.zsh b/lib/completion.zsh index c7db2eb7b..ebaab0856 100644 --- a/lib/completion.zsh +++ b/lib/completion.zsh @@ -32,17 +32,17 @@ zstyle ':completion:*' list-colors '' zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#) ([0-9a-z-]#)*=01;34=0=01' if [[ "$OSTYPE" = solaris* ]]; then - zstyle ':completion:*:*:*:*:processes' command "ps -u $USER -o pid,user,comm" + zstyle ':completion:*:*:*:*:processes' command "ps -u $USERNAME -o pid,user,comm" else - zstyle ':completion:*:*:*:*:processes' command "ps -u $USER -o pid,user,comm -w -w" + zstyle ':completion:*:*:*:*:processes' command "ps -u $USERNAME -o pid,user,comm -w -w" fi # disable named-directories autocompletion zstyle ':completion:*:cd:*' tag-order local-directories directory-stack path-directories # Use caching so that commands like apt and dpkg complete are useable -zstyle ':completion::complete:*' use-cache 1 -zstyle ':completion::complete:*' cache-path $ZSH_CACHE_DIR +zstyle ':completion:*' use-cache yes +zstyle ':completion:*' cache-path $ZSH_CACHE_DIR # Don't complete uninteresting users zstyle ':completion:*:*:*:users' ignored-patterns \ @@ -60,14 +60,16 @@ zstyle '*' single-ignored show if [[ $COMPLETION_WAITING_DOTS = true ]]; then expand-or-complete-with-dots() { - # toggle line-wrapping off and back on again - [[ -n "$terminfo[rmam]" && -n "$terminfo[smam]" ]] && echoti rmam - print -Pn "%{%F{red}......%f%}" - [[ -n "$terminfo[rmam]" && -n "$terminfo[smam]" ]] && echoti smam - + print -Pn "%F{red}…%f" zle expand-or-complete zle redisplay } zle -N expand-or-complete-with-dots - bindkey "^I" expand-or-complete-with-dots + # Set the function as the default tab completion widget + bindkey -M emacs "^I" expand-or-complete-with-dots + bindkey -M viins "^I" expand-or-complete-with-dots + bindkey -M vicmd "^I" expand-or-complete-with-dots fi + +# automatically load bash completion functions +autoload -U +X bashcompinit && bashcompinit diff --git a/lib/diagnostics.zsh b/lib/diagnostics.zsh index 9c9905e4d..650520797 100644 --- a/lib/diagnostics.zsh +++ b/lib/diagnostics.zsh @@ -112,7 +112,7 @@ function _omz_diag_dump_one_big_text() { command uname -a builtin echo OSTYPE=$OSTYPE builtin echo ZSH_VERSION=$ZSH_VERSION - builtin echo User: $USER + builtin echo User: $USERNAME builtin echo umask: $(umask) builtin echo _omz_diag_dump_os_specific_version @@ -192,19 +192,19 @@ function _omz_diag_dump_one_big_text() { command ls -ld ~/.oh* builtin echo builtin echo oh-my-zsh git state: - (cd $ZSH && builtin echo "HEAD: $(git rev-parse HEAD)" && git remote -v && git status | command grep "[^[:space:]]") + (builtin cd $ZSH && builtin echo "HEAD: $(git rev-parse HEAD)" && git remote -v && git status | command grep "[^[:space:]]") if [[ $verbose -ge 1 ]]; then - (cd $ZSH && git reflog --date=default | command grep pull) + (builtin cd $ZSH && git reflog --date=default | command grep pull) fi builtin echo if [[ -e $ZSH_CUSTOM ]]; then local custom_dir=$ZSH_CUSTOM if [[ -h $custom_dir ]]; then - custom_dir=$(cd $custom_dir && pwd -P) + custom_dir=$(builtin cd $custom_dir && pwd -P) fi builtin echo "oh-my-zsh custom dir:" builtin echo " $ZSH_CUSTOM ($custom_dir)" - (cd ${custom_dir:h} && command find ${custom_dir:t} -name .git -prune -o -print) + (builtin cd ${custom_dir:h} && command find ${custom_dir:t} -name .git -prune -o -print) builtin echo fi diff --git a/lib/directories.zsh b/lib/directories.zsh index 14064b86f..cf87bd7e4 100644 --- a/lib/directories.zsh +++ b/lib/directories.zsh @@ -21,7 +21,15 @@ alias 9='cd -9' alias md='mkdir -p' alias rd=rmdir -alias d='dirs -v | head -10' + +function d () { + if [[ -n $1 ]]; then + dirs "$@" + else + dirs -v | head -10 + fi +} +compdef _dirs d # List directory contents alias lsa='ls -lah' diff --git a/lib/functions.zsh b/lib/functions.zsh index 4ef8920f6..9cc735196 100644 --- a/lib/functions.zsh +++ b/lib/functions.zsh @@ -1,16 +1,19 @@ function zsh_stats() { - fc -l 1 | awk '{CMD[$2]++;count++;}END { for (a in CMD)print CMD[a] " " CMD[a]/count*100 "% " a;}' | grep -v "./" | column -c3 -s " " -t | sort -nr | nl | head -n20 + fc -l 1 \ + | awk '{ CMD[$2]++; count++; } END { for (a in CMD) print CMD[a] " " CMD[a]*100/count "% " a }' \ + | grep -v "./" | sort -nr | head -20 | column -c3 -s " " -t | nl } function uninstall_oh_my_zsh() { - env ZSH=$ZSH sh $ZSH/tools/uninstall.sh + env ZSH="$ZSH" sh "$ZSH/tools/uninstall.sh" } function upgrade_oh_my_zsh() { - env ZSH=$ZSH sh $ZSH/tools/upgrade.sh + echo >&2 "${fg[yellow]}Note: \`$0\` is deprecated. Use \`omz update\` instead.$reset_color" + omz update } -function take() { +function takedir() { mkdir -p $@ && cd ${@:$#} } @@ -21,7 +24,7 @@ function open_command() { case "$OSTYPE" in darwin*) open_cmd='open' ;; cygwin*) open_cmd='cygstart' ;; - linux*) ! [[ $(uname -a) =~ "Microsoft" ]] && open_cmd='xdg-open' || { + linux*) [[ "$(uname -r)" != *icrosoft* ]] && open_cmd='nohup xdg-open' || { open_cmd='cmd.exe /c start ""' [[ -e "$1" ]] && { 1="$(wslpath -w "${1:a}")" || return 1 } } ;; @@ -31,11 +34,30 @@ function open_command() { ;; esac - # don't use nohup on OSX - if [[ "$OSTYPE" == darwin* ]]; then - ${=open_cmd} "$@" &>/dev/null + ${=open_cmd} "$@" &>/dev/null +} + +function takeurl() { + data=$(mktemp) + curl -L $1 > $data + tar xf $data + thedir=$(tar tf $data | head -1) + rm $data + cd $thedir +} + +function takegit() { + git clone $1 + cd $(basename ${1%%.git}) +} + +function take() { + if [[ $1 =~ ^(https?|ftp).*\.tar\.(gz|bz2|xz)$ ]]; then + takeurl $1 + elif [[ $1 =~ ^([A-Za-z0-9]\+@|https?|git|ssh|ftps?|rsync).*\.git/?$ ]]; then + takegit $1 else - nohup ${=open_cmd} "$@" &>/dev/null + takedir $1 fi } @@ -79,7 +101,7 @@ function try_alias_value() { # 0 if the variable exists, 3 if it was set # function default() { - test `typeset +m "$1"` && return 0 + (( $+parameters[$1] )) && return 0 typeset -g "$1"="$2" && return 3 } @@ -93,8 +115,8 @@ function default() { # 0 if the env variable exists, 3 if it was set # function env_default() { - env | grep -q "^$1=" && return 0 - export "$1=$2" && return 3 + [[ ${parameters[$1]} = *-export* ]] && return 0 + export "$1=$2" && return 3 } @@ -127,6 +149,7 @@ zmodload zsh/langinfo # -P causes spaces to be encoded as '%20' instead of '+' function omz_urlencode() { emulate -L zsh + local -a opts zparseopts -D -E -a opts r m P local in_str=$1 diff --git a/lib/git.zsh b/lib/git.zsh index b92373153..c9363274c 100644 --- a/lib/git.zsh +++ b/lib/git.zsh @@ -1,26 +1,57 @@ -# Outputs current branch info in prompt format +# The git prompt's git commands are read-only and should not interfere with +# other processes. This environment variable is equivalent to running with `git +# --no-optional-locks`, but falls back gracefully for older versions of git. +# See git(1) for and git-status(1) for a description of that flag. +# +# We wrap in a local function instead of exporting the variable directly in +# order to avoid interfering with manually-run git commands by the user. +function __git_prompt_git() { + GIT_OPTIONAL_LOCKS=0 command git "$@" +} + function git_prompt_info() { + # If we are on a folder not tracked by git, get out. + # Otherwise, check for hide-info at global and local repository level + if ! __git_prompt_git rev-parse --git-dir &> /dev/null \ + || [[ "$(__git_prompt_git config --get oh-my-zsh.hide-info 2>/dev/null)" == 1 ]]; then + return 0 + fi + local ref - if [[ "$(command git config --get oh-my-zsh.hide-status 2>/dev/null)" != "1" ]]; then - ref=$(command git symbolic-ref HEAD 2> /dev/null) || \ - ref=$(command git rev-parse --short HEAD 2> /dev/null) || return 0 - echo "$ZSH_THEME_GIT_PROMPT_PREFIX${ref#refs/heads/}$(parse_git_dirty)$ZSH_THEME_GIT_PROMPT_SUFFIX" + ref=$(__git_prompt_git symbolic-ref --short HEAD 2> /dev/null) \ + || ref=$(__git_prompt_git rev-parse --short HEAD 2> /dev/null) \ + || return 0 + + # Use global ZSH_THEME_GIT_SHOW_UPSTREAM=1 for including upstream remote info + local upstream + if (( ${+ZSH_THEME_GIT_SHOW_UPSTREAM} )); then + upstream=$(__git_prompt_git rev-parse --abbrev-ref --symbolic-full-name "@{upstream}" 2>/dev/null) \ + && upstream=" -> ${upstream}" fi + + echo "${ZSH_THEME_GIT_PROMPT_PREFIX}${ref}${upstream}$(parse_git_dirty)${ZSH_THEME_GIT_PROMPT_SUFFIX}" } # Checks if working tree is dirty function parse_git_dirty() { - local STATUS='' + local STATUS local -a FLAGS FLAGS=('--porcelain') - if [[ "$(command git config --get oh-my-zsh.hide-dirty)" != "1" ]]; then - if [[ $POST_1_7_2_GIT -gt 0 ]]; then - FLAGS+='--ignore-submodules=dirty' - fi - if [[ "$DISABLE_UNTRACKED_FILES_DIRTY" == "true" ]]; then + if [[ "$(__git_prompt_git config --get oh-my-zsh.hide-dirty)" != "1" ]]; then + if [[ "${DISABLE_UNTRACKED_FILES_DIRTY:-}" == "true" ]]; then FLAGS+='--untracked-files=no' fi - STATUS=$(command git status ${FLAGS} 2> /dev/null | tail -n1) + case "${GIT_STATUS_IGNORE_SUBMODULES:-}" in + git) + # let git decide (this respects per-repo config in .gitmodules) + ;; + *) + # if unset: ignore dirty submodules + # other values are passed to --ignore-submodules + FLAGS+="--ignore-submodules=${GIT_STATUS_IGNORE_SUBMODULES:-dirty}" + ;; + esac + STATUS=$(__git_prompt_git status ${FLAGS} 2> /dev/null | tail -1) fi if [[ -n $STATUS ]]; then echo "$ZSH_THEME_GIT_PROMPT_DIRTY" @@ -32,10 +63,10 @@ function parse_git_dirty() { # Gets the difference between the local and remote branches function git_remote_status() { local remote ahead behind git_remote_status git_remote_status_detailed - remote=${$(command git rev-parse --verify ${hook_com[branch]}@{upstream} --symbolic-full-name 2>/dev/null)/refs\/remotes\/} + remote=${$(__git_prompt_git rev-parse --verify ${hook_com[branch]}@{upstream} --symbolic-full-name 2>/dev/null)/refs\/remotes\/} if [[ -n ${remote} ]]; then - ahead=$(command git rev-list ${hook_com[branch]}@{upstream}..HEAD 2>/dev/null | wc -l) - behind=$(command git rev-list HEAD..${hook_com[branch]}@{upstream} 2>/dev/null | wc -l) + ahead=$(__git_prompt_git rev-list ${hook_com[branch]}@{upstream}..HEAD 2>/dev/null | wc -l) + behind=$(__git_prompt_git rev-list HEAD..${hook_com[branch]}@{upstream} 2>/dev/null | wc -l) if [[ $ahead -eq 0 ]] && [[ $behind -eq 0 ]]; then git_remote_status="$ZSH_THEME_GIT_PROMPT_EQUAL_REMOTE" @@ -64,11 +95,11 @@ function git_remote_status() { # it's not a symbolic ref, but in a Git repo. function git_current_branch() { local ref - ref=$(command git symbolic-ref --quiet HEAD 2> /dev/null) + ref=$(__git_prompt_git symbolic-ref --quiet HEAD 2> /dev/null) local ret=$? if [[ $ret != 0 ]]; then [[ $ret == 128 ]] && return # no git repo. - ref=$(command git rev-parse --short HEAD 2> /dev/null) || return + ref=$(__git_prompt_git rev-parse --short HEAD 2> /dev/null) || return fi echo ${ref#refs/heads/} } @@ -76,8 +107,8 @@ function git_current_branch() { # Gets the number of commits ahead from remote function git_commits_ahead() { - if command git rev-parse --git-dir &>/dev/null; then - local commits="$(git rev-list --count @{upstream}..HEAD 2>/dev/null)" + if __git_prompt_git rev-parse --git-dir &>/dev/null; then + local commits="$(__git_prompt_git rev-list --count @{upstream}..HEAD 2>/dev/null)" if [[ -n "$commits" && "$commits" != 0 ]]; then echo "$ZSH_THEME_GIT_COMMITS_AHEAD_PREFIX$commits$ZSH_THEME_GIT_COMMITS_AHEAD_SUFFIX" fi @@ -86,8 +117,8 @@ function git_commits_ahead() { # Gets the number of commits behind remote function git_commits_behind() { - if command git rev-parse --git-dir &>/dev/null; then - local commits="$(git rev-list --count HEAD..@{upstream} 2>/dev/null)" + if __git_prompt_git rev-parse --git-dir &>/dev/null; then + local commits="$(__git_prompt_git rev-list --count HEAD..@{upstream} 2>/dev/null)" if [[ -n "$commits" && "$commits" != 0 ]]; then echo "$ZSH_THEME_GIT_COMMITS_BEHIND_PREFIX$commits$ZSH_THEME_GIT_COMMITS_BEHIND_SUFFIX" fi @@ -96,21 +127,21 @@ function git_commits_behind() { # Outputs if current branch is ahead of remote function git_prompt_ahead() { - if [[ -n "$(command git rev-list origin/$(git_current_branch)..HEAD 2> /dev/null)" ]]; then + if [[ -n "$(__git_prompt_git rev-list origin/$(git_current_branch)..HEAD 2> /dev/null)" ]]; then echo "$ZSH_THEME_GIT_PROMPT_AHEAD" fi } # Outputs if current branch is behind remote function git_prompt_behind() { - if [[ -n "$(command git rev-list HEAD..origin/$(git_current_branch) 2> /dev/null)" ]]; then + if [[ -n "$(__git_prompt_git rev-list HEAD..origin/$(git_current_branch) 2> /dev/null)" ]]; then echo "$ZSH_THEME_GIT_PROMPT_BEHIND" fi } # Outputs if current branch exists on remote or not function git_prompt_remote() { - if [[ -n "$(command git show-ref origin/$(git_current_branch) 2> /dev/null)" ]]; then + if [[ -n "$(__git_prompt_git show-ref origin/$(git_current_branch) 2> /dev/null)" ]]; then echo "$ZSH_THEME_GIT_PROMPT_REMOTE_EXISTS" else echo "$ZSH_THEME_GIT_PROMPT_REMOTE_MISSING" @@ -120,102 +151,130 @@ function git_prompt_remote() { # Formats prompt string for current git commit short SHA function git_prompt_short_sha() { local SHA - SHA=$(command git rev-parse --short HEAD 2> /dev/null) && echo "$ZSH_THEME_GIT_PROMPT_SHA_BEFORE$SHA$ZSH_THEME_GIT_PROMPT_SHA_AFTER" + SHA=$(__git_prompt_git rev-parse --short HEAD 2> /dev/null) && echo "$ZSH_THEME_GIT_PROMPT_SHA_BEFORE$SHA$ZSH_THEME_GIT_PROMPT_SHA_AFTER" } # Formats prompt string for current git commit long SHA function git_prompt_long_sha() { local SHA - SHA=$(command git rev-parse HEAD 2> /dev/null) && echo "$ZSH_THEME_GIT_PROMPT_SHA_BEFORE$SHA$ZSH_THEME_GIT_PROMPT_SHA_AFTER" + SHA=$(__git_prompt_git rev-parse HEAD 2> /dev/null) && echo "$ZSH_THEME_GIT_PROMPT_SHA_BEFORE$SHA$ZSH_THEME_GIT_PROMPT_SHA_AFTER" } -# Get the status of the working tree function git_prompt_status() { - local INDEX STATUS - INDEX=$(command git status --porcelain -b 2> /dev/null) - STATUS="" - if $(echo "$INDEX" | command grep -E '^\?\? ' &> /dev/null); then - STATUS="$ZSH_THEME_GIT_PROMPT_UNTRACKED$STATUS" - fi - if $(echo "$INDEX" | grep '^A ' &> /dev/null); then - STATUS="$ZSH_THEME_GIT_PROMPT_ADDED$STATUS" - elif $(echo "$INDEX" | grep '^M ' &> /dev/null); then - STATUS="$ZSH_THEME_GIT_PROMPT_ADDED$STATUS" - elif $(echo "$INDEX" | grep '^MM ' &> /dev/null); then - STATUS="$ZSH_THEME_GIT_PROMPT_ADDED$STATUS" - fi - if $(echo "$INDEX" | grep '^ M ' &> /dev/null); then - STATUS="$ZSH_THEME_GIT_PROMPT_MODIFIED$STATUS" - elif $(echo "$INDEX" | grep '^AM ' &> /dev/null); then - STATUS="$ZSH_THEME_GIT_PROMPT_MODIFIED$STATUS" - elif $(echo "$INDEX" | grep '^MM ' &> /dev/null); then - STATUS="$ZSH_THEME_GIT_PROMPT_MODIFIED$STATUS" - elif $(echo "$INDEX" | grep '^ T ' &> /dev/null); then - STATUS="$ZSH_THEME_GIT_PROMPT_MODIFIED$STATUS" - fi - if $(echo "$INDEX" | grep '^R ' &> /dev/null); then - STATUS="$ZSH_THEME_GIT_PROMPT_RENAMED$STATUS" - fi - if $(echo "$INDEX" | grep '^ D ' &> /dev/null); then - STATUS="$ZSH_THEME_GIT_PROMPT_DELETED$STATUS" - elif $(echo "$INDEX" | grep '^D ' &> /dev/null); then - STATUS="$ZSH_THEME_GIT_PROMPT_DELETED$STATUS" - elif $(echo "$INDEX" | grep '^AD ' &> /dev/null); then - STATUS="$ZSH_THEME_GIT_PROMPT_DELETED$STATUS" - fi - if $(command git rev-parse --verify refs/stash >/dev/null 2>&1); then - STATUS="$ZSH_THEME_GIT_PROMPT_STASHED$STATUS" - fi - if $(echo "$INDEX" | grep '^UU ' &> /dev/null); then - STATUS="$ZSH_THEME_GIT_PROMPT_UNMERGED$STATUS" - fi - if $(echo "$INDEX" | grep '^## [^ ]\+ .*ahead' &> /dev/null); then - STATUS="$ZSH_THEME_GIT_PROMPT_AHEAD$STATUS" + [[ "$(__git_prompt_git config --get oh-my-zsh.hide-status 2>/dev/null)" = 1 ]] && return + + # Maps a git status prefix to an internal constant + # This cannot use the prompt constants, as they may be empty + local -A prefix_constant_map + prefix_constant_map=( + '\?\? ' 'UNTRACKED' + 'A ' 'ADDED' + 'M ' 'ADDED' + 'MM ' 'MODIFIED' + ' M ' 'MODIFIED' + 'AM ' 'MODIFIED' + ' T ' 'MODIFIED' + 'R ' 'RENAMED' + ' D ' 'DELETED' + 'D ' 'DELETED' + 'UU ' 'UNMERGED' + 'ahead' 'AHEAD' + 'behind' 'BEHIND' + 'diverged' 'DIVERGED' + 'stashed' 'STASHED' + ) + + # Maps the internal constant to the prompt theme + local -A constant_prompt_map + constant_prompt_map=( + 'UNTRACKED' "$ZSH_THEME_GIT_PROMPT_UNTRACKED" + 'ADDED' "$ZSH_THEME_GIT_PROMPT_ADDED" + 'MODIFIED' "$ZSH_THEME_GIT_PROMPT_MODIFIED" + 'RENAMED' "$ZSH_THEME_GIT_PROMPT_RENAMED" + 'DELETED' "$ZSH_THEME_GIT_PROMPT_DELETED" + 'UNMERGED' "$ZSH_THEME_GIT_PROMPT_UNMERGED" + 'AHEAD' "$ZSH_THEME_GIT_PROMPT_AHEAD" + 'BEHIND' "$ZSH_THEME_GIT_PROMPT_BEHIND" + 'DIVERGED' "$ZSH_THEME_GIT_PROMPT_DIVERGED" + 'STASHED' "$ZSH_THEME_GIT_PROMPT_STASHED" + ) + + # The order that the prompt displays should be added to the prompt + local status_constants + status_constants=( + UNTRACKED ADDED MODIFIED RENAMED DELETED + STASHED UNMERGED AHEAD BEHIND DIVERGED + ) + + local status_text="$(__git_prompt_git status --porcelain -b 2> /dev/null)" + + # Don't continue on a catastrophic failure + if [[ $? -eq 128 ]]; then + return 1 fi - if $(echo "$INDEX" | grep '^## [^ ]\+ .*behind' &> /dev/null); then - STATUS="$ZSH_THEME_GIT_PROMPT_BEHIND$STATUS" + + # A lookup table of each git status encountered + local -A statuses_seen + + if __git_prompt_git rev-parse --verify refs/stash &>/dev/null; then + statuses_seen[STASHED]=1 fi - if $(echo "$INDEX" | grep '^## [^ ]\+ .*diverged' &> /dev/null); then - STATUS="$ZSH_THEME_GIT_PROMPT_DIVERGED$STATUS" + + local status_lines + status_lines=("${(@f)${status_text}}") + + # If the tracking line exists, get and parse it + if [[ "$status_lines[1]" =~ "^## [^ ]+ \[(.*)\]" ]]; then + local branch_statuses + branch_statuses=("${(@s/,/)match}") + for branch_status in $branch_statuses; do + if [[ ! $branch_status =~ "(behind|diverged|ahead) ([0-9]+)?" ]]; then + continue + fi + local last_parsed_status=$prefix_constant_map[$match[1]] + statuses_seen[$last_parsed_status]=$match[2] + done fi - echo $STATUS -} - -# Compares the provided version of git to the version installed and on path -# Outputs -1, 0, or 1 if the installed version is less than, equal to, or -# greater than the input version, respectively. -function git_compare_version() { - local INPUT_GIT_VERSION INSTALLED_GIT_VERSION i - INPUT_GIT_VERSION=(${(s/./)1}) - INSTALLED_GIT_VERSION=($(command git --version 2>/dev/null)) - INSTALLED_GIT_VERSION=(${(s/./)INSTALLED_GIT_VERSION[3]}) - - for i in {1..3}; do - if [[ $INSTALLED_GIT_VERSION[$i] -gt $INPUT_GIT_VERSION[$i] ]]; then - echo 1 - return 0 + + # For each status prefix, do a regex comparison + for status_prefix in ${(k)prefix_constant_map}; do + local status_constant="${prefix_constant_map[$status_prefix]}" + local status_regex=$'(^|\n)'"$status_prefix" + + if [[ "$status_text" =~ $status_regex ]]; then + statuses_seen[$status_constant]=1 fi - if [[ $INSTALLED_GIT_VERSION[$i] -lt $INPUT_GIT_VERSION[$i] ]]; then - echo -1 - return 0 + done + + # Display the seen statuses in the order specified + local status_prompt + for status_constant in $status_constants; do + if (( ${+statuses_seen[$status_constant]} )); then + local next_display=$constant_prompt_map[$status_constant] + status_prompt="$next_display$status_prompt" fi done - echo 0 + + echo $status_prompt } # Outputs the name of the current user # Usage example: $(git_current_user_name) function git_current_user_name() { - command git config user.name 2>/dev/null + __git_prompt_git config user.name 2>/dev/null } # Outputs the email of the current user # Usage example: $(git_current_user_email) function git_current_user_email() { - command git config user.email 2>/dev/null + __git_prompt_git config user.email 2>/dev/null } -# This is unlikely to change so make it all statically assigned -POST_1_7_2_GIT=$(git_compare_version "1.7.2") -# Clean up the namespace slightly by removing the checker function -unfunction git_compare_version +# Output the name of the root directory of the git repository +# Usage example: $(git_repo_name) +function git_repo_name() { + local repo_path + if repo_path="$(__git_prompt_git rev-parse --show-toplevel 2>/dev/null)" && [[ -n "$repo_path" ]]; then + echo ${repo_path:t} + fi +} diff --git a/lib/grep.zsh b/lib/grep.zsh index abc1650a1..a725e0f26 100644 --- a/lib/grep.zsh +++ b/lib/grep.zsh @@ -1,28 +1,41 @@ -# is x grep argument available? -grep-flag-available() { - echo | grep $1 "" >/dev/null 2>&1 -} +__GREP_CACHE_FILE="$ZSH_CACHE_DIR"/grep-alias -GREP_OPTIONS="" +# See if there's a cache file modified in the last day +__GREP_ALIAS_CACHES=("$__GREP_CACHE_FILE"(Nm-1)) +if [[ -n "$__GREP_ALIAS_CACHES" ]]; then + source "$__GREP_CACHE_FILE" +else + grep-flags-available() { + command grep "$@" "" &>/dev/null <<< "" + } -# color grep results -if grep-flag-available --color=auto; then - GREP_OPTIONS+=" --color=auto" -fi + # Ignore these folders (if the necessary grep flags are available) + EXC_FOLDERS="{.bzr,CVS,.git,.hg,.svn,.idea,.tox}" -# ignore VCS folders (if the necessary grep flags are available) -VCS_FOLDERS="{.bzr,CVS,.git,.hg,.svn}" + # Check for --exclude-dir, otherwise check for --exclude. If --exclude + # isn't available, --color won't be either (they were released at the same + # time (v2.5): https://git.savannah.gnu.org/cgit/grep.git/tree/NEWS?id=1236f007 + if grep-flags-available --color=auto --exclude-dir=.cvs; then + GREP_OPTIONS="--color=auto --exclude-dir=$EXC_FOLDERS" + elif grep-flags-available --color=auto --exclude=.cvs; then + GREP_OPTIONS="--color=auto --exclude=$EXC_FOLDERS" + fi -if grep-flag-available --exclude-dir=.cvs; then - GREP_OPTIONS+=" --exclude-dir=$VCS_FOLDERS" -elif grep-flag-available --exclude=.cvs; then - GREP_OPTIONS+=" --exclude=$VCS_FOLDERS" -fi + if [[ -n "$GREP_OPTIONS" ]]; then + # export grep, egrep and fgrep settings + alias grep="grep $GREP_OPTIONS" + alias egrep="egrep $GREP_OPTIONS" + alias fgrep="fgrep $GREP_OPTIONS" -# export grep settings -alias grep="grep $GREP_OPTIONS" + # write to cache file if cache directory is writable + if [[ -w "$ZSH_CACHE_DIR" ]]; then + alias -L grep egrep fgrep >| "$__GREP_CACHE_FILE" + fi + fi + + # Clean up + unset GREP_OPTIONS EXC_FOLDERS + unfunction grep-flags-available +fi -# clean up -unset GREP_OPTIONS -unset VCS_FOLDERS -unfunction grep-flag-available +unset __GREP_CACHE_FILE __GREP_ALIAS_CACHES diff --git a/lib/history.zsh b/lib/history.zsh index d8bbd41c4..794076904 100644 --- a/lib/history.zsh +++ b/lib/history.zsh @@ -6,18 +6,19 @@ function omz_history { if [[ -n "$clear" ]]; then # if -c provided, clobber the history file echo -n >| "$HISTFILE" - echo >&2 History file deleted. Reload the session to see its effects. + fc -p "$HISTFILE" + echo >&2 History file deleted. elif [[ -n "$list" ]]; then # if -l provided, run as if calling `fc' directly builtin fc "$@" else # unless a number is provided, show all history events (starting from 1) - [[ ${@[-1]} = *[0-9]* ]] && builtin fc -l "$@" || builtin fc -l "$@" 1 + [[ ${@[-1]-} = *[0-9]* ]] && builtin fc -l "$@" || builtin fc -l "$@" 1 fi } # Timestamp format -case $HIST_STAMPS in +case ${HIST_STAMPS-} in "mm/dd/yyyy") alias history='omz_history -f' ;; "dd.mm.yyyy") alias history='omz_history -E' ;; "yyyy-mm-dd") alias history='omz_history -i' ;; @@ -27,8 +28,8 @@ esac ## History file configuration [ -z "$HISTFILE" ] && HISTFILE="$HOME/.zsh_history" -HISTSIZE=50000 -SAVEHIST=10000 +[ "$HISTSIZE" -lt 50000 ] && HISTSIZE=50000 +[ "$SAVEHIST" -lt 10000 ] && SAVEHIST=10000 ## History command configuration setopt extended_history # record timestamp of command in HISTFILE @@ -36,5 +37,4 @@ setopt hist_expire_dups_first # delete duplicates first when HISTFILE size excee setopt hist_ignore_dups # ignore duplicated commands history list setopt hist_ignore_space # ignore commands that start with space setopt hist_verify # show command with history expansion to user before running it -setopt inc_append_history # add commands to HISTFILE in order of execution setopt share_history # share command history data diff --git a/lib/key-bindings.zsh b/lib/key-bindings.zsh index 0e056dc72..aaa73046e 100644 --- a/lib/key-bindings.zsh +++ b/lib/key-bindings.zsh @@ -15,56 +15,101 @@ if (( ${+terminfo[smkx]} )) && (( ${+terminfo[rmkx]} )); then zle -N zle-line-finish fi -bindkey -e # Use emacs key bindings +# Use emacs key bindings +bindkey -e -bindkey '\ew' kill-region # [Esc-w] - Kill from the cursor to the mark -bindkey -s '\el' 'ls\n' # [Esc-l] - run command: ls -bindkey '^r' history-incremental-search-backward # [Ctrl-r] - Search backward incrementally for a specified string. The string may begin with ^ to anchor the search to the beginning of the line. -if [[ "${terminfo[kpp]}" != "" ]]; then - bindkey "${terminfo[kpp]}" up-line-or-history # [PageUp] - Up a line of history +# [PageUp] - Up a line of history +if [[ -n "${terminfo[kpp]}" ]]; then + bindkey -M emacs "${terminfo[kpp]}" up-line-or-history + bindkey -M viins "${terminfo[kpp]}" up-line-or-history + bindkey -M vicmd "${terminfo[kpp]}" up-line-or-history fi -if [[ "${terminfo[knp]}" != "" ]]; then - bindkey "${terminfo[knp]}" down-line-or-history # [PageDown] - Down a line of history +# [PageDown] - Down a line of history +if [[ -n "${terminfo[knp]}" ]]; then + bindkey -M emacs "${terminfo[knp]}" down-line-or-history + bindkey -M viins "${terminfo[knp]}" down-line-or-history + bindkey -M vicmd "${terminfo[knp]}" down-line-or-history fi -# start typing + [Up-Arrow] - fuzzy find history forward -if [[ "${terminfo[kcuu1]}" != "" ]]; then +# Start typing + [Up-Arrow] - fuzzy find history forward +if [[ -n "${terminfo[kcuu1]}" ]]; then autoload -U up-line-or-beginning-search zle -N up-line-or-beginning-search - bindkey "${terminfo[kcuu1]}" up-line-or-beginning-search + + bindkey -M emacs "${terminfo[kcuu1]}" up-line-or-beginning-search + bindkey -M viins "${terminfo[kcuu1]}" up-line-or-beginning-search + bindkey -M vicmd "${terminfo[kcuu1]}" up-line-or-beginning-search fi -# start typing + [Down-Arrow] - fuzzy find history backward -if [[ "${terminfo[kcud1]}" != "" ]]; then +# Start typing + [Down-Arrow] - fuzzy find history backward +if [[ -n "${terminfo[kcud1]}" ]]; then autoload -U down-line-or-beginning-search zle -N down-line-or-beginning-search - bindkey "${terminfo[kcud1]}" down-line-or-beginning-search + + bindkey -M emacs "${terminfo[kcud1]}" down-line-or-beginning-search + bindkey -M viins "${terminfo[kcud1]}" down-line-or-beginning-search + bindkey -M vicmd "${terminfo[kcud1]}" down-line-or-beginning-search fi -if [[ "${terminfo[khome]}" != "" ]]; then - bindkey "${terminfo[khome]}" beginning-of-line # [Home] - Go to beginning of line +# [Home] - Go to beginning of line +if [[ -n "${terminfo[khome]}" ]]; then + bindkey -M emacs "${terminfo[khome]}" beginning-of-line + bindkey -M viins "${terminfo[khome]}" beginning-of-line + bindkey -M vicmd "${terminfo[khome]}" beginning-of-line fi -if [[ "${terminfo[kend]}" != "" ]]; then - bindkey "${terminfo[kend]}" end-of-line # [End] - Go to end of line +# [End] - Go to end of line +if [[ -n "${terminfo[kend]}" ]]; then + bindkey -M emacs "${terminfo[kend]}" end-of-line + bindkey -M viins "${terminfo[kend]}" end-of-line + bindkey -M vicmd "${terminfo[kend]}" end-of-line fi -bindkey ' ' magic-space # [Space] - do history expansion - -bindkey '^[[1;5C' forward-word # [Ctrl-RightArrow] - move forward one word -bindkey '^[[1;5D' backward-word # [Ctrl-LeftArrow] - move backward one word - -if [[ "${terminfo[kcbt]}" != "" ]]; then - bindkey "${terminfo[kcbt]}" reverse-menu-complete # [Shift-Tab] - move through the completion menu backwards +# [Shift-Tab] - move through the completion menu backwards +if [[ -n "${terminfo[kcbt]}" ]]; then + bindkey -M emacs "${terminfo[kcbt]}" reverse-menu-complete + bindkey -M viins "${terminfo[kcbt]}" reverse-menu-complete + bindkey -M vicmd "${terminfo[kcbt]}" reverse-menu-complete fi -bindkey '^?' backward-delete-char # [Backspace] - delete backward -if [[ "${terminfo[kdch1]}" != "" ]]; then - bindkey "${terminfo[kdch1]}" delete-char # [Delete] - delete forward +# [Backspace] - delete backward +bindkey -M emacs '^?' backward-delete-char +bindkey -M viins '^?' backward-delete-char +bindkey -M vicmd '^?' backward-delete-char +# [Delete] - delete forward +if [[ -n "${terminfo[kdch1]}" ]]; then + bindkey -M emacs "${terminfo[kdch1]}" delete-char + bindkey -M viins "${terminfo[kdch1]}" delete-char + bindkey -M vicmd "${terminfo[kdch1]}" delete-char else - bindkey "^[[3~" delete-char - bindkey "^[3;5~" delete-char - bindkey "\e[3~" delete-char + bindkey -M emacs "^[[3~" delete-char + bindkey -M viins "^[[3~" delete-char + bindkey -M vicmd "^[[3~" delete-char + + bindkey -M emacs "^[3;5~" delete-char + bindkey -M viins "^[3;5~" delete-char + bindkey -M vicmd "^[3;5~" delete-char fi +# [Ctrl-Delete] - delete whole forward-word +bindkey -M emacs '^[[3;5~' kill-word +bindkey -M viins '^[[3;5~' kill-word +bindkey -M vicmd '^[[3;5~' kill-word + +# [Ctrl-RightArrow] - move forward one word +bindkey -M emacs '^[[1;5C' forward-word +bindkey -M viins '^[[1;5C' forward-word +bindkey -M vicmd '^[[1;5C' forward-word +# [Ctrl-LeftArrow] - move backward one word +bindkey -M emacs '^[[1;5D' backward-word +bindkey -M viins '^[[1;5D' backward-word +bindkey -M vicmd '^[[1;5D' backward-word + + +bindkey '\ew' kill-region # [Esc-w] - Kill from the cursor to the mark +bindkey -s '\el' 'ls\n' # [Esc-l] - run command: ls +bindkey '^r' history-incremental-search-backward # [Ctrl-r] - Search backward incrementally for a specified string. The string may begin with ^ to anchor the search to the beginning of the line. +bindkey ' ' magic-space # [Space] - don't do history expansion + + # Edit the current command line in $EDITOR autoload -U edit-command-line zle -N edit-command-line diff --git a/lib/misc.zsh b/lib/misc.zsh index f45c10757..a5d3af998 100644 --- a/lib/misc.zsh +++ b/lib/misc.zsh @@ -1,17 +1,17 @@ -## Load smart urls if available -# bracketed-paste-magic is known buggy in zsh 5.1.1 (only), so skip it there; see #4434 autoload -Uz is-at-least -if [[ $ZSH_VERSION != 5.1.1 ]]; then + +# *-magic is known buggy in some versions; disable if so +if [[ $DISABLE_MAGIC_FUNCTIONS != true ]]; then for d in $fpath; do - if [[ -e "$d/url-quote-magic" ]]; then - if is-at-least 5.1; then - autoload -Uz bracketed-paste-magic - zle -N bracketed-paste bracketed-paste-magic - fi - autoload -Uz url-quote-magic - zle -N self-insert url-quote-magic - break - fi + if [[ -e "$d/url-quote-magic" ]]; then + if is-at-least 5.1; then + autoload -Uz bracketed-paste-magic + zle -N bracketed-paste bracketed-paste-magic + fi + autoload -Uz url-quote-magic + zle -N self-insert url-quote-magic + break + fi done fi @@ -22,20 +22,14 @@ env_default 'PAGER' 'less' env_default 'LESS' '-R' ## super user alias -alias _='sudo' -alias please='sudo' +alias _='sudo ' ## more intelligent acking for ubuntu users -if which ack-grep &> /dev/null; then +if (( $+commands[ack-grep] )); then alias afind='ack-grep -il' else alias afind='ack -il' fi -# only define LC_CTYPE if undefined -if [[ -z "$LC_CTYPE" && -z "$LC_ALL" ]]; then - export LC_CTYPE=${LANG%%:*} # pick the first entry from LANG -fi - # recognize comments setopt interactivecomments diff --git a/lib/nvm.zsh b/lib/nvm.zsh index 4a8b6811e..2fe57a8f4 100644 --- a/lib/nvm.zsh +++ b/lib/nvm.zsh @@ -1,9 +1,6 @@ -# get the node.js version +# get the nvm-controlled node.js version function nvm_prompt_info() { - [[ -f "$NVM_DIR/nvm.sh" ]] || return - local nvm_prompt - nvm_prompt=$(node -v 2>/dev/null) - [[ "${nvm_prompt}x" == "x" ]] && return - nvm_prompt=${nvm_prompt:1} + which nvm &>/dev/null || return + local nvm_prompt=${$(nvm current)#v} echo "${ZSH_THEME_NVM_PROMPT_PREFIX}${nvm_prompt}${ZSH_THEME_NVM_PROMPT_SUFFIX}" } diff --git a/lib/prompt_info_functions.zsh b/lib/prompt_info_functions.zsh index 1d5c23e41..48f033da6 100644 --- a/lib/prompt_info_functions.zsh +++ b/lib/prompt_info_functions.zsh @@ -10,9 +10,16 @@ # Dummy implementations that return false to prevent command_not_found # errors with themes, that implement these functions # Real implementations will be used when the respective plugins are loaded -function chruby_prompt_info hg_prompt_info pyenv_prompt_info \ - rbenv_prompt_info svn_prompt_info vi_mode_prompt_info \ - virtualenv_prompt_info jenv_prompt_info { +function chruby_prompt_info \ + rbenv_prompt_info \ + hg_prompt_info \ + pyenv_prompt_info \ + svn_prompt_info \ + vi_mode_prompt_info \ + virtualenv_prompt_info \ + jenv_prompt_info \ + tf_prompt_info \ +{ return 1 } @@ -22,10 +29,13 @@ function rvm_prompt_info() { [ -f $HOME/.rvm/bin/rvm-prompt ] || return 1 local rvm_prompt rvm_prompt=$($HOME/.rvm/bin/rvm-prompt ${=ZSH_THEME_RVM_PROMPT_OPTIONS} 2>/dev/null) - [[ "${rvm_prompt}x" == "x" ]] && return 1 - echo "${ZSH_THEME_RVM_PROMPT_PREFIX:=(}${rvm_prompt}${ZSH_THEME_RVM_PROMPT_SUFFIX:=)}" + [[ -z "${rvm_prompt}" ]] && return 1 + echo "${ZSH_THEME_RUBY_PROMPT_PREFIX}${rvm_prompt}${ZSH_THEME_RUBY_PROMPT_SUFFIX}" } +ZSH_THEME_RVM_PROMPT_OPTIONS="i v g" + + # use this to enable users to see their ruby version, no matter which # version management system they use function ruby_prompt_info() { diff --git a/lib/spectrum.zsh b/lib/spectrum.zsh index 312ab2248..d5c22a8c5 100644 --- a/lib/spectrum.zsh +++ b/lib/spectrum.zsh @@ -1,4 +1,3 @@ -#! /bin/zsh # A script to make using 256 colors in zsh less painful. # P.C. Shyamshankar <sykora@lucentbeing.com> # Copied from https://github.com/sykora/etc/blob/master/zsh/functions/spectrum/ @@ -6,32 +5,31 @@ typeset -AHg FX FG BG FX=( - reset "%{[00m%}" - bold "%{[01m%}" no-bold "%{[22m%}" - italic "%{[03m%}" no-italic "%{[23m%}" - underline "%{[04m%}" no-underline "%{[24m%}" - blink "%{[05m%}" no-blink "%{[25m%}" - reverse "%{[07m%}" no-reverse "%{[27m%}" + reset "%{[00m%}" + bold "%{[01m%}" no-bold "%{[22m%}" + italic "%{[03m%}" no-italic "%{[23m%}" + underline "%{[04m%}" no-underline "%{[24m%}" + blink "%{[05m%}" no-blink "%{[25m%}" + reverse "%{[07m%}" no-reverse "%{[27m%}" ) for color in {000..255}; do - FG[$color]="%{[38;5;${color}m%}" - BG[$color]="%{[48;5;${color}m%}" + FG[$color]="%{[38;5;${color}m%}" + BG[$color]="%{[48;5;${color}m%}" done - -ZSH_SPECTRUM_TEXT=${ZSH_SPECTRUM_TEXT:-Arma virumque cano Troiae qui primus ab oris} - # Show all 256 colors with color number function spectrum_ls() { + local ZSH_SPECTRUM_TEXT=${ZSH_SPECTRUM_TEXT:-Arma virumque cano Troiae qui primus ab oris} for code in {000..255}; do - print -P -- "$code: %{$FG[$code]%}$ZSH_SPECTRUM_TEXT%{$reset_color%}" + print -P -- "$code: $FG[$code]$ZSH_SPECTRUM_TEXT%{$reset_color%}" done } # Show all 256 colors where the background is set to specific color function spectrum_bls() { + local ZSH_SPECTRUM_TEXT=${ZSH_SPECTRUM_TEXT:-Arma virumque cano Troiae qui primus ab oris} for code in {000..255}; do - print -P -- "$code: %{$BG[$code]%}$ZSH_SPECTRUM_TEXT%{$reset_color%}" + print -P -- "$code: $BG[$code]$ZSH_SPECTRUM_TEXT%{$reset_color%}" done } diff --git a/lib/termsupport.zsh b/lib/termsupport.zsh index 87d55ee89..33451ef1f 100644 --- a/lib/termsupport.zsh +++ b/lib/termsupport.zsh @@ -10,39 +10,39 @@ function title { emulate -L zsh setopt prompt_subst - [[ "$EMACS" == *term* ]] && return + [[ "$INSIDE_EMACS" == *term* ]] && return # if $2 is unset use $1 as default # if it is set and empty, leave it as is : ${2=$1} case "$TERM" in - cygwin|xterm*|putty*|rxvt*|ansi) - print -Pn "\e]2;$2:q\a" # set window name - print -Pn "\e]1;$1:q\a" # set tab name + cygwin|xterm*|putty*|rxvt*|konsole*|ansi|mlterm*|alacritty|st*) + print -Pn "\e]2;${2:q}\a" # set window name + print -Pn "\e]1;${1:q}\a" # set tab name ;; - screen*) - print -Pn "\ek$1:q\e\\" # set screen hardstatus + screen*|tmux*) + print -Pn "\ek${1:q}\e\\" # set screen hardstatus ;; *) if [[ "$TERM_PROGRAM" == "iTerm.app" ]]; then - print -Pn "\e]2;$2:q\a" # set window name - print -Pn "\e]1;$1:q\a" # set tab name + print -Pn "\e]2;${2:q}\a" # set window name + print -Pn "\e]1;${1:q}\a" # set tab name else # Try to use terminfo to set the title # If the feature is available set title if [[ -n "$terminfo[fsl]" ]] && [[ -n "$terminfo[tsl]" ]]; then - echoti tsl - print -Pn "$1" - echoti fsl - fi + echoti tsl + print -Pn "$1" + echoti fsl + fi fi ;; esac } ZSH_THEME_TERM_TAB_TITLE_IDLE="%15<..<%~%<<" #15 char left truncated PWD -ZSH_THEME_TERM_TITLE_IDLE="%n@%m: %~" +ZSH_THEME_TERM_TITLE_IDLE="%n@%m:%~" # Avoid duplication of directory in terminals with independent dir display if [[ "$TERM_PROGRAM" == Apple_Terminal ]]; then ZSH_THEME_TERM_TITLE_IDLE="%n@%m" @@ -50,22 +50,52 @@ fi # Runs before showing the prompt function omz_termsupport_precmd { - emulate -L zsh - - if [[ "$DISABLE_AUTO_TITLE" == true ]]; then - return - fi - + [[ "${DISABLE_AUTO_TITLE:-}" == true ]] && return title $ZSH_THEME_TERM_TAB_TITLE_IDLE $ZSH_THEME_TERM_TITLE_IDLE } # Runs before executing the command function omz_termsupport_preexec { + [[ "${DISABLE_AUTO_TITLE:-}" == true ]] && return + emulate -L zsh setopt extended_glob - if [[ "$DISABLE_AUTO_TITLE" == true ]]; then - return + # split command into array of arguments + local -a cmdargs + cmdargs=("${(z)2}") + # if running fg, extract the command from the job description + if [[ "${cmdargs[1]}" = fg ]]; then + # get the job id from the first argument passed to the fg command + local job_id jobspec="${cmdargs[2]#%}" + # logic based on jobs arguments: + # http://zsh.sourceforge.net/Doc/Release/Jobs-_0026-Signals.html#Jobs + # https://www.zsh.org/mla/users/2007/msg00704.html + case "$jobspec" in + <->) # %number argument: + # use the same <number> passed as an argument + job_id=${jobspec} ;; + ""|%|+) # empty, %% or %+ argument: + # use the current job, which appears with a + in $jobstates: + # suspended:+:5071=suspended (tty output) + job_id=${(k)jobstates[(r)*:+:*]} ;; + -) # %- argument: + # use the previous job, which appears with a - in $jobstates: + # suspended:-:6493=suspended (signal) + job_id=${(k)jobstates[(r)*:-:*]} ;; + [?]*) # %?string argument: + # use $jobtexts to match for a job whose command *contains* <string> + job_id=${(k)jobtexts[(r)*${(Q)jobspec}*]} ;; + *) # %string argument: + # use $jobtexts to match for a job whose command *starts with* <string> + job_id=${(k)jobtexts[(r)${(Q)jobspec}*]} ;; + esac + + # override preexec function arguments with job command + if [[ -n "${jobtexts[$job_id]}" ]]; then + 1="${jobtexts[$job_id]}" + 2="${jobtexts[$job_id]}" + fi fi # cmd name only, or if this is sudo or ssh, the next cmd @@ -75,8 +105,9 @@ function omz_termsupport_preexec { title '$CMD' '%100>...>$LINE%<<' } -precmd_functions+=(omz_termsupport_precmd) -preexec_functions+=(omz_termsupport_preexec) +autoload -U add-zsh-hook +add-zsh-hook precmd omz_termsupport_precmd +add-zsh-hook preexec omz_termsupport_preexec # Keep Apple Terminal.app's current working directory updated @@ -90,16 +121,17 @@ if [[ "$TERM_PROGRAM" == "Apple_Terminal" ]] && [[ -z "$INSIDE_EMACS" ]]; then function update_terminalapp_cwd() { emulate -L zsh - # Percent-encode the pathname. - local URL_PATH="$(omz_urlencode -P $PWD)" - [[ $? != 0 ]] && return 1 + # Percent-encode the host and path names. + local URL_HOST URL_PATH + URL_HOST="$(omz_urlencode -P $HOST)" || return 1 + URL_PATH="$(omz_urlencode -P $PWD)" || return 1 # Undocumented Terminal.app-specific control sequence - printf '\e]7;%s\a' "file://$HOST$URL_PATH" + printf '\e]7;%s\a' "file://$URL_HOST$URL_PATH" } # Use a precmd hook instead of a chpwd hook to avoid contaminating output - precmd_functions+=(update_terminalapp_cwd) + add-zsh-hook precmd update_terminalapp_cwd # Run once to get initial cwd set update_terminalapp_cwd fi diff --git a/lib/theme-and-appearance.zsh b/lib/theme-and-appearance.zsh index 96f34aa81..0b71de372 100644 --- a/lib/theme-and-appearance.zsh +++ b/lib/theme-and-appearance.zsh @@ -19,7 +19,7 @@ if [[ "$DISABLE_LS_COLORS" != "true" ]]; then # coreutils, so prefer it to "gls". gls --color -d . &>/dev/null && alias ls='gls --color=tty' colorls -G -d . &>/dev/null && alias ls='colorls -G' - elif [[ "$OSTYPE" == darwin* ]]; then + elif [[ "$OSTYPE" == (darwin|freebsd)* ]]; then # this is a good alias, it works by default just using $LSCOLORS ls -G . &>/dev/null && alias ls='ls -G' @@ -39,17 +39,21 @@ if [[ "$DISABLE_LS_COLORS" != "true" ]]; then fi fi +# enable diff color if possible. +if command diff --color . . &>/dev/null; then + alias diff='diff --color' +fi + setopt auto_cd setopt multios setopt prompt_subst [[ -n "$WINDOW" ]] && SCREEN_NO="%B$WINDOW%b " || SCREEN_NO="" -# Apply theming defaults -PS1="%n@%m:%~%# " - # git theming default: Variables for theming the git info prompt ZSH_THEME_GIT_PROMPT_PREFIX="git:(" # Prefix at the very beginning of the prompt, before the branch name ZSH_THEME_GIT_PROMPT_SUFFIX=")" # At the very end of the prompt ZSH_THEME_GIT_PROMPT_DIRTY="*" # Text to display if the branch is dirty ZSH_THEME_GIT_PROMPT_CLEAN="" # Text to display if the branch is clean +ZSH_THEME_RUBY_PROMPT_PREFIX="(" +ZSH_THEME_RUBY_PROMPT_SUFFIX=")" |