How to configure indentation in emacs lua-mode

emacslualua-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(),
}

Best Answer

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.

Related Topic