]> code.delx.au - gnu-emacs-elpa/blobdiff - packages/sm-c-mode/sm-c-mode.el
Bump version number
[gnu-emacs-elpa] / packages / sm-c-mode / sm-c-mode.el
index f582677f0ca2df65d2c3bec1cef36a2cf2d2c2b1..54b9a54ee377ef46f418a52ba0c0c033baa3f03c 100644 (file)
 ;; might even do it OK for simple cases, but it really doesn't benefit much
 ;; from SMIE:
 ;; - it does a lot of its own parsing by hand.
-;; - its smie-ruled-function also does a lot of indentation by hand.
+;; - its smie-rules-function also does a lot of indentation by hand.
 ;; Hopefully at some point, someone will find a way to extend SMIE such that
 ;; it can handle C without having to constantly work around SMIE, e.g.
-;; it'd be nice to hook the sm-c--while-to-do, sm-c--else-to-if, and sm-c--boi
-;; functions into SMIE at some level.
+;; it'd be nice to hook sm-c--while-to-do, sm-c--else-to-if, sm-c--boi,
+;; sm-c--boe, ... into SMIE at some level.
 
-;; FIXME:
-;; - M-; mistakes # for a comment in CPP directives!
-;; Ha!  As if this was the only/main problem!
+;; Note that this mode makes no attempt to try and handle sanely K&R style
+;; function definitions.
+
+;;;; Benchmarks
+
+;; This code can't be compared to CC-mode since its scope is much more limited
+;; (only tries to handle the kind of code found in Emacs's source code, for
+;; example; does not intend to be extensible to handle C++ or ObjC; does not
+;; offer the same kind of customizability of indentation style, ...).
+;; But in order to make sure it's doing a good enough job on the code for which
+;; it was tuned, I did run some quick benchmarks against CC-mode:
+;;
+;; Benchmarks: reindent emacs/src/*.[ch] (skipping macuvs.h and globals.h
+;; because CC-mode gets pathologically slow on them).
+;;    (cd src/emacs/work/; git reset --hard; mv src/macuvs.h src/globals.h ./);
+;;    files=($(echo ~/src/emacs/work/src/*.[ch]));
+;;    (cd src/emacs/work/; mv macuvs.h globals.h src/);
+;;    time make -j4 ${^${files}}.reindent EMACS="emacs24 -Q";
+;;    (cd src/emacs/work/; git diff|wc)
+;; - Default settings:
+;;   diff|wc =>  86800  379362 2879534
+;;   make -j4  191.57s user 1.77s system 334% cpu   57.78 total
+;; - With (setq sm-c-indent-cpp-basic 0)
+;;   diff|wc =>  59909  275415 2034045
+;;   make -j4  177.88s user 1.70s system 340% cpu   52.80 total
+;; - For reference, CC-mode gets:
+;;   diff|wc =>  79164  490894 3428542
+;;   make -j4  804.83s user 2.79s system 277% cpu 4:51.08 total
+;;
+;; Again: take this with a large grain of salt, since this is testing sm-c-mode
+;; in the most favorable light (IOW it's a very strongly biased benchmark).
+;; All this says, is that sm-c-mode's indentation might actually be usable if
+;; you use it on C code that is sufficiently similar to Emacs's.
+
+;;;; FIXME:
+
+;; - We "use but don't use" SMIE.
+;; - CPP directives are treated as comments.  To some extent this is OK, but in
+;;   many other cases it isn't.  See for instance the comment-only-p advice.
+;; - M-q in a comment doesn't do the right thing.
 
 ;;; Code:
 
@@ -54,7 +91,10 @@ Typically 2 for GNU style and `tab-width' for Linux style."
   :type 'integer)
 
 (defcustom sm-c-indent-braces t
-  "If non-nil, braces in if/while/... are indented."
+  "If nil, braces in if/while/... are aligned with the if/while/...
+Else, they're indented by `sm-c-indent-basic' columns.
+For braces placed at the end of lines (which SMIE calls \"hanging\"), it makes
+no difference."
   :type 'boolean)
 
 ;;; Handling CPP directives.
@@ -116,7 +156,9 @@ Typically 2 for GNU style and `tab-width' for Linux style."
 ;;;; Indenting CPP directives.
 
 (defcustom sm-c-indent-cpp-basic 1
-  "Indent step for CPP directives."
+  "Indent step for CPP directives.
+If non-zero, CPP directives are indented according to CPP depth.
+E.g. a #define nested within 2 #ifs will be turned into \"#  define\"."
   :type 'integer)
 
 (defun sm-c--cpp-prev (tok)
@@ -163,8 +205,19 @@ Typically 2 for GNU style and `tab-width' for Linux style."
 
 (defconst sm-c--cpp-smie-indent-functions
   ;; FIXME: Don't just align line after #define with the "d"!
-  (remq #'smie-indent-comment-inside
-        (default-value 'smie-indent-functions)))
+  (mapcar
+   (lambda (f)
+     (cond
+      ((eq f #'smie-indent-comment-inside) #'sm-c--cpp-indent-comment-inside)
+      ;; ((eq f #'smie-indent-exps) #'sm-c--cpp-indent-exps)
+      (t f)))
+   (default-value 'smie-indent-functions)))
+
+(defun sm-c--cpp-indent-comment-inside ()
+  (let ((ppss (syntax-ppss)))
+    (when (nth 4 ppss)
+      ;; Indicate where's the comment start.
+      `(noindent . ,(nth 8 ppss)))))
 
 (defun sm-c--cpp-smie-indent ()
   (let ((ppss (syntax-ppss)))
@@ -172,15 +225,36 @@ Typically 2 for GNU style and `tab-width' for Linux style."
      ((sm-c--cpp-inside-p ppss)
       (save-restriction
         (narrow-to-region (nth 8 ppss) (point-max))
-        (let ((smie-indent-functions sm-c--cpp-smie-indent-functions))
-          (smie-indent-calculate))))
+        (let ((indent
+               (let ((smie-indent-functions sm-c--cpp-smie-indent-functions)
+                     (syntax-ppss-cache nil)
+                     (syntax-ppss-last nil)
+                     (parse-sexp-lookup-properties nil))
+                 (smie-indent-calculate))))
+          (if (not (eq 'noindent (car-safe indent)))
+              (if (integerp indent)
+                  (max (funcall smie-rules-function :elem 'basic) indent)
+                indent)
+            ;; We can't just return `noindent' if we're inside a comment,
+            ;; because the indent.el code would then be similarly confused,
+            ;; thinking the `noindent' is because we're inside the cpp
+            ;; pseudo-comment, and would hence align the code with the content
+            ;; of the psuedo-comment rather than the nested real comment!
+            ;;
+            ;; FIXME: Copy&paste from indent--default-inside-comment.
+            ;; FIXME: This will always re-indent inside these comments, even
+            ;; during indent-region.
+            (save-excursion
+              (forward-line -1)
+              (skip-chars-forward " \t")
+              (when (< (1- (point)) (cdr indent) (line-end-position))
+                (goto-char (cdr indent))
+                (when (looking-at comment-start-skip)
+                  (goto-char (match-end 0))))
+              (current-column))))))
+
      ((equal (syntax-after (point)) (string-to-syntax "< c")) 0)
-     ((looking-at sm-c--cpp-regexp)
-      (message "s-p-l=%S s-p-d=%S" syntax-ppss-last syntax-propertize--done)
-      (when (get-buffer "*trace-output*")
-        (with-current-buffer "*trace-output*"
-          (message "%S" (buffer-string))))
-      (debug)))))
+     )))
 
 ;;; Syntax table
 
@@ -201,7 +275,15 @@ Typically 2 for GNU style and `tab-width' for Linux style."
   (sm-c--cpp-syntax-propertize end)
   (funcall
    (syntax-propertize-rules
-    (sm-c--cpp-regexp (2 (prog1 "< c" (sm-c--cpp-syntax-propertize end)))))
+    (sm-c--cpp-regexp
+     (2 (prog1 "< c"
+          (when (and (equal (match-string 3) "include")
+                     (looking-at "[ \t]*\\(<\\)[^>\n]*\\(>\\)"))
+            (put-text-property (match-beginning 1) (match-end 1)
+                               'syntax-table (string-to-syntax "|"))
+            (put-text-property (match-beginning 2) (match-end 2)
+                               'syntax-table (string-to-syntax "|")))
+          (sm-c--cpp-syntax-propertize end)))))
    (point) end))
 
 (defun sm-c-syntactic-face-function (ppss)
@@ -239,36 +321,48 @@ Typically 2 for GNU style and `tab-width' for Linux style."
 
 (defconst sm-c-smie-grammar
   ;; `((:smie-closer-alist ("{" . "}")) ("{" (39) 0) ("}" 0 (40)) ("else" 27 26) ("," 38 38) ("do" (41) 22) ("while" (42) 23) ("for" (43) 24) (";" 11 11) ("if" (44) 25))
-  (smie-prec2->grammar
-   (smie-merge-prec2s
-    (smie-bnf->prec2
-     '((decls ("typedef" decl) ("extern" decl)
-              (decls ";" decls))
-       (decl)
-       (id)
-       (insts ("{" insts "}")
-              (insts ";" insts)
-              ("return" exp)
-              ("goto" exp)
-              (":label")
-              ("case" subexp ": case")
-              ("else" exp-if))
-       (exp-if ("if" exp) ("do" exp) ("while" exp) ("switch" exp) ("for" exp)
-               (exp))
-       (exp ("(" exp ")") (exp "," exp) (subexp "?" exp ":" exp))
-       (subexp (subexp "||" subexp))
-       ;; Some of the precedence table deals with pre/postfixes, which
-       ;; smie-precs->prec2 can't handle, so handle it here instead.
-       (exp11 (exp12) (exp11 "/" exp11))
-       (exp12 (exp13))                  ;C++ only.
-       (exp13 (exp14) ("++ prefix" exp13) ("-- prefix" exp13)
-              ("!" exp13) ("~" exp13) ("&" exp13) ("* deref" exp13))
-       (exp14 (id) (exp14 "++ postfix") (exp14 "-- postfix")
-              (exp14 "->" id) (exp14 "." id)))
-     '((assoc ";") (assoc ",") (nonassoc "?" ":"))
-     sm-c-smie-precedence-table)
-    (smie-precs->prec2 sm-c-smie-precedence-table)
-    (smie-precs->prec2 '((nonassoc ";") (nonassoc ":"))))))
+  (let ((grm
+         (smie-prec2->grammar
+          (smie-merge-prec2s
+           (smie-bnf->prec2
+            '((decls ("typedef" decl) ("extern" decl)
+                     (decls ";" decls))
+              (decl)
+              (id)
+              (insts ("{" insts "}")
+                     (insts ";" insts)
+                     ("return" exp)
+                     ("goto" exp)
+                     (":label")
+                     ("case" subexp ": case")
+                     ("else" exp-if))
+              (exp-if ("if" exp) ("do" exp) ("while" exp) ("switch" exp) ("for" exp)
+                      (exp))
+              (exp ("(" exp ")") (exp "," exp) (subexp "?" exp ":" exp))
+              (subexp (subexp "||" subexp))
+              ;; Some of the precedence table deals with pre/postfixes, which
+              ;; smie-precs->prec2 can't handle, so handle it here instead.
+              (exp11 (exp12) (exp11 "/" exp11))
+              (exp12 (exp13))           ;C++ only.
+              (exp13 (exp14) ("++ prefix" exp13) ("-- prefix" exp13)
+                     ("!" exp13) ("~" exp13) ("&" exp13) ("* deref" exp13))
+              (exp14 (id) (exp14 "++ postfix") (exp14 "-- postfix")
+                     (exp14 "->" id) (exp14 "." id)))
+            '((assoc ";") (assoc ",") (nonassoc "?" ":"))
+            sm-c-smie-precedence-table)
+           (smie-precs->prec2 sm-c-smie-precedence-table)
+           (smie-precs->prec2 '((nonassoc ";") (nonassoc ":")))))))
+    ;; SMIE gives (":label" 261 262), but really this could just as well be
+    ;; (":label" nil nil) because labels don't have any argument to their left
+    ;; or right.  They're like both openers and closers at the same time.
+    (mapcar (lambda (x)
+              (if (equal (car-safe x) ":label")
+                  ;; Rather than (":label" (n1) (n2)) we use
+                  ;; (":label" (n1) n2) because SMIE otherwise complains:
+                  ;; cl--assertion-failed((numberp (funcall op-forw toklevels)))
+                  ;; in smie-next-sexp.
+                  `(,(nth 0 x) (,(nth 1 x)) ,(nth 2 x)) x))
+            grm)))
 
 ;; (defun sm-c--:-discriminate ()
 ;;   (save-excursion
@@ -282,7 +376,7 @@ Typically 2 for GNU style and `tab-width' for Linux style."
 (defconst sm-c-smie-operator-regexp
   (let ((ops '()))
     (pcase-dolist (`(,token . ,_) sm-c-smie-grammar)
-      (when (and (stringp token) (string-match "\\`[^ [:alnum:]]+" token))
+      (when (and (stringp token) (string-match "\\`[^ [:alnum:](){}]+" token))
         (push (match-string 0 token) ops)))
     (regexp-opt ops)))
 
@@ -385,7 +479,10 @@ Typically 2 for GNU style and `tab-width' for Linux style."
              t))
           (_ (goto-char start) nil)))))
 
-(defun sm-c--boi ()
+(defun sm-c--boi (&optional inner)
+  "Jump to the beginning-of-instruction.
+By default for things like nested ifs, it jumps to the outer if, but
+if INNER is non-nil, it stops at the innermost one."
   (while
       (let ((pos (point)))
         (pcase (smie-backward-sexp)
@@ -401,6 +498,12 @@ Typically 2 for GNU style and `tab-width' for Linux style."
                t
              (goto-char pos) nil))
           (`(,_ ,_ ,(or "(" "{" "[")) nil) ;Found it!
+          (`(,_ ,pos ,(and tok
+                           (guard (when inner
+                                    (or (member tok sm-c-paren-block-keywords)
+                                        (equal tok "do"))))))
+           (goto-char pos) nil) ;Found it!
+          (`(t ,(pred (eq (point-min))) . ,_) nil)
           (`(,_ ,pos . ,_) (goto-char pos) t)))))
 
 ;; (defun sm-c--if-tail-to-head ()
@@ -414,36 +517,47 @@ Typically 2 for GNU style and `tab-width' for Linux style."
 
 (defun sm-c--boe (tok)
   (let ((start (point))
-        (res (smie-backward-sexp tok))
-        (min (point)))
-    (while
-        (and (member (nth 2 res) '("if" "while" "do" "for" "else"))
-             (let ((skip (cdr (assoc (nth 2 res)
-                                '(("{" . 1)
-                                  ("else" . 1)
-                                  ("do" . 1)
-                                  ("if" . 2)
-                                  ("for" . 2)
-                                  ("while" . 2))))))
-               (let ((forward-sexp-function nil))
-                 (forward-sexp (1- skip)))
-               (forward-comment (point-max))
-               (if (< (point) start)
-                   (setq min (point))
-                 (goto-char min)
-                 nil))))))
+        (res (smie-backward-sexp tok)))
+    (when (member (nth 2 res) '("if" "while" "do" "for" "else"))
+      (when (member (nth 2 res) '("if" "for"))
+        (let ((forward-sexp-function nil))
+          (forward-sexp 1))
+        (forward-comment (point-max)))
+      (when (looking-at "{")
+        (let ((forward-sexp-function nil))
+          (forward-sexp 1))
+        (forward-comment (point-max)))
+      (if (> (point) start) (goto-char start)))))
 
 (defun sm-c-smie--*-token ()
   (save-excursion
     (let ((pos (point)))
       (pcase (car (smie-indent-backward-token))
-        ((or ")" "]") "* mult")                ;Multiplication.
-        ((or "(" "[" "{") "* deref")
+        (")"
+         ;; Can be a multiplication (as in "(a+b)*c"), or a deref
+         ;; (as in "if (stop) *a = 0;")
+         (if (and (goto-char (nth 1 (syntax-ppss)))
+                  (eq ?\( (char-after))
+                  (member (smie-default-backward-token) '("if" "for")))
+             "* deref"
+           "* mult"))
+        ("]" "* mult")                         ;Multiplication.
+        ((or "(" "[" "{" "}") "* deref")
         (`nil
          (goto-char pos)
-         (pcase (smie-backward-sexp "* mult")
-           (`(,_ ,_ ,(or ";" "{")) "* deref")
-           (_ "* mult")))
+         (let ((res nil))
+           (while (not (or res (bobp)))
+             (pcase (smie-backward-sexp)
+               (`(,_ ,_ ,(or ";" "{")) (setq res "* deref"))
+               ((and `nil (guard (looking-at "{"))) (setq res "* deref"))
+               (`(,left ,_ ,op)
+                (if (and (numberp left)
+                         (numberp (nth 2 (assoc op smie-grammar)))
+                         (< (nth 2 (assoc op smie-grammar))
+                            (nth 1 (assoc "* mult" smie-grammar))))
+                    (smie-backward-sexp 'halfsexp)
+                  (setq res "* mult")))))
+           (or res "* mult")))
         (_ "* mult")))))
 
 (defun sm-c-smie-hanging-eolp ()
@@ -507,16 +621,45 @@ Typically 2 for GNU style and `tab-width' for Linux style."
      (cond
       ((smie-rule-prev-p "=") nil)      ;Not a block of instructions!
       ((save-excursion
-         (sm-c--boi) (sm-c--skip-labels (point-max))
-         (let ((tok (save-excursion (sm-c-smie-forward-token))))
-           (cond
-            ((member tok '("enum" "struct" "typedef"))
-             `(column . ,(+ (funcall smie-rules-function :elem 'basic)
-                            (smie-indent-virtual))))
-            ((or (member tok sm-c-paren-block-keywords)
-                 (equal tok "do"))
-             nil)
-            (t `(column . ,(smie-indent-virtual)))))))
+         (let ((pos (point)))
+           (sm-c--boi 'inner) (sm-c--skip-labels (point-max))
+           (let ((tok (save-excursion (sm-c-smie-forward-token))))
+             (cond
+              ((or (equal tok "typedef")
+                   (and (member tok '("enum" "struct"))
+                        ;; Make sure that the {...} is about this struct/enum,
+                        ;; as opposed to "struct foo *get_foo () {...}"!
+                        (save-excursion
+                          (smie-indent-forward-token)
+                          (smie-indent-forward-token)
+                          (forward-comment (point-max))
+                          (>= (point) pos))))
+               `(column . ,(+ (if (save-excursion
+                                    (goto-char pos)
+                                    (smie-rule-hanging-p))
+                                  0
+                                (funcall smie-rules-function :elem 'basic))
+                              (smie-indent-virtual))))
+              ((and (member tok '("enum" "struct"))
+                    ;; Make sure that the {...} is about this struct/enum, as
+                    ;; opposed to "struct foo *get_foo () {...}"!
+                    (save-excursion
+                      (smie-indent-forward-token)
+                      (smie-indent-forward-token)
+                      (forward-comment (point-max))
+                      (>= (point) pos)))
+               `(column . ,(+ (funcall smie-rules-function :elem 'basic)
+                              (smie-indent-virtual))))
+              ((or (member tok sm-c-paren-block-keywords)
+                   (equal tok "do"))
+               nil)
+              ((save-excursion
+                 (goto-char pos)
+                 (when (and (> (car (syntax-ppss)) 0)
+                            (equal ")" (car (smie-indent-backward-token))))
+                   (up-list -1)
+                   `(column . ,(sm-c--smie-virtual)))))
+              (t `(column . ,(smie-indent-virtual))))))))
       ((smie-rule-hanging-p)
        (cond
         ((smie-rule-prev-p "do" "else")
@@ -524,7 +667,7 @@ Typically 2 for GNU style and `tab-width' for Linux style."
         ((smie-rule-prev-p ")")
          (smie-backward-sexp)
          (smie-indent-backward-token))
-        (t (sm-c--boi)))
+        (t (sm-c--boi 'inner)))
        `(column . ,(sm-c--smie-virtual)))
       (t
        (let ((pos (point)))
@@ -542,23 +685,35 @@ Typically 2 for GNU style and `tab-width' for Linux style."
      (save-excursion
        (let ((res (smie-backward-sexp)))
          (pcase res
-           (`nil `(column . ,(+ (funcall smie-rules-function :elem 'basic)
-                                (sm-c--smie-virtual))))
-           (`(nil ,_ "(")
-            (unless (save-excursion
-                      (member (sm-c-smie-backward-token)
-                              sm-c-paren-block-keywords))
-              `(column . ,(sm-c--smie-virtual))))))))
+           (`nil
+            (if (looking-at "(")
+                ;; (unless (save-excursion
+                ;;           (member (sm-c-smie-backward-token)
+                ;;                   sm-c-paren-block-keywords))
+                ;;   `(column . ,(sm-c--smie-virtual)))
+                nil
+              `(column . ,(+ (funcall smie-rules-function :elem 'basic)
+                             (sm-c--smie-virtual)))))))))
     (`(:after . "else")
      (save-excursion
        (funcall smie-rules-function :elem 'basic)))
     (`(:after . ")")
      (save-excursion
-       (forward-char 1) (backward-sexp 1)
-       (let ((prev (sm-c-smie-backward-token)))
-         (when (member prev sm-c-paren-block-keywords)
+       (let ((_ (progn (forward-char 1) (backward-sexp 1)))
+             (pos (point))
+             (prev (sm-c-smie-backward-token)))
+         (cond
+          ((member prev sm-c-paren-block-keywords)
            `(column . ,(+ (funcall smie-rules-function :elem 'basic)
-                          (smie-indent-virtual)))))))
+                          (smie-indent-virtual))))
+          ((and (looking-at "[[:alnum:]_]+(")
+                (save-excursion
+                  (forward-line 0)
+                  (and (bobp) (looking-at sm-c--cpp-regexp))))
+           ;; Will be bumped up presumably by the "max" in
+           ;; sm-c--cpp-smie-indent.
+           `(column . 0))
+          (t (goto-char pos) `(column . ,(sm-c--smie-virtual)))))))
     (`(:after . "}")
      (save-excursion
        (forward-char 1) (backward-sexp 1)
@@ -594,8 +749,89 @@ Typically 2 for GNU style and `tab-width' for Linux style."
                 (goto-char pos)
                 (throw 'found `(column . ,(smie-indent-virtual))))))))))
 
+;;; Backslash alignment
+
+(defvar-local sm-c--bs-changed nil)
+
+(defun sm-c--bs-after-change (beg end _len)
+  (unless undo-in-progress
+    (if (null sm-c--bs-changed)
+        (setq sm-c--bs-changed (cons beg end))
+      (cl-callf (lambda (x) (min x beg)) (car sm-c--bs-changed))
+      (cl-callf (lambda (x) (max x end)) (cdr sm-c--bs-changed)))))
+
+(defun sm-c--bs-realign ()
+  (when sm-c--bs-changed
+    (sm-c--bs-realign-1 (car sm-c--bs-changed) (cdr sm-c--bs-changed))
+    (setq sm-c--bs-changed nil)))
+
+(defun sm-c--bs-realign-1 (from to)
+  (save-excursion
+    (goto-char from)
+    (end-of-line)
+    (unless (zerop (mod (skip-chars-backward "\\\\") 2))
+      (skip-chars-backward " \t")
+      (setq from (point))
+      (let ((col (current-column))
+            start end)
+        (while
+            (progn (setq start (point))
+                   (end-of-line 0)
+                   (and (< (point) start)
+                        (not (zerop (mod (skip-chars-backward "\\\\") 2)))))
+          (skip-chars-backward " \t")
+          (setq col (max (current-column) col)))
+        (goto-char from)
+        (while
+            (progn (setq end (point))
+                   (end-of-line 2)
+                   (and (> (line-beginning-position) end)
+                        (not (zerop (mod (skip-chars-backward "\\\\") 2)))))
+          (skip-chars-backward " \t")
+          (setq col (max (current-column) col)))
+        (goto-char to)
+        (beginning-of-line)
+        (unless (or (> (point) end)       ;Don't realign if we changed outside!
+                    (<= end start))        ;A lone \
+          
+          (setq col (1+ col))         ;Add a space before the backslashes.
+          (goto-char end)
+          (end-of-line)
+          (while (>= (point) start)
+            (cl-assert (eq (char-before) ?\\))
+            (forward-char -1)
+            (let ((curcol (current-column)))
+              (cond
+               ((> col curcol) (indent-to col))
+               ((< col curcol)
+                (move-to-column col t)
+                (delete-region (point)
+                               (progn (skip-chars-forward " \t") (point))))))
+            (end-of-line 0)))))))
+            
 ;;; Font-lock support
 
+(defconst sm-c--comment-regexp
+  "/\\(?:/.*\n\\|\\*\\(?:[^*]+\\(?:\\*+[^/*]\\)*\\)*\\*/\\)")
+
+(defconst sm-c--defun-regexp
+  (let* ((spc0 (concat "\\(?:\n?[ \t]\\|" sm-c--comment-regexp "\\)*"))
+         (spc1 (concat "\n?[ \t]" spc0))
+         (id "\\(?:\\sw\\|\\s_\\)+"))
+    (cl-flet ((repeat (repetition &rest res)
+                      (concat "\\(?:" (apply #'concat res) "\\)"
+                              (pcase repetition
+                                ((pred symbolp) (symbol-name repetition))
+                                (1 "")))))
+      (concat
+       "^\\(?:"
+       (repeat '* "\\*" spc0)
+       (repeat '* id (repeat 1 spc1 "\\|" spc0 "\\*" spc0))
+       "\\(" id "\\)[ \t\n]*("
+       "\\|"
+       "[ \t]*#[ \t]*define[ \t]+\\(?1:" id "\\)("
+       "\\)"))))
+
 (defconst sm-c-font-lock-keywords
   `((,sm-c--cpp-regexp (1 font-lock-preprocessor-face))
     ("\\_<\\(?:true\\|false\\)\\_>" (0 font-lock-constant-face))
@@ -622,8 +858,20 @@ Typically 2 for GNU style and `tab-width' for Linux style."
                          ;; "case" already handled above.
                          (delete "case" kws)))
                 "\\_>"))
