├── CHANGELOG.md ├── README.md ├── directory-history.plugin.zsh └── dirhist /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 1.1.0 (2016-04-07) 4 | 5 | **Implemented enhancements:** 6 | 7 | - Faster processing of large history files 8 | - Commands in the history file are no longer separated by newlines. Instead the plugin uses `\0` to separate commands 9 | - History file formats from previous versions are no longer supported 10 | 11 | **Closed issues:** 12 | 13 | - Plugin crash when command contains newline [\#12](https://github.com/tymm/zsh-directory-history/issues/12) 14 | - Commands containing `;` do not show up entirely [\#13](https://github.com/tymm/zsh-directory-history/issues/13) 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | zsh-directory-history 2 | ================= 3 | 4 | A per directory history for zsh. 5 | 6 | DESCRIPTION 7 | ----------- 8 | 9 | zsh-directory-history is a zsh plugin giving you a history which is sensitive to the directory you are in. 10 | It implements forward/backward navigation as well as substring search in a directory sensitive manner. 11 | Since zsh-directory-history includes [zsh-history-substring-search](https://github.com/zsh-users/zsh-history-substring-search), do not load zsh-history-substring-search when loading this plugin. 12 | 13 | Behavior: 14 | * Commands executed in the current directory will pop up first when navigating the history or using substring search. 15 | * A substring unknown in the current directory, will be searched for globally (it falls back to the normal substring search behavior). 16 | 17 | Since the plugin creates its own history (in `~/.directory_history`), it needs some time to fill up and become useful. 18 | 19 | INSTALL 20 | ------- 21 | 22 | 1. Install script 23 | 24 | git clone https://github.com/tymm/zsh-directory-history 25 | sudo cp zsh-directory-history/dirhist /usr/bin/ 26 | 27 | 2. Activate plugin by appending the following line to your _.zshrc_ file 28 | 29 | source /path/to/directory-history.plugin.zsh 30 | 31 | 3. Bind keyboard shortcuts in your _.zshrc_ 32 | `directory-history-search-forward`/`directory-history-search-backward` needs to be bind for forward/backward navigation. 33 | `history-substring-search-up`/`history-substring-search-down` needs to be bind for substring search. 34 | For example: 35 | 36 | # Bind up/down arrow keys to navigate through your history 37 | bindkey '\e[A' directory-history-search-backward 38 | bindkey '\e[B' directory-history-search-forward 39 | 40 | # Bind CTRL+k and CTRL+j to substring search 41 | bindkey '^j' history-substring-search-up 42 | bindkey '^k' history-substring-search-down 43 | It is possible that \e[A and \e[B will not work for you. 44 | Look [here](https://github.com/zsh-users/zsh-history-substring-search) under 2. for more information. 45 | 46 | 47 | For more information on how to configure substring search, take a look here: https://github.com/zsh-users/zsh-history-substring-search 48 | -------------------------------------------------------------------------------- /directory-history.plugin.zsh: -------------------------------------------------------------------------------- 1 | zmodload zsh/datetime 2 | 3 | # Generates a new history for the current directory 4 | function generate_history() { 5 | history_dir=("${(@ps.\0\n.)$(dirhist -a -d $PWD)}") 6 | MAX_INDEX_HISTORY=$#history_dir 7 | (( INDEX_HISTORY = $#history_dir + 1 )) 8 | 9 | # After creating a new history_dir we possibly have to adjust $_history_substring_search_matches 10 | refresh_substring_search_matches 11 | } 12 | 13 | # Append to history file 14 | function log_command() { 15 | echo -n ": ${EPOCHSECONDS}:0;${PWD};${1}\0\n" >> ~/.directory_history 16 | } 17 | 18 | # Refresh substring search 19 | function refresh_substring_search_matches() { 20 | _history_substring_search_matches=("${(@f)$(dirhist -s "${_history_substring_search_query_escaped}" -d $PWD)}") 21 | } 22 | 23 | # Reset $_history_substring_search_result after every command 24 | function reset_substring_search() { 25 | _history_substring_search_result= 26 | } 27 | 28 | # Call generate_history() everytime the directory is changed 29 | chpwd_functions=(${chpwd_functions[@]} "generate_history") 30 | 31 | # Call generate_history() everytime the user opens a prompt 32 | precmd_functions=(${precmd_functions[@]} "generate_history") 33 | 34 | # Call log_command() everytime a command is executed 35 | preexec_functions=(${preexec_functions[@]} "log_command") 36 | 37 | # Call generate_history() everytime a command is executed 38 | preexec_functions=(${preexec_functions[@]} "generate_history") 39 | 40 | # Call reset() everytime a command is executed 41 | preexec_functions=(${preexec_functions[@]} "reset_substring_search") 42 | 43 | # generate_history() gets executed after the following so we have to generate it here to get access to $history_dir 44 | history_dir=("${(@ps.\0\n.)$(dirhist -a -d $PWD)}") 45 | 46 | directory-history-search-forward() { 47 | # Go forward as long as possible; Last command is at $history_dir[1] 48 | if [[ $INDEX_HISTORY -ne 1 ]] && (( INDEX_HISTORY-- )) 49 | 50 | # Get command and put it into the buffer 51 | COMMAND=$history_dir[$INDEX_HISTORY] 52 | zle kill-whole-line 53 | BUFFER=$COMMAND 54 | zle end-of-line 55 | } 56 | 57 | directory-history-search-backward() { 58 | # Go back as long as possible; First command is at $history_dir[$MAX_INDEX_HISTORY] 59 | if [[ $INDEX_HISTORY -ne (( $MAX_INDEX_HISTORY + 1 )) ]] && (( INDEX_HISTORY++ )) 60 | 61 | # If index is greater than the maximal index 62 | if [[ $INDEX_HISTORY -gt $MAX_INDEX_HISTORY ]]; then 63 | # Go back to blank 64 | BUFFER="" 65 | else 66 | # Get command and put it into the buffer 67 | COMMAND=$history_dir[$INDEX_HISTORY] 68 | zle kill-whole-line 69 | BUFFER=$COMMAND 70 | zle end-of-line 71 | fi 72 | } 73 | 74 | zle -N directory-history-search-backward 75 | zle -N directory-history-search-forward 76 | 77 | if test "$CASE_SENSITIVE" = true; then 78 | unset HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS 79 | fi 80 | 81 | if test "$DISABLE_COLOR" = true; then 82 | unset HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND 83 | unset HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND 84 | fi 85 | 86 | ############################################################################## 87 | # 88 | # Copyright (c) 2009 Peter Stephenson 89 | # Copyright (c) 2011 Guido van Steen 90 | # Copyright (c) 2011 Suraj N. Kurapati 91 | # Copyright (c) 2011 Sorin Ionescu 92 | # Copyright (c) 2011 Vincent Guerci 93 | # All rights reserved. 94 | # 95 | # Redistribution and use in source and binary forms, with or without 96 | # modification, are permitted provided that the following conditions are met: 97 | # 98 | # * Redistributions of source code must retain the above copyright 99 | # notice, this list of conditions and the following disclaimer. 100 | # 101 | # * Redistributions in binary form must reproduce the above 102 | # copyright notice, this list of conditions and the following 103 | # disclaimer in the documentation and/or other materials provided 104 | # with the distribution. 105 | # 106 | # * Neither the name of the FIZSH nor the names of its contributors 107 | # may be used to endorse or promote products derived from this 108 | # software without specific prior written permission. 109 | # 110 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 111 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 112 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 113 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 114 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 115 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 116 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 117 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 118 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 119 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 120 | # POSSIBILITY OF SUCH DAMAGE. 121 | # 122 | ############################################################################## 123 | 124 | #----------------------------------------------------------------------------- 125 | # configuration variables 126 | #----------------------------------------------------------------------------- 127 | 128 | HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND='bg=magenta,fg=white,bold' 129 | HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND='bg=red,fg=white,bold' 130 | HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS='i' 131 | 132 | #----------------------------------------------------------------------------- 133 | # the main ZLE widgets 134 | #----------------------------------------------------------------------------- 135 | 136 | function history-substring-search-up() { 137 | _history-substring-search-begin 138 | 139 | _history-substring-search-up-history || 140 | _history-substring-search-up-buffer || 141 | _history-substring-search-up-search 142 | 143 | _history-substring-search-end 144 | } 145 | 146 | function history-substring-search-down() { 147 | _history-substring-search-begin 148 | 149 | _history-substring-search-down-history || 150 | _history-substring-search-down-buffer || 151 | _history-substring-search-down-search 152 | 153 | _history-substring-search-end 154 | } 155 | 156 | zle -N history-substring-search-up 157 | zle -N history-substring-search-down 158 | 159 | #----------------------------------------------------------------------------- 160 | # implementation details 161 | #----------------------------------------------------------------------------- 162 | 163 | zmodload -F zsh/parameter 164 | 165 | # 166 | # We have to "override" some keys and widgets if the 167 | # zsh-syntax-highlighting plugin has not been loaded: 168 | # 169 | # https://github.com/nicoulaj/zsh-syntax-highlighting 170 | # 171 | if [[ $+functions[_zsh_highlight] -eq 0 ]]; then 172 | # 173 | # Dummy implementation of _zsh_highlight() that 174 | # simply removes any existing highlights when the 175 | # user inserts printable characters into $BUFFER. 176 | # 177 | function _zsh_highlight() { 178 | if [[ $KEYS == [[:print:]] ]]; then 179 | region_highlight=() 180 | fi 181 | } 182 | 183 | # 184 | # The following snippet was taken from the zsh-syntax-highlighting project: 185 | # 186 | # https://github.com/zsh-users/zsh-syntax-highlighting/blob/56b134f5d62ae3d4e66c7f52bd0cc2595f9b305b/zsh-syntax-highlighting.zsh#L126-161 187 | # 188 | # Copyright (c) 2010-2011 zsh-syntax-highlighting contributors 189 | # All rights reserved. 190 | # 191 | # Redistribution and use in source and binary forms, with or without 192 | # modification, are permitted provided that the following conditions are 193 | # met: 194 | # 195 | # * Redistributions of source code must retain the above copyright 196 | # notice, this list of conditions and the following disclaimer. 197 | # 198 | # * Redistributions in binary form must reproduce the above copyright 199 | # notice, this list of conditions and the following disclaimer in the 200 | # documentation and/or other materials provided with the distribution. 201 | # 202 | # * Neither the name of the zsh-syntax-highlighting contributors nor the 203 | # names of its contributors may be used to endorse or promote products 204 | # derived from this software without specific prior written permission. 205 | # 206 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 207 | # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 208 | # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 209 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 210 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 211 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 212 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 213 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 214 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 215 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 216 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 217 | # 218 | #--------------8<-------------------8<-------------------8<----------------- 219 | # Rebind all ZLE widgets to make them invoke _zsh_highlights. 220 | _zsh_highlight_bind_widgets() 221 | { 222 | # Load ZSH module zsh/zleparameter, needed to override user defined widgets. 223 | zmodload zsh/zleparameter 2>/dev/null || { 224 | echo 'zsh-syntax-highlighting: failed loading zsh/zleparameter.' >&2 225 | return 1 226 | } 227 | 228 | # Override ZLE widgets to make them invoke _zsh_highlight. 229 | local cur_widget 230 | for cur_widget in ${${(f)"$(builtin zle -la)"}:#(.*|_*|orig-*|run-help|which-command|beep)}; do 231 | case $widgets[$cur_widget] in 232 | 233 | # Already rebound event: do nothing. 234 | user:$cur_widget|user:_zsh_highlight_widget_*);; 235 | 236 | # User defined widget: override and rebind old one with prefix "orig-". 237 | user:*) eval "zle -N orig-$cur_widget ${widgets[$cur_widget]#*:}; \ 238 | _zsh_highlight_widget_$cur_widget() { builtin zle orig-$cur_widget -- \"\$@\" && _zsh_highlight }; \ 239 | zle -N $cur_widget _zsh_highlight_widget_$cur_widget";; 240 | 241 | # Completion widget: override and rebind old one with prefix "orig-". 242 | completion:*) eval "zle -C orig-$cur_widget ${${widgets[$cur_widget]#*:}/:/ }; \ 243 | _zsh_highlight_widget_$cur_widget() { builtin zle orig-$cur_widget -- \"\$@\" && _zsh_highlight }; \ 244 | zle -N $cur_widget _zsh_highlight_widget_$cur_widget";; 245 | 246 | # Builtin widget: override and make it call the builtin ".widget". 247 | builtin) eval "_zsh_highlight_widget_$cur_widget() { builtin zle .$cur_widget -- \"\$@\" && _zsh_highlight }; \ 248 | zle -N $cur_widget _zsh_highlight_widget_$cur_widget";; 249 | 250 | # Default: unhandled case. 251 | *) echo "zsh-syntax-highlighting: unhandled ZLE widget '$cur_widget'" >&2 ;; 252 | esac 253 | done 254 | } 255 | #-------------->8------------------->8------------------->8----------------- 256 | 257 | _zsh_highlight_bind_widgets 258 | fi 259 | 260 | function _history-substring-search-begin() { 261 | setopt localoptions extendedglob 262 | 263 | _history_substring_search_refresh_display= 264 | _history_substring_search_query_highlight= 265 | 266 | # 267 | # Continue using the previous $_history_substring_search_result by default, 268 | # unless the current query was cleared or a new/different query was entered. 269 | # 270 | if [[ -z $BUFFER || $BUFFER != $_history_substring_search_result ]]; then 271 | # 272 | # For the purpose of highlighting we will also keep 273 | # a version without doubly-escaped meta characters. 274 | # 275 | _history_substring_search_query=$BUFFER 276 | 277 | # 278 | # $BUFFER contains the text that is in the command-line currently. 279 | # we put an extra "\\" before meta characters such as "\(" and "\)", 280 | # so that they become "\\\(" and "\\\)". 281 | # 282 | _history_substring_search_query_escaped=${BUFFER//(#m)[\][()|\\*?#<>~^]/\\$MATCH} 283 | 284 | # 285 | # Find all occurrences of the search query in the history file. 286 | # 287 | # (k) turns it an array of line numbers. 288 | # 289 | # (on) seems to remove duplicates, which are default 290 | # options. They can be turned off by (ON). 291 | # 292 | #_history_substring_search_matches=(${(kon)history[(R)(#$HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS)*${_history_substring_search_query_escaped}*]}) 293 | _history_substring_search_matches=("${(@f)$(dirhist -s "${_history_substring_search_query_escaped}" -d $PWD)}") 294 | if [[ $#_history_substring_search_matches -eq 0 || $_history_substring_search_matches[1] == "NONE" ]]; then 295 | _history_substring_search_matches=() 296 | fi 297 | 298 | # 299 | # Define the range of values that $_history_substring_search_match_index 300 | # can take: [0, $_history_substring_search_matches_count_plus]. 301 | # 302 | _history_substring_search_matches_count=$#_history_substring_search_matches 303 | _history_substring_search_matches_count_plus=$(( _history_substring_search_matches_count + 1 )) 304 | _history_substring_search_matches_count_sans=$(( _history_substring_search_matches_count - 1 )) 305 | 306 | # 307 | # If $_history_substring_search_match_index is equal to 308 | # $_history_substring_search_matches_count_plus, this indicates that we 309 | # are beyond the beginning of $_history_substring_search_matches. 310 | # 311 | # If $_history_substring_search_match_index is equal to 0, this indicates 312 | # that we are beyond the end of $_history_substring_search_matches. 313 | # 314 | # If we have initially pressed "up" we have to initialize 315 | # $_history_substring_search_match_index to 316 | # $_history_substring_search_matches_count_plus so that it will be 317 | # decreased to $_history_substring_search_matches_count. 318 | # 319 | # If we have initially pressed "down" we have to initialize 320 | # $_history_substring_search_match_index to 321 | # $_history_substring_search_matches_count so that it will be increased to 322 | # $_history_substring_search_matches_count_plus. 323 | # 324 | if [[ $WIDGET == history-substring-search-down ]]; then 325 | _history_substring_search_match_index=$_history_substring_search_matches_count 326 | else 327 | _history_substring_search_match_index=$_history_substring_search_matches_count_plus 328 | fi 329 | fi 330 | } 331 | 332 | function _history-substring-search-end() { 333 | setopt localoptions extendedglob 334 | 335 | _history_substring_search_result=$BUFFER 336 | 337 | # the search was succesful so display the result properly by clearing away 338 | # existing highlights and moving the cursor to the end of the result buffer 339 | if [[ $_history_substring_search_refresh_display -eq 1 ]]; then 340 | region_highlight=() 341 | CURSOR=${#BUFFER} 342 | fi 343 | 344 | # highlight command line using zsh-syntax-highlighting 345 | _zsh_highlight 346 | 347 | # highlight the search query inside the command line 348 | if [[ -n $_history_substring_search_query_highlight && -n $_history_substring_search_query ]]; then 349 | # 350 | # The following expression yields a variable $MBEGIN, which 351 | # indicates the begin position + 1 of the first occurrence 352 | # of _history_substring_search_query_escaped in $BUFFER. 353 | # 354 | : ${(S)BUFFER##(#m$HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS)($_history_substring_search_query##)} 355 | local begin=$(( MBEGIN - 1 )) 356 | local end=$(( begin + $#_history_substring_search_query )) 357 | region_highlight+=("$begin $end $_history_substring_search_query_highlight") 358 | fi 359 | 360 | # For debugging purposes: 361 | # zle -R "mn: "$_history_substring_search_match_index" m#: "${#_history_substring_search_matches} 362 | # read -k -t 200 && zle -U $REPLY 363 | 364 | # Exit successfully from the history-substring-search-* widgets. 365 | return 0 366 | } 367 | 368 | function _history-substring-search-up-buffer() { 369 | # 370 | # Check if the UP arrow was pressed to move the cursor within a multi-line 371 | # buffer. This amounts to three tests: 372 | # 373 | # 1. $#buflines -gt 1. 374 | # 375 | # 2. $CURSOR -ne $#BUFFER. 376 | # 377 | # 3. Check if we are on the first line of the current multi-line buffer. 378 | # If so, pressing UP would amount to leaving the multi-line buffer. 379 | # 380 | # We check this by adding an extra "x" to $LBUFFER, which makes 381 | # sure that xlbuflines is always equal to the number of lines 382 | # until $CURSOR (including the line with the cursor on it). 383 | # 384 | local buflines XLBUFFER xlbuflines 385 | buflines=(${(f)BUFFER}) 386 | XLBUFFER=$LBUFFER"x" 387 | xlbuflines=(${(f)XLBUFFER}) 388 | 389 | if [[ $#buflines -gt 1 && $CURSOR -ne $#BUFFER && $#xlbuflines -ne 1 ]]; then 390 | zle up-line-or-history 391 | return 0 392 | fi 393 | 394 | return 1 395 | } 396 | 397 | function _history-substring-search-down-buffer() { 398 | # 399 | # Check if the DOWN arrow was pressed to move the cursor within a multi-line 400 | # buffer. This amounts to three tests: 401 | # 402 | # 1. $#buflines -gt 1. 403 | # 404 | # 2. $CURSOR -ne $#BUFFER. 405 | # 406 | # 3. Check if we are on the last line of the current multi-line buffer. 407 | # If so, pressing DOWN would amount to leaving the multi-line buffer. 408 | # 409 | # We check this by adding an extra "x" to $RBUFFER, which makes 410 | # sure that xrbuflines is always equal to the number of lines 411 | # from $CURSOR (including the line with the cursor on it). 412 | # 413 | local buflines XRBUFFER xrbuflines 414 | buflines=(${(f)BUFFER}) 415 | XRBUFFER="x"$RBUFFER 416 | xrbuflines=(${(f)XRBUFFER}) 417 | 418 | if [[ $#buflines -gt 1 && $CURSOR -ne $#BUFFER && $#xrbuflines -ne 1 ]]; then 419 | zle down-line-or-history 420 | return 0 421 | fi 422 | 423 | return 1 424 | } 425 | 426 | function _history-substring-search-up-history() { 427 | # 428 | # Behave like up in ZSH, except clear the $BUFFER 429 | # when beginning of history is reached like in Fish. 430 | # 431 | if [[ -z $_history_substring_search_query ]]; then 432 | 433 | # we have reached the absolute top of history 434 | if [[ $HISTNO -eq 1 ]]; then 435 | BUFFER= 436 | 437 | # going up from somewhere below the top of history 438 | else 439 | zle up-line-or-history 440 | fi 441 | 442 | return 0 443 | fi 444 | 445 | return 1 446 | } 447 | 448 | function _history-substring-search-down-history() { 449 | # 450 | # Behave like down-history in ZSH, except clear the 451 | # $BUFFER when end of history is reached like in Fish. 452 | # 453 | if [[ -z $_history_substring_search_query ]]; then 454 | 455 | # going down from the absolute top of history 456 | if [[ $HISTNO -eq 1 && -z $BUFFER ]]; then 457 | BUFFER=${history[1]} 458 | _history_substring_search_refresh_display=1 459 | 460 | # going down from somewhere above the bottom of history 461 | else 462 | zle down-line-or-history 463 | fi 464 | 465 | return 0 466 | fi 467 | 468 | return 1 469 | } 470 | 471 | function _history-substring-search-not-found() { 472 | # 473 | # Nothing matched the search query, so put it back into the $BUFFER while 474 | # highlighting it accordingly so the user can revise it and search again. 475 | # 476 | _history_substring_search_old_buffer=$BUFFER 477 | BUFFER=$_history_substring_search_query 478 | _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND 479 | } 480 | 481 | function _history-substring-search-up-search() { 482 | _history_substring_search_refresh_display=1 483 | 484 | # 485 | # Highlight matches during history-substring-up-search: 486 | # 487 | # The following constants have been initialized in 488 | # _history-substring-search-up/down-search(): 489 | # 490 | # $_history_substring_search_matches is the current list of matches 491 | # $_history_substring_search_matches_count is the current number of matches 492 | # $_history_substring_search_matches_count_plus is the current number of matches + 1 493 | # $_history_substring_search_matches_count_sans is the current number of matches - 1 494 | # $_history_substring_search_match_index is the index of the current match 495 | # 496 | # The range of values that $_history_substring_search_match_index can take 497 | # is: [0, $_history_substring_search_matches_count_plus]. A value of 0 498 | # indicates that we are beyond the end of 499 | # $_history_substring_search_matches. A value of 500 | # $_history_substring_search_matches_count_plus indicates that we are beyond 501 | # the beginning of $_history_substring_search_matches. 502 | # 503 | # In _history-substring-search-up-search() the initial value of 504 | # $_history_substring_search_match_index is 505 | # $_history_substring_search_matches_count_plus. This value is set in 506 | # _history-substring-search-begin(). _history-substring-search-up-search() 507 | # will initially decrease it to $_history_substring_search_matches_count. 508 | # 509 | if [[ $_history_substring_search_match_index -ge 2 ]]; then 510 | # 511 | # Highlight the next match: 512 | # 513 | # 1. Decrease the value of $_history_substring_search_match_index. 514 | # 515 | # 2. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND 516 | # to highlight the current buffer. 517 | # 518 | (( _history_substring_search_match_index-- )) 519 | #BUFFER=$history[$_history_substring_search_matches[$_history_substring_search_match_index]] 520 | BUFFER=$history_dir[$_history_substring_search_matches[$_history_substring_search_match_index]] 521 | _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND 522 | 523 | elif [[ $_history_substring_search_match_index -eq 1 ]]; then 524 | # 525 | # We will move beyond the end of $_history_substring_search_matches: 526 | # 527 | # 1. Decrease the value of $_history_substring_search_match_index. 528 | # 529 | # 2. Save the current buffer in $_history_substring_search_old_buffer, 530 | # so that it can be retrieved by 531 | # _history-substring-search-down-search() later. 532 | # 533 | # 3. Make $BUFFER equal to $_history_substring_search_query. 534 | # 535 | # 4. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND 536 | # to highlight the current buffer. 537 | # 538 | (( _history_substring_search_match_index-- )) 539 | _history-substring-search-not-found 540 | 541 | elif [[ $_history_substring_search_match_index -eq $_history_substring_search_matches_count_plus ]]; then 542 | # 543 | # We were beyond the beginning of $_history_substring_search_matches but 544 | # UP makes us move back to $_history_substring_search_matches: 545 | # 546 | # 1. Decrease the value of $_history_substring_search_match_index. 547 | # 548 | # 2. Restore $BUFFER from $_history_substring_search_old_buffer. 549 | # 550 | # 3. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND 551 | # to highlight the current buffer. 552 | # 553 | (( _history_substring_search_match_index-- )) 554 | BUFFER=$_history_substring_search_old_buffer 555 | _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND 556 | 557 | else 558 | # 559 | # We are at the beginning of history and there are no further matches. 560 | # 561 | _history-substring-search-not-found 562 | fi 563 | } 564 | 565 | function _history-substring-search-down-search() { 566 | _history_substring_search_refresh_display=1 567 | 568 | # 569 | # Highlight matches during history-substring-up-search: 570 | # 571 | # The following constants have been initialized in 572 | # _history-substring-search-up/down-search(): 573 | # 574 | # $_history_substring_search_matches is the current list of matches 575 | # $_history_substring_search_matches_count is the current number of matches 576 | # $_history_substring_search_matches_count_plus is the current number of matches + 1 577 | # $_history_substring_search_matches_count_sans is the current number of matches - 1 578 | # $_history_substring_search_match_index is the index of the current match 579 | # 580 | # The range of values that $_history_substring_search_match_index can take 581 | # is: [0, $_history_substring_search_matches_count_plus]. A value of 0 582 | # indicates that we are beyond the end of 583 | # $_history_substring_search_matches. A value of 584 | # $_history_substring_search_matches_count_plus indicates that we are beyond 585 | # the beginning of $_history_substring_search_matches. 586 | # 587 | # In _history-substring-search-down-search() the initial value of 588 | # $_history_substring_search_match_index is 589 | # $_history_substring_search_matches_count. This value is set in 590 | # _history-substring-search-begin(). 591 | # _history-substring-search-down-search() will initially increase it to 592 | # $_history_substring_search_matches_count_plus. 593 | # 594 | if [[ $_history_substring_search_match_index -le $_history_substring_search_matches_count_sans ]]; then 595 | # 596 | # Highlight the next match: 597 | # 598 | # 1. Increase $_history_substring_search_match_index by 1. 599 | # 600 | # 2. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND 601 | # to highlight the current buffer. 602 | # 603 | (( _history_substring_search_match_index++ )) 604 | #BUFFER=$history[$_history_substring_search_matches[$_history_substring_search_match_index]] 605 | BUFFER=$history_dir[$_history_substring_search_matches[$_history_substring_search_match_index]] 606 | _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND 607 | 608 | elif [[ $_history_substring_search_match_index -eq $_history_substring_search_matches_count ]]; then 609 | # 610 | # We will move beyond the beginning of $_history_substring_search_matches: 611 | # 612 | # 1. Increase $_history_substring_search_match_index by 1. 613 | # 614 | # 2. Save the current buffer in $_history_substring_search_old_buffer, so 615 | # that it can be retrieved by _history-substring-search-up-search() 616 | # later. 617 | # 618 | # 3. Make $BUFFER equal to $_history_substring_search_query. 619 | # 620 | # 4. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND 621 | # to highlight the current buffer. 622 | # 623 | (( _history_substring_search_match_index++ )) 624 | _history-substring-search-not-found 625 | 626 | elif [[ $_history_substring_search_match_index -eq 0 ]]; then 627 | # 628 | # We were beyond the end of $_history_substring_search_matches but DOWN 629 | # makes us move back to the $_history_substring_search_matches: 630 | # 631 | # 1. Increase $_history_substring_search_match_index by 1. 632 | # 633 | # 2. Restore $BUFFER from $_history_substring_search_old_buffer. 634 | # 635 | # 3. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND 636 | # to highlight the current buffer. 637 | # 638 | (( _history_substring_search_match_index++ )) 639 | BUFFER=$_history_substring_search_old_buffer 640 | _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND 641 | 642 | else 643 | # 644 | # We are at the end of history and there are no further matches. 645 | # 646 | _history-substring-search-not-found 647 | fi 648 | } 649 | 650 | 651 | -------------------------------------------------------------------------------- /dirhist: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | import sys 3 | import os 4 | import re 5 | from collections import OrderedDict 6 | from os.path import expanduser 7 | 8 | # Size of the history 9 | HISTSIZE = 10000 10 | 11 | # History file 12 | HISTFILE = ".directory_history" 13 | 14 | # Home directory of user 15 | home = expanduser("~") 16 | 17 | def get_dir_and_command(line): 18 | parts = line.split(";") 19 | return (parts[1], ";".join(parts[2:])) 20 | 21 | def remove_duplicates_front(commands): 22 | """Remove duplicate entries from the front in commands. 23 | 24 | Remove from the front since entries in the front are older 25 | than the ones at the end of commands. 26 | """ 27 | # [ls,vim,su,cat,ls] -> [vim,su,cat,ls] 28 | commands.reverse() 29 | commands_unique = list(OrderedDict.fromkeys(commands)) 30 | commands_unique.reverse() 31 | 32 | return commands_unique 33 | 34 | def read_backwards(f, num_lines, delimiter, blocksize=1024): 35 | """Read num_lines seperated by delimiter from end of file without 36 | loading whole file.""" 37 | f.seek(0, 2) 38 | end = f.tell() 39 | block_number = -1 40 | blocks = [] 41 | 42 | while num_lines > 0 and end > 0: 43 | if (end - blocksize > 0): 44 | f.seek(block_number*blocksize, 2) 45 | blocks.append(f.read(blocksize)) 46 | else: 47 | # Read remaining data 48 | f.seek(0,0) 49 | blocks.append(f.read(end)) 50 | 51 | num_lines -= blocks[-1].count(delimiter) 52 | end -= blocksize 53 | block_number -= 1 54 | 55 | return ''.join(reversed(blocks)) 56 | 57 | def get_commands_in_directory(directory): 58 | """Returns all commands executed in directory. 59 | 60 | Duplicates will be removed and more important commands will be at 61 | the end of the returned list. 62 | The most recent command in directory is considered the most 63 | important command. 64 | The oldest command from all other directories is considered the 65 | least important command. 66 | """ 67 | commands_dir = [] 68 | commands_not_dir = [] 69 | try: 70 | with open(home + "/" + HISTFILE, "r") as f: 71 | # Get commands from this directory 72 | for line in reversed(read_backwards(f, HISTSIZE, '\0').strip().strip('\0').split('\0')): 73 | try: 74 | directory_in_history, command = get_dir_and_command(line.strip()) 75 | except (ValueError, IndexError): 76 | continue 77 | 78 | if directory_in_history == directory: 79 | commands_dir.append(command) 80 | 81 | # Get commands not in this directory 82 | for line in reversed(read_backwards(f, HISTSIZE, '\0').strip().strip('\0').split('\0')): 83 | try: 84 | directory_in_history, command = get_dir_and_command(line.strip()) 85 | except (ValueError, IndexError): 86 | continue 87 | 88 | if len(commands_not_dir) + len(commands_dir) >= HISTSIZE: 89 | break 90 | 91 | if directory_in_history != directory: 92 | commands_not_dir.append(command) 93 | except IOError: 94 | open(home + "/" + HISTFILE, 'a').close() 95 | 96 | # Least important command needs to be at the start 97 | commands = list(reversed(commands_not_dir)) + list(reversed(commands_dir)) 98 | 99 | # Remove duplicates from the front 100 | commands = remove_duplicates_front(commands) 101 | 102 | return commands 103 | 104 | def get_indices_by_substring_and_directory(directory, substring): 105 | """Return indices of substrings from a certain directory.""" 106 | commands = get_commands_in_directory(directory) 107 | 108 | # Remove backslashes in front of ][()|\*?#<>~^ 109 | substring_no_escape = re.sub(r'\\([\][()|\\*?#<>~^])', r'\1', substring) 110 | 111 | indices = [] 112 | for cmd in commands: 113 | if substring_no_escape.lower() in cmd.lower(): 114 | index = commands.index(cmd)+1 115 | indices.append(index) 116 | 117 | return indices 118 | 119 | # Return complete history for a directory if -a/-all -d DIRECTORY are arguments 120 | if len(sys.argv) == 4 and (sys.argv[1] == "-a" or sys.argv[1] == "--all") and (sys.argv[2] == "-d"): 121 | directory = sys.argv[3] 122 | 123 | print "\0\n".join(get_commands_in_directory(directory)) 124 | 125 | # Return list of indizes which match a given substring 126 | # dirhist -s/--substring SUBSTRING -d DIRECTORY 127 | if len(sys.argv) == 5 and (sys.argv[1] == "-s" or sys.argv[1] == "--substring") and sys.argv[3] == "-d": 128 | directory = sys.argv[4] 129 | substring = sys.argv[2] 130 | 131 | indices = [str(i) for i in get_indices_by_substring_and_directory(directory, substring)] 132 | if indices: 133 | print "\n".join(indices) 134 | else: 135 | print "NONE" 136 | --------------------------------------------------------------------------------