如何在Emacs的lua-mode中配置缩进?

19

我是一个完全的emacs新手。

我正在Ubuntu上使用emacs 23.1.1和emacs starter kit。我主要在lua-mode中工作(使用package-install lua-mode安装)。

我需要调整缩进方式,使其符合我的编码规范。

规范如下:

  • 制表符转换为空格;
  • 每次缩进两个空格;
  • 每行最多80个字符,不包括尾随空格。

例如:

local foo = function()
  print("Hello, world!")
end

如果我不试图与其自动缩进抗争,我得到的emacs结果如下:

local foo = function()
               print("Hello, world")
end

更新:

(这属于评论,但由于需要额外的格式化,我必须将其放在这里。)

如果我尝试Thomas的解决方案,我会得到以下结果:

local foo = function()
               print("Hello, world")
        end

请注意,end缩进了制表符和四个空格。 还不太对...

更新2:

这个东西的缩进方式也不对:

local bar = foo(
    "one",
    "two",
   baz(), -- 注意三个空格
   "quo"
)  

它应该是:

local bar = foo(
    "one",
    "two",
    baz(),
    "quo"
  )

更新3:

第三种错误的缩进情况:

local bar = foo(
    "one",
    "two"
  )
local t = 5 -- 这一行不应该缩进, -- 还要注意local和t之间的制表符。

更新4:

以下是我从Thomas的当前版本中得到的结果:

local foo = function()
               print("Hello, world")
        end
local bar = 5 -- Emacs在5前放了\t
local zzz = foo( -- Emacs在foo前放了\t "one", -- 在这里按两次TAB "two", three(), "four" )

除非明确说明,否则我没有做任何缩进,只是输入代码并在每行末按RETURN。我实际上没有输入任何注释。

它应该如下所示:

local foo = function()
  print("Hello, world")
end

local bar = 5

local zzz = foo(
    "one",
    "two",
    three(),
    "four"
  )

更新5:

另一个错误的缩进案例:

本应为:
local foo =
{
  bar();
  baz;
}

更新6:

为了完整起见,以下是我使用lua-mode的最新Git HEAD得到的结果,未使用Thomas的配置调整:

local foo = function()
               print("Hello, world!")
            end

local bar = 5

local foo = bar(
bar,
   baz(),
   quo(),
aaa
)

local t =
{
"one",
two(),
}

调整后的代码如下:

local foo = function()
  print("Hello, world!")
end

local bar = 5

local foo = bar(
  bar,
  baz(),
  quo(),
  aaa
)

local t =
{
  "one",
  two(),
}

根据我的编码准则,代码应该如下所示:

local foo = function()
  print("Hello, world!")
end

local bar = 5

local foo = bar(
  bar,
  baz(),
  quo(),
  aaa
)

local t =
{
  "one",
  two(),
}

