¿Hay una mejor manera de manejar cadenas de documentos multilínea en elisp?

9

Odio la forma en que elisp (no estoy seguro si LISP en general) maneja cadenas de documentos multilínea.

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

Seguro que desearía poder hacer algo como

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

para que la sangría fuera consistente.

Desafortunadamente, eval-when-compile no hace el trabajo.

¿Alguien tiene alguna idea?

Krazy Glew
fuente
Debería ser bastante fácil crear una macro que se expanda en a defun. El inconveniente de ese enfoque, y es muy importante, es que confundirá cualquier software (que no sea el compilador / intérprete elisp) que está analizando su código en busca de defuns.
Harald Hanche-Olsen
3
Curiosamente, la razón por la cual su truco no funciona es que eval-when-compilecita su resultado (para convertirlo en un valor en una expresión). Si fuera un poco más inteligente y solo citara su resultado cuando no es autocita, funcionaría.
Stefan

Respuestas:

7

Por supuesto, una my-defunmacro es la salida fácil. Pero una solución más simple sería

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

Lo que debería hacer que su truco funcione, al menos en todos los casos en que la función se expande macro antes de que se defina realmente, que debe incluir los casos de uso principales (por ejemplo, si se carga desde un archivo, si se compila en bytes o si se define vía M-C-x).

Aún así, esto no solucionará todo el código existente, por lo que tal vez una mejor respuesta sea algo como:

;; -*- 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)))

que solo debe desplazar las cadenas de documentos en 2 espacios, pero solo en el lado de la pantalla, sin afectar el contenido real del búfer.

Stefan
fuente
1
Realmente me gusta tu segunda solución. Pero mi miedo irracional a los consejos me hace dar vueltas al principio. :-)
Malabarba
6

Podrías usar una macro como esta:

(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)))

Luego puede definir sus funciones de esta manera:

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

Aún así, me fuertemente recomiendo no ir en contra de las normas para tal beneficio marginal. La "sangría irregular" que le molesta es solo de 2 columnas, sin mencionar que ayuda a resaltar la primera línea de documentación que es más importante.

Malabarba
fuente
En realidad, el cuerpo de un defun se evalúa (cuando se llama a la función) y se expande macro cuando se define la función. Entonces su truco debería / podría funcionar.
Stefan
@ Stefan Eso es cierto. Olvidé que eval-when-compileera una macro.
Malabarba
-1

He visto paquetes que definen cadenas de documentos como esta:

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

Coloque la primera cita en la primera línea y luego comience el texto en la siguiente para que todos se alineen. Definitivamente no es el estándar, pero no sería el único que lo haría.

Jordon Biondo
fuente
1
Esa es una mala idea. En contextos como Apropos, solo se muestra la primera línea de la cadena de documentación, de modo que la primera línea debe proporcionar información (y mantenerse por sí misma). De esta manera obtienes una descripción vacía.
Gilles 'SO- deja de ser malvado'