n-history 12.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
# Copy this file into /usr/share/zsh/site-functions/
# and add 'autoload n-history` to .zshrc
#
# This function allows to browse Z shell's history and use the
# entries
#
# Uses n-list

emulate -L zsh

setopt extendedglob
zmodload zsh/curses
13
zmodload zsh/parameter
14
15
16
17

local IFS="
"

Sebastian Gniazdowski's avatar
Sebastian Gniazdowski committed
18
19
20
21
22
23
24
25
26
27
28
29
30
# Variables to save list's state when switching views
# The views are: history and "most frequent history words"
local one_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
local one_NLIST_CURRENT_IDX
local one_NLIST_IS_SEARCH_MODE
local one_NLIST_SEARCH_BUFFER
local one_NLIST_TEXT_OFFSET
local one_NLIST_IS_UNIQ_MODE
local one_NLIST_IS_F_MODE
local one_NLIST_GREP_STRING
local one_NLIST_NONSELECTABLE_ELEMENTS
local one_NLIST_REMEMBER_STATE
local one_NLIST_ENABLED_EVENTS
31

Sebastian Gniazdowski's avatar
Sebastian Gniazdowski committed
32
33
34
35
36
37
38
39
40
41
42
local two_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
local two_NLIST_CURRENT_IDX
local two_NLIST_IS_SEARCH_MODE
local two_NLIST_SEARCH_BUFFER
local two_NLIST_TEXT_OFFSET
local two_NLIST_IS_UNIQ_MODE
local two_NLIST_IS_F_MODE
local two_NLIST_GREP_STRING
local two_NLIST_NONSELECTABLE_ELEMENTS
local two_NLIST_REMEMBER_STATE
local two_NLIST_ENABLED_EVENTS
43

Sebastian Gniazdowski's avatar
Sebastian Gniazdowski committed
44
45
46
47
48
49
50
51
52
53
54
local three_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
local three_NLIST_CURRENT_IDX
local three_NLIST_IS_SEARCH_MODE
local three_NLIST_SEARCH_BUFFER
local three_NLIST_TEXT_OFFSET
local three_NLIST_IS_UNIQ_MODE
local three_NLIST_IS_F_MODE
local three_NLIST_GREP_STRING
local three_NLIST_NONSELECTABLE_ELEMENTS
local three_NLIST_REMEMBER_STATE
local three_NLIST_ENABLED_EVENTS
55

Sebastian Gniazdowski's avatar
Sebastian Gniazdowski committed
56
57
# history view
integer active_view=0
58

