]> src.twobees.de Git - dotfiles.git/blob - stow/oh-my-zsh/.oh-my-zsh/plugins/zsh-navigation-tools/n-list
initial
[dotfiles.git] / stow / oh-my-zsh / .oh-my-zsh / plugins / zsh-navigation-tools / n-list
1 # $1, $2, ... - elements of the list
2 # $NLIST_NONSELECTABLE_ELEMENTS - array of indexes (1-based) that cannot be selected
3 # $REPLY is the output variable - contains index (1-based) or -1 when no selection
4 # $reply (array) is the second part of the output - use the index (REPLY) to get selected element
5 #
6 # Copy this file into /usr/share/zsh/site-functions/
7 # and add 'autoload n-list` to .zshrc
8 #
9 # This function outputs a list of elements that can be
10 # navigated with keyboard. Uses curses library
11
12 emulate -LR zsh
13
14 setopt typesetsilent extendedglob noshortloops
15
16 _nlist_has_terminfo=0
17
18 zmodload zsh/curses
19 zmodload zsh/terminfo 2>/dev/null && _nlist_has_terminfo=1
20
21 trap "REPLY=-2; reply=(); return" TERM INT QUIT
22 trap "_nlist_exit" EXIT
23
24 # Drawing and input
25 autoload n-list-draw n-list-input
26
27 # Cleanup before any exit
28 _nlist_exit() {
29     setopt localoptions
30     setopt extendedglob
31
32     [[ "$REPLY" = -(#c0,1)[0-9]## || "$REPLY" = F<-> || "$REPLY" = "EDIT" || "$REPLY" = "HELP" ]] || REPLY="-1"
33     zcurses 2>/dev/null delwin inner
34     zcurses 2>/dev/null delwin main
35     zcurses 2>/dev/null refresh
36     zcurses end
37     _nlist_alternate_screen 0
38     _nlist_cursor_visibility 1
39     unset _nlist_has_terminfo
40 }
41
42 # Outputs a message in the bottom of the screen
43 _nlist_status_msg() {
44     # -1 for border, -1 for 0-based indexing
45     zcurses move main $(( term_height - 1 - 1 )) 2
46     zcurses clear main eol
47     zcurses string main "$1"
48     #status_msg_strlen is localized in caller
49     status_msg_strlen=$#1
50 }
51
52 # Prefer tput, then module terminfo
53 _nlist_cursor_visibility() {
54     if type tput 2>/dev/null 1>&2; then
55         [ "$1" = "1" ] && { tput cvvis; tput cnorm }
56         [ "$1" = "0" ] && tput civis
57     elif [ "$_nlist_has_terminfo" = "1" ]; then
58         [ "$1" = "1" ] && { [ -n $terminfo[cvvis] ] && echo -n $terminfo[cvvis];
59                            [ -n $terminfo[cnorm] ] && echo -n $terminfo[cnorm] }
60         [ "$1" = "0" ] && [ -n $terminfo[civis] ] && echo -n $terminfo[civis]
61     fi 
62 }
63
64 # Reason for this function is that on some systems
65 # smcup and rmcup are not knowing why left empty
66 _nlist_alternate_screen() {
67     [ "$_nlist_has_terminfo" -ne "1" ] && return
68     [[ "$1" = "1" && -n "$terminfo[smcup]" ]] && return
69     [[ "$1" = "0" && -n "$terminfo[rmcup]" ]] && return
70
71     case "$TERM" in
72         *rxvt*)
73             [ "$1" = "1" ] && echo -n $'\x1b7\x1b[?47h'
74             [ "$1" = "0" ] && echo -n $'\x1b[2J\x1b[?47l\x1b8'
75             ;;
76         *)
77             [ "$1" = "1" ] && echo -n $'\x1b[?1049h'
78             [ "$1" = "0" ] && echo -n $'\x1b[?1049l'
79             # just to remember two other that work: $'\x1b7\x1b[r\x1b[?47h', $'\x1b[?47l\x1b8'
80             ;;
81     esac
82 }
83
84 _nlist_compute_user_vars_difference() {
85         if [[ "${(t)NLIST_NONSELECTABLE_ELEMENTS}" != "array" &&
86                 "${(t)NLIST_NONSELECTABLE_ELEMENTS}" != "array-local" ]]
87         then
88             last_element_difference=0
89             current_difference=0
90         else
91             last_element_difference=$#NLIST_NONSELECTABLE_ELEMENTS
92             current_difference=0
93             local idx
94             for idx in "${(n)NLIST_NONSELECTABLE_ELEMENTS[@]}"; do
95                 [ "$idx" -le "$NLIST_CURRENT_IDX" ] && current_difference+=1 || break
96             done
97         fi
98 }
99
100 # List was processed, check if variables aren't off range
101 _nlist_verify_vars() {
102     [ "$NLIST_CURRENT_IDX" -gt "$last_element" ] && NLIST_CURRENT_IDX="$last_element"
103     [[ "$NLIST_CURRENT_IDX" -eq 0 && "$last_element" -ne 0 ]] && NLIST_CURRENT_IDX=1
104     (( NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN=0+((NLIST_CURRENT_IDX-1)/page_height)*page_height+1 ))
105 }
106
107 # Compute the variables which are shown to the user
108 _nlist_setup_user_vars() {
109     if [ "$1" = "1" ]; then
110         # Basic values when there are no non-selectables
111         NLIST_USER_CURRENT_IDX="$NLIST_CURRENT_IDX"
112         NLIST_USER_LAST_ELEMENT="$last_element"
113     else
114         _nlist_compute_user_vars_difference
115         NLIST_USER_CURRENT_IDX=$(( NLIST_CURRENT_IDX - current_difference ))
116         NLIST_USER_LAST_ELEMENT=$(( last_element - last_element_difference ))
117     fi
118 }
119
120 _nlist_colorify_disp_list() {
121     local col=$'\x1b[00;34m' reset=$'\x1b[0m'
122     [ -n "$NLIST_COLORING_COLOR" ] && col="$NLIST_COLORING_COLOR"
123     [ -n "$NLIST_COLORING_END_COLOR" ] && reset="$NLIST_COLORING_END_COLOR"
124
125     if [ "$NLIST_COLORING_MATCH_MULTIPLE" -eq 1 ]; then
126         disp_list=( "${(@)disp_list//(#mi)$~NLIST_COLORING_PATTERN/$col${MATCH}$reset}" )
127     else
128         disp_list=( "${(@)disp_list/(#mi)$~NLIST_COLORING_PATTERN/$col${MATCH}$reset}" )
129     fi
130 }
131
132 #
133 # Main code
134 #
135
136 # Check if there is proper input
137 if [ "$#" -lt 1 ]; then
138     echo "Usage: n-list element_1 ..."
139     return 1
140 fi
141
142 REPLY="-1"
143 typeset -ga reply
144 reply=()
145
146 integer term_height="$LINES"
147 integer term_width="$COLUMNS"
148 if [[ "$term_height" -lt 1 || "$term_width" -lt 1 ]]; then
149     local stty_out=$( stty size )
150     term_height="${stty_out% *}"
151     term_width="${stty_out#* }"
152 fi
153 integer inner_height=term_height-3
154 integer inner_width=term_width-3
155 integer page_height=inner_height
156 integer page_width=inner_width
157
158 typeset -a list disp_list
159 integer last_element=$#
160 local action
161 local final_key
162 integer selection
163 integer last_element_difference=0
164 integer current_difference=0
165 local prev_search_buffer=""
166 integer prev_uniq_mode=0
167 integer prev_start_idx=-1
168 local MBEGIN MEND MATCH mbegin mend match
169
170 # Iteration over predefined keywords
171 integer curkeyword nkeywords
172 local keywordisfresh="0"
173 if [[ "${(t)keywords}" != *array* ]]; then
174     local -a keywords
175     keywords=()
176 fi
177 curkeyword=0
178 nkeywords=${#keywords}
179
180 # Iteration over themes
181 integer curtheme nthemes
182 local themeisfresh="0"
183 if [[ "${(t)themes}" != *array* ]]; then
184     local -a themes
185     themes=()
186 fi
187 curtheme=0
188 nthemes=${#themes}
189
190 # Ability to remember the list between calls
191 if [[ -z "$NLIST_REMEMBER_STATE" || "$NLIST_REMEMBER_STATE" -eq 0 || "$NLIST_REMEMBER_STATE" -eq 2 ]]; then
192     NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN=1
193     NLIST_CURRENT_IDX=1
194     NLIST_IS_SEARCH_MODE=0
195     NLIST_SEARCH_BUFFER=""
196     NLIST_TEXT_OFFSET=0
197     NLIST_IS_UNIQ_MODE=0
198     NLIST_IS_F_MODE=0
199
200     # Zero - because it isn't known, unless we
201     # confirm that first element is selectable
202     NLIST_USER_CURRENT_IDX=0
203     [[ ${NLIST_NONSELECTABLE_ELEMENTS[(r)1]} != 1 ]] && NLIST_USER_CURRENT_IDX=1
204     NLIST_USER_LAST_ELEMENT=$(( last_element - $#NLIST_NONSELECTABLE_ELEMENTS ))
205
206     # 2 is init once, then remember
207     [ "$NLIST_REMEMBER_STATE" -eq 2 ] && NLIST_REMEMBER_STATE=1
208 fi
209
210 if [ "$NLIST_START_IN_SEARCH_MODE" -eq 1 ]; then
211     NLIST_START_IN_SEARCH_MODE=0
212     NLIST_IS_SEARCH_MODE=1
213 fi
214
215 if [ -n "$NLIST_SET_SEARCH_TO" ]; then
216     NLIST_SEARCH_BUFFER="$NLIST_SET_SEARCH_TO"
217     NLIST_SET_SEARCH_TO=""
218 fi
219
220 if [ "$NLIST_START_IN_UNIQ_MODE" -eq 1 ]; then
221     NLIST_START_IN_UNIQ_MODE=0
222     NLIST_IS_UNIQ_MODE=1
223 fi
224
225 _nlist_alternate_screen 1
226 zcurses init
227 zcurses delwin main 2>/dev/null
228 zcurses delwin inner 2>/dev/null
229 zcurses addwin main "$term_height" "$term_width" 0 0
230 zcurses addwin inner "$inner_height" "$inner_width" 1 2
231 # From n-list.conf
232 [ "$colorpair" = "" ] && colorpair="white/black"
233 [ "$border" = "0" ] || border="1"
234 local background="${colorpair#*/}"
235 local backuptheme="$colorpair/$bold"
236 zcurses bg main "$colorpair"
237 zcurses bg inner "$colorpair"
238 if [ "$NLIST_IS_SEARCH_MODE" -ne 1 ]; then
239     _nlist_cursor_visibility 0
240 fi
241
242 zcurses refresh
243
244 #
245 # Listening for input
246 #
247
248 local key keypad
249
250 # Clear input buffer
251 zcurses timeout main 0
252 zcurses input main key keypad
253 zcurses timeout main -1
254 key=""
255 keypad=""
256
257 # This loop makes script faster on some Zsh's (e.g. 5.0.8)
258 repeat 1; do
259     list=( "$@" )
260 done
261
262 last_element="$#list"
263
264 zcurses clear main redraw
265 zcurses clear inner redraw
266 while (( 1 )); do
267     # Do searching (filtering with string)
268     if [ -n "$NLIST_SEARCH_BUFFER" ]; then
269         # Compute new list?
270         if [[ "$NLIST_SEARCH_BUFFER" != "$prev_search_buffer" || "$NLIST_IS_UNIQ_MODE" -ne "$prev_uniq_mode"
271                 || "$NLIST_IS_F_MODE" -ne "$prev_f_mode" ]]
272         then
273             prev_search_buffer="$NLIST_SEARCH_BUFFER"
274             prev_uniq_mode="$NLIST_IS_UNIQ_MODE"
275             prev_f_mode="$NLIST_IS_F_MODE"
276             # regenerating list -> regenerating disp_list
277             prev_start_idx=-1
278
279             # Take all elements, including duplicates and non-selectables
280             typeset +U list
281             repeat 1; do
282                 list=( "$@" )
283             done
284
285             # Remove non-selectable elements
286             [ "$#NLIST_NONSELECTABLE_ELEMENTS" -gt 0 ] && for i in "${(nO)NLIST_NONSELECTABLE_ELEMENTS[@]}"; do
287                 if [[ "$i" = <-> ]]; then
288                     list[$i]=()
289                 fi
290             done
291
292             # Remove duplicates
293             [ "$NLIST_IS_UNIQ_MODE" -eq 1 ] && typeset -U list
294
295             last_element="$#list"
296
297             # Next do the filtering
298             local search_buffer="${NLIST_SEARCH_BUFFER%% ##}"
299             search_buffer="${search_buffer## ##}"
300             search_buffer="${search_buffer//(#m)[][*?|#~^()><\\]/\\$MATCH}"
301             local search_pattern=""
302             local colsearch_pattern=""
303             if [ -n "$search_buffer" ]; then
304                 # The repeat will make the matching work on a fresh heap
305                 repeat 1; do
306                     if [ "$NLIST_IS_F_MODE" -eq "1" ]; then
307                         search_pattern="${search_buffer// ##/*~^(#a1)*}"
308                         colsearch_pattern="${search_buffer// ##/|(#a1)}"
309                         list=( "${(@M)list:#(#ia1)*$~search_pattern*}" )
310                     elif [ "$NLIST_IS_F_MODE" -eq "2" ]; then
311                         search_pattern="${search_buffer// ##/*~^(#a2)*}"
312                         colsearch_pattern="${search_buffer// ##/|(#a2)}"
313                         list=( "${(@M)list:#(#ia2)*$~search_pattern*}" )
314                     else
315                         # Pattern will be *foo*~^*bar* (inventor: Mikael Magnusson)
316                         search_pattern="${search_buffer// ##/*~^*}"
317                         # Pattern will be (foo|bar)
318                         colsearch_pattern="${search_buffer// ##/|}"
319                         list=( "${(@M)list:#(#i)*$~search_pattern*}" )
320                     fi
321                 done
322
323                 last_element="$#list"
324             fi
325
326             # Called after processing list
327             _nlist_verify_vars
328         fi
329
330         _nlist_setup_user_vars 1
331
332         integer end_idx=$(( NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN + page_height - 1 ))
333         [ "$end_idx" -gt "$last_element" ] && end_idx=last_element
334
335         if [ "$prev_start_idx" -ne "$NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN" ]; then
336             prev_start_idx="$NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN"
337             disp_list=( "${(@)list[NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN, end_idx]}" )
338
339             if [ -n "$colsearch_pattern" ]; then
340                 local red=$'\x1b[00;31m' reset=$'\x1b[00;00m'
341                 # The repeat will make the matching work on a fresh heap
342                 repeat 1; do
343                     if [ "$NLIST_IS_F_MODE" -eq "1" ]; then
344                         disp_list=( "${(@)disp_list//(#mia1)($~colsearch_pattern)/$red${MATCH}$reset}" )
345                     elif [ "$NLIST_IS_F_MODE" -eq "2" ]; then
346                         disp_list=( "${(@)disp_list//(#mia2)($~colsearch_pattern)/$red${MATCH}$reset}" )
347                     else
348                         disp_list=( "${(@)disp_list//(#mi)($~colsearch_pattern)/$red${MATCH}$reset}" )
349                     fi
350                 done
351             fi
352
353             # We have display list, lets replace newlines with "\n" when needed (1/2)
354             [ "$NLIST_REPLACE_NEWLINES" -eq 1 ] && disp_list=( "${(@)disp_list//$'\n'/\\n}" )
355         fi
356
357         # Output colored list
358         zcurses clear inner
359         n-list-draw "$(( (NLIST_CURRENT_IDX-1) % page_height + 1 ))" \
360             "$page_height" "$page_width" 0 0 "$NLIST_TEXT_OFFSET" inner \
361             "$disp_list[@]"
362     else
363         # There is no search, but there was in previous loop
364         # OR
365         # Uniq mode was entered or left out
366         # -> compute new list
367         if [[ -n "$prev_search_buffer" || "$NLIST_IS_UNIQ_MODE" -ne "$prev_uniq_mode" ]]; then
368             prev_search_buffer=""
369             prev_uniq_mode="$NLIST_IS_UNIQ_MODE"
370             # regenerating list -> regenerating disp_list
371             prev_start_idx=-1
372
373             # Take all elements, including duplicates and non-selectables
374             typeset +U list
375             repeat 1; do
376                 list=( "$@" )
377             done
378
379             # Remove non-selectable elements only when in uniq mode
380             [ "$NLIST_IS_UNIQ_MODE" -eq 1 ] && [ "$#NLIST_NONSELECTABLE_ELEMENTS" -gt 0 ] &&
381             for i in "${(nO)NLIST_NONSELECTABLE_ELEMENTS[@]}"; do
382                 if [[ "$i" = <-> ]]; then
383                     list[$i]=()
384                 fi
385             done
386
387             # Remove duplicates when in uniq mode
388             [ "$NLIST_IS_UNIQ_MODE" -eq 1 ] && typeset -U list
389
390             last_element="$#list"
391             # Called after processing list
392             _nlist_verify_vars
393         fi
394
395         # "1" - shouldn't bother with non-selectables
396         _nlist_setup_user_vars "$NLIST_IS_UNIQ_MODE"
397
398         integer end_idx=$(( NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN + page_height - 1 ))
399         [ "$end_idx" -gt "$last_element" ] && end_idx=last_element
400
401         if [ "$prev_start_idx" -ne "$NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN" ]; then
402             prev_start_idx="$NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN"
403             disp_list=( "${(@)list[NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN, end_idx]}" )
404
405             [ -n "$NLIST_COLORING_PATTERN" ] && _nlist_colorify_disp_list
406
407             # We have display list, lets replace newlines with "\n" when needed (2/2)
408             [ "$NLIST_REPLACE_NEWLINES" -eq 1 ] && disp_list=( "${(@)disp_list//$'\n'/\\n}" )
409         fi
410
411         # Output the list
412         zcurses clear inner
413         n-list-draw "$(( (NLIST_CURRENT_IDX-1) % page_height + 1 ))" \
414             "$page_height" "$page_width" 0 0 "$NLIST_TEXT_OFFSET" inner \
415             "$disp_list[@]"
416     fi
417
418     local status_msg_strlen
419     local keywordmsg=""
420     if [ "$keywordisfresh" = "1" ]; then
421         keywordmsg="($curkeyword/$nkeywords) "
422         keywordisfresh="0"
423     fi
424
425     local thememsg=""
426     if [ "$themeisfresh" = "1" ]; then
427         local theme="$backuptheme"
428         [ "$curtheme" -gt 0 ] && theme="${themes[curtheme]}"
429         thememsg="($curtheme/$nthemes $theme) "
430         themeisfresh="0"
431     fi
432
433     local _txt2="" _txt3=""
434     [ "$NLIST_IS_UNIQ_MODE" -eq 1 ] && _txt2="[-UNIQ-] "
435     [ "$NLIST_IS_F_MODE" -eq 1 ] && _txt3="[-FIX-] "
436     [ "$NLIST_IS_F_MODE" -eq 2 ] && _txt3="[-FIX2-] "
437
438     if [ "$NLIST_IS_SEARCH_MODE" = "1" ]; then
439         _nlist_status_msg "${_txt2}${_txt3}${keywordmsg}${thememsg}Filtering with: ${NLIST_SEARCH_BUFFER// /+}"
440     elif [[ ${NLIST_NONSELECTABLE_ELEMENTS[(r)$NLIST_CURRENT_IDX]} != $NLIST_CURRENT_IDX ||
441             -n "$NLIST_SEARCH_BUFFER" || "$NLIST_IS_UNIQ_MODE" -eq 1 ]]; then
442         local _txt=""
443         [ -n "$NLIST_GREP_STRING" ] && _txt=" [$NLIST_GREP_STRING]"
444         _nlist_status_msg "${_txt2}${_txt3}${keywordmsg}${thememsg}Current #$NLIST_USER_CURRENT_IDX (of #$NLIST_USER_LAST_ELEMENT entries)$_txt"
445     else
446         _nlist_status_msg "${keywordmsg}${thememsg}"
447     fi
448
449     [ "$border" = "1" ] && zcurses border main
450
451     local top_msg=" ${(C)ZSH_NAME} $ZSH_VERSION, shell level $SHLVL "
452     if [[ "${NLIST_ENABLED_EVENTS[(r)F1]}" = "F1" ]]; then
453         top_msg=" F1-change view,$top_msg"
454     fi
455     zcurses move main 0 $(( term_width / 2 - $#top_msg / 2 ))
456     zcurses string main $top_msg
457
458     zcurses refresh main inner
459     zcurses move main $(( term_height - 1 - 1 )) $(( status_msg_strlen + 2 ))
460
461     # Wait for input
462     zcurses input main key keypad
463
464     # Get the special (i.e. "keypad") key or regular key
465     if [ -n "$key" ]; then
466         final_key="$key"
467     elif [ -n "$keypad" ]; then
468         final_key="$keypad"
469     else
470         _nlist_status_msg "Inproper input detected"
471         zcurses refresh main inner
472     fi
473
474     n-list-input "$NLIST_CURRENT_IDX" "$NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN" \
475                     "$page_height" "$page_width" "$last_element" "$NLIST_TEXT_OFFSET" \
476                     "$final_key" "$NLIST_IS_SEARCH_MODE" "$NLIST_SEARCH_BUFFER" \
477                     "$NLIST_IS_UNIQ_MODE" "$NLIST_IS_F_MODE"
478
479     selection="$reply[1]"
480     action="$reply[2]"
481     NLIST_CURRENT_IDX="$reply[3]"
482     NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN="$reply[4]"
483     NLIST_TEXT_OFFSET="$reply[5]"
484     NLIST_IS_SEARCH_MODE="$reply[6]"
485     NLIST_SEARCH_BUFFER="$reply[7]"
486     NLIST_IS_UNIQ_MODE="$reply[8]"
487     NLIST_IS_F_MODE="$reply[9]"
488
489     if [ -z "$action" ]; then
490         continue
491     elif [ "$action" = "SELECT" ]; then
492         REPLY="$selection"
493         reply=( "$list[@]" )
494         break
495     elif [ "$action" = "QUIT" ]; then
496         REPLY=-1
497         reply=( "$list[@]" )
498         break
499     elif [ "$action" = "REDRAW" ]; then
500         zcurses clear main redraw
501         zcurses clear inner redraw
502     elif [[ "$action" = F<-> ]]; then
503         REPLY="$action"
504         reply=( "$list[@]" )
505         break
506     elif [[ "$action" = "EDIT" ]]; then
507         REPLY="EDIT"
508         reply=( "$list[@]" )
509         break
510     elif [[ "$action" = "HELP" ]]; then
511         REPLY="HELP"
512         reply=( "$list[@]" )
513         break
514     fi
515 done
516
517 # vim: set filetype=zsh: