11

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

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

I sure do wish that I could do something like

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

so that the indentation was consistent.

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

Does anyone have any ideas?

Drew
  • 75,699
  • 9
  • 109
  • 225
Krazy Glew
  • 242
  • 2
  • 8
  • 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 `defun`s. – Harald Hanche-Olsen Oct 31 '14 at 08: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

3 Answers3

7

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

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

Stefan
  • 26,154
  • 3
  • 46
  • 84
  • 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 '21 at 01: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 '21 at 13:55
5

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")
       ,@forms)))

Then you can define your functions like this:

(my-defun test (a)
  "Description"
  "asodksad"
  "ok"
  (interactive)
  (+ 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.

Malabarba
  • 22,878
  • 6
  • 78
  • 163
  • 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
-1

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.

Jordon Biondo
  • 12,332
  • 2
  • 41
  • 62
  • 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. – Gilles 'SO- stop being evil' Nov 10 '14 at 00:56