@AG: 关于括号的问题:我认为这是lua-mode中的一个bug。它们直接调用了自己的缩进函数(将闭合括号放在行首),而没有通过调用绑定到indent-line-function的标准机制来进行缩进。 - Thomas
@AG:C-h k ENTER 会给你绑定到回车键的函数是什么? - Thomas
@Thomas:RET 运行命令 my-lua-enter,该命令是 `agladysh.el' 中交互式 Lisp 函数。它绑定到 RET 键。插入一个换行符并将该行缩进,类似于上一行非空行的缩进方式。 - Alexander Gladysh
@Thomas:还有一些lua2-mode:http://www.enyo.de/fw/software/lua-emacs/lua2-mode.html 但我从未成功地让它工作过... - Alexander Gladysh
@Thomas:添加了一个例子。 - Alexander Gladysh
显示剩余18条评论
7个回答

9

好的,让我们再试一次...浏览了lua-mode的源代码后,我提出了以下方法。

诚然,引人注目的默认缩进方式之所以奇怪,是因为一个叫做"lua-calculate-indentation"的函数计算当前行应该缩进到哪一列。不幸的是,它返回的值与您期望的规格不匹配。

例如,如果您在一个新的.lua文件中输入单行内容:

local foo = function()

按enter键将光标移动到第二行,您可以通过输入 M-: (lua-calculate-indentation) 来调用上述函数。结果是15,这意味着lua-mode将缩进第二列到第15列。这就是你在原始问题中描述和举例的非正统缩进的原因。

现在,为了解决这个问题,我建议重新定义函数“lua-calculate-indentation”,以便返回您想要的缩进。为此,请将以下代码放入除空文件中并将其保存在名为“my-lua.el”的文件中,在“lua-mode.el”所在的同一目录下保存。

;; use an indentation width of two spaces
(setq lua-indent-level 2)

;; Add dangling '(', remove '='
(setq lua-cont-eol-regexp
      (eval-when-compile
        (concat
         "\\((\\|\\_<"
         (regexp-opt '("and" "or" "not" "in" "for" "while"
                       "local" "function") t)
         "\\_>\\|"
         "\\(^\\|[^" lua-operator-class "]\\)"
         (regexp-opt '("+" "-" "*" "/" "^" ".." "==" "<" ">" "<=" ">=" "~=") t)
         "\\)"
         "\\s *\\=")))

(defun lua-calculate-indentation (&optional parse-start)
  "Overwrites the default lua-mode function that calculates the
column to which the current line should be indented to."
  (save-excursion
    (when parse-start
      (goto-char parse-start))

    ;; We calculate the indentation column depending on the previous
    ;; non-blank, non-comment code line. Also, when the current line
    ;; is a continuation of that previous line, we add one additional
    ;; unit of indentation.
    (+ (if (lua-is-continuing-statement-p) lua-indent-level 0)
       (if (lua-goto-nonblank-previous-line)
           (+ (current-indentation) (lua-calculate-indentation-right-shift-next))
         0))))

(defun lua-calculate-indentation-right-shift-next (&optional parse-start)
  "Assuming that the next code line is not a block ending line,
this function returns the column offset that line should be
indented to with respect to the current line."
  (let ((eol)
        (token)
        (token-info)
        (shift 0))
    (save-excursion
      (when parse-start
        (goto-char parse-start))

      ; count the balance of block-opening and block-closing tokens
      ; from the beginning to the end of this line.
      (setq eol (line-end-position))
      (beginning-of-line)
      (while (and (lua-find-regexp 'forward lua-indentation-modifier-regexp)
                  (<= (point) eol)
                  (setq token (match-string 0))
                  (setq token-info (assoc token lua-block-token-alist)))
        ; we found a token. Now, is it an opening or closing token?
        (if (eq (nth 2 token-info) 'open)
            (setq shift (+ shift lua-indent-level))
          (when (or (> shift 0)
                    (string= token ")"))
            (setq shift (- shift lua-indent-level))))))
    shift))

这段代码将缩进级别设置为两个空格(而不是3个),修改了检测语句是否跨越多行的正则表达式,最后使用辅助函数重新定义缩进函数。
现在只需要确保这段代码被加载。这必须发生在原始lua-mode加载之后,否则该代码将重新安装原始缩进函数。
我们在这里使用的方法有点巧妙:我们安装了一个回调函数,每当一个缓冲区将其major-mode更改为lua-mode时,它就会被调用。然后它检查前面提到的辅助函数是否已定义 - 如果没有,它就加载"my-lua.el"。 这有点脆弱,但只要你不玩lua源代码,就应该没问题。
请将以下行添加到您的~/emacs.d/agladysh.el文件中(假设"agladysh"是您的用户名):
(add-hook 'lua-mode-hook 
          (lambda () (unless (fboundp 'lua-calculate-indentation-right-shift-next)
                       (load-file (locate-file "my-lua.el" load-path)))))

我假设lua-mode已经在你的load-path路径中,如果你按照lua-mode的安装说明进行操作的话,它应该已经在其中了。

希望这次能够成功,如果不行,请告诉我。


这通常很有效 - 谢谢。为了使其适用于表格,需要进行哪些更改?即打开一个带有{的表格,但下一行缩进到与{相同的列而不是2个空格。 - davidA
@meowsqueak 最新版本的lua-mode(github.com/immerrr/lua-mode)在我的看法中对表格进行了合理的缩进,即表格内容左对齐。这难道不是您想要的吗? - Thomas
@Thomas,我刚试了最新的提交,对于简单的表格你是对的。我刚注意到我看到的问题实际上是由序列({(将内联表传递给函数)引起的——在我看来,如果这样的表格从前一行开始,则下一行相对于打开(下面的列进行缩进,而不是与前面的代码左对齐。 - davidA
@meowsqueak 我建议你直接联系 lua-mode 的维护者,因为这看起来更像是一个 bug 而不是配置问题。 - Thomas
@Thomas 已确认,我会做的。谢谢。 - davidA
显示剩余3条评论

5
我知道这个问题问得有点久了,但我想指出这仍然是一个问题,即通过Emacs包系统安装的lua-mode。
然而,在GitHub上的最新版本效果非常好,并没有注意到任何缩进怪异。要符合Lua样式指南,您只需要将indent-tabs-mode设置为nil,并将lua-indent-level设置为2

2
如果你将以下代码输入到位于您主目录的.emacs文件中,它将使lua-mode(仅限lua-mode)按以下方式运行:
  • 如果按下ENTER,则会插入一个换行符,并且默认情况下下一行将像上一行一样缩进。
  • 每当你按TAB缩进行时,光标要么跳转到该行的第一个非空白字符,要么(如果该行为空或光标已经在该字符处)插入两个空格。
特别是后者可能不是你想要的,但也许这是一个初步的近似值。
(defvar my-lua-indent 2
  "The number of spaces to insert for indentation")

(defun my-lua-enter ()
  "Inserts a newline and indents the line like the previous
non-empty line."
  (interactive)
  (newline)
  (indent-relative-maybe))

(defun my-lua-indent ()
  "Moves point to the first non-whitespace character of the
line if it is left of it. If point is already at that
position, or if it is at the beginning of an empty line,
inserts two spaces at point."
  (interactive)
  (when (looking-back "^\\s *")
    (if (looking-at "[\t ]")
        (progn (back-to-indentation)
               (when (looking-at "$")
                 (kill-line 0)
                 (indent-relative-maybe)
                 (insert (make-string my-lua-indent ? ))))
      (insert (make-string my-lua-indent ? )))))

(defun my-lua-setup ()
  "Binds ENTER to my-lua-enter and configures indentation the way
I want it. Makes sure spaces are used for indentation, not tabs."
  (setq indent-tabs-mode nil)
  (local-set-key "\r" 'my-lua-enter)
  (setq indent-line-function 'my-lua-indent))

;; add `my-lua-setup' as a call-back that is invoked whenever lua-mode
;; is activated.
(add-hook 'lua-mode-hook 'my-lua-setup)

