]> code.delx.au - gnu-emacs/blobdiff - lisp/progmodes/make-mode.el
(gnus-newsrc-file-version): Add defvar.
[gnu-emacs] / lisp / progmodes / make-mode.el
index 94fc375d1fc09a42ac9096390a774a97d3dec2a1..b8336691307863839dbe61991d308663530c3cea 100644 (file)
@@ -1,6 +1,7 @@
 ;;; make-mode.el --- makefile editing commands for Emacs
 
-;; Copyright (C) 1992, 1994, 1999, 2000 Free Software Foundation, Inc.
+;; Copyright (C) 1992, 1994, 1999, 2000, 2001, 2002, 2003, 2004, 2005
+;; Free Software Foundation, Inc.
 
 ;; Author: Thomas Neumann <tom@smart.bo.open.de>
 ;;     Eric S. Raymond <esr@snark.thyrsus.com>
@@ -8,11 +9,6 @@
 ;; Adapted-By: ESR
 ;; Keywords: unix, tools
 
-;; RMS:
-;; This needs work.
-;; Also, the doc strings need fixing: the first line doesn't stand alone,
-;; and other usage is not high quality.  Symbol names don't have `...'.
-
 ;; This file is part of GNU Emacs.
 
 ;; GNU Emacs is free software; you can redistribute it and/or modify
@@ -27,8 +23,8 @@
 
 ;; You should have received a copy of the GNU General Public License
 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
 
 ;;; Commentary:
 
@@ -52,7 +48,7 @@
 ;; prerequisites.
 ;;
 ;; The command C-c C-b pops up a browser window listing all target and
-;; macro names.  You can mark or unmark items wit C-c SPC, and insert
+;; macro names.  You can mark or unmark items with C-c SPC, and insert
 ;; all marked items back in the Makefile with C-c TAB.
 ;;
 ;; The command C-c TAB in the makefile buffer inserts a GNU make builtin.
@@ -63,6 +59,7 @@
 ;;
 ;; To Do:
 ;;
+;; * Add missing doc strings, improve terse doc strings.
 ;; * Eliminate electric stuff entirely.
 ;; * It might be nice to highlight targets differently depending on
 ;;   whether they are up-to-date or not.  Not sure how this would
@@ -88,8 +85,6 @@
 
 ;;; Code:
 