-     (0 font-lock-keyword-face))))
-
+     (0 font-lock-keyword-face))
+    (,sm-c--defun-regexp
+     (1
+      (prog1 font-lock-function-name-face
+        (if (< (match-beginning 0) (line-beginning-position))
+            (put-text-property (match-beginning 0) (match-end 0)
+                               'font-lock-multiline t)))))))
+
+(defconst sm-c--def-regexp
+  (let ((spc0 (concat "\\(?:[ \t\n]\\|" sm-c--comment-regexp "\\)*"))
+        (id "\\(?:\\sw\\|\\s_\\)+"))
+    (concat sm-c--defun-regexp
+            "\\|"
+            "\\_<\\(?1:\\(?:struct\\|enum\\)[ \t]+" id "\\)" spc0 "{")))
 
 ;;;###autoload
 (define-derived-mode sm-c-mode prog-mode "smC"
@@ -641,12 +889,29 @@ Typically 2 for GNU style and `tab-width' for Linux style."
               :forward-token #'sm-c-smie-forward-token)
   ;; FIXME: The stock SMIE forward-sexp-function is not good enough here, since
   ;; our grammar is much too poor.  We should setup another function instead
-  ;; (and ideally teach SMIE to use it).
+  ;; (or ideally teach SMIE to use it).
   (kill-local-variable 'forward-sexp-function)
   (add-hook 'smie-indent-functions #'sm-c--cpp-smie-indent nil t)
   (add-function :after (local 'indent-line-function)
                 #'sm-c--cpp-indent-line)
-  (setq-local smie--hanging-eolp-function #'sm-c-smie-hanging-eolp))
+  (setq-local smie--hanging-eolp-function #'sm-c-smie-hanging-eolp)
+  ;; Backslash auto-realign.
+  (add-hook 'after-change-functions #'sm-c--bs-after-change nil t)
+  (add-hook 'post-command-hook #'sm-c--bs-realign nil t)
+  (setq-local add-log-current-defun-header-regexp sm-c--def-regexp)
+  (setq-local imenu-generic-expression `((nil ,sm-c--def-regexp 1))))
+
+(defun sm-c--cpp-is-not-really-a-comment (&rest args)
+  ;; Without this, placing the region around a CPP directive and hitting
+  ;; M-; would just strip the leading "#" instead of commenting things out.
+  (if (not (derived-mode-p 'sm-c-mode))
+      (apply args)
+    (let ((parse-sexp-lookup-properties nil))
+      (apply args))))
+
+;; FIXME: Maybe we should change newcomment.el instead; or maybe CPP directives
+;; should not be defined as comments, or at least "not always"!
+(advice-add 'comment-only-p :around #'sm-c--cpp-is-not-really-a-comment)
 
 (provide 'sm-c-mode)
 ;;; sm-c-mode.el ends here