[comp.emacs] Here's a C++ mode

wrs@k.cs.cmu.edu (Walter Smith) (05/01/87)

I got several requests for this, so I'm posting it.  Thanks to Dave
Detlefs and Stewart Clamen.

- Walt

------------------------------cut here------------------------------
;; C++ code editing commands for Emacs
;; Dave Detlefs and Stewart Clamen   1987
;; Derived from c-mode.el, Copyright (C) 1985 Richard M. Stallman.

;; Copyright (C) 1987 Free Software Foundation, Inc.

;; This file is part of GNU Emacs.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY.  No author or distributor
;; accepts responsibility to anyone for the consequences of using it
;; or for whether it serves any particular purpose or works at all,
;; unless he says so in writing.  Refer to the GNU Emacs General Public
;; License for full details.

;; Everyone is granted permission to copy, modify and redistribute
;; GNU Emacs, but only under the conditions described in the
;; GNU Emacs General Public License.   A copy of this license is
;; supposed to have been given to you along with GNU Emacs so you
;; can know your rights and responsibilities.  It should be in a
;; file named COPYING.  Among other things, the copyright notice
;; and this notice must be preserved on all copies.

(defvar c++-mode-syntax-table nil
  "Syntax table in use in C++-mode buffers.")
(defvar c++-mode-abbrev-table nil
  "Abbrev table in use in C++-mode buffers.")

(defvar c++-mode-map (make-sparse-keymap)
  "Keymap used in C++ mode.")

