# Emacs-based bookmark system # Enable color utilities autoload colors && colors autoload regexp-replace __bm_bookmark_cache=() 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 } function __bm_update_bookmark_list { local quoted_output 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') if ! [[ -v BM_BOOKMARK_PATH ]]; then local BM_BOOKMARK_PATH="$(__bm_find_user_emacs_dir)" fi 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 } 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 printf '%s\n%d\n%s\n' "${__bm_bookmark_cache[${i} + 2]}" \ "${__bm_bookmark_cache[${i} + 3]}" "${rest}" return 0 fi done printf '%s\n' "${bm_name}" 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 -S '/' "${__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 loc=(${(f)"$(__bm_bookmark_location "${1}")"}) if [[ -d "${loc[1]}" ]]; then cd "${loc[1]}" if ! [[ -z "${loc[3]}" ]]; then if ! [[ -d "${loc[3]}" ]]; then cd - >/dev/null printf 'Bookmark exists, but trailing path doesn''t: "%s"\n' \ "${loc[3]}" return 1 fi cd "${loc[3]}" fi [[ "${BM_CWD_LS}" = '1' ]] && ls || true elif [[ -e "${loc[1]}" ]]; then ${=EDITOR} "${loc[1]}" else printf 'No such bookmark: "%s"\n' "${loc[1]}" return 1 fi } function _bm { local arg="${words[${CURRENT}]}" if ! [[ "${arg}" == */* ]]; then _arguments '1::bookmark:_bookmarks' else local loc=(${(f)"$(__bm_bookmark_location "${arg}")"}) if [[ -d "${loc[1]}" ]]; then local parts=(${(s:/:)loc[3]}) local bm="${${(s:/:)${arg}}[1]}" local subdir="${(j:/:)parts[1,${#parts}-1]}" local search="${parts[${#parts}]}" if ((${#parts} > 0)) && [[ "${arg}" == */ ]]; then subdir="${subdir}/${search}" subdir="${subdir#/}" search="" fi for file in "${loc[1]}/${subdir}/${search}"*(/); do local clean_file="${bm}/${file#"${loc[1]}"}" regexp-replace clean_file '/+' '/' compadd -U -S '/' "${clean_file}" 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] ' "${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' "${name}" \ || { printf '%s\n' "${res}"; return 1 } } 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] ' "${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' "${1}" \ || { printf '%s\n' "${res}"; return 1 } else printf '\n' return 1 fi } function _bmrm { _arguments ':bookmark:_bookmarks' } compdef _bmrm bmrm __bm_update_bookmark_list