Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bash/zsh completion: reimplement and decrease runtime by factor 1863 #960

Merged
merged 1 commit into from
Feb 23, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
324 changes: 69 additions & 255 deletions contrib/tig-completion.bash
Original file line number Diff line number Diff line change
@@ -1,276 +1,88 @@
##
# bash completion support for tig
#
# bash/zsh completion for tig
#
# Copyright (C) 2019 Roland Hieber, Pengutronix
# Copyright (C) 2007-2010 Jonas fonseca
# Copyright (C) 2006,2007 Shawn Pearce
#
# Based git's git-completion.sh: http://repo.or.cz/w/git/fastimport.git
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of
# the License, or (at your option) any later version.
#
# The contained completion routines provide support for completing:
# This completion builds upon the git completion (>= git 1.17.11),
# which most tig users should already have available at this point.
# To use these routines:
#
# *) local and remote branch names
# *) local and remote tag names
# *) tig 'subcommands'
# *) tree paths within 'ref:path/to/file' expressions
# 1) Copy this file to somewhere (e.g. ~/.bash_completion.d/tig).
#
# To use these routines:
# 2) Add the following line to your .bashrc:
#
# 1) Copy this file to somewhere (e.g. ~/.tig-completion.sh).
# 2) Added the following line to your .bashrc:
# source ~/.tig-completion.sh
# source ~/.bash_completion.d/tig
#
# Note that most Linux distributions source everything in
# ~/.bash_completion.d/ automatically at bash startup, so you
# have to source this script manually only in shells that were
# already running before.
#
# 3) You may want to make sure the git executable is available
# in your PATH before this script is sourced, as some caching
# is performed while the script loads. If git isn't found
# at source time then all lookups will be done on demand,
# which may be slightly slower.
#

__tigdir ()
{
if [ -z "$1" ]; then
if [ -n "$__git_dir" ]; then
echo "$__git_dir"
elif [ -d .git ]; then
echo .git
else
git rev-parse --git-dir 2>/dev/null
fi
elif [ -d "$1/.git" ]; then
echo "$1/.git"
else
echo "$1"
fi
}

_tigcomp ()
{
local all c s=$'\n' IFS=' '$'\t'$'\n'
local cur="${COMP_WORDS[COMP_CWORD]}"
if [ $# -gt 2 ]; then
cur="$3"
fi
for c in $1; do
case "$c$4" in
--*=*) all="$all$c$4$s" ;;
*.) all="$all$c$4$s" ;;
*) all="$all$c$4 $s" ;;
esac
done
IFS=$s
COMPREPLY=($(compgen -P "$2" -W "$all" -- "$cur"))
return
}

__tig_refs ()
{
local cmd i is_hash=y dir="$(__tigdir "$1")"
if [ -d "$dir" ]; then
for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do
if [ -e "$dir/$i" ]; then echo $i; fi
done
for i in $(git --git-dir="$dir" \
for-each-ref --format='%(refname)' \
refs/tags refs/heads refs/remotes); do
case "$i" in
refs/tags/*) echo "${i#refs/tags/}" ;;
refs/heads/*) echo "${i#refs/heads/}" ;;
refs/remotes/*) echo "${i#refs/remotes/}" ;;
*) echo "$i" ;;
esac
done
return
fi
for i in $(git-ls-remote "$dir" 2>/dev/null); do
case "$is_hash,$i" in
y,*) is_hash=n ;;
n,*^{}) is_hash=y ;;
n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;;
n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;;
n,refs/remotes/*) is_hash=y; echo "${i#refs/remotes/}" ;;
n,*) is_hash=y; echo "$i" ;;
esac
done
}

__tig_complete_file ()
{
local pfx ls ref cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
?*:*)
ref="${cur%%:*}"
cur="${cur#*:}"
case "$cur" in
?*/*)
pfx="${cur%/*}"
cur="${cur##*/}"
ls="$ref:$pfx"
pfx="$pfx/"
;;
*)
ls="$ref"
;;
esac
COMPREPLY=($(compgen -P "$pfx" \
-W "$(git --git-dir="$(__tigdir)" ls-tree "$ls" \
| sed '/^100... blob /s,^.* ,,
/^040000 tree /{
s,^.* ,,
s,$,/,
}
s/^.* //')" \
-- "$cur"))
;;
*)
_tigcomp "$(__tig_refs)"
;;
esac
}

__tig_complete_revlist ()
{
local pfx cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
*...*)
pfx="${cur%...*}..."
cur="${cur#*...}"
_tigcomp "$(__tig_refs)" "$pfx" "$cur"
;;
*..*)
pfx="${cur%..*}.."
cur="${cur#*..}"
_tigcomp "$(__tig_refs)" "$pfx" "$cur"
;;
*.)
_tigcomp "$cur."
;;
*)
_tigcomp "$(__tig_refs)"
;;
esac
}

_tig_options ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--pretty=*)
_tigcomp "
oneline short medium full fuller email raw
" "" "${cur##--pretty=}"
return
;;
--*)
_tigcomp "
--max-count= --max-age= --since= --after=
--min-age= --before= --until=
--root --not --topo-order --date-order
--no-merges
--abbrev-commit --abbrev=
--relative-date
--author= --committer= --grep=
--all-match
--pretty= --name-status --name-only
--not --all
--help --version
"
return
;;
-*)
_tigcomp "-v -h"
return
;;
esac
__tig_complete_revlist
}

_tig_blame ()
{
local reply="" ref=HEAD cur="${COMP_WORDS[COMP_CWORD]}" p=""
local pfx=$(git rev-parse --show-prefix 2>/dev/null)

if test "$COMP_CWORD" -lt 3; then
_tigcomp "$(__tig_refs)"
else
ref="${COMP_WORDS[2]}"
fi

case "$cur" in
*/) p=${cur%/} ;;
*/*) p=${cur%/*} ;;
*) p= ;;
esac

i=${#COMPREPLY[@]}
local IFS=$'\n'
for c in $(git --git-dir="$(__tigdir)" ls-tree "$ref:$pfx$p" 2>/dev/null |
sed -n '/^100... blob /{
s,^.* ,,
s,$, ,
p
}
/^040000 tree /{
s,^.* ,,
s,$,/,
p
}')
do
c="${p:+$p/}$c"
if [[ "$c" == "$cur"* ]]; then
COMPREPLY[i++]=$c
fi
done
}

_tig_show ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--pretty=*)
_tigcomp "
oneline short medium full fuller email raw
" "" "${cur##--pretty=}"
return
;;
--*)
_tigcomp "--pretty="
return
;;
esac
__tig_complete_file
}

_tig ()
{
local i c=1 command __tig_dir

while [ $c -lt $COMP_CWORD ]; do
i="${COMP_WORDS[c]}"
__tig_options="
-v --version
-h --help
-C
--
+
"
__tig_commands="
blame
grep
log
reflog
refs
stash
status
show
"

_tig() {
# parse already existing parameters
local i c=1 command
while [ $c -lt $cword ]; do
i="${words[c]}"
case "$i" in
--) command="log"; break;;
-*) ;;
*) command="$i"; break ;;
--) command="log"; break;;
-*) ;;
*) command="$i"; break ;;
esac
c=$((++c))
done

if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
case "${COMP_WORDS[COMP_CWORD]}" in
--*=*) COMPREPLY=() ;;
-*) _tig_options ;;
*) _tigcomp "blame status show log stash grep $(__tig_refs)" ;;
esac
return
fi
# options -- only before command
case "$command$cur" in
-C*)
COMPREPLY=( $(compgen -d -P '-C' -- ${cur##-C}) )
return
;;
esac

# commands
case "$command" in
blame) _tig_blame ;;
show) _tig_show ;;
status) ;;
*) _tigcomp "
$(__tig_complete_file)
$(__tig_refs)
" ;;
refs|status|stash)
COMPREPLY=( $(compgen -W "$__tig_options" -- "$cur") )
;;
reflog)
__git_complete_command log
;;
"")
__git_complete_command log
__gitcompappend "$(compgen -W "$__tig_options $__tig_commands" -- "$cur")"
;;
*)
__git_complete_command $command
;;
esac
}

Expand All @@ -281,11 +93,13 @@ if [ -n "$ZSH_VERSION" ]; then
bashcompinit
fi

complete -o default -o nospace -F _tig tig
# we use internal git-completion functions, so wrap _tig for all necessary
# variables (like cword and prev) to be defined
__git_complete tig _tig

# The following are necessary only for Cygwin, and only are needed
# when the user has tab-completed the executable name and consequently
# included the '.exe' suffix.
if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
complete -o default -o nospace -F _tig tig.exe
__git_complete tig.exe _tig
fi