zsh-config/emacs-bookmark.zsh

277 lines
8.8 KiB
Bash

# Emacs-based bookmark system
# Enable color utilities
autoload colors && colors
autoload regexp-replace
zmodload -F zsh/stat b:zstat
zmodload zsh/datetime
local __bm_bookmark_cache=()
local __bm_res=()
let __bm_last_read_time=-1
function __bm_find_user_emacs_dir {
[[ -f "${HOME}/.emacs.d/var/bookmark-default.el" ]] &&
{ printf '%s' "${HOME}/.emacs.d/var/bookmark-default.el"; return 0 }
[[ -f "${HOME}/.emacs.d/bookmark-default.el" ]] &&
{ printf '%s' "${HOME}/.emacs.d/bookmark-default.el"; return 0 }
[[ -f "${HOME}/.config/emacs/var/bookmark-default.el" ]] &&
{ printf '%s' "${HOME}/.config/emacs/var/bookmark-default.el"; return 0 }
[[ -f "${HOME}/.config/emacs/bookmark-default.el" ]] &&
{ printf '%s' "${HOME}/.config/emacs/bookmark-default.el"; return 0 }
[[ -f "${XDG_CONFIG_HOME}/emacs/var/bookmark-default.el" ]] &&
{ printf '%s' "${XDG_CONFIG_HOME}/emacs/var/bookmark-default.el"; return 0 }
[[ -f "${XDG_CONFIG_HOME}/emacs/bookmark-default.el" ]] &&
{ printf '%s' "${XDG_CONFIG_HOME}/emacs/bookmark-default.el"; return 0 }
printf 'Could not discover Emacs bookmark file! Please set $BM_MODE to
"daemon" or define $BM_BOOKMARK_PATH!\n'
return 1
}
BM_BOOKMARK_PATH="${BM_BOOKMARK_PATH:-"$(__bm_find_user_emacs_dir)"}"
function __bm_hash_dirs {
# First empty the hash table
hash -dr
# Then add the hash dirs
for ((i = 1; i < ${#__bm_bookmark_cache}; i+=4)); do
local name="${__bm_bookmark_cache[${i}]://=/}"
hash -d "${name}=${__bm_bookmark_cache[${i} + 2]}"
done
}
function __bm_update_bookmark_list {
local quoted_output
__bm_last_read_time="${EPOCHSECONDS}"
case "${BM_MODE}" in
'daemon')
quoted_output=(${(z)${"$(command emacs -Q --batch --eval \
"(prin1
(progn
(require 'server)
(server-eval-at \"server\"
'(let ((out))
(dolist (entry bookmark-alist out)
(let ((path (alist-get 'filename (cdr entry) ""))
(pos (alist-get 'position (cdr entry) 1)))
(setq out (append (list (car entry) path
(expand-file-name path) pos)
out))))))))")":1:-1}})
;;
''|'emacs')
quoted_output=(${(z)${"$(command emacs -Q --batch --eval \
"(prin1
(with-temp-buffer
(insert-file-contents \"${BM_BOOKMARK_PATH:gs#\\#\\\\#:gs#\"#\\\"#}\")
(require 'cl-lib)
(let ((out))
(dolist (entry (read (current-buffer)) out)
(let ((path (alist-get 'filename (cdr entry) ""))
(pos (alist-get 'position (cdr entry) 1)))
(setq out (append (list (car entry) path
(expand-file-name path) pos)
out)))))))))")":1:-1}})
;;
*)
printf 'Unknown value for $BM_MODE: "%s"\n' "${BM_MODE}"
return 1
;;
esac
__bm_bookmark_cache=()
for entry in ${quoted_output}; do
__bm_bookmark_cache+="${(Q)entry}"
done
__bm_hash_dirs
}
function __bm_bookmark_location {
local parts=(${(s:/:)"${1}"})
local bm_name="${parts[1]}"
local rest_arr=(${parts:1})
local rest="${(j:/:)rest_arr}"
for ((i = 1; i < ${#__bm_bookmark_cache}; i+=4)); do
if [[ "${bm_name}" = "${__bm_bookmark_cache[${i}]}" ]]; then
__bm_res=("${__bm_bookmark_cache[${i} + 2]}"
"${__bm_bookmark_cache[${i} + 3]}"
"${rest}")
return 0
fi
done
__bm_res=()
return 1
}
function __bm_list_bookmarks {
for ((i = 1; i < ${#__bm_bookmark_cache}; i+=4)); do
local name="${__bm_bookmark_cache[${i}]}"
local pretty_path="${__bm_bookmark_cache[${i} + 1]}"
local abs_path="${__bm_bookmark_cache[${i} + 2]}"
local print_color=""
if [[ -d "${abs_path}" ]]; then
print_color="${bold_color}${fg[blue]}"
fi
printf "${print_color}%s${reset_color} => %s\n" \
"${name}" "${pretty_path}"
done
}
function _bookmarks {
for ((i = 1; i < ${#__bm_bookmark_cache}; i+=4)); do
compadd -q "${__bm_bookmark_cache[${i}]}"
done
}
alias lsbm="__bm_update_bookmark_list && __bm_list_bookmarks"
function bm {
__bm_update_bookmark_list || \
{ printf 'Updating bookmark list failed!\n'; return 1 }
(( ${#} == 0 )) && { __bm_list_bookmarks; return }
__bm_bookmark_location "${1}"
local bm_loc="${__bm_res[1]}"
local target="${__bm_res[1]}/${__bm_res[3]}"
if (( ${#__bm_res} != 0 )) && [[ -e "${bm_loc}" ]]; then
if [[ -d "${target}" ]]; then
cd "${target}"
(( "${BM_CWD_LS:-0}" )) && ls || true
elif [[ -e "${target}" ]]; then
${=EDITOR} "${target}"
else
printf 'Bookmark exists, but trailing path doesn'"'"'t: "%s"\n' \
"${(q)__bm_res[3]}"
return 1
fi
else
printf 'No such bookmark: "%s"\n' "${(q)1}"
return 1
fi
}
function _bm {
(( "${CURRENT}" == 2 )) || return
local arg="${(Q)words[${CURRENT}]}"
if ! [[ "${arg}" == */* ]]; then
for ((i = 1; i < ${#__bm_bookmark_cache}; i+=4)); do
compadd -q -S '/' "${__bm_bookmark_cache[${i}]}"
done
printf '' # I don't know why, but this fixes things
else
__bm_bookmark_location "${arg}"
if [[ -d "${__bm_res[1]}" ]]; then
local parts=(${(s:/:)__bm_res[3]})
local bm="${${(s:/:)${arg}}[1]}"
local subdir="${(j:/:)parts[1,-2]}"
local search="${parts[${#parts}]}"
if ((${#parts} > 0)) && [[ "${arg}" == */ ]]; then
subdir="${subdir}/${search}"
subdir="${subdir#/}"
search=""
fi
local pre_path="${__bm_res[1]}/${subdir}/"
local raw_arg="${words[${CURRENT}]}"
local prefix="${${(s:/:)${raw_arg}}[1]}/${subdir}"
if ! [[ -z "${subdir}" ]]; then
prefix+='/'
fi
compset -P "${(b)prefix}"
for file in "${pre_path}"*; do
compadd -W "${pre_path}" -f "${file:t}"
done
fi
fi
}
compdef _bm bm
function bmadd {
if [[ "${1}" = '-h' ]]; then
printf 'usage: bmadd [PATH] [NAME]\n'
return 0
fi
[[ "${1}" = '--' ]] && shift 1
local name
local loc
case "${#}" in
0)
loc="${PWD}"
name="${PWD:t}"
;;
1)
loc="${1}"
name="${1:t}"
;;
*)
loc="${1}"
name="${2}"
;;
esac
__bm_update_bookmark_list
if __bm_bookmark_location "${name}" >/dev/null; then
printf 'Bookmark "%s" already exists. Overwrite it? [y/N] ' "${(q)name}"
read -q
let ans=${?}
printf '\n'
(( ${ans} != 0 )) && return 1
fi
local res="$(emacsclient --eval \
"(let* ((loc \"${loc:gs#\\#\\\\#:gs#\"#\\\"#}\")
(name \"${name:gs#\\#\\\\#:gs#\"#\\\"#}\")
(res (with-temp-buffer
(set-visited-file-name loc t nil)
(bookmark-set name)
(set-buffer-modified-p nil)))
(inhibit-message t))
(bookmark-save)
res)")"
[[ "${res}" = 'nil' ]] && printf 'Added bookmark "%s"\n' "${(q)name}" \
|| { printf '%s\n' "${res}"; return 1 }
__bm_update_bookmark_list
}
function _bmadd {
_arguments ':file:_files' ':name'
}
compdef _bmadd bmadd
function bmrm {
if [[ "${1}" = '-h' ]]; then
printf 'usage: bmrm [NAME]\n'
return 0
fi
[[ "${1}" = '--' ]] && shift 1
if (( ${#} < 1 )); then
printf 'usage: bmrm [NAME]\n'
return 1
fi
__bm_update_bookmark_list
__bm_bookmark_location "${1}" >/dev/null || \
{ printf 'No such bookmark: "%s"\n' "${1}"; return 1 }
printf 'Really delete "%s"? [y/N] ' "${(q)1}"
if read -q; then
printf '\n'
local res="$(emacsclient --eval \
"(let* ((res (bookmark-delete \"${1:gs#\\#\\\\#:gs#\"#\\\"#}\"))
(inhibit-message t))
(bookmark-save)
res)")"
[[ "${res}" = 'nil' ]] && printf 'Deleted bookmark "%s"\n' "${(q)1}" \
|| { printf '%s\n' "${res}"; return 1 }
__bm_update_bookmark_list
else
printf '\n'
return 1
fi
}
function _bmrm {
_arguments ':bookmark:_bookmarks'
}
compdef _bmrm bmrm
function __bm_precmd_hook {
# Auto reload
if (( "${BM_AUTO_RELOAD:-0}" )) &&
(( ${__bm_last_read_time} < "$(zstat +mtime "${BM_BOOKMARK_PATH}")" )); then
__bm_update_bookmark_list
fi
}
(( ${precmd_functions[(I)__bm_precmd_hook]} )) ||
precmd_functions+=(__bm_precmd_hook)
__bm_update_bookmark_list