Subject: bug#78159: shell-script indent regression in Emacs 30

From: Stefan Monnier <[email-masked]>
Date: Thu, 01 May 2025 17:24:54 -0400

> haveid1 () {
>     (id) 2> /dev/null 1>&2
> 	 if [ $? -eq 0 ]; then
>              echo "haveid1: yes"
>          else
>              echo "haveid1: no"
>          fi
> }
> haveid2 () {
>     id 2> /dev/null 1>&2
>     if [ $? -eq 0 ]; then
> 	echo "haveid2: yes"
>     else
>         echo "haveid2: no"
>     fi
> }

Oh, yes, it's due to the change to accept:

    case FOO {
      (id) ...;;
      (other) ...;;
    }

The previous code accepted only

    case FOO in
      (id) ...;;
      (other) ...;;
    esac

and it checked only the presence of `in` without checking that it's part
of a case statement, which was sufficient because `in` doesn't seem to
ever occur elsewhere than in case statements.

The new code similarly fails to check that the `{` is part of a case
statement but `{` doesn't enjoy this same property as `in`, hence the
bug you're seeing.

A crude hack could be something like the patch below, but we should try
to do a more thorough job of deciding if this `{` is part of case
statement or not.

        Stefan

diff --git a/lisp/progmodes/sh-script.el b/lisp/progmodes/sh-script.el
index 287f0501350..b5ccc5507e5 100644
--- a/lisp/progmodes/sh-script.el
+++ b/lisp/progmodes/sh-script.el
@@ -1065,14 +1065,19 @@ sh-font-lock-paren
                     ;; a normal command rather than the real `in' keyword.
                     ;; I.e. we should look back to try and find the
                     ;; corresponding `case'.
-                    ;; Also recognize OpenBSD's case X { ... } (bug#55764).
-                    (and (looking-at ";\\(?:;&?\\|[&|]\\)\\|\\_<in\\|.{")
-                         ;; ";; esac )" is a case that looks
-                         ;; like a case-pattern but it's really just a close
-                         ;; paren after a case statement.  I.e. if we skipped
-                         ;; over `esac' just now, we're not looking
-                         ;; at a case-pattern.
-                         (not (looking-at "..[ \t\n]+esac[^[:word:]_]"))))
+                    (cond
+                     ((looking-at ";\\(?:;&?\\|[&|]\\)\\|\\_<in")
+                      ;; ";; esac )" is a case that looks
+                      ;; like a case-pattern but it's really just a close
+                      ;; paren after a case statement.  I.e. if we skipped
+                      ;; over `esac' just now, we're not looking
+                      ;; at a case-pattern.
+                      (not (looking-at "..[ \t\n]+esac[^[:word:]_]")))
+                     ;; Also recognize OpenBSD's case X { ... } (bug#55764).
+                     ((looking-at ".{")
+                      (save-excursion
+                        ;; Try and make sure this is a case statement!
+                        (re-search-backward "esac" (pos-bol) t)))))
              (progn
                (when open
                  (put-text-property open (1+ open) 'syntax-table sh-st-punc))