Apakah ada cara yang lebih baik untuk menangani dokumen multiline di elisp?

9

Saya benci cara elisp (tidak yakin apakah LISP secara umum) menangani dokumen multiline.

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

Saya sungguh berharap dapat melakukan sesuatu seperti

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

sehingga lekukannya konsisten.

Sayangnya, eval-saat-kompilasi tidak melakukan pekerjaan.

Adakah yang punya ide?

Krazy Glew
sumber
Seharusnya cukup mudah untuk membuat makro yang akan berkembang menjadi defun. Kelemahan dari pendekatan itu - dan ini yang besar - adalah yang akan membingungkan perangkat lunak apa pun (selain dari compiler / interpreter elisp) yang mem-parsing kode Anda mencari defuns.
Harald Hanche-Olsen
3
Lucunya, alasan mengapa trik Anda tidak berhasil adalah karena eval-when-compilemengutip hasilnya (untuk mengubahnya dari nilai menjadi ekspresi). Jika itu sedikit lebih pintar dan hanya mengutip hasilnya ketika tidak mengutip sendiri, itu akan berhasil.
Stefan

Jawaban:

7

Tentu saja my-defunmakro adalah jalan keluar yang mudah. Tetapi solusi yang lebih sederhana adalah

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

Yang seharusnya membuat trik Anda bekerja, setidaknya dalam semua kasus di mana fungsi ini diperluas makro sebelum benar-benar didefinisikan, yang harus mencakup kasus penggunaan utama (misalnya jika itu diambil dari file, jika byte-dikompilasi, atau jika itu didefinisikan via M-C-x).

Namun, ini tidak akan memperbaiki semua kode yang ada, jadi mungkin jawaban yang lebih baik adalah seperti:

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

yang seharusnya hanya menggeser dokumen dengan 2 spasi, tetapi hanya pada sisi tampilan, tanpa mempengaruhi konten buffer sebenarnya.

Stefan
sumber
1
Saya sangat suka solusi kedua Anda. Tetapi rasa takut saya yang tidak rasional terhadap nasihat membuat saya enggan pada awalnya. :-)
Malabarba
6

Anda bisa menggunakan makro seperti ini:

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

Kemudian Anda dapat mendefinisikan fungsi Anda seperti ini:

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

Namun, saya sangat menyarankan untuk tidak melanggar standar untuk manfaat marjinal. "Lekukan tidak beraturan" yang mengganggu Anda hanya dengan 2 kolom, belum lagi membantu menyoroti baris pertama dokumentasi yang lebih penting.

Malabarba
sumber
Sebenarnya, tubuh defun suatu yang dievaluasi (ketika fungsi dipanggil) dan itu adalah makro-diperluas ketika fungsi didefinisikan. Jadi triknya harus / bisa bekerja.
Stefan
@Stefan Itu benar. Lupa eval-when-compileadalah makro.
Malabarba
-1

Saya telah melihat paket yang mendefinisikan dokumen seperti ini:

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

Menempatkan kutipan pertama pada baris pertama kemudian memulai teks pada baris berikutnya sehingga mereka semua berbaris. Ini jelas bukan standar tetapi Anda tidak akan menjadi satu-satunya yang melakukannya.

Jordon Biondo
sumber
1
Itu ide yang buruk. Dalam konteks seperti Apropos, hanya baris pertama dari docstring yang ditampilkan, sehingga baris pertama harus memberikan informasi (dan berdiri sendiri). Dengan cara ini Anda mendapatkan deskripsi kosong.
Gilles 'SO- stop being evil'