org-capture: Fix freeze when capture templates are ill-defined

* lisp/org-capture.el (org-mks): Do not freeze when there is a missing
  step in the key hierarchy.  Fix docstring.  Refactor code for clarity.

Reported-by: Roland Everaert <reveatwork@gmail.com>
<http://permalink.gmane.org/gmane.emacs.orgmode/111716>
This commit is contained in:
Nicolas Goaziou 2017-01-30 23:36:52 +01:00
parent f2a5104e45
commit 8194e7b09d
1 changed files with 68 additions and 79 deletions

View File

@ -1437,6 +1437,7 @@ Use PREFIX as a prefix for the name of the indirect buffer."
(defun org-mks (table title &optional prompt specials) (defun org-mks (table title &optional prompt specials)
"Select a member of an alist with multiple keys. "Select a member of an alist with multiple keys.
TABLE is the alist which should contain entries where the car is a string. TABLE is the alist which should contain entries where the car is a string.
There should be two types of entries. There should be two types of entries.
@ -1444,7 +1445,7 @@ There should be two types of entries.
This indicates that `a' is a prefix key for multi-letter selection, and This indicates that `a' is a prefix key for multi-letter selection, and
that there are entries following with keys like \"ab\", \"ax\"... that there are entries following with keys like \"ab\", \"ax\"...
2. Selectable members must have more than two elements, with the first 2. Select-able members must have more than two elements, with the first
being the string of keys that lead to selecting it, and the second a being the string of keys that lead to selecting it, and the second a
short description string of the item. short description string of the item.
@ -1455,84 +1456,72 @@ When you press a prefix key, the commands (and maybe further prefixes)
under this key will be shown and offered for selection. under this key will be shown and offered for selection.
TITLE will be placed over the selection in the temporary buffer, TITLE will be placed over the selection in the temporary buffer,
PROMPT will be used when prompting for a key. SPECIAL is an alist with PROMPT will be used when prompting for a key. SPECIAL is an
also (\"key\" \"description\") entries. When one of these is selection, alist with (\"key\" \"description\") entries. When one of these
only the bare key is returned." is selected, only the bare key is returned."
(setq prompt (or prompt "Select: "))
(let (tbl orig-table dkey ddesc des-keys allowed-keys
current prefix rtn re pressed buffer (inhibit-quit t))
(save-window-excursion (save-window-excursion
(setq buffer (org-switch-to-buffer-other-window "*Org Select*")) (let ((inhibit-quit t)
(setq orig-table table) (buffer (org-switch-to-buffer-other-window "*Org Select*"))
(prompt (or prompt "Select: "))
current)
(unwind-protect
(catch 'exit (catch 'exit
(while t (while t
(erase-buffer) (erase-buffer)
(insert title "\n\n") (insert title "\n\n")
(setq tbl table (let ((des-keys nil)
des-keys nil (allowed-keys '("\C-g"))
allowed-keys nil (cursor-type nil))
cursor-type nil) ;; Populate allowed keys and descriptions keys
(setq prefix (if current (concat current " ") "")) ;; available with CURRENT selector.
(while tbl (let ((re (format "\\`%s\\(.\\)\\'"
(cond (if current (regexp-quote current) "")))
((and (= 2 (length (car tbl))) (= (length (caar tbl)) 1)) (prefix (if current (concat current " ") "")))
;; This is a description on this level (dolist (entry table)
(setq dkey (caar tbl) ddesc (cl-cadar tbl)) (pcase entry
(pop tbl) ;; Description.
(push dkey des-keys) (`(,(and key (pred (string-match re))) ,desc)
(push dkey allowed-keys) (let ((k (match-string 1 key)))
(insert prefix "[" dkey "]" "..." " " ddesc "..." "\n") (push k des-keys)
;; Skip keys which are below this prefix (push k allowed-keys)
(setq re (concat "\\`" (regexp-quote dkey))) (insert prefix "[" k "]" "..." " " desc "..." "\n")))
(let (case-fold-search) ;; Usable entry.
(while (and tbl (string-match re (caar tbl))) (pop tbl)))) (`(,(and key (pred (string-match re))) ,desc . ,_)
((= 2 (length (car tbl))) (let ((k (match-string 1 key)))
;; Not yet a usable description, skip it (insert prefix "[" k "]" " " desc "\n")
) (push k allowed-keys)))
(t (_ nil))))
;; usable entry on this level ;; Insert special entries, if any.
(insert prefix "[" (caar tbl) "]" " " (nth 1 (car tbl)) "\n")
(push (caar tbl) allowed-keys)
(pop tbl))))
(when specials (when specials
(insert "-------------------------------------------------------------------------------\n") (insert "----------------------------------------------------\
(let ((sp specials)) ---------------------------\n")
(while sp (pcase-dolist (`(,key ,description) specials)
(insert (format "[%s] %s\n" (insert (format "[%s] %s\n" key description))
(caar sp) (nth 1 (car sp)))) (push key allowed-keys)))
(push (caar sp) allowed-keys) ;; Display UI and let user select an entry or
(pop sp)))) ;; a sub-level prefix.
(push "\C-g" allowed-keys)
(goto-char (point-min)) (goto-char (point-min))
(if (not (pos-visible-in-window-p (point-max))) (unless (pos-visible-in-window-p (point-max))
(org-fit-window-to-buffer)) (org-fit-window-to-buffer))
(message prompt) (message prompt)
(setq pressed (char-to-string (read-char-exclusive))) (let ((pressed (char-to-string (read-char-exclusive))))
(while (not (member pressed allowed-keys)) (while (not (member pressed allowed-keys))
(message "Invalid key `%s'" pressed) (sit-for 1) (message "Invalid key `%s'" pressed) (sit-for 1)
(message prompt) (message prompt)
(setq pressed (char-to-string (read-char-exclusive)))) (setq pressed (char-to-string (read-char-exclusive))))
(when (equal pressed "\C-g") (cond
(kill-buffer buffer) ((equal pressed "\C-g") (user-error "Abort"))
(user-error "Abort")) ;; Selection is a prefix: open a new menu.
(when (and (not (assoc pressed table)) ((member pressed des-keys)
(not (member pressed des-keys)) (setq current (concat current pressed)))
(assoc pressed specials)) ;; Selection matches an association: return it.
(throw 'exit (setq rtn pressed))) ((let ((entry (assoc pressed table)))
(unless (member pressed des-keys) (and entry (throw 'exit entry))))
(throw 'exit (setq rtn (rassoc (cdr (assoc pressed table)) ;; Selection matches a special entry: return the
orig-table)))) ;; selection prefix.
(setq current (concat current pressed)) ((assoc pressed specials) (throw 'exit pressed))
(setq table (mapcar (t (error "No entry available")))))))
(lambda (x) (when buffer (kill-buffer buffer))))))
(if (and (> (length (car x)) 1)
(equal (substring (car x) 0 1) pressed))
(cons (substring (car x) 1) (cdr x))
nil))
table))
(setq table (remove nil table)))))
(when buffer (kill-buffer buffer))
rtn))
;;; The template code ;;; The template code
(defun org-capture-select-template (&optional keys) (defun org-capture-select-template (&optional keys)