n-history 13 KB

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