Czy istnieje lepszy sposób obsługi wielowierszowych dokumentów w elisp?

9

Nienawidzę sposobu, w jaki elisp (nie jestem pewien, czy ogólnie LISP) obsługuje wieloliniowe dokumenty.

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

Na pewno żałuję, że nie mogę zrobić czegoś takiego

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

tak aby wcięcie było spójne.

Niestety, eval-when-compile nie działa.

Czy ktoś ma jakieś pomysły?

Krazy Glew
źródło
Utworzenie makra, które rozwinie się w plik, powinno być dość łatwe defun. Wadą tego podejścia - i jest ono duże - jest to, że dezorientuje każde oprogramowanie (inne niż kompilator / interpreter elisp), które analizuje Twój kod w poszukiwaniu defuns.
Harald Hanche-Olsen
3
Co zabawne, powodem, dla którego twoja sztuczka nie działa, jest eval-when-compilecytowanie jej wyniku (aby zamienić ją z wartości na wyrażenie). Gdyby był nieco mądrzejszy i cytował swój wynik tylko wtedy, gdy nie jest cytatem własnym, działałby.
Stefan

Odpowiedzi:

7

Oczywiście my-defunmakro jest łatwym wyjściem. Ale byłoby prostsze rozwiązanie

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

Które powinny sprawić, że twoja sztuczka zadziała, przynajmniej we wszystkich przypadkach, w których funkcja jest makroekspandowana przed jej faktyczną definicją, która powinna obejmować główne przypadki użycia (np. Jeśli jest ładowana z pliku, jeśli jest skompilowana bajtowo lub jeśli jest zdefiniowana przez M-C-x).

Jednak to nie naprawi całego istniejącego kodu, więc może lepsza odpowiedź to coś takiego:

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

które powinny po prostu przesuwać dokumenty o 2 spacje, ale tylko po stronie wyświetlania, bez wpływu na rzeczywistą zawartość bufora.

Stefan
źródło
1
Naprawdę podoba mi się twoje drugie rozwiązanie. Ale mój irracjonalny lęk przed radami sprawia, że ​​najpierw się zawracam. :-)
Malabarba
6

Możesz użyć takiego makra:

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

Następnie możesz zdefiniować swoje funkcje w następujący sposób:

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

Mimo to zdecydowanie odradzam postępowanie wbrew standardom w przypadku tak marginalnych korzyści. „Nieprawidłowe wcięcie”, które przeszkadza, to tylko 2 kolumny, nie wspominając o tym, że pomaga podkreślić pierwszy wiersz dokumentacji, który jest ważniejszy.

Malabarba
źródło
Właściwie, ciało defun jest oceniane (gdy funkcja jest wywoływana) i jest makropolewane, gdy funkcja jest zdefiniowana. Więc jego sztuczka powinna / mogłaby zadziałać.
Stefan
@Stefan To prawda. Zapomniałem eval-when-compilebyło makrem.
Malabarba
-1

Widziałem pakiety, które definiują takie dokumenty:

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

Umieszczenie pierwszego cytatu w pierwszym wierszu, a następnie rozpoczęcie tekstu w następnym wierszu, tak aby wszystkie były w jednej linii. To zdecydowanie nie jest standard, ale nie byłbyś jedynym, który to robi.

Jordon Biondo
źródło
1
To zły pomysł. W kontekstach takich jak Apropos wyświetlana jest tylko pierwsza linia dokumentacji, tak aby pierwsza linia zawierała informacje (i pozostawała samodzielna). W ten sposób otrzymujesz pusty opis.
Gilles „SO- przestań być zły”