Subject: bug#66159: 30.0.50; lua-ts-mode semantic indentation problems

From: john muhl <[email-masked]>
Date: Tue, 03 Oct 2023 10:04:56 -0500

Whoops. Looks like I forgot to update the patch before sending. This one
should be better.

>From 4ee42d82557e188537693c3a27b2246ed690c2a6 Mon Sep 17 00:00:00 2001
From: john muhl <[email-masked]>
Date: Sat, 23 Sep 2023 10:49:11 -0500
Subject: [PATCH] Various improvements in lus-ts-mode (Bug#66159)

* lisp/progmodes/lua-ts-mode.el (lua-ts--first-child)
(lua-ts--end-indent-offset)
(lua-ts--simple-indent-rules): Improve indentation.
(lua-ts-inferior--write-history)
(lua-ts-inferior-lua): Improve inferior Lua interaction.
(lua-ts-mode-map): Add keymap.
(lua-ts-mode-menu): Add menu.
(lua-ts-mode): Add mode map and improve sexp navigation.
(lua-ts-send-buffer)
(lua-ts-send-file)
(lua-ts-send-line)
(lua-ts-send-region)
(lua-ts-show-process-buffer)
(lua-ts-hide-process-buffer)
(lua-ts-kill-process): New functions.
(lua-ts-mode-hook)
(lua-ts-inferior-prompt-regexp)
(lua-ts-inferior-prompt)
(lua-ts-inferior-prompt-continue)
(lua-ts-inferior-history): New variables.
---
 lisp/progmodes/lua-ts-mode.el                 | 210 ++++++++++++++++--
 .../lua-ts-mode-resources/indent.erts         | 199 +++++++++++++++++
 .../lua-ts-mode-resources/movement.erts       |  56 +++++
 3 files changed, 441 insertions(+), 24 deletions(-)

diff --git a/lisp/progmodes/lua-ts-mode.el b/lisp/progmodes/lua-ts-mode.el
index 030a3585158..d1930d9f2b1 100644
--- a/lisp/progmodes/lua-ts-mode.el
+++ b/lisp/progmodes/lua-ts-mode.el
@@ -39,7 +39,9 @@
   (require 'rx))
 
 (declare-function treesit-node-child-by-field-name "treesit.c")
+(declare-function treesit-node-first-child-for-pos "treesit.c")
 (declare-function treesit-node-type "treesit.c")
+(declare-function treesit-node-start "treesit.c")
 (declare-function treesit-parser-create "treesit.c")
 (declare-function treesit-search-subtree "treesit.c")
 
@@ -48,6 +50,15 @@ lua-ts
   :prefix "lua-ts-"
   :group 'languages)
 
+(defcustom lua-ts-mode-hook nil
+  "Hook run after entering `lua-ts-mode'."
+  :type 'hook
+  :options '(flymake-mode
+             hs-minor-mode
+             outline-minor-mode)
+  :group 'lua-ts
+  :version "30.1")
+
 (defcustom lua-ts-indent-offset 4
   "Number of spaces for each indentation step in `lua-ts-mode'."
   :type 'natnum
@@ -86,9 +97,22 @@ lua-ts-inferior-startfile
   :group 'lua-ts
   :version "30.1")
 
-(defcustom lua-ts-inferior-prompt-regexp "^>>?[[:blank:]]"
-  "Regular expression matching the prompt of the inferior Lua process."
-  :type 'regexp
+(defcustom lua-ts-inferior-prompt ">"
+  "The prompt of the inferior Lua process."
+  :type 'string
+  :group 'lua-ts
+  :version "30.1")
+
+(defcustom lua-ts-inferior-prompt-continue ">>"
+  "The continuation prompt of the inferior Lua process."
+  :type 'string
+  :group 'lua-ts
+  :version "30.1")
+
+(defcustom lua-ts-inferior-history nil
+  "File used to save command history of the inferior Lua process."
+  :type '(choice (const nil) file)
+  :safe 'string-or-null-p
   :group 'lua-ts
   :version "30.1")
 
@@ -233,27 +257,65 @@ lua-ts--font-lock-settings
    '((ERROR) @font-lock-warning-face))
   "Tree-sitter font-lock settings for `lua-ts-mode'.")
 
+(defun lua-ts--first-child (node _p _b &rest _)
+  "Matches if NODE is the first among its siblings."
+  (= (treesit-node-index node) 1))
+
+(defun lua-ts--end-indent-offset (_n _p _b &rest _)
+  "Calculate indent offset based on `end' count."
+  (let* ((beg (line-beginning-position))
+         (end (line-end-position))
+         (count (count-matches "end" beg end)))
+    (- (* (1- count) lua-ts-indent-offset))))
+
 (defvar lua-ts--simple-indent-rules
   `((lua
+     ((n-p-gp "end" "function_definition" "arguments")
+      parent 0)
+     ((n-p-gp "end" "function_definition" "parenthesized_expression")
+      parent 0)
+     ((and (n-p-gp "block" "function_definition" "arguments")
+           (lambda (_n parent &rest _)
+             (save-excursion
+               (goto-char (treesit-node-start parent))
+               (backward-char 2)
+               (not (looking-at ")")))))
+      parent lua-ts-indent-offset)
+     ((and (n-p-gp "block" "function_definition" "parenthesized_expression")
+           (lambda (node _p bol &rest _)
+             (equal "assignment_statement"
+                    (treesit-node-type
+                     (treesit-node-first-child-for-pos node bol)))))
+      parent lua-ts-indent-offset)
      ((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 "do") standalone-parent 0)
+     ((node-is "then") standalone-parent 0)
      ((node-is "else_statement") parent-bol 0)
      ((node-is "elseif_statement") parent-bol 0)
-     ((node-is "end") parent-bol 0)
+     ((node-is "end") parent-bol lua-ts--end-indent-offset)
      ((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 "function_definition") standalone-parent lua-ts-indent-offset)
+     ((parent-is "parenthesized_expression")
+      standalone-parent 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)
+     ((and (parent-is "table_constructor") lua-ts--first-child)
+      parent-bol lua-ts-indent-offset)
+     ((parent-is "table_constructor") (nth-sibling 1) 0)
+     ((and (parent-is "arguments") lua-ts--first-child)
+      parent-bol lua-ts-indent-offset)
+     ((parent-is "arguments") (nth-sibling 1) 0)
+     ((and (parent-is "parameters") lua-ts--first-child)
+      parent-bol lua-ts-indent-offset)
+     ((parent-is "parameters") (nth-sibling 1) 0)
      ((parent-is "ERROR") no-indent 0))))
 
 (defvar lua-ts--syntax-table
@@ -348,30 +410,128 @@ lua-ts-flymake-luacheck
       (process-send-region lua-ts--flymake-process (point-min) (point-max))
       (process-send-eof lua-ts--flymake-process))))
 
+(defun lua-ts-inferior--write-history (process _event)
+  "Write history file for inferior Lua PROCESS."
+  ;; Depending on how the process is killed the buffer may
+  ;; not be around anymore; e.g. `kill-buffer'.
+  (when-let* ((buffer (process-buffer process))
+              ((buffer-live-p buffer)))
+    (with-current-buffer buffer (comint-write-input-ring))))
+
 ;;;###autoload
 (defun lua-ts-inferior-lua ()
   "Run a Lua interpreter in an inferior process."
   (interactive)
-  (let* ((buffer lua-ts-inferior-buffer)
-         (name (string-replace "*" "" buffer))
-         (program lua-ts-inferior-program)
-         (prompt-regexp lua-ts-inferior-prompt-regexp)
-         (switches lua-ts-inferior-options)
-         (startfile lua-ts-inferior-startfile))
-    (unless (comint-check-proc buffer)
-      (set-buffer (apply (function make-comint) name program startfile switches))
-      (setq-local comint-input-ignoredups t
-                  comint-prompt-read-only t
-                  comint-prompt-regexp prompt-regexp
-                  comint-use-prompt-regexp t))
-    (select-window (display-buffer buffer '((display-buffer-reuse-window
-                                             display-buffer-pop-up-frame)
-                                            (reusable-frames . t))))))
+  (unless (comint-check-proc lua-ts-inferior-buffer)
+    (apply #'make-comint-in-buffer
+           (string-replace "*" "" lua-ts-inferior-buffer)
+           lua-ts-inferior-buffer
+           lua-ts-inferior-program
+           lua-ts-inferior-startfile
+           lua-ts-inferior-options)
+    (when lua-ts-inferior-history
+      (set-process-sentinel (get-buffer-process lua-ts-inferior-buffer)
+                            'lua-ts-inferior--write-history))
+    (with-current-buffer lua-ts-inferior-buffer
+      (setq comint-prompt-regexp (rx-to-string `(: bol
+                                                   ,lua-ts-inferior-prompt
+                                                   space)))
+      (setq-local comint-input-ignoredups t)
+      (setq-local comint-input-ring-file-name lua-ts-inferior-history)
+      (setq-local comint-prompt-read-only t)
+      (setq-local comint-use-prompt-regexp t)
+      (comint-read-input-ring t)
+      (add-hook 'comint-preoutput-filter-functions
+                (lambda (string)
+                  (if (equal string (concat lua-ts-inferior-prompt-continue " "))
+                      string   ; Don't mess with continuation prompts.
+                    (concat
+                     ;; Filter out the extra prompt characters that
+                     ;; accumulate in the output when sending regions
+                     ;; to the inferior process.
+                     (replace-regexp-in-string (rx-to-string
+                                                `(: bol
+                                                    (* ,lua-ts-inferior-prompt
+                                                       (? ,lua-ts-inferior-prompt)
+                                                       space)
+                                                    (group (* nonl))))
+                                               "\\1" string)
+                     ;; Re-add the prompt for the next line.
+                     lua-ts-inferior-prompt " "))))))
+  (select-window (display-buffer lua-ts-inferior-buffer
+                                 '((display-buffer-reuse-window
+                                    display-buffer-pop-up-window)
+                                   (reusable-frames . t))))
+  (get-buffer-process (current-buffer)))
+
+(defun lua-ts-send-buffer ()
+  "Send current buffer to the inferior Lua process."
+  (interactive)
+  (lua-ts-send-region (point-min) (point-max)))
+
+(defun lua-ts-send-file (file)
+  "Send contents of FILE to the inferior Lua process."
+  (interactive "f")
+  (with-temp-buffer
+    (insert-file-contents-literally file)
+    (lua-ts-send-region (point-min) (point-max))))
+
+(defun lua-ts-send-region (beg end)
+  "Send region between BEG and END to the inferior Lua process."
+  (interactive "r")
+  (let ((string (buffer-substring-no-properties beg end))
+        (proc-buffer (lua-ts-inferior-lua)))
+    (comint-send-string proc-buffer "print()") ; Prevent output from
+    (comint-send-string proc-buffer "\n")      ; appearing at prompt.
+    (comint-send-string proc-buffer string)
+    (comint-send-string proc-buffer "\n")))
+
+(defun lua-ts-show-process-buffer ()
+  "Show the inferior Lua process buffer."
+  (interactive)
+  (display-buffer lua-ts-inferior-buffer))
+
+(defun lua-ts-hide-process-buffer ()
+  "Hide the inferior Lua process buffer."
+  (interactive)
+  (delete-windows-on lua-ts-inferior-buffer))
+
+(defun lua-ts-kill-process ()
+  "Kill the inferior Lua process."
+  (interactive)
+  (with-current-buffer lua-ts-inferior-buffer
+    (kill-buffer-and-window)))
+
+(defvar lua-ts-mode-map
+  (let ((map (make-sparse-keymap "Lua")))
+    (define-key map "\C-c\C-n" 'lua-ts-inferior-lua)
+    (define-key map "\C-c\C-c" 'lua-ts-send-buffer)
+    (define-key map "\C-c\C-l" 'lua-ts-send-file)
+    (define-key map "\C-c\C-r" 'lua-ts-send-region)
+    map)
+  "Keymap for `lua-ts-mode' buffers.")
+
+(easy-menu-define lua-ts-mode-menu lua-ts-mode-map
+  "Menu bar entry for `lua-ts-mode'."
+  `("Lua"
+    ["Evaluate Buffer" lua-ts-send-buffer]
+    ["Evaluate File" lua-ts-send-file]
+    ["Evaluate Region" lua-ts-send-region]
+    "--"
+    ["Start Process" lua-ts-inferior-lua]
+    ["Show Process Buffer" lua-ts-show-process-buffer]
+    ["Hide Process Buffer" lua-ts-hide-process-buffer]
+    ["Kill Process" lua-ts-kill-process]
+    "--"
+    ["Customize" (lambda () (interactive) (customize-group "lua-ts"))]))
 
 ;;;###autoload
 (define-derived-mode lua-ts-mode prog-mode "Lua"
-  "Major mode for editing Lua files, powered by tree-sitter."
+  "Major mode for editing Lua files, powered by tree-sitter.
+
+\\{lua-ts-mode-map}"
   :syntax-table lua-ts--syntax-table
+  (use-local-map lua-ts-mode-map)
 
   (when (treesit-ready-p 'lua)
     (treesit-parser-create 'lua)
@@ -415,7 +575,9 @@ lua-ts-mode
                                       "while_statement")))
                    (sexp ,(rx (or "arguments"
                                   "block"
+                                  "function_declaration"
                                   "parameters"
+                                  "parenthesized_expression"
                                   "string"
                                   "table_constructor")))
                    (text "comment"))))
diff --git a/test/lisp/progmodes/lua-ts-mode-resources/indent.erts b/test/lisp/progmodes/lua-ts-mode-resources/indent.erts
index 040225c8580..c407d3400f8 100644
--- a/test/lisp/progmodes/lua-ts-mode-resources/indent.erts
+++ b/test/lisp/progmodes/lua-ts-mode-resources/indent.erts
@@ -32,6 +32,22 @@ f({
 ;(function()
 return false
 )()
+
+function foo (e)
+    if e == nil
+      then return 1000
+    else return e
+    end
+end
+
+function foo (e)
+    if e == nil
+    then
+        return 1000
+    else
+        return e
+    end
+end
 =-=
 print(
   0,
@@ -57,6 +73,22 @@ f({
 ;(function()
   return false
 )()
+
+function foo (e)
+  if e == nil
+  then return 1000
+  else return e
+  end
+end
+
+function foo (e)
+  if e == nil
+  then
+    return 1000
+  else
+    return e
+  end
+end
 =-=-=
 
 Name: Argument Indent
@@ -77,6 +109,13 @@ cost = 2,
 length = 8,
 	parallelism = 4,
 })
+
+fn(1,
+2,
+     3)
+
+fn(1, 2,
+3)
 =-=
 function h(
   string,
@@ -93,6 +132,13 @@ local p = h(
     length = 8,
     parallelism = 4,
   })
+
+fn(1,
+   2,
+   3)
+
+fn(1, 2,
+   3)
 =-=-=
 
 Name: Continuation Indent
@@ -130,10 +176,20 @@ for k, v in pairs({}) do
 	 print(k, v)
 end
 
+for i=1,10
+    do
+    print(i)
+end
+
 while n < 10 do
 n = n + 1
 end
 
+while n < 10
+ do
+ n = n + 1
+end
+
 repeat
 z = z * 2
  until z > 12
@@ -142,11 +198,154 @@ for k, v in pairs({}) do
   print(k, v)
 end
 
+for i=1,10
+do
+  print(i)
+end
+
 while n < 10 do
   n = n + 1
 end
 
+while n < 10
+do
+  n = n + 1
+end
+
 repeat
   z = z * 2
 until z > 12
 =-=-=
+
+Name: Parameter Indent
+
+=-=
+fn(a,
+b)
+
+fn(a, b,
+c)
+
+fn(
+a,
+b
+)
+=-=
+fn(a,
+   b)
+
+fn(a, b,
+   c)
+
+fn(
+  a,
+  b
+)
+=-=-=
+
+Code:
+  (lambda ()
+    (setq indent-tabs-mode nil)
+    (setq lua-ts-indent-offset 4)
+    (lua-ts-mode)
+    (indent-region (point-min) (point-max)))
+
+Name: Table Indent
+
+=-=
+local Recipe = {
+    Floor={up={Floor=true,Wall=true},
+        down={Floor=true,Wall=true},
+        left={Floor=true,Wall=true},
+        right={Floor=true,Wall=true}},
+    Wall={up={Floor=true,Wall=true},
+        down={Floor=true,Wall=true},
+        left={Floor=true,Wall=true},
+        right={Floor=true,Wall=true}},
+    Corridor={up={Corridoor=true},
+        down={Corridoor=true},
+        left={Corridoor=true},
+        right={Corridoor=true}}
+}
+
+local Other = {
+a = 1,
+ b = 2,
+  c = 3,
+}
+=-=
+local Recipe = {
+    Floor={up={Floor=true,Wall=true},
+           down={Floor=true,Wall=true},
+           left={Floor=true,Wall=true},
+           right={Floor=true,Wall=true}},
+    Wall={up={Floor=true,Wall=true},
+          down={Floor=true,Wall=true},
+          left={Floor=true,Wall=true},
+          right={Floor=true,Wall=true}},
+    Corridor={up={Corridoor=true},
+              down={Corridoor=true},
+              left={Corridoor=true},
+              right={Corridoor=true}}
+}
+
+local Other = {
+    a = 1,
+    b = 2,
+    c = 3,
+}
+=-=-=
+
+Name: Single Line End
+
+=-=
+function lowest_entropy_cell(world)
+    local lowest,res=math.huge,nil
+    for y=1,world.height do
+        for x=1,world.width do
+            local cell=world:get(x,y)
+            if cell.is_set then
+                local e=cell_enthropy(cell)
+                trace(e)
+                if e <= lowest then
+                    lowest,res=e,{x,y}
+                end end end end
+    return res or {math.random(w.w),math.random(w.h)}
+end
+
+for y=1,world.height do
+    for x=1,world.width do
+        local cell=world:get(x,y)
+        if cell.is_set then
+            local e=cell_enthropy(cell)
+            trace(e)
+            if e <= lowest then
+                lowest,res=e,{x,y}
+            end
+            end end end
+=-=
+function lowest_entropy_cell(world)
+    local lowest,res=math.huge,nil
+    for y=1,world.height do
+        for x=1,world.width do
+            local cell=world:get(x,y)
+            if cell.is_set then
+                local e=cell_enthropy(cell)
+                trace(e)
+                if e <= lowest then
+                    lowest,res=e,{x,y}
+    end end end end
+    return res or {math.random(w.w),math.random(w.h)}
+end
+
+for y=1,world.height do
+    for x=1,world.width do
+        local cell=world:get(x,y)
+        if cell.is_set then
+            local e=cell_enthropy(cell)
+            trace(e)
+            if e <= lowest then
+                lowest,res=e,{x,y}
+            end
+end end end
+=-=-=
diff --git a/test/lisp/progmodes/lua-ts-mode-resources/movement.erts b/test/lisp/progmodes/lua-ts-mode-resources/movement.erts
index 770aa23b18d..afebe93de3f 100644
--- a/test/lisp/progmodes/lua-ts-mode-resources/movement.erts
+++ b/test/lisp/progmodes/lua-ts-mode-resources/movement.erts
@@ -481,6 +481,34 @@ local t = { 1,
   3 }|
 =-=-=
 
+Name: forward-sexp moves over parenthesized expressions
+
+=-=
+|(function (x) return x + 1 end)(41)
+=-=
+(function (x) return x + 1 end)|(41)
+=-=-=
+
+Name: forward-sexp moves over function declarations
+
+=-=
+|function foo (x)
+    if false then
+        print "foo"
+    elseif true then
+        print "bar"
+    end
+end
+=-=
+function foo (x)
+    if false then
+        print "foo"
+    elseif true then
+        print "bar"
+    end
+end|
+=-=-=
+
 Code:
   (lambda ()
     (lua-ts-mode)
@@ -551,3 +579,31 @@ local t = |{ 1,
   2,
   3 }
 =-=-=
+
+Name: backward-sexp moves over parenthesized expressions
+
+=-=
+(function (x) return x + 1 end)|(41)
+=-=
+|(function (x) return x + 1 end)(41)
+=-=-=
+
+Name: backward-sexp moves over function declarations
+
+=-=
+function foo (x)
+    if false then
+        print "foo"
+    elseif true then
+        print "bar"
+    end
+end|
+=-=
+|function foo (x)
+    if false then
+        print "foo"
+    elseif true then
+        print "bar"
+    end
+end
+=-=-=
-- 
2.41.0