[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
scratch/eldoc-async 78498a1 4/5: Rewrite and simplify docstring formatti
From: |
Jo�o T�vora |
Subject: |
scratch/eldoc-async 78498a1 4/5: Rewrite and simplify docstring formatting in Eldoc |
Date: |
Sun, 14 Jun 2020 09:09:14 -0400 (EDT) |
branch: scratch/eldoc-async
commit 78498a12040ee3515b8f76a796838578899f3ebd
Author: João Távora <joaotavora@gmail.com>
Commit: João Távora <joaotavora@gmail.com>
Rewrite and simplify docstring formatting in Eldoc
Eldoc is now more responsible for handling the format of the displayed
docstrings, elisp-mode is less responsible. We achieve this by having
elisp-mode's eldoc-documentation-functions inform Eldoc of the things
they are documenting.
From here on, a natural development would be to have a new hook called
eldoc-display-functions which would let users control the destinations
of the gathered documention. Third parties could write hook functions
suitable for it, for displaying documentation in separate frames,
external systems, etc.
* lisp/cedet/semantic/grammar.el
(semantic--docstring-format-sym-doc): Copied from
eldoc-docstring-format-sym-doc.
(semantic-grammar-eldoc-get-macro-docstring): Use
semantic--docstring-format-sym-doc and correctly use
elisp-get-fnsym-args-string.
* lisp/emacs-lisp/eldoc.el (eldoc-documentation-functions):
Describemeaning of :THING and :FACE arguments to callback.
(eldoc--handle-docs): Rewrite.
* lisp/emacs-lisp/eldoc.el (eldoc-docstring-format-sym-doc):
Delete.
* lisp/progmodes/elisp-mode.el (elisp-eldoc-funcall)
(elisp-eldoc-var-docstring): Use a CALLBACK arg, pass it :thing
and and :face.
(elisp-get-var-docstring, elisp-get-fnsym-args-string): Don't do
any truncation or special formatting..
---
lisp/cedet/semantic/grammar.el | 45 ++++++++--
lisp/emacs-lisp/eldoc.el | 185 +++++++++++++++++++++--------------------
lisp/progmodes/elisp-mode.el | 41 +++++----
3 files changed, 156 insertions(+), 115 deletions(-)
diff --git a/lisp/cedet/semantic/grammar.el b/lisp/cedet/semantic/grammar.el
index 2c3b24b..1ed1833 100644
--- a/lisp/cedet/semantic/grammar.el
+++ b/lisp/cedet/semantic/grammar.el
@@ -1663,6 +1663,42 @@ Select the buffer containing the tag's definition, and
move point there."
(defvar semantic-grammar-eldoc-last-data (cons nil nil))
+(defun semantic--docstring-format-sym-doc (prefix doc &optional face)
+ "Combine PREFIX and DOC, and shorten the result to fit in the echo area.
+
+When PREFIX is a symbol, propertize its symbol name with FACE
+before combining it with DOC. If FACE is not provided, just
+apply the nil face.
+
+See also: `eldoc-echo-area-use-multiline-p'."
+ ;; Hoisted from old `eldoc-docstring-format-sym-doc'.
+ ;; If the entire line cannot fit in the echo area, the symbol name may be
+ ;; truncated or eliminated entirely from the output to make room for the
+ ;; description.
+ (when (symbolp prefix)
+ (setq prefix (concat (propertize (symbol-name prefix) 'face face) ": ")))
+ (let* ((ea-multi eldoc-echo-area-use-multiline-p)
+ ;; Subtract 1 from window width since emacs will not write
+ ;; any chars to the last column, or in later versions, will
+ ;; cause a wraparound and resize of the echo area.
+ (ea-width (1- (window-width (minibuffer-window))))
+ (strip (- (+ (length prefix)
+ (length doc))
+ ea-width)))
+ (cond ((or (<= strip 0)
+ (eq ea-multi t)
+ (and ea-multi (> (length doc) ea-width)))
+ (concat prefix doc))
+ ((> (length doc) ea-width)
+ (substring (format "%s" doc) 0 ea-width))
+ ((>= strip (string-match-p ":? *\\'" prefix))
+ doc)
+ (t
+ ;; Show the end of the partial symbol name, rather
+ ;; than the beginning, since the former is more likely
+ ;; to be unique given package namespace conventions.
+ (concat (substring prefix strip) doc)))))
+
(defun semantic-grammar-eldoc-get-macro-docstring (macro expander)
"Return a one-line docstring for the given grammar MACRO.
EXPANDER is the name of the function that expands MACRO."
@@ -1681,19 +1717,18 @@ EXPANDER is the name of the function that expands
MACRO."
(setq doc (eldoc-function-argstring expander))))
(when doc
(setq doc
- (eldoc-docstring-format-sym-doc
+ (semantic--docstring-format-sym-doc
macro (format "==> %s %s" expander doc) 'default))
(setq semantic-grammar-eldoc-last-data (cons expander doc)))
doc))
((fboundp 'elisp-get-fnsym-args-string) ;; Emacs≥25
- (elisp-get-fnsym-args-string
- expander nil
- (concat (propertize (symbol-name macro)
+ (concat (propertize (symbol-name macro)
'face 'font-lock-keyword-face)
" ==> "
(propertize (symbol-name macro)
'face 'font-lock-function-name-face)
- ": ")))))
+ ": "
+ (elisp-get-fnsym-args-string expander nil )))))
(define-mode-local-override semantic-idle-summary-current-symbol-info
semantic-grammar-mode ()
diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el
index be78b13..a3437ca 100644
--- a/lisp/emacs-lisp/eldoc.el
+++ b/lisp/emacs-lisp/eldoc.el
@@ -369,26 +369,29 @@ information.
Each hook function is called with at least one argument CALLBACK
and decides whether to display a doc short string about the
-context around point. If that decision can be taken quickly, the
-hook function may ignore CALLBACK and immediately return either
-the doc string or nil if there's no doc appropriate for the
-context. Otherwise, it should return a non-string, non-nil value
-and arrange for CALLBACK to be called later.
-
-In particular, if the computation of said docstring (or the
-decision as to whether there is documentation at all) is
-expensive or can't be performed directly, the hook function
-should return a non-nil, non-string value and arrange for
-CALLBACK to be called at a later time, using asynchronous
-processes or other asynchronous mechanisms.
-
-However CALLBACK is called, it expects to be passed an argument
-DOCSTRING followed by an optional list of keyword-value pairs of
-the form (:KEY VALUE :KEY2 VALUE2...). KEY can be:
-
-* `:hint', whereby the corresponding VALUE should be a short
- string designating the type of documentation reported on by the
- hook function.
+context around point.
+
+- If that decision can be taken quickly, the hook function may
+ call CALLBACK immediately following the protocol described
+ berlow. Alternatively it may ignore CALLBACK entirely and
+ return either the doc string, or nil if there's no doc
+ appropriate for the context.
+
+- If the computation of said doc string (or the decision whether
+ there is one at all) is expensive or can't be performed
+ directly, the hook function should return a non-nil, non-string
+ value and arrange for CALLBACK to be called at a later time,
+ using asynchronous processes or other asynchronous mechanisms.
+
+Regardless of the context in which CALLBACK is called, it expects
+to be passed an argument DOCSTRING followed by an optional list
+of keyword-value pairs of the form (:KEY VALUE :KEY2 VALUE2...).
+KEY can be:
+
+* `:thing', VALUE is be a short string or symbol designating what
+ is being reported on;
+
+* `:face', VALUE is a symbol designating a face;
Major modes should modify this hook locally, for example:
(add-hook \\='eldoc-documentation-functions #\\='foo-mode-eldoc nil t)
@@ -410,11 +413,12 @@ return any documentation.")
"Display multiple DOCS in echo area.
DOCS is a list of (STRING PLIST...). It is already sorted.
Honour most of `eldoc-echo-area-use-multiline-p'."
- (with-current-buffer (eldoc-doc-buffer)
- (let ((inhibit-read-only t))
- (erase-buffer) (setq buffer-read-only t)
- (local-set-key "q" 'quit-window)
- (insert (mapconcat #'identity (mapcar #'car docs) "\n")))
+ (if (null docs) (eldoc--message nil) ; if there's nothing to report
+ ; clear the echo area, but
+ ; don't erase the last *eldoc*
+ ; buffer.
+ ;; If there's something to report on, first establish some
+ ;; parameterso
(let* ((width (1- (window-width (minibuffer-window))))
(val (if (and (symbolp eldoc-echo-area-use-multiline-p)
eldoc-echo-area-use-multiline-p)
@@ -423,36 +427,74 @@ Honour most of `eldoc-echo-area-use-multiline-p'."
(available (cl-typecase val
(float (truncate (* (frame-height) val)))
(integer val)
- (t 1))))
- (eldoc--message nil) ; clear the echo area
- (when docs
- (goto-char (point-min))
- (cond
- ((> available 1)
- (cl-loop
- initially (goto-char (line-end-position (1+ available)))
- for truncated = nil then t
- for needed
- = (let ((truncate-lines message-truncate-lines))
- (count-screen-lines (point-min) (point) t (minibuffer-window)))
- while (> needed (if truncated (1- available) available))
- do (goto-char (line-end-position (if truncated 0 -1)))
- (while (bolp) (goto-char (line-end-position 0)))
- finally
- (unless (and truncated
- eldoc-prefer-doc-buffer
- (get-buffer-window eldoc--doc-buffer))
- (eldoc--message
- (concat (buffer-substring (point-min) (point))
- (and truncated
- (format
- "\n(Documentation truncated. Use `%s' to see rest)"
- (substitute-command-keys
"\\[eldoc-doc-buffer]"))))))))
- ((= available 1)
- ;; truncate brutally ; FIXME: use `eldoc-prefer-doc-buffer' here,
too?
- (eldoc--message
- (truncate-string-to-width
- (buffer-substring (point-min) (line-end-position 1)) width))))))))
+ (t 1)))
+ (things-reported-on)
+ single-sym-name)
+ ;; Then, compose the contents of the `*eldoc*' buffer
+ (with-current-buffer (eldoc-doc-buffer)
+ (let ((inhibit-read-only t))
+ (erase-buffer) (setq buffer-read-only t)
+ (local-set-key "q" 'quit-window)
+ (cl-loop for (docs . rest) on docs
+ for (this-doc . plist) = docs
+ for thing = (plist-get plist :thing)
+ when thing do
+ (cl-pushnew thing things-reported-on)
+ (setq this-doc
+ (concat
+ (propertize (format "%s" thing)
+ 'face (plist-get plist :face))
+ ": "
+ this-doc))
+ do (insert this-doc)
+ when rest do (insert "\n")))
+ ;; rename the buffer
+ (when things-reported-on
+ (rename-buffer (format "*eldoc for %s*"
+ (mapconcat (lambda (s) (format "%s" s))
+ things-reported-on
+ ", ")))))
+ ;; Finally, output to the echo area. We currently do handling
+ ;; the `truncate-sym-name-if-fit' special case first, then by
+ ;; selecting a top-section of the `*eldoc' buffer. I'm pretty
+ ;; sure nicer strategies can be used here, probably by splitting
+ ;; this pfunction into some `eldoc-display-functions' special
+ ;; hook.
+ (if (and (eq 'truncate-sym-name-if-fit eldoc-echo-area-use-multiline-p)
+ (null (cdr docs))
+ (setq single-sym-name
+ (format "%s" (plist-get (cdar docs) :thing)))
+ (> (+ (length (caar docs)) (length single-sym-name) 2) width))
+ (eldoc--message (caar docs))
+ (with-current-buffer (eldoc-doc-buffer)
+ (goto-char (point-min))
+ (cond
+ ;; We truncate a long message
+ ((> available 1)
+ (cl-loop
+ initially (goto-char (line-end-position (1+ available)))
+ for truncated = nil then t
+ for needed
+ = (let ((truncate-lines message-truncate-lines))
+ (count-screen-lines (point-min) (point) t
(minibuffer-window)))
+ while (> needed (if truncated (1- available) available))
+ do (goto-char (line-end-position (if truncated 0 -1)))
+ (while (bolp) (goto-char (line-end-position 0)))
+ finally
+ (unless (and truncated
+ eldoc-prefer-doc-buffer
+ (get-buffer-window eldoc--doc-buffer))
+ (eldoc--message
+ (concat (buffer-substring (point-min) (point))
+ (and truncated
+ (format
+ "\n(Documentation truncated. Use `%s' to see
rest)"
+ (substitute-command-keys
"\\[eldoc-doc-buffer]"))))))))
+ ((= available 1)
+ ;; truncate brutally ; FIXME: use `eldoc-prefer-doc-buffer'
here, too?
+ (eldoc--message
+ (truncate-string-to-width
+ (buffer-substring (point-min) (line-end-position 1))
width)))))))))
;; this variable should be unbound, but that confuses
;; `describe-symbol' for some reason.
@@ -613,39 +655,6 @@ documentation themselves."
(;; got something else, trust callback will be called
t)))))))))
-;; If the entire line cannot fit in the echo area, the symbol name may be
-;; truncated or eliminated entirely from the output to make room for the
-;; description.
-(defun eldoc-docstring-format-sym-doc (prefix doc &optional face)
- "Combine PREFIX and DOC, and shorten the result to fit in the echo area.
-
-When PREFIX is a symbol, propertize its symbol name with FACE
-before combining it with DOC. If FACE is not provided, just
-apply the nil face.
-
-See also: `eldoc-echo-area-use-multiline-p'."
- (when (symbolp prefix)
- (setq prefix (concat (propertize (symbol-name prefix) 'face face) ": ")))
- (let* ((ea-multi eldoc-echo-area-use-multiline-p)
- ;; Subtract 1 from window width since emacs will not write
- ;; any chars to the last column, or in later versions, will
- ;; cause a wraparound and resize of the echo area.
- (ea-width (1- (window-width (minibuffer-window))))
- (strip (- (+ (length prefix) (length doc)) ea-width)))
- (cond ((or (<= strip 0)
- (eq ea-multi t)
- (and ea-multi (> (length doc) ea-width)))
- (concat prefix doc))
- ((> (length doc) ea-width)
- (substring (format "%s" doc) 0 ea-width))
- ((>= strip (string-match-p ":? *\\'" prefix))
- doc)
- (t
- ;; Show the end of the partial symbol name, rather
- ;; than the beginning, since the former is more likely
- ;; to be unique given package namespace conventions.
- (concat (substring prefix strip) doc)))))
-
;; When point is in a sexp, the function args are not reprinted in the echo
;; area after every possible interactive command because some of them print
;; their own messages in the echo area; the eldoc functions would instantly
diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el
index a003f0f..6df5411 100644
--- a/lisp/progmodes/elisp-mode.el
+++ b/lisp/progmodes/elisp-mode.el
@@ -1405,20 +1405,27 @@ which see."
or argument string for functions.
2 - `function' if function args, `variable' if variable documentation.")
-(defun elisp-eldoc-funcall (&rest _ignored)
+(defun elisp-eldoc-funcall (callback &rest _ignored)
"Document function call at point.
Intended for `eldoc-documentation-functions' (which see)."
- (let ((fn-sym (elisp--fnsym-in-current-sexp)))
+ (let* ((sym-info (elisp--fnsym-in-current-sexp))
+ (fn-sym (car sym-info)))
(when fn-sym
- (apply #'elisp-get-fnsym-args-string fn-sym))))
+ (funcall callback (apply #'elisp-get-fnsym-args-string sym-info)
+ :thing fn-sym
+ :face (if (functionp fn-sym)
+ 'font-lock-function-name-face
+ 'font-lock-keyword-face)))))
-(defun elisp-eldoc-var-docstring (&rest _ignored)
+(defun elisp-eldoc-var-docstring (callback &rest _ignored)
"Document variable at point.
Intended for `eldoc-documentation-functions' (which see)."
(let ((sym (elisp--current-symbol)))
- (when sym (elisp-get-var-docstring sym))))
+ (when sym (funcall callback (elisp-get-var-docstring sym)
+ :thing sym
+ :face 'font-lock-variable-name-face))))
-(defun elisp-get-fnsym-args-string (sym &optional index prefix)
+(defun elisp-get-fnsym-args-string (sym &optional index)
"Return a string containing the parameter list of the function SYM.
If SYM is a subr and no arglist is obtainable from the docstring
or elsewhere, return a 1-line docstring."
@@ -1444,20 +1451,13 @@ or elsewhere, return a 1-line docstring."
;; Stringify, and store before highlighting, downcasing, etc.
(elisp--last-data-store sym (elisp-function-argstring args)
'function))))))
- ;; Highlight, truncate.
+ ;; Highlight
(if argstring
(elisp--highlight-function-argument
- sym argstring index
- (or prefix
- (concat (propertize (symbol-name sym) 'face
- (if (functionp sym)
- 'font-lock-function-name-face
- 'font-lock-keyword-face))
- ": "))))))
-
-(defun elisp--highlight-function-argument (sym args index prefix)
- "Highlight argument INDEX in ARGS list for function SYM.
-In the absence of INDEX, just call `eldoc-docstring-format-sym-doc'."
+ sym argstring index))))
+
+(defun elisp--highlight-function-argument (sym args index)
+ "Highlight argument INDEX in ARGS list for function SYM."
;; FIXME: This should probably work on the list representation of `args'
;; rather than its string representation.
;; FIXME: This function is much too long, we need to split it up!
@@ -1560,7 +1560,6 @@ In the absence of INDEX, just call
`eldoc-docstring-format-sym-doc'."
(when start
(setq doc (copy-sequence args))
(add-text-properties start end (list 'face argument-face) doc))
- (setq doc (eldoc-docstring-format-sym-doc prefix doc))
doc)))
;; Return a string containing a brief (one-line) documentation string for
@@ -1573,9 +1572,7 @@ In the absence of INDEX, just call
`eldoc-docstring-format-sym-doc'."
(t
(let ((doc (documentation-property sym 'variable-documentation t)))
(when doc
- (let ((doc (eldoc-docstring-format-sym-doc
- sym (elisp--docstring-first-line doc)
- 'font-lock-variable-name-face)))
+ (let ((doc (elisp--docstring-first-line doc)))
(elisp--last-data-store sym doc 'variable)))))))
(defun elisp--last-data-store (symbol doc type)