/
bookmarks.zsh
118 lines (112 loc) · 4.17 KB
/
bookmarks.zsh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# -*- sh -*-
# Handle bookmarks. This uses the dynamic named directories feature of
# zsh. When you refer to ~[...] during a file expansion, the `...` is
# proposed to some function to be resolved. This is like tilde
# expansion but you can plug the resolution in a function. This also
# works to resolve a directory name to a shorten name.
#
# So, we can jump to a bookmark with `cd ~[@bookmark]`. Prompt
# expansion is also aware of those bookmarks. The prompt should show
# the bookmark name. And we get completion.
#
# With autocd, you can just type `~[@bookmark]`. Since this can be
# cumbersome to type, you can also type `@@` and this will be turned
# into `~[@` by ZLE.
is-at-least 4.3.12 && __() {
MARKPATH=$ZSH/run/marks
_bookmark_directory_name() {
emulate -L zsh
setopt extendedglob
case $1 in
d)
# Turn the directory into a shortest name using
# bookmarks. We need to sort them by length of solved
# path.
local link slink
local -A links
local cache=$ZSH/run/bookmarks-$HOST-$UID
if [[ -f $cache ]] && [[ $MARKPATH -ot $cache ]]; then
. $cache
else
for link ($MARKPATH/*(N@)) links[${#link:A}$'\0'${link:A}]=${link:t}
print -r "links=( ${(kv@)^^links} )" > $cache
fi
for slink (${(@On)${(k)links}}) {
link=${slink#*$'\0'}
if [[ $2 = (#b)(${link})(|/*) ]]; then
typeset -ga reply
reply=("@"${links[$slink]} $(( ${#match[1]} )) )
return 0
fi
}
return 1
;;
n)
# Turn the name into a directory
[[ $2 != (#b)"@"(?*) ]] && return 1
typeset -ga reply
reply=(${${:-$MARKPATH/$match[1]}:A})
return 0
;;
c)
# Completion
local expl
local -a dirs
dirs=($MARKPATH/*(N@:t))
dirs=("@"${^dirs})
vbe-remove-slash-after-bookmark () {
case $KEYS in
'/'|' '|$'\n'|$'\r')
LBUFFER="${LBUFFER[0,-2]}"
;;
esac
}
_wanted dynamic-dirs expl 'bookmarked directory' compadd -S\]/ -R vbe-remove-slash-after-bookmark -a dirs
return
;;
*)
return 1
;;
esac
return 0
}
add-zsh-hook zsh_directory_name _bookmark_directory_name
vbe-insert-bookmark() {
emulate -L zsh
LBUFFER=${LBUFFER}"~[@"
}
zle -N vbe-insert-bookmark
bindkey '@@' vbe-insert-bookmark
# Manage bookmarks
bookmark() {
[[ -d $MARKPATH ]] || mkdir -p $MARKPATH
if (( $# == 0 )); then
# When no arguments are provided, just display existing
# bookmarks
for link in $MARKPATH/*(N@); do
local markname="$fg[green]${link:t}$reset_color"
local markpath="$fg[blue]${link:A}$reset_color"
printf "%-30s -> %s\n" $markname $markpath
done
else
# Otherwise, we may want to add a bookmark or delete an
# existing one.
local -a delete
zparseopts -D d=delete
if (( $+delete[1] )); then
# With `-d`, we delete an existing bookmark
command rm $MARKPATH/$1
else
# Otherwise, add a bookmark to the current
# directory. The first argument is the bookmark
# name. `.` is special and means the bookmark should
# be named after the current directory.
local name=$1
[[ $name == "." ]] && name=${PWD:t}
ln -s $PWD $MARKPATH/$name
fi
# Clean up the cache
command rm $ZSH/run/bookmarks-$HOST-$UID
fi
}
} && __