Subject: 30.0.50; lua-ts-mode semantic indentation problems
From: Andrey Listopadov <[email-masked]>
To: [email-masked]
Date: Fri, 22 Sep 2023 22:17:55 +0300
List-Id: <debbugs-submit.debbugs.gnu.org>

I've been editing Lua using the long-existing lua-mode[1] and it works
well enough for me, but it's indentation implementation is very slow.
Indenting a file with just 600 lines takes several seconds on my
machine.  So when I saw that Emacs now features a lua-ts-mode package in
its core, I tried to move to it, in hopes that it would be faster.

However, a lot of code is now indented differently, not how I used to,
and this creates a lot of noise in commits, as I often re-indent the
whole buffer before saving the file. Here are some examples.

lua-mode:

function foo (e)
    if e == nil
    then return 1000
    else return e
end end

lua-ts-mode:

function foo (e)
    if e == nil
        then return 1000
    else return e
    end end

My thoughts:

`then' is indented as the part of the block of code that is part of the
`true' branch itself. In lua-mode, it is indented the same as `if' itself,
and everything after `then' is indented if moved to a new line:

function foo (e)
    if e == nil
    then
        return 1000
    else
        return e
end end

In lua-ts-mode it is indented together with the body which isn't
idiomatic:

function foo (e)
    if e == nil
        then
        return 1000
    else
        return e
    end end

Another thing that bothers me is that I prefer Gassanenko-style packing
of `end' keywords so that they vertically align with the scope of the
opened block, as it saves so much vertical space and is easier for me to
read, but lua-ts-mode moves it to the latest innermost indentation
level, as opposed to the outermost depending on the count of ends in the
line itself:

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(world.width),math.random(world.height)}
end

However, lua-ts-mode indents it like so:

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(world.width),math.random(world.height)}
end

I noticed the difference in table indentations as well.

lua-mode:

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}}
}

lua-ts-mode:

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}}
}

The first one is more logical and obeys scope levels.

Should I expect this to be the default new way of indenting or the
indentation can be made to match the lua-mode where it is sensible?

Thanks!

