Existe uma maneira melhor de lidar com documentos de várias linhas no elisp?

9

Eu odeio a maneira como elisp (não tenho certeza se LISP em geral) lida com doutrinas de várias linhas.

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

Eu com certeza gostaria de poder fazer algo como

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

para que o recuo fosse consistente.

Infelizmente, eval-when-compile não faz o trabalho.

Alguém tem alguma idéia?

Krazy Glew
fonte
Deve ser bastante fácil criar uma macro que será expandida para a defun. A desvantagem dessa abordagem - e é grande - é que irá confundir qualquer software (que não seja o elisp compilador / intérprete) que está analisando seu código procurando por defuns.
Harald Hanche-Olsen
3
Curiosamente, a razão pela qual seu truque não funciona é que eval-when-compilecita seu resultado (para transformá-lo de valor em expressão). Se fosse um pouco mais inteligente e apenas citasse seu resultado quando não é auto-citado, funcionaria.
31514 Stefan #

Respostas:

7

Claro que uma my-defunmacro é a saída mais fácil. Mas uma solução mais simples seria

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

O que deve fazer o seu truque funcionar, pelo menos em todos os casos em que a função é expandida por macro antes de ser realmente definida, o que deve incluir os principais casos de uso (por exemplo, se for carregado a partir de um arquivo, se for compilado em bytes ou se for definido via M-C-x).

Ainda assim, isso não corrigirá todo o código existente, portanto, talvez uma resposta melhor seja 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 deve apenas mudar as docstrings em 2 espaços, mas apenas no lado da tela, sem afetar o conteúdo real do buffer.

Stefan
fonte
11
Eu realmente gosto da sua segunda solução. Mas meu medo irracional de conselhos me faz depender do começo. :-)
Malabarba
6

Você pode usar uma 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)))

Então você pode definir suas funções assim:

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

Ainda assim, eu recomendo fortemente não ir contra os padrões para um benefício tão marginal. O “recuo irregular” que o incomoda é apenas de 2 colunas, sem mencionar que ajuda a destacar a primeira linha de documentação que é mais importante.

Malabarba
fonte
Na verdade, o corpo de um defun é avaliado (quando a função é chamada) e é macro expandido quando a função é definida. Então o truque dele deve / poderia funcionar.
31514 Stefan #
@ Stefan Isso é verdade. Esqueceu eval-when-compileera uma macro.
Malabarba
-1

Eu vi pacotes que definem documentos como este:

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

Colocando a primeira citação na primeira linha e iniciando o texto na próxima, para que todas elas se alinhem. Definitivamente não é o padrão, mas você não seria o único a fazê-lo.

Jordon Biondo
fonte
11
Isso é uma péssima ideia. Em contextos como Apropos, apenas a primeira linha da doutrina é mostrada, para que a primeira linha forneça informações (e se mantenha por si só). Dessa forma, você obtém uma descrição vazia.
Gilles 'SO- stop be evil'