(define-key c++-mode-map "\C-j" 'reindent-then-newline-and-indent)
(define-key c++-mode-map "{" 'electric-c++-brace)
(define-key c++-mode-map "}" 'electric-c++-brace)
(define-key c++-mode-map ";" 'electric-c++-semi)
(define-key c++-mode-map ":" 'electric-c++-terminator)
(define-key c++-mode-map "\e\C-h" 'mark-c-function)
(define-key c++-mode-map "\e\C-q" 'indent-c++-exp)
(define-key c++-mode-map "\177" 'backward-delete-char-untabify)
(define-key c++-mode-map "\t" 'c++-indent-line)

;(define-funkey c++-mode-map 'r14 'fill-C-comment)

;; Indentation parameters are defined in C-mode, and reused here.

;(defconst c-indent-level 2
;  "*Indentation of C statements with respect to containing block.")
;(defconst c-brace-imaginary-offset 0
;  "*Imagined indentation of a C open brace that actually follows a statement.")
;(defconst c-brace-offset 0
;  "*Extra indentation for braces, compared with other text in same context.")
;(defconst c-argdecl-indent 5
;  "*Indentation level of declarations of C function arguments.")
;(defconst c-label-offset -2
;  "*Offset of C label lines and case statements relative to usual indentation.")
;(defconst c-continued-statement-offset 2
;  "*Extra indent for lines not starting new statements.")

;(defconst c-auto-newline nil
;  "*Non-nil means automatically newline before and after braces,
;and after colons and semicolons, inserted in C code.")


(defun c++-mode ()
  "Major mode for editing C++ code.  Very much like editing C code.
Expression and list commands understand all C++ brackets.
Tab indents for C++ code.
Comments are delimited with /* ... */ {or with // ... <newline>}
Paragraphs are separated by blank lines only.
Delete converts tabs to spaces as it moves back.
\\{c++-mode-map}
Variables controlling indentation style:
 c-auto-newline
    Non-nil means automatically newline before and after braces,
    and after colons and semicolons, inserted in C code.
 c-indent-level
    Indentation of C statements within surrounding block.
    The surrounding block's indentation is the indentation
    of the line on which the open-brace appears.
 c-continued-statement-offset
    Extra indentation given to a substatement, such as the
    then-clause of an if or body of a while.
 c-brace-offset
    Extra indentation for line if it starts with an open brace.
 c-brace-imaginary-offset
    An open brace following other text is treated as if it were
    this far to the right of the start of its line.
 c-argdecl-indent
    Indentation level of declarations of C function arguments.
 c-label-offset
    Extra indentation for line that is a label, or case or default.

Turning on C++ mode calls the value of the variable c++-mode-hook with
no args,if that value is non-nil."
  (interactive)
  (kill-all-local-variables)
  (use-local-map c++-mode-map)
  (setq major-mode 'c++-mode)
  (setq mode-name "C++")
  (define-abbrev-table 'c++-mode-abbrev-table ())
  (setq local-abbrev-table c++-mode-abbrev-table)
  (if (not c++-mode-syntax-table)
      (let ((i 0))
	(setq c++-mode-syntax-table (make-syntax-table))
	(set-syntax-table c++-mode-syntax-table)
	(modify-syntax-entry ?\\ "\\")
	(modify-syntax-entry ?/ ". 12")
	(modify-syntax-entry ?\n ">")
	(modify-syntax-entry ?+ ".")
	(modify-syntax-entry ?- ".")
	(modify-syntax-entry ?= ".")
	(modify-syntax-entry ?% ".")
	(modify-syntax-entry ?< ".")
	(modify-syntax-entry ?> ".")
	(modify-syntax-entry ?\' "\""))
      (set-syntax-table c++-mode-syntax-table))
  (make-local-variable 'paragraph-start)
  (setq paragraph-start (concat "^$\\|" page-delimiter))
  (make-local-variable 'paragraph-separate)
  (setq paragraph-separate paragraph-start)
  (make-local-variable 'indent-line-function)
  (setq indent-line-function 'c++-indent-line)
  (make-local-variable 'require-final-newline)
  (setq require-final-newline t)
  (make-local-variable 'comment-start)
  (setq comment-start "// ")
  (make-local-variable 'comment-end)
  (setq comment-end "")
  (make-local-variable 'comment-column)
  (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 'parse-sexp-ignore-comments)
  (setq parse-sexp-ignore-comments nil)
  (run-hooks 'c++-mode-hook))

;; This is used by indent-for-comment
;; to decide how much to indent a comment in C++ code
;; based on its context.

(defun c++-comment-indent ()
  (let ((cur-col (current-column)))
    (save-excursion
      (forward-line -1)
      (end-of-line 1)
      (if (re-search-backward comment-start-skip
			      (save-excursion (beginning-of-line 1) (point))
			      t)
	  (progn (match-beginning 0)
		 (if (= (current-column) 0) 0
		     (max (1+ cur-col) (current-column))))
	  ; Else indent at comment column, except leave at least one space.
	  (forward-line 1)
	  (end-of-line 1)
	  (skip-chars-backward " \t")
	  (max (1+ cur-col) comment-column)))))

(defun electric-c++-brace (arg)
  "Insert character and correct line's indentation."
  (interactive "P")
  (let (insertpos)
    (if (and (not arg)
	     (eolp)
	     (or (save-excursion
		   (skip-chars-backward " \t")
		   (bolp))
		 (if c-auto-newline
		     (progn (c++-indent-line) (newline) t)
		   nil)))
	(progn
	  (insert last-command-char)
	  (c++-indent-line nil)
	  (if c-auto-newline
	      (progn
		(setq insertpos (1- (point)))
		(newline)
		(c++-indent-line nil)))
	  (save-excursion
	    (if insertpos (goto-char (1+ insertpos)))
	    (delete-char -1))))
    (if insertpos
	(save-excursion
	  (goto-char insertpos)
	  (self-insert-command (prefix-numeric-value arg)))
      (self-insert-command (prefix-numeric-value arg)))))

(defun electric-c++-semi (arg)
  "Insert character and correct line's indentation."
  (interactive "P")
  (if c-auto-newline
      (electric-c++-terminator arg)
    (self-insert-command (prefix-numeric-value arg))))

(defun electric-c++-terminator (arg)
  "Insert character and correct line's indentation."
  (interactive "P")
  (let (insertpos (end (point)))
    (if (and (not arg) (eolp)
	     (not (save-excursion
		    (beginning-of-line)
		    (skip-chars-forward " \t")
		    (or (= (following-char) ?#)
			(let ((pps (parse-partial-sexp (point) end)))
			  (or (nth 3 pps) (nth 4 pps) (nth 5 pps)))))))
	(progn
	  (insert last-command-char)
	  (c++-indent-line nil)
	  (and c-auto-newline
	       (not (c-inside-parens-p))
	       (progn
		 (setq insertpos (1- (point)))
		 (newline)
		 (c++-indent-line nil)))
	  (save-excursion
	    (if insertpos (goto-char (1+ insertpos)))
	    (delete-char -1))))
    (if insertpos
	(save-excursion
	  (goto-char insertpos)
	  (self-insert-command (prefix-numeric-value arg)))
      (self-insert-command (prefix-numeric-value arg)))))

(defun c-inside-parens-p ()
  (condition-case ()
      (save-excursion
	(save-restriction
	  (narrow-to-region (point)
			    (progn (beginning-of-defun) (point)))
	  (goto-char (point-max))
	  (= (char-after (or (scan-lists (point) -1 1) (point-min))) ?\()))
    (error nil)))

(defun c++-indent-line (&optional whole-exp)
  "Indent current line as C++ code.
Argument means shift any additional lines of grouping
rigidly with thls line."
  (interactive "P")
  (let ((indent (calculate-c++-indent nil))
	beg end shift-amt
	(case-fold-search nil)
	(pos (- (point-max) (point))))
    (beginning-of-line)
    (setq beg (point))
    (cond ((eq indent nil)
	   (setq indent (current-indentation)))
	  ((eq indent t)
	   (setq indent (calculate-c-indent-within-comment)))
	  ((looking-at "[ \t]*#")
	   (setq indent 0))
	  (t
	   (skip-chars-forward " \t")
	   (if (listp indent) (setq indent (car indent)))
	   (cond ((looking-at "\\(public\\|private\\|protected\\):")
		  (setq indent (- indent c-indent-level)))
		 ((and (looking-at "[A-Za-z]")
		       (save-excursion
			 (forward-sexp 1)
			 (looking-at ":[^:]")))
		  (setq indent (max 1 (+ indent c-label-offset))))
		 ((looking-at "case\\b")
		  (setq indent (- indent c-indent-level)))
		 ((looking-at "else\\b")
		  (setq indent (save-excursion
				 (c-backward-to-start-of-if)
				 (current-indentation))))
		 ((looking-at "friend\[ \t]class[ \t]")
		  (setq indent (- indent c-indent-level)))
		 ((= (following-char) ?})
		  (setq indent (- indent c-indent-level)))
		 ((= (following-char) ?{)
		  (setq indent (+ indent c-brace-offset))))))
    (skip-chars-forward " \t")
    (setq shift-amt (- indent (current-column)))
    (if (zerop shift-amt)
	(if (> (- (point-max) pos) (point))
	    (goto-char (- (point-max) pos)))
      (delete-region beg (point))
      (indent-to indent)
      ;; If initial point was within line's indentation,
      ;; position after the indentation.  Else stay at same point in text.
      (if (> (- (point-max) pos) (point))
	  (goto-char (- (point-max) pos)))
      ;; If desired, shift remaining lines of expression the same amount.
      (and whole-exp
	   (save-excursion
	     (goto-char beg)
	     (forward-sexp 1)
	     (setq end (point))
	     (goto-char beg)
	     (forward-line 1)
	     (setq beg (point))
	     (> end beg))
	   (indent-code-rigidly beg end shift-amt "#")))))

(defun calculate-c++-indent (&optional parse-start)
  "Return appropriate indentation for current line as C++ code.
In usual case returns an integer: the column to indent to.
Returns nil if line starts inside a string, t if in a comment."
  (save-excursion
    (beginning-of-line)
    (let ((indent-point (point))
	  (case-fold-search nil)
	  state
	  containing-sexp)
      (if parse-start
	  (goto-char parse-start)
	(beginning-of-defun))
      (while (< (point) indent-point)
	(setq parse-start (point))
	(setq state (parse-partial-sexp (point) indent-point 0))
	(setq containing-sexp (car (cdr state))))
      (cond ((or (nth 3 state) (nth 4 state))
	     ;; return nil or t if should not change this line
	     (nth 4 state))
	    ((null containing-sexp)
	     ;; Line is at top level.  May be data or function definition,
	     ;; or may be function argument declaration.
	     ;; Indent like the previous top level line
	     ;; unless that ends in a closeparen without semicolon,
	     ;; in which case this line is the first argument decl.
	     (goto-char indent-point)
	     (skip-chars-forward " \t")
	     (if (= (following-char) ?{)
		 0   ; Unless it starts a function body
	       (c++-backward-to-noncomment (or parse-start (point-min)))
	       (if (= (preceding-char) ?\))
		   c-argdecl-indent
		 (current-indentation))))
	    ((/= (char-after containing-sexp) ?{)
	     ;; line is expression, not statement:
	     ;; indent to just after the surrounding open.
	     (goto-char (1+ containing-sexp))
	     (current-column))
	    (t
	     ;; Statement.  Find previous non-comment character.
;;;	     (message "statement..")
	     (goto-char indent-point)
	     (c++-backward-to-noncomment containing-sexp)
	     (if (not (memq (preceding-char) '(nil ?\, ?\; ?} ?: ?\{)))
		 ;; This line is continuation of preceding line's statement;
		 ;; indent  c-continued-statement-offset  more than the
		 ;; previous line of the statement.
		 (progn
		   (c-backward-to-start-of-continued-exp containing-sexp)
		   (+ c-continued-statement-offset (current-column)))
	       ;; This line starts a new statement.
	       ;; Position following last unclosed open.
	       (goto-char containing-sexp)
	       ;; Is line first statement after an open-brace?
	       (or
		 ;; If no, find that first statement and indent like it.
		 (save-excursion
		   (forward-char 1)
		   (while (progn (skip-chars-forward " \t\n")
				 (looking-at
				  (concat
				   "#\\|/\\*\\|//"
				   "\\|case[ \t]"
				   "\\|[a-zA-Z0-9_$]*:[^:]"
				   "\\|friend[ \t]class[ \t]")))
		     ;; Skip over comments and labels following openbrace.
		     (cond ((= (following-char) ?\#)
			    (forward-line 1))
			   ((looking-at "/\\*")
			    (search-forward "*/" nil 'move))
			   ((looking-at "//\\|friend[ \t]class[ \t]")
			    (forward-line 1))
			   (t
			    (re-search-forward ":[^:]"))))
;;;		   (message "P=%d IP=%d CC=%d" (point)
;;;			    indent-point (current-column))
		   ;; The first following code counts
		   ;; if it is before the line we want to indent.
		   (and (< (point) indent-point)
			(current-column)))
		 ;; If no previous statement,
		 ;; indent it relative to line brace is on.
		 ;; For open brace in column zero, don't let statement
		 ;; start there too.  If c-indent-offset is zero,
		 ;; use c-brace-offset + c-continued-statement-offset instead.
		 ;; For open-braces not the first thing in a line,
		 ;; add in c-brace-imaginary-offset.
;;;		 (and (message "1st statement...") nil)
		 (+ (if (and (bolp) (zerop c-indent-level))
			(+ c-brace-offset c-continued-statement-offset)
		      c-indent-level)
		    (if (save-excursion (skip-chars-backward " \t")
					(bolp))
			0 c-brace-imaginary-offset)
		    (current-indentation)))))))))

(defun calculate-c-indent-within-comment ()
  "Return the indentation amount for line, assuming that
the current line is to be regarded as part of a block comment."
  (let (end star-start)
    (save-excursion
      (beginning-of-line)
      (skip-chars-forward " \t")
      (setq star-start (= (following-char) ?\*))
      (skip-chars-backward " \t\n")
      (setq end (point))
      (beginning-of-line)
      (skip-chars-forward " \t")
      (and (re-search-forward "/\\*[ \t]*" end t)
	   star-start
	   (goto-char (1+ (match-beginning 0))))
      (current-column))))


(defun c++-backward-to-noncomment (lim)
  (let (opoint stop)
    (while (not stop)
      (skip-chars-backward " \t\n\r\f" lim)
      (setq opoint (point))
      (if (and (>= (point) (+ 2 lim))
	       (save-excursion
		 (forward-char -2)
		 (looking-at "\\*/")))
	  (search-backward "/*" lim 'move)
	(or (search-backward "//" (max (point-bol) lim) 'move)
	    (progn
	      (beginning-of-line)
	      (skip-chars-forward " \t")
	      (if (looking-at "#\\|//")
		  (setq stop (<= (point) lim))
		(setq stop t)
		(goto-char opoint))))))))

(defun c-backward-to-start-of-continued-exp (lim)
  (if (= (preceding-char) ?\))
      (forward-sexp -1))
  (beginning-of-line)
  (if (<= (point) lim)
      (goto-char (1+ lim)))
  (skip-chars-forward " \t"))

(defun c-backward-to-start-of-if (&optional limit)
  "Move to the start of the last ``unbalanced'' if."
  (or limit (setq limit (save-excursion (beginning-of-defun) (point))))
  (let ((if-level 1)
	(case-fold-search nil))
    (while (not (zerop if-level))
      (backward-sexp 1)
      (cond ((looking-at "else\\b")
	     (setq if-level (1+ if-level)))
	    ((looking-at "if\\b")
	     (setq if-level (1- if-level)))
	    ((< (point) limit)
	     (setq if-level 0)
	     (goto-char limit))))))


(defun indent-c++-exp ()
  "Indent each line of the C++ grouping following point."
  (interactive)
  (let ((indent-stack (list nil))
	(contain-stack (list (point)))
	(case-fold-search nil)
	restart outer-loop-done inner-loop-done state ostate
	this-indent last-sexp
	at-else
	(opoint (point))
	(next-depth 0))
    (save-excursion
      (forward-sexp 1))
    (save-excursion
      (setq outer-loop-done nil)
      (while (and (not (eobp)) (not outer-loop-done))
	(setq last-depth next-depth)
	;; Compute how depth changes over this line
	;; plus enough other lines to get to one that
	;; does not end inside a comment or string.
	;; Meanwhile, do appropriate indentation on comment lines.
	(setq innerloop-done nil)
	(while (and (not innerloop-done)
		    (not (and (eobp) (setq outer-loop-done t))))
	  (setq ostate state)
	  (setq state (parse-partial-sexp (point) (progn (end-of-line) (point))
					  nil nil state))
	  (setq next-depth (car state))
	  (if (and (car (cdr (cdr state)))
		   (>= (car (cdr (cdr state))) 0))
	      (setq last-sexp (car (cdr (cdr state)))))
	  (if (or (nth 4 ostate))
	      (c++-indent-line))
	  (if (or (nth 3 state))
	      (forward-line 1)
	    (setq innerloop-done t)))
	(if (<= next-depth 0)
	    (setq outer-loop-done t))
	(if outer-loop-done
	    nil
	  (if (/= last-depth next-depth)
	      (setq last-sexp nil))
	  (while (> last-depth next-depth)
	    (setq indent-stack (cdr indent-stack)
		  contain-stack (cdr contain-stack)
		  last-depth (1- last-depth)))
	  (while (< last-depth next-depth)
	    (setq indent-stack (cons nil indent-stack)
		  contain-stack (cons nil contain-stack)
		  last-depth (1+ last-depth)))
	  (if (null (car contain-stack))
	      (setcar contain-stack (or (car (cdr state))
					(save-excursion (forward-sexp -1)
							(point)))))
	  (forward-line 1)
	  (skip-chars-forward " \t")
	  (if (eolp)
	      nil
	    (if (and (car indent-stack)
		     (>= (car indent-stack) 0))
		;; Line is on an existing nesting level.
		;; Lines inside parens are handled specially.
		(if (/= (char-after (car contain-stack)) ?{)
		    (setq this-indent (car indent-stack))
		  ;; Line is at statement level.
		  ;; Is it a new statement?  Is it an else?
		  ;; Find last non-comment character before this line
		  (save-excursion
		    (setq at-else (looking-at "else\\W"))
		    (c++-backward-to-noncomment opoint)
		    (if (not (memq (preceding-char) '(nil ?\, ?\; ?} ?: ?{)))
			;; Preceding line did not end in comma or semi;
			;; indent this line  c-continued-statement-offset
			;; more than previous.
			(progn
			  (c-backward-to-start-of-continued-exp (car contain-stack))
			  (setq this-indent
				(+ c-continued-statement-offset (current-column))))
		      ;; Preceding line ended in comma or semi;
		      ;; use the standard indent for this level.
		      (if at-else
			  (progn (c-backward-to-start-of-if opoint)
				 (setq this-indent (current-indentation)))
			(setq this-indent (car indent-stack))))))
	      ;; Just started a new nesting level.
	      ;; Compute the standard indent for this level.
	      (let ((val (calculate-c++-indent
			  (if (car indent-stack)
			      (- (car indent-stack))))))
		(setcar indent-stack
			(setq this-indent val))))
	    ;; Adjust line indentation according to its contents
	    (if (looking-at "\\(public\\|private\\|protected\\):")
		(setq this-indent (- this-indent c-indent-level)))
	    (if (and (looking-at "[A-Za-z]")
		     (save-excursion
		       (forward-sexp 1)
		       (looking-at ":[^:]")))
		(setq this-indent (max 1 (+ this-indent c-label-offset))))
	    (if (looking-at "case[ \t]")
		(setq this-indent (- this-indent c-indent-level)))
	    (if (looking-at "friend[ \t]class[ \t]")
		(setq this-indent (- this-indent c-indent-level)))
	    (if (= (following-char) ?})
		(setq this-indent (- this-indent c-indent-level)))
	    (if (= (following-char) ?{)
		(setq this-indent (+ this-indent c-brace-offset)))
	    ;; Put chosen indentation into effect.
	    (or (= (current-column) this-indent)
		(= (following-char) ?\#)
		(progn
		  (delete-region (point) (progn (beginning-of-line) (point)))
		  (indent-to this-indent)))
	    ;; Indent any comment following the text.
	    (or (looking-at comment-start-skip)
		(if (re-search-forward comment-start-skip (save-excursion (end-of-line) (point)) t)
		    (progn (indent-for-comment) (beginning-of-line))))))))))

(defun fill-C-comment ()
  (interactive)
  (save-excursion
    (let ((save fill-prefix))
      (beginning-of-line 1)
      (save-excursion
	(re-search-forward comment-start-skip
			   (save-excursion (end-of-line) (point))
			   t)
	(goto-char (match-end 0))
	(set-fill-prefix))
      (while (looking-at fill-prefix)
	(previous-line 1))
      (next-line 1)
      (insert-string "\n")
      (fill-paragraph nil)
      (delete-char -1)
      (setq fill-prefix save))))

(defun point-bol ()
  "Returns the value of the point at the beginning of the current
line."
  (save-excursion
    (beginning-of-line)
    (point)))

(defun previous-line-starts-with (string)
  "Returns t if previous nonblank line begins with STRING"
  (save-excursion
    (beginning-of-line 0)
    (skip-chars-forward " \t")
    (if (eolp)
	(previous-line-starts-with string)
      (looking-at string))))

;;; This section defines some functions that are useful in defining
;;; parameterized functions and classes using macros.

(defun c++-macroize-region (start end)
  (interactive "r")
  (save-excursion
    (let ((last-line (progn (goto-char end)
			    (count-lines (point-min) (point)))))
      (goto-char start)
      (let ((i (count-lines (point-min) (point))))
	(while (<= i last-line)
	  (end-of-line 1)
	  (let ((target (max (+ (current-column) 1) 78)))
	    (while (< (current-column) target) (insert "\t"))
	    (delete-char -1)
	    (while (< (current-column) target) (insert " "))
	    (insert "\\")
	    (next-line 1)
	    (setq i (1+ i))))))))

(define-key c++-mode-map "\C-c\\" 'c++-macroize-region)
------------------------------cut here------------------------------
-- 
      Walter Smith, Math/CS undergraduate, Carnegie-Mellon University
		    CS graduate student starting August!
       uucp: ...!seismo!cmu-cs-k!wrs          ARPA: wrs@k.cs.cmu.edu
	       usps: 5139 Forbes Ave.; Pittsburgh, PA  15213