12

I'm trying to add functionality found in some other code editors to my Emacs configuration, whereby C/C++ code within #if 0...#endif blocks is automatically set to the comment face/font. Based on my testing, cpp-highlight-mode does something like what I want, but requires user action. It seems like tying into the font-lock functionality is the correct option to make the behavior automatic.

I have successfully followed examples in the GNU documentation to change the face of single-line regular expressions. For example:

(add-hook 'c-mode-common-hook
  (lambda ()
    (font-lock-add-keywords nil
      '(("\\<\\(FIXME\\|TODO\\|HACK\\|fixme\\|todo\\|hack\\)" 1 
        font-lock-warning-face t)))))

works fine to highlight debug related keywords anywhere in a file. However, I am having problems matching #if 0...#endif as a multiline regular expression. I found some useful information in this post (How to compose region like "<?php foo; bar; ?>"), that suggested that Emacs must be told specifically to allow for multiline matches. But this code:

(add-hook 'c-mode-common-hook
  (lambda ()
    '(progn
      (setq font-lock-multiline t)
      (font-lock-add-keywords nil
        '(("#if 0\\(.\\|\n\\)*?#endif" 1
          font-lock-comment-face t))))))

still does not work for me. Perhaps my regular expression is wrong (though it appears to work using M-x re-builder), I've messed up my syntax, or I'm following the wrong approach entirely. I'm using Aquamacs 2.1 (which is based on GNU Emacs 23.2.50.1) on OS X 10.6.5, if that makes a difference.

Any assistance would be appreciated!

Community
  • 1
  • 1
pogopop77
  • 123
  • 1
  • 8

1 Answers1

15

Even if you got the multiline regexp to work, you'd still have problems with nested #ifdef/#endif's since it would stop font-locking at the first #endif. This code works, although I'm not sure if there will be a noticeable slow down for large files:

(defun my-c-mode-font-lock-if0 (limit)
  (save-restriction
    (widen)
    (save-excursion
      (goto-char (point-min))
      (let ((depth 0) str start start-depth)
        (while (re-search-forward "^\\s-*#\\s-*\\(if\\|else\\|endif\\)" limit 'move)
          (setq str (match-string 1))
          (if (string= str "if")
              (progn
                (setq depth (1+ depth))
                (when (and (null start) (looking-at "\\s-+0"))
                  (setq start (match-end 0)
                        start-depth depth)))
            (when (and start (= depth start-depth))
              (c-put-font-lock-face start (match-beginning 0) 'font-lock-comment-face)
              (setq start nil))
            (when (string= str "endif")
              (setq depth (1- depth)))))
        (when (and start (> depth 0))
          (c-put-font-lock-face start (point) 'font-lock-comment-face)))))
  nil)

(defun my-c-mode-common-hook ()
  (font-lock-add-keywords
   nil
   '((my-c-mode-font-lock-if0 (0 font-lock-comment-face prepend))) 'add-to-end))

(add-hook 'c-mode-common-hook 'my-c-mode-common-hook)

EDIT: Take into account #else

EDIT #2: Niftier code to handle arbitrary nesting of if/else/endif's

scottfrazer
  • 17,079
  • 4
  • 51
  • 49
  • I added 'else' as another keyword to stop the comment face at. So the one line becomes: (while (and (> depth 0) (re-search-forward "^\\s-*#\\s-*\\(if\\|endif\\|else\\\)" limit 'move)) . Also, for small files (< 100-200k) the slowdown is minimal. On a larger file (4.3MB) it is noticeable -- but I don't often edit files that large. – pogopop77 Dec 29 '10 at 18:25
  • @pogopop77: Handling `#else` is a little trickier than what you did, so I updated the code. It doesn't work on an `#if 0` inside the `#else` of an `#if 0`, but I'll probably update it later. Also: 200k is a small file? ;) – scottfrazer Dec 29 '10 at 21:35
  • Updated for arbitrary nesting. And it's simpler which is always a positive sign :) – scottfrazer Dec 30 '10 at 04:24
  • Works great. I guess I considered any code I'm working on "small" :). I used the SQLite amalgamated C file as my torture test. – pogopop77 Dec 30 '10 at 22:49
  • This works awesome! Anybody have the elisp experience to make it handle #if 1 #else #endif code? – Joseph Lisee Jun 01 '11 at 20:19