summary refs log tree commit diff
path: root/Completion/Commands/_read_comp
blob: a32879b56a8f3d85f85ab141a348b0712095978f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#compdef -k complete-word \C-x\C-r

# This allows an on-the-fly choice of completions.  On typing the key
# sequence given above, you will be prompted for a string of arguments.  If
# this string begins with `_', it will be taken as the name of a function to
# evaluate to generate the completions; unambiguous strings in the function
# name are automatically completed.
#
# Else it is taken to be a set of arguments for compgen to generate a list
# of choices.  The possibilities are the same as the flags for generating
# completions given in the zshcompctl manual page.  Note the arguments are
# verbatim:  include minus signs, spaces, quotes, etc.
#
# On subsequent calls, the same completion will be re-performed.  To
# force a new type of completion to be read, supply a numeric argument.
#
# For example,
#  % bindkey | grep rever<C-xC-r>
#  Completion: -b<RET>
#  % bindkey | grep reverse-menu-complete _
#
# Global variables used:
#  _read_comp         Last completion string read from user

emulate -L zsh
setopt extendedglob nobadpattern # xtrace promptsubst
# local PS4='%N:%i:$((#key))> '

# Took me ages to work this out.  If we're not on the first global
# matcher specification, we mustn't do any I/O.
if [[ compstate[matcher] -gt 1 && -z $_read_comp ]]; then
  return 1
fi

if [[ compstate[matcher] -gt 1 ||
  ( ${+NUMERIC} = 0 && -n $_read_comp ) ]]; then
  if [[ $_read_comp = _* ]]; then
    eval $_read_comp
  else
    eval "compgen $_read_comp"
  fi
  return
fi

_read_comp=

local key search str str2 newch funcs funcs2 exact msg list
integer pos

msg="Completion: "

zle -R $msg

if ! read -k key; then
  zle -cR ''
  return 1
fi

while [[ '#key' -ne 10 && '#key' -ne 13 ]]; do
  if [[ '#key' -eq 0 && '#key' -eq 3 || '#key' -eq 7 ]]; then
    zle -cR ''
    return 1
  fi
  if [[ ( '#key' -eq 8 || '#key' -eq 127 ) && -n $str ]]; then
    # delete character
    str="$str[1,-2]"
    exact=
    list=()
  elif [[ '#key' -eq 21 ]]; then
    # ^U: delete line
    str=
    exact=
    list=()
  elif [[ '#key' -eq 4 && $str = _[^\ ]# && $str != *' '* ]]; then
    # ^D: list completions
    list=(${$(whence -m "$str*" 2>/dev/null)%: function})
  elif [[ ( -n $exact && $key != ' ' ) || '#key & 127' -lt 32 ]]; then
    # If we've got an exact function, only allow a space after it.
    # Don't try to insert non-printing characters.
    if [[ -n $ZBEEP ]]; then
      print -nb $ZBEEP
    elif [[ -o beep ]]; then
      print -n "\a"
    fi
    list=()
  else
    str="$str$key"
    if [[ $str = _[^\ ]# ]]; then
      # Rudimentary completion for function names.
      # Allow arguments, i.e. don't do this after we've got a space.
      funcs=(${$(whence -m "$str*" 2>/dev/null)%: function})
      if [[ -o autolist && $#str -gt 1 ]]; then
	list=($funcs)
      else
	list=()
      fi
      if (( $#funcs == 1 )); then
	# Exact match; prompt the user for a newline to confirm
	str=$funcs[1]
	exact=" (Confirm)"
      elif (( $#funcs == 0 )); then
	# We can't call zle beep, because this isn't a zle widget.
	if [[ -n $ZBEEP ]]; then
	  print -nb $ZBEEP
	elif [[ -o beep ]]; then
	  print -n "\a"
	fi
	str="$str[1,-2]"
	list=()
      else
	# Add characters to the string until a name doesn't
	# match any more, then backtrack one character to get
	# the longest unambiguous match.
	str2=$str
	pos=$#str2
	while true; do
	  (( pos++ ))
	  newch=${funcs[1][pos]}
	  [[ -z $newch ]] && break
	  str2=$str2$newch
	  funcs2=(${funcs##$str2*})
	  (( $#funcs2 )) && break
	  str=$str2
	done
      fi
    else
      exact=
    fi
  fi
  if (( $#list )); then
    zle -R "$msg$str$exact" $list
  else
    zle -cR "$msg$str$exact"
  fi
  if ! read -k key; then
    zle -cR ''
    return 1
  fi
done

if [[ -z $str ]]; then
  # string must be non-zero
  return 1
elif [[ $str = _* ]] && ! whence ${str%% *} >& /dev/null; then
  # a function must be known to the shell
  return 1
else
  # remember the string for re-use
  _read_comp=$str
fi

zle -cR ''

if [[ $str = _* ]]; then
  eval $str
else
  eval "compgen $str"
fi