简体   繁体   中英

How to configure indentation in emacs lua-mode?

Complete emacs newbie here.

I'm using emacs 23.1.1 on Ubuntu with emacs starter kit . I primarily work in the lua-mode (installed with package-install lua-mode ).

I need to tune how indentation works, so it would match my coding guidelines.

The guidelines are:

  • tabs-to-spaces;
  • two spaces per indent;
  • 80 chars per line maximum, without trailing spaces.

Example:

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

What I get with emacs if I don't try to fight with its auto-indent:

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

Update:

(This belongs to a comment, but since it needs extra formatting, I have to place it here.)

If I try solution by Thomas, I get this:

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

Note that end is indented with a tab and four spaces. Does not quite work...

Update 2:

This thing is also gets indented in the wrong way:

local bar = foo(
    "one",
    "two",
   baz(), -- Note three spaces
   "quo"
)

It should be:

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

Update 3:

Third case of the wrong indentation:

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

  local t = 5 -- This line should not be indented, 
              -- also note tab between local and t.

Update 4:

Here is what I get with the current version from Thomas:

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

            local bar = 5 -- Emacs put \t before 5

            local zzz = foo( -- Emacs put \t before foo
                "one", -- Pressed TAB here twice
                "two",
               three(),
               "four"
            )

Except where explicitly noted, I did not do anything for indentation, only typed in the code and pressed RETURN at the end of each line. I did not actually type any comments.

It should look as follows:

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

local bar = 5

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

Update 5:

One more wrong indentation case:

local foo =
{
bar(); -- Did press a TAB here, but closing brace killed it
baz;
}

Should be:

local foo =
{
  bar();
  baz;
}

Update 6:

For the sake of completeness, here is what I get with the current Git HEAD of lua-mode , without Thomas's configuration tuning:

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

local bar = 5

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

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

With tuning:

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

            local bar = 5

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

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

To match my coding guidelines, it should look as follows:

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

local bar = 5

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

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

Okay, let's give this another try... After browsing through the source code of lua-mode, I've come up with the following approach.

The reason for the admittedly strange default indentation is a function called "lua-calculate-indentation" which computes the column to which to indent the current line. Unfortunately, the values returned by it do not match your desired specification.

For instance, if you enter a single line into a fresh .lua file like this one:

local foo = function()

and hit enter to move the point to the second line, you can invoke the above function by typing M-: (lua-calculate-indentation) . The result is 15, which means that lua-mode will indent the second to column 15. This is the reason for the unorthodox indentation you've described and exemplified in your original question.

Now, to fix this I suggest re-defining the function "lua-calculate-indentation" so that it returns the indentation you want. For this, put the following code into an otherwise empty file, and save it under the name "my-lua.el" in the same directory where "lua-mode.el" lives.

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

This code sets the indentation level to two spaces (instead of 3), modifies a regular expression that detects if a statement stretches over multiple lines, and finally redefines the indentation function using an auxiliary.

All that's left to do is make sure this code is actually loaded. That must happen after the original lua-mode is loaded, or else that code would re-install the original indentation function.

The way we do that here is a little hacky: we install a call-back function that is invoked each time a buffer changes its major-mode to lua-mode. It then checks if the auxiliary function mentioned before is defined - if not, it loads "my-lua.el". That is a little fragile, but as long as you don't play around with the lua source code, you should be fine.

Add the following lines to your ~/emacs.d/agladysh.el file (assuming that "agladysh" is your username):

(add-hook 'lua-mode-hook 
          (lambda () (unless (fboundp 'lua-calculate-indentation-right-shift-next)
                       (load-file (locate-file "my-lua.el" load-path)))))

I assume that lua-mode is on your load-path which it should be if you followed lua-mode's installation instructions.

I hope that it works for you this time, if not, let me know.

I know it's been a while since this was asked, but I just wanted to point out that this is still an issue, with lua-mode installed via the Emacs package system.

However, the latest version on GitHub works very well, didn't notice any indentation weirdness. All you have to do to conform with the Lua style guide is to set indent-tabs-mode to nil and lua-indent-level to 2 .

If you enter the following code into .emacs file in your home directory, it will make lua-mode (and only lua-mode) behave the following way:

  • If you press ENTER, a newline will be inserted and by default the next line will be indented like the previous line.
  • Whenever you press TAB to indent the line, point either jumps to the first non-whitespace character of the line or, if the line is empty of point is already at that character, two spaces are inserted.

Especially the latter may not be what you want, but perhaps its a first approximation.

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

Restart Emacs for these changes to take effect.

A cleaner way to do this was added in 2019 , in the form of two lua-indent- variables. This gets us almost there, but it still double-indents nested blocks for some reason. Adding a little advice hack finishes the job.

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

I can't help much right now - I have a deadline in two days 8-( - but here is what I use in my .emacs to make lua-mode usable for me...

(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?

I indent my code in an unusual way - see the example below - and so I've disciplined myself to only press TAB when lua-mode can infer the right indentation correctly from the lines above...

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

I'm the maintainer (but not author) of lua-mode.el. As I'm a lot less fluent in Emacs Lisp than other contributors to this thread, I welcome patches. I'd just like to point out that there's nothing weird or incorrect about the default rules: the idea, as far as I can see, is simply that when you're in an anonymous function, the indentation should take the function keyword as its left margin. This makes sense when you consider using function expressions in other places, eg as function parameters.

So, one simple workaround is not to write

local f = function...

but

local function f...

Unless perhaps you're working with a version of Lua that predates the "local function" syntax.

Having said that, I can see why you might want to indent differently. In this case it seems to me reasonable to have a configuration variable lua-indent-function-from-function-keyword (better name, anyone?), and I'd be happy to accept a patch that implemented it.

I think a lot of what you're looking for can be found in the emacs manual on custom C indentation definitions which falls under general indentation engine descriptions.

You can make it do anything you can imagine, which would be strongly preferable to just doing anything you imagine.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM