在Emacs中Python多行缩进
我刚开始学用emacs,想让emacs能像这样自动缩进我的代码
egg = spam.foooooo('vivivivivivivivivi')\
.foooooo('emacs', 'emacs', 'emacs', 'emacs')
默认情况下,这个功能是不能自动实现的(也就是说不能不手动插入空格或者按C-c >),因为emacs总是缩进4个空格(除非我把多个参数分到多行上)。
有什么好的方法可以做到这一点吗?
PS: 如果这个主意不好(比如违反了PEP 8之类的规范),请告诉我。
2 个回答
这看起来挺糟糕的,而且你需要写一些emacs lisp代码。我需要学习emacs lisp,如果它不是这么丑的话,我可能会愿意去做。但它就是这样,所以我不想。看起来你得去学emacs lisp了 :)(如果你真的想做这个的话)。我有点嫉妒。无论如何,你说告诉你这是个坏主意是可以接受的答案,所以我就说说:
这真是个糟糕的风格选择。难道
egg = spam.foo('viviviv')
egg = egg.foo('emacs', 'emacs', 'emacs')
不是更容易阅读吗?
虽然这并不完全违反PEP 8的规定,但提到应该尽量少用换行符。此外,这绝对是违背了PEP 8的精神。我只是有点不确定怎么说;)
我同意Aaron关于你风格选择的看法,但因为我也同意他认为Emacs Lisp很有趣,所以我来描述一下你可以如何实现这个功能。
Emacs的python-mode
有一个函数叫python-calculate-indentation
,它负责计算一行的缩进。而处理续行的相关部分深藏在这个函数里面,没有简单的方法可以配置它。
所以我们有两个选择:
- 用我们自己的版本完全替换
python-calculate-indentation
(每次python-mode
更新时都得维护,真是个噩梦);或者 - 对
python-calculate-indentation
进行“建议”:也就是说,把它包裹在我们自己的函数里,处理我们感兴趣的情况,其余的则交给原来的函数。
在这种情况下,选择(2)似乎是可行的。所以我们就这样做吧!第一步是阅读关于建议的手册,它建议我们的建议应该像这样:
(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))
这里的ad-do-it
是一个魔法标记,defadvice
会用原始函数替换它。你可能会问:“为什么不使用装饰器风格呢?”Emacs的建议机制设计是为了(1)将建议与原始函数很好地分开;(2)允许一个函数有多个不需要合作的建议;(3)让你可以单独控制哪些建议是开启或关闭的。你当然可以想象在Python中写出类似的东西。
下面是如何判断当前行是否是点状续行:
(beginning-of-line)
(when (and (python-continuation-line-p)
(looking-at "\\s-*\\."))
;; Yup, it's a dotted continuation line. (TODO)
...)
这里有一个问题:调用beginning-of-line
实际上会把光标移动到行的开头。哎呀。我们在计算缩进时不想移动光标。所以我们最好把这个调用包裹在save-excursion
中,以确保光标不会乱跑。
我们可以通过向后跳过标记或括号表达式(Lisp称之为“S表达式”或“sexps”)来找到需要对齐的点,直到找到点或者到达语句的开头。一个好的Emacs习惯是在缓冲区的限制部分进行搜索,可以通过缩小缓冲区,只包含我们想要的部分:
(narrow-to-region (point)
(save-excursion
(end-of-line -1)
(python-beginning-of-statement)
(point)))
然后继续向后跳过sexps,直到找到点,或者backward-sexp
停止进展:
(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)))
这里的ad-return-value
是一个魔法变量,defadvice
用它来获取被建议函数的返回值。虽然看起来有点丑,但很实用。
现在这里有两个问题。第一个是backward-sexp
在某些情况下可能会报错,所以我们最好捕获这个错误:
(ignore-errors (backward-sexp))
另一个问题是如何退出循环并指示成功。我们可以通过声明一个命名的block
来同时做到这两点,然后调用return-from
。块和退出是Common Lisp的特性,所以我们需要(require 'cl)
。
让我们把所有的内容整合在一起:
(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)
我不会说这是最好的做法,但它展示了一些中等复杂的Emacs和Lisp特性:建议、远足、缩小、跳过sexps、错误处理、块和退出。祝你玩得开心!