P.S. I know that stacking `end' isn't really idiomatic Lua style, but it
works in old-lua mode sensible enough that it is actually surprising,
and transforms Lua into code that is easy to parse just by looking on
the indentation level, much like Lisps.

[1] https://github.com/immerrr/lua-mode

In GNU Emacs 30.0.50 (build 1, x86_64-pc-linux-gnu, GTK+ Version
 3.24.38, cairo version 1.17.8) of 2023-09-18 built on toolbox
Repository revision: 861f9cb78370e2b78f852e5ccde9b63c94486ca8
Repository branch: master
System Description: Fedora Linux 38 (Container Image)

Configured using:
 'configure --without-compress-install --with-native-compilation=aot
 --with-pgtk --with-mailutils --with-xwidgets
 --prefix=/var/home/alist/.local'

Configured features:
ACL CAIRO DBUS FREETYPE GIF GLIB GMP GNUTLS GPM GSETTINGS HARFBUZZ JPEG
JSON LCMS2 LIBOTF LIBSELINUX LIBXML2 MODULES NATIVE_COMP NOTIFY INOTIFY
PDUMPER PGTK PNG RSVG SECCOMP SOUND SQLITE3 THREADS TIFF
TOOLKIT_SCROLL_BARS TREE_SITTER XIM XWIDGETS GTK3 ZLIB

Important settings:
  value of $LANG: en_US.UTF-8
  locale-coding-system: utf-8-unix

Major mode: Lua

Minor modes in effect:
  repeat-mode: t
  global-git-commit-mode: t
  magit-auto-revert-mode: t
  electric-pair-mode: t
  hl-todo-mode: t
  savehist-mode: t
  delete-selection-mode: t
  pixel-scroll-precision-mode: t
  global-auto-revert-mode: t
  corfu-popupinfo-mode: t
  global-corfu-mode: t
  corfu-mode: t
  global-region-bindings-mode: t
  recentf-mode: t
  gcmh-mode: t
  server-mode: t
  marginalia-mode: t
  vertico-mode: t
  override-global-mode: t
  tooltip-mode: t
  global-eldoc-mode: t
  show-paren-mode: t
  electric-indent-mode: t
  mouse-wheel-mode: t
  menu-bar-mode: t
  file-name-shadow-mode: t
  context-menu-mode: t
  global-font-lock-mode: t
  font-lock-mode: t
  blink-cursor-mode: t
  minibuffer-regexp-mode: t
  column-number-mode: t
  line-number-mode: t
  transient-mark-mode: t
  auto-composition-mode: t
  auto-encryption-mode: t
  auto-compression-mode: t
  abbrev-mode: t
  hs-minor-mode: t

Load-path shadows:
/var/home/alist/.config/emacs/elpa/transient-20230915.1911/transient hides /var/home/alist/.local/share/emacs/30.0.50/lisp/transient
/var/home/alist/.config/emacs/elpa/modus-themes-20230916.905/theme-loaddefs hides /var/home/alist/.local/share/emacs/30.0.50/lisp/theme-loaddefs

Features:
(shadow emacsbug dabbrev lua-ts-mode rect lua-mode modus-operandi-theme
python geiser-mode geiser-xref geiser-compile geiser-guile tramp
trampver tramp-integration tramp-message tramp-compat tramp-loaddefs
info-look geiser geiser-debug geiser-repl geiser-image geiser-capf
geiser-doc geiser-menu geiser-autodoc geiser-edit etags fileloop
geiser-completion geiser-eval geiser-connection tq geiser-syntax
geiser-log geiser-popup geiser-impl help-fns radix-tree geiser-custom
geiser-base scheme find-dired grep display-line-numbers css-mode treesit
smie mu4e-alert advice time ht alert log4e notifications gntp mu4e
mu4e-org mu4e-main mu4e-view mu4e-headers mu4e-compose mu4e-draft
mu4e-actions smtpmail mu4e-search mu4e-lists mu4e-bookmarks mu4e-mark
mu4e-message flow-fill hl-line mu4e-contacts mu4e-update mu4e-folders
mu4e-server mu4e-context mu4e-vars mu4e-helpers mu4e-config ido tabify
cus-start repeat files-x org-archive puni pulse color consult ox-odt
rng-loc rng-uri rng-parse rng-match rng-dt rng-util rng-pttrn nxml-parse
nxml-ns nxml-enc xmltok nxml-util ox-icalendar org-agenda ox-ascii
ox-latex ox-hugo ox-hugo-deprecated ffap ox-blackfriday ox-md ox-html
table ox-publish tomelr ox sgml-mode facemenu xwidget magit-bookmark
bookmark goto-addr vc-hg vc-bzr vc-src vc-sccs vc-svn vc-cvs vc-rcs
log-view vc bug-reference vc-git vc-dispatcher misearch multi-isearch
dired-aux face-remap magit-extras orderless flyspell ispell org-element
org-persist org-id org-refile avl-tree generator oc-basic ol-eww eww xdg
url-queue mm-url ol-rmail ol-mhe ol-irc ol-info ol-gnus nnselect
gnus-art mm-uu mml2015 mm-view mml-smime smime gnutls dig gnus-sum shr
pixel-fill kinsoku url-file svg dom gnus-group gnus-undo gnus-start
gnus-dbus gnus-cloud nnimap nnmail mail-source utf7 nnoo parse-time
gnus-spec gnus-int gnus-range gnus-win gnus nnheader range ol-docview
doc-view jka-compr image-mode exif ol-bibtex bibtex iso8601 ol-bbdb
ol-w3m ol-doi org-link-doi org-tempo tempo ob-fennel fennel-proto-repl
fennel-mode inf-lisp xref magit-submodule magit-blame magit-stash
magit-reflog magit-bisect magit-push magit-pull magit-fetch magit-clone
magit-remote magit-commit magit-sequence magit-notes magit-worktree
magit-tag magit-merge magit-branch magit-reset magit-files magit-refs
magit-status magit magit-repos magit-apply magit-wip magit-log
which-func imenu magit-diff smerge-mode diff diff-mode git-commit
log-edit message sendmail yank-media puny dired dired-loaddefs rfc822
mml mml-sec epa epg rfc6068 epg-config gnus-util mm-decode mm-bodies
mm-encode mail-parse rfc2231 rfc2047 rfc2045 mm-util ietf-drums
mail-prsvr mailabbrev mail-utils gmm-utils mailheader pcvs-util add-log
magit-core magit-autorevert magit-margin magit-transient magit-process
with-editor magit-mode transient magit-git magit-base magit-section
cursor-sensor crm project ob-lua ob-shell shell org ob ob-tangle ob-ref
ob-lob ob-table ob-exp org-macro org-src ob-comint org-pcomplete
pcomplete org-list org-footnote org-faces org-entities ob-emacs-lisp
ob-core ob-eval org-cycle org-table org-keys oc org-loaddefs find-func
cal-menu calendar cal-loaddefs vertico-directory mule-util time-date
noutline outline elec-pair isayt disp-table hideshow hl-todo savehist
delsel pixel-scroll cua-base autorevert filenotify corfu-popupinfo cape
corfu region-bindings recentf tree-widget gcmh init proxy gsettings s
gvariant parsec dash zig-compilation-mode fennel-compilation-mode
clojure-compilation-mode derived compile text-property-search server
ediff ediff-merg ediff-mult ediff-wind ediff-diff ediff-help ediff-init
ediff-util sql-indent sql view thingatpt comint ansi-osc ring zig-mode
reformatter ansi-color ol org-fold org-fold-core org-compat org-version
org-macs format-spec blog marginalia vertico compat use-package-delight
formfeed modus-vivendi-theme modus-themes dbus xml common-lisp-modes
novice cus-edit pp cus-load wid-edit font mode-line edmacro kmacro
messages use-package-bind-key bind-key defaults functions local-config
delight comp comp-cstr warnings icons rx use-package-ensure cl-extra
help-mode use-package-core early-init cape-autoloads
clj-decompiler-autoloads clj-refactor-autoloads cider-autoloads
clojure-mode-autoloads common-lisp-modes-autoloads consult-autoloads
corfu-terminal-autoloads corfu-autoloads csv-mode-autoloads
delight-autoloads dumb-jump-autoloads eat-autoloads elfeed-autoloads
expand-region-autoloads fennel-mode-autoloads gcmh-autoloads
geiser-guile-autoloads geiser-autoloads gsettings-autoloads
gvariant-autoloads hl-todo-autoloads inflections-autoloads
isayt-autoloads jdecomp-autoloads lisp-extra-font-lock-autoloads
lsp-java-autoloads lsp-metals-autoloads dap-mode-autoloads
lsp-docker-autoloads bui-autoloads lsp-treemacs-autoloads
lsp-mode-autoloads f-autoloads lua-mode-autoloads marginalia-autoloads
markdown-mode-autoloads message-view-patch-autoloads magit-autoloads
pcase magit-section-autoloads git-commit-autoloads
modus-themes-autoloads mu4e-alert-autoloads alert-autoloads
log4e-autoloads gntp-autoloads multiple-cursors-autoloads
orderless-autoloads org-modern-autoloads org-tree-slide-autoloads
ox-hugo-autoloads package-lint-flymake-autoloads package-lint-autoloads
paredit-autoloads parsec-autoloads parseedn-autoloads parseclj-autoloads
phi-search-autoloads poly-org-autoloads polymode-autoloads
popon-autoloads popup-autoloads puni-autoloads easy-mmode finder-inf
queue-autoloads racket-mode-autoloads region-bindings-autoloads
request-autoloads scala-mode-autoloads separedit-autoloads
edit-indirect-autoloads sesman-autoloads sly-autoloads spinner-autoloads
sql-indent-autoloads tomelr-autoloads transient-autoloads
treemacs-autoloads cfrs-autoloads posframe-autoloads ht-autoloads
hydra-autoloads lv-autoloads pfuture-autoloads ace-window-autoloads
avy-autoloads s-autoloads dash-autoloads vertico-autoloads
vundo-autoloads with-editor-autoloads info compat-autoloads
yaml-autoloads yaml-mode-autoloads yasnippet-autoloads
zig-mode-autoloads reformatter-autoloads package browse-url url
url-proxy url-privacy url-expand url-methods url-history url-cookie
generate-lisp-file url-domsuf url-util mailcap url-handlers url-parse
auth-source cl-seq eieio eieio-core cl-macs password-cache json subr-x
map byte-opt gv bytecomp byte-compile url-vars cl-loaddefs cl-lib rmc
iso-transl tooltip cconv eldoc paren electric uniquify ediff-hook
vc-hooks lisp-float-type elisp-mode mwheel term/pgtk-win pgtk-win
term/common-win pgtk-dnd touch-screen tool-bar dnd fontset image
regexp-opt fringe tabulated-list replace newcomment text-mode lisp-mode
prog-mode register page tab-bar menu-bar rfn-eshadow isearch easymenu
timer select scroll-bar mouse jit-lock font-lock syntax font-core
term/tty-colors frame minibuffer nadvice seq simple cl-generic
indonesian philippine cham georgian utf-8-lang misc-lang vietnamese
tibetan thai tai-viet lao korean japanese eucjp-ms cp51932 hebrew greek
romanian slovak czech european ethiopic indian cyrillic chinese
composite emoji-zwj charscript charprop case-table epa-hook
jka-cmpr-hook help abbrev obarray oclosure cl-preloaded button loaddefs
theme-loaddefs faces cus-face macroexp files window text-properties
overlay sha1 md5 base64 format env code-pages mule custom widget keymap
hashtable-print-readable backquote threads xwidget-internal dbusbind
inotify dynamic-setting system-font-setting font-render-setting cairo
gtk pgtk lcms2 multi-tty move-toolbar make-network-process
native-compile emacs)

Memory information:
((conses 16 1147781 1425356) (symbols 48 52742 50)
 (strings 32 272573 78657) (string-bytes 1 9908365)
 (vectors 16 119200) (vector-slots 8 2827929 951125)
 (floats 8 888 21739) (intervals 56 40788 17575) (buffers 992 46))

-- 
Andrey Listopadov