Move into an external dir
[zsh.git] / .zsh / external / zaw / functions / filter-select
1 #
2 # filter-select
3 #
4 # using filter-select, you can incrementaly filter candidate
5 # and select one with ^N/^P keys.
6 #
7 # press enter for filter-select to update $reply and return 0,
8 # press meta (alt) + enter to update $reply but return 1,
9 # and press ^C or ^G not to update $reply and return 1.
10 #
11 # you can use ^@ to mark items. marked items are stored in $reply_marked.
12 #
13 # you can customize keybinds using bindkey command.
14 # first, you call::
15 #
16 # autoload -U filter-select; filter-select -i
17 #
18 # to initialize `filterselect` keymap and then do like::
19 #
20 # bindkey -M filterselect '^E' accept-search
21 #
22 #
23 # usage:
24 # filter-select [-t title] [-A assoc-array-name]
25 # [-d array-of-description] [-D assoc-array-of-descrption]
26 # [-s initial-filter-contents]
27 # [-n] [-r] [-m] [-e exit-zle-widget-name]... [--] [arg]...
28 # filter-select -i
29 #
30 # -t title
31 # title string displayed top of selection.
32 #
33 # -A assoc-array-name
34 # name of associative array that contains candidates.
35 # this option is designed to speed up history selection.
36 #
37 # -d array-of-description
38 # name of array that contains each candidate's descriptions.
39 # it is used to display and filter candidates.
40 #
41 # if not specified, copied from candidates.
42 #
43 # -s initial-filter-contents
44 # initial contents of the filter buffer that users type into
45 #
46 # -n
47 # assign a number to the description when -d is not specified
48 #
49 # -D assoc-array-of-descrption
50 # same as ``-d`` but associative array.
51 #
52 # -r
53 # reverse order.
54 #
55 # -m
56 # enable mark feature
57 #
58 # -e exit-zle-widget-name
59 # if keys bound to `exit-zle-widget-name` is pressed,
60 # filter-select exits and set it's name to $reply[1].
61 #
62 # args
63 # selection candidates.
64 #
65 # -i
66 # only initialize `filterselect` keymaps.
67 #
68 #
69 # default key binds in filterselect:
70 # enter: accept-line (update $reply and return)
71 # meta + enter: accept-search (update $reply but return 1)
72 # ^G: send-break (return 0)
73 # ^H, backspace: backward-delete-char
74 # ^F, right key: forward-char
75 # ^B, left key: backward-char
76 # ^A: beginning-of-line
77 # ^E: end-of-line
78 # ^W: backward-kill-word
79 # ^K: kill-line
80 # ^U: kill-whole-line
81 # ^N, down key: down-line-or-history (select next item)
82 # ^P, up key: up-line-or-history (select previous item)
83 # ^V, page up key: forward-word (page down)
84 # ^[V, page down key: backward-word (page up)
85 # ^[<, home key: beginning-of-history (select first item)
86 # ^[>, end key: end-of-history (select last item)
87 #
88 # available zstyles:
89 # ':filter-select:highlight' selected
90 # ':filter-select:highlight' matched
91 # ':filter-select:highlight' title
92 # ':filter-select:highlight' error
93 # ':filter-select' max-lines
94 # ':filter-select' rotate-list
95 # ':filter-select' case-insensitive
96 # ':filter-select' extended-search
97 #
98 # example:
99 # zstyle ':filter-select:highlight' matched fg=yellow,standout
100 # zstyle ':filter-select' max-lines 10 # use 10 lines for filter-select
101 # zstyle ':filter-select' max-lines -10 # use $LINES - 10 for filter-select
102 # zstyle ':filter-select' rotate-list yes # enable rotation for filter-select
103 # zstyle ':filter-select' case-insensitive yes # enable case-insensitive search
104 # zstyle ':filter-select' extended-search yes # enable extended search regardless of the case-insensitive style
105 #
106 # extended-search:
107 # If this style set to be true value, the searching bahavior will be
108 # extended as follows:
109 #
110 # ^ Match the beginning of the line if the word begins with ^
111 # $ Match the end of the line if the word ends with $
112 # ! Match anything except the word following it if the word begins with !
113 # so-called smartcase searching
114 #
115 # If you want to search these metacharacters, please doubly escape them.
116
117 typeset -ga reply_marked
118
119 function filter-select() {
120 emulate -L zsh
121 setopt local_options extended_glob
122
123 # save ZLE related variables
124 local orig_lbuffer="${LBUFFER}"
125 local orig_rbuffer="${RBUFFER}"
126 local orig_predisplay="${PREDISPLAY}"
127 local orig_postdisplay="${POSTDISPLAY}"
128 local -a orig_region_highlight words
129 orig_region_highlight=("${region_highlight[@]}")
130
131 local key cand lines selected cand_disp buffer_pre_zle last_buffer initbuffer=''
132 local opt pattern msg unused title='' exit_pattern nl=$'\n'
133 local selected_index mark_idx_disp hi start end spec
134 local desc desc_num desc_disp bounds
135
136 local -a displays matched_desc_keys match mbegin mend outs exit_wigdets
137 local -a init_region_highlight marked_lines
138 local -A candidates descriptions matched_descs
139
140 integer i bottom_lines cursor_line=1 display_head_line=1 cand_num disp_num ii num_desc
141 integer offset display_bottom_line selected_num rev=0 ret=0 enum=0
142 integer mark_idx markable=0 is_marked
143
144 local hi_selected hi_matched hi_marked hi_title hi_error
145 zstyle -s ':filter-select:highlight' selected hi_selected || hi_selected='standout'
146 zstyle -s ':filter-select:highlight' matched hi_matched || hi_matched='fg=magenta,underline'
147 zstyle -s ':filter-select:highlight' marked hi_marked || hi_marked='fg=blue,standout'
148 zstyle -s ':filter-select:highlight' title hi_title || hi_title='bold'
149 zstyle -s ':filter-select:highlight' error hi_error || hi_error='fg=white,bg=red'
150
151 integer max_lines
152 zstyle -s ':filter-select' max-lines max_lines || max_lines=0
153
154 local rotate_list
155 zstyle -b ':filter-select' rotate-list rotate_list
156
157 _filter-select-init-keybind
158
159 candidates=()
160 descriptions=()
161 exit_wigdets=(accept-line accept-search send-break)
162
163 while getopts 't:A:d:D:nrme:s:i' opt; do
164 case "${opt}" in
165 t)
166 title="${OPTARG}"
167 ;;
168 A)
169 # copy input assc array
170 candidates=("${(@kvP)${OPTARG}}")
171 ;;
172 d)
173 # copy input array
174 integer i=0
175 for desc in "${(@P)${OPTARG}}"; do
176 (( i++ ))
177 descriptions+=( $i "${desc}" )
178 done
179 ;;
180 D)
181 # copy input assc array
182 descriptions=("${(@kvP)${OPTARG}}")
183 ;;
184 n)
185 enum=1
186 ;;
187 r)
188 # reverse ordering
189 rev=1
190 ;;
191 m)
192 # can use set-mark-command
193 markable=1
194 ;;
195 e)
196 exit_wigdets+="${OPTARG}"
197 ;;
198 s)
199 initbuffer="${OPTARG}"
200 ;;
201 i)
202 # do nothing. only keybinds are initialized
203 return
204 esac
205 done
206
207 if (( OPTIND > 1 )); then
208 shift $(( OPTIND - 1 ))
209 fi
210 integer i=0
211 for cand in "$@"; do
212 (( i++ ))
213 candidates+=( $i "${cand}" )
214 done
215
216 if (( ${#descriptions} == 0 )); then
217 # copy candidates
218 descriptions=("${(@kv)candidates}")
219 # add number
220 if (( enum )); then
221 num_desc="${#descriptions}"
222 for i in {1.."$num_desc"}; do
223 if (( rev )); then
224 ii="$(($num_desc-$i+1))"
225 else
226 ii="$i"
227 fi
228 descriptions[$i]="${(r.5.)ii} ${descriptions[$i]}"
229 done
230 fi
231 fi
232
233 desc_num="${#descriptions}"
234 matched_desc_keys=("${(onk@)descriptions}")
235 if (( rev )); then
236 matched_desc_keys=("${(Oa@)matched_desc_keys}")
237 fi
238
239 key=''
240 bounds=''
241
242 # clear edit buffer
243 BUFFER="$initbuffer"
244
245 # display original edit buffer's contants as PREDISPLAY
246 PREDISPLAY="${orig_predisplay}${orig_lbuffer}${orig_rbuffer}${orig_postdisplay}${nl}"
247
248 # re-calculate region_highlight
249 init_region_highlight=()
250 for hi in "${(@)orig_region_highlight}"; do
251 if [[ "${hi}" == P* ]]; then
252 init_region_highlight+="${hi}"
253 else
254 print -r -- "${hi}" | read start end spec
255 init_region_highlight+="P$(( start + ${#orig_predisplay} )) $(( end + ${#orig_predisplay} )) $spec"
256 fi
257 done
258
259 # prompt for filter-select
260 PREDISPLAY+="filter: "
261
262 # clear strings displayed below the command line
263 zle -Rc
264
265 _filter-select-reset
266
267 exit_pattern="(${(j:|:)exit_wigdets})"
268
269 while [[ "${bounds}" != ${~exit_pattern} ]]; do
270 case "${bounds}" in
271 set-mark-command)
272 if (( markable )); then
273 # check if ${selected_index} is already in the marked_lines
274 if (( ${marked_lines[(ie)${selected_index}]} <= ${#marked_lines} )); then
275 # remove selected_index
276 marked_lines=("${(@)marked_lines:#${selected_index}}")
277 else
278 marked_lines+="${selected_index}"
279 fi
280 fi
281 ;;
282 *down-line-or-history)
283 (( cursor_line++ ))
284 ;;
285
286 *up-line-or-history)
287 (( cursor_line-- ))
288 ;;
289
290 *forward-word)
291 (( cursor_line += bottom_lines ))
292 ;;
293
294 *backward-word)
295 (( cursor_line -= bottom_lines ))
296 ;;
297
298 beginning-of-history)
299 (( cursor_line = 1 ))
300 (( display_head_line = 1 ))
301 ;;
302
303 end-of-history)
304 (( cursor_line = desc_num ))
305 ;;
306
307 self-insert|undefined-key)
308 LBUFFER="${LBUFFER}${key}"
309 _filter-select-reset
310 ;;
311
312 '')
313 # empty, initial state
314 ;;
315
316 *)
317 buffer_pre_zle="${BUFFER}"
318
319 zle "${bounds}"
320
321 if [[ "${BUFFER}" != "${buffer_pre_zle}" ]]; then
322 _filter-select-reset
323 fi
324 esac
325
326 if (( cursor_line < 1 )); then
327 (( display_head_line -= 1 - cursor_line ))
328 if (( display_head_line < 1 )); then
329 (( display_head_line = 1 ))
330 fi
331 if [[ $rotate_list == "yes" ]] && (( selected_num <= 1 )); then
332 (( cursor_line = bottom_lines ))
333 (( display_head_line = desc_num - bottom_lines + 1 ))
334 else
335 (( cursor_line = 1 ))
336 fi
337
338 elif (( bottom_lines == 0 )); then
339 (( display_head_line = 1 ))
340 (( cursor_line = 1 ))
341
342 elif (( cursor_line > bottom_lines )); then
343 (( display_head_line += cursor_line - bottom_lines ))
344 if (( display_head_line > desc_num - bottom_lines + 1 )); then
345 (( display_head_line = desc_num - bottom_lines + 1 ))
346 fi
347 if [[ $rotate_list == "yes" ]] && (( selected_num >= desc_num )); then
348 (( cursor_line = 1 ))
349 (( display_head_line = 1 ))
350 else
351 (( cursor_line = bottom_lines ))
352 fi
353 fi
354
355 if (( ! PENDING )); then
356 region_highlight=("${(@)init_region_highlight}")
357
358 displays=()
359 offset="${#BUFFER}"
360 if [[ -n "${title}" ]]; then
361 offset+=$(( 1 + ${#title} ))
362 fi
363
364 selected=""
365 selected_num=0
366
367 if [[ "${BUFFER}" != "${last_buffer}" ]]; then
368 if [[ -n "${BUFFER}" ]]; then
369 if _filter-select-buffer-words words; then
370 matched_descs=("${(kv@)descriptions}")
371 for pattern in $words; do
372 matched_descs=("${(kv@)matched_descs[(R)*${pattern}*]}")
373 done
374 matched_desc_keys=("${(onk@)matched_descs}")
375 else
376 matched_desc_keys=("${(onk@)descriptions}")
377 fi
378 else
379 matched_desc_keys=("${(onk@)descriptions}")
380 fi
381 if (( rev )); then
382 matched_desc_keys=("${(Oa@)matched_desc_keys}")
383 fi
384 last_buffer="${BUFFER}"
385 fi
386
387 # nums pattern matched
388 desc_num="${#matched_desc_keys}"
389
390 # nums displayed
391 disp_num=0
392
393 _filter-select-update-bottom-lines
394 display_bottom_line=$(( display_head_line + bottom_lines))
395
396 if (( desc_num )); then
397 for i in "${(@)matched_desc_keys[${display_head_line},$(( display_bottom_line - 1 ))]}"; do
398 (( disp_num++ ))
399 desc="${descriptions[$i]}"
400
401 desc_disp="${desc}"
402
403 if zstyle -T ':filter-select' escape-descriptions ; then
404 # escape \r\n\t\
405 desc_disp="${desc_disp//\\/\\\\}"
406 desc_disp="${desc_disp//$'\n'/\\n}"
407 desc_disp="${desc_disp//$'\r'/\\r}"
408 desc_disp="${desc_disp//$'\t'/\\t}"
409 fi
410
411 mark_idx="${marked_lines[(ie)${i}]}"
412 (( is_marked = mark_idx <= ${#marked_lines} ))
413
414 if (( is_marked )); then
415 mark_idx_disp=" (${mark_idx})"
416 else
417 mark_idx_disp=""
418 fi
419
420 if (( ${(m)#desc_disp} + ${#mark_idx_disp} > COLUMNS - 1 )); then
421 # strip long line
422 desc_disp="${(mr:$(( COLUMNS - ${#mark_idx_disp} - 6 )):::::)desc_disp} ...${mark_idx_disp}"
423 else
424 desc_disp="${desc_disp}${mark_idx_disp}"
425 fi
426
427 displays+="${desc_disp}"
428
429 if [[ -n "${BUFFER}" ]]; then
430 # highlight matched words
431 for pattern in \
432 "(${(j.|.)${(@M)words:#*'(#e)'}})" \
433 "(${(j.|.)${(@)words:#(\~*|*'(#e)')}})" ; do
434 if [[ "$pattern" != '()' ]]; then
435 region_highlight+=( "${(f)${(S)desc_disp//*(#b)${~pattern}/$(( offset + mbegin[1] )) $(( offset + mend[1] + 1 )) ${hi_matched}${nl}}%$nl*}" )
436 fi
437 done
438 fi
439
440 if (( is_marked )); then
441 region_highlight+="${offset} $(( offset + ${#desc_disp} - ${#mark_idx_disp} + 1 )) ${hi_marked}"
442 fi
443
444 if (( disp_num == cursor_line )); then
445 region_highlight+="${offset} $(( offset + ${#desc_disp} + 1 )) ${hi_selected}"
446 selected="${candidates[$i]}"
447 (( selected_num = display_head_line + disp_num - 1 ))
448 selected_index="${i}"
449 fi
450
451 (( offset += ${#desc_disp} + 1 )) # +1 -> \n
452 done
453 fi
454
455 POSTDISPLAY=$'\n'
456 if [[ -n "${title}" ]]; then
457 POSTDISPLAY+="${title}"$'\n'
458 region_highlight+="${#BUFFER} $(( ${#BUFFER} + ${#title} + 1 )) ${hi_title}"
459 fi
460
461 if (( ${#displays} == 0 )); then
462 if (( ${#candidates} == 0 )); then
463 msg='no candidate'
464 else
465 msg='pattern not found'
466 fi
467 POSTDISPLAY+="${msg}"
468 region_highlight+="${offset} $(( offset + ${#msg} + 1 )) ${hi_error}"
469 fi
470
471 POSTDISPLAY+="${(F)displays}"$'\n'"[${selected_num}/${desc_num}]"
472 zle -R
473
474 fi
475
476 _filter-select-read-keys
477
478 if [[ $? != 0 ]]; then
479 # maybe ^C
480 key=''
481 bounds=''
482 break
483 else
484 key="${reply}"
485 # TODO: key sequence
486 outs=("${(z)$( bindkey -M filterselect -- "${key}" )}")
487 # XXX: will $outs contains more than two values?
488 bounds="${outs[2]}"
489 fi
490 done
491
492 if [[ -z "${key}" && -z "${bounds}" ]]; then
493 # ^C
494 reply=()
495 reply_marked=()
496 ret=1
497
498 elif [[ "${bounds}" == send-break ]]; then
499 # ^G
500 reply=()
501 reply_marked=()
502 ret=1
503
504 elif (( ${#displays} == 0 )); then
505 # no candidate matches pattern (no candidate selected)
506 reply=()
507 reply_marked=()
508 ret=1
509
510 else
511 reply=("${bounds}" "${selected}")
512 reply_marked=()
513 if (( ${#marked_lines} > 0 )); then
514 for i in "${(@)marked_lines}"; do
515 reply_marked+="${candidates[${i}]}"
516 done
517 fi
518 ret=0
519 fi
520
521 LBUFFER="${orig_lbuffer}"
522 RBUFFER="${orig_rbuffer}"
523 PREDISPLAY="${orig_predisplay}"
524 POSTDISPLAY="${orig_postdisplay}"
525 region_highlight=("${orig_region_highlight[@]}")
526 zle -Rc
527 zle reset-prompt
528
529 return $ret
530 }
531
532 function _filter-select-update-lines() {
533 # XXX: this function override ${lines}
534 # that define as local in filter-select
535 # also use ${title}
536
537 local _tmp_postdisplay="${POSTDISPLAY}"
538 # to re-calculate ${BUFFERLINES}
539 if [[ -n "${title}" ]]; then
540 POSTDISPLAY="${title}"$'\n'
541 else
542 POSTDISPLAY=""
543 fi
544 zle -R
545
546 # lines that can be used to display candidates
547 # -1 for current/total number display area
548 (( lines = LINES - BUFFERLINES - 1 ))
549
550 POSTDISPLAY="${_tmp_postdisplay}"
551 zle -R
552 }
553
554 function _filter-select-update-bottom-lines() {
555 # cursor が移動できる一番下の行
556 # ${max_lines} か ${lines} か ${desc_num} の小さい方を使う
557 if (( max_lines > 0 && max_lines < lines )); then
558 (( bottom_lines = max_lines ))
559 elif (( max_lines < 0 )); then
560 (( bottom_lines = lines + max_lines ))
561 else
562 (( bottom_lines = lines ))
563 fi
564
565 if (( desc_num < bottom_lines )); then
566 (( bottom_lines = desc_num ))
567 fi
568
569 if (( bottom_lines < 1 )); then
570 (( bottom_lines = 1 ))
571 fi
572 }
573
574 function _filter-select-reset() {
575 display_head_line=1
576 cursor_line=1
577 _filter-select-update-lines
578 _filter-select-update-bottom-lines
579 }
580
581 function _filter-select-buffer-words() {
582 local place="$1"
583 local -a a
584 local MATCH MBEGIN MEND
585 # split into words using shell's command line parsing,
586 # unquote the words, remove duplicated,
587 # escape "(", ")", "[", "]" and "#" to avoid crash
588 # also escape "|" and "~"
589 a=("${(@)${(@Qu)${(z)BUFFER}}//(#m)[()[\]#\|~]/\\${MATCH}}")
590
591 if ! zstyle -t ':filter-select' extended-search ; then
592 if zstyle -t ':filter-select' case-insensitive; then
593 : ${(A)a::=(#i)${^a}}
594 fi
595 else
596 # remove single "\\", "!",
597 # "^" like the history-incremental-pattern-searches',
598 # and "!^".
599 : ${(A)a::=${a:#([\\!^]|'!^')}}
600
601 # escape "^" other than the beginning's
602 # unescape "\\^" one level
603 : ${(A)a::=${a//(#m)('^'~(#s)'^')/\\${MATCH}}}
604 : ${(A)a::=${a//(#m)'\\^'/${MATCH#\\}}}
605
606 # "!aoe" -> "~*aoe",
607 # ("a!oe" should be held on, the beginning's "!" only be considered)
608 # unescape "\\!" one level
609 : ${(A)a::=${a/(#m)(#s)\!?##/\~\*${MATCH#\!}}}
610 : ${(A)a::=${a//(#m)'\!'/${MATCH#\\}}} # XXX: not '\\!' though...
611
612 # "^abc" -> "(#s)abc",
613 # ("a^bc" should be held on, the beginning's "^" only be considered)
614 : ${(A)a::=${a/(#m)(#s)\^?##/(#s)${MATCH#\^}}}
615
616 # "xyz$" -> "xyz(#e)",
617 # ("x$yz" shoud be held on, the ending's "$" only be considered)
618 # unescape "\\$" one level
619 : ${(A)a::=${a/(#m)*[^\\]\$(#e)/${MATCH%\$}(#e)}}
620 : ${(A)a::=${a//(#m)'\$'/${MATCH#\\}}} # XXX: not '\\$' though...
621
622 # smartcase searching ("(#i)(#I)Search" searches case sensitively)
623 : ${(A)a::=${a/(#m)*[[:upper:]##]*/(#I)${MATCH}}}
624 : ${(A)a::=(#i)${^a}}
625
626 # make "~" to be at the beginning
627 #: ${(A)a::=${a/#(#b)('(#i)'('(#I)')#)'~'/\~${match[1]}}}
628 : ${(A)a::=${a/#'(#i)(#I)~'/\~(#i)(#I)}}
629 : ${(A)a::=${a/#'(#i)~'/\~(#i)}}
630
631 # fixup the '!^'; "~(#i)*\^" -> "~(#i)(#s)"
632 # (for example, "!^aoe" -> "~(#i)*\^aoe" -> "~(#i)(#s)aoe")
633 #: ${(A)a::=${a/#(#b)'~'('(#i)'('(#I)')#)'*\^'/\~${match[1]}(#s)}}
634 : ${(A)a::=${a/#'~(#i)(#I)*\^'/'~(#i)(#I)(#s)'}}
635 : ${(A)a::=${a/#'~(#i)*\^'/'~(#i)(#s)'}}
636 fi
637 : ${(PA)place::=$a}
638 (( ${#a} > 1 )) || (( ${#a} == 1 )) && [[ -n "$a" ]]
639 }
640
641 function _filter-select-init-keybind() {
642 integer fd ret
643
644 # be quiet and check filterselect keybind defined
645 exec {fd}>&2 2>/dev/null
646 bindkey -l filterselect > /dev/null
647 ret=$?
648 exec 2>&${fd} {fd}>&-
649
650 if (( ret != 0 )); then
651 bindkey -N filterselect
652
653 bindkey -M filterselect '^J' accept-line
654 bindkey -M filterselect '^M' accept-line
655 bindkey -M filterselect '\e^J' accept-search
656 bindkey -M filterselect '\e^M' accept-search
657
658 bindkey -M filterselect '\e^G' send-break
659 bindkey -M filterselect '^G' send-break
660
661 bindkey -M filterselect '^@' set-mark-command
662
663 bindkey -M filterselect '^H' backward-delete-char
664 bindkey -M filterselect '^?' backward-delete-char
665
666 bindkey -M filterselect '^F' forward-char
667 bindkey -M filterselect '\e[C' forward-char
668 bindkey -M filterselect '\eOC' forward-char
669
670 bindkey -M filterselect '^B' backward-char
671 bindkey -M filterselect '\e[D' backward-char
672 bindkey -M filterselect '\eOD' backward-char
673
674 bindkey -M filterselect '^A' beginning-of-line
675 bindkey -M filterselect '^E' end-of-line
676
677 bindkey -M filterselect '^W' backward-kill-word
678 bindkey -M filterselect '^K' kill-line
679 bindkey -M filterselect '^U' kill-whole-line
680
681 # move cursor down/up
682 bindkey -M filterselect '^N' down-line-or-history
683 bindkey -M filterselect '\e[B' down-line-or-history
684 bindkey -M filterselect '\eOB' down-line-or-history
685 bindkey -M filterselect '^P' up-line-or-history
686 bindkey -M filterselect '\e[A' up-line-or-history
687 bindkey -M filterselect '\eOA' up-line-or-history
688
689 # page down/up
690 bindkey -M filterselect '^V' forward-word
691 bindkey -M filterselect '\e[6~' forward-word
692
693 bindkey -M filterselect '\eV' backward-word
694 bindkey -M filterselect '\ev' backward-word
695 bindkey -M filterselect '\e[5~' backward-word
696
697 # home/end
698 bindkey -M filterselect '\e<' beginning-of-history
699 bindkey -M filterselect '\e[1~' beginning-of-history
700
701 bindkey -M filterselect '\e>' end-of-history
702 bindkey -M filterselect '\e[4~' end-of-history
703 fi
704 }
705
706 function _filter-select-read-keys() {
707 local key key2 key3 nkey
708 integer ret
709
710 read -k key
711 ret=$?
712 reply="${key}"
713 if [[ '#key' -eq '#\\e' ]]; then
714 # M-...
715 read -t $(( KEYTIMEOUT / 1000 )) -k key2
716 if [[ "${key2}" == 'O' ]]; then
717 # ^[O (SS3) affects next character only.
718 # Example: cursor keys on some terminals.
719 read -k key3
720 ret=$?
721 reply="${key}${key2}${key3}"
722 else
723 if [[ "${key2}" == '[' ]]; then
724 # ^[[ (CSI) starts a sequence of [0-9;?] terminated by [@-~].
725 # Examples: Home, End, PgUp, PgDn ...
726 reply="${key}${key2}"
727 while true; do
728 read -k nkey
729 reply+=$nkey
730 ret=$?
731 (( $ret == 0 )) && [[ "${nkey}" =~ '^[0-9;?]$' ]] || break
732 done
733 else
734 reply="${key}${key2}"
735 fi
736 fi
737 else
738 reply="${key}"
739 fi
740 return $ret
741 }
742
743 filter-select "$@"