[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
bug#65672: [PATCH emacs 1/1] Add lua-ts-mode
From: |
~johnmuhl |
Subject: |
bug#65672: [PATCH emacs 1/1] Add lua-ts-mode |
Date: |
Thu, 31 Aug 2023 16:32:06 -0500 |
From: john muhl <jm@pub.pink>
* test/lisp/progmodes/lua-ts-mode-tests.el:
* test/lisp/progmodes/lua-ts-mode-resources/indent.ert:
* lisp/progmodes/lua-ts-mode.el: New file.
* lisp/progmodes/eglot.el (eglot-server-programs):
* lisp/progmodes/hideshow.el (hs-special-modes-alist):
Add support for lua-ts-mode.
* admin/notes/tree-sitter/build-module/batch.sh:
* admin/notes/tree-sitter/build-module/build.sh: Add Lua grammar.
* test/infra/Dockerfile.emba:
* test/infra/test-jobs.yml: Add lua-ts-mode tests.
---
admin/notes/tree-sitter/build-module/batch.sh | 1 +
admin/notes/tree-sitter/build-module/build.sh | 3 +
etc/NEWS | 4 +
lisp/progmodes/eglot.el | 2 +-
lisp/progmodes/hideshow.el | 1 +
lisp/progmodes/lua-ts-mode.el | 457 ++++++++++++++++++
test/infra/Dockerfile.emba | 1 +
test/infra/test-jobs.yml | 1 +
.../lua-ts-mode-resources/indent.erts | 152 ++++++
test/lisp/progmodes/lua-ts-mode-tests.el | 32 ++
10 files changed, 653 insertions(+), 1 deletion(-)
create mode 100644 lisp/progmodes/lua-ts-mode.el
create mode 100644 test/lisp/progmodes/lua-ts-mode-resources/indent.erts
create mode 100644 test/lisp/progmodes/lua-ts-mode-tests.el
diff --git a/admin/notes/tree-sitter/build-module/batch.sh
b/admin/notes/tree-sitter/build-module/batch.sh
index 1d4076564dc..685a35c269d 100755
--- a/admin/notes/tree-sitter/build-module/batch.sh
+++ b/admin/notes/tree-sitter/build-module/batch.sh
@@ -15,6 +15,7 @@ languages=(
'html'
'javascript'
'json'
+ 'lua'
'python'
'rust'
'toml'
diff --git a/admin/notes/tree-sitter/build-module/build.sh
b/admin/notes/tree-sitter/build-module/build.sh
index 0832875168b..969187b7f92 100755
--- a/admin/notes/tree-sitter/build-module/build.sh
+++ b/admin/notes/tree-sitter/build-module/build.sh
@@ -42,6 +42,9 @@ case "${lang}" in
"heex")
org="phoenixframework"
;;
+ "lua")
+ org="MunifTanjim"
+ ;;
"typescript")
sourcedir="tree-sitter-typescript/typescript/src"
grammardir="tree-sitter-typescript/typescript"
diff --git a/etc/NEWS b/etc/NEWS
index 9a98db8c83a..1a40a62615b 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -730,6 +730,10 @@ A major mode based on the tree-sitter library for editing
HEEx files.
A major mode based on the tree-sitter library for editing Elixir
files.
+---
+*** New major mode 'lua-ts-mode'.
+A major mode based on the tree-sitter library for editing Lua files.
+
---
** The highly accessible Modus themes collection has eight items.
The 'modus-operandi' and 'modus-vivendi' are the main themes that have
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 65daa0941d5..f249e3c81b4 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -237,7 +237,7 @@ chosen (interactively or automatically)."
(gdscript-mode . ("localhost" 6008))
((fortran-mode f90-mode) . ("fortls"))
(futhark-mode . ("futhark" "lsp"))
- (lua-mode . ,(eglot-alternatives
+ ((lua-mode lua-ts-mode) . ,(eglot-alternatives
'("lua-language-server"
"lua-lsp")))
(zig-mode . ("zls"))
((css-mode css-ts-mode)
diff --git a/lisp/progmodes/hideshow.el b/lisp/progmodes/hideshow.el
index b878986d7a4..78e39fad740 100644
--- a/lisp/progmodes/hideshow.el
+++ b/lisp/progmodes/hideshow.el
@@ -264,6 +264,7 @@ This has effect only if `search-invisible' is set to
`open'."
(java-ts-mode "{" "}" "/[*/]" nil nil)
(js-mode "{" "}" "/[*/]" nil)
(js-ts-mode "{" "}" "/[*/]" nil)
+ (lua-ts-mode "{\\|\\[\\[" "}\\|\\]\\]" "--" nil)
(mhtml-mode "{\\|<[^/>]*?" "}\\|</[^/>]*[^/]>" "<!--" mhtml-forward nil)
;; Add more support here.
))
diff --git a/lisp/progmodes/lua-ts-mode.el b/lisp/progmodes/lua-ts-mode.el
new file mode 100644
index 00000000000..76d1781b30e
--- /dev/null
+++ b/lisp/progmodes/lua-ts-mode.el
@@ -0,0 +1,457 @@
+;;; lua-ts-mode.el --- Major mode for editing Lua files -*- lexical-binding: t
-*-
+
+;; Copyright (C) 2023 Free Software Foundation, Inc.
+
+;; Author: John Muhl <jm@pub.pink>
+;; Created: June 27, 2023
+;; Keywords: lua languages tree-sitter
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+;;
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This package provides `lua-ts-mode' which is a major mode for Lua
+;; files that uses Tree Sitter to parse the language.
+;;
+;; This package is compatible with and tested against the grammar
+;; for Lua found at https://github.com/MunifTanjim/tree-sitter-lua
+
+;;; Code:
+
+(require 'comint)
+(require 'treesit)
+
+(eval-when-compile
+ (require 'cl-lib)
+ (require 'rx))
+
+(defcustom lua-ts-indent-offset 4
+ "Number of spaces for each indentation step in `lua-ts-mode'."
+ :type 'integer
+ :safe 'integerp
+ :group 'lua
+ :version "30.1")
+
+(defcustom lua-ts-luacheck-program "luacheck"
+ "Location of the Luacheck program."
+ :type 'string
+ :group 'lua
+ :version "30.1")
+
+(defcustom lua-ts-lua-manual
+ (if (file-readable-p "/usr/share/doc/lua/manual.html")
+ "file:///usr/share/doc/lua/manual.html" "")
+ "Location of the Lua `manual.html' file."
+ :type 'string
+ :safe 'stringp
+ :group 'lua
+ :version "30.1")
+
+(defcustom inferior-lua-buffer "*Lua*"
+ "Name of the inferior Lua buffer."
+ :type 'string
+ :safe 'stringp
+ :group 'lua
+ :version "30.1")
+
+(defcustom inferior-lua-interpreter "lua"
+ "Program to run in the inferior Lua process."
+ :type 'string
+ :safe 'stringp
+ :group 'lua
+ :version "30.1")
+
+(defcustom inferior-lua-switches "-i"
+ "Command line options for the inferior Lua process."
+ :type 'string
+ :safe 'stringp
+ :group 'lua
+ :version "30.1")
+
+(defcustom inferior-lua-startfile ""
+ "File to load into the inferior Lua process at startup."
+ :type 'string
+ :safe 'stringp
+ :group 'lua
+ :version "30.1")
+
+(defcustom inferior-lua-prompt-regexp "^>>?[[:blank:]]"
+ "Regular expression matching the prompt of the inferior Lua process."
+ :type 'regexp
+ :group 'lua
+ :version "30.1")
+
+(defvar lua-ts--builtins
+ '("assert" "collectgarbage" "coroutine" "debug" "dofile"
+ "error" "getmetatable" "io" "ipairs" "load" "loadfile"
+ "math" "next" "os" "package" "pairs" "pcall" "print"
+ "rawequal" "rawget" "rawlen" "rawset" "require" "select"
+ "setmetatable" "string" "table" "tonumber" "tostring"
+ "type" "utf8" "warn" "xpcall"
+ ;; methods for file handlers
+ "close" "flush" "lines" "read" "seek" "setvbuf" "write")
+ "Lua built-in functions for tree-sitter font-locking.")
+
+(defvar lua-ts--font-lock-settings
+ (treesit-font-lock-rules
+ :language 'lua
+ :feature 'bracket
+ '(["(" ")" "[" "]" "{" "}"] @font-lock-bracket-face)
+
+ :language 'lua
+ :feature 'delimiter
+ '(["," ";"] @font-lock-delimiter-face)
+
+ :language 'lua
+ :feature 'escape
+ '((escape_sequence) @font-lock-escape-face)
+
+ :language 'lua
+ :feature 'constant
+ '((variable_list
+ attribute: (attribute (["<" ">"] (identifier))))
+ @font-lock-constant-face)
+
+ :language 'lua
+ :feature 'operator
+ '(["and" "not" "or" "+" "-" "*" "/" "%" "^"
+ "#" "==" "~=" "<=" ">=" "<" ">" "=" "&"
+ "~" "|" "<<" ">>" "//" ".."]
+ @font-lock-operator-face
+ (vararg_expression) @font-lock-operator-face)
+
+ :language 'lua
+ :feature 'property
+ '((field name: (identifier) @font-lock-property-name-face)
+ (dot_index_expression
+ field: (identifier) @font-lock-property-use-face))
+
+ :language 'lua
+ :feature 'builtin
+ `(((identifier) @font-lock-builtin-face
+ (:match ,(regexp-opt lua-ts--builtins 'symbols)
+ @font-lock-builtin-face)))
+
+ :language 'lua
+ :feature 'function
+ '((function_call name: (identifier) @font-lock-function-call-face)
+ (function_call
+ name: (method_index_expression
+ method: (identifier) @font-lock-function-call-face))
+ (function_call
+ name: (dot_index_expression
+ table: (identifier) @font-lock-function-call-face)))
+
+ :language 'lua
+ :feature 'punctuation
+ '(["." ":"] @font-lock-punctuation-face)
+
+ :language 'lua
+ :feature 'variable
+ '((function_call
+ arguments: (arguments (identifier))
+ @font-lock-variable-use-face)
+ (function_call
+ name: (method_index_expression
+ table: (identifier) @font-lock-variable-use-face))
+ (goto_statement (identifier) @font-lock-variable-use-face))
+
+ :language 'lua
+ :feature 'assignment
+ '((variable_list (identifier) @font-lock-variable-name-face))
+
+ :language 'lua
+ :feature 'number
+ '((number) @font-lock-number-face)
+
+ :language 'lua
+ :feature 'keyword
+ '((break_statement) @font-lock-keyword-face
+ (true) @font-lock-constant-face
+ (false) @font-lock-constant-face
+ (nil) @font-lock-constant-face
+ ["and" "do" "else" "elseif" "end" "for" "function"
+ "goto" "if" "in" "local" "not" "or" "repeat"
+ "return" "then" "until" "while"]
+ @font-lock-keyword-face)
+
+ :language 'lua
+ :feature 'string
+ '((string) @font-lock-string-face)
+
+ :language 'lua
+ :feature 'comment
+ '((comment) @font-lock-comment-face
+ (hash_bang_line) @font-lock-comment-face)
+
+ :language 'lua
+ :feature 'definition
+ '((function_declaration
+ name: (identifier) @font-lock-function-name-face)
+ (parameters
+ name: (identifier) @font-lock-variable-name-face)
+ (label_statement) @font-lock-variable-name-face)
+
+ :language 'lua
+ :feature 'error
+ :override t
+ '((ERROR) @font-lock-warning-face))
+ "Tree-sitter font-lock settings for `lua-ts-mode'.")
+
+(defvar lua-ts--simple-indent-rules
+ `((lua
+ ((parent-is "chunk") column-0 0)
+ ((node-is "comment_end") column-0 0)
+ ((parent-is "block") parent-bol 0)
+ ((node-is "}") parent-bol 0)
+ ((node-is ")") parent-bol 0)
+ ((node-is "else_statement") parent-bol 0)
+ ((node-is "elseif_statement") parent-bol 0)
+ ((node-is "end") parent-bol 0)
+ ((node-is "until") parent-bol 0)
+ ((parent-is "for_statement") parent-bol lua-ts-indent-offset)
+ ((parent-is "function_declaration") parent-bol lua-ts-indent-offset)
+ ((parent-is "function_definition") parent-bol lua-ts-indent-offset)
+ ((parent-is "if_statement") parent-bol lua-ts-indent-offset)
+ ((parent-is "else_statement") parent-bol lua-ts-indent-offset)
+ ((parent-is "repeat_statement") parent-bol lua-ts-indent-offset)
+ ((parent-is "while_statement") parent-bol lua-ts-indent-offset)
+ ((parent-is "table_constructor") parent-bol lua-ts-indent-offset)
+ ((parent-is "arguments") parent-bol lua-ts-indent-offset)
+ ((parent-is "parameters") parent-bol lua-ts-indent-offset)
+ ((parent-is "ERROR") no-indent 0))))
+
+(defvar lua-ts--syntax-table
+ (let ((table (make-syntax-table)))
+ (modify-syntax-entry ?+ "." table)
+ (modify-syntax-entry ?- ". 12" table)
+ (modify-syntax-entry ?= "." table)
+ (modify-syntax-entry ?% "." table)
+ (modify-syntax-entry ?^ "." table)
+ (modify-syntax-entry ?~ "." table)
+ (modify-syntax-entry ?< "." table)
+ (modify-syntax-entry ?> "." table)
+ (modify-syntax-entry ?/ "." table)
+ (modify-syntax-entry ?* "." table)
+ (modify-syntax-entry ?\n ">" table)
+ (modify-syntax-entry ?\' "\"" table)
+ (modify-syntax-entry ?\" "\"" table)
+ table)
+ "Syntax table for `lua-ts-mode'.")
+
+(defun lua-ts--defun-name-function (node)
+ "Return the defun name of NODE.
+Return nil if there is no name or if NODE is not a defun node."
+ (let ((child (treesit-node-child-by-field-name node "name")))
+ (pcase (treesit-node-type node)
+ ((or "function_declaration" "function_definition")
+ (treesit-node-text child t))
+ ("variable_declaration"
+ (if child
+ (treesit-node-text child t)
+ (treesit-node-text
+ (treesit-node-child-by-field-name
+ (treesit-search-subtree node "assignment_statement" nil nil 1)
+ "name"))))
+ ("field"
+ (and (treesit-search-subtree node "function_definition" nil nil 1)
+ (treesit-node-text child t))))))
+
+(defun lua-ts--builtin-p (node)
+ "Return t if the NODE is a builtin or nil if not."
+ (let ((name (pcase (treesit-node-type node)
+ ("dot_index_expression"
+ (car (string-split (treesit-node-text node t) "\\." t)))
+ ("function_call"
+ (let* ((child (treesit-node-child-by-field-name node "name"))
+ (text (treesit-node-text child t)))
+ (if (string-match-p ":" text)
+ (cadr (string-split text ":" t))
+ text))))))
+ (and name (string-match-p (regexp-opt lua-ts--builtins 'words) name))))
+
+(defvar-local lua-ts--flymake-process nil)
+
+(defun lua-ts-flymake-luacheck (report-fn &rest _args)
+ "Luacheck backend for Flymake.
+Calls REPORT-FN directly."
+ (when (process-live-p lua-ts--flymake-process)
+ (kill-process lua-ts--flymake-process))
+ (let ((source (current-buffer)))
+ (save-restriction
+ (widen)
+ (setq lua-ts--flymake-process
+ (make-process
+ :name "lua-ts-flymake-luacheck"
+ :noquery t
+ :connection-type 'pipe
+ :buffer (generate-new-buffer " *lua-ts-flymake-luacheck*")
+ :command `(,lua-ts-luacheck-program
+ "--codes" "--ranges" "--formatter" "plain" "-")
+ :sentinel
+ (lambda (proc _event)
+ (when (eq 'exit (process-status proc))
+ (unwind-protect
+ (if (with-current-buffer source
+ (eq proc lua-ts--flymake-process))
+ (with-current-buffer (process-buffer proc)
+ (goto-char (point-min))
+ (cl-loop
+ while (search-forward-regexp
+ (rx (seq bol
+ (0+ alnum) ":"
+ (group (1+ digit)) ":"
+ (group (1+ digit)) "-"
+ (group (1+ digit)) ": "
+ (group (0+ nonl))
+ eol))
+ nil t)
+ for line = (string-to-number (match-string 1))
+ for beg = (string-to-number (match-string 2))
+ for end = (string-to-number (match-string 3))
+ for msg = (match-string 4)
+ for type = (if (string-match "^(W" msg)
+ :warning
+ :error)
+ when (and beg end)
+ collect (flymake-make-diagnostic source
+ (cons line beg)
+ (cons line (1+
end))
+ type
+ msg)
+ into diags
+ finally (funcall report-fn diags)))
+ (flymake-log :warning "Canceling obsolete check %s"
proc))
+ (kill-buffer (process-buffer proc)))))))
+ (process-send-region lua-ts--flymake-process (point-min) (point-max))
+ (process-send-eof lua-ts--flymake-process))))
+
+(defun lua-ts-documentation-at-point ()
+ "Show documentation of function at point in Lua manual."
+ (interactive)
+ (unless (string-blank-p lua-ts-lua-manual)
+ (let ((character-before (char-to-string (char-before)))
+ id)
+ (save-excursion
+ ;; When point is mid-word `treesit-thing-at-point'
+ ;; may return the parent node of the thing at point.
+ (unless (or (bolp)
+ (not (string-match-p "[[:alnum:]]" character-before)))
+ (backward-word))
+ (let ((node (treesit-thing-at-point 'builtin nil)))
+ (setq id (pcase (treesit-node-type node)
+ ("dot_index_expression" (treesit-node-text node t))
+ ("function_call"
+ (let* ((child (treesit-node-child-by-field-name node
"name"))
+ (name (treesit-node-text child t)))
+ (if (string-match-p ":" name)
+ (replace-regexp-in-string "^.*:" "file:" name)
+ name)))))))
+ (when id (browse-url (concat lua-ts-lua-manual "#pdf-" id))))))
+
+;;;###autoload
+(defun inferior-lua ()
+ "Run the Lua interpreter in an inferior process."
+ (interactive)
+ (let ((buffer (get-buffer-create inferior-lua-buffer))
+ (program inferior-lua-interpreter)
+ (switches inferior-lua-switches)
+ (startfile (unless (string-blank-p inferior-lua-startfile)
+ (expand-file-name inferior-lua-startfile))))
+ (make-comint-in-buffer program buffer program startfile switches)
+ (with-current-buffer buffer
+ (setq-local comint-input-ignoredups t
+ comint-prompt-read-only t
+ comint-prompt-regexp inferior-lua-prompt-regexp
+ comint-use-prompt-regexp t))
+ (pop-to-buffer buffer)))
+
+;;;###autoload
+(define-derived-mode lua-ts-mode prog-mode "Lua"
+ "Major mode for editing Lua files, powered by tree-sitter."
+ :group 'lua
+ :syntax-table lua-ts--syntax-table
+
+ (when (treesit-ready-p 'lua)
+ (treesit-parser-create 'lua)
+
+ (setq-local treesit-thing-settings
+ '((lua . ((builtin lua-ts--builtin-p)))))
+
+ ;; Comments.
+ (setq-local comment-start "--")
+ (setq-local comment-start-skip "--\\s-*")
+ (setq-local comment-end "")
+
+ ;; Font-lock.
+ (setq-local treesit-font-lock-settings lua-ts--font-lock-settings)
+
+ (setq-local treesit-font-lock-feature-list
+ '((comment definition)
+ (keyword property string)
+ (assignment builtin constant number)
+ (bracket
+ delimiter
+ escape
+ function
+ operator
+ punctuation
+ variable)))
+
+ ;; Indent.
+ (setq-local treesit-simple-indent-rules lua-ts--simple-indent-rules)
+
+ ;; Navigation.
+ (setq-local treesit-defun-name-function #'lua-ts--defun-name-function)
+
+ (setq-local treesit-defun-type-regexp
+ (rx (or "function_declaration" "function_definition")))
+
+ (setq-local treesit-sentence-type-regexp
+ (rx (or "do_statement" "while_statement" "repeat_statement"
+ "if_statement" "for_statement"
"variable_declaration")))
+
+ (setq-local treesit-sexp-type-regexp
+ (rx (or "arguments" "comment" "string" "table_constructor")))
+
+ ;; Imenu.
+ (setq-local treesit-simple-imenu-settings
+ `(("Variable" ,(rx bos "variable_declaration" eos) nil nil)
+ ("Function" ,(rx bos
+ (or "function_declaration"
+ "function_definition"
+ "field")
+ eos)
+ nil nil)))
+
+ ;; Which-function.
+ (setq-local which-func-functions (treesit-defun-at-point))
+
+ ;; Outline.
+ (setq-local outline-regexp
+ (rx (or "--[[" "do" "for" "if" "repeat" "while"
+ (seq (** 0 1 "local ") "function"))))
+
+ (treesit-major-mode-setup))
+
+ (add-hook 'flymake-diagnostic-functions #'lua-ts-flymake-luacheck nil
'local))
+
+(if (treesit-ready-p 'lua)
+ (add-to-list 'auto-mode-alist '("\\.lua\\'" . lua-ts-mode)))
+
+(provide 'lua-ts-mode)
+
+;;; lua-ts-mode.el ends here
diff --git a/test/infra/Dockerfile.emba b/test/infra/Dockerfile.emba
index 584e4444dc1..e29098ec270 100644
--- a/test/infra/Dockerfile.emba
+++ b/test/infra/Dockerfile.emba
@@ -126,6 +126,7 @@ RUN src/emacs -Q --batch \
(java "https://github.com/tree-sitter/tree-sitter-java") \
(javascript "https://github.com/tree-sitter/tree-sitter-javascript") \
(json "https://github.com/tree-sitter/tree-sitter-json") \
+ (lua "https://github.com/MunifTanjim/tree-sitter-lua") \
(python "https://github.com/tree-sitter/tree-sitter-python") \
(ruby "https://github.com/tree-sitter/tree-sitter-ruby") \
(tsx "https://github.com/tree-sitter/tree-sitter-typescript" "master"
"tsx/src") \
diff --git a/test/infra/test-jobs.yml b/test/infra/test-jobs.yml
index 2f6e0dab4d5..1f5d607eda4 100644
--- a/test/infra/test-jobs.yml
+++ b/test/infra/test-jobs.yml
@@ -580,6 +580,7 @@ test-src-inotify:
lisp/progmodes/go-ts-mode-tests.log
lisp/progmodes/heex-ts-mode-tests.log
lisp/progmodes/java-ts-mode-tests.log
+ lisp/progmodes/lua-ts-mode-tests.log
lisp/progmodes/ruby-ts-mode-tests.log
lisp/progmodes/typescript-ts-mode-tests.log
src/treesit-tests.log
diff --git a/test/lisp/progmodes/lua-ts-mode-resources/indent.erts
b/test/lisp/progmodes/lua-ts-mode-resources/indent.erts
new file mode 100644
index 00000000000..040225c8580
--- /dev/null
+++ b/test/lisp/progmodes/lua-ts-mode-resources/indent.erts
@@ -0,0 +1,152 @@
+Code:
+ (lambda ()
+ (setq indent-tabs-mode nil)
+ (setq lua-ts-indent-offset 2)
+ (lua-ts-mode)
+ (indent-region (point-min) (point-max)))
+
+Name: Basic Indent
+
+=-=
+ print(
+0,
+ 1
+)
+
+local function f(o)
+ if o.x > o.y then
+ return o.x
+elseif o.y > o.z then
+ return o.y
+ else
+return o.z
+ end
+end
+
+f({
+ x = 1,
+ y = 2,
+ z = 3,
+})
+
+;(function()
+return false
+)()
+=-=
+print(
+ 0,
+ 1
+)
+
+local function f(o)
+ if o.x > o.y then
+ return o.x
+ elseif o.y > o.z then
+ return o.y
+ else
+ return o.z
+ end
+end
+
+f({
+ x = 1,
+ y = 2,
+ z = 3,
+})
+
+;(function()
+ return false
+)()
+=-=-=
+
+Name: Argument Indent
+
+=-=
+function h(
+string,
+number,
+options)
+print(string, number, options)
+end
+
+local p = h(
+"sring",
+ 1000,
+ {
+cost = 2,
+length = 8,
+ parallelism = 4,
+})
+=-=
+function h(
+ string,
+ number,
+ options)
+ print(string, number, options)
+end
+
+local p = h(
+ "sring",
+ 1000,
+ {
+ cost = 2,
+ length = 8,
+ parallelism = 4,
+ })
+=-=-=
+
+Name: Continuation Indent
+
+=-=
+function f()
+ local str = [[
+ multi-line
+ string
+ ]]
+--[[
+multi-line
+comment
+ ]]
+return true
+end
+=-=
+function f()
+ local str = [[
+ multi-line
+ string
+ ]]
+ --[[
+multi-line
+comment
+ ]]
+ return true
+end
+=-=-=
+
+Name: Loop Indent
+
+=-=
+for k, v in pairs({}) do
+ print(k, v)
+end
+
+while n < 10 do
+n = n + 1
+end
+
+repeat
+z = z * 2
+ until z > 12
+=-=
+for k, v in pairs({}) do
+ print(k, v)
+end
+
+while n < 10 do
+ n = n + 1
+end
+
+repeat
+ z = z * 2
+until z > 12
+=-=-=
diff --git a/test/lisp/progmodes/lua-ts-mode-tests.el
b/test/lisp/progmodes/lua-ts-mode-tests.el
new file mode 100644
index 00000000000..62ec2652ba8
--- /dev/null
+++ b/test/lisp/progmodes/lua-ts-mode-tests.el
@@ -0,0 +1,32 @@
+;;; lua-ts-mode-tests.el --- Tests for lua-ts-mode -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2023 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'ert)
+(require 'ert-x)
+(require 'treesit)
+
+(ert-deftest lua-ts-mode-test-indentation ()
+ (skip-unless (treesit-ready-p 'lua))
+ (ert-test-erts-file (ert-resource-file "indent.erts")))
+
+(provide 'lua-ts-mode-tests)
+
+;;; lua-ts-mode-tests.el ends here
--
2.38.5