-(provide 'make-mode)
-
 ;; Sadly we need this for a macro.
 (eval-when-compile
   (require 'imenu)
   :group 'tools
   :prefix "makefile-")
 
-(defface makefile-space-face
-   '((((class color)) (:background  "hotpink"))
-     (t (:reverse-video t)))
+(defface makefile-space
+  '((((class color)) (:background  "hotpink"))
+    (t (:reverse-video t)))
   "Face to use for highlighting leading spaces in Font-Lock mode."
   :group 'faces
-  :group 'makemode)
+  :group 'makefile)
+(put 'makefile-space-face 'face-alias 'makefile-space)
+
+(defface makefile-targets
+  ;; This needs to go along both with foreground and background colors (i.e. shell)
+  '((t (:inherit font-lock-function-name-face)))
+  "Face to use for additionally highlighting rule targets in Font-Lock mode."
+  :group 'faces
+  :group 'makefile
+  :version "22.1")
+
+(defface makefile-shell
+  ()
+  ;;'((((class color) (min-colors 88) (background light)) (:background  "seashell1"))
+  ;;  (((class color) (min-colors 88) (background dark)) (:background  "seashell4")))
+  "Face to use for additionally highlighting Shell commands in Font-Lock mode."
+  :group 'faces
+  :group 'makefile
+  :version "22.1")
+
+(defface makefile-makepp-perl
+  '((((class color) (background light)) (:background  "LightBlue1")) ; Camel Book
+    (((class color) (background dark)) (:background  "DarkBlue"))
+    (t (:reverse-video t)))
+  "Face to use for additionally highlighting Perl code in Font-Lock mode."
+  :group 'faces
+  :group 'makefile
+  :version "22.1")
 
 (defcustom makefile-browser-buffer-name "*Macros and Targets*"
   "*Name of the macro- and target browser buffer."
@@ -194,7 +216,7 @@ Otherwise filenames are omitted."
   :type 'boolean
   :group 'makefile)
 
-(defcustom makefile-cleanup-continuations-p t
+(defcustom makefile-cleanup-continuations nil
   "*If non-nil, automatically clean up continuation lines when saving.
 A line is cleaned up by removing all whitespace following a trailing
 backslash.  This is done silently.
@@ -212,7 +234,7 @@ to MODIFY A FILE WITHOUT YOUR CONFIRMATION when \"it seems necessary\"."
 
 ;;
 ;; Special targets for DMake, Sun's make ...
-;; 
+;;
 (defcustom makefile-special-targets-list
   '(("DEFAULT")      ("DONE")        ("ERROR")        ("EXPORT")
     ("FAILED")       ("GROUPEPILOG") ("GROUPPROLOG")  ("IGNORE")
@@ -240,67 +262,250 @@ not be enclosed in { } or ( )."
 ;; Note that the first big subexpression is used by font lock.  Note
 ;; that if you change this regexp you might have to fix the imenu
 ;; index in makefile-imenu-generic-expression.
-(defconst makefile-dependency-regex
-  "^ *\\([^ \n\t#:=]+\\([ \t]+\\([^ \t\n#:=]+\\|\\$[({][^ \t\n#})]+[})]\\)\\)*\\)[ \t]*:\\([ \t]*$\\|\\([^=\n].*$\\)\\)"
+(defvar makefile-dependency-regex
+  ;; Allow for two nested levels $(v1:$(v2:$(v3:a=b)=c)=d)
+  "^\\(\\(?:\\$\\(?:[({]\\(?:\\$\\(?:[({]\\(?:\\$\\(?:[^({]\\|.[^\n$#})]+?[})]\\)\\|[^\n$#)}]\\)+?[})]\\|[^({]\\)\\|[^\n$#)}]\\)+?[})]\\|[^({]\\)\\|[^\n$#:=]\\)+?\\)\\(:\\)\\(?:[ \t]*$\\|[^=\n]\\(?:[^#\n]*?;[ \t]*\\(.+\\)\\)?\\)"
   "Regex used to find dependency lines in a makefile.")
 
-;; Note that the first subexpression is used by font lock.  Note
-;; that if you change this regexp you might have to fix the imenu
-;; index in makefile-imenu-generic-expression.
+(defconst makefile-bsdmake-dependency-regex
+  (progn (string-match (regexp-quote "\\(:\\)") makefile-dependency-regex)
+        (replace-match "\\([:!]\\)" t t makefile-dependency-regex))
+  "Regex used to find dependency lines in a BSD makefile.")
+
+(defvar makefile-dependency-skip "^:"
+  "Characters to skip to find a line that might be a dependency.")
+
+(defvar makefile-rule-action-regex
+  "^\t[ \t]*\\([-@]*\\)[ \t]*\\(\\(?:.*\\\\\n\\)*.*\\)"
+  "Regex used to highlight rule action lines in font lock mode.")
+
+(defconst makefile-makepp-rule-action-regex
+  ;; Don't care about initial tab, but I don't know how to font-lock correctly without.
+  "^\t[ \t]*\\(\\(?:\\(?:noecho\\|ignore[-_]error\\|[-@]+\\)[ \t]*\\)*\\)\\(\\(&\\S +\\)?\\(?:.*\\\\\n\\)*.*\\)"
+  "Regex used to highlight makepp rule action lines in font lock mode.")
+
+(defconst makefile-bsdmake-rule-action-regex
+  (progn (string-match "-@" makefile-rule-action-regex)
+        (replace-match "-+@" t t makefile-rule-action-regex))
+  "Regex used to highlight BSD rule action lines in font lock mode.")
+
+;; Note that the first and second subexpression is used by font lock.  Note
+;; that if you change this regexp you might have to fix the imenu index in
+;; makefile-imenu-generic-expression.
 (defconst makefile-macroassign-regex
-  "^ *\\([^ \n\t][^:#= \t\n]*\\)[ \t]*[*:+]?[:?]?="
+  "^ *\\([^ \n\t][^:#= \t\n]*\\)[ \t]*\\(?:!=\\|[*:+]?[:?]?=\\)"
   "Regex used to find macro assignment lines in a makefile.")
 
+(defconst makefile-var-use-regex
+  "[^$]\\$[({]\\([-a-zA-Z0-9_.]+\\|[@%<?^+*][FD]?\\)"
+  "Regex used to find $(macro) uses in a makefile.")
+
 (defconst makefile-ignored-files-in-pickup-regex
   "\\(^\\..*\\)\\|\\(.*~$\\)\\|\\(.*,v$\\)\\|\\(\\.[chy]\\)"
   "Regex for filenames that will NOT be included in the target list.")
 
 (if (fboundp 'facemenu-unlisted-faces)
-    (add-to-list 'facemenu-unlisted-faces 'makefile-space-face))
-(defvar makefile-space-face 'makefile-space-face
+    (add-to-list 'facemenu-unlisted-faces 'makefile-space))
+(defvar makefile-space 'makefile-space
   "Face to use for highlighting leading spaces in Font-Lock mode.")
 
-(defconst makefile-font-lock-keywords
-  (list
-
-   ;; Do macro assignments.  These get the "variable-name" face rather
-   ;; arbitrarily.
-   (list makefile-macroassign-regex 1 'font-lock-variable-name-face)
+;; These lists were inspired by the old solution.  But they are silly, because
+;; you can't differentiate what follows.  They need to be split up.
+(defconst makefile-statements '("include")
+  "List of keywords understood by standard make.")
+
+(defconst makefile-automake-statements
+  `("if" "else" "endif" ,@makefile-statements)
+  "List of keywords understood by automake.")
+
+(defconst makefile-gmake-statements
+  `("-sinclude" "sinclude" "override" "vpath"
+    "ifdef" "ifndef" "ifeq" "ifneq" "-include" "define" "endef" "export"
+    "unexport"
+    ,@(cdr makefile-automake-statements))
+  "List of keywords understood by gmake.")
+
+;; These are even more silly, because you can have more spaces in between.
+(defconst makefile-makepp-statements
+  `("and ifdef" "and ifndef" "and ifeq" "and ifneq" "and ifperl"
+    "and ifmakeperl" "and ifsys" "and ifnsys" "build_cache" "build_check"
+    "else ifdef" "else ifndef" "else ifeq" "else ifneq" "else ifperl"
+    "else ifmakeperl" "else ifsys" "else ifnsys" "enddef" "load_makefile"
+    "ifperl" "ifmakeperl" "ifsys" "ifnsys" "_include"  "makeperl" "makesub"
+    "no_implicit_load" "perl" "perl-begin" "perl_begin" "perl-end" "perl_end"
+    "prebuild" "or ifdef" "or ifndef" "or ifeq" "or ifneq" "or ifperl"
+    "or ifmakeperl" "or ifsys" "or ifnsys" "register_command_parser"
+    "register_scanner" "repository" "runtime" "signature" "sub"
+    ,@(nthcdr 4 makefile-gmake-statements))
+  "List of keywords understood by gmake.")
+
+(defconst makefile-bsdmake-statements
+  `(".elif" ".elifdef" ".elifmake" ".elifndef" ".elifnmake" ".else" ".endfor"
+    ".endif" ".for" ".if" ".ifdef" ".ifmake" ".ifndef" ".ifnmake" ".undef")
+  "List of keywords understood by BSD make.")
+
+(defun makefile-make-font-lock-keywords (var keywords space
+                                            &optional negation
+                                            &rest font-lock-keywords)
+  `(;; Do macro assignments.  These get the "variable-name" face.
+    (,makefile-macroassign-regex
+     (1 font-lock-variable-name-face)
+     ;; This is for after !=
+     (2 'makefile-shell prepend t)
+     ;; This is for after normal assignment
+     (3 'font-lock-string-face prepend t))
+
+    ;; Rule actions.
+    (makefile-match-action
+     (1 font-lock-type-face)
+     (2 'makefile-shell prepend)
+     ;; Only makepp has builtin commands.
+     (3 font-lock-builtin-face prepend t))
+
+    ;; Variable references even in targets/strings/comments.
+    (,var 1 font-lock-variable-name-face prepend)
+
+    ;; Automatic variable references and single character variable references,
+    ;; but not shell variables references.
+    ("[^$]\\$\\([@%<?^+*_]\\|[a-zA-Z0-9]\\>\\)"
+     1 font-lock-constant-face prepend)
+    ("[^$]\\(\\$[@%*]\\)"
+     1 'makefile-targets append)
+
+    ;; Fontify conditionals and includes.
+    (,(concat "^\\(?: [ \t]*\\)?"
+             (regexp-opt keywords t)
+             "\\>[ \t]*\\([^: \t\n#]*\\)")
+     (1 font-lock-keyword-face) (2 font-lock-variable-name-face))
+
+    ,@(if negation
+         `((,negation (1 font-lock-negation-char-face prepend)
+                      (2 font-lock-negation-char-face prepend t))))
+
+    ,@(if space
+         '(;; Highlight lines that contain just whitespace.
+           ;; They can cause trouble, especially if they start with a tab.
+           ("^[ \t]+$" . makefile-space)
+
+           ;; Highlight shell comments that Make treats as commands,
+           ;; since these can fool people.
+           ("^\t+#" 0 makefile-space t)
+
+           ;; Highlight spaces that precede tabs.
+           ;; They can make a tab fail to be effective.
+           ("^\\( +\\)\t" 1 makefile-space)))
+
+    ,@font-lock-keywords
+
+    ;; Do dependencies.
+    (makefile-match-dependency
+     (1 'makefile-targets prepend)
+     (3 'makefile-shell prepend t))))
 
-   ;; Do dependencies.  These get the function name face.
-   (list makefile-dependency-regex 1 'font-lock-function-name-face)
-
-   ;; Variable references even in targets/strings/comments:
-   '("\\$[({]\\([-a-zA-Z0-9_.]+\\)[}):]" 1 font-lock-constant-face prepend)
-
-   ;; Automatic variable references.
-   '("\\$\\([@%<?^+*]\\)" 1 font-lock-reference-face prepend)
-
-   ;; Fontify conditionals and includes.
-   ;; Note that plain `if' is an automake conditional, and not a bug.
-   (list
-    (concat "^\\(?: [ \t]*\\)?"
-           (regexp-opt '("-include" "-sinclude" "include" "sinclude" "ifeq"
-                         "if" "ifneq" "ifdef" "ifndef" "endif" "else") t)
-           "\\>[ \t]*\\([^: \t\n#]*\\)")
-    '(1 font-lock-keyword-face) '(2 font-lock-variable-name-face))
-
-   ;; Highlight lines that contain just whitespace.
-   ;; They can cause trouble, especially if they start with a tab.
-   '("^[ \t]+$" . makefile-space-face)
-
-   ;; Highlight shell comments that Make treats as commands,
-   ;; since these can fool people.
-   '("^\t+#" 0 makefile-space-face t)
-
-   ;; Highlight spaces that precede tabs.
-   ;; They can make a tab fail to be effective.
-   '("^\\( +\\)\t" 1 makefile-space-face)))
+(defconst makefile-font-lock-keywords
+  (makefile-make-font-lock-keywords
+   makefile-var-use-regex
+   makefile-statements
+   t))
+
+(defconst makefile-automake-font-lock-keywords
+  (makefile-make-font-lock-keywords
+   makefile-var-use-regex
+   makefile-automake-statements
+   t))
+
+(defconst makefile-gmake-font-lock-keywords
+  (makefile-make-font-lock-keywords
+   makefile-var-use-regex
+   makefile-gmake-statements
+   t
+   "^\\(?: [ \t]*\\)?if\\(n\\)\\(?:def\\|eq\\)\\>"
+
+   '("[^$]\\(\\$[({][@%*][DF][})]\\)"
+     1 'makefile-targets append)
+
+   ;; $(function ...) ${function ...}
+   '("[^$]\\$[({]\\([-a-zA-Z0-9_.]+\\s \\)"
+     1 font-lock-function-name-face prepend)
+
+   ;; $(shell ...) ${shell ...}
+   '("[^$]\\$\\([({]\\)shell[ \t]+"
+     makefile-match-function-end nil nil
+     (1 'makefile-shell prepend t))))
+
+(defconst makefile-makepp-font-lock-keywords
+  (makefile-make-font-lock-keywords
+   makefile-var-use-regex
+   makefile-makepp-statements
+   nil
+   "^\\(?: [ \t]*\\)?\\(?:and[ \t]+\\|else[ \t]+\\|or[ \t]+\\)?if\\(n\\)\\(?:def\\|eq\\|sys\\)\\>"
+
+   '("[^$]\\(\\$[({]\\(?:output\\|stem\\|target\\)s?\\_>.*?[})]\\)"
+     1 'makefile-targets append)
+
+   ;; Colon modifier keywords.
+   '("\\(:\\s *\\)\\(build_c\\(?:ache\\|heck\\)\\|env\\(?:ironment\\)?\\|foreach\\|signature\\|scanner\\|quickscan\\|smartscan\\)\\>\\([^:\n]*\\)"
+     (1 font-lock-type-face t)
+     (2 font-lock-keyword-face t)
+     (3 font-lock-variable-name-face t))
+
+   ;; $(function ...) $((function ...)) ${function ...} ${{function ...}}
+   '("[^$]\\$\\(?:((?\\|{{?\\)\\([-a-zA-Z0-9_.]+\\s \\)"
+     1 font-lock-function-name-face prepend)
+
+   ;; $(shell ...) $((shell ...)) ${shell ...} ${{shell ...}}
+   '("[^$]\\$\\(((?\\|{{?\\)shell\\(?:[-_]\\(?:global[-_]\\)?once\\)?[ \t]+"
+     makefile-match-function-end nil nil
+     (1 'makefile-shell prepend t))
+
+   ;; $(perl ...) $((perl ...)) ${perl ...} ${{perl ...}}
+   '("[^$]\\$\\(((?\\|{{?\\)makeperl[ \t]+"
+     makefile-match-function-end nil nil
+     (1 'makefile-makepp-perl prepend t))
+   '("[^$]\\$\\(((?\\|{{?\\)perl[ \t]+"
+     makefile-match-function-end nil nil
+     (1 'makefile-makepp-perl t t))
+
+   ;; Can we unify these with (if (match-end 1) 'prepend t)?
+   '("ifmakeperl\\s +\\(.*\\)" 1 'makefile-makepp-perl prepend)
+   '("ifperl\\s +\\(.*\\)" 1 'makefile-makepp-perl t)
+
+   ;; Perl block single- or multiline, as statement or rule action.
+   ;; Don't know why the initial newline in 2nd variant of group 2 doesn't get skipped.
+   '("\\<make\\(?:perl\\|sub\\s +\\S +\\)\\s *\n?\\s *{\\(?:{\\s *\n?\\(\\(?:.*\n\\)+?\\)\\s *}\\|\\s *\\(\\(?:.*?\\|\n?\\(?:.*\n\\)+?\\)\\)\\)}"
+     (1 'makefile-makepp-perl prepend t)
+     (2 'makefile-makepp-perl prepend t))
+   '("\\<\\(?:perl\\|sub\\s +\\S +\\)\\s *\n?\\s *{\\(?:{\\s *\n?\\(\\(?:.*\n\\)+?\\)\\s *}\\|\\s *\\(\\(?:.*?\\|\n?\\(?:.*\n\\)+?\\)\\)\\)}"
+     (1 'makefile-makepp-perl t t)
+     (2 'makefile-makepp-perl t t))
+
+   ;; Statement style perl block.
+   '("perl[-_]begin\\s *\\(?:\\s #.*\\)?\n\\(\\(?:.*\n\\)+?\\)\\s *perl[-_]end\\>"
+     1 'makefile-makepp-perl t)))
+
+(defconst makefile-bsdmake-font-lock-keywords
+  (makefile-make-font-lock-keywords
+   ;; A lot more could be done for variables here:
+   makefile-var-use-regex
+   makefile-bsdmake-statements
+   t
+   "^\\(?: [ \t]*\\)?\\.\\(?:el\\)?if\\(n?\\)\\(?:def\\|make\\)?\\>[ \t]*\\(!?\\)"
+   '("^[ \t]*\\.for[ \t].+[ \t]\\(in\\)\\>" 1 font-lock-keyword-face)))
+
+
+(defconst makefile-font-lock-syntactic-keywords
+  ;; From sh-script.el.
+  ;; A `#' begins a comment in sh when it is unquoted and at the beginning
+  ;; of a word.  In the shell, words are separated by metacharacters.
+  ;; The list of special chars is taken from the single-unix spec of the
+  ;; shell command language (under `quoting') but with `$' removed.
+  '(("[^|&;<>()`\\\"' \t\n]\\(#+\\)" 1 "_")
+    ;; Change the syntax of a quoted newline so that it does not end a comment.
+    ("\\\\\n" 0 ".")))
 
 (defvar makefile-imenu-generic-expression
-  (list
-   (list "Dependencies" makefile-dependency-regex  1)
-   (list "Macro Assignment" makefile-macroassign-regex 1))
+  `(("Dependencies" makefile-previous-dependency 1)
+    ("Macro Assignment" ,makefile-macroassign-regex 1))
   "Imenu generic expression for Makefile mode.  See `imenu-generic-expression'.")
 
 ;;; ------------------------------------------------------------
@@ -359,47 +564,50 @@ The function must satisfy this calling convention:
     ()
   (define-abbrev-table 'makefile-mode-abbrev-table ()))
 
-(defvar makefile-mode-map nil
+(defvar makefile-mode-map
+  (let ((map (make-sparse-keymap)))
+    ;; set up the keymap
+    (define-key map "\C-c:" 'makefile-insert-target-ref)
+    (if makefile-electric-keys
+       (progn
+         (define-key map "$" 'makefile-insert-macro-ref)
+         (define-key map ":" 'makefile-electric-colon)
+         (define-key map "=" 'makefile-electric-equal)
+         (define-key map "." 'makefile-electric-dot)))
+    (define-key map "\C-c\C-f" 'makefile-pickup-filenames-as-targets)
+    (define-key map "\C-c\C-b" 'makefile-switch-to-browser)
+    (define-key map "\C-c\C-c" 'comment-region)
+    (define-key map "\C-c\C-p" 'makefile-pickup-everything)
+    (define-key map "\C-c\C-u" 'makefile-create-up-to-date-overview)
+    (define-key map "\C-c\C-i" 'makefile-insert-gmake-function)
+    (define-key map "\C-c\C-\\" 'makefile-backslash-region)
+    (define-key map "\C-c\C-m\C-a" 'makefile-automake-mode)
+    (define-key map "\C-c\C-m\C-b" 'makefile-bsdmake-mode)
+    (define-key map "\C-c\C-m\C-g" 'makefile-gmake-mode)
+    (define-key map "\C-c\C-m\C-m" 'makefile-mode)
+    (define-key map "\C-c\C-m\C-p" 'makefile-makepp-mode)
+    (define-key map "\M-p"     'makefile-previous-dependency)
+    (define-key map "\M-n"     'makefile-next-dependency)
+    (define-key map "\e\t"     'makefile-complete)
+
+    ;; Make menus.
+    (define-key map [menu-bar makefile-mode]
+      (cons "Makefile" (make-sparse-keymap "Makefile")))
+
+    (define-key map [menu-bar makefile-mode browse]
+      '("Pop up Makefile Browser" . makefile-switch-to-browser))
+    (define-key map [menu-bar makefile-mode complete]
+      '("Complete Target or Macro" . makefile-complete))
+    (define-key map [menu-bar makefile-mode pickup]
+      '("Find Targets and Macros" . makefile-pickup-everything))
+
+    (define-key map [menu-bar makefile-mode prev]
+      '("Move to Previous Dependency" . makefile-previous-dependency))
+    (define-key map [menu-bar makefile-mode next]
+      '("Move to Next Dependency" . makefile-next-dependency))
+    map)
   "The keymap that is used in Makefile mode.")
 
-(if makefile-mode-map
-    ()
-  (setq makefile-mode-map (make-sparse-keymap))
-  ;; set up the keymap
-  (define-key makefile-mode-map "\C-c:" 'makefile-insert-target-ref)
-  (if makefile-electric-keys
-      (progn
-       (define-key makefile-mode-map "$" 'makefile-insert-macro-ref)
-       (define-key makefile-mode-map ":" 'makefile-electric-colon)
-       (define-key makefile-mode-map "=" 'makefile-electric-equal)
-       (define-key makefile-mode-map "." 'makefile-electric-dot)))
-  (define-key makefile-mode-map "\C-c\C-f" 'makefile-pickup-filenames-as-targets)
-  (define-key makefile-mode-map "\C-c\C-b" 'makefile-switch-to-browser)
-  (define-key makefile-mode-map "\C-c\C-c" 'comment-region)
-  (define-key makefile-mode-map "\C-c\C-p" 'makefile-pickup-everything)
-  (define-key makefile-mode-map "\C-c\C-u" 'makefile-create-up-to-date-overview)
-  (define-key makefile-mode-map "\C-c\C-i" 'makefile-insert-gmake-function)
-  (define-key makefile-mode-map "\C-c\C-\\" 'makefile-backslash-region)
-  (define-key makefile-mode-map "\M-p"     'makefile-previous-dependency)
-  (define-key makefile-mode-map "\M-n"     'makefile-next-dependency)
-  (define-key makefile-mode-map "\e\t"     'makefile-complete)
-
-  ;; Make menus.
-  (define-key makefile-mode-map [menu-bar makefile-mode]
-    (cons "Makefile" (make-sparse-keymap "Makefile")))
-
-  (define-key makefile-mode-map [menu-bar makefile-mode browse]
-    '("Pop up Makefile Browser" . makefile-switch-to-browser))
-  (define-key makefile-mode-map [menu-bar makefile-mode complete]
-    '("Complete Target or Macro" . makefile-complete))
-  (define-key makefile-mode-map [menu-bar makefile-mode pickup]
-    '("Find Targets and Macros" . makefile-pickup-everything))
-
-  (define-key makefile-mode-map [menu-bar makefile-mode prev]
-    '("Move to Previous Dependency" . makefile-previous-dependency))
-  (define-key makefile-mode-map [menu-bar makefile-mode next]
-    '("Move to Next Dependency" . makefile-next-dependency)))
-
 (defvar makefile-browser-map nil
   "The keymap that is used in the macro- and target browser.")
 (if makefile-browser-map
@@ -492,8 +700,19 @@ The function must satisfy this calling convention:
 
 ;;;###autoload
 (defun makefile-mode ()
-  "Major mode for editing Makefiles.
-This function ends by invoking the function(s) `makefile-mode-hook'.
+  "Major mode for editing standard Makefiles.
+
+If you are editing a file for a different make, try one of the
+variants `makefile-automake-mode', `makefile-gmake-mode',
+`makefile-makepp-mode' or `makefile-bsdmake-mode'.  All but the
+last should be correctly chosen based on the file name, except if
+it is *.mk.  This function ends by invoking the function(s)
+`makefile-mode-hook'.
+
+It is strongly recommended to use `font-lock-mode', because that
+provides additional parsing information.  This is used for
+example to see that a rule action `echo foo: bar' is a not rule
+dependency, despite the colon.
 
 \\{makefile-mode-map}
 
@@ -547,7 +766,7 @@ Makefile mode can be configured by modifying the following variables:
    (i.e. it calls `makefile-pickup-filenames-as-targets'), otherwise
    filenames are omitted.
 
-`makefile-cleanup-continuations-p':
+`makefile-cleanup-continuations':
    If this variable is set to a non-nil value then Makefile mode
    will assure that no line in the file ends with a backslash
    (the continuation character) followed by any whitespace.
@@ -567,9 +786,12 @@ Makefile mode can be configured by modifying the following variables:
 
   (interactive)
   (kill-all-local-variables)
-  (make-local-variable 'local-write-file-hooks)
-  (setq local-write-file-hooks
-       '(makefile-cleanup-continuations makefile-warn-suspicious-lines))
+  (add-hook 'write-file-functions
+           'makefile-warn-suspicious-lines nil t)
+  (add-hook 'write-file-functions
+           'makefile-warn-continuations nil t)
+  (add-hook 'write-file-functions
+           'makefile-cleanup-continuations nil t)
   (make-local-variable 'makefile-target-table)
   (make-local-variable 'makefile-macro-table)
   (make-local-variable 'makefile-has-prereqs)
@@ -582,7 +804,12 @@ Makefile mode can be configured by modifying the following variables:
        ;; SYNTAX-BEGIN set to backward-paragraph to avoid slow-down
        ;; near the end of a large buffer, due to parse-partial-sexp's
        ;; trying to parse all the way till the beginning of buffer.
-       '(makefile-font-lock-keywords nil nil nil backward-paragraph))
+       '(makefile-font-lock-keywords
+         nil nil
+         ((?$ . "."))
+         backward-paragraph
+         (font-lock-syntactic-keywords . makefile-font-lock-syntactic-keywords)
+         (font-lock-support-mode)))    ; JIT breaks on long series of continuation lines.
 
   ;; Add-log.
   (make-local-variable 'add-log-current-defun-function)
@@ -624,7 +851,42 @@ Makefile mode can be configured by modifying the following variables:
 
   ;; Real TABs are important in makefiles
   (setq indent-tabs-mode t)
-  (run-hooks 'makefile-mode-hook))
+  (run-mode-hooks 'makefile-mode-hook))
+
+;; These should do more than just differentiate font-lock.
+;;;###autoload
+(define-derived-mode makefile-automake-mode makefile-mode "Makefile.am"
+  "An adapted `makefile-mode' that knows about automake."
+  (setq font-lock-defaults
+       `(makefile-automake-font-lock-keywords ,@(cdr font-lock-defaults))))
+
+;;;###autoload
+(define-derived-mode makefile-gmake-mode makefile-mode "GNUmakefile"
+  "An adapted `makefile-mode' that knows about gmake."
+  (setq font-lock-defaults
+       `(makefile-gmake-font-lock-keywords ,@(cdr font-lock-defaults))))
+
+;;;###autoload
+(define-derived-mode makefile-makepp-mode makefile-mode "Makeppfile"
+  "An adapted `makefile-mode' that knows about makepp."
+  (set (make-local-variable 'makefile-rule-action-regex)
+       makefile-makepp-rule-action-regex)
+  (setq font-lock-defaults
+       `(makefile-makepp-font-lock-keywords ,@(cdr font-lock-defaults))
+       imenu-generic-expression
+       `(("Functions" "^[ \t]*\\(?:make\\)?sub[ \t]+\\([A-Za-z0-9_]+\\)" 1)
+         ,@imenu-generic-expression)))
+
+;;;###autoload
+(define-derived-mode makefile-bsdmake-mode makefile-mode "BSDmakefile"
+  "An adapted `makefile-mode' that knows about BSD make."
+  (set (make-local-variable 'makefile-dependency-regex)
+       makefile-bsdmake-dependency-regex)
+  (set (make-local-variable 'makefile-dependency-skip) "^:!")
+  (set (make-local-variable 'makefile-rule-action-regex)
+       makefile-bsdmake-rule-action-regex)
+  (setq font-lock-defaults
+       `(makefile-bsdmake-font-lock-keywords ,@(cdr font-lock-defaults))))
 
 \f
 
@@ -635,18 +897,29 @@ Makefile mode can be configured by modifying the following variables:
   (interactive)
   (let ((here (point)))
     (end-of-line)
-    (if (re-search-forward makefile-dependency-regex (point-max) t)
+    (if (makefile-match-dependency nil)
        (progn (beginning-of-line) t)   ; indicate success
       (goto-char here) nil)))
 
 (defun makefile-previous-dependency ()
   "Move point to the beginning of the previous dependency line."
   (interactive)
-  (let ((here (point)))
+  (let ((pt (point)))
     (beginning-of-line)
-    (if (re-search-backward makefile-dependency-regex (point-min) t)
-       (progn (beginning-of-line) t)   ; indicate success
-      (goto-char here) nil)))
+    ;; makefile-match-dependency done backwards:
+    (catch 'found
+      (while (progn (skip-chars-backward makefile-dependency-skip)
+                   (not (bobp)))
+       (or (prog1 (eq (char-after) ?=)
+             (backward-char))
+           (get-text-property (point) 'face)
+           (beginning-of-line)
+           (if (> (point) (+ (point-min) 2))
+               (eq (char-before (1- (point))) ?\\))
+           (if (looking-at makefile-dependency-regex)
+               (throw 'found t))))
+      (goto-char pt)
+      nil)))
 
 \f
 
@@ -744,74 +1017,56 @@ Anywhere else just self-inserts."
 (defun makefile-pickup-targets ()
   "Notice names of all target definitions in Makefile."
   (interactive)
-  (if (not makefile-need-target-pickup)
-      nil
-    (setq makefile-need-target-pickup nil)
-    (setq makefile-target-table nil)
-    (setq makefile-has-prereqs nil)
+  (when makefile-need-target-pickup
+    (setq makefile-need-target-pickup nil
+         makefile-target-table nil
+         makefile-has-prereqs nil)
     (save-excursion
       (goto-char (point-min))
-      (while (re-search-forward makefile-dependency-regex (point-max) t)
-       (makefile-add-this-line-targets)))
-    (message "Read targets OK.")))
-
-(defun makefile-add-this-line-targets ()
-  (save-excursion
-    (beginning-of-line)
-    (let ((done-with-line nil)
-         (line-number (1+ (count-lines (point-min) (point)))))
-      (while (not done-with-line)
-       (skip-chars-forward " \t")
-       (if (not (setq done-with-line (or (eolp)
-                                         (char-equal (char-after (point)) ?:))))
-           (progn
-             (let* ((start-of-target-name (point))
-                    (target-name
-                     (progn
-                       (skip-chars-forward "^ \t:#")
-                       (buffer-substring start-of-target-name (point))))
+      (while (makefile-match-dependency nil)
+       (goto-char (match-beginning 1))
+       (while (let ((target-name
+                     (buffer-substring-no-properties (point)
+                                                     (progn
+                                                       (skip-chars-forward "^ \t:#")
+                                                       (point))))
                     (has-prereqs
                      (not (looking-at ":[ \t]*$"))))
-               (if (makefile-remember-target target-name has-prereqs)
-                   (message "Picked up target \"%s\" from line %d"
-                            target-name line-number)))))))))
+                (if (makefile-remember-target target-name has-prereqs)
+                    (message "Picked up target \"%s\" from line %d"
+                             target-name (line-number-at-pos)))
+                (skip-chars-forward " \t")
+                (not (or (eolp) (eq (char-after) ?:)))))
+       (forward-line)))
+    (message "Read targets OK.")))
 
 (defun makefile-pickup-macros ()
   "Notice names of all macro definitions in Makefile."
   (interactive)
-  (if (not makefile-need-macro-pickup)
-      nil
-    (setq makefile-need-macro-pickup nil)
-    (setq makefile-macro-table nil)
+  (when makefile-need-macro-pickup
+    (setq makefile-need-macro-pickup nil
+         makefile-macro-table nil)
     (save-excursion
       (goto-char (point-min))
-      (while (re-search-forward makefile-macroassign-regex (point-max) t)
-       (makefile-add-this-line-macro)
-       (forward-line 1)))
-    (message "Read macros OK.")))
-
-(defun makefile-add-this-line-macro ()
-  (save-excursion
-    (beginning-of-line)
-    (skip-chars-forward " \t")
-    (if (not (eolp))
-       (let* ((start-of-macro-name (point))
-              (line-number (1+ (count-lines (point-min) (point))))
-              (macro-name (progn
-                            (skip-chars-forward "^ \t:#=*")
-                            (buffer-substring start-of-macro-name (point)))))
+      (while (re-search-forward makefile-macroassign-regex nil t)
+       (goto-char (match-beginning 1))
+       (let ((macro-name (buffer-substring-no-properties (point)
+                                                         (progn
+                                                           (skip-chars-forward "^ \t:#=*")
+                                                           (point)))))
          (if (makefile-remember-macro macro-name)
              (message "Picked up macro \"%s\" from line %d"
-                      macro-name line-number))))))
+                      macro-name (line-number-at-pos))))
+       (forward-line)))
+    (message "Read macros OK.")))
 
 (defun makefile-pickup-everything (arg)
   "Notice names of all macros and targets in Makefile.
 Prefix arg means force pickups to be redone."
   (interactive "P")
   (if arg
-      (progn
-       (setq makefile-need-target-pickup t)
-       (setq makefile-need-macro-pickup t)))
+      (setq makefile-need-target-pickup t
+           makefile-need-macro-pickup t))
   (makefile-pickup-macros)
   (makefile-pickup-targets)
   (if makefile-pickup-everything-picks-up-filenames-p
@@ -822,17 +1077,12 @@ Prefix arg means force pickups to be redone."
 Checks each filename against `makefile-ignored-files-in-pickup-regex'
 and adds all qualifying names to the list of known targets."
   (interactive)
-  (let* ((dir (file-name-directory (buffer-file-name)))
-        (raw-filename-list (if dir
-                               (file-name-all-completions "" dir)
-                             (file-name-all-completions "" ""))))
-    (mapcar (lambda (name)
-              (if (and (not (file-directory-p name))
-                       (not (string-match makefile-ignored-files-in-pickup-regex
-                                          name)))
-                  (if (makefile-remember-target name)
-                      (message "Picked up file \"%s\" as target" name))))
-           raw-filename-list)))
+  (mapc (lambda (name)
+         (or (file-directory-p name)
+             (string-match makefile-ignored-files-in-pickup-regex name)
+             (if (makefile-remember-target name)
+                 (message "Picked up file \"%s\" as target" name))))
+       (file-name-all-completions "" (or (file-name-directory (buffer-file-name)) ""))))
 
 \f
 
@@ -1012,23 +1262,35 @@ definition and conveniently use this command."
   (save-excursion
     (beginning-of-line)
     (cond
-     ((looking-at "^#+ ")
-      ;; Found a comment.  Set the fill prefix and then fill.
-      (let ((fill-prefix (buffer-substring-no-properties (match-beginning 0)
-                                                        (match-end 0)))
+     ((looking-at "^#+")
+      ;; Found a comment.  Set the fill prefix, and find the paragraph
+      ;; boundaries by searching for lines that look like comment-only
+      ;; lines.
+      (let ((fill-prefix (match-string-no-properties 0))
            (fill-paragraph-function nil))
-       (fill-paragraph nil)
-       t))
+       (save-excursion
+         (save-restriction
+           (narrow-to-region
+            ;; Search backwards.
+            (save-excursion
+              (while (and (zerop (forward-line -1))
+                          (looking-at "^#")))
+              ;; We may have gone too far.  Go forward again.
+              (or (looking-at "^#")
+                  (forward-line 1))
+              (point))
+            ;; Search forwards.
+            (save-excursion
+              (while (looking-at "^#")
+                (forward-line))
+              (point)))
+           (fill-paragraph nil)
+           t))))
 
      ;; Must look for backslashed-region before looking for variable
      ;; assignment.
-     ((save-excursion
-       (end-of-line)
-       (or
-        (= (preceding-char) ?\\)
-        (progn
-          (end-of-line -1)
-          (= (preceding-char) ?\\))))
+     ((or (eq (char-before (line-end-position 1)) ?\\)
+         (eq (char-before (line-end-position 0)) ?\\))
       ;; A backslash region.  Find beginning and end, remove
       ;; backslashes, fill, and then reapply backslahes.
       (end-of-line)
@@ -1342,7 +1604,7 @@ and generates the overview, one line per target name."
   (delete-file filename))              ; remove the tmpfile
 
 (defun makefile-query-by-make-minus-q (target &optional filename)
-  (not (zerop
+  (not (eq 0
        (call-process makefile-brave-make nil nil nil
                      "-f" filename "-q" target))))
 
@@ -1354,11 +1616,11 @@ and generates the overview, one line per target name."
 
 (defun makefile-cleanup-continuations ()
   (if (eq major-mode 'makefile-mode)
-      (if (and makefile-cleanup-continuations-p
+      (if (and makefile-cleanup-continuations
               (not buffer-read-only))
          (save-excursion
            (goto-char (point-min))
-           (while (re-search-forward "\\\\[ \t]+$" (point-max) t)
+           (while (re-search-forward "\\\\[ \t]+$" nil t)
              (replace-match "\\" t t))))))
 
 
@@ -1373,9 +1635,17 @@ and generates the overview, one line per target name."
        (goto-char (point-min))
        (if (re-search-forward "^\\(\t+$\\| +\t\\)" nil t)
            (not (y-or-n-p
-                 (format "Suspicious line %d. Save anyway "
+                 (format "Suspicious line %d. Save anyway? "
+                         (count-lines (point-min) (point)))))))))
+
+(defun makefile-warn-continuations ()
+  (if (eq major-mode 'makefile-mode)
+      (save-excursion
+       (goto-char (point-min))
+       (if (re-search-forward "\\\\[ \t]+$" nil t)
+           (not (y-or-n-p
+                 (format "Suspicious continuation in line %d. Save anyway? "
                          (count-lines (point-min) (point)))))))))
-         
 \f
 
 ;;; ------------------------------------------------------------
@@ -1414,6 +1684,56 @@ Then prompts for all required parameters."
 ;;; Utility functions
 ;;; ------------------------------------------------------------
 
+(defun makefile-match-function-end (end)
+  "To be called as an anchored matcher by font-lock.
+The anchor must have matched the opening parens in the first group."
+  (let ((s (match-string-no-properties 1)))
+    (setq s (cond ((string= s "(") "\\(.*?\\)[ \t]*)")
+                 ((string= s "{") "\\(.*?\\)[ \t]*}")
+                 ((string= s "((") "\\(.*?\\)[ \t]*))")
+                 ((string= s "{{") "\\(.*?\\)[ \t]*}}")))
+    (if s (looking-at s))))
+
+(defun makefile-match-dependency (bound)
+  "Search for `makefile-dependency-regex' up to BOUND.
+Checks that the colon has not already been fontified, else we
+matched in a rule action."
+  (catch 'found
+    (let ((pt (point)))
+      (while (progn (skip-chars-forward makefile-dependency-skip bound)
+                   (< (point) (or bound (point-max))))
+       (forward-char)
+       (or (eq (char-after) ?=)
+           (get-text-property (1- (point)) 'face)
+           (if (> (line-beginning-position) (+ (point-min) 2))
+               (eq (char-before (line-end-position 0)) ?\\))
+           (when (save-excursion
+                   (beginning-of-line)
+                   (looking-at makefile-dependency-regex))
+             (save-excursion
+               (let ((deps-end (match-end 1))
+                     (match-data (match-data)))
+                 (goto-char deps-end)
+                 (skip-chars-backward " \t")
+                 (setq deps-end (point))
+                 (beginning-of-line)
+                 (skip-chars-forward " \t")
+                 ;; Alter the bounds recorded for subexp 1,
+                 ;; which is what is supposed to match the targets.
+                 (setcar (nthcdr 2 match-data) (point))
+                 (setcar (nthcdr 3 match-data) deps-end)
+                 (store-match-data match-data)))
+             (end-of-line)
+             (throw 'found (point)))))
+      (goto-char pt))
+    nil))
+
+(defun makefile-match-action (bound)
+  (catch 'found
+    (while (re-search-forward makefile-rule-action-regex bound t)
+      (or (eq ?\\ (char-after (- (match-beginning 0) 2)))
+         (throw 'found t)))))
+
 (defun makefile-do-macro-insertion (macro-name)
   "Insert a macro reference."
   (if (not (zerop (length macro-name)))
@@ -1506,21 +1826,16 @@ If it isn't in one, return nil."
       ;; Scan back line by line, noticing when we come to a
       ;; variable or rule definition, and giving up when we see
       ;; a line that is not part of either of those.
-      (while (not found)
-       (cond
-        ((looking-at makefile-macroassign-regex)
-         (setq found (buffer-substring-no-properties (match-beginning 1)
-                                                       (match-end 1))))
-        ((looking-at makefile-dependency-regex)
-         (setq found (buffer-substring-no-properties (match-beginning 1)
-                                                     (match-end 1))))
-        ;; Don't keep looking across a blank line or comment.  Give up.
-        ((looking-at "$\\|#")
-         (setq found 'bobp))
-        ((bobp)
-         (setq found 'bobp)))
-       (or found
-           (forward-line -1)))
-      (if (stringp found) found))))
+      (while (not (or (setq found
+                           (when (or (looking-at makefile-macroassign-regex)
+                                     (looking-at makefile-dependency-regex))
+                             (match-string-no-properties 1)))
+                     ;; Don't keep looking across a blank line or comment.
+                     (looking-at "$\\|#")
+                     (not (zerop (forward-line -1))))))
+      found)))
+
+(provide 'make-mode)
 
+;;; arch-tag: bd23545a-de91-44fb-b1b2-feafbb2635a0
 ;;; make-mode.el ends here