Subject: [PATCH] Automatically detect whether .h file is C or C++

From: Michal Nazarewicz <[email-masked]>
Date: Tue, 7 Jun 2016 15:16:25 +0200

* lisp/progmodes/cc-mode.el (c-or-c++-mode): A new function which
analyses contents of the buffer to determine whether it looks like C++
source code and based on that enables c-mode or c++-mode.
(c-or-c++-mode--regexp): Regular expression which, when matches
a buffer, signals file is C++.
---
 etc/NEWS                       |  7 +++++
 lisp/progmodes/cc-mode.el      | 39 +++++++++++++++++++++++--
 test/lisp/progmodes/cc-mode.el | 65 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 109 insertions(+), 2 deletions(-)
 create mode 100644 test/lisp/progmodes/cc-mode.el

diff --git a/etc/NEWS b/etc/NEWS
index 23f6efc..8f4be56 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -373,6 +373,13 @@ string is computed dynamically based on 'url-privacy-level'.
 colorful faces to make it more obvious to the user what the state is.
 See the 'vc-faces' customization group.
 
+** CC mode
+
+*** Opening a .h file will turn C or C++ mode depending on language used.
+This is done with the help of 'c-or-c++-mode' function which analyses
+contents of the buffer to determine whether it's a C or C++ source
+file.
+
 
 * New Modes and Packages in Emacs 25.2
 
diff --git a/lisp/progmodes/cc-mode.el b/lisp/progmodes/cc-mode.el
index 9ab0480..f544dcb 100644
--- a/lisp/progmodes/cc-mode.el
+++ b/lisp/progmodes/cc-mode.el
@@ -1444,7 +1444,8 @@ c-mode-map
 ;;;###autoload (add-to-list 'auto-mode-alist '("\\.[ch]\\(pp\\|xx\\|\\+\\+\\)\\'" . c++-mode))
 ;;;###autoload (add-to-list 'auto-mode-alist '("\\.\\(CC?\\|HH?\\)\\'" . c++-mode))
 
-;;;###autoload (add-to-list 'auto-mode-alist '("\\.[ch]\\'" . c-mode))
+;;;###autoload (add-to-list 'auto-mode-alist '("\\.c\\'" . c-mode))
+;;;###autoload (add-to-list 'auto-mode-alist '("\\.h\\'" . c-or-c++-mode))
 
 ;; NB: The following two associate yacc and lex files to C Mode, which
 ;; is not really suitable for those formats.  Anyway, afaik there's
@@ -1485,6 +1486,40 @@ c-mode
   (cc-imenu-init cc-imenu-c-generic-expression)
   (c-run-mode-hooks 'c-mode-common-hook))
 
+(defconst c-or-c++-mode--regexp
+  (eval-when-compile
+    (let ((id "[a-zA-Z0-9_]+") (ws "[ \t\r]+") (ws-maybe "[ \t\r]*"))
+      (concat "^" ws-maybe "\\(?:"
+                    "using"     ws "\\(?:namespace" ws "std;\\|std::\\)"
+              "\\|" "namespace" "\\(:?" ws id "\\)?" ws-maybe "{"
+              "\\|" "class"     ws id ws-maybe "[:{\n]"
+              "\\|" "template"  ws-maybe "<.*>"
+              "\\|" "#include"  ws-maybe "<\\(?:string\\|iostream\\|map\\)>"
+              "\\)")))
+  "A regexp applied to C header files to check if they are really C++.")
+
+;;;###autoload
+(defun c-or-c++-mode ()
+  "Analyse buffer and enable either C or C++ mode.
+
+Some people and projects use .h extension for C++ header files
+which is also the one used for C header files.  This makes
+matching on file name insufficient for detecting major mode that
+should be used.
+
+This function attempts to use file contents to determine whether
+the code is C or C++ and based on that chooses whether to enable
+`c-mode' or `c++-mode'."
+  (if (save-excursion
+        (save-restriction
+          (save-match-data
+            (widen)
+            (goto-char (point-min))
+            (re-search-forward c-or-c++-mode--regexp
+                               (+ (point) c-guess-region-max) t))))
+      (c++-mode)
+    (c-mode)))
+
 
 ;; Support for C++
 
diff --git a/test/lisp/progmodes/cc-mode.el b/test/lisp/progmodes/cc-mode.el
new file mode 100644
index 0000000..6cd9fa4
--- /dev/null
+++ b/test/lisp/progmodes/cc-mode.el
@@ -0,0 +1,65 @@
+;;; cc-mode-tests.el --- Test suite for cc-mode.  -*- lexical-binning: t -*-
+
+;; Copyright (C) 2016 Free Software Foundation, Inc.
+
+;; Author: Michal Nazarewicz <[email-masked]>
+;; Keywords:       internal
+;; Human-Keywords: internal
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'ert)
+(require 'ert-x)
+(require 'cc-mode)
+
+(ert-deftest c-or-c++-mode ()
+  "Test c-or-c++-mode language detection."
+  (cl-letf* ((mode nil)
+             (do-test (lambda (content expected)
+                        (delete-region (point-min) (point-max))
+                        (insert content)
+                        (setq mode nil)
+                        (c-or-c++-mode)
+                        (unless(eq expected mode)
+                          (ert-fail
+                           (format "expected %s but got %s when testing '%s'"
+                                   expected mode content)))))
+             ((symbol-function 'c-mode) (lambda () (setq mode 'c-mode)))
+             ((symbol-function 'c++-mode) (lambda () (setq mode 'c++-mode))))
+    (with-temp-buffer
+      (mapc (lambda (content)
+              (funcall do-test content 'c++-mode)
+              (funcall do-test (concat "// " content) 'c-mode)
+              (funcall do-test (concat " * " content) 'c-mode))
+            '("using \t namespace \t std;"
+              "using \t std::string;"
+              "namespace \t {"
+              "namespace \t foo \t {"
+              "class \t Blah_42 \t {"
+              "class \t Blah_42 \t \n"
+              "class \t _42_Blah:public Foo {"
+              "template \t < class T >"
+              "template< class T >"
+              "#include <string>"
+              "#include<iostream>"
+              "#include \t <map>"))
+
+      (mapc (lambda (content) (funcall do-test content 'c-mode))
+            '("struct \t Blah_42 \t {"
+              "struct template {"
+              "#include <string.h>")))))
-- 
2.8.0.rc3.226.g39d4020