Sebastian Gniazdowski's avatar
Sebastian Gniazdowski committed
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# Lists are "0", "1", "2" - 1st, 2nd, 3rd
# Switching is done in cyclic manner
# i.e. 0 -> 1, 1 -> 2, 2 -> 0
_nhistory_switch_lists_states() {
    # First argument is current, newly selected list, i.e. $active_view
    # This implies that we are switching from previous view
   
    if [ "$1" = "0" ]; then
        # Switched to 1st list, save 3rd list's state
        three_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN=$NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
        three_NLIST_CURRENT_IDX=$NLIST_CURRENT_IDX
        three_NLIST_IS_SEARCH_MODE=$NLIST_IS_SEARCH_MODE
        three_NLIST_SEARCH_BUFFER=$NLIST_SEARCH_BUFFER
        three_NLIST_TEXT_OFFSET=$NLIST_TEXT_OFFSET
        three_NLIST_IS_UNIQ_MODE=$NLIST_IS_UNIQ_MODE
        three_NLIST_IS_F_MODE=$NLIST_IS_F_MODE
        three_NLIST_GREP_STRING=$NLIST_GREP_STRING
        three_NLIST_NONSELECTABLE_ELEMENTS=( ${NLIST_NONSELECTABLE_ELEMENTS[@]} )
        three_NLIST_REMEMBER_STATE=$NLIST_REMEMBER_STATE
        three_NLIST_ENABLED_EVENTS=( ${NLIST_ENABLED_EVENTS[@]} )
79

Sebastian Gniazdowski's avatar
Sebastian Gniazdowski committed
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
159
        # ..and restore 1st list's state
        NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN=$one_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
        NLIST_CURRENT_IDX=$one_NLIST_CURRENT_IDX
        NLIST_IS_SEARCH_MODE=$one_NLIST_IS_SEARCH_MODE
        NLIST_SEARCH_BUFFER=$one_NLIST_SEARCH_BUFFER
        NLIST_TEXT_OFFSET=$one_NLIST_TEXT_OFFSET
        NLIST_IS_UNIQ_MODE=$one_NLIST_IS_UNIQ_MODE
        NLIST_IS_F_MODE=$one_NLIST_IS_F_MODE
        NLIST_GREP_STRING=$one_NLIST_GREP_STRING
        NLIST_NONSELECTABLE_ELEMENTS=( ${one_NLIST_NONSELECTABLE_ELEMENTS[@]} )
        NLIST_REMEMBER_STATE=$one_NLIST_REMEMBER_STATE
        NLIST_ENABLED_EVENTS=( ${one_NLIST_ENABLED_EVENTS[@]} )
    elif [ "$1" = "1" ]; then
        # Switched to 2nd list, save 1st list's state
        one_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN=$NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
        one_NLIST_CURRENT_IDX=$NLIST_CURRENT_IDX
        one_NLIST_IS_SEARCH_MODE=$NLIST_IS_SEARCH_MODE
        one_NLIST_SEARCH_BUFFER=$NLIST_SEARCH_BUFFER
        one_NLIST_TEXT_OFFSET=$NLIST_TEXT_OFFSET
        one_NLIST_IS_UNIQ_MODE=$NLIST_IS_UNIQ_MODE
        one_NLIST_IS_F_MODE=$NLIST_IS_F_MODE
        one_NLIST_GREP_STRING=$NLIST_GREP_STRING
        one_NLIST_NONSELECTABLE_ELEMENTS=( ${NLIST_NONSELECTABLE_ELEMENTS[@]} )
        one_NLIST_REMEMBER_STATE=$NLIST_REMEMBER_STATE
        one_NLIST_ENABLED_EVENTS=( ${NLIST_ENABLED_EVENTS[@]} )

        # ..and restore 2nd list's state
        NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN=$two_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
        NLIST_CURRENT_IDX=$two_NLIST_CURRENT_IDX
        NLIST_IS_SEARCH_MODE=$two_NLIST_IS_SEARCH_MODE
        NLIST_SEARCH_BUFFER=$two_NLIST_SEARCH_BUFFER
        NLIST_TEXT_OFFSET=$two_NLIST_TEXT_OFFSET
        NLIST_IS_UNIQ_MODE=$two_NLIST_IS_UNIQ_MODE
        NLIST_IS_F_MODE=$two_NLIST_IS_F_MODE
        NLIST_GREP_STRING=$two_NLIST_GREP_STRING
        NLIST_NONSELECTABLE_ELEMENTS=( ${two_NLIST_NONSELECTABLE_ELEMENTS[@]} )
        NLIST_REMEMBER_STATE=$two_NLIST_REMEMBER_STATE
        NLIST_ENABLED_EVENTS=( ${two_NLIST_ENABLED_EVENTS[@]} )
    elif [ "$1" = "2" ]; then
        # Switched to 3rd list, save 2nd list's state
        two_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN=$NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
        two_NLIST_CURRENT_IDX=$NLIST_CURRENT_IDX
        two_NLIST_IS_SEARCH_MODE=$NLIST_IS_SEARCH_MODE
        two_NLIST_SEARCH_BUFFER=$NLIST_SEARCH_BUFFER
        two_NLIST_TEXT_OFFSET=$NLIST_TEXT_OFFSET
        two_NLIST_IS_UNIQ_MODE=$NLIST_IS_UNIQ_MODE
        two_NLIST_IS_F_MODE=$NLIST_IS_F_MODE
        two_NLIST_GREP_STRING=$NLIST_GREP_STRING
        two_NLIST_NONSELECTABLE_ELEMENTS=( ${NLIST_NONSELECTABLE_ELEMENTS[@]} )
        two_NLIST_REMEMBER_STATE=$NLIST_REMEMBER_STATE
        two_NLIST_ENABLED_EVENTS=( ${NLIST_ENABLED_EVENTS[@]} )

        # ..and restore 3rd list's state
        NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN=$three_NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN
        NLIST_CURRENT_IDX=$three_NLIST_CURRENT_IDX
        NLIST_IS_SEARCH_MODE=$three_NLIST_IS_SEARCH_MODE
        NLIST_SEARCH_BUFFER=$three_NLIST_SEARCH_BUFFER
        NLIST_TEXT_OFFSET=$three_NLIST_TEXT_OFFSET
        NLIST_IS_UNIQ_MODE=$three_NLIST_IS_UNIQ_MODE
        NLIST_IS_F_MODE=$three_NLIST_IS_F_MODE
        NLIST_GREP_STRING=$three_NLIST_GREP_STRING
        NLIST_NONSELECTABLE_ELEMENTS=( ${three_NLIST_NONSELECTABLE_ELEMENTS[@]} )
        NLIST_REMEMBER_STATE=$three_NLIST_REMEMBER_STATE
        NLIST_ENABLED_EVENTS=( ${three_NLIST_ENABLED_EVENTS[@]} )
    fi
}

