Existe-t-il un meilleur moyen de gérer les docstrings multilignes dans elisp?

9

Je déteste la façon dont elisp (pas sûr si LISP en général) gère les docstrings multilignes.

(defun foo ()
  "This is
a multi
liner
docstring"
  (do-stuff))

Je souhaite vraiment pouvoir faire quelque chose comme

(defun foo ()
  (eval-when-compile 
    (concat
      "This is\n"
       "a multi\n"
       "line\n"
       "docstring"))
  (do-stuff))

de sorte que l'indentation était cohérente.

Malheureusement, eval-when-compile ne fait pas le travail.

Quelqu'un a-t-il une idée?

Krazy Glew
la source
Il devrait être assez facile de créer une macro qui se développera en a defun. L'inconvénient de cette approche - et elle est importante - est de confondre tout logiciel (autre que le compilateur / interprète elisp) qui analyse votre code à la recherche de defuns.
Harald Hanche-Olsen
3
Curieusement, la raison pour laquelle votre astuce ne fonctionne pas est de eval-when-compileciter son résultat (pour le transformer d'une valeur en une expression). S'il était un peu plus intelligent et ne citait son résultat que lorsqu'il n'était pas auto-cité, cela fonctionnerait.
Stefan

Réponses:

7

Bien sûr, une my-defunmacro est la solution de facilité. Mais une solution plus simple serait

(advice-add 'eval-when-compile :filter-return
            (lambda (exp)
              (if (and (eq 'quote (car-safe exp))
                       (stringp (cadr exp)))
                  (cadr exp)
                exp)))

Ce qui devrait faire fonctionner votre astuce, au moins dans tous les cas où la fonction est agrandie avant d'être réellement définie, ce qui devrait inclure les principaux cas d'utilisation (par exemple, si elle est chargée à partir d'un fichier, si elle est compilée en octets ou si elle est définie via M-C-x).

Pourtant, cela ne résoudra pas tout le code existant, alors peut-être qu'une meilleure réponse est quelque chose comme:

;; -*- lexical-binding:t -*-

(defun my-shift-docstrings (orig ppss)
  (let ((face (funcall orig ppss)))
    (when (eq face 'font-lock-doc-face)
      (save-excursion
        (let ((start (point)))
          (parse-partial-sexp (point) (point-max) nil nil ppss 'syntax-table)
          (while (search-backward "\n" start t)
            (put-text-property (point) (1+ (point)) 'display
                               (propertize "\n  " 'cursor 0))))))
    face))

(add-hook 'emacs-lisp-mode-hook
          (lambda ()
            (font-lock-mode 1)
            (push 'display font-lock-extra-managed-props)
            (add-function :around (local 'font-lock-syntactic-face-function)
                          #'my-shift-docstrings)))

ce qui devrait simplement déplacer les docstrings de 2 espaces, mais uniquement du côté de l'affichage, sans affecter le contenu réel du tampon.

Stefan
la source
1
J'aime vraiment votre deuxième solution. Mais ma peur irrationnelle des conseils me fait charnière au premier abord. :-)
Malabarba
6

Vous pouvez utiliser une macro comme celle-ci:

(defmacro my-defun (name arglist &rest forms)
  "Like `defun', but concatenates strings."
  (declare (indent defun))
  (let (doc-lines)
    (while (and (stringp (car-safe forms))
                (> (length forms) 1))
      (setq doc-lines
            (append doc-lines (list (pop forms)))))
    `(defun ,name ,arglist
       ,(mapconcat #'identity doc-lines "\n")
       ,@forms)))

Ensuite, vous pouvez définir vos fonctions comme ceci:

(my-defun test (a)
  "Description"
  "asodksad"
  "ok"
  (interactive)
  (+ 1 a))

Néanmoins, je recommande fortement de ne pas aller à l'encontre des normes pour un avantage aussi marginal. L '«indentation irrégulière» qui vous dérange n'est que de 2 colonnes, sans oublier qu'elle permet de mettre en évidence la première ligne de documentation qui est plus importante.

Malabarba
la source
En fait, le corps d'un defun est évalué (lorsque la fonction est appelée) et il est macro-développé lorsque la fonction est définie. Son astuce devrait / pourrait donc fonctionner.
Stefan
@Stefan C'est vrai. Oublié eval-when-compileétait une macro.
Malabarba
-1

J'ai vu des packages qui définissent des docstrings comme ceci:

(defun my-function (x y) "
this is my docstring
that lines always lines up
across multiple lines."
  (+ x y))

Placer la première citation sur la première ligne puis commencer le texte sur la suivante pour qu'ils s'alignent tous. Ce n'est certainement pas la norme, mais vous ne seriez pas le seul à le faire.

Jordon Biondo
la source
1
C'est une mauvaise idée. Dans des contextes tels que Apropos, seule la première ligne de la docstring est affichée, de sorte que la première ligne doit fournir des informations (et être autonome). De cette façon, vous obtenez une description vide.
Gilles 'SO- arrête d'être méchant'