├── LICENSE ├── README.md ├── conf.d └── fzf-marks.plugin.fish ├── fzf-marks.plugin.bash ├── fzf-marks.plugin.zsh └── init.zsh /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Urbain Vaes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fzf-marks 2 | This plugin can be used to create, delete, and navigate marks in *bash* and *zsh*. 3 | It depends on Junegunn Choi's fuzzy-finder [fzf](https://github.com/junegunn/fzf). 4 | 5 | ![](https://raw.github.com/uvaes/fuzzy-zsh-marks/demo/demo.gif) 6 | (This video was generated with `screenkey -g $(slop -n -f '%g')` and `simplescreenrecorder`.) 7 | 8 | # Installation 9 | 10 | ### Zsh 11 | 12 | If you use *zsh*, I recommend installing with a plugin manager. 13 | In the case of [zgen](https://github.com/tarjoilija/zgen), for example, 14 | simply add the following line to your plugin list: 15 | ```zsh 16 | zgen load urbainvaes/fzf-marks 17 | ``` 18 | ### Fish 19 | 20 | If you use *fish*, then you can use [fisher](https://github.com/jorgebucaran/fisher): 21 | ```fish 22 | fisher install urbainvaes/fzf-marks 23 | ``` 24 | 25 | ### Bash 26 | 27 | If you use *bash*, 28 | or if you use *zsh* without a plugin manager, 29 | source the file `fzf-marks.plugin.bash` or `fzf-marks.plugin.zsh` from your shell startup file 30 | to enable the plugin. 31 | 32 | **Bash installation example**: 33 | ```bash 34 | # Clone the git repository in the current directory 35 | git clone https://github.com/urbainvaes/fzf-marks.git 36 | 37 | # Add a line to ~/.bashrc to load the plugin whenever bash starts in interactive mode 38 | echo "source $PWD/fzf-marks/fzf-marks.plugin.bash" >> ~/.bashrc 39 | 40 | # Source the plugin now so we don't have to restart bash to start using it 41 | source fzf-marks/fzf-marks.plugin.bash 42 | ``` 43 | 44 | **Enabling completion**: 45 | 46 | In `bash`, completion should be available automatically. 47 | In `zsh`, completion needs to be enabled explicitly before sourcing the plugin. 48 | This is usually done automatically by plugin managers, 49 | but it can also be achieved manually with the following command in your `.zshrc`. 50 | ```zsh 51 | autoload -Uz compinit && compinit 52 | ``` 53 | 54 | # Usage 55 | The script exposes two functions: 56 | 57 | - **mark \**, to register a new mark to the current directory; 58 | - **fzm [\]**, to jump to or delete a mark using `fzf`. 59 | 60 | Most of the keybindings in the search window are the default fzf ones. 61 | The only additions are 62 | 63 | - **ctrl-y**, to jump to a match; 64 | - **ctrl-t**, to toggle a match for deletion; 65 | - **ctrl-d**, to delete selected matches. 66 | 67 | By default, the plugin binds the key `ctrl-g` to `fzm`. 68 | 69 | # Customization 70 | 71 | | Config | Default | Description | 72 | | ------ | ------- | ----------- | 73 | | `FZF_MARKS_FILE` | `${HOME}/.fzf-marks` | File containing the marks data | 74 | | `FZF_MARKS_COMMAND` | `fzf --height 40% --reverse` | Command used to call `fzf` | 75 | | `FZF_MARKS_JUMP` | `\C-g` (*bash*) or `^g` (*zsh*) | Keybinding to `fzm` | 76 | | `FZF_MARKS_COLOR_LHS` | 39 (default) | ANSI color code of left-hand side | 77 | | `FZF_MARKS_COLOR_RHS` | 36 (cyan) | ANSI color code of right-hand side | 78 | | `FZF_MARKS_COLOR_COLON` | 33 (yellow) | ANSI color code of separator | 79 | | `FZF_MARKS_NO_COLORS` | 0 | Set this to 1 to disable colors | 80 | | `FZF_MARKS_KEEP_ORDER` | 0 | Set this to 1 to keep order of marks | 81 | 82 | See e.g. [here](http://pueblo.sourceforge.net/doc/manual/ansi_color_codes.html) for a description of ANSI color codes. 83 | 84 | # FAQ 85 | 86 | **Question**: *Is it possible to limit the fzf search to the mark label, i.e. to exclude the path from the search?* 87 | 88 | Yes, this is possible by passing the options `-n` (for the field number to use for the search) and `-d` (for the delimiter) to `fzf`. 89 | For example, 90 | ``` 91 | FZF_MARKS_COMMAND="fzf --height 40% --reverse -n 1 -d ' : '" 92 | ``` 93 | 94 | # License 95 | 96 | MIT 97 | -------------------------------------------------------------------------------- /conf.d/fzf-marks.plugin.fish: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Marcel Patzwahl, Urbain Vaes 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | command -v fzf > /dev/null 2>&1 or return 24 | 25 | if test -z "$FZF_MARKS_FILE" 26 | set -g FZF_MARKS_FILE "$HOME/.fzf-marks" 27 | end 28 | 29 | if test ! -f "$FZF_MARKS_FILE" 30 | touch "$FZF_MARKS_FILE" 31 | end 32 | 33 | if test -z "$FZF_MARKS_COMMAND" 34 | set -l fzf_version (fzf --version | awk -F. '{ print $1 * 1e6 + $2 * 1e3 + $3 }') 35 | set -l minimum_version 16001 36 | 37 | if test $fzf_version -gt $minimum_version 38 | set -g FZF_MARKS_COMMAND fzf --height 40% --reverse --header=\'ctrl-y:jump, ctrl-t:toggle, ctrl-d:delete\' 39 | else if test $FZF_TMUX -eq 1 40 | set -l tmux_height $FZF_TMUX_HEIGHT 41 | set -lq tmux_height[1]; or set tmux_height[1] 40 42 | set -g FZF_MARKS_COMMAND "fzf-tmux -d$tmux_height" 43 | else 44 | set -g FZF_MARKS_COMMAND "fzf" 45 | end 46 | end 47 | 48 | function mark 49 | set -l mark_to_add "$argv : "(pwd) 50 | 51 | if grep -qxFe "$mark_to_add" "$FZF_MARKS_FILE" 52 | echo "** The following mark already exists **" 53 | else 54 | echo "$mark_to_add" >> "$FZF_MARKS_FILE" 55 | echo "** The following mark has been added **" 56 | end 57 | echo "$mark_to_add" | _color_marks 58 | end 59 | 60 | function _handle_symlinks 61 | if test -L $FZF_MARKS_FILE 62 | set -l link (readlink "$FZF_MARKS_FILE") 63 | switch $link 64 | case '/*' 65 | echo $link 66 | case '*' 67 | echo (dirname $FZF_MARKS_FILE)/$link 68 | end 69 | else 70 | echo $FZF_MARKS_FILE 71 | end 72 | end 73 | 74 | function _color_marks 75 | if test "$FZF_MARKS_NO_COLORS" = "1" 76 | cat 77 | else 78 | set -l esc (printf '\033') 79 | set -l c_lhs $FZF_MARKS_COLOR_LHS 80 | set -lq c_lhs[1]; or set c_lhs[1] 39 81 | set -l c_rhs $FZF_MARKS_COLOR_RHS 82 | set -lq c_rhs[1]; or set c_rhs[1] 36 83 | set -l c_colon $FZF_MARKS_COLOR_COLON 84 | set -lq c_colon[1]; or set c_colon[1] 33 85 | sed "s/^\\(.*\\) : \\(.*\\)\$/"$esc"["$c_lhs"m\\1"$esc"[0m "$esc"["$c_colon"m:"$esc"[0m "$esc"["$c_rhs"m\\2"$esc"[0m/" 86 | end 87 | end 88 | 89 | function fzm 90 | set -l marks_del $FZF_MARKS_DELETE 91 | set -lq marks_del[1]; or set marks_del[1] "ctrl-d" 92 | 93 | set lines (_color_marks < $FZF_MARKS_FILE | eval $FZF_MARKS_COMMAND \ 94 | --ansi \ 95 | --expect="$marks_del" \ 96 | --multi \ 97 | --bind=ctrl-y:accept,ctrl-t:toggle+down \ 98 | --query=$argv \ 99 | --select-1 \ 100 | --tac) 101 | if test -z "$lines" 102 | commandline -f repaint 103 | return 1 104 | end 105 | set -l key (echo "$lines" | head -1 | string split " ") 106 | if test $marks_del = $key[1] 107 | dmark "-->-->-->" (echo "$key[2..-1]") 108 | else 109 | jump "-->-->-->" (echo "$lines" | tail -1) 110 | end 111 | end 112 | 113 | function jump 114 | if test $argv[1] = "-->-->-->" 115 | set jumpline $argv[2] 116 | else 117 | set jumpline (_color_marks < $FZF_MARKS_FILE | $FZF_MARKS_COMMAND \ 118 | --ansi \ 119 | --bind=ctrl-y:accept \ 120 | --query="$argv" \ 121 | --select-1 \ 122 | --tac) 123 | end 124 | if test -n $jumpline 125 | set -l jumpdir (echo "$jumpline" | sed 's/.*: \(.*\)$/\1/' | sed "s#^~#$HOME#") 126 | set -l bookmarks (_handle_symlinks) 127 | cd $jumpdir 128 | commandline -f repaint 129 | end 130 | end 131 | 132 | function dmark 133 | if test $argv[1] = "-->-->-->" 134 | set marks_to_delete $argv[2] 135 | else 136 | set marks_to_delete (_color_marks < $FZF_MARKS_FILE | $FZF_MARKS_COMMAND \ 137 | -m \ 138 | --ansi \ 139 | --bind=ctrl-y:accept,ctrl-t:toggle+down \ 140 | --query="$argv" \ 141 | --tac) 142 | end 143 | set -l bookmarks (_handle_symlinks) 144 | for line in $marks_to_delete 145 | set -l line (string replace -a "/" "\/" $line) 146 | perl -n -i -e "print unless /^\Q$line\E\$/" "$bookmarks" 147 | end 148 | if test (echo $marks_to_delete | wc -l) = 1 149 | echo "** The following mark has been deleted **" 150 | echo "$marks_to_delete" | _color_marks 151 | else 152 | echo "** The following marks have been deleted **" 153 | echo "$marks_to_delete" | _color_marks 154 | end 155 | commandline -f repaint 156 | end 157 | 158 | set -q FZF_MARKS_JUMP; or set FZF_MARKS_JUMP \cg 159 | bind -M insert $FZF_MARKS_JUMP fzm 160 | bind $FZF_MARKS_JUMP fzm 161 | 162 | if test -n "$FZF_MARKS_DMARK" 163 | bind -M insert "$FZF_MARKS_DMARK" dmark 164 | bind "$FZF_MARKS_DMARK" dmark 165 | end 166 | -------------------------------------------------------------------------------- /fzf-marks.plugin.bash: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2018 Urbain Vaes 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | command -v fzf >/dev/null 2>&1 || return 24 | 25 | if [[ -z ${FZF_MARKS_FILE-} ]] ; then 26 | FZF_MARKS_FILE=$HOME/.fzf-marks 27 | fi 28 | 29 | if [[ ! -f $FZF_MARKS_FILE ]]; then 30 | touch "$FZF_MARKS_FILE" 31 | fi 32 | 33 | if [[ -z ${FZF_MARKS_COMMAND-} ]] ; then 34 | 35 | _fzm_FZF_VERSION=$(fzf --version | awk -F. '{ print $1 * 1e6 + $2 * 1e3 + $3 }') 36 | _fzm_MINIMUM_VERSION=16001 37 | 38 | if [[ $_fzm_FZF_VERSION -gt $_fzm_MINIMUM_VERSION ]]; then 39 | FZF_MARKS_COMMAND="fzf --height 40% --reverse" 40 | elif [[ ${FZF_TMUX:-1} -eq 1 ]]; then 41 | FZF_MARKS_COMMAND="fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" 42 | else 43 | FZF_MARKS_COMMAND="fzf" 44 | fi 45 | fi 46 | 47 | function _fzm_setup_completion { 48 | complete -W "$(sed 's/\(.*\) : .*$/"\1"/' < "$FZF_MARKS_FILE")" fzm 49 | } 50 | 51 | function mark { 52 | local mark_to_add 53 | mark_to_add="${*:-$(basename "$(pwd)")} : $(pwd)" 54 | 55 | if grep -qxFe "${mark_to_add}" "${FZF_MARKS_FILE}"; then 56 | echo "** The following mark already exists **" 57 | else 58 | printf '%s\n' "${mark_to_add}" >> "${FZF_MARKS_FILE}" 59 | echo "** The following mark has been added **" 60 | fi 61 | _fzm_color_marks <<< $mark_to_add 62 | _fzm_setup_completion 63 | } 64 | 65 | function _fzm_handle_symlinks { 66 | local fname link 67 | if [ -L "${FZF_MARKS_FILE}" ]; then 68 | link=$(readlink "${FZF_MARKS_FILE}") 69 | case "$link" in 70 | /*) fname="$link";; 71 | *) fname="$(dirname "$FZF_MARKS_FILE")/$link";; 72 | esac 73 | else 74 | fname=${FZF_MARKS_FILE} 75 | fi 76 | printf '%s\n' "${fname}" 77 | } 78 | 79 | function _fzm_color_marks { 80 | if [[ "${FZF_MARKS_NO_COLORS-}" == "1" ]]; then 81 | cat 82 | else 83 | local esc c_lhs c_rhs c_colon 84 | esc=$(printf '\033') 85 | c_lhs=${FZF_MARKS_COLOR_LHS:-39} 86 | c_rhs=${FZF_MARKS_COLOR_RHS:-36} 87 | c_colon=${FZF_MARKS_COLOR_COLON:-33} 88 | sed "s/^\\(.*\\) : \\(.*\\)$/${esc}[${c_lhs}m\\1${esc}[0m ${esc}[${c_colon}m:${esc}[0m ${esc}[${c_rhs}m\\2${esc}[0m/" 89 | fi 90 | } 91 | 92 | function fzm { 93 | local delete_key=${FZF_MARKS_DELETE:-ctrl-d} paste_key=${FZF_MARKS_PASTE:-ctrl-v} 94 | local lines=$(_fzm_color_marks < "${FZF_MARKS_FILE}" | eval ${FZF_MARKS_COMMAND} \ 95 | --ansi \ 96 | --expect='"$delete_key,$paste_key"' \ 97 | --multi \ 98 | --bind=ctrl-y:accept,ctrl-t:toggle+down \ 99 | --header='"ctrl-y:jump, ctrl-t:toggle, $delete_key:delete, $paste_key:paste"' \ 100 | --query='"$*"' \ 101 | --select-1 \ 102 | --tac) 103 | if [[ -z "$lines" ]]; then 104 | return 1 105 | fi 106 | 107 | local key=$(head -1 <<< "$lines") 108 | 109 | if [[ $key == "$delete_key" ]]; then 110 | dmark "-->-->-->" "$(sed 1d <<< "$lines")" 111 | elif [[ $key == "$paste_key" || ! -t 1 ]]; then 112 | pmark "-->-->-->" "$(tail -1 <<< "$lines")" 113 | else 114 | jump "-->-->-->" "$(tail -1 <<< "${lines}")" 115 | fi 116 | } 117 | 118 | function jump { 119 | local jumpline jumpdir bookmarks 120 | if [[ $1 == "-->-->-->" ]]; then 121 | jumpline=$2 122 | else 123 | jumpline=$(_fzm_color_marks < "${FZF_MARKS_FILE}" | eval ${FZF_MARKS_COMMAND} \ 124 | --ansi \ 125 | --bind=ctrl-y:accept \ 126 | --header='"ctrl-y:jump"' \ 127 | --query='"$*"' \ 128 | --select-1 \ 129 | --tac) 130 | fi 131 | if [[ -n ${jumpline} ]]; then 132 | jumpdir=$(sed 's/.*: \(.*\)$/\1/;'"s#^~#${HOME}#" <<< $jumpline) 133 | bookmarks=$(_fzm_handle_symlinks) 134 | cd "${jumpdir}" || return 135 | if [[ ! "${FZF_MARKS_KEEP_ORDER}" == 1 && -w ${FZF_MARKS_FILE} ]]; then 136 | perl -n -i -e "print unless /^\\Q${jumpline//\//\\/}\\E\$/" "${bookmarks}" 137 | printf '%s\n' "${jumpline}" >> "${FZF_MARKS_FILE}" 138 | fi 139 | fi 140 | } 141 | 142 | function pmark { 143 | local selected 144 | if [[ $1 == "-->-->-->" ]]; then 145 | selected=$2 146 | else 147 | selected=$(_fzm_color_marks < "${FZF_MARKS_FILE}" | eval ${FZF_MARKS_COMMAND} \ 148 | --ansi \ 149 | --bind=ctrl-y:accept --header='"ctrl-y:paste"' \ 150 | --query='"$*"' --select-1 --tac) 151 | fi 152 | if [[ $selected ]]; then 153 | selected=$(sed 's/.*: \(.*\)$/\1/;'"s#^~#${HOME}#" <<< $selected) 154 | local paste_command=${FZF_MARKS_PASTE_COMMAND:-"printf '%s\n'"} 155 | eval -- "$paste_command \"\$selected\"" 156 | fi 157 | } 158 | 159 | function dmark { 160 | local marks_to_delete line bookmarks 161 | if [[ $1 == "-->-->-->" ]]; then 162 | marks_to_delete=$2 163 | else 164 | marks_to_delete=$(_fzm_color_marks < "${FZF_MARKS_FILE}" | eval ${FZF_MARKS_COMMAND} \ 165 | -m --ansi \ 166 | --bind=ctrl-y:accept,ctrl-t:toggle+down --header='"ctrl-y:delete, ctrl-t:toggle"' \ 167 | --query='"$*"' --tac) 168 | fi 169 | bookmarks=$(_fzm_handle_symlinks) 170 | 171 | if [[ -n ${marks_to_delete} ]]; then 172 | while IFS='' read -r line; do 173 | perl -n -i -e "print unless /^\\Q${line//\//\\/}\\E\$/" "${bookmarks}" 174 | done <<< "$marks_to_delete" 175 | 176 | [[ $(wc -l <<< "${marks_to_delete}") == 1 ]] \ 177 | && echo "** The following mark has been deleted **" \ 178 | || echo "** The following marks have been deleted **" 179 | _fzm_color_marks <<< $marks_to_delete 180 | fi 181 | _fzm_setup_completion 182 | } 183 | 184 | if ((BASH_VERSINFO[0] >= 4)); then 185 | # Widget for Bash 4.0+ 186 | 187 | # bashbug https://lists.gnu.org/archive/html/bug-bash/2018-04/msg00040.html 188 | function _fzm_widget_has_readline_point_bug { 189 | [[ BASH_VERSINFO[0] -lt 5 && ! $_ble_attached ]] 190 | } 191 | function _fzm_widget_has_readline_mark { 192 | [[ BASH_VERSINFO[0] -ge 5 || $_ble_attached ]] 193 | } 194 | function _fzm_widget_insert { 195 | local insert=$1 196 | 197 | # Work around bashbug 198 | if _fzm_widget_has_readline_point_bug; then 199 | # Convert READLINE_POINT from bytes to characters 200 | local old_lc_all=$LC_ALL old_lc_ctype=$LC_CTYPE 201 | local LC_ALL= LC_CTYPE=C 202 | local head=${READLINE_LINE::READLINE_POINT} 203 | LC_ALL=$old_lc_all LC_CTYPE=$old_lc_ctype 204 | READLINE_POINT=${#head} 205 | fi 206 | 207 | READLINE_LINE=${READLINE_LINE::READLINE_POINT}$insert${READLINE_LINE:READLINE_POINT} 208 | if _fzm_widget_has_readline_mark && ((READLINE_MARK > READLINE_POINT)); then 209 | # Bash 5.0 has new variable READLINE_MARK 210 | ((READLINE_MARK += ${#insert})) 211 | fi 212 | ((READLINE_POINT += ${#insert})) 213 | 214 | # Work around bashbug 215 | if _fzm_widget_has_readline_point_bug; then 216 | # Convert READLINE_POINT from characters to bytes 217 | local head=${READLINE_LINE::READLINE_POINT} 218 | local LC_ALL= LC_CTYPE=C 219 | READLINE_POINT=${#head} 220 | LC_ALL=$old_lc_all LC_CTYPE=$old_lc_ctype 221 | fi 222 | } 2>/dev/null # Suppress locale error messages 223 | function _fzm_widget_stash_line { 224 | _fzm_line=$READLINE_LINE 225 | _fzm_point=$READLINE_POINT 226 | READLINE_LINE= 227 | READLINE_POINT=0 228 | if _fzm_widget_has_readline_mark; then 229 | _fzm_mark=$READLINE_MARK 230 | READLINE_MARK=0 231 | fi 232 | } 233 | function _fzm_widget_pop_line { 234 | READLINE_LINE=$_fzm_line 235 | READLINE_POINT=$_fzm_point 236 | if _fzm_widget_has_readline_mark; then 237 | READLINE_MARK=$_fzm_mark 238 | fi 239 | } 240 | function _fzm-widget { 241 | local pwd=$PWD 242 | local FZF_MARKS_PASTE_COMMAND=_fzm_widget_insert 243 | fzm 244 | 245 | if [[ $PWD != "$pwd" ]]; then 246 | # Force the prompt update 247 | _fzm_widget_stash_line 248 | bind "\"$_fzm_key2\": \"\C-m$_fzm_key3\"" 249 | bind -x "\"$_fzm_key3\": _fzm_widget_pop_line" 250 | else 251 | bind "\"$_fzm_key2\": \"\"" 252 | fi 253 | } 254 | 255 | else 256 | # Widget for Bash 3.0-3.2 257 | function _fzm_widget_untranslate_keyseq { 258 | local value=$1 259 | if [[ $value == *[\'\"$'\001'-$'\037']* ]]; then 260 | local a b 261 | a='\' b='\\' value=${value//$a/$b} 262 | a='"' b='\"' value=${value//$a/$b} 263 | a=$'\001' b='\C-q\001' value=${value//$a/$b} 264 | a=$'\002' b='\C-q\002' value=${value//$a/$b} 265 | a=$'\003' b='\C-q\003' value=${value//$a/$b} 266 | a=$'\004' b='\C-q\004' value=${value//$a/$b} 267 | a=$'\005' b='\C-q\005' value=${value//$a/$b} 268 | a=$'\006' b='\C-q\006' value=${value//$a/$b} 269 | a=$'\007' b='\C-q\007' value=${value//$a/$b} 270 | a=$'\010' b='\C-q\010' value=${value//$a/$b} 271 | a=$'\011' b='\C-q\011' value=${value//$a/$b} 272 | a=$'\012' b='\C-q\012' value=${value//$a/$b} 273 | a=$'\013' b='\C-q\013' value=${value//$a/$b} 274 | a=$'\014' b='\C-q\014' value=${value//$a/$b} 275 | a=$'\015' b='\C-q\015' value=${value//$a/$b} 276 | a=$'\016' b='\C-q\016' value=${value//$a/$b} 277 | a=$'\017' b='\C-q\017' value=${value//$a/$b} 278 | a=$'\020' b='\C-q\020' value=${value//$a/$b} 279 | a=$'\021' b='\C-q\021' value=${value//$a/$b} 280 | a=$'\022' b='\C-q\022' value=${value//$a/$b} 281 | a=$'\023' b='\C-q\023' value=${value//$a/$b} 282 | a=$'\024' b='\C-q\024' value=${value//$a/$b} 283 | a=$'\025' b='\C-q\025' value=${value//$a/$b} 284 | a=$'\026' b='\C-q\026' value=${value//$a/$b} 285 | a=$'\027' b='\C-q\027' value=${value//$a/$b} 286 | a=$'\030' b='\C-q\030' value=${value//$a/$b} 287 | a=$'\031' b='\C-q\031' value=${value//$a/$b} 288 | a=$'\032' b='\C-q\032' value=${value//$a/$b} 289 | a=$'\033' b='\C-q\033' value=${value//$a/$b} 290 | a=$'\034' b='\C-q\034' value=${value//$a/$b} 291 | a=$'\035' b='\C-q\035' value=${value//$a/$b} 292 | a=$'\036' b='\C-q\036' value=${value//$a/$b} 293 | a=$'\037' b='\C-q\037' value=${value//$a/$b} 294 | fi 295 | _fzm_keyseq=$value 296 | } 297 | function _fzm-widget { 298 | local pwd=$PWD 299 | local FZF_MARKS_PASTE_COMMAND=_fzm_widget_untranslate_keyseq _fzm_keyseq= 300 | fzm 301 | 302 | if [[ $PWD != "$pwd" ]]; then 303 | # Force the prompt update 304 | _fzm_keyseq=' \C-b\C-k \C-u\C-m\C-y\C-?\e \C-y\ey\C-x\C-x\C-d'$_fzm_keyseq 305 | fi 306 | bind "\"$_fzm_key2\": \"$_fzm_keyseq\"" 307 | } 308 | fi 309 | 310 | # Widget for ble.sh 311 | function ble/widget/fzm { 312 | ble/widget/.hide-current-line 313 | ble/util/buffer.flush >&2 314 | 315 | local pwd=$PWD 316 | local FZF_MARKS_PASTE_COMMAND=ble/widget/insert-string 317 | fzm 318 | 319 | # Force the prompt update 320 | [[ $PWD != "$pwd" ]] && ble/prompt/clear 321 | } 322 | 323 | function _fzm_setup_bindings { 324 | local jump_key=${FZF_MARKS_JUMP:-'\C-g'} 325 | if ((${_ble_version:-0} >= 400)); then 326 | ble-bind -f keyseq:"$jump_key" 'fzm' 327 | else 328 | # Intiialize special keys used for key bindings 329 | _fzm_key1='\200' 330 | _fzm_key2='\201' 331 | _fzm_key3='\202' 332 | local locale=${LC_ALL:-${LC_CTYPE:-$LANG}} 333 | local rex_utf8='\.([uU][tT][fF]-?8)$' 334 | if [[ $locale =~ $rex_utf8 ]]; then 335 | # Change keys for UTF-8 encodings: 336 | # Two-byte sequence does not work for Bash 3 and 4. 337 | # \xC0-\xC1 and \xF5-\xFF are unused bytes in UTF-8. 338 | # Bash 4 unintendedly exits with \xFE-\xFF. 339 | _fzm_key1='\xC0' 340 | _fzm_key2='\xC1' 341 | _fzm_key3='\xFD' 342 | fi 343 | 344 | bind -x "\"$_fzm_key1\": _fzm-widget" 345 | bind "\"$jump_key\":\"$_fzm_key1$_fzm_key2\"" 346 | fi 347 | 348 | if [[ ${FZF_MARKS_DMARK-} ]]; then 349 | bind -x "\"${FZF_MARKS_DMARK}\": dmark" 350 | fi 351 | } 352 | 353 | _fzm_setup_bindings 354 | _fzm_setup_completion 355 | -------------------------------------------------------------------------------- /fzf-marks.plugin.zsh: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Urbain Vaes 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | command -v fzf >/dev/null 2>&1 || return 22 | 23 | if [[ -z ${FZF_MARKS_FILE-} ]] ; then 24 | FZF_MARKS_FILE=$HOME/.fzf-marks 25 | fi 26 | 27 | if [[ ! -f $FZF_MARKS_FILE ]]; then 28 | touch "$FZF_MARKS_FILE" 29 | fi 30 | 31 | if [[ -z ${FZF_MARKS_COMMAND-} ]] ; then 32 | 33 | _fzm_FZF_VERSION=$(fzf --version | awk -F. '{ print $1 * 1e6 + $2 * 1e3 + $3 }') 34 | _fzm_MINIMUM_VERSION=16001 35 | 36 | if [[ $_fzm_FZF_VERSION -gt $_fzm_MINIMUM_VERSION ]]; then 37 | FZF_MARKS_COMMAND="fzf --height 40% --reverse" 38 | elif [[ ${FZF_TMUX:-1} -eq 1 ]]; then 39 | FZF_MARKS_COMMAND="fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" 40 | else 41 | FZF_MARKS_COMMAND="fzf" 42 | fi 43 | fi 44 | 45 | function mark { 46 | local mark_to_add 47 | mark_to_add="${*:-$(basename "$(pwd)")} : $(pwd)" 48 | 49 | if grep -qxFe "${mark_to_add}" "${FZF_MARKS_FILE}"; then 50 | echo "** The following mark already exists **" 51 | else 52 | printf '%s\n' "${mark_to_add}" >> "${FZF_MARKS_FILE}" 53 | echo "** The following mark has been added **" 54 | fi 55 | _fzm_color_marks <<< $mark_to_add 56 | } 57 | 58 | function _fzm_handle_symlinks { 59 | local fname link 60 | if [ -L "${FZF_MARKS_FILE}" ]; then 61 | link=$(readlink "${FZF_MARKS_FILE}") 62 | case "$link" in 63 | /*) fname="$link";; 64 | *) fname="$(dirname "$FZF_MARKS_FILE")/$link";; 65 | esac 66 | else 67 | fname=${FZF_MARKS_FILE} 68 | fi 69 | printf '%s\n' "${fname}" 70 | } 71 | 72 | # Ensure precmds are run after cd 73 | function redraw-prompt { 74 | local precmd 75 | for precmd in $precmd_functions; do 76 | $precmd 77 | done 78 | zle reset-prompt 79 | } 80 | zle -N redraw-prompt 81 | 82 | function _fzm_color_marks { 83 | if [[ "${FZF_MARKS_NO_COLORS-}" == "1" ]]; then 84 | cat 85 | else 86 | local esc c_lhs c_rhs c_colon 87 | esc=$(printf '\033') 88 | c_lhs=${FZF_MARKS_COLOR_LHS:-39} 89 | c_rhs=${FZF_MARKS_COLOR_RHS:-36} 90 | c_colon=${FZF_MARKS_COLOR_COLON:-33} 91 | sed "s/^\\(.*\\) : \\(.*\\)$/${esc}[${c_lhs}m\\1${esc}[0m ${esc}[${c_colon}m:${esc}[0m ${esc}[${c_rhs}m\\2${esc}[0m/" 92 | fi 93 | } 94 | 95 | function _fzm_paste_command { 96 | local directory="$1" 97 | LBUFFER="${LBUFFER}$directory" 98 | zle reset-prompt > /dev/null 2>&1 99 | } 100 | 101 | function fzm { 102 | local delete_key=${FZF_MARKS_DELETE:-ctrl-d} paste_key=${FZF_MARKS_PASTE:-ctrl-v} 103 | local lines=$(_fzm_color_marks < "${FZF_MARKS_FILE}" | eval ${FZF_MARKS_COMMAND} \ 104 | --ansi \ 105 | --expect='"$delete_key,$paste_key"' \ 106 | --multi \ 107 | --bind=ctrl-y:accept,ctrl-t:toggle+down \ 108 | --header='"ctrl-y:jump, ctrl-t:toggle, $delete_key:delete, $paste_key:paste"' \ 109 | --query='"$*"' \ 110 | --select-1 \ 111 | --tac) 112 | if [[ -z "$lines" ]]; then 113 | zle && zle redraw-prompt 114 | return 1 115 | fi 116 | 117 | local key=$(head -1 <<< "$lines") 118 | 119 | if [[ $key == "$delete_key" ]]; then 120 | dmark "-->-->-->" "$(sed 1d <<< "$lines")" 121 | elif [[ $key == "$paste_key" || ! -t 1 ]]; then 122 | zle && local FZF_MARKS_PASTE_COMMAND=_fzm_paste_command 123 | pmark "-->-->-->" "$(tail -1 <<< "$lines")" 124 | else 125 | jump "-->-->-->" "$(tail -1 <<< "${lines}")" 126 | fi 127 | } 128 | 129 | function jump { 130 | local jumpline jumpdir bookmarks 131 | if [[ $1 == "-->-->-->" ]]; then 132 | jumpline=$2 133 | else 134 | jumpline=$(_fzm_color_marks < "${FZF_MARKS_FILE}" | eval ${FZF_MARKS_COMMAND} \ 135 | --ansi \ 136 | --bind=ctrl-y:accept \ 137 | --header='"ctrl-y:jump"' \ 138 | --query='"$*"' \ 139 | --select-1 \ 140 | --tac) 141 | fi 142 | if [[ -n ${jumpline} ]]; then 143 | jumpdir=$(sed 's/.*: \(.*\)$/\1/;'"s#^~#${HOME}#" <<< $jumpline) 144 | bookmarks=$(_fzm_handle_symlinks) 145 | cd "${jumpdir}" || return 146 | if [[ ! "${FZF_MARKS_KEEP_ORDER}" == 1 && -w ${FZF_MARKS_FILE} ]]; then 147 | perl -n -i -e "print unless /^\\Q${jumpline//\//\\/}\\E\$/" "${bookmarks}" 148 | printf '%s\n' "${jumpline}" >> "${FZF_MARKS_FILE}" 149 | fi 150 | fi 151 | zle && zle redraw-prompt 152 | } 153 | 154 | function pmark { 155 | local selected 156 | if [[ $1 == "-->-->-->" ]]; then 157 | selected=$2 158 | else 159 | selected=$(_fzm_color_marks < "${FZF_MARKS_FILE}" | eval ${FZF_MARKS_COMMAND} \ 160 | --ansi \ 161 | --bind=ctrl-y:accept --header='"ctrl-y:paste"' \ 162 | --query='"$*"' --select-1 --tac) 163 | fi 164 | if [[ $selected ]]; then 165 | selected=$(sed 's/.*: \(.*\)$/\1/;'"s#^~#${HOME}#" <<< "$selected") 166 | local paste_command=${FZF_MARKS_PASTE_COMMAND:-"printf '%s\n'"} 167 | eval -- "$paste_command \"\$selected\"" 168 | fi 169 | } 170 | 171 | function dmark { 172 | local marks_to_delete line bookmarks 173 | if [[ $1 == "-->-->-->" ]]; then 174 | marks_to_delete=$2 175 | else 176 | marks_to_delete=$(_fzm_color_marks < "${FZF_MARKS_FILE}" | eval ${FZF_MARKS_COMMAND} \ 177 | -m --ansi \ 178 | --bind=ctrl-y:accept,ctrl-t:toggle+down --header='"ctrl-y:delete, ctrl-t:toggle"' \ 179 | --query='"$*"' --tac) 180 | fi 181 | bookmarks=$(_fzm_handle_symlinks) 182 | 183 | if [[ -n ${marks_to_delete} ]]; then 184 | while IFS='' read -r line; do 185 | perl -n -i -e "print unless /^\\Q${line//\//\\/}\\E\$/" "${bookmarks}" 186 | done <<< "$marks_to_delete" 187 | 188 | [[ $(wc -l <<< "${marks_to_delete}") == 1 ]] \ 189 | && echo "** The following mark has been deleted **" \ 190 | || echo "** The following marks have been deleted **" 191 | _fzm_color_marks <<< $marks_to_delete 192 | fi 193 | zle && zle reset-prompt 194 | } 195 | 196 | zle -N dmark 197 | zle -N fzm 198 | 199 | bindkey ${FZF_MARKS_JUMP:-'^g'} fzm 200 | if [ "${FZF_MARKS_DMARK-}" ]; then 201 | bindkey ${FZF_MARKS_DMARK} dmark 202 | fi 203 | 204 | command -v compdef >/dev/null 2>&1 || return 205 | # Completion: for documentation, see e.g. 206 | # https://mads-hartmann.com/2017/08/06/writing-zsh-completion-scripts.html 207 | # https://github.com/zsh-users/zsh-completions/blob/master/zsh-completions-howto.org#user-content-actions 208 | function _fzm { 209 | _arguments -C \ 210 | "1: :(($(sed "s/\\(.*\\) : \\(.*\\)/'\1'\\\\:'\2'/" < "$FZF_MARKS_FILE")))" \ 211 | } 212 | 213 | compdef _fzm fzm 214 | -------------------------------------------------------------------------------- /init.zsh: -------------------------------------------------------------------------------- 1 | fzf-marks.plugin.zsh --------------------------------------------------------------------------------