]> code.delx.au - gnu-emacs/blobdiff - lisp/progmodes/c-mode.el
(c-indent-region): As first thing, advance to a nonblank line.
[gnu-emacs] / lisp / progmodes / c-mode.el
index b92fe6ec819b138772744f40b09a760e43655753..429d0bf2292959e1632936a8dd8873a82c63bcf2 100644 (file)
 ;; along with GNU Emacs; see the file COPYING.  If not, write to
 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 
+;;; Commentary:
+
+;; A smart editing mode for C code.  It knows a lot about C syntax and tries
+;; to position the curser according to C layout conventions.  You can
+;; change the details of the layout style with option variables.  Load it
+;; and do M-x describe-mode for details.
+
 ;;; Code:
 
 (defvar c-mode-abbrev-table nil
@@ -41,6 +48,9 @@
   (define-key c-mode-map "\ea" 'c-beginning-of-statement)
   (define-key c-mode-map "\ee" 'c-end-of-statement)
   (define-key c-mode-map "\eq" 'c-fill-paragraph)
+  (define-key c-mode-map "\C-c\C-n" 'c-forward-conditional)
+  (define-key c-mode-map "\C-c\C-p" 'c-backward-conditional)
+  (define-key c-mode-map "\C-c\C-u" 'c-up-conditional)
   (define-key c-mode-map "\177" 'backward-delete-char-untabify)
   (define-key c-mode-map "\t" 'c-indent-command))
 
@@ -104,7 +114,7 @@ This is in addition to c-continued-statement-offset.")
      (c-brace-offset               . -4)
      (c-label-offset               . -4)
      (c-continued-statement-offset .  4))
-    (C++
+    ("C++"
      (c-indent-level               . 4)
      (c-continued-statement-offset . 4)
      (c-brace-offset               . -4)
@@ -127,6 +137,11 @@ If you do not want a leading newline before braces then use:
 (defconst c-tab-always-indent t
   "*Non-nil means TAB in C mode should always reindent the current line,
 regardless of where in the line point is when the TAB command is used.")
+
+;;; Regular expression used internally to recognize labels in switch
+;;; statements.
+(defconst c-switch-label-regexp "case[ \t'/(]\\|default\\(\\S_\\|'\\)")
+
 \f
 (defun c-mode ()
   "Major mode for editing C code.
@@ -199,8 +214,8 @@ if that value is non-nil."
   (setq comment-column 32)
   (make-local-variable 'comment-start-skip)
   (setq comment-start-skip "/\\*+ *")
-  (make-local-variable 'comment-indent-hook)
-  (setq comment-indent-hook 'c-comment-indent)
+  (make-local-variable 'comment-indent-function)
+  (setq comment-indent-function 'c-comment-indent)
   (make-local-variable 'parse-sexp-ignore-comments)
   (setq parse-sexp-ignore-comments t)
   (run-hooks 'c-mode-hook))
@@ -244,7 +259,7 @@ preserving the comment indentation or line-starting decorations."
          ;; Check for obvious entry to comment.
          (save-excursion
            (beginning-of-line)
-           (skip-chars-forward " \t")
+           (skip-chars-forward " \t\n")
            (and (looking-at comment-start-skip)
                 (setq comment-start-place (point))))))
     (if (or first-line
@@ -252,13 +267,14 @@ preserving the comment indentation or line-starting decorations."
            (eq (calculate-c-indent) t)
            ;; t if this line contains a comment starter.
            (setq first-line
-                 (save-excursion (beginning-of-line)
-                                 (prog1
-                                     (re-search-forward comment-start-skip
-                                                        (save-excursion (end-of-line)
-                                                                        (point))
-                                                        t)
-                                   (setq comment-start-place (point))))))
+                 (save-excursion
+                   (beginning-of-line)
+                   (prog1
+                       (re-search-forward comment-start-skip
+                                          (save-excursion (end-of-line)
+                                                          (point))
+                                          t)
+                     (setq comment-start-place (point))))))
        ;; Inside a comment: fill one comment paragraph.
        (let ((fill-prefix
               ;; The prefix for each line of this paragraph
@@ -270,20 +286,59 @@ preserving the comment indentation or line-starting decorations."
                     (progn (re-search-forward comment-start-skip)
                            (make-string (current-column) ?\ ))
                   (if first-line (forward-line 1))
-                  (buffer-substring (point)
-                                    (progn
-                                      (move-to-column
-                                       (calculate-c-indent-within-comment t)
-                                       t)
-                                      (point))))))
+
+                  (let ((line-width (progn (end-of-line) (current-column))))
+                    (beginning-of-line)
+                    (prog1
+                        (buffer-substring
+                         (point)
+
+                         ;; How shall we decide where the end of the
+                         ;; fill-prefix is?
+                         ;; calculate-c-indent-within-comment bases its value
+                         ;; on the indentation of previous lines; if they're
+                         ;; indented specially, it could return a column
+                         ;; that's well into the current line's text.  So
+                         ;; we'll take at most that many space, tab, or *
+                         ;; characters, and use that as our fill prefix.
+                         (let ((max-prefix-end
+                                (progn
+                                  (move-to-column
+                                   (calculate-c-indent-within-comment t)
+                                   t)
+                                  (point))))
+                           (beginning-of-line)
+                           (skip-chars-forward " \t*" max-prefix-end)
+                           (point)))
+
+                      ;; If the comment is only one line followed by a blank
+                      ;; line, calling move-to-column above may have added
+                      ;; some spaces and tabs to the end of the line; the
+                      ;; fill-paragraph function will then delete it and the
+                      ;; newline following it, so we'll lose a blank line
+                      ;; when we shouldn't.  So delete anything
+                      ;; move-to-column added to the end of the line.  We
+                      ;; record the line width instead of the position of the
+                      ;; old line end because move-to-column might break a
+                      ;; tab into spaces, and the new characters introduced
+                      ;; there shouldn't be deleted.
+
+                      ;; If you can see a better way to do this, please make
+                      ;; the change.  This seems very messy to me.
+                      (delete-region (progn (move-to-column line-width)
+                                            (point))
+                                     (progn (end-of-line) (point))))))))
+
              (paragraph-start
               ;; Lines containing just a comment start or just an end
               ;; should not be filled into paragraphs they are next to.
-              (concat paragraph-start
-                      "\\|^[ \t]*/\\*[ \t]*$\\|^[ \t]*\\*/[ \t]*$\\|^[^ \t/*]"))
+              (concat 
+               paragraph-start
+               "\\|^[ \t]*/\\*[ \t]*$\\|^[ \t]*\\*/[ \t]*$\\|^[ \t/*]*$"))
              (paragraph-separate
-              (concat paragraph-separate
-                      "\\|^[ \t]*/\\*[ \t]*$\\|^[ \t]*\\*/[ \t]*$\\|^[^ \t/*]"))
+              (concat
+               paragraph-separate
+               "\\|^[ \t]*/\\*[ \t]*$\\|^[ \t]*\\*/[ \t]*$\\|^[ \t/*]*$"))
              (chars-to-delete 0))
          (save-restriction
            ;; Don't fill the comment together with the code following it.
@@ -320,7 +375,7 @@ preserving the comment indentation or line-starting decorations."
              ;; given the narrowing) and don't leave it on its own line.
              (goto-char (point-max))
              (forward-line -1)
-             (search-forward "*/")
+             (search-forward "*/" nil 'move)
              (beginning-of-line)
              (if (looking-at "[ \t]*\\*/")
                  (delete-indentation)))))
@@ -385,7 +440,7 @@ preserving the comment indentation or line-starting decorations."
                        ;; So quickly rule out most other uses of colon
                        ;; and do no indentation for them.
                        (and (eq last-command-char ?:)
-                            (not (looking-at "case[ \t'/(]\\|default\\>"))
+                            (not (looking-at c-switch-label-regexp))
                             (save-excursion
                               (skip-chars-forward "a-zA-Z0-9_$")
                               (skip-chars-forward " \t")
@@ -477,7 +532,7 @@ Return the amount the indentation changed by."
          (t
           (skip-chars-forward " \t")
           (if (listp indent) (setq indent (car indent)))
-          (cond ((or (looking-at "case[ \t'/(]\\|default\\>")
+          (cond ((or (looking-at c-switch-label-regexp)
                      (and (looking-at "[A-Za-z]")
                           (save-excursion
                             (forward-sexp 1)
@@ -492,6 +547,7 @@ Return the amount the indentation changed by."
                  (setq indent (save-excursion
                                 (forward-char)
                                 (backward-sexp)
+                                (c-backward-to-start-of-if)
                                 (current-indentation))))
                 ((and (looking-at "while\\b")
                       (save-excursion
@@ -569,7 +625,10 @@ Returns nil if line starts inside a string, t if in a comment."
                                (looking-at "DEFUN\\b"))
                              c-argdecl-indent
                            (if (and (looking-at "\\sw\\|\\s_")
-                                    (looking-at "[^\"\n=]*(")
+                                    ;; This is careful to stop at the first
+                                    ;; paren if we have
+                                    ;; int foo Proto ((int, int));
+                                    (looking-at "[^\"\n=(]*(")
                                     (progn
                                       (goto-char (1- (match-end 0)))
                                       (setq lim (point))
@@ -583,6 +642,8 @@ Returns nil if line starts inside a string, t if in a comment."
                                     ;; Make sure the "function decl" we found
                                     ;; is not inside a comment.
                                     (progn
+                                      ;; Move back to the `(' starting arglist
+                                      (goto-char lim)
                                       (beginning-of-line)
                                       (while (and (not comment)
                                                   (search-forward "/*" lim t))
@@ -825,14 +886,19 @@ Otherwise return nil and don't move point."
 (defun c-beginning-of-statement (count)
   "Go to the beginning of the innermost C statement.
 With prefix arg, go back N - 1 statements.  If already at the beginning of a
-statement then go to the beginning of the preceeding one.
-If within a string or comment, move by sentences instead of statements."
+statement then go to the beginning of the preceding one.
+If within a string or comment, or next to a comment (only whitespace between),
+move by sentences instead of statements."
   (interactive "p")
   (let ((here (point)) state)
     (save-excursion
       (beginning-of-defun)
       (setq state (parse-partial-sexp (point) here nil nil)))
-    (if (or (nth 3 state) (nth 4 state))
+    (if (or (nth 3 state) (nth 4 state)
+           (looking-at (concat "[ \t]*" comment-start-skip))
+           (save-excursion (skip-chars-backward " \t")
+                           (goto-char (- (point) 2))
+                           (looking-at "\\*/")))
        (forward-sentence (- count))
       (while (> count 0)
        (c-beginning-of-statement-1)
@@ -887,14 +953,14 @@ If within a string or comment, move by sentences instead of statements."
   (interactive)
   (push-mark (point))
   (end-of-defun)
-  (push-mark (point))
+  (push-mark (point) nil t)
   (beginning-of-defun)
   (backward-paragraph))
 \f
+;; Idea of ENDPOS is, indent each line, stopping when
+;; ENDPOS is encountered.  But it's too much of a pain to make that work.
 (defun indent-c-exp (&optional endpos)
-  "Indent each line of the C grouping following point.
-If optional arg ENDPOS is given, indent each line, stopping when
-ENDPOS is encountered."
+  "Indent each line of the C grouping following point."
   (interactive)
   (let* ((indent-stack (list nil))
         (opoint (point))  ;; May be altered below.
@@ -905,13 +971,15 @@ ENDPOS is encountered."
                      (save-excursion (forward-char 1)
                                      (beginning-of-defun)
                                      (setq funbeg (point)))
+                     (setq opoint funbeg)
                      ;; Try to find containing open,
                      ;; but don't scan past that fcn-start.
                      (save-restriction
                        (narrow-to-region funbeg (point))
                        (condition-case nil
                            (save-excursion
-                             (backward-up-list 1) (point))
+                             (backward-up-list 1)
+                             (point))
                          ;; We gave up: must be between fcns.
                          ;; Set opoint to beg of prev fcn
                          ;; since otherwise calculate-c-indent
@@ -923,7 +991,7 @@ ENDPOS is encountered."
         restart outer-loop-done inner-loop-done state ostate
         this-indent last-sexp
         at-else at-brace at-while
-        last-depth
+        last-depth this-point
         (next-depth 0))
     ;; If the braces don't match, get an error right away.
     (save-excursion
@@ -934,6 +1002,12 @@ ENDPOS is encountered."
        (and (re-search-forward
              comment-start-skip
              (save-excursion (end-of-line) (point)) t)
+            ;; Make sure this isn't a comment alone on a line
+            ;; (which should be indented like code instead).
+            (save-excursion
+              (goto-char (match-beginning 0))
+              (skip-chars-backward " \t")
+              (not (bolp)))
             ;; Make sure the comment starter we found
             ;; is not actually in a string or quoted.
             (let ((new-state
@@ -961,9 +1035,12 @@ ENDPOS is encountered."
          (if (and (car (cdr (cdr state)))
                   (>= (car (cdr (cdr state))) 0))
              (setq last-sexp (car (cdr (cdr state)))))
-         (if (or (nth 4 ostate))
+         ;; If this line started within a comment, indent it as such.
+         (if (or (nth 4 ostate) (nth 7 ostate))
              (c-indent-line))
-         (if (or (nth 3 state))
+         ;; If it ends outside of comments or strings, exit the inner loop.
+         ;; Otherwise move on to next line.
+         (if (or (nth 3 state) (nth 4 state) (nth 7 state))
              (forward-line 1)
            (setq inner-loop-done t)))
        (and endpos
@@ -996,7 +1073,12 @@ ENDPOS is encountered."
                                                        (point)))))
          (forward-line 1)
          (skip-chars-forward " \t")
-         (if (eolp)
+         ;; Don't really reindent if the line is just whitespace,
+         ;; or if it is past the endpos.
+         ;; (The exit test in the outer while
+         ;; does not exit until we have passed the first line
+         ;; past the region.)
+         (if (or (eolp) (and endpos (>= (point) endpos)))
              nil
            (if (and (car indent-stack)
                     (>= (car indent-stack) 0))
@@ -1008,6 +1090,7 @@ ENDPOS is encountered."
                  ;; Is it a new statement?  Is it an else?
                  ;; Find last non-comment character before this line
                  (save-excursion
+                   (setq this-point (point))
                    (setq at-else (looking-at "else\\W"))
                    (setq at-brace (= (following-char) ?{))
                    (setq at-while (looking-at "while\\b"))
@@ -1028,6 +1111,9 @@ ENDPOS is encountered."
                                                  (current-indentation))))
                            ((and at-while (c-backward-to-start-of-do opoint))
                             (setq this-indent (current-indentation)))
+                           ((eq (preceding-char) ?\,)
+                            (goto-char this-point)
+                            (setq this-indent (calculate-c-indent)))
                            (t (setq this-indent (car indent-stack)))))))
              ;; Just started a new nesting level.
              ;; Compute the standard indent for this level.
@@ -1035,10 +1121,14 @@ ENDPOS is encountered."
                           (if (car indent-stack)
                               (- (car indent-stack))
                             opoint))))
+               ;; t means we are in a block comment and should
+               ;; calculate accordingly.
+               (if (eq val t)
+                   (setq val (calculate-c-indent-within-comment)))
                (setcar indent-stack
                        (setq this-indent val))))
            ;; Adjust line indentation according to its contents
-           (if (or (looking-at "case[ \t'/(]\\|default\\>")
+           (if (or (looking-at c-switch-label-regexp)
                    (and (looking-at "[A-Za-z]")
                         (save-excursion
                           (forward-sexp 1)
@@ -1047,7 +1137,16 @@ ENDPOS is encountered."
            (if (= (following-char) ?})
                (setq this-indent (- this-indent c-indent-level)))
            (if (= (following-char) ?{)
-               (setq this-indent (+ this-indent c-brace-offset)))
+               ;; Don't move an open-brace in column 0.
+               ;; This is good when constructs such as
+               ;; `extern "C" {' surround a function definition
+               ;; that should be indented as usual.
+               ;; It is also good for nested functions.
+               ;; It is bad when an open-brace is indented at column 0
+               ;; and you want to fix that, but we can't win 'em all.
+               (if (zerop (current-column))
+                   (setq this-indent 0)
+                 (setq this-indent (+ this-indent c-brace-offset))))
            ;; Don't leave indentation in empty lines.
            (if (eolp) (setq this-indent 0))
            ;; Put chosen indentation into effect.
@@ -1092,10 +1191,46 @@ ENDPOS is encountered."
 (defun c-indent-region (start end)
   (save-excursion
     (goto-char start)
-    (let ((endmark (copy-marker end)))
-      (and (bolp) (not (eolp))
-          (c-indent-line))
-      (indent-c-exp endmark)
+    ;; Advance to first nonblank line.
+    (skip-chars-forward " \t\n")
+    (beginning-of-line)
+    (let ((endmark (copy-marker end))
+         (c-tab-always-indent t))
+      (while (and (bolp) (not (eolp)))
+       ;; Indent one line as with TAB.
+       (let ((shift-amt (c-indent-line))
+             nextline sexpbeg sexpend)
+         (save-excursion
+           ;; Find beginning of following line.
+           (save-excursion
+             (forward-line 1) (setq nextline (point)))
+           ;; Find first beginning-of-sexp for sexp extending past this line.
+           (beginning-of-line)
+           (while (< (point) nextline)
+             (condition-case nil
+                 (progn
+                   (forward-sexp 1)
+                   (setq sexpend (point-marker)))
+               (error (setq sexpend nil)
+                      (goto-char nextline)))
+             (skip-chars-forward " \t\n"))
+           (if sexpend
+               (progn
+                 ;; Make sure the sexp we found really starts on the
+                 ;; current line and extends past it.
+                 (goto-char sexpend)
+                 (backward-sexp 1)
+                 (setq sexpbeg (point)))))
+         ;; If that sexp ends within the region,
+         ;; indent it all at once, fast.
+         (if (and sexpend (> sexpend nextline) (<= sexpend endmark)
+                  (< sexpbeg nextline))
+             (progn
+               (indent-c-exp)
+               (goto-char sexpend)))
+         ;; Move to following line and try again.
+         (and sexpend (set-marker sexpend nil))
+         (forward-line 1)))
       (set-marker endmark nil))))
 \f
 (defun set-c-style (style &optional global)
@@ -1191,15 +1326,28 @@ move forward to the end of the containing preprocessor conditional.
 When going backwards, `#elif' is treated like `#else' followed by `#if'.
 When going forwards, `#elif' is ignored."
   (interactive "p")
-  (let* ((forward (< count 0))
+  (c-forward-conditional (- count) t))
+
+(defun c-backward-conditional (count &optional up-flag)
+  "Move back across a preprocessor conditional, leaving mark behind.
+A prefix argument acts as a repeat count.  With a negative argument,
+move forward across a preprocessor conditional."
+  (interactive "p")
+  (c-forward-conditional (- count) up-flag))
+
+(defun c-forward-conditional (count &optional up-flag)
+  "Move forward across a preprocessor conditional, leaving mark behind.
+A prefix argument acts as a repeat count.  With a negative argument,
+move backward across a preprocessor conditional."
+  (interactive "p")
+  (let* ((forward (> count 0))
         (increment (if forward -1 1))
         (search-function (if forward 're-search-forward 're-search-backward))
         (opoint (point))
         (new))
     (save-excursion
       (while (/= count 0)
-       (if forward (end-of-line))
-       (let ((depth 0) found)
+       (let ((depth (if up-flag 0 -1)) found)
          (save-excursion
            ;; Find the "next" significant line in the proper direction.
            (while (and (not found)
@@ -1211,28 +1359,35 @@ When going forwards, `#elif' is ignored."
                        ;; the regexp matcher.
                        (funcall search-function
                                 "#[ \t]*\\(if\\|elif\\|endif\\)"
-                                nil t)
-                       (progn
-                         (beginning-of-line)
-                         (looking-at "^[ \t]*#[ \t]*\\(if\\|elif\\|endif\\)")))
-             ;; Update depth according to what we found.
+                                nil t))
              (beginning-of-line)
-             (cond ((looking-at "[ \t]*#[ \t]*endif")
-                    (setq depth (+ depth increment)))
-                   ((looking-at "[ \t]*#[ \t]*elif")
-                    (if (and forward (= depth 0))
-                        (setq found (point))))
-                   (t (setq depth (- depth increment))))
-             ;; If this line exits a level of conditional, exit inner loop.
-             (if (< depth 0)
-                 (setq found (point)))
-             ;; When searching forward, start from end of line
-             ;; so that we don't find the same line again.
-             (if forward (end-of-line))))
+             ;; Now verify it is really a preproc line.
+             (if (looking-at "^[ \t]*#[ \t]*\\(if\\|elif\\|endif\\)")
+                 (let ((prev depth))
+                   ;; Update depth according to what we found.
+                   (beginning-of-line)
+                   (cond ((looking-at "[ \t]*#[ \t]*endif")
+                          (setq depth (+ depth increment)))
+                         ((looking-at "[ \t]*#[ \t]*elif")
+                          (if (and forward (= depth 0))
+                              (setq found (point))))
+                         (t (setq depth (- depth increment))))
+                   ;; If we are trying to move across, and we find
+                   ;; an end before we find a beginning, get an error.
+                   (if (and (< prev 0) (< depth prev))
+                       (error (if forward
+                                  "No following conditional at this level"
+                                "No previous conditional at this level")))
+                   ;; When searching forward, start from next line
+                   ;; so that we don't find the same line again.
+                   (if forward (forward-line 1))
+                   ;; If this line exits a level of conditional, exit inner loop.
+                   (if (< depth 0)
+                       (setq found (point)))))))
          (or found
              (error "No containing preprocessor conditional"))
          (goto-char (setq new found)))
-       (setq count (- count increment))))
+       (setq count (+ count increment))))
     (push-mark)
     (goto-char new)))