重新启动Emacs以使这些更改生效。


由于我使用emacs starter kit,因此适当的位置是~/.emacs.d/<my-username>.el - Alexander Gladysh
@AG:我更新了缩进,对我来说运行良好。你能否编辑你的原始问题,解释一下你遇到了什么问题? - Thomas

1
一种更加简洁的方法是在2019年添加,即使用两个lua-indent-变量。这几乎可以满足我们的需求,但由于某些原因,它仍会对嵌套块进行双重缩进。添加一些建议性的 hack 就可以完成工作了。
(setq lua-indent-nested-block-content-align nil)
(setq lua-indent-close-paren-align nil)

(defun lua-at-most-one-indent (old-function &rest arguments)
  (let ((old-res (apply old-function arguments)))
    (if (> old-res lua-indent-level) lua-indent-level old-res)))

(advice-add #'lua-calculate-indentation-block-modifier
            :around #'lua-at-most-one-indent)

0

我是lua-mode.el的维护者(但不是作者)。由于我在Emacs Lisp方面比其他贡献者要少得多,因此我欢迎补丁。我想指出的是,默认规则没有什么奇怪或不正确的地方:据我所见,这个想法很简单,即当您处于匿名函数中时,缩进应该以函数关键字作为其左边距。当您考虑在其他地方使用函数表达式时,例如作为函数参数时,这是有道理的。

因此,一个简单的解决方法是不写

local f = function...

而是写

local function f...

除非您使用的Lua版本早于“local function”语法。

话虽如此,我可以理解为什么您可能希望进行不同的缩进。在这种情况下,我认为有一个配置变量lua-indent-function-from-function-keyword(更好的名称,任何人?),如果实现了它,我将很高兴接受补丁。


@RT:嗨,鲁本,感谢你的光临。我在称缩进“怪异”时并不是要苛刻,但AG提供的示例似乎确实以一种不太正统的方式进行缩进。(请注意,AG的问题除了`function'的情况外还包括其他情况。)但这就是一个如此可配置的编辑器的好处,每个人都可以让它按照自己的喜好运作... - Thomas
@RT:确实感谢您的光临。我遵循的编码准则不赞成使用“local function f()”语法。 - Alexander Gladysh
@Thomas:我还没有遇到广泛推广的Lua格式化标准,因此讨论什么是“正统”可能是徒劳的。我再次重申:我欢迎补丁。我允许自己的编码标准受lua-mode的指导;我有幸没有被外部强加的标准所束缚。 - Reuben Thomas
@RT:你说的“(un-)orthodox”是对的。我应该更加谨慎地选择措辞。 - Thomas
@RT:我不懂Lua编程,因此我会犹豫是否提交真正的修补程序。更具体地说,我的答案是一个小技巧,用于解决特定的编码指南。不知道对其他人有多大帮助。 - Thomas

0

我现在帮不上太多忙 - 我有两天的截止日期 8-( - 但是这是我在我的 .emacs 中使用的,让 lua-mode 对我有用...

(setq lua-indent-level 2)
(setq lua-electric-flag nil)
(defun lua-abbrev-mode-off () (abbrev-mode 0))
(add-hook 'lua-mode-hook 'lua-abbrev-mode-off)
(setq save-abbrevs nil)   ;; is this still needed?

我以一种不寻常的方式缩进我的代码 - 请参见下面的示例 - 因此,我已经训练自己只有在lua-mode可以从上面的行正确推断出缩进时才按TAB键...

map = function (f, A, n)
    local B = {}                 -- TAB here doesn't work
    for i=1,(n or #A) do         -- TAB here works
      table.insert(B, f(A[i]))   -- TAB here works
    end                          -- TAB here works
    return B                     -- TAB here works
  end                            -- TAB here works

谢谢!我想要一个完整的修复。如果我必须与编辑器斗争,我最好使用我习惯斗争的那个。 - Alexander Gladysh

-1

我认为你所寻找的很多内容都可以在emacs自定义C缩进定义手册中找到,该手册属于一般缩进引擎描述。

你可以让它做任何你能想象到的事情,这比只是做你想象的任何事情要好得多。


这个答案不正确,自定义 C 缩进不会影响 lua-mode - npostavs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接