(require 'slime)
(require 'eldoc)
(require 'cl-lib)
(require 'slime-parse)
(define-slime-contrib slime-autodoc
"Show fancy arglist in echo area."
(:license "GPL")
(:authors "Luke Gorrie <luke@bluetail.com>"
"Lawrence Mitchell <wence@gmx.li>"
"Matthias Koeppe <mkoeppe@mail.math.uni-magdeburg.de>"
"Tobias C. Rittweiler <tcr@freebits.de>")
(:slime-dependencies slime-parse)
(:swank-dependencies swank-arglists)
(:on-load (slime-autodoc--enable))
(:on-unload (slime-autodoc--disable)))
(defcustom slime-autodoc-accuracy-depth 10
"Number of paren levels that autodoc takes into account for
context-sensitive arglist display (local functions. etc)"
:type 'integer
:group 'slime-ui)
;;;###autoload
(defcustom slime-autodoc-mode-string (purecopy " adoc")
"String to display in mode line when Autodoc Mode is enabled; nil for none."
:type '(choice string (const :tag "None" nil))
:group 'slime-ui)
(defun slime-arglist (name)
"Show the argument list for NAME."
(interactive (list (slime-read-symbol-name "Arglist of: " t)))
(let ((arglist (slime-retrieve-arglist name)))
(if (eq arglist :not-available)
(error "Arglist not available")
(message "%s" (slime-autodoc--fontify arglist)))))
;; used also in slime-c-p-c.el.
(defun slime-retrieve-arglist (name)
(let ((name (cl-etypecase name
(string name)
(symbol (symbol-name name)))))
(car (slime-eval `(swank:autodoc '(,name ,slime-cursor-marker))))))
(defun slime-autodoc-manually ()
"Like autodoc informtion forcing multiline display."
(interactive)
(let ((doc (slime-autodoc t)))
(cond (doc (eldoc-message doc))
(t (eldoc-message nil)))))
;; Must call eldoc-add-command otherwise (eldoc-display-message-p)
;; returns nil and eldoc clears the echo area instead.
(eldoc-add-command 'slime-autodoc-manually)
(defun slime-autodoc-space (n)
"Like `slime-space' but nicer."
(interactive "p")
(self-insert-command n)
(let ((doc (slime-autodoc)))
(when doc
(eldoc-message doc))))
(eldoc-add-command 'slime-autodoc-space)
;;;; Autodoc cache
(defvar slime-autodoc--cache-last-context nil)
(defvar slime-autodoc--cache-last-autodoc nil)
(defun slime-autodoc--cache-get (context)
"Return the cached autodoc documentation for `context', or nil."
(and (equal context slime-autodoc--cache-last-context)
slime-autodoc--cache-last-autodoc))
(defun slime-autodoc--cache-put (context autodoc)
"Update the autodoc cache for CONTEXT with AUTODOC."
(setq slime-autodoc--cache-last-context context)
(setq slime-autodoc--cache-last-autodoc autodoc))
;;;; Formatting autodoc
(defsubst slime-autodoc--canonicalize-whitespace (string)
(replace-regexp-in-string "[ \n\t]+" " " string))
(defun slime-autodoc--format (doc multilinep)
(let ((doc (slime-autodoc--fontify doc)))
(cond (multilinep doc)
(t (slime-oneliner (slime-autodoc--canonicalize-whitespace doc))))))
(defun slime-autodoc--fontify (string)
"Fontify STRING as `font-lock-mode' does in Lisp mode."
(with-current-buffer (get-buffer-create (slime-buffer-name :fontify 'hidden))
(erase-buffer)
(unless (eq major-mode 'lisp-mode)
;; Just calling (lisp-mode) will turn slime-mode on in that buffer,
;; which may interfere with this function
(setq major-mode 'lisp-mode)
(lisp-mode-variables t))
(insert string)
(let ((font-lock-verbose nil))
(font-lock-fontify-buffer))
(goto-char (point-min))
(when (re-search-forward "===> \\(\\(.\\|\n\\)*\\) <===" nil t)
(let ((highlight (match-string 1)))
;; Can't use (replace-match highlight) here -- broken in Emacs 21
(delete-region (match-beginning 0) (match-end 0))
(slime-insert-propertized '(face eldoc-highlight-function-argument) highlight)))
(buffer-substring (point-min) (point-max))))
(define-obsolete-function-alias 'slime-fontify-string
'slime-autodoc--fontify
"SLIME 2.10")
;;;; Autodocs (automatic context-sensitive help)
(defun slime-autodoc (&optional force-multiline)
"Returns the cached arglist information as string, or nil.
If it's not in the cache, the cache will be updated asynchronously."
(save-excursion
(save-match-data
(let ((context (slime-autodoc--parse-context)))
(when context
(let* ((cached (slime-autodoc--cache-get context))
(multilinep (or force-multiline
eldoc-echo-area-use-multiline-p)))
(cond (cached (slime-autodoc--format cached multilinep))
(t
(when (slime-background-activities-enabled-p)
(slime-autodoc--async context multilinep))
nil))))))))
;; Return the context around point that can be passed to
;; swank:autodoc. nil is returned if nothing reasonable could be
;; found.
(defun slime-autodoc--parse-context ()
(and (slime-autodoc--parsing-safe-p)
(let ((levels slime-autodoc-accuracy-depth))
(slime-parse-form-upto-point levels))))
(defun slime-autodoc--parsing-safe-p ()
(cond ((fboundp 'slime-repl-inside-string-or-comment-p)
(not (slime-repl-inside-string-or-comment-p)))
(t
(not (slime-inside-string-or-comment-p)))))
(defun slime-autodoc--async (context multilinep)
(slime-eval-async
`(swank:autodoc ',context ;; FIXME: misuse of quote
:print-right-margin ,(window-width (minibuffer-window)))
(slime-curry #'slime-autodoc--async% context multilinep)))
(defun slime-autodoc--async% (context multilinep doc)
(cl-destructuring-bind (doc &optional cache-p) doc
(unless (eq doc :not-available)
(when cache-p
(slime-autodoc--cache-put context doc))
;; Now that we've got our information,
;; get it to the user ASAP.
(when (eldoc-display-message-p)
(eldoc-message (slime-autodoc--format doc multilinep))))))
;;; Minor mode definition
;; Compute the prefix for slime-doc-map, usually this is C-c C-d.
(defun slime-autodoc--doc-map-prefix ()
(concat
(car (rassoc '(slime-prefix-map) slime-parent-bindings))
(car (rassoc '(slime-doc-map) slime-prefix-bindings))))
(define-minor-mode slime-autodoc-mode
"Toggle echo area display of Lisp objects at point."
:lighter slime-autodoc-mode-string
:keymap (let ((prefix (slime-autodoc--doc-map-prefix)))
`((,(concat prefix "A") . slime-autodoc-manually)
(,(concat prefix (kbd "C-A")) . slime-autodoc-manually)
(,(kbd "SPC") . slime-autodoc-space)))
(set (make-local-variable 'eldoc-minor-mode-string) nil)
(if (boundp 'eldoc-documentation-functions)
(add-hook 'eldoc-documentation-functions 'slime-autodoc nil t)
(set (make-local-variable 'eldoc-documentation-function) 'slime-autodoc))
(eldoc-mode arg)
(setq slime-autodoc-mode t)
(when (called-interactively-p 'interactive)
(message "Slime autodoc mode %s."
(if slime-autodoc-mode "enabled" "disabled"))))
;;; Noise to enable/disable slime-autodoc-mode
(defun slime-autodoc--on () (slime-autodoc-mode 1))
(defun slime-autodoc--off () (slime-autodoc-mode 0))
(defvar slime-autodoc--relevant-hooks
'(slime-mode-hook slime-repl-mode-hook sldb-mode-hook))
(defun slime-autodoc--enable ()
(dolist (h slime-autodoc--relevant-hooks)
(add-hook h 'slime-autodoc--on))
(dolist (b (buffer-list))
(with-current-buffer b
(when slime-mode
(slime-autodoc--on)))))
(defun slime-autodoc--disable ()
(dolist (h slime-autodoc--relevant-hooks)
(remove-hook h 'slime-autodoc--on))
(dolist (b (buffer-list))
(with-current-buffer b
(when slime-autodoc-mode
(slime-autodoc--off)))))
(provide 'slime-autodoc)