301 lines
9.1 KiB
Bash
301 lines
9.1 KiB
Bash
# Emacs-based bookmark system
|
|
|
|
autoload colors && colors
|
|
zmodload -F zsh/stat b:zstat
|
|
zmodload zsh/datetime
|
|
zmodload zsh/mapfile
|
|
|
|
local __bm_bookmark_cache=()
|
|
let __bm_last_read_time=-1
|
|
|
|
function __bm_offset_to_row_col {
|
|
let off="${2}"
|
|
let row=1
|
|
let col=0
|
|
for line in "${(@f)mapfile[${1}]}"; do
|
|
let len="${#line}"
|
|
if (( off > len )); then
|
|
off='off - (len + 1)'
|
|
if (( off <= 0 )); then
|
|
(( len == 0 )) && col=0 || col='len - 1'
|
|
break
|
|
fi
|
|
row+=1
|
|
else
|
|
col='off'
|
|
off=0
|
|
break;
|
|
fi
|
|
done <"${1}"
|
|
if (( off > 0 )); then
|
|
row+=-1
|
|
fi
|
|
printf '%d\0%d' "${row}" "${col}"
|
|
}
|
|
|
|
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 {
|
|
__bm_last_read_time="${EPOCHSECONDS}"
|
|
local args
|
|
local script
|
|
case "${BM_MODE}" in
|
|
'daemon')
|
|
script=$(<<'EOF'
|
|
(progn
|
|
(require 'server)
|
|
(dolist (entry (server-eval-at
|
|
"server"
|
|
'(when (boundp 'bookmark-alist)
|
|
bookmark-alist)))
|
|
(let ((path (alist-get 'filename (cdr entry) ""))
|
|
(pos (alist-get 'position (cdr entry) 1)))
|
|
(princ (format "%s\0%s\0%s\0%s\0" (car entry) path
|
|
(expand-file-name path) pos)))))
|
|
EOF
|
|
)
|
|
;;
|
|
''|'emacs')
|
|
args="--insert=${BM_BOOKMARK_PATH}"
|
|
script=$(<<'EOF'
|
|
(progn
|
|
(dolist (entry (read (current-buffer)))
|
|
(let ((path (alist-get 'filename (cdr entry) ""))
|
|
(pos (alist-get 'position (cdr entry) 1)))
|
|
(princ (format "%s\0%s\0%s\0%s\0" (car entry) path
|
|
(expand-file-name path) pos)))))
|
|
EOF
|
|
)
|
|
;;
|
|
*)
|
|
printf 'Unknown value for $BM_MODE: "%s"\n' "${BM_MODE}"
|
|
return 1
|
|
;;
|
|
esac
|
|
__bm_bookmark_cache=(${(0)"$(command emacs --batch ${args} --eval "${script}")"})
|
|
__bm_hash_dirs
|
|
}
|
|
function __bm_bookmark_location {
|
|
local parts=(${(s:/:)"${2}"})
|
|
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 }
|
|
local __bm_res
|
|
__bm_bookmark_location __bm_res "${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 "${bm_loc}" ]]; then
|
|
let offset="${__bm_res[2]}"
|
|
local rowcol=(${(0)"$(__bm_offset_to_row_col "${bm_loc}" "${offset}")"})
|
|
${=EDITOR} "+${rowcol[1]}:${rowcol[2]}" "${bm_loc}"
|
|
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
|
|
else
|
|
local __bm_res
|
|
__bm_bookmark_location __bm_res "${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
|
|
local __bm_res
|
|
if __bm_bookmark_location __bm_res "${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
|
|
local __bm_res
|
|
__bm_bookmark_location __bm_res "${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
|