]> 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 fc784d71518745a57df7089b16a4a7bbafc1022c..429d0bf2292959e1632936a8dd8873a82c63bcf2 100644 (file)
@@ -1,6 +1,5 @@
 ;;; c-mode.el --- C code editing commands for Emacs
-
-;; Copyright (C) 1985, 1986, 1987 Free Software Foundation, Inc.
+;; Copyright (C) 1985, 1986, 1987, 1992 Free Software Foundation, Inc.
 
 ;; Maintainer: FSF
 ;; Keywords: c
 ;; 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
@@ -42,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))
 
@@ -105,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)
@@ -123,11 +132,16 @@ This is in addition to c-continued-statement-offset.")
   "*Non-nil means automatically newline before and after braces,
 and after colons and semicolons, inserted in C code.
 If you do not want a leading newline before braces then use:
-  (define-key c-mode-map "{" 'electric-c-semi)")
+  (define-key c-mode-map \"{\" 'electric-c-semi)")
 
 (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.
@@ -200,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))
@@ -236,16 +250,31 @@ if that value is non-nil."
 
 (defun c-fill-paragraph (&optional arg)
   "Like \\[fill-paragraph] but handle C comments.
-If point is inside a comment, the current paragraph of the comment
-is filled, preserving the comment indentation or line-starting decorations."
+If any of the current line is a comment or within a comment,
+fill the comment or the paragraph of it that point is in,
+preserving the comment indentation or line-starting decorations."
   (interactive "P")
-  (let ((first-line
-        (save-excursion
-          (beginning-of-line)
-          (skip-chars-forward " \t")
-          (looking-at comment-start-skip))))
+  (let* (comment-start-place
+        (first-line
+         ;; Check for obvious entry to comment.
+         (save-excursion
+           (beginning-of-line)
+           (skip-chars-forward " \t\n")
+           (and (looking-at comment-start-skip)
+                (setq comment-start-place (point))))))
     (if (or first-line
-           (eq (calculate-c-indent) t))
+           ;; t if we enter a comment between start of function and this line.
+           (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))))))
        ;; Inside a comment: fill one comment paragraph.
        (let ((fill-prefix
               ;; The prefix for each line of this paragraph
@@ -257,30 +286,96 @@ is filled, 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.
-           (narrow-to-region (point-min)
+           ;; So temporarily exclude everything before the comment start,
+           ;; and everything after the line where the comment ends.
+           ;; If comment-start-place is non-nil, the comment starter is there.
+           ;; Otherwise, point is inside the comment.
+           (narrow-to-region (save-excursion
+                               (if comment-start-place
+                                   (goto-char comment-start-place)
+                                 (search-backward "/*"))
+                               ;; Protect text before the comment start 
+                               ;; by excluding it.  Add spaces to bring back 
+                               ;; proper indentation of that point.
+                               (let ((column (current-column)))
+                                 (prog1 (point)
+                                   (setq chars-to-delete column)
+                                   (insert-char ?\  column))))
                              (save-excursion
+                               (if comment-start-place
+                                   (goto-char (+ comment-start-place 2)))
                                (search-forward "*/" nil 'move)
                                (forward-line 1)
                                (point)))
+           
            (fill-paragraph arg)
            (save-excursion
-             (search-forward "*/")
+             ;; Delete the chars we inserted to avoid clobbering
+             ;; the stuff before the comment start.
+             (goto-char (point-min))
+             (if (> chars-to-delete 0)
+                 (delete-region (point) (+ (point) chars-to-delete)))
+             ;; Find the comment ender (should be on last line of buffer,
+             ;; given the narrowing) and don't leave it on its own line.
+             (goto-char (point-max))
+             (forward-line -1)
+             (search-forward "*/" nil 'move)
              (beginning-of-line)
              (if (looking-at "[ \t]*\\*/")
                  (delete-indentation)))))
@@ -345,7 +440,7 @@ is filled, 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")
@@ -437,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)
@@ -452,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
@@ -515,26 +611,46 @@ Returns nil if line starts inside a string, t if in a comment."
                      (save-excursion
                        (re-search-backward "^[^ \^L\t\n#]" nil 'move)
                        (let (comment lim)
-                         (if (and (looking-at "\\sw\\|\\s_")
-                                  (looking-at "[^\"\n=]*(")
-                                  (progn
-                                    (goto-char (1- (match-end 0)))
-                                    (setq lim (point))
-                                    (forward-sexp 1)
-                                    (skip-chars-forward " \t\f")
-                                    (and (< (point) indent-point)
-                                         (not (memq (following-char)
-                                                    '(?\, ?\;)))))
-                                  ;; Make sure the "function decl" we found
-                                  ;; is not inside a comment.
-                                  (progn
-                                    (beginning-of-line)
-                                    (while (and (not comment)
-                                                (search-forward "/*" lim t))
-                                      (setq comment
-                                            (not (search-forward "*/" lim t))))
-                                    (not comment)))
-                             c-argdecl-indent 0)))))
+                         ;; Recognize the DEFUN macro in Emacs.
+                         (if (save-excursion
+                               ;; Move down to the (putative) argnames line.
+                               (while (and (not (eobp))
+                                           (not (looking-at " *[({}#/]")))
+                                 (forward-line 1))
+                               ;; Go back to the DEFUN, if it is one.
+                               (condition-case nil
+                                   (backward-sexp 1)
+                                 (error))
+                               (beginning-of-line)
+                               (looking-at "DEFUN\\b"))
+                             c-argdecl-indent
+                           (if (and (looking-at "\\sw\\|\\s_")
+                                    ;; 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))
+                                      (condition-case nil
+                                          (forward-sexp 1)
+                                        (error))
+                                      (skip-chars-forward " \t\f")
+                                      (and (< (point) indent-point)
+                                           (not (memq (following-char)
+                                                      '(?\, ?\;)))))
+                                    ;; 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))
+                                        (setq comment
+                                              (not (search-forward "*/" lim t))))
+                                      (not comment)))
+                               c-argdecl-indent 0))))))
                 basic-indent)))
 
 ;;              ;; Now add a little if this is a continuation line.
