I hate the way that elisp (not sure if LISP in general) handles multiline docstrings.

(defun foo ()
  "This is
a multi

I sure do wish that I could do something like

(defun foo ()
      "This is\n"
       "a multi\n"

so that the indentation was consistent.

Unfortunately, eval-when-compile does not do the job.

Does anyone have any ideas?

  • It should be fairly easy to create a macro that will expand into a defun. The drawback to that approach – and it is a big one – is that will confuse any software (other than the elisp compiler/interpreter) that is parsing your code looking for defuns. Oct 31 '14 at 8:49
  • 3
    Funnily enough, the reason why your trick doesn't work is that eval-when-compile quotes its result (to turn it from a value to an expression). If it were a bit more clever and only quoted its result when it's not self-quoting, it would work.
    – Stefan
    Oct 31 '14 at 13:37

Of course a my-defun macro is the easy way out. But a simpler solution would be

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

Which should make your trick work, at least in all the cases where the function is macroexpanded before it's actually defined, which should include the main use cases (e.g. if it'sloaded from a file, if it's byte-compiled, or if it's defined via M-C-x).

Still, this won't fix all the existing code, so maybe a better answer is something like:

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

(defun my-shift-docstrings (orig ppss)
  (let ((face (funcall orig ppss)))
    (when (eq face 'font-lock-doc-face)
        (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))))))

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

which should just shift the docstrings by 2 spaces, but only on the display side, without affecting the buffer's actual content.

  • 1
    I really like your second solution. But my irrational fear of advices makes me hinge at the first. :-)
    – Malabarba
    Oct 31 '14 at 14:32
  • Was reminded of this, and looking at my-shift-docstrings I see that it shifts by two, which is appropriate when the defund is at the top level, not nested. But I admit that I occasionally put the funds within if or cond j statements if only as a way of disabling large blocks of code quickly. Q: is there a way of detecting the lexical nesting depth so that the correct indentation could be applied?
    – Krazy Glew
    Feb 26 at 1:30
  • @KrazyGlew: [ Hi Andy, long time no see! ] Of course you can replace the "\n " with something like (concat "\n" (make-string N ?\s)) and compute the N e.g. as (* 2 (car ppss)) (since (car ppss) should return the paren-nesting). Another option is something like (save-excursion (goto-char (nth 8 ppss)) (current-column)) which should give you the indentation of the opening double quote.
    – Stefan
    Feb 27 at 13:55

You could use a macro like this:

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

Then you can define your functions like this:

(my-defun test (a)
  (+ 1 a))

Still, I'd strongly recommend not going against the standards for such a marginal benefit. The “irregular indentation” that bothers you is just by 2 columns, not to mention it helps highlight the first line of documentation which is more important.

  • Actually, the body of a defun is evaluated (when the function is called) and it is macro-expanded when the function is defined. So his trick should/could work.
    – Stefan
    Oct 31 '14 at 13:39
  • @Stefan That's true. Forgot eval-when-compile was a macro.
    – Malabarba
    Oct 31 '14 at 14:04

I've seen packages that define docstrings like this:

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

Placing the first quote on the first line then starting the text on the next so they all line up. It's definitely not the standard but you wouldn't be the only one doing it.

  • 3
    That's a bad idea. In contexts such as Apropos, only the first line of the docstring is shown, so that first line should provide information (and stand on its own). This way you get an empty description. Nov 10 '14 at 0:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.