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=[[[,]]]: