Emacs24 and python-mode: indention in docstrings

933 views Asked by At

I have the following code/text:

def f():
    """
    Return nothing.

    .. NOTE::

        First note line
second note line

In Emacs23 (23.4.1) I was able to press TAB in the last line ("second note line"; nomatter how this line was indented) and it was aligned correctly like this:

def f():
    """
    Return nothing.

    .. NOTE::

        First note line
        second note line

I.e., it uses the previous line and indents the following line in the same way.

Now in Emacs24 (24.3.1) this does not work anymore and it is aligned like this:

def f():
    """
    Return nothing.

    .. NOTE::

        First note line
    second note line

I.e. it aligns the multi-line string block, but does not depend on the previous line.

It affects only docstrings; code is indented as I want. I am using python-mode. How can I change this, so that pressing TAB aligns the block correctly?

2

There are 2 answers

7
Andrzej Pronobis On

Python mode has changed quite a bit between Emacs 23 and 24. There is no configuration that would allow you to do what you want.

But, Emacs is quite flexible and you can advise the (python-indent-context) function to make it return a different result that will lead to the behavior you want. The function (python-indent-context) returns a character at which the indentation is measured and used for indenting the current line. By default, when inside a string, it returns the point where the beginning of the string resides. Thus, your line will be indented to the indentation of the start of the string. We can easily modify it to return a point in the previous non-empty line instead, for instance like this:

(defun python-fake-indent-context (orig-fun &rest args)
  (let ((res (apply orig-fun args)))  ; Get the original result
    (pcase res
      (`(:inside-string . ,start)  ; When inside a string
       `(:inside-string . ,(save-excursion  ; Find a point in previous non-empty line
                             (beginning-of-line)
                             (backward-sexp)
                             (point))))
      (_ res))))  ; Otherwise, return the result as is

;; Add the advice
(advice-add 'python-indent-context :around #'python-fake-indent-context)

The same effect can be achieved using the old defadvice for older Emacs:

(defadvice python-indent-context (after python-fake-indent-context)
  (pcase ad-return-value
    (`(:inside-string . ,start)  ; When inside a string
     (setq ad-return-value       ; Set return value
           `(:inside-string . ,(save-excursion  ; Find a point in previous non-empty line
                                 (beginning-of-line)
                                 (backward-sexp)
                                 (point)))))))
(ad-activate 'python-indent-context)
1
Andreas Röhler On

What about editing the section de-stringified in a separate buffer? That would allow python-mode with all its facilities.

Here a first draft - original string will be stored in kill-ring:

(defun temp-edit-docstring ()
  "Edit docstring in python-mode. "
  (interactive "*")
  (let ((orig (point))
    (pps (parse-partial-sexp (point-min) (point))))
    (when (nth 3 pps)
      (let* (;; relative position in string
         (relpos (- orig (+ 2 (nth 8 pps))))
         (beg (progn (goto-char (nth 8 pps))
             (skip-chars-forward (char-to-string (char-after)))(push-mark)(point)))

         (end (progn (goto-char (nth 8 pps))
             (forward-sexp)
             (skip-chars-backward (char-to-string (char-before)))
             (point)))

         (docstring (buffer-substring beg end)))
    (kill-region beg end)
    (set-buffer (get-buffer-create "Edit docstring"))
    (erase-buffer)
    (switch-to-buffer (current-buffer))
    (insert docstring)
    (python-mode)
    (goto-char relpos)))))

When ready, copy the contents back into original buffer. Which remains to be implemented still.