multi lines python indentation on emacs
Im an emacs newbie, I want emacs to be able to indent my code like this
egg = spam.foooooo('vivivivivivivivivi')\
.foooooo('emacs', 'emacs', 'emacs', 'emacs')
It's no开发者_如何学编程t possible to do this automatically by default (without manually inserting spaces or C-c >), since emacs always indents 4 spaces (unless Im splitting multiple arguments over multiple lines).
Whats the best approach to do this?
PS: If this is a bad idea (against PEP 8 or something) please do tell me
I agree with Aaron about the desirability of your stylistic choice, but since I also agree with him that Emacs Lisp is fun, I'll describe how you might go about implementing this.
Emacs python-mode
computes the indentation of a line in the function python-calculate-indentation
and the relevant section for handling continuation lines is buried deep inside the function, with no easy way to configure it.
So we have two options:
- Replace the whole of
python-calculate-indentation
with our own version (a maintenance nightmare wheneverpython-mode
changes); or - "Advise" the function
python-calculate-indentation
: that is, wrap it in our own function that handles the case we're interested in, and otherwise defers to the original.
Option (2) seems just about doable in this case. So let's go for it! The first thing to do is to read the manual on advice which suggests that our advice should look like this:
(defadvice python-calculate-indentation (around continuation-with-dot)
"Handle continuation lines that start with a dot and try to
line them up with a dot in the line they continue from."
(unless
(this-line-is-a-dotted-continuation-line) ; (TODO)
ad-do-it))
Here ad-do-it
is a magic token that defadvice
substitutes with the original function. Coming from a Python background you might well ask, "why not do this decorator-style?" The Emacs advice mechanism is designed (1) to keep advice well separated from the original; and (2) to have multiple pieces of advice for a single function that don't need to co-operate; (3) to allow you individual control over which pieces of advice are turned on and off. You could certainly imagine writing something similar in Python.
Here's how to tell if the current line is a dotted continuation line:
(beginning-of-line)
(when (and (python-continuation-line-p)
(looking-at "\\s-*\\."))
;; Yup, it's a dotted continuation line. (TODO)
...)
There's one problem with this: that call to beginning-of-line
actually moves point to the beginning of the line. Oops. We don't want to move point around when merely calculating indention. So we better wrap this up in a call to save-excursion
to make sure that point doesn't go a-wandering.
We can find the dot that we need to line up with by skipping backwards over tokens or parenthesized expressions (what Lisp calls "S-expressions" or "sexps") until either we find the dot, or else we get to the start of the statement. A good Emacs idiom for doing a search in a restricted part of the buffer is to narrow the buffer to contain just the part we want:
(narrow-to-region (point)
(save-excursion
(end-of-line -1)
(python-beginning-of-statement)
(point)))
and then keep skipping sexps backwards until we find the dot, or until backward-sexp
stops making progress:
(let ((p -1))
(while (/= p (point))
(setq p (point))
(when (looking-back "\\.")
;; Found the dot to line up with.
(setq ad-return-value (1- (current-column)))
;; Stop searching backward and report success (TODO)
...)
(backward-sexp)))
Here ad-return-value
is a magic variable that defadvice
uses for the return value from the advised function. Ugly but practical.
Now there are two problems with this. The first is that backward-sexp
can signal an error in certain circumstances, so we better catch that error:
(ignore-errors (backward-sexp))
The other problem is that of breaking out of the loop and also indicating success. We can do both at once by declaring a named block
and then calling return-from
. Blocks and exits are Common Lisp features so we'll need to (require 'cl)
Let's put it all together:
(require 'cl)
(defadvice python-calculate-indentation (around continuation-with-dot)
"Handle continuation lines that start with a dot and try to
line them up with a dot in the line they continue from."
(unless
(block 'found-dot
(save-excursion
(beginning-of-line)
(when (and (python-continuation-line-p)
(looking-at "\\s-*\\."))
(save-restriction
;; Handle dotted continuation line.
(narrow-to-region (point)
(save-excursion
(end-of-line -1)
(python-beginning-of-statement)
(point)))
;; Move backwards until we find a dot or can't move backwards
;; any more (e.g. because we hit a containing bracket)
(let ((p -1))
(while (/= p (point))
(setq p (point))
(when (looking-back "\\.")
(setq ad-return-value (1- (current-column)))
(return-from 'found-dot t))
(ignore-errors (backward-sexp))))))))
;; Use original indentation.
ad-do-it))
(ad-activate 'python-calculate-indentation)
I won't claim that this is the best way to do this, but it illustrates a bunch of moderately tricky Emacs and Lisp features: advice, excursions, narrowing, moving over sexps, error handling, blocks and exits. Enjoy!
That's pretty ugly and would require you to write some emacs lisp. I need to learn emacs lisp so if it wasn't so ugly, I would probably be up for doing it. But it is and I'm not. Looks like you get to learn emacs lisp :) (if you actually want to do this). I'm sort of jealous. At any rate, you said that informing you that this is a bad idea was an acceptable answer so here goes:
That's a terrible stylistic choice. Isn't
egg = spam.foo('viviviv')
egg = egg.foo('emacs', 'emacs', 'emacs')
easier to read?
While not specifically against PEP 8, it is mentioned that use of the line continuation character should be kept to a minimum. Also, this most definitively and objectively goes against the spirit of PEP 8. I'm just not sure how ;)
精彩评论