fgit_helper (11286B) - raw


      1 #!/usr/bin/env zsh
      2 
      3 0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}"
      4 0="${${(M)0:#/*}:-$PWD/$0}"
      5 
      6 emulate -LR zsh
      7 setopt warncreateglobal noshortloops typesetsilent extendedglob
      8 
      9 function print::warning() { print -Pru2 -- "%F{3}[WARNING]%f: $*"; }
     10 function print::error()   { print -Pru2 -- "%F{1}%B[ERROR]%f%b: $*"; }
     11 
     12 zmodload -Fa zsh/parameter p:commands || {
     13   print::error "Unable to load %F{2}zsh/paramter%f. Exiting..."
     14   exit 1
     15 }
     16 
     17 #######################################
     18 ########## SETTING VARIABLES ##########
     19 #######################################
     20 () {
     21   typeset -g \
     22          FZFGIT_TREE="$(command git rev-parse --show-toplevel)" \
     23          FZFGIT_DIFF_PAGER="${FZFGIT_DIFF_PAGER:-$(git config core.pager || print -r -- 'cat')}" \
     24          FZFGIT_LOG_FMT=${FZFGIT_LOG_FMT:-%C(auto)%h%d %s %C(black)%C(bold)%cr%Creset} \
     25          EDITOR=${EDITOR:-${VISUAL:-vim}} \
     26          FZFGIT_VERSION="v1.3"
     27 
     28   local fd=${${${:-=fd}:c}:A}
     29   [[ -x "$fd" ]] && typeset -g fzfgit_find="$fd" || typeset -g fzfgit_find="find"
     30 
     31   [[ -z "${FZF_DEFAULT_OPTS}" ]] && export FZF_DEFAULT_OPTS='--cycle'
     32 
     33   if [[ -z "${FZFGIT_KEY}" ]]; then
     34     typeset -g FZFGIT_KEY="
     35       --bind='ctrl-a:toggle-all'
     36       --bind='ctrl-b:execute(bat --paging=always -f {+})'
     37       --bind='ctrl-y:execute-silent(echo {+} | pbcopy)'
     38       --bind='ctrl-e:execute(echo {+} | xargs -o $EDITOR)'
     39       --bind='ctrl-k:preview-up'
     40       --bind='ctrl-j:preview-down'
     41       --bind='alt-j:jump'
     42       --bind='alt-0:top'
     43       --bind='ctrl-s:toggle-sort'
     44       --bind='?:toggle-preview'
     45   "
     46   fi
     47 
     48   FZF_DEFAULT_OPTS="
     49     $FZF_DEFAULT_OPTS
     50     --ansi
     51     --cycle
     52     --exit-0
     53     $FZFGIT_DEFAULT_OPTS
     54     $FZFGIT_KEY
     55   "
     56 
     57   (( ! $COLUMNS ))    && COLUMNS=${(s: :)$(stty size < /dev/tty)[2]}
     58   (( $COLUMNS < 80 )) && FZF_DEFAULT_OPTS="$FZF_DEFAULT_OPTS --preview-window=hidden"
     59 }
     60 
     61 #######################################
     62 # determine to set multi selection or not
     63 # Globals:
     64 #   ${FZF_DEFAULT_OPTS}: fzf options
     65 # Arguments:
     66 #   $1: if exists, disable multi, set single
     67 #######################################
     68 function set_fzf_multi() {
     69   local no_multi="$1"
     70   [[ -z "$no_multi" ]] && {
     71     export FZF_DEFAULT_OPTS="${FZF_DEFAULT_OPTS} --multi"
     72   } || export FZF_DEFAULT_OPTS="${FZF_DEFAULT_OPTS} --no-multi"
     73 }
     74 
     75 #######################################
     76 # Helper function to get user confirmation
     77 # Globals:
     78 #   None
     79 # Locals:
     80 #   ${confirm}: user confirmation status
     81 # Arguments:
     82 #   $1: confirmation message to show during confirm
     83 # Outputs:
     84 #   ${confirm}: y or n indicating user response
     85 #######################################
     86 function get_confirmation() {
     87   local confirm message="${1:-Confirm?}"
     88   # Must be printed to stderr for other functions accepting stdout
     89   builtin print -Pru2 -- "%F{4}$message%f"
     90   builtin read -rq '?[Y/n]? ' confirm && {
     91   notify-send "before: $confirm"
     92     builtin print -r -- "${confirm}"
     93   }
     94 }
     95 
     96 
     97 #######################################
     98 # let user select a commit interactively
     99 # credit to forgit for the git log format
    100 # Arguments:
    101 #   $1: the helper message to display in the fzf header
    102 #   $2: files to show diff against HEAD
    103 # Outputs:
    104 #   the selected commit 6 char code
    105 #   e.g. b60b330
    106 #######################################
    107 function get_commit() {
    108   # TODO: Maybe one off
    109   local header="${1:-Select a commit}"
    110   local -a files=( ${@[2,-1]} )
    111   if (( $#files )) {
    112     command git log \
    113       --oneline \
    114       --color=always \
    115       --decorate=short \
    116     | command fzf \
    117       --header="$header" \
    118       --no-multi \
    119       --preview "echo {} \
    120           | awk '{print \$1}' \
    121           | xargs -I __ git  \
    122               diff --color=always __ ${files[*]} \
    123           | ${FZFGIT_DIFF_PAGER}" \
    124       | awk '{print $1}'
    125   } else {
    126     command git log \
    127       --color=always \
    128       --format='%C(auto)%h%d %s %C(black)%C(bold)%cr' \
    129     | command fzf \
    130       --header="$header" \
    131       --no-multi \
    132       --preview "echo {} \
    133         | awk '{print \$1}' \
    134         | xargs -I __ git  \
    135         show --color=always  __ \
    136         | ${FZFGIT_DIFF_PAGER}" \
    137         | awk '{print $1}'
    138   }
    139 }
    140 
    141 #######################################
    142 # let user select a branch interactively
    143 # Arguments:
    144 #   $1: the helper message to display in the fzf header
    145 # Outputs:
    146 #   the selected branch name
    147 #   e.g. master
    148 #######################################
    149 function get_branch() {
    150   local header="${1:-Select a branch}"
    151   command git branch -a \
    152     | command awk '{
    153         if ($0 ~ /\*.*\(HEAD.*/) {
    154           gsub(/\* /, "", $0)
    155           print "\033[32m" $0 "\033[0m"
    156         } else if (match($1, "\\*") != 0) {
    157           print "\033[32m" $2 "\033[0m"
    158         } else if ($0 ~ /^[ \t]+remotes\/.*/) {
    159           gsub(/[ \t]+/, "", $0)
    160           print "\033[31m" $0 "\033[0m"
    161         } else {
    162           gsub(/^[ \t]+/, "", $0)
    163           print $0
    164         }
    165       }' \
    166     | command fzf --no-multi --header="$header" \
    167         --preview="echo {} \
    168       | awk '{
    169           if (\$0 ~ /.*HEAD.*/) {
    170             print \"HEAD\"
    171           } else {
    172             print \$0
    173           }
    174         }' \
    175       | xargs -I __ git \
    176           log --color=always --color=always --format='%C(auto)%h%d %s %C(black)%C(bold)%cr' __"
    177 }
    178 
    179 #######################################
    180 # let user select a git tracked file interactively
    181 # Arguments:
    182 #   $1: the helper message to display in the fzf header
    183 #   $2: print option, values (full|raw)
    184 #   $3: if exist, don't do multi selection, do single
    185 # Outputs:
    186 #   the selected file path
    187 #   e.g.$HOME/.config/nvim/init.vim
    188 #######################################
    189 function get_git_file() {
    190   # TODO: Why is this here?
    191   local mydir=${0:A:h}
    192   local header="${1:-Select tracked file}"
    193   local print_opt="${2:-full}"
    194   set_fzf_multi "$3"
    195   command git ls-files \
    196     --full-name \
    197     --directory "$FZFGIT_TREE" \
    198     | fzf \
    199       --header="$header" \
    200       --preview "preview.sh $FZFGIT_TREE/{}" \
    201     | command awk -v home="$FZFGIT_TREE" -v print_opt="$print_opt" '{
    202         if (print_opt == "full") {
    203           print home "/" $0
    204         } else {
    205           print $0
    206         }
    207       }'
    208 }
    209 
    210 #######################################
    211 # let user select a modified file interactively
    212 # Arguments:
    213 #   $1: the helper message to display in the fzf header
    214 #   $2: display mode of modified files.
    215 #     default: true
    216 #     all: display all modified, include staged and unstaged
    217 #     staged: display only staged files
    218 #     unstaged: display only unstaged files
    219 #   $3: output_format
    220 #     default: name
    221 #     name: formatted name of the file
    222 #     raw: raw file name with status
    223 #   $4: if exists, don't do multi selection, do single
    224 # Outputs:
    225 #   the selected file path
    226 #   e.g.$HOME/.config/nvim/init.vim
    227 #######################################
    228 function get_modified_file() {
    229   local header="${1:-Select a modified file}"
    230   local display_mode="${2:-all}"
    231   local output_format="${3:-name}"
    232   set_fzf_multi "$4"
    233   command git status --porcelain \
    234     | awk -v display_mode="${display_mode}" '{
    235         if ($0 ~ /^[[[:alpha:]]{2}.*$/) {
    236           print "\033[32m" substr($0, 1, 1) "\033[31m" substr($0, 2) "\033[0m"
    237         } else if ($0 ~ /^[A-Za-z][ \t].*$/) {
    238           if (display_mode == "all" || display_mode == "staged") {
    239             print "\033[32m" $0 "\033[0m"
    240           }
    241         } else {
    242           if (display_mode == "all" || display_mode == "unstaged") {
    243             print "\033[31m" $0 "\033[0m"
    244           }
    245         }
    246       }' \
    247     | command fzf --header="$header" --preview "echo {} \
    248         | awk '{sub(\$1 FS,\"\"); print \$0}' \
    249         | xargs -I __ git \
    250             diff HEAD --color=always -- $FZFGIT_TREE/__ \
    251         | $FZFGIT_DIFF_PAGER" \
    252     | awk -v home="$FZFGIT_TREE" -v format="$output_format" '{
    253         if (format == "name") {
    254           $1=""
    255           gsub(/^[ \t]/, "", $0)
    256           gsub(/"/, "", $0)
    257           print home "/" $0
    258         } else {
    259           print $0
    260         }
    261       }'
    262 }
    263 
    264 #######################################
    265 # let user select a stash interactively
    266 # Arguments:
    267 #   $1: the help message to display in header
    268 #   $2: if exists, don't do multi select, only allow single selection
    269 # Outputs:
    270 #   the selected stash identifier
    271 #   e.g. stash@{0}
    272 #######################################
    273 function get_stash() {
    274   local header="${1:-select a stash}"
    275   set_fzf_multi "$2"
    276   command git \
    277     stash list \
    278     | command fzf --header="${header}" --preview "echo {} \
    279         | awk '{
    280             gsub(/:/, \"\", \$1)
    281             print \$1
    282           }' \
    283         | xargs -I __ git \
    284             stash show -p __ --color=always \
    285         | $FZFGIT_DIFF_PAGER" \
    286     | command awk '{
    287         gsub(/:/, "", $1)
    288         print $1
    289       }'
    290 }
    291 
    292 #######################################
    293 # Using git grep to find word within
    294 # all tracked files in the bare repo.
    295 # Arguments:
    296 #   $1: the help message to display in header
    297 #   $2: the fzf delimiter to start searching, default is 3
    298 #   $3: if exists, don't do multi select, only allow single selection
    299 # Outputs:
    300 #   the selected file name with it's line number and line, separated by ":"
    301 #   e.g. .bash_profile:1:echo hello
    302 #######################################
    303 function grep_words() {
    304   local header="${1:-Select matches to edit}"
    305   local delimiter="${2:-3}" mydir=${0:A:h}
    306   local grep_cmd
    307   (( $+commands[rg] )) && \
    308     grep_cmd="rg --line-number --no-heading --color=auto --smart-case --ignore='.git' -- ." || \
    309     grep_cmd="git grep --line-number -- ."
    310   set_fzf_multi "$2"
    311   builtin cd -q "$FZFGIT_TREE" || exit
    312 
    313   eval "$grep_cmd" \
    314     | command fzf \
    315       --delimiter : \
    316       --nth "${delimiter}.." \
    317       --header="${header}" \
    318       --preview "$mydir/preview.sh $FZFGIT_TREE/{}" \
    319     | command awk -F ":" -v home="${FZFGIT_TREE}" '{
    320         print home "/" $1 ":" $2
    321       }'
    322 }
    323 
    324 #######################################
    325 # search local file
    326 # Arguments:
    327 #   $1: string, f or d, search file or directory
    328 # Outputs:
    329 #   A user selected file path
    330 #######################################
    331 function search_file() {
    332   emulate -LR zsh
    333   local search_type="$1" mydir=${0:A:h}
    334 
    335   if [[ $search_type = f ]]; then
    336     builtin print -rl -- \
    337       ${${(@f)$(eval "${(@)${(@f)${${(M)${fzfgit_find:t}:#fd}:+fd . -d1 -tf}:-find . -maxdepth 1 -type f}}")}#./} \
    338       | command fzf --multi --preview "${mydir}/preview.sh {}"
    339   elif [[ $search_type == d ]]; then
    340     if (( $+commands[exa] )) {
    341       eval "${(@)${(@f)${${(M)${fzfgit_find:t}:#fd}:+fd . -d1 -td}:-find . -maxdepth 1 -type d}}" \
    342         | command awk '{ if ($0 != "." && $0 != "./.git") { gsub(/^\.\//, "", $0); print $0 } }' \
    343         | command fzf --multi --preview "exa -TL 1 --color=always --group-directories-first --icons {}"
    344     } elif (( $+commands[tree] )) {
    345       eval "${(@)${(@f)${${(M)${fzfgit_find:t}:#fd}:+fd . -d1 -td}:-find . -maxdepth 1 -type d}}" \
    346         | command awk '{ if ($0 != "." && $0 != "./.git") { gsub(/^\.\//, "", $0); print $0 } }' \
    347         | command fzf --multi --preview "tree -L 1 -C --dirsfirst {}"
    348     } else {
    349       eval "${(@)${(@f)${${(M)${fzfgit_find:t}:#fd}:+fd . -d1 -td}:-find . -maxdepth 1 -type d}}" \
    350         | command awk '{ if ($0 != "." && $0 != "./.git") { gsub(/^\.\//, "", $0); print $0 } }' \
    351         | command fzf --multi
    352     }
    353   fi
    354 }
    355 
    356 # vim: ft=zsh:et:sw=2:ts=2:sts=-1:fdm=marker:fmr=[[[,]]]: