* lisp/help.el: Rework describe-key's handling of up and double clicks

Use lexical-binding.
(help--binding-undefined-p): New function, extracted from help--analyze-key.
(help--analyze-key): Use it.
(help--filter-info-list): New function.
(describe-key-briefly): Change calling convention.
Handle a list of key sequences now.
(help--binding-locus): Remove unused var 'found'.
(help--read-key-sequence): Rename from help-read-key-sequence.
Almost complete rewrite, with a different return value.
(help-downify-mouse-event-type): Remove.
(describe-key): Change calling convention.
Handle a list of key sequences now.
This commit is contained in:
Stefan Monnier 2018-01-30 11:57:40 -05:00
parent fd6972ac07
commit 9d4af3e6bd

View file

@ -1,4 +1,4 @@
;;; help.el --- help commands for Emacs
;;; help.el --- help commands for Emacs -*- lexical-binding:t -*-
;; Copyright (C) 1985-1986, 1993-1994, 1998-2018 Free Software
;; Foundation, Inc.
@ -593,19 +593,27 @@ If INSERT (the prefix arg) is non-nil, insert the message in the buffer."
string
(format "%s (translated from %s)" string otherstring))))))
(defun help--binding-undefined-p (defn)
(or (null defn) (integerp defn) (equal defn 'undefined)))
(defun help--analyze-key (key untranslated)
"Get information about KEY its corresponding UNTRANSLATED events.
Returns a list of the form (BRIEF-DESC DEFN EVENT MOUSE-MSG)."
(if (numberp untranslated)
(setq untranslated (this-single-command-raw-keys)))
(let* ((event (aref key (if (and (symbolp (aref key 0))
(> (length key) 1)
(consp (aref key 1)))
1
0)))
(error "Missing `untranslated'!"))
(let* ((event (when (> (length key) 0)
(aref key (if (and (symbolp (aref key 0))
(> (length key) 1)
(consp (aref key 1)))
;; Look at the second event when the first
;; is a pseudo-event like `mode-line' or
;; `left-fringe'.
1
0))))
(modifiers (event-modifiers event))
(mouse-msg (if (or (memq 'click modifiers) (memq 'down modifiers)
(memq 'drag modifiers)) " at that spot" ""))
(memq 'drag modifiers))
" at that spot" ""))
(defn (key-binding key t)))
;; Handle the case where we faked an entry in "Select and Paste" menu.
(when (and (eq defn nil)
@ -621,27 +629,47 @@ Returns a list of the form (BRIEF-DESC DEFN EVENT MOUSE-MSG)."
(list
;; Now describe the key, perhaps as changed.
(let ((key-desc (help-key-description key untranslated)))
(if (or (null defn) (integerp defn) (equal defn 'undefined))
(if (help--binding-undefined-p defn)
(format "%s%s is undefined" key-desc mouse-msg)
(format "%s%s runs the command %S" key-desc mouse-msg defn)))
defn event mouse-msg)))
(defun describe-key-briefly (&optional key insert untranslated)
"Print the name of the function KEY invokes. KEY is a string.
If INSERT (the prefix arg) is non-nil, insert the message in the buffer.
If non-nil, UNTRANSLATED is a vector of the untranslated events.
It can also be a number in which case the untranslated events from
the last key hit are used.
(defun help--filter-info-list (info-list i)
"Drop the undefined keys."
(or
;; Remove all `undefined' keys.
(delq nil (mapcar (lambda (x)
(unless (help--binding-undefined-p (nth i x)) x))
info-list))
;; If nothing left, then keep one (the last one).
(last info-list)))
If KEY is a menu item or a tool-bar button that is disabled, this command
temporarily enables it to allow getting help on disabled items and buttons."
(defun describe-key-briefly (&optional key-list insert untranslated)
"Print the name of the functions KEY-LIST invokes.
KEY-LIST is a list of pairs (SEQ . RAW-SEQ) of key sequences, where
RAW-SEQ is the untranslated form of the key sequence SEQ.
If INSERT (the prefix arg) is non-nil, insert the message in the buffer.
While reading KEY-LIST interactively, this command temporarily enables
menu items or tool-bar buttons that are disabled to allow getting help
on them."
(declare (advertised-calling-convention (key-list &optional insert) "27.1"))
(interactive
;; Ignore mouse movement events because it's too easy to miss the
;; message while moving the mouse.
(pcase-let ((`(,key ,_up-event) (help-read-key-sequence 'no-mouse-movement)))
`(,key ,current-prefix-arg 1)))
(princ (car (help--analyze-key key untranslated))
(if insert (current-buffer) standard-output)))
(let ((key-list (help--read-key-sequence 'no-mouse-movement)))
`(,key-list ,current-prefix-arg)))
(when (arrayp key-list)
;; Old calling convention, changed
(setq key-list (list (cons key-list
(if (numberp untranslated)
(this-single-command-raw-keys)
untranslated)))))
(let* ((info-list (mapcar (lambda (kr)
(help--analyze-key (car kr) (cdr kr)))
key-list))
(msg (mapconcat #'car (help--filter-info-list info-list 1) "\n")))
(if insert (insert msg) (message "%s" msg))))
(defun help--key-binding-keymap (key &optional accept-default no-remap position)
"Return a keymap holding a binding for KEY within current keymaps.
@ -688,8 +716,7 @@ function `key-binding'."
(format "%s-map" mode)))))
minor-mode-map-alist))
(list 'global-map
(intern-soft (format "%s-map" major-mode)))))
found)
(intern-soft (format "%s-map" major-mode))))))
;; Look into these advertised symbols first.
(dolist (sym advertised-syms)
(when (and
@ -706,225 +733,137 @@ function `key-binding'."
(throw 'found x))))
nil)))))
(defun help-read-key-sequence (&optional no-mouse-movement)
"Reads a key sequence from the user.
Returns a list of the form (KEY UP-EVENT), where KEY is the key
sequence, and UP-EVENT is the up-event that was discarded by
reading KEY, or nil.
(defun help--read-key-sequence (&optional no-mouse-movement)
"Read a key sequence from the user.
Usually reads a single key sequence, except when that sequence might
hide another one (e.g. a down event, where the user is interested
in getting info about the up event, or a click event, where the user
wants to get info about the double click).
Return a list of elements of the form (SEQ . RAW-SEQ), where SEQ is a key
sequence, and RAW-SEQ is its untranslated form.
If NO-MOUSE-MOVEMENT is non-nil, ignore key sequences starting
with `mouse-movement' events."
(let ((enable-disabled-menus-and-buttons t)
(cursor-in-echo-area t)
saved-yank-menu)
(unwind-protect
(let (key keys down-ev discarded-up)
(let (last-modifiers key-list)
;; If yank-menu is empty, populate it temporarily, so that
;; "Select and Paste" menu can generate a complete event.
(when (null (cdr yank-menu))
(setq saved-yank-menu (copy-sequence yank-menu))
(menu-bar-update-yank-menu "(any string)" nil))
(while
(pcase (setq key (read-key-sequence "\
;; Read at least one key-sequence.
(or (null key-list)
;; After a down event, also read the (presumably) following
;; up-event.
(memq 'down last-modifiers)
;; After a click, see if a double click is on the way.
(and (memq 'click last-modifiers)
(not (sit-for (/ double-click-time 1000.0) t))))
(let* ((seq (read-key-sequence "\
Describe the following key, mouse click, or menu item: "))
((and (pred vectorp) (let `(,key0 . ,_) (aref key 0))
(guard (symbolp key0)) (let keyname (symbol-name key0)))
(or
(and no-mouse-movement
(string-match "mouse-movement" keyname))
(progn (push key keys) nil)
(and (string-match "\\(mouse\\|down\\|click\\|drag\\)"
keyname)
(progn
;; Discard events (e.g. <help-echo>) which might
;; spuriously trigger the `sit-for'.
(sleep-for 0.01)
(while (read-event nil nil 0.01))
(not (sit-for
(if (numberp double-click-time)
(/ double-click-time 1000.0)
3.0)
t))))))))
;; When we have a sequence of mouse events, discard the most
;; recent ones till we find one with a binding.
(let ((keys-1 keys))
(while (and keys-1
(not (key-binding (car keys-1))))
;; If we discard the last event, and this was a mouse
;; up, remember this.
(if (and (eq keys-1 keys)
(vectorp (car keys-1))
(let* ((last-idx (1- (length (car keys-1))))
(last (aref (car keys-1) last-idx)))
(and (eventp last)
(memq 'click (event-modifiers last)))))
(setq discarded-up t))
(setq keys-1 (cdr keys-1)))
(if keys-1
(setq key (car keys-1))))
(list
key
;; If KEY is a down-event, read and include the
;; corresponding up-event. Note that there are also
;; down-events on scroll bars and mode lines: the actual
;; event then is in the second element of the vector.
(and (not discarded-up) ; Don't attempt to ignore the up-event twice.
(vectorp key)
(let ((last-idx (1- (length key))))
(and (eventp (aref key last-idx))
(memq 'down (event-modifiers (aref key last-idx)))))
(or (and (eventp (setq down-ev (aref key 0)))
(memq 'down (event-modifiers down-ev))
;; However, for the C-down-mouse-2 popup
;; menu, there is no subsequent up-event. In
;; this case, the up-event is the next
;; element in the supplied vector.
(= (length key) 1))
(and (> (length key) 1)
(eventp (setq down-ev (aref key 1)))
(memq 'down (event-modifiers down-ev))))
(if (and (terminal-parameter nil 'xterm-mouse-mode)
(equal (terminal-parameter nil 'xterm-mouse-last-down)
down-ev))
(aref (read-key-sequence-vector nil) 0)
(read-event)))))
(raw-seq (this-single-command-raw-keys))
(keyn (when (> (length seq) 0)
(aref seq (1- (length seq)))))
(base (event-basic-type keyn))
(modifiers (event-modifiers keyn)))
(cond
((zerop (length seq))) ;FIXME: Can this happen?
((and no-mouse-movement (eq base 'mouse-movement)) nil)
((eq base 'help-echo) nil)
(t
(setq last-modifiers modifiers)
(push (cons seq raw-seq) key-list)))))
(nreverse key-list))
;; Put yank-menu back as it was, if we changed it.
(when saved-yank-menu
(setq yank-menu (copy-sequence saved-yank-menu))
(fset 'yank-menu (cons 'keymap yank-menu))))))
(defun help-downify-mouse-event-type (base)
"Add \"down-\" to BASE if it is not already there.
BASE is a symbol, a mouse event type. If the modification is done,
return the new symbol. Otherwise return nil."
(let ((base-s (symbol-name base)))
;; Note: the order of the components in the following string is
;; determined by `apply_modifiers_uncached' in src/keyboard.c.
(string-match "\\(A-\\)?\
\\(C-\\)?\
\\(H-\\)?\
\\(M-\\)?\
\\(S-\\)?\
\\(s-\\)?\
\\(double-\\)?\
\\(triple-\\)?\
\\(up-\\)?\
\\(\\(down-\\)?\\)\
\\(drag-\\)?" base-s)
(when (and (null (match-beginning 11)) ; "down-"
(null (match-beginning 12))) ; "drag-"
(intern (replace-match "down-" t t base-s 10)) )))
(defun describe-key (&optional key untranslated up-event)
"Display documentation of the function invoked by KEY.
KEY can be any kind of a key sequence; it can include keyboard events,
(defun describe-key (&optional key-list buffer up-event)
"Display documentation of the function invoked by KEY-LIST.
KEY-LIST can be any kind of a key sequence; it can include keyboard events,
mouse events, and/or menu events. When calling from a program,
pass KEY as a string or a vector.
pass KEY-LIST as a list of elements (SEQ . RAW-SEQ) where SEQ is
a key-sequence and RAW-SEQ is its untranslated form.
If non-nil, UNTRANSLATED is a vector of the corresponding untranslated events.
It can also be a number, in which case the untranslated events from
the last key sequence entered are used.
UP-EVENT is the up-event that was discarded by reading KEY, or nil.
While reading KEY-LIST interactively, this command temporarily enables
menu items or tool-bar buttons that are disabled to allow getting help
on them.
If KEY is a menu item or a tool-bar button that is disabled, this command
temporarily enables it to allow getting help on disabled items and buttons."
(interactive
(pcase-let ((`(,key ,up-event) (help-read-key-sequence)))
`(,key ,(prefix-numeric-value current-prefix-arg) ,up-event)))
(pcase-let ((`(,brief-desc ,defn ,event ,mouse-msg)
(help--analyze-key key untranslated))
(defn-up nil) (defn-up-tricky nil)
(key-locus-up nil) (key-locus-up-tricky nil)
(mouse-1-remapped nil) (mouse-1-tricky nil)
(ev-type nil))
(if (or (null defn)
(integerp defn)
(equal defn 'undefined))
(message "%s" brief-desc)
(help-setup-xref (list #'describe-function defn)
(called-interactively-p 'interactive))
;; Need to do this before erasing *Help* buffer in case event
;; is a mouse click in an existing *Help* buffer.
(when up-event
(setq ev-type (event-basic-type up-event))
(let ((sequence (vector up-event)))
(when (and (eq ev-type 'mouse-1)
mouse-1-click-follows-link
(not (eq mouse-1-click-follows-link 'double))
(setq mouse-1-remapped
(mouse-on-link-p (event-start up-event))))
(setq mouse-1-tricky (and (integerp mouse-1-click-follows-link)
(> mouse-1-click-follows-link 0)))
(cond ((stringp mouse-1-remapped)
(setq sequence mouse-1-remapped))
((vectorp mouse-1-remapped)
(setcar up-event (elt mouse-1-remapped 0)))
(t (setcar up-event 'mouse-2))))
(setq defn-up (key-binding sequence nil nil (event-start up-event)))
(setq key-locus-up (help--binding-locus sequence (event-start up-event)))
(when mouse-1-tricky
(setq sequence (vector up-event))
(aset sequence 0 'mouse-1)
(setq defn-up-tricky (key-binding sequence nil nil (event-start up-event)))
(setq key-locus-up-tricky (help--binding-locus sequence (event-start up-event))))))
BUFFER is the buffer in which to lookup those keys; it defaults to the
current buffer."
(declare (advertised-calling-convention (key-list &optional buffer) "27.1"))
(interactive (list (help--read-key-sequence)))
(when (arrayp key-list)
;; Compatibility with old calling convention.
(setq key-list (cons (list key-list) (if up-event (list up-event))))
(when buffer
(let ((raw (if (numberp buffer) (this-single-command-raw-keys) buffer)))
(setf (cdar (last key-list)) raw)))
(setq buffer nil))
(let* ((buf (or buffer (current-buffer)))
(on-link
(mapcar (lambda (kr)
(let ((raw (cdr kr)))
(and (not (memq mouse-1-click-follows-link '(nil double)))
(> (length raw) 0)
(eq (car-safe (aref raw 0)) 'mouse-1)
(with-current-buffer buf
(mouse-on-link-p (event-start (aref raw 0)))))))
key-list))
(info-list
(help--filter-info-list
(with-current-buffer buf
(mapcar (lambda (x)
(pcase-let* ((`(,seq . ,raw-seq) x)
(`(,brief-desc ,defn ,event ,_mouse-msg)
(help--analyze-key seq raw-seq))
(locus
(help--binding-locus
seq (event-start event))))
`(,seq ,brief-desc ,defn ,locus)))
key-list))
2)))
(help-setup-xref (list (lambda (key-list buf)
(describe-key key-list
(if (buffer-live-p buf) buf)))
key-list buf)
(called-interactively-p 'interactive))
(if (and (<= (length info-list) 1)
(help--binding-undefined-p (nth 2 (car info-list))))
(message "%s" (nth 1 (car info-list)))
(with-help-window (help-buffer)
(princ brief-desc)
(let ((key-locus (help--binding-locus key (event-start event))))
(when key-locus
(princ (format " (found in %s)" key-locus))))
(princ ", which is ")
(describe-function-1 defn)
(when (vectorp key)
(let* ((last (1- (length key)))
(elt (aref key last))
(elt-1 (if (listp elt) (copy-sequence elt) elt))
key-1 down-event-type)
(when (and (listp elt-1)
(symbolp (car elt-1))
(setq down-event-type (help-downify-mouse-event-type
(car elt-1))))
(setcar elt-1 down-event-type)
(setq key-1 (vector elt-1))
(when (key-binding key-1)
(princ (format "
(when (> (length info-list) 1)
;; FIXME: Make this into clickable hyperlinks.
(princ "There were several key-sequences:\n\n")
(princ (mapconcat (lambda (info)
(pcase-let ((`(,_seq ,brief-desc ,_defn ,_locus)
info))
(concat " " brief-desc)))
info-list
"\n"))
(when (delq nil on-link)
(princ "\n\nThose are influenced by `mouse-1-click-follows-link'"))
(princ "\n\nThey're all described below."))
(pcase-dolist (`(,_seq ,brief-desc ,defn ,locus)
info-list)
(when defn
(when (> (length info-list) 1)
(with-current-buffer standard-output
(insert "\n\n"
;; FIXME: Can't use eval-when-compile because purified
;; strings lose their text properties :-(
(propertize "\n" 'face '(:height 0.1 :inverse-video t))
"\n")))
For documentation of the corresponding mouse down event <%s>,
click and hold the mouse button longer than %s second(s)."
down-event-type (if (numberp double-click-time)
(/ double-click-time 1000.0)
3)))))))
(when up-event
(unless (or (null defn-up)
(integerp defn-up)
(equal defn-up 'undefined))
(princ (format "
----------------- up-event %s----------------
%s%s%s runs the command %S%s, which is "
(if mouse-1-tricky "(short click) " "")
(key-description (vector up-event))
mouse-msg
(if mouse-1-remapped
" is remapped to <mouse-2>, which" "")
defn-up (if key-locus-up
(format " (found in %s)" key-locus-up)
"")))
(describe-function-1 defn-up))
(unless (or (null defn-up-tricky)
(integerp defn-up-tricky)
(eq defn-up-tricky 'undefined))
(princ (format "
----------------- up-event (long click) ----------------
Pressing <%S>%s for longer than %d milli-seconds
runs the command %S%s, which is "
ev-type mouse-msg
mouse-1-click-follows-link
defn-up-tricky (if key-locus-up-tricky
(format " (found in %s)" key-locus-up-tricky)
"")))
(describe-function-1 defn-up-tricky)))))))
(princ brief-desc)
(when locus
(princ (format " (found in %s)" locus)))
(princ ", which is ")
(describe-function-1 defn)))))))
(defun describe-mode (&optional buffer)
"Display documentation of current major mode and minor modes.
@ -1120,7 +1059,7 @@ is currently activated with completion."
;;; Automatic resizing of temporary buffers.
(defcustom temp-buffer-max-height
(lambda (buffer)
(lambda (_buffer)
(if (and (display-graphic-p) (eq (selected-window) (frame-root-window)))
(/ (x-display-pixel-height) (frame-char-height) 2)
(/ (- (frame-height) 2) 2)))
@ -1137,7 +1076,7 @@ function is called, the window to be resized is selected."
:version "24.3")
(defcustom temp-buffer-max-width
(lambda (buffer)
(lambda (_buffer)
(if (and (display-graphic-p) (eq (selected-window) (frame-root-window)))
(/ (x-display-pixel-width) (frame-char-width) 2)
(/ (- (frame-width) 2) 2)))