emacs 上的多行 python 缩进

发布于 2024-09-29 16:23:05 字数 323 浏览 1 评论 0原文

我是一个 emacs 新手,我希望 emacs 能够像这样缩进我的代码

egg = spam.foooooo('vivivivivivivivivi')\
          .foooooo('emacs', 'emacs', 'emacs', 'emacs')

默认情况下不可能自动执行此操作(无需手动插入空格或抄送>),因为 emacs 总是缩进 4 个空格(除非我将多个参数拆分为多行)。

执行此操作的最佳方法是什么?

PS:如果这是一个坏主意(针对 PEP 8 或其他),请告诉我

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 not 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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

不及他 2024-10-06 16:23:05

我同意 Aaron 关于您风格选择的可取性的观点,但由于我也同意他的观点,即 Emacs Lisp 很有趣,所以我将描述您如何实现这一点。

Emacs python-mode 计算函数 python-calculate-indentation 以及相关部分处理连续线被深埋在函数内部,没有简单的方法来配置它。

所以我们有两个选择:

  1. 用我们自己的版本替换整个 python-calculate-indentation (每当 python-mode 改变时维护噩梦);或
  2. 建议”函数python-calculate-indentation:也就是说,将其包装在我们自己的函数中来处理我们感兴趣的情况,否则遵循原始函数。

在这种情况下,选项(2)似乎是可行的。那么让我们一起努力吧!首先要做的是阅读建议手册< /a> 这表明我们的建议应该如下所示:

(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 用原始函数替换。来自 Python 背景的你可能会问,“为什么不采用这种装饰器风格呢?” 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- 的调用中游览以确保该点不会漂移。

我们可以通过向后跳过标记或括号表达式(Lisp 称之为“S 表达式”或“sexps”)来找到需要对齐的点,直到找到该点,或者到达该点的开头。陈述。在缓冲区的受限部分进行搜索的一个很好的 Emacs 惯用法是 缩小缓冲区以仅包含我们想要的部分:

(narrow-to-region (point)
                  (save-excursion
                    (end-of-line -1)
                    (python-beginning-of-statement)
                    (point)))

然后继续向后跳过 sexp,直到找到点,或者直到 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))

另一个问题是跳出循环并指示成功。我们可以通过声明一个命名的块然后调用 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错误处理阻止并退出。享受!

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:

  1. Replace the whole of python-calculate-indentation with our own version (a maintenance nightmare whenever python-mode changes); or
  2. "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!

嘿咻 2024-10-06 16:23:05

这非常难看,并且需要你编写一些 emacs lisp。我需要学习 emacs lisp,所以如果它不是那么难看的话,我可能会愿意这样做。但它是,而我不是。看起来你要学习 emacs lisp :)(如果你真的想这样做的话)。我有点嫉妒。无论如何,你说过告诉你这是一个坏主意是一个可以接受的答案,所以这里是:

这是一个糟糕的风格选择。不是

egg = spam.foo('viviviv')
egg = egg.foo('emacs', 'emacs', 'emacs')

更容易阅读吗?

虽然不是专门针对 PEP 8,但提到应尽量减少行继续字符的使用。另外,这最明确、最客观地违背了 PEP 8 的精神。我只是不知道怎么办;)

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文