@@ -629,9 +745,15 @@ Returns nil if line starts inside a string, t if in a comment."
                     ;; The first following code counts
                     ;; if it is before the line we want to indent.
                     (and (< (point) indent-point)
-                         (if (> colon-line-end (point))
-                             (- (current-indentation) c-label-offset)
-                           (current-column)))))
+                         (- 
+                          (if (> colon-line-end (point))
+                              (- (current-indentation) c-label-offset)
+                            (current-column))
+                          ;; If prev stmt starts with open-brace, that
+                          ;; open brace was offset by c-brace-offset.
+                          ;; Compensate to get the column where
+                          ;; an ordinary statement would start.
+                          (if (= (following-char) ?\{) c-brace-offset 0)))))
                 ;; If no previous statement,
                 ;; indent it relative to line brace is on.
                 ;; For open brace in column zero, don't let statement
@@ -764,19 +886,32 @@ 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."
+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")
-  (while (> count 0)
-    (c-beginning-of-statement-1)
-    (setq count (1- count)))
-  (while (< count 0)
-    (c-end-of-statement-1)
-    (setq count (1+ count))))
+  (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)
+           (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)
+       (setq count (1- count)))
+      (while (< count 0)
+       (c-end-of-statement-1)
+       (setq count (1+ count))))))
 
 (defun c-end-of-statement (count)
   "Go to the end of the innermost C statement.
-With prefix arg, go forward N - 1 statements.  Moves forward to end of the
-next statement if already at end."
+With prefix arg, go forward N - 1 statements.
+Move forward to end of the next statement if already at end.
+If within a string or comment, move by sentences instead of statements."
   (interactive "p")
   (c-beginning-of-statement (- count)))
 
@@ -818,14 +953,14 @@ next statement if already at end."
   (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.
@@ -836,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
@@ -854,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
@@ -865,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
@@ -892,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
@@ -927,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))
@@ -939,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"))
@@ -959,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.
@@ -966,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)
@@ -978,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.
@@ -1023,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)
@@ -1114,5 +1318,77 @@ definition and conveniently use this command."
   (if (looking-at "\\\\")
       (delete-region (1+ (point))
                     (progn (skip-chars-backward " \t") (point)))))
+\f
+(defun c-up-conditional (count)
+  "Move back to the containing preprocessor conditional, leaving mark behind.
+A prefix argument acts as a repeat count.  With a negative argument,
+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")
+  (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)
+       (let ((depth (if up-flag 0 -1)) found)
+         (save-excursion
+           ;; Find the "next" significant line in the proper direction.
+           (while (and (not found)
+                       ;; Rather than searching for a # sign that comes
+                       ;; at the beginning of a line aside from whitespace,
+                       ;; search first for a string starting with # sign.
+                       ;; Then verify what precedes it.
+                       ;; This is faster on account of the fastmap feature of
+                       ;; the regexp matcher.
+                       (funcall search-function
+                                "#[ \t]*\\(if\\|elif\\|endif\\)"
+                                nil t))
+             (beginning-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))))
+    (push-mark)
+    (goto-char new)))
 
 ;;; c-mode.el ends here