Parent directory

n-history

12837 bytes
  1# Copy this file into /usr/share/zsh/site-functions/
  2# and add 'autoload n-history` to .zshrc
  3#
  4# This function allows to browse Z shell's history and use the
  5# entries
  6#
  7# Uses n-list
  8
  9emulate -L zsh
 10
 11setopt extendedglob
 12zmodload zsh/curses
 13zmodload zsh/parameter
 14
 15local IFS="
 16"
 17
 18# Variables to save list's state when switching views
 19# The views are: history and "most frequent history words"
 20local one_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
 21local one_NLIST_CURRENT_IDX
 22local one_NLIST_IS_SEARCH_MODE
 23local one_NLIST_SEARCH_BUFFER
 24local one_NLIST_TEXT_OFFSET
 25local one_NLIST_IS_UNIQ_MODE
 26local one_NLIST_IS_F_MODE
 27local one_NLIST_GREP_STRING
 28local one_NLIST_NONSELECTABLE_ELEMENTS
 29local one_NLIST_REMEMBER_STATE
 30local one_NLIST_ENABLED_EVENTS
 31
 32local two_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
 33local two_NLIST_CURRENT_IDX
 34local two_NLIST_IS_SEARCH_MODE
 35local two_NLIST_SEARCH_BUFFER
 36local two_NLIST_TEXT_OFFSET
 37local two_NLIST_IS_UNIQ_MODE
 38local two_NLIST_IS_F_MODE
 39local two_NLIST_GREP_STRING
 40local two_NLIST_NONSELECTABLE_ELEMENTS
 41local two_NLIST_REMEMBER_STATE
 42local two_NLIST_ENABLED_EVENTS
 43
 44local three_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
 45local three_NLIST_CURRENT_IDX
 46local three_NLIST_IS_SEARCH_MODE
 47local three_NLIST_SEARCH_BUFFER
 48local three_NLIST_TEXT_OFFSET
 49local three_NLIST_IS_UNIQ_MODE
 50local three_NLIST_IS_F_MODE
 51local three_NLIST_GREP_STRING
 52local three_NLIST_NONSELECTABLE_ELEMENTS
 53local three_NLIST_REMEMBER_STATE
 54local three_NLIST_ENABLED_EVENTS
 55
 56# history view
 57integer active_view=0
 58
 59# Lists are "0", "1", "2" - 1st, 2nd, 3rd
 60# Switching is done in cyclic manner
 61# i.e. 0 -> 1, 1 -> 2, 2 -> 0
 62_nhistory_switch_lists_states() {
 63    # First argument is current, newly selected list, i.e. $active_view
 64    # This implies that we are switching from previous view
 65   
 66    if [ "$1" = "0" ]; then
 67        # Switched to 1st list, save 3rd list's state
 68        three_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN=$NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
 69        three_NLIST_CURRENT_IDX=$NLIST_CURRENT_IDX
 70        three_NLIST_IS_SEARCH_MODE=$NLIST_IS_SEARCH_MODE
 71        three_NLIST_SEARCH_BUFFER=$NLIST_SEARCH_BUFFER
 72        three_NLIST_TEXT_OFFSET=$NLIST_TEXT_OFFSET
 73        three_NLIST_IS_UNIQ_MODE=$NLIST_IS_UNIQ_MODE
 74        three_NLIST_IS_F_MODE=$NLIST_IS_F_MODE
 75        three_NLIST_GREP_STRING=$NLIST_GREP_STRING
 76        three_NLIST_NONSELECTABLE_ELEMENTS=( ${NLIST_NONSELECTABLE_ELEMENTS[@]} )
 77        three_NLIST_REMEMBER_STATE=$NLIST_REMEMBER_STATE
 78        three_NLIST_ENABLED_EVENTS=( ${NLIST_ENABLED_EVENTS[@]} )
 79
 80        # ..and restore 1st list's state
 81        NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN=$one_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
 82        NLIST_CURRENT_IDX=$one_NLIST_CURRENT_IDX
 83        NLIST_IS_SEARCH_MODE=$one_NLIST_IS_SEARCH_MODE
 84        NLIST_SEARCH_BUFFER=$one_NLIST_SEARCH_BUFFER
 85        NLIST_TEXT_OFFSET=$one_NLIST_TEXT_OFFSET
 86        NLIST_IS_UNIQ_MODE=$one_NLIST_IS_UNIQ_MODE
 87        NLIST_IS_F_MODE=$one_NLIST_IS_F_MODE
 88        NLIST_GREP_STRING=$one_NLIST_GREP_STRING
 89        NLIST_NONSELECTABLE_ELEMENTS=( ${one_NLIST_NONSELECTABLE_ELEMENTS[@]} )
 90        NLIST_REMEMBER_STATE=$one_NLIST_REMEMBER_STATE
 91        NLIST_ENABLED_EVENTS=( ${one_NLIST_ENABLED_EVENTS[@]} )
 92    elif [ "$1" = "1" ]; then
 93        # Switched to 2nd list, save 1st list's state
 94        one_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN=$NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
 95        one_NLIST_CURRENT_IDX=$NLIST_CURRENT_IDX
 96        one_NLIST_IS_SEARCH_MODE=$NLIST_IS_SEARCH_MODE
 97        one_NLIST_SEARCH_BUFFER=$NLIST_SEARCH_BUFFER
 98        one_NLIST_TEXT_OFFSET=$NLIST_TEXT_OFFSET
 99        one_NLIST_IS_UNIQ_MODE=$NLIST_IS_UNIQ_MODE
100        one_NLIST_IS_F_MODE=$NLIST_IS_F_MODE
101        one_NLIST_GREP_STRING=$NLIST_GREP_STRING
102        one_NLIST_NONSELECTABLE_ELEMENTS=( ${NLIST_NONSELECTABLE_ELEMENTS[@]} )
103        one_NLIST_REMEMBER_STATE=$NLIST_REMEMBER_STATE
104        one_NLIST_ENABLED_EVENTS=( ${NLIST_ENABLED_EVENTS[@]} )
105
106        # ..and restore 2nd list's state
107        NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN=$two_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
108        NLIST_CURRENT_IDX=$two_NLIST_CURRENT_IDX
109        NLIST_IS_SEARCH_MODE=$two_NLIST_IS_SEARCH_MODE
110        NLIST_SEARCH_BUFFER=$two_NLIST_SEARCH_BUFFER
111        NLIST_TEXT_OFFSET=$two_NLIST_TEXT_OFFSET
112        NLIST_IS_UNIQ_MODE=$two_NLIST_IS_UNIQ_MODE
113        NLIST_IS_F_MODE=$two_NLIST_IS_F_MODE
114        NLIST_GREP_STRING=$two_NLIST_GREP_STRING
115        NLIST_NONSELECTABLE_ELEMENTS=( ${two_NLIST_NONSELECTABLE_ELEMENTS[@]} )
116        NLIST_REMEMBER_STATE=$two_NLIST_REMEMBER_STATE
117        NLIST_ENABLED_EVENTS=( ${two_NLIST_ENABLED_EVENTS[@]} )
118    elif [ "$1" = "2" ]; then
119        # Switched to 3rd list, save 2nd list's state
120        two_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN=$NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
121        two_NLIST_CURRENT_IDX=$NLIST_CURRENT_IDX
122        two_NLIST_IS_SEARCH_MODE=$NLIST_IS_SEARCH_MODE
123        two_NLIST_SEARCH_BUFFER=$NLIST_SEARCH_BUFFER
124        two_NLIST_TEXT_OFFSET=$NLIST_TEXT_OFFSET
125        two_NLIST_IS_UNIQ_MODE=$NLIST_IS_UNIQ_MODE
126        two_NLIST_IS_F_MODE=$NLIST_IS_F_MODE
127        two_NLIST_GREP_STRING=$NLIST_GREP_STRING
128        two_NLIST_NONSELECTABLE_ELEMENTS=( ${NLIST_NONSELECTABLE_ELEMENTS[@]} )
129        two_NLIST_REMEMBER_STATE=$NLIST_REMEMBER_STATE
130        two_NLIST_ENABLED_EVENTS=( ${NLIST_ENABLED_EVENTS[@]} )
131
132        # ..and restore 3rd list's state
133        NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN=$three_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
134        NLIST_CURRENT_IDX=$three_NLIST_CURRENT_IDX
135        NLIST_IS_SEARCH_MODE=$three_NLIST_IS_SEARCH_MODE
136        NLIST_SEARCH_BUFFER=$three_NLIST_SEARCH_BUFFER
137        NLIST_TEXT_OFFSET=$three_NLIST_TEXT_OFFSET
138        NLIST_IS_UNIQ_MODE=$three_NLIST_IS_UNIQ_MODE
139        NLIST_IS_F_MODE=$three_NLIST_IS_F_MODE
140        NLIST_GREP_STRING=$three_NLIST_GREP_STRING
141        NLIST_NONSELECTABLE_ELEMENTS=( ${three_NLIST_NONSELECTABLE_ELEMENTS[@]} )
142        NLIST_REMEMBER_STATE=$three_NLIST_REMEMBER_STATE
143        NLIST_ENABLED_EVENTS=( ${three_NLIST_ENABLED_EVENTS[@]} )
144    fi
145}
146
147local most_frequent_db="$HOME/.config/znt/mostfrequent.db"
148_nhistory_generate_most_frequent() {
149    local title=$'\x1b[00;31m'"Most frequent history words:"$'\x1b[00;00m\0'
150
151    typeset -A uniq
152    for k in "${historywords[@]}"; do
153        uniq[$k]=$(( ${uniq[$k]:-0} + 1 ))
154    done
155    vk=()
156    for k v in ${(kv)uniq}; do
157        vk+="$v"$'\t'"$k"
158    done
159
160    print -rl -- "$title" "${(On)vk[@]}" > "$most_frequent_db"
161}
162
163# Load configuration
164unset NLIST_COLORING_PATTERN
165[ -f ~/.config/znt/n-list.conf ] && builtin source ~/.config/znt/n-list.conf
166[ -f ~/.config/znt/n-history.conf ] && builtin source ~/.config/znt/n-history.conf
167
168local list
169local selected
170local private_history_db="$HOME/.config/znt/privhist.db"
171
172local NLIST_GREP_STRING="$1"
173# 2 is: init once, then remember
174local NLIST_REMEMBER_STATE=2
175two_NLIST_REMEMBER_STATE=2
176three_NLIST_REMEMBER_STATE=2
177
178# Only Private history has EDIT
179local -a NLIST_ENABLED_EVENTS
180NLIST_ENABLED_EVENTS=( "F1" "HELP" )
181two_NLIST_ENABLED_EVENTS=( "F1" "EDIT" "HELP" )
182three_NLIST_ENABLED_EVENTS=( "F1" "HELP" )
183
184# All view should attempt to replace new lines with \n
185local NLIST_REPLACE_NEWLINES="1"
186two_NLIST_REPLACE_NEWLINES="1"
187three_NLIST_REPLACE_NEWLINES="1"
188
189# Only second and third view has non-selectable first entry
190local -a NLIST_NONSELECTABLE_ELEMENTS
191NLIST_NONSELECTABLE_ELEMENTS=( )
192two_NLIST_NONSELECTABLE_ELEMENTS=( 1 )
193three_NLIST_NONSELECTABLE_ELEMENTS=( 1 )
194
195while (( 1 )); do
196
197    #
198    # View 1 - history
199    #
200    if [ "$active_view" = "0" ]; then
201        list=( "$history[@]" )
202        list=( "${(@M)list:#(#i)*$NLIST_GREP_STRING*}" )
203
204        if [ "$#list" -eq 0 ]; then
205            echo "No matching history entries"
206            return 1
207        fi
208
209        n-list "${list[@]}"
210
211        # Selection or quit?
212        if [[ "$REPLY" = -(#c0,1)[0-9]## && ("$REPLY" -lt 0 || "$REPLY" -gt 0) ]]; then
213            break
214        fi
215
216        # View change?
217        if [ "$REPLY" = "F1" ]; then
218            # Target view: 2
219            active_view=1
220            _nhistory_switch_lists_states "1"
221        elif [ "$REPLY" = "HELP" ]; then
222            n-help
223        fi
224
225    #
226    # View 3 - most frequent words in history
227    #
228    elif [ "$active_view" = "2" ]; then
229        local -a dbfile
230        dbfile=( $most_frequent_db(Nm+1) )
231
232        # Compute most frequent history words
233        if [[ "${#NHISTORY_WORDS}" -eq "0" || "${#dbfile}" -ne "0" ]]; then
234            # Read the list if it's there
235            local -a list
236            list=()
237            [ -s "$most_frequent_db" ] && list=( ${(f)"$(<$most_frequent_db)"} )
238
239            # Will wait for the data?
240            local message=0
241            if [[ "${#list}" -eq 0 ]]; then
242                message=1
243                _nlist_alternate_screen 1
244                zcurses init
245                zcurses delwin info 2>/dev/null
246                zcurses addwin info "$term_height" "$term_width" 0 0
247                zcurses bg info white/black
248                zcurses string info "Computing most frequent history words..."$'\n'
249                zcurses string info "(This is done once per day, from now on transparently)"$'\n'
250                zcurses refresh info
251                sleep 3
252            fi
253
254            # Start periodic list regeneration?
255            if [[ "${#list}" -eq 0 || "${#dbfile}" -ne "0" ]]; then
256                # Mark the file with current time, to prevent double
257                # regeneration (on quick double change of view)
258                print >> "$most_frequent_db"
259                (_nhistory_generate_most_frequent &) &> /dev/null
260            fi
261
262            # Ensure we got the list, wait for it if needed
263            while [[ "${#list}" -eq 0 ]]; do
264                zcurses string info "."
265                zcurses refresh info
266                LANG=C sleep 0.5
267                [ -s "$most_frequent_db" ] && list=( ${(f)"$(<$most_frequent_db)"} )
268            done
269
270            NHISTORY_WORDS=( "${list[@]}" )
271
272            if [ "$message" -eq "1" ]; then
273                zcurses delwin info 2>/dev/null
274                zcurses refresh
275                zcurses end
276                _nlist_alternate_screen 0
277            fi
278        else
279            # Reuse most frequent history words
280            local -a list
281            list=( "${NHISTORY_WORDS[@]}" )
282        fi
283
284        n-list "${list[@]}"
285
286        if [ "$REPLY" = "F1" ]; then
287            # Target view: 1
288            active_view=0
289            _nhistory_switch_lists_states "0"
290        elif [[ "$REPLY" = -(#c0,1)[0-9]## && "$REPLY" -lt 0 ]]; then
291            break
292        elif [[ "$REPLY" = -(#c0,1)[0-9]## && "$REPLY" -gt 0 ]]; then
293            local word="${reply[REPLY]#(#s) #[0-9]##$'\t'}"
294            one_NLIST_SEARCH_BUFFER="$word"
295            one_NLIST_SEARCH_BUFFER="${one_NLIST_SEARCH_BUFFER## ##}"
296
297            # Target view: 1
298            active_view=0
299            _nhistory_switch_lists_states "0"
300        elif [ "$REPLY" = "HELP" ]; then
301            n-help
302        fi
303
304    #
305    # View 2 - private history
306    #
307    elif [ "$active_view" = "1" ]; then
308        if [ -s "$private_history_db" ]; then
309            local title=$'\x1b[00;32m'"Private history:"$'\x1b[00;00m\0'
310            () { fc -Rap "$private_history_db" 20000 0; list=( "$title" ${history[@]} ) }
311        else
312            list=( "Private history - history entries selected via this tool will be put here" )
313        fi
314
315        n-list "${list[@]}"
316
317        # Selection or quit?
318        if [[ "$REPLY" = -(#c0,1)[0-9]## && ("$REPLY" -lt 0 || "$REPLY" -gt 0) ]]; then
319            break
320        fi
321
322        # View change?
323        if [ "$REPLY" = "F1" ]; then
324            # Target view: 3
325            active_view=2
326            _nhistory_switch_lists_states "2"
327        # Edit of the history?
328        elif [ "$REPLY" = "EDIT" ]; then
329            "${EDITOR:-vim}" "$private_history_db"
330        elif [ "$REPLY" = "HELP" ]; then
331            n-help
332        fi
333    fi
334done
335
336if [ "$REPLY" -gt 0 ]; then
337    selected="$reply[REPLY]"
338
339    # Append to private history
340    if [[ "$active_view" = "0" ]]; then
341        local newline=$'\n'
342        local selected_ph="${selected//$newline/\\$newline}"
343        print -r -- "$selected_ph" >> "$private_history_db"
344    fi
345
346    # TMUX?
347    if [[ "$ZNT_TMUX_MODE" = "1" ]]; then
348        tmux send -t "$ZNT_TMUX_ORIGIN_SESSION:$ZNT_TMUX_ORIGIN_WINDOW.$ZNT_TMUX_ORIGIN_PANE" "$selected"
349        tmux kill-window
350        return 0
351    # ZLE?
352    elif [ "${(t)CURSOR}" = "integer-local-special" ]; then
353        zle .redisplay
354        zle .kill-buffer
355        LBUFFER+="$selected"
356    else
357        print -zr -- "$selected"
358    fi
359else
360    # TMUX?
361    if [[ "$ZNT_TMUX_MODE" = "1" ]]; then
362        tmux kill-window
363    # ZLE?
364    elif [[ "${(t)CURSOR}" = "integer-local-special" ]]; then
365        zle redisplay
366    fi
367fi
368
369return 0
370
371# vim: set filetype=zsh: