提交 7010fa5b 编写于 作者: M Mislav Marohnić

Merge branch 'fix-zsh-completion'

default: --format progress -t ~@completion
completion: --format pretty -t @completion
......@@ -16,6 +16,7 @@ fork
create
browse
compare
ci-status
EOF
__git_list_all_commands_without_hub
}
......
# hub tab-completion script for zsh.
# This script complements the completion script that ships with git.
#compdef hub
# Zsh will source this file when attempting to autoload the "_hub" function,
# typically on the first attempt to complete the hub command. We define two new
# setup helper routines (one for the zsh-distributed version, one for the
# git-distributed, bash-based version). Then we redefine the "_hub" function to
# call "_git" after some other interception.
#
# vim: ft=zsh sw=2 ts=2 et
# This is pretty fragile, if you think about it. Any number of implementation
# changes in the "_git" scripts could cause problems down the road. It would be
# better if the stock git completions were just a bit more permissive about how
# it allowed third-party commands to be added.
# Autoload _git completion functions
if declare -f _git > /dev/null; then
_git
fi
if declare -f _git_commands > /dev/null; then
_hub_commands=(
'alias:show shell instructions for wrapping git'
'pull-request:open a pull request on GitHub'
'fork:fork origin repo on GitHub'
'create:create new repo on GitHub for the current project'
'browse:browse the project on GitHub'
'compare:open GitHub compare view'
(( $+functions[__hub_setup_zsh_fns] )) ||
__hub_setup_zsh_fns () {
(( $+functions[_git-alias] )) ||
_git-alias () {
_arguments \
'-s[output shell script suitable for eval]' \
'1::shell:(zsh bash csh)'
}
(( $+functions[_git-browse] )) ||
_git-browse () {
_arguments \
'-u[output the URL]' \
'2::subpage:(wiki commits issues)'
}
(( $+functions[_git-compare] )) ||
_git-compare () {
_arguments \
'-u[output the URL]' \
':[start...]end range:'
}
(( $+functions[_git-create] )) ||
_git-create () {
_arguments \
'::name (REPOSITORY or ORGANIZATION/REPOSITORY):' \
'-p[make repository private]' \
'-d[description]:description' \
'-h[home page]:repository home page URL:_urls'
}
(( $+functions[_git-fork] )) ||
_git-fork () {
_arguments \
'--no-remote[do not add a remote for the new fork]'
}
(( $+functions[_git-pull-request] )) ||
_git-pull-request () {
_arguments \
'-f[force (skip check for local commits)]' \
'-b[base]:base ("branch", "owner\:branch", "owner/repo\:branch"):' \
'-h[head]:head ("branch", "owner\:branch", "owner/repo\:branch"):' \
- set1 \
'-m[message]' \
'-F[file]' \
- set2 \
'-i[issue]:issue number:' \
- set3 \
'::issue-url:_urls'
}
# stash the "real" command for later
functions[_hub_orig_git_commands]=$functions[_git_commands]
# Replace it with our own wrapper.
declare -f _git_commands >& /dev/null && unfunction _git_commands
_git_commands () {
local ret=1
# call the original routine
_call_function ret _hub_orig_git_commands
# Effectively "append" our hub commands to the behavior of the original
# _git_commands function. Using this wrapper function approach ensures
# that we only offer the user the hub subcommands when the user is
# actually trying to complete subcommands.
hub_commands=(
alias:'show shell instructions for wrapping git'
pull-request:'open a pull request on GitHub'
fork:'fork origin repo on GitHub'
create:'create new repo on GitHub for the current project'
browse:'browse the project on GitHub'
compare:'open GitHub compare view'
ci-status:'lookup commit in GitHub Status API'
)
# Extend the '_git_commands' function with hub commands
eval "$(declare -f _git_commands | sed -e 's/base_commands=(/base_commands=(${_hub_commands} /')"
fi
_describe -t hub-commands 'hub command' hub_commands && ret=0
return ret
}
}
(( $+functions[__hub_setup_bash_fns] )) ||
__hub_setup_bash_fns () {
# TODO more bash-style fns needed here to complete subcommand args. They take
# the form "_git_CMD" where "CMD" is something like "pull-request".
# Duplicate and rename the 'list_all_commands' function
eval "$(declare -f __git_list_all_commands | \
sed 's/__git_list_all_commands/__git_list_all_commands_without_hub/')"
# Wrap the 'list_all_commands' function with extra hub commands
__git_list_all_commands() {
cat <<-EOF
alias
pull-request
fork
create
browse
compare
ci-status
EOF
__git_list_all_commands_without_hub
}
# Ensure cached commands are cleared
__git_all_commands=""
}
# redefine _hub to a much smaller function in the steady state
_hub () {
# only attempt to intercept the normal "_git" helper functions once
(( $+__hub_func_replacement_done )) ||
() {
# At this stage in the shell's execution the "_git" function has not yet
# been autoloaded, so the "_git_commands" or "__git_list_all_commands"
# functions will not be defined. Call it now (with a bogus no-op service
# to prevent premature completion) so that we can wrap them.
if declare -f _git >& /dev/null ; then
_hub_noop () { __hub_zsh_provided=1 } # zsh-provided will call this one
__hub_noop_main () { __hub_git_provided=1 } # git-provided will call this one
local service=hub_noop
_git
unfunction _hub_noop
unfunction __hub_noop_main
service=git
fi
if (( $__hub_zsh_provided )) ; then
__hub_setup_zsh_fns
elif (( $__hub_git_provided )) ; then
__hub_setup_bash_fns
fi
__hub_func_replacement_done=1
}
# Now perform the actual completion, allowing the "_git" function to call our
# replacement "_git_commands" function as needed. Both versions expect
# service=git or they will call nonexistent routines or end up in an infinite
# loop.
service=git
declare -f _git >& /dev/null && _git
}
# make sure we actually attempt to complete on the first "tab" from the user
_hub
......@@ -33,10 +33,7 @@ Feature: hub alias
When I successfully run `hub alias -s`
Then the output should contain exactly:
"""
alias git=hub
if type compdef >/dev/null; then
compdef hub=git
fi\n
alias git=hub\n
"""
Scenario: unsupported shell
......
@completion
Feature: bash tab-completion
Scenario: "pu" matches multiple commands including "pull-request"
Given my shell is bash
And I'm using git-distributed base git completions
When I type "git pu" and press <Tab>
Then the command should not expand
When I press <Tab> again
Then the completion menu should offer "pull pull-request push"
Scenario: "ci-" expands to "ci-status"
Given my shell is bash
And I'm using git-distributed base git completions
When I type "git ci-" and press <Tab>
Then the command should expand to "git ci-status"
# In this combination, zsh uses completion support from a bash script.
Scenario: "ci-" expands to "ci-status"
Given my shell is zsh
And I'm using git-distributed base git completions
When I type "git ci-" and press <Tab>
Then the command should expand to "git ci-status"
# Driver for completion tests executed via a separate tmux pane in which we
# spawn an interactive shell, send keystrokes to and inspect the outcome of
# tab-completion.
#
# Prerequisites:
# - tmux
# - bash
# - zsh
# - git
require 'fileutils'
require 'rspec/expectations'
require 'pathname'
tmpdir = Pathname.new(ENV.fetch('TMPDIR', '/tmp')) + 'hub-test'
cpldir = tmpdir + 'completion'
zsh_completion = File.expand_path('../../../etc/hub.zsh_completion', __FILE__)
bash_completion = File.expand_path('../../../etc/hub.bash_completion.sh', __FILE__)
_git_prefix = nil
git_prefix = lambda {
_git_prefix ||= begin
git_core = Pathname.new(`git --exec-path`.chomp)
git_core.dirname.dirname
end
}
git_distributed_zsh_completion = lambda {
git_prefix.call + 'share/zsh/site-functions/_git'
}
git_distributed_bash_completion = lambda {
git_prefix.call + 'etc/bash_completion.d/git-completion.bash'
}
link_completion = lambda { |from, name = nil|
name ||= from.basename
raise ArgumentError, from.to_s unless File.exist?(from)
FileUtils.ln_s(from, cpldir + name)
}
setup_tmp_home = lambda { |shell|
FileUtils.rm_rf(tmpdir)
FileUtils.mkdir_p(cpldir)
case shell
when 'zsh'
File.open(File.join(tmpdir, '.zshrc'), 'w') do |zshrc|
zshrc.write <<-SH
PS1='$ '
for site_fn in /usr/{local/,}share/zsh/site-functions; do
fpath=(${fpath#\$site_fn})
done
fpath=('#{cpldir}' $fpath)
alias git=hub
autoload -U compinit
compinit -i
SH
end
when 'bash'
File.open(File.join(tmpdir, '.bashrc'), 'w') do |bashrc|
bashrc.write <<-SH
PS1='$ '
alias git=hub
. '#{git_distributed_bash_completion.call}'
. '#{bash_completion}'
SH
end
end
}
After('@completion') do
tmux_kill_pane
end
World Module.new {
attr_reader :shell
def set_shell(shell)
@shell = shell
end
define_method(:tmux_pane) do
return @tmux_pane if tmux_pane?
@tmux_pane = `tmux new-window -dP -n test -c "#{tmpdir}" 'env HOME="#{tmpdir}" #{shell}'`.chomp
end
def tmux_pane?
defined?(@tmux_pane) && @tmux_pane
end
def tmux_pane_contents
`tmux capture-pane -p -t #{tmux_pane}`.rstrip
end
def tmux_send_keys(*keys)
system 'tmux', 'send-keys', '-t', tmux_pane, *keys
end
def tmux_kill_pane
system 'tmux', 'kill-pane', '-t', tmux_pane if tmux_pane?
end
def tmux_wait_for_prompt
num_waited = 0
while tmux_pane_contents !~ /\$\Z/
sleep 0.01
num_waited += 1
raise "timeout while waiting for shell prompt" if num_waited > 100
end
end
def tmux_wait_for_completion
# bash can be pretty slow
sleep 0.4
end
def tmux_completion_menu
tmux_wait_for_completion
hash = {}
tmux_pane_contents.split("\n").grep(/^[^\$].+ -- /).each do |line|
item, description = line.split(/ +-- +/, 2)
hash[item] = description
end
hash
end
def tmux_completion_menu_basic
tmux_wait_for_completion
tmux_pane_contents.split("\n").grep(/^[^\$]/).map {|line|
line.split(/\s+/)
}.flatten
end
}
Given /^my shell is (\w+)$/ do |shell|
set_shell(shell)
setup_tmp_home.call(shell)
end
Given /^I'm using ((?:zsh|git)-distributed) base git completions$/ do |type|
link_completion.call(zsh_completion, '_hub')
case type
when 'zsh-distributed'
raise "this combination makes no sense!" if 'bash' == shell
(cpldir + '_git').exist?.should be_false
when 'git-distributed'
if 'zsh' == shell
link_completion.call(git_distributed_zsh_completion.call)
link_completion.call(git_distributed_bash_completion.call)
end
else
raise ArgumentError, type
end
end
When /^I type "(.+?)" and press <Tab>$/ do |string|
tmux_wait_for_prompt
@last_command = string
tmux_send_keys(string)
tmux_send_keys('Tab')
end
When /^I press <Tab> again$/ do
tmux_send_keys('Tab')
end
Then /^the completion menu should offer "([^"]+?)"$/ do |items|
menu = tmux_completion_menu_basic
menu.join(' ').should eq(items)
end
Then /^the completion menu should offer "(.+?)" with description "(.+?)"$/ do |item, description|
menu = tmux_completion_menu
menu.keys.should include(item)
menu[item].should eq(description)
end
Then /^the completion menu should offer:/ do |table|
menu = tmux_completion_menu
menu.should eq(table.rows_hash)
end
Then /^the command should expand to "(.+?)"$/ do |cmd|
tmux_wait_for_completion
tmux_pane_contents.should match(/^\$ #{cmd}$/)
end
Then /^the command should not expand$/ do
tmux_wait_for_completion { false }
tmux_pane_contents.should match(/^\$ #{@last_command}$/)
end
@completion
Feature: zsh tab-completion
Background:
Given my shell is zsh
And I'm using zsh-distributed base git completions
Scenario: "pu" expands to "pull-request" after "pull"
When I type "git pu" and press <Tab>
Then the completion menu should offer "pull-request" with description "open a pull request on GitHub"
When I press <Tab> again
Then the command should expand to "git pull"
When I press <Tab> again
Then the command should expand to "git pull-request"
Scenario: "ci-" expands to "ci-status"
When I type "git ci-" and press <Tab>
Then the command should expand to "git ci-status"
Scenario: Completion of pull-request arguments
When I type "git pull-request -" and press <Tab>
Then the completion menu should offer:
| -b | base |
| -h | head |
| -m | message |
| -F | file |
| -i | issue |
| -f | force (skip check for local commits) |
Scenario: Completion of fork arguments
When I type "git fork -" and press <Tab>
Then the command should expand to "git fork --no-remote"
Scenario: Completion of 2nd browse argument
When I type "git browse -- i" and press <Tab>
Then the command should expand to "git browse -- issues"
......@@ -726,11 +726,6 @@ module Hub
if script
puts "alias git=hub"
if 'zsh' == shell
puts "if type compdef >/dev/null; then"
puts " compdef hub=git"
puts "fi"
end
else
profile = case shell
when 'bash' then '~/.bash_profile'
......@@ -764,12 +759,18 @@ module Hub
if command == 'hub' || custom_command?(command)
puts hub_manpage
exit
elsif command.nil? && !args.has_flag?('-a', '--all')
elsif command.nil?
if args.has_flag?('-a', '--all')
# Add the special hub commands to the end of "git help -a" output.
args.after 'echo', ["\nhub custom commands\n"]
args.after 'echo', CUSTOM_COMMANDS.map {|cmd| " #{cmd}" }
else
ENV['GIT_PAGER'] = '' unless args.has_flag?('-p', '--paginate') # Use `cat`.
puts improved_help_text
exit
end
end
end
alias_method "--help", :help
private
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册