1

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?

Daniel Krenn
  • 251
  • 2
  • 9

2 Answers2

0

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)
Andrzej Pronobis
  • 33,828
  • 17
  • 76
  • 92
  • It seems that "advice-add" only exists from 24.4 on; I have 24.3.1 :( – Daniel Krenn Aug 18 '15 at 17:06
  • Please see the advice converted to the "old" advices – Andrzej Pronobis Aug 19 '15 at 02:53
  • In order to indentify the indent of a previous non-empty line in string (beginning-of-line) (backward-sexp) will not be sufficient. Words are sexp in string. Check for non-empty and use back-to-indentation/current-column. – Andreas Röhler Aug 19 '15 at 05:42
  • That's perfectly fine, it just has to be any position on the previous non empty line. It does not have to be the point of indentation. – Andrzej Pronobis Aug 20 '15 at 15:37
  • Copied "older Emacs code" to my .emacs, but not change in behavior :( – Daniel Krenn Aug 25 '15 at 06:50
  • Could you try executing this after you open a python file? It might be the case that your python-mode is not yet loaded. You should also be able to test it by adding `(require 'python)` before the advices. – Andrzej Pronobis Aug 25 '15 at 18:30
  • I've tried the following: 1. open a Python file with content of original posting 2. execute our code for older Emacs in my .emacs (by marking it and evaluating region) 3. tried to press TAB in the Python file with no effect different from what already was. Any ideas? – Daniel Krenn Oct 17 '15 at 13:11
0

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.

Andreas Röhler
  • 4,804
  • 14
  • 18
  • Thanks (but not what I will use, since I have a lot of smaller multi-line strings; thus it seems unpracticable to use a separate buffer). – Daniel Krenn Aug 25 '15 at 06:48