local most_frequent_db="$HOME/.config/znt/mostfrequent.db"
_nhistory_generate_most_frequent() {
    local title=$'\x1b[00;31m'"Most frequent history words:"$'\x1b[00;00m\0'

    typeset -A uniq
    for k in "${historywords[@]}"; do
        uniq[$k]=$(( ${uniq[$k]:-0} + 1 ))
    done
    vk=()
    for k v in ${(kv)uniq}; do
        vk+="$v"$'\t'"$k"
    done

psprint's avatar
psprint committed
160
    print -rl -- "$title" "${(On)vk[@]}" > "$most_frequent_db"
Sebastian Gniazdowski's avatar
Sebastian Gniazdowski committed
161
162
163
164
165
166
167
168
169
170
}

# Load configuration
unset NLIST_COLORING_PATTERN
[ -f ~/.config/znt/n-list.conf ] && builtin source ~/.config/znt/n-list.conf
[ -f ~/.config/znt/n-history.conf ] && builtin source ~/.config/znt/n-history.conf

local list
local selected
local private_history_db="$HOME/.config/znt/privhist.db"
171

172
local NLIST_GREP_STRING="$1"
Sebastian Gniazdowski's avatar
Sebastian Gniazdowski committed
173
174
175
176
177
178
179
180
181
182
183
184
# 2 is: init once, then remember
local NLIST_REMEMBER_STATE=2
two_NLIST_REMEMBER_STATE=2
three_NLIST_REMEMBER_STATE=2

# Only Private history has EDIT
local -a NLIST_ENABLED_EVENTS
NLIST_ENABLED_EVENTS=( "F1" "HELP" )
two_NLIST_ENABLED_EVENTS=( "F1" "EDIT" "HELP" )
three_NLIST_ENABLED_EVENTS=( "F1" "HELP" )

# All view should attempt to replace new lines with \n
185
local NLIST_REPLACE_NEWLINES="1"
Sebastian Gniazdowski's avatar
Sebastian Gniazdowski committed
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
two_NLIST_REPLACE_NEWLINES="1"
three_NLIST_REPLACE_NEWLINES="1"

# Only second and third view has non-selectable first entry
local -a NLIST_NONSELECTABLE_ELEMENTS
NLIST_NONSELECTABLE_ELEMENTS=( )
two_NLIST_NONSELECTABLE_ELEMENTS=( 1 )
three_NLIST_NONSELECTABLE_ELEMENTS=( 1 )

while (( 1 )); do

    #
    # View 1 - history
    #
    if [ "$active_view" = "0" ]; then
        list=( "$history[@]" )
        list=( "${(@M)list:#(#i)*$NLIST_GREP_STRING*}" )

        if [ "$#list" -eq 0 ]; then
            echo "No matching history entries"
            return 1
        fi

        n-list "${list[@]}"

        # Selection or quit?
        if [[ "$REPLY" = -(#c0,1)[0-9]## && ("$REPLY" -lt 0 || "$REPLY" -gt 0) ]]; then
            break
        fi

        # View change?
        if [ "$REPLY" = "F1" ]; then
            # Target view: 2
            active_view=1
            _nhistory_switch_lists_states "1"
        elif [ "$REPLY" = "HELP" ]; then
            n-help
        fi

    #
    # View 3 - most frequent words in history
    #
    elif [ "$active_view" = "2" ]; then
        local -a dbfile
        dbfile=( $most_frequent_db(Nm+1) )

        # Compute most frequent history words
        if [[ "${#NHISTORY_WORDS}" -eq "0" || "${#dbfile}" -ne "0" ]]; then
            # Read the list if it's there
            local -a list
            list=()
            [ -s "$most_frequent_db" ] && list=( ${(f)"$(<$most_frequent_db)"} )

            # Will wait for the data?
            local message=0
            if [[ "${#list}" -eq 0 ]]; then
                message=1
                _nlist_alternate_screen 1
                zcurses init
                zcurses delwin info 2>/dev/null
                zcurses addwin info "$term_height" "$term_width" 0 0
                zcurses bg info white/black
                zcurses string info "Computing most frequent history words..."$'\n'
                zcurses string info "(This is done once per day, from now on transparently)"$'\n'
                zcurses refresh info
                sleep 3
            fi

            # Start periodic list regeneration?
            if [[ "${#list}" -eq 0 || "${#dbfile}" -ne "0" ]]; then
                # Mark the file with current time, to prevent double
                # regeneration (on quick double change of view)
                print >> "$most_frequent_db"
                (_nhistory_generate_most_frequent &) &> /dev/null
            fi

            # Ensure we got the list, wait for it if needed
            while [[ "${#list}" -eq 0 ]]; do
                zcurses string info "."
                zcurses refresh info
                LANG=C sleep 0.5
                [ -s "$most_frequent_db" ] && list=( ${(f)"$(<$most_frequent_db)"} )
            done

            NHISTORY_WORDS=( "${list[@]}" )

            if [ "$message" -eq "1" ]; then
                zcurses delwin info 2>/dev/null
                zcurses refresh
                zcurses end
                _nlist_alternate_screen 0
            fi
        else
            # Reuse most frequent history words
            local -a list
            list=( "${NHISTORY_WORDS[@]}" )
        fi

        n-list "${list[@]}"

        if [ "$REPLY" = "F1" ]; then
            # Target view: 1
            active_view=0
            _nhistory_switch_lists_states "0"
        elif [[ "$REPLY" = -(#c0,1)[0-9]## && "$REPLY" -lt 0 ]]; then
            break
        elif [[ "$REPLY" = -(#c0,1)[0-9]## && "$REPLY" -gt 0 ]]; then
            local word="${reply[REPLY]#(#s) #[0-9]##$'\t'}"
            one_NLIST_SEARCH_BUFFER="$word"
            one_NLIST_SEARCH_BUFFER="${one_NLIST_SEARCH_BUFFER## ##}"

            # Target view: 1
            active_view=0
            _nhistory_switch_lists_states "0"
        elif [ "$REPLY" = "HELP" ]; then
            n-help
        fi

    #
    # View 2 - private history
    #
    elif [ "$active_view" = "1" ]; then
        if [ -s "$private_history_db" ]; then
            local title=$'\x1b[00;32m'"Private history:"$'\x1b[00;00m\0'
            () { fc -ap -R "$private_history_db"; list=( "$title" ${history[@]} ) }
        else
            list=( "Private history - history entries selected via this tool will be put here" )
        fi

        n-list "${list[@]}"

        # Selection or quit?
        if [[ "$REPLY" = -(#c0,1)[0-9]## && ("$REPLY" -lt 0 || "$REPLY" -gt 0) ]]; then
            break
        fi

        # View change?
        if [ "$REPLY" = "F1" ]; then
            # Target view: 3
            active_view=2
            _nhistory_switch_lists_states "2"
        # Edit of the history?
        elif [ "$REPLY" = "EDIT" ]; then
            "${EDITOR:-vim}" "$private_history_db"
        elif [ "$REPLY" = "HELP" ]; then
            n-help
        fi
    fi
done
335
336
337
338
339

if [ "$REPLY" -gt 0 ]; then
    selected="$reply[REPLY]"
    # ZLE?
    if [ "${(t)CURSOR}" = "integer-local-special" ]; then
psprint's avatar
psprint committed
340
341
        zle .redisplay
        zle .kill-buffer
Sebastian Gniazdowski's avatar
Sebastian Gniazdowski committed
342
343
344
345
346
        LBUFFER+="$selected"

        # Append to private history
        local newline=$'\n'
        selected="${selected//$newline/\\$newline}"
psprint's avatar
psprint committed
347
        [ "$active_view" = "0" ] && print -r -- "$selected" >> "$private_history_db"
348
    else
psprint's avatar
psprint committed
349
        print -zr -- "$selected"
350
351
352
353
354
355
    fi
else
    [ "${(t)CURSOR}" = "integer-local-special" ] && zle redisplay
fi

# vim: set filetype=zsh: