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: