├── HACKING.md ├── README.md ├── colemak_keys.sh.example ├── deer └── screenshots ├── 2017-08-03-215440_640x364_scrot.png └── 2017-08-03-215509_640x364_scrot.png /HACKING.md: -------------------------------------------------------------------------------- 1 | # WARNING: MAY BE OUTDATED, SORRY # 2 | 3 | In this file I'll try to explain some design decisions and how `deer` works. 4 | 5 | VARIABLES 6 | ========= 7 | 8 | PATHS 9 | ----- 10 | 11 | **DEER_DIRNAME** 12 | 13 | The full path to the current directory, without the trailing slash. 14 | 15 | **DEER_BASENAME** 16 | 17 | An associative array mapping the dirnames to the files or directories 18 | focuesed there. 19 | 20 | **DEER_BASENAME[$DEER_DIRNAME]** 21 | 22 | The name of the currently selected file or directory. 23 | 24 | Due to the special case of the root directory (`/`), simply using 25 | `$DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]` to get the full path to the selected 26 | directory is not sufficient as it would result in a double slash. 27 | Currently I use `${DEER_DIRNAME%/}/$DEER_BASENAME[$DEER_DIRNAME]` to eliminate one 28 | slash in that corner case. It complicates the code in a few places but 29 | it's more maintainable than keeping the slash in `DEER_BASENAME` and 30 | concatenating dirname with basename without the additional slash in 31 | between (it was the case up until recently). 32 | 33 | OTHERS 34 | ------ 35 | 36 | **DEER_FILTER** 37 | 38 | An associative array mapping the dirnames to the filter to be applied 39 | there. It's modified in the function `deer-enter`. If the directory 40 | does not exist in that map, it's value should be assumed to be `*`. 41 | The prefered notation is quite ugly but it works: 42 | `${DEER_FILTER[$DEER_DIRNAME]:-'*'}` 43 | 44 | **OLD_LBUFFER and OLD_RBUFFER** 45 | 46 | The line buffer stored to restore the `LBUFFER` and `RBUFFER` contents 47 | later (in the `deer-restore` function) after adding the selected files 48 | to them. 49 | 50 | **PREDISPLAY** 51 | 52 | The built-in variable used to display the preview of the left side of 53 | the buffer (`OLD_LBUFFER`). 54 | 55 | **POSTDISPLAY** 56 | 57 | Used to display the status information. Currently only displays the 58 | applied filter if any. 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | deer 2 | ==== 3 | 4 | [![Join the chat at https://gitter.im/Vifon/deer](https://badges.gitter.im/Vifon/deer.svg)](https://gitter.im/Vifon/deer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | 6 | DESCRIPTION 7 | ----------- 8 | 9 | `deer` is a file navigator for [zsh](http://zsh.sourceforge.net/) 10 | heavily inspired by [ranger](http://ranger.nongnu.org/). 11 | 12 | **WHY** 13 | 14 | I've created `deer` because I really like to use `ranger` as an 15 | extension of my shell to quickly navigate the 16 | directories. Unfortunately, its startup time (even though short) is 17 | sometimes cumbersome. `deer` implements the very basic ranger-like 18 | file navigation + some basic operations on the commandline, like 19 | inserting the selected path (in various ways), which makes it fast to 20 | launch and ideal for the task. Not using the whole terminal can be 21 | viewed as an another feature. 22 | 23 | Pros: 24 | * Launches much faster. 25 | * Better shell integration. 26 | * Retains the terminal contents and only uses a small part of the terminal. 27 | 28 | Cons: 29 | * Offers only a small subset of `ranger's` features. 30 | * Needs `zsh`. 31 | 32 | [![](screenshots/2017-08-03-215440_640x364_scrot.png)](screenshots/2017-08-03-215440_640x364_scrot.png) 33 | [![](screenshots/2017-08-03-215509_640x364_scrot.png)](screenshots/2017-08-03-215509_640x364_scrot.png) 34 | 35 | USAGE 36 | ----- 37 | 38 | To launch `deer`, press `alt+k`. 39 | 40 | You can supply a numeric argument (`alt-number`) to go up the 41 | appropriate number of directory levels upon start. 42 | 43 | If you activate `deer` with the cursor on a path, it will start in there. 44 | 45 | **KEYS AND FUNCTIONS** 46 | 47 | These functions can be bound custom keys (the default is in the 48 | parentheses): 49 | 50 | * `down` (j) -- One item down. 51 | * `page_down` (J) -- Five items down. 52 | * `up` (k) -- One item up. 53 | * `page_up` (K) -- Five items up. 54 | * `enter` (l) -- Enter the selected directory. 55 | * `leave` (h) -- Leave the current directory (one directory up). 56 | * `next_parent` (]) -- One item down in the left column. 57 | * `prev_parent` ([) -- One item up in the left column. 58 | * `search` (/) -- Select the first file matching the given pattern. 59 | * `filter` (f) -- Shows only files matching the given pattern. 60 | * `toggle_hidden` (H) -- Show/hide the hidden files. 61 | * `quit` (q) -- Exit `deer`. 62 | * `append_path` (a) -- Insert the current path and leave the cursor on its right. 63 | * `append_abs_path` (A) -- Absolute path version. 64 | * `insert_path` (i) -- Insert the current path and leave the cursor on its left. 65 | * `insert_abs_path` (I) -- Absolute path version. 66 | * `multi_insert_dwim` (s) -- Insert the current path, add a smart separator using the last character before the cursor (unless it's an opening brace, then use a comma), move the cursor down and don't quit yet. 67 | * `multi_insert_abs` (S) -- Insert the current absolute path and don't quit yet. 68 | * `chdir` (c) -- `cd` into the current directory and quit. 69 | * `chdir_selected` (C) -- `cd` into the selected directory and quit. 70 | * `rifle` (r) -- Run `rifle(1)` on the selected file. 71 | * `edit` (e) -- Run `$EDITOR` on the selected file (default to vim). 72 | 73 | To bind a function to a different key, add something like this to your 74 | `.zshrc`: 75 | 76 | ``` 77 | typeset -Ag DEER_KEYS 78 | DEER_KEYS[function]=key 79 | ``` 80 | 81 | The `DEER_KEYS` variable is an associative array holding the keys 82 | associated with functions. One function may be bound to only one key 83 | (meaning the previous one is overwritten). 84 | 85 | INSTALLATION 86 | ------------ 87 | 88 | **First method** 89 | 90 | I assume you have `~/.fpath` added to your `$FPATH` variable here. If 91 | you don't, either add it or use the second installation method. If you 92 | use some other directory, modify the commands below accordingly. 93 | 94 | Copy the `deer` main file to `~/.fpath/deer` and make sure it gets 95 | autoloaded in your `zshrc`: 96 | 97 | ``` 98 | autoload -U deer 99 | ``` 100 | 101 | Adding these lines will make the script available to the line editor, and bind 102 | it to `alt+k` respectively: 103 | 104 | ``` 105 | zle -N deer 106 | bindkey '\ek' deer 107 | ``` 108 | 109 | **Second method** 110 | 111 | Alternatively, you can directly source the file `deer`, and bind the 112 | initialization function to `alt+k` as follows: 113 | 114 | ``` 115 | source /path/to/deer.sh 116 | zle -N deer 117 | bindkey '\ek' deer 118 | ``` 119 | 120 | **Third method** 121 | 122 | With a plugin manager. Tested with 123 | [antigen](https://github.com/zsh-users/antigen), 124 | [zgen](https://github.com/tarjoilija/zgen) and 125 | [zplug](https://github.com/zplug/zplug): 126 | 127 | - antigen: 128 | 129 | ``` 130 | antigen bundle Vifon/deer 131 | antigen apply 132 | autoload -U deer 133 | zle -N deer 134 | bindkey '\ek' deer 135 | ``` 136 | 137 | - zgen: 138 | 139 | ``` 140 | zgen load Vifon/deer 141 | zgen save 142 | autoload -U deer 143 | zle -N deer 144 | bindkey '\ek' deer 145 | ``` 146 | 147 | - zplug: 148 | 149 | ``` 150 | zplug "vifon/deer", use:deer 151 | zle -N deer 152 | bindkey '\ek' deer 153 | ``` 154 | 155 | CONFIGURATION 156 | ------------- 157 | 158 | By default, `deer` will use 22 lines of your terminal. This is configurable 159 | with the `zstyle` mechanism. Drop a line like this in `zshrc` to adjust this 160 | setting: 161 | 162 | ``` 163 | zstyle ':deer:' height 35 164 | ``` 165 | 166 | Show the hidden files by default: 167 | 168 | ``` 169 | zstyle :deer: show_hidden yes 170 | ``` 171 | 172 | To customize the keys used by `deer`, you may use the following code: 173 | 174 | ``` 175 | typeset -Ag DEER_KEYS # Prepare the associative table. 176 | DEER_KEYS[action_name]=key 177 | ``` 178 | 179 | Users of the Colemak keyboard layout may use the included 180 | `colemak_keys.sh.example` file to adjust the default keys to Colemak: 181 | 182 | ``` 183 | source colemak_keys.sh.example. 184 | ``` 185 | 186 | KNOWN ISSUES 187 | ------------ 188 | 189 | _These are the issues that I'm aware of, along with the reason for why 190 | I've decided not to fix them for now. If you think any of them is a 191 | dealbreaker, open a ticket on Github and I'll see what I can do._ 192 | 193 | **Slashes are replaced with division slashes (U+2215) in file previews** 194 | 195 | This is an ugly workaround, and the issue is with the way the output is 196 | formatted (slash is used as a separator for `paste(1)` and 197 | `column(1)` as it cannot appear in the filename). 198 | 199 | **The ../ directory is not correctly shown when completing the 200 | previously typed path** 201 | 202 | The shown path is created by deleting the preexisting prefix from the 203 | absolute path. It greatly simplifies the code and makes it easier to 204 | maintain (it's already quite messy in some places). 205 | 206 | **Buggy behavior in the root directory (/)** 207 | 208 | The root directory handling is quite tricky as it's the only directory 209 | where going up and down does not keep you in the same directory. I've 210 | concluded that that directory itself is used very rarely and most of 211 | the bugs are not fatal. 212 | 213 | FAQ 214 | --- 215 | 216 | **Can you add colors?** 217 | 218 | Unfortunately, no. It's a limitation of the underlying `zle` (zsh 219 | line editor) and I cannot do much about it. 220 | 221 | **Why are there so many strange features and their variations?** 222 | 223 | At first I add new features taylored for myself. Later I plan to 224 | review these features and clean them up. If you need something else, 225 | please leave a feature request or add it yourself if you know how. 226 | 227 | **The key binding system is ugly** 228 | 229 | Yes, it is. I plan to replace it with a proper keymap but for now it 230 | should suffice. 231 | 232 | RELATED PROJECTS 233 | ---------------- 234 | 235 | [lscd](https://github.com/hut/lscd) from the author of the original 236 | ranger is a minimal file browser written in a POSIX shell (with only a 237 | few necessary bashisms). 238 | 239 | [blscd](https://github.com/D630/blscd) is a Bash fork of `lscd` which 240 | is very similar to ranger. 241 | 242 | SEE ALSO 243 | -------- 244 | 245 | ranger(1), zsh(1) 246 | 247 | AUTHOR 248 | ------ 249 | 250 | Wojciech 'vifon' Siewierski < wojciech dot siewierski at gmail dot com > 251 | 252 | COPYRIGHT 253 | --------- 254 | 255 | Copyright (C) 2014-2015 Wojciech Siewierski 256 | 257 | This program is free software: you can redistribute it and/or modify 258 | it under the terms of the GNU General Public License as published by 259 | the Free Software Foundation, either version 3 of the License, or 260 | (at your option) any later version. 261 | 262 | This program is distributed in the hope that it will be useful, 263 | but WITHOUT ANY WARRANTY; without even the implied warranty of 264 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 265 | GNU General Public License for more details. 266 | 267 | You should have received a copy of the GNU General Public License 268 | along with this program. If not, see . 269 | -------------------------------------------------------------------------------- /colemak_keys.sh.example: -------------------------------------------------------------------------------- 1 | # Keys for the Colemak keyboard layout users. 2 | # Courtesy of Tadeusz 'tadzik' Sośnierz. 3 | 4 | typeset -Ag DEER_KEYS 5 | 6 | DEER_KEYS[down]=n 7 | DEER_KEYS[page_down]=N 8 | DEER_KEYS[up]=e 9 | DEER_KEYS[page_up]=E 10 | DEER_KEYS[enter]=i 11 | -------------------------------------------------------------------------------- /deer: -------------------------------------------------------------------------------- 1 | # -*- mode: shell-script -*- 2 | # vim: set ft=zsh : 3 | ######################################################################### 4 | # Copyright (C) 2014-2015 Wojciech Siewierski # 5 | # # 6 | # This program is free software: you can redistribute it and/or modify # 7 | # it under the terms of the GNU General Public License as published by # 8 | # the Free Software Foundation, either version 3 of the License, or # 9 | # (at your option) any later version. # 10 | # # 11 | # This program is distributed in the hope that it will be useful, # 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # 14 | # GNU General Public License for more details. # 15 | # # 16 | # You should have received a copy of the GNU General Public License # 17 | # along with this program. If not, see . # 18 | ######################################################################### 19 | 20 | zstyle -s ":deer:" height DEER_HEIGHT || DEER_HEIGHT=22 21 | zstyle -b ":deer:" show_hidden DEER_SHOW_HIDDEN 22 | 23 | 24 | typeset -Ag DEER_KEYS 25 | function () 26 | { 27 | while [ -n "$2" ]; do 28 | DEER_KEYS[$1]=${DEER_KEYS[$1]:-$2} 29 | shift 2 30 | done 31 | } down j \ 32 | page_down J \ 33 | up k \ 34 | page_up K \ 35 | enter l \ 36 | leave h \ 37 | next_parent ']' \ 38 | prev_parent '[' \ 39 | search / \ 40 | filter f \ 41 | toggle_hidden H \ 42 | quit q \ 43 | append_path a \ 44 | append_abs_path A \ 45 | insert_path i \ 46 | insert_abs_path I \ 47 | multi_insert_dwim s \ 48 | multi_insert_abs S \ 49 | chdir c \ 50 | chdir_selected C \ 51 | rifle r \ 52 | edit e \ 53 | 54 | 55 | # Select the Nth next file. Pass a negative argument for the previous file. 56 | deer-move() 57 | { 58 | local FILES MOVEMENT INDEX 59 | MOVEMENT=$1 60 | 61 | FILES=($DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-/:t) 62 | $DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-^/:t)) 63 | 64 | INDEX=${(k)FILES[(re)$DEER_BASENAME[$DEER_DIRNAME]]} 65 | 66 | if (( INDEX+MOVEMENT <= 0 )); then 67 | DEER_BASENAME[$DEER_DIRNAME]=$FILES[1] 68 | elif (( INDEX+MOVEMENT > $#FILES )); then 69 | DEER_BASENAME[$DEER_DIRNAME]=$FILES[$#FILES] 70 | else 71 | DEER_BASENAME[$DEER_DIRNAME]=$FILES[$INDEX+$MOVEMENT] 72 | fi 73 | } 74 | 75 | # Select the first visible directory (or file if there are no 76 | # directories) in the current directory. Useful when changing the file 77 | # filter. 78 | deer-refocus() 79 | { 80 | local TMP 81 | TMP=($DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-/:t) 82 | $DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-^/:t)) 83 | DEER_BASENAME[$DEER_DIRNAME]=$TMP[1] 84 | 85 | [ -n "$DEER_BASENAME[$DEER_DIRNAME]" ] # Return if there were any files at all. 86 | } 87 | 88 | # Enter the selected directory 89 | deer-enter() 90 | { 91 | # Abort if there is no file focused at all or if it is not a 92 | # directory. 93 | [ -n "$DEER_BASENAME[$DEER_DIRNAME]" -a \ 94 | -d "$DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]" ] || return 95 | 96 | DEER_DIRNAME=${DEER_DIRNAME%/}/$DEER_BASENAME[$DEER_DIRNAME] 97 | 98 | if [ -z $DEER_BASENAME[$DEER_DIRNAME] ]; then 99 | deer-refocus 100 | fi 101 | } 102 | 103 | # Move to the parent directory 104 | deer-leave() 105 | { 106 | [ $DEER_DIRNAME = / ] && return 107 | DEER_BASENAME[$DEER_DIRNAME:h]=$DEER_DIRNAME:t 108 | DEER_DIRNAME=$DEER_DIRNAME:h 109 | } 110 | 111 | # Display a given prompt, read a string and save it into $BUFFER. 112 | deer-prompt() 113 | { 114 | BUFFER="" 115 | PREDISPLAY="$1/ " 116 | POSTDISPLAY="" 117 | 118 | local region_highlight 119 | region_highlight=("P0 $#1 fg=green") 120 | zle recursive-edit 121 | } 122 | 123 | # Read a pattern and select the first matching file. 124 | deer-search() 125 | { 126 | deer-prompt "search" 127 | 128 | local TMP 129 | TMP=($DEER_DIRNAME/${~BUFFER}${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-:t)) 130 | [ -n "$TMP[1]" ] && DEER_BASENAME[$DEER_DIRNAME]=$TMP[1] 131 | } 132 | 133 | # Read a pattern and use it as a new filter. 134 | deer-filter() 135 | { 136 | deer-prompt "filter" 137 | 138 | if [ -n "$BUFFER" ] && [[ ! $BUFFER == *\** ]]; then 139 | BUFFER=*$BUFFER* 140 | fi 141 | 142 | deer-apply-filter $BUFFER || deer-apply-filter 143 | } 144 | 145 | deer-apply-filter() 146 | { 147 | DEER_FILTER[$DEER_DIRNAME]=$1 148 | deer-refocus 149 | } 150 | 151 | # Draw an arrow pointing to the selected file. 152 | deer-mark-file-list() 153 | { 154 | local MARKED=$1 155 | shift 156 | 157 | print -l -- "$@" \ 158 | | grep -Fx -B5 -A$DEER_HEIGHT -- "$MARKED" \ 159 | | perl -pe 'BEGIN{$name = shift} 160 | if ($name."\n" eq $_) { 161 | $_="-> $_" 162 | } else { 163 | $_=" $_" 164 | }' -- "$MARKED" 165 | } 166 | 167 | # Draw the file lists in the form of Miller columns. 168 | deer-refresh() 169 | { 170 | local FILES PREVIEW PARENTFILES OUTPUT REL_DIRNAME 171 | local SEPARATOR="------" 172 | 173 | PREDISPLAY=$OLD_LBUFFER 174 | REL_DIRNAME=${${DEER_DIRNAME%/}#$DEER_STARTDIR}/ 175 | [ -n "$DEER_STARTDIR" ] && REL_DIRNAME=${REL_DIRNAME#/} 176 | LBUFFER=$REL_DIRNAME$DEER_BASENAME[$DEER_DIRNAME] 177 | RBUFFER="" 178 | local TMP_FILTER 179 | TMP_FILTER=${DEER_FILTER[$DEER_DIRNAME]} 180 | POSTDISPLAY=${TMP_FILTER:+ filt:$TMP_FILTER} 181 | region_highlight=("P0 $#PREDISPLAY fg=black,bold" 182 | "0 $#REL_DIRNAME fg=blue,bold" 183 | "$#BUFFER $[$#BUFFER+$#POSTDISPLAY] fg=yellow,bold") 184 | 185 | 186 | FILES=($DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-/:t) 187 | $SEPARATOR 188 | $DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-^/:t)) 189 | PARENTFILES=($DEER_DIRNAME:h/${~DEER_FILTER[$DEER_DIRNAME:h]:-'*'}(N$DEER_GLOBFLAGS-/:t)) 190 | 191 | local IFS=$'\n' 192 | FILES=($(deer-mark-file-list "$DEER_BASENAME[$DEER_DIRNAME]" $FILES)) 193 | PARENTFILES=($(deer-mark-file-list "$DEER_DIRNAME:t" $PARENTFILES)) 194 | unset IFS 195 | 196 | FILES=(${(F)FILES[1,$DEER_HEIGHT]}) 197 | PARENTFILES=(${(F)PARENTFILES[1,$DEER_HEIGHT]}) 198 | 199 | 200 | if [ -f $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] ]; then 201 | if file $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] | grep -Fq text; then 202 | PREVIEW="--- Preview: ---"$'\n'$(head -n$DEER_HEIGHT $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]) 203 | 204 | # Replace '/' with '∕' (division slash, U+2215) to allow using it as a 205 | # paste(1)/column(1) separator. 206 | PREVIEW=${PREVIEW//\//∕} 207 | else 208 | PREVIEW="--- Binary file, preview unavailable ---" 209 | fi 210 | else 211 | # I'm really sorry about what you see below. 212 | # It basically means: PREVIEW=(directories separator files) 213 | PREVIEW=($DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]/${~DEER_FILTER[$DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]]:-'*'}(N$DEER_GLOBFLAGS-/:t) 214 | $SEPARATOR 215 | $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]/${~DEER_FILTER[$DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]]:-'*'}(N$DEER_GLOBFLAGS-^/:t)) 216 | PREVIEW=${(F)PREVIEW[1,$DEER_HEIGHT]} 217 | fi 218 | 219 | OUTPUT="$(paste -d/ <(<<< $PARENTFILES \ 220 | | awk '{print substr($0,1,16)}') \ 221 | <(<<< $FILES) \ 222 | <(<<< $PREVIEW) \ 223 | | sed 's,/, / ,g' \ 224 | | column -t -s/ 2> /dev/null \ 225 | | awk -v width=$COLUMNS '{print substr($0,1,width-1)}')" 226 | zle -M -- $OUTPUT 227 | zle -R 228 | } 229 | 230 | # Run `deer-add' with the same arguments, restore the shell state and 231 | # then exit. 232 | deer-restore() 233 | { 234 | deer-add "$@" 235 | PREDISPLAY="" 236 | POSTDISPLAY="" 237 | region_highlight=() 238 | LBUFFER=$OLD_LBUFFER 239 | RBUFFER=$OLD_RBUFFER 240 | zle reset-prompt 241 | zle -M "" 242 | } 243 | 244 | # Add the given string before or after the cursor. 245 | deer-add() 246 | { 247 | case $1 in 248 | --append) 249 | OLD_LBUFFER+=$2 250 | shift 2 251 | ;; 252 | --insert) 253 | OLD_RBUFFER=$2$OLD_RBUFFER 254 | shift 2 255 | ;; 256 | esac 257 | } 258 | 259 | # Get the quoted relative path from the absolute unquoted path. 260 | deer-get-relative() 261 | { 262 | local TMP 263 | TMP=${1:-${DEER_DIRNAME%/}/$DEER_BASENAME[$DEER_DIRNAME]} 264 | TMP="`python -c ' 265 | import sys, os 266 | print(os.path.relpath(sys.argv[1], sys.argv[2])) 267 | ' $TMP ${DEER_STARTDIR:-$PWD}`" 268 | print -R $TMP:q 269 | } 270 | 271 | # Tries to guess a directory to start in from the current argument. 272 | deer-set-initial-directory() 273 | { 274 | autoload -U split-shell-arguments modify-current-argument 275 | local REPLY REPLY2 reply 276 | local DIRECTORY 277 | 278 | ((--CURSOR)) 279 | split-shell-arguments 280 | ((++CURSOR)) 281 | 282 | # Find the longest existing directory path in the current argument. 283 | DEER_STARTDIR=${(Q)${${reply[$REPLY]%%[[:space:]]#}:a}%/} 284 | while [ -n "$DEER_STARTDIR" -a \ 285 | ! -d "$DEER_STARTDIR" ]; do 286 | DEER_STARTDIR=${DEER_STARTDIR%/*} 287 | done 288 | 289 | DEER_DIRNAME=${DEER_STARTDIR:-$PWD} 290 | } 291 | 292 | # The main entry function. 293 | deer-launch() 294 | { 295 | emulate -L zsh 296 | setopt extended_glob 297 | local DEER_DIRNAME DEER_STARTDIR DEER_GLOBFLAGS 298 | local -A DEER_FILTER DEER_BASENAME 299 | local REPLY OLD_LBUFFER OLD_RBUFFER 300 | 301 | local GREP_OPTIONS 302 | GREP_OPTIONS="" 303 | 304 | OLD_LBUFFER=$LBUFFER 305 | OLD_RBUFFER=$RBUFFER 306 | 307 | deer-set-initial-directory 308 | 309 | if [ "$DEER_SHOW_HIDDEN" = yes ]; then 310 | DEER_GLOBFLAGS=D 311 | else 312 | DEER_GLOBFLAGS="" 313 | fi 314 | 315 | if [ -n "$NUMERIC" ]; then 316 | for i in {1..$NUMERIC}; do 317 | deer-leave 318 | done 319 | else 320 | # Don't change cwd but initialize the variables. 321 | deer-leave 322 | deer-enter 323 | fi 324 | 325 | deer-refresh 326 | while read -k; do 327 | case $REPLY in 328 | # Movement 329 | $DEER_KEYS[up]) 330 | deer-move -1 331 | deer-refresh 332 | ;; 333 | $DEER_KEYS[page_up]) 334 | deer-move -5 335 | deer-refresh 336 | ;; 337 | $DEER_KEYS[down]) 338 | deer-move 1 339 | deer-refresh 340 | ;; 341 | $DEER_KEYS[page_down]) 342 | deer-move 5 343 | deer-refresh 344 | ;; 345 | $DEER_KEYS[enter]) 346 | deer-enter 347 | deer-refresh 348 | ;; 349 | $DEER_KEYS[leave]) 350 | deer-leave 351 | deer-refresh 352 | ;; 353 | $DEER_KEYS[next_parent]) 354 | deer-leave 355 | deer-move 1 356 | deer-enter 357 | deer-refresh 358 | ;; 359 | $DEER_KEYS[prev_parent]) 360 | deer-leave 361 | deer-move -1 362 | deer-enter 363 | deer-refresh 364 | ;; 365 | # Search 366 | $DEER_KEYS[search]) 367 | deer-search 368 | deer-refresh 369 | ;; 370 | # Filter 371 | $DEER_KEYS[filter]) 372 | deer-filter 373 | deer-refresh 374 | ;; 375 | $DEER_KEYS[toggle_hidden]) 376 | if [ -z $DEER_GLOBFLAGS ]; then 377 | DEER_GLOBFLAGS="D" # show hidden files 378 | else 379 | DEER_GLOBFLAGS="" 380 | fi 381 | # make sure the focus is on a visible file 382 | DEER_BASENAME[$DEER_DIRNAME]= 383 | deer-leave 384 | deer-enter 385 | deer-refresh 386 | ;; 387 | # Quit 388 | $DEER_KEYS[quit]) 389 | deer-restore 390 | break 391 | ;; 392 | # Insert the path and quit. 393 | $DEER_KEYS[append_path]) 394 | deer-restore --append "`deer-get-relative` " 395 | break 396 | ;; 397 | $DEER_KEYS[append_abs_path]) 398 | deer-restore --append "${${DEER_DIRNAME%/}:q}/${DEER_BASENAME[$DEER_DIRNAME]:q} " 399 | break 400 | ;; 401 | $DEER_KEYS[insert_path]) 402 | deer-restore --insert " `deer-get-relative`" 403 | break 404 | ;; 405 | $DEER_KEYS[insert_abs_path]) 406 | deer-restore --insert " ${${DEER_DIRNAME%/}:q}/${DEER_BASENAME[$DEER_DIRNAME]:q}" 407 | break 408 | ;; 409 | # Insert the path and don't quit yet. 410 | $DEER_KEYS[multi_insert_dwim]) 411 | if [ "$OLD_LBUFFER[-1]" = "/" ]; then 412 | OLD_LBUFFER+="{" 413 | fi 414 | # replacement used to insert ',' instead of '{' as a separator in {foo,bar,...} lists 415 | deer-add --append "`deer-get-relative`"${${OLD_LBUFFER[-1]/\{/,}:- } 416 | deer-move 1 417 | deer-refresh 418 | ;; 419 | # Insert the absolute path and don't quit yet. 420 | $DEER_KEYS[multi_insert_abs]) 421 | deer-add --append " ${${DEER_DIRNAME%/}:q}/${DEER_BASENAME[$DEER_DIRNAME]:q}" 422 | deer-move 1 423 | deer-refresh 424 | ;; 425 | # Quit and change the shell's current directory to the selected one. 426 | $DEER_KEYS[chdir]) 427 | deer-leave 428 | ;& 429 | $DEER_KEYS[chdir_selected]) 430 | if [[ -d $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] && \ 431 | -x $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] ]]; then 432 | cd -- $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] 433 | deer-restore 434 | break 435 | fi 436 | ;; 437 | $DEER_KEYS[edit]) 438 | if [[ -f $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] ]]; then 439 | "${EDITOR:-vim}" $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] 440 | fi 441 | ;; 442 | # See rifle(1) manpage (included with ranger(1)). 443 | $DEER_KEYS[rifle]) 444 | if [[ -f $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] ]]; then 445 | rifle $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] 446 | fi 447 | ;; 448 | # Arrow keys 449 | $'\e') 450 | read -k 451 | case $REPLY in 452 | '[') 453 | read -k 454 | case $REPLY in 455 | 'A') 456 | deer-move -1 457 | deer-refresh 458 | ;; 459 | 'B') 460 | deer-move 1 461 | deer-refresh 462 | ;; 463 | 'C') 464 | deer-enter 465 | deer-refresh 466 | ;; 467 | 'D') 468 | deer-leave 469 | deer-refresh 470 | ;; 471 | esac 472 | ;; 473 | esac 474 | ;; 475 | esac 476 | done 477 | } 478 | 479 | if zle; then 480 | deer-launch 481 | else 482 | deer() 483 | { 484 | deer-launch "$@" 485 | } 486 | fi 487 | -------------------------------------------------------------------------------- /screenshots/2017-08-03-215440_640x364_scrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vifon/deer/da9b86e98d712f4feadfd96070ddb4c99bd29e29/screenshots/2017-08-03-215440_640x364_scrot.png -------------------------------------------------------------------------------- /screenshots/2017-08-03-215509_640x364_scrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vifon/deer/da9b86e98d712f4feadfd96070ddb4c99bd29e29/screenshots/2017-08-03-215509_640x364_scrot.png --------------------------------------------------------------------------------