]> code.delx.au - gnu-emacs-elpa/blobdiff - packages/sm-c-mode/sm-c-mode.el
Rename a local variable
[gnu-emacs-elpa] / packages / sm-c-mode / sm-c-mode.el
index 7616616c2ec91c88def9a513ae6e4ed5e6c5b6dc..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)
@@ -233,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)
@@ -495,9 +545,19 @@ if INNER is non-nil, it stops at the innermost one."
         ((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 ()
@@ -565,7 +625,29 @@ if INNER is non-nil, it stops at the innermost one."
            (sm-c--boi 'inner) (sm-c--skip-labels (point-max))
            (let ((tok (save-excursion (sm-c-smie-forward-token))))
              (cond
-              ((member tok '("typedef")) ; "enum" "struct"
+              ((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)
@@ -689,6 +771,7 @@ if INNER is non-nil, it stops at the innermost one."
     (end-of-line)
     (unless (zerop (mod (skip-chars-backward "\\\\") 2))
       (skip-chars-backward " \t")
+      (setq from (point))
       (let ((col (current-column))
             start end)
         (while
@@ -702,14 +785,14 @@ if INNER is non-nil, it stops at the innermost one."
         (while
             (progn (setq end (point))
                    (end-of-line 2)
-                   (and (> (point) start)
+                   (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 \
+                    (<= end start))        ;A lone \
           
           (setq col (1+ col))         ;Add a space before the backslashes.
           (goto-char end)
@@ -728,6 +811,27 @@ if INNER is non-nil, it stops at the innermost one."
             
 ;;; 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))
@@ -755,25 +859,19 @@ if INNER is non-nil, it stops at the innermost one."
                          (delete "case" kws)))
                 "\\_>"))
      (0 font-lock-keyword-face))
-    (,(let* ((spc0 "\\(?:\n?[ \t]\\|/\\*.*?\\*/\\)*")
-             (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]*(")))
+    (,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"
@@ -799,7 +897,21 @@ if INNER is non-nil, it stops at the innermost one."
   (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))
+  (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