X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/5c2946c73b11d9eedc7527d7353b55a7b8988ca7..8a9cad92b1c9eecbdc9268a885413b7c5d08c7fa:/lisp/progmodes/make-mode.el diff --git a/lisp/progmodes/make-mode.el b/lisp/progmodes/make-mode.el index 928f8243b3..c887b14496 100644 --- a/lisp/progmodes/make-mode.el +++ b/lisp/progmodes/make-mode.el @@ -1,18 +1,18 @@ -;;; makefile.el --- makefile editing commands for Emacs +;;; make-mode.el --- makefile editing commands for Emacs -;; Copyright (C) 1992 Free Software Foundation, Inc. +;; Copyright (C) 1992,94,99,2000,2001, 2002, 2003 Free Software Foundation, Inc. ;; Author: Thomas Neumann +;; Eric S. Raymond +;; Maintainer: FSF ;; Adapted-By: ESR ;; Keywords: unix, tools -;; $Id: makefile.el,v 1.5 1993/03/28 06:24:54 eric Exp rms $ - ;; 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 1, or (at your option) +;; the Free Software Foundation; either version 2, or (at your option) ;; any later version. ;; GNU Emacs is distributed in the hope that it will be useful, @@ -21,100 +21,193 @@ ;; GNU General Public License for more details. ;; 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, 675 Mass Ave, Cambridge, MA 02139, USA. +;; 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. ;;; Commentary: -;; A major mode for editing Makefiles. Hairy. Needs more documentation. -;; If you get familiar with it, please write some and send it to the GNU -;; Emacs maintainers. +;; A major mode for editing makefiles. The mode knows about Makefile +;; syntax and defines M-n and M-p to move to next and previous productions. +;; +;; The keys $, =, : and . are electric; they try to help you fill in a +;; macro reference, macro definition, ordinary target name, or special +;; target name, respectively. Such names are completed using a list of +;; targets and macro names parsed out of the makefile. This list is +;; automatically updated, if necessary, whenever you invoke one of +;; these commands. You can force it to be updated with C-c C-p. +;; +;; The command C-c C-f adds certain filenames in the current directory +;; as targets. You can filter out filenames by setting the variable +;; makefile-ignored-files-in-pickup-regex. +;; +;; The command C-c C-u grinds for a bit, then pops up a report buffer +;; showing which target names are up-to-date with respect to their +;; prerequisites, which targets are out-of-date, and which have no +;; 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 +;; 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. +;; You will be prompted for the builtin's args. +;; +;; There are numerous other customization variables. + +;; +;; 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 +;; interact with font-lock. +;; * Would be nice to edit the commands in ksh-mode and have +;; indentation and slashification done automatically. Hard. +;; * Consider removing browser mode. It seems useless. +;; * ":" should notice when a new target is made and add it to the +;; list (or at least set makefile-need-target-pickup). +;; * Make browser into a major mode. +;; * Clean up macro insertion stuff. It is a mess. +;; * Browser entry and exit is weird. Normalize. +;; * Browser needs to be rewritten. Right now it is kind of a crock. +;; Should at least: +;; * Act more like dired/buffer menu/whatever. +;; * Highlight as mouse traverses. +;; * B2 inserts. +;; * Update documentation above. +;; * Update texinfo manual. +;; * Update files.el. + + ;;; Code: -(provide 'makefile) +;; Sadly we need this for a macro. +(eval-when-compile + (require 'imenu) + (require 'dabbrev) + (require 'add-log)) ;;; ------------------------------------------------------------ -;;; Configureable stuff +;;; Configurable stuff ;;; ------------------------------------------------------------ -(defvar makefile-mode-name "makefile" - "The \"pretty name\" of makefile-mode, as it -appears in the modeline.") - -(defvar makefile-browser-buffer-name "*Macros and Targets*" - "Name of the macro- and target browser buffer.") - -(defvar makefile-target-colon ":" - "The string that gets appended to all target names -inserted by makefile-insert-target. -\":\" or \"::\" are quite common values.") - -(defvar makefile-macro-assign " = " - "The string that gets appended to all macro names -inserted by makefile-insert-macro. +(defgroup makefile nil + "Makefile editing commands for Emacs." + :group 'tools + :prefix "makefile-") + +(defface makefile-space-face + '((((class color)) (:background "hotpink")) + (t (:reverse-video t))) + "Face to use for highlighting leading spaces in Font-Lock mode." + :group 'faces + :group 'makefile) + +(defcustom makefile-browser-buffer-name "*Macros and Targets*" + "*Name of the macro- and target browser buffer." + :type 'string + :group 'makefile) + +(defcustom makefile-target-colon ":" + "*String to append to all target names inserted by `makefile-insert-target'. +\":\" or \"::\" are common values." + :type 'string + :group 'makefile) + +(defcustom makefile-macro-assign " = " + "*String to append to all macro names inserted by `makefile-insert-macro'. The normal value should be \" = \", since this is what -standard make expects. However, newer makes such as dmake +standard make expects. However, newer makes such as dmake allow a larger variety of different macro assignments, so you -might prefer to use \" += \" or \" := \" .") - -(defvar makefile-use-curly-braces-for-macros-p nil - "Set this variable to a non-nil value if you prefer curly braces -in macro-references, so it looks like ${this}. A value of nil -will cause makefile-mode to use parantheses, making macro references -look like $(this) .") - -(defvar makefile-tab-after-target-colon t - "If you want a TAB (instead of a space) to be appended after the -target colon, then set this to a non-nil value.") - -(defvar makefile-browser-leftmost-column 10 - "Number of blanks to the left of the browser selection mark.") - -(defvar makefile-browser-cursor-column 10 - "Column in which the cursor is positioned when it moves -up or down in the browser.") - -(defvar makefile-browser-selected-mark "+ " - "String used to mark selected entries in the browser.") - -(defvar makefile-browser-unselected-mark " " - "String used to mark unselected entries in the browser.") - -(defvar makefile-browser-auto-advance-after-selection-p t - "If this variable is set to a non-nil value the cursor -will automagically advance to the next line after an item -has been selected in the browser.") - -(defvar makefile-find-file-autopickup-p t - "If this variable is set to a non-nil value then finding a file in -a makefile-mode buffer will cause an automatic initial pickup of -all macros and targets from the found file.") - -(defvar makefile-pickup-everything-picks-up-filenames-p nil - "If this variable is set to a non-nil value then -makefile-pickup-everything also picks up filenames as targets -(i.e. it calls makefile-find-filenames-as-targets), otherwise -filenames are omitted.") - -(defvar makefile-cleanup-continuations-p t - "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. -This is done by silently removing the trailing whitespace, leaving -the backslash itself intact. -IMPORTANT: Please note that enabling this option causes makefile-mode -to MODIFY A FILE WITHOUT YOUR CONFIRMATION when \'it seems necessary\'.") - -(defvar makefile-browser-hook '() - "A function or list of functions to be called just before the -browser is entered. This is executed in the makefile buffer, so -you can for example run a makefile-pickup-everything automatically.") +might prefer to use \" += \" or \" := \" ." + :type 'string + :group 'makefile) + +(defcustom makefile-electric-keys nil + "*If non-nil, Makefile mode should install electric keybindings. +Default is nil." + :type 'boolean + :group 'makefile) + +(defcustom makefile-use-curly-braces-for-macros-p nil + "*Controls the style of generated macro references. +Non-nil means macro references should use curly braces, like `${this}'. +nil means use parentheses, like `$(this)'." + :type 'boolean + :group 'makefile) + +(defcustom makefile-tab-after-target-colon t + "*If non-nil, insert a TAB after a target colon. +Otherwise, a space is inserted. +The default is t." + :type 'boolean + :group 'makefile) + +(defcustom makefile-browser-leftmost-column 10 + "*Number of blanks to the left of the browser selection mark." + :type 'integer + :group 'makefile) + +(defcustom makefile-browser-cursor-column 10 + "*Column the cursor goes to when it moves up or down in the Makefile browser." + :type 'integer + :group 'makefile) + +(defcustom makefile-backslash-column 48 + "*Column in which `makefile-backslash-region' inserts backslashes." + :type 'integer + :group 'makefile) + +(defcustom makefile-backslash-align t + "*If non-nil, `makefile-backslash-region' will align backslashes." + :type 'boolean + :group 'makefile) + +(defcustom makefile-browser-selected-mark "+ " + "*String used to mark selected entries in the Makefile browser." + :type 'string + :group 'makefile) + +(defcustom makefile-browser-unselected-mark " " + "*String used to mark unselected entries in the Makefile browser." + :type 'string + :group 'makefile) + +(defcustom makefile-browser-auto-advance-after-selection-p t + "*If non-nil, cursor will move after item is selected in Makefile browser." + :type 'boolean + :group 'makefile) + +(defcustom makefile-pickup-everything-picks-up-filenames-p nil + "*If non-nil, `makefile-pickup-everything' picks up filenames as targets. +This means it calls `makefile-pickup-filenames-as-targets'. +Otherwise filenames are omitted." + :type 'boolean + :group 'makefile) + +(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. +IMPORTANT: Please note that enabling this option causes Makefile mode +to MODIFY A FILE WITHOUT YOUR CONFIRMATION when \"it seems necessary\"." + :type 'boolean + :group 'makefile) + +(defcustom makefile-mode-hook nil + "*Normal hook run by `makefile-mode'." + :type 'hook + :group 'makefile) + +(defvar makefile-browser-hook '()) ;; ;; Special targets for DMake, Sun's make ... -;; -(defvar makefile-special-targets-list +;; +(defcustom makefile-special-targets-list '(("DEFAULT") ("DONE") ("ERROR") ("EXPORT") ("FAILED") ("GROUPEPILOG") ("GROUPPROLOG") ("IGNORE") ("IMPORT") ("INCLUDE") ("INCLUDEDIRS") ("INIT") @@ -123,33 +216,106 @@ you can for example run a makefile-pickup-everything automatically.") ("SCCS_GET") ("SILENT") ("SOURCE") ("SUFFIXES") ("WAIT") ("c.o") ("C.o") ("m.o") ("el.elc") ("y.c") ("s.o")) - "List of special targets. You will be offered to complete -on one of those in the minibuffer whenever you enter a \".\" -at the beginning of a line in makefile-mode.") - -(defvar makefile-runtime-macros-list - '(("@") ("&") (">") ("<") ("*") ("^") ("?") ("%")) - "List of macros that are resolved by make at runtime. -If you insert a macro reference using makefile-insert-macro-ref, the name -of the macro is checked against this list. If it can be found its name will -not be enclosed in { } or ( ).") - + "*List of special targets. +You will be offered to complete on one of those in the minibuffer whenever +you enter a \".\" at the beginning of a line in `makefile-mode'." + :type '(repeat (list string)) + :group 'makefile) + +(defcustom makefile-runtime-macros-list + '(("@") ("&") (">") ("<") ("*") ("^") ("+") ("?") ("%") ("$")) + "*List of macros that are resolved by make at runtime. +If you insert a macro reference using `makefile-insert-macro-ref', the name +of the macro is checked against this list. If it can be found its name will +not be enclosed in { } or ( )." + :type '(repeat (list string)) + :group 'makefile) + +;; 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 - "^[^ \t#:]+\\([ \t]+[^ \t#:]+\\)*[ \t]*:\\($\\|\\([^=].*$\\)\\)" + "^ *\\([^ \n\t#:=]+\\([ \t]+\\([^ \t\n#:=]+\\|\\$[({][^ \t\n#})]+[})]\\)\\)*\\)[ \t]*:\\([ \t]*$\\|\\([^=\n].*$\\)\\)" "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-macroassign-regex - "^[^ \t][^:#=]*[\\*:\\+]?:?=.*$" + "^ *\\([^ \n\t][^:#= \t\n]*\\)[ \t]*[*:+]?[:?]?=" "Regex used to find macro assignment lines in a makefile.") (defconst makefile-ignored-files-in-pickup-regex - "\\(^\\..*\\)\\|\\(.*~$\\)\\|\\(.*,v$\\)" + "\\(^\\..*\\)\\|\\(.*~$\\)\\|\\(.*,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 + "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) + + ;; 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) + + ;; 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" + "define" "endef" "override" + "export" "unexport" "vpath") 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-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)) + "Imenu generic expression for Makefile mode. See `imenu-generic-expression'.") + ;;; ------------------------------------------------------------ ;;; The following configurable variables are used in the ;;; up-to-date overview . -;;; The standard configuration assumes that your `make' programm +;;; The standard configuration assumes that your `make' program ;;; can be run in question/query mode using the `-q' option, this ;;; means that the command ;;; @@ -158,69 +324,90 @@ not be enclosed in { } or ( ).") ;;; should return an exit status of zero if the target `foo' is ;;; up to date and a nonzero exit status otherwise. ;;; Many makes can do this although the docs/manpages do not mention -;;; it. Try it with your favourite one. GNU make and Dennis Vaduras -;;; DMake have no problems. +;;; it. Try it with your favourite one. GNU make, System V make, and +;;; Dennis Vadura's DMake have no problems. ;;; Set the variable `makefile-brave-make' to the name of the ;;; make utility that does this on your system. -;;; To understand what this is all about see the function defintion +;;; To understand what this is all about see the function definition ;;; of `makefile-query-by-make-minus-q' . ;;; ------------------------------------------------------------ -(defvar makefile-brave-make "gmake" - "A make that can handle the \'-q\' option.") +(defcustom makefile-brave-make "make" + "*How to invoke make, for `makefile-query-targets'. +This should identify a `make' command that can handle the `-q' option." + :type 'string + :group 'makefile) -(defvar makefile-query-one-target-method 'makefile-query-by-make-minus-q - "A function symbol [one that can be used as the first argument to -funcall] that provides a function that must conform to the following -interface: +(defcustom makefile-query-one-target-method 'makefile-query-by-make-minus-q + "*Function to call to determine whether a make target is up to date. +The function must satisfy this calling convention: * As its first argument, it must accept the name of the target to be checked, as a string. * As its second argument, it may accept the name of a makefile - as a string. Depending on what you\'re going to do you may + as a string. Depending on what you're going to do you may not need this. * It must return the integer value 0 (zero) if the given target should be considered up-to-date in the context of the given - makefile, any nonzero integer value otherwise.") + makefile, any nonzero integer value otherwise." + :type 'function + :group 'makefile) -(defvar makefile-up-to-date-buffer-name "*Makefile Up-to-date overview*" - "Name of the Up-to-date overview buffer.") - -(defvar makefile-target-needs-rebuild-mark " .. NEEDS REBUILD" - "A string that is appended to the target name in the up-to-date -overview if that target is considered to require a rebuild.") - -(defvar makefile-target-up-to-date-mark " .. is up to date" - "A string that is appenden to the target name in the up-to-date -overview if that target is considered up-to-date.") +(defcustom makefile-up-to-date-buffer-name "*Makefile Up-to-date overview*" + "*Name of the Up-to-date overview buffer." + :type 'string + :group 'makefile) ;;; --- end of up-to-date-overview configuration ------------------ +(defvar makefile-mode-abbrev-table nil + "Abbrev table in use in Makefile buffers.") +(if makefile-mode-abbrev-table + () + (define-abbrev-table 'makefile-mode-abbrev-table ())) (defvar makefile-mode-map nil - "The keymap that is used in makefile-mode.") + "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 "$" 'makefile-insert-macro-ref) - (define-key makefile-mode-map "\C-c:" 'makefile-insert-target-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-t" 'makefile-pickup-targets) - (define-key makefile-mode-map "\C-c\C-m" 'makefile-pickup-macros) + (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-0" 'makefile-forget-everything) - (define-key makefile-mode-map "\C-c0" 'makefile-forget-everything) (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 "\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.") @@ -228,58 +415,59 @@ overview if that target is considered up-to-date.") () (setq makefile-browser-map (make-sparse-keymap)) (define-key makefile-browser-map "n" 'makefile-browser-next-line) - (define-key makefile-browser-map "\C-n" 'makefile-browser-next-line) + (define-key makefile-browser-map "\C-n" 'makefile-browser-next-line) (define-key makefile-browser-map "p" 'makefile-browser-previous-line) (define-key makefile-browser-map "\C-p" 'makefile-browser-previous-line) (define-key makefile-browser-map " " 'makefile-browser-toggle) (define-key makefile-browser-map "i" 'makefile-browser-insert-selection) - (define-key makefile-browser-map "I" 'makefile-browser-insert-selection-and-quit) + (define-key makefile-browser-map "I" 'makefile-browser-insert-selection-and-quit) (define-key makefile-browser-map "\C-c\C-m" 'makefile-browser-insert-continuation) (define-key makefile-browser-map "q" 'makefile-browser-quit) ;; disable horizontal movement (define-key makefile-browser-map "\C-b" 'undefined) - (define-key makefile-browser-map "\C-f" 'undefined)) + (define-key makefile-browser-map "\C-f" 'undefined)) -(defvar makefile-mode-syntax-table nil - "The syntax-table used in makefile mode.") +(defvar makefile-mode-syntax-table nil) (if makefile-mode-syntax-table () (setq makefile-mode-syntax-table (make-syntax-table)) (modify-syntax-entry ?\( "() " makefile-mode-syntax-table) (modify-syntax-entry ?\) ")( " makefile-mode-syntax-table) (modify-syntax-entry ?\[ "(] " makefile-mode-syntax-table) - (modify-syntax-entry ?\] "([ " makefile-mode-syntax-table) - (modify-syntax-entry ?\{ "(} " makefile-mode-syntax-table) + (modify-syntax-entry ?\] ")[ " makefile-mode-syntax-table) + (modify-syntax-entry ?\{ "(} " makefile-mode-syntax-table) (modify-syntax-entry ?\} "){ " makefile-mode-syntax-table) + (modify-syntax-entry ?\' "\" " makefile-mode-syntax-table) + (modify-syntax-entry ?\` "\" " makefile-mode-syntax-table) (modify-syntax-entry ?# "< " makefile-mode-syntax-table) (modify-syntax-entry ?\n "> " makefile-mode-syntax-table)) - - + + ;;; ------------------------------------------------------------ ;;; Internal variables. ;;; You don't need to configure below this line. ;;; ------------------------------------------------------------ (defvar makefile-target-table nil - "Table of all targets that have been inserted in -this Makefile buffer using makefile-insert-target or picked up -using makefile-pickup-targets.") + "Table of all target names known for this buffer.") (defvar makefile-macro-table nil - "Table of all macros that have been iserted in -this Makefile buffer using makefile-insert-macro or picked up -using makefile-pickup-macros.") + "Table of all macro names known for this buffer.") (defvar makefile-browser-client - "A buffer in makefile-mode that is currently using the browser.") + "A buffer in Makefile mode that is currently using the browser.") (defvar makefile-browser-selection-vector nil) +(defvar makefile-has-prereqs nil) +(defvar makefile-need-target-pickup t) +(defvar makefile-need-macro-pickup t) (defvar makefile-mode-hook '()) +;; Each element looks like '("GNU MAKE FUNCTION" "ARG" "ARG" ... ) +;; Each "ARG" is used as a prompt for a required argument. (defconst makefile-gnumake-functions-alist - '( ;; Text functions ("subst" "From" "To" "In") @@ -294,6 +482,7 @@ using makefile-pickup-macros.") ("notdir" "Names") ("suffix" "Names") ("basename" "Names") + ("addprefix" "Prefix" "Names") ("addsuffix" "Suffix" "Names") ("join" "List 1" "List 2") ("word" "Index" "Text") @@ -303,10 +492,7 @@ using makefile-pickup-macros.") ;; Misc functions ("foreach" "Variable" "List" "Text") ("origin" "Variable") - ("shell" "Command")) - "A list of GNU make 3.62 function names associated with -the prompts for each function. -This is used in the function makefile-insert-gmake-function .") + ("shell" "Command"))) ;;; ------------------------------------------------------------ @@ -316,8 +502,7 @@ This is used in the function makefile-insert-gmake-function .") ;;;###autoload (defun makefile-mode () "Major mode for editing Makefiles. -Calling this function invokes the function(s) \"makefile-mode-hook\" before -doing anything else. +This function ends by invoking the function(s) `makefile-mode-hook'. \\{makefile-mode-map} @@ -325,122 +510,153 @@ In the browser, use the following keys: \\{makefile-browser-map} -makefile-mode can be configured by modifying the following -variables: +Makefile mode can be configured by modifying the following variables: -makefile-mode-name: - The \"pretty name\" of makefile-mode, as it - appears in the modeline. - -makefile-browser-buffer-name: +`makefile-browser-buffer-name': Name of the macro- and target browser buffer. -makefile-target-colon: +`makefile-target-colon': The string that gets appended to all target names - inserted by makefile-insert-target. + inserted by `makefile-insert-target'. \":\" or \"::\" are quite common values. -makefile-macro-assign: +`makefile-macro-assign': The string that gets appended to all macro names - inserted by makefile-insert-macro. + inserted by `makefile-insert-macro'. The normal value should be \" = \", since this is what - standard make expects. However, newer makes such as dmake + standard make expects. However, newer makes such as dmake allow a larger variety of different macro assignments, so you might prefer to use \" += \" or \" := \" . -makefile-tab-after-target-colon: +`makefile-tab-after-target-colon': If you want a TAB (instead of a space) to be appended after the target colon, then set this to a non-nil value. -makefile-browser-leftmost-column: +`makefile-browser-leftmost-column': Number of blanks to the left of the browser selection mark. -makefile-browser-cursor-column: +`makefile-browser-cursor-column': Column in which the cursor is positioned when it moves up or down in the browser. -makefile-browser-selected-mark: +`makefile-browser-selected-mark': String used to mark selected entries in the browser. -makefile-browser-unselected-mark: +`makefile-browser-unselected-mark': String used to mark unselected entries in the browser. -makefile-browser-auto-advance-after-selection-p: +`makefile-browser-auto-advance-after-selection-p': If this variable is set to a non-nil value the cursor will automagically advance to the next line after an item has been selected in the browser. -makefile-find-file-autopickup-p: - If this variable is set to a non-nil value then finding a file in - a makefile-mode buffer will cause an automatic initial pickup of - all macros and targets from the found file. - -makefile-pickup-everything-picks-up-filenames-p: +`makefile-pickup-everything-picks-up-filenames-p': If this variable is set to a non-nil value then - makefile-pickup-everything also picks up filenames as targets - (i.e. it calls makefile-find-filenames-as-targets), otherwise + `makefile-pickup-everything' also picks up filenames as targets + (i.e. it calls `makefile-pickup-filenames-as-targets'), otherwise filenames are omitted. -makefile-cleanup-continuations-p: - If this variable is set to a non-nil value then makefile-mode +`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. This is done by silently removing the trailing whitespace, leaving the backslash itself intact. - IMPORTANT: Please note that enabling this option causes makefile-mode - to MODIFY A FILE WITHOUT YOUR CONFIRMATION when \'it seems necessary\'. + IMPORTANT: Please note that enabling this option causes Makefile mode + to MODIFY A FILE WITHOUT YOUR CONFIRMATION when \"it seems necessary\". -makefile-browser-hook: +`makefile-browser-hook': A function or list of functions to be called just before the - browser is entered. This is executed in the makefile buffer, so - you can for example run a makefile-pickup-everything automatically. + browser is entered. This is executed in the makefile buffer. -makefile-special-targets-list: +`makefile-special-targets-list': List of special targets. You will be offered to complete - on one of those in the minibuffer whenever you enter a \".\" - at the beginning of a line in makefile-mode." + on one of those in the minibuffer whenever you enter a `.'. + at the beginning of a line in Makefile mode." + (interactive) (kill-all-local-variables) - (if (not (memq 'makefile-find-file-autopickup find-file-hooks)) - (setq find-file-hooks - (append find-file-hooks (list 'makefile-find-file-autopickup)))) - (if (not (memq 'makefile-cleanup-continuations write-file-hooks)) - (setq write-file-hooks - (append write-file-hooks (list 'makefile-cleanup-continuations)))) - (make-variable-buffer-local 'makefile-target-table) - (make-variable-buffer-local 'makefile-macro-table) - (makefile-forget-all-macros) - (makefile-forget-all-targets) + (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) + (make-local-variable 'makefile-need-target-pickup) + (make-local-variable 'makefile-need-macro-pickup) + + ;; Font lock. + (make-local-variable 'font-lock-defaults) + (setq font-lock-defaults + ;; 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 + ((?$ . ".")) + backward-paragraph + (font-lock-syntactic-keywords . makefile-font-lock-syntactic-keywords))) + + ;; Add-log. + (make-local-variable 'add-log-current-defun-function) + (setq add-log-current-defun-function 'makefile-add-log-defun) + + ;; Imenu. + (make-local-variable 'imenu-generic-expression) + (setq imenu-generic-expression makefile-imenu-generic-expression) + + ;; Dabbrev. + (make-local-variable 'dabbrev-abbrev-skip-leading-regexp) + (setq dabbrev-abbrev-skip-leading-regexp "\\$") + + ;; Other abbrevs. + (setq local-abbrev-table makefile-mode-abbrev-table) + + ;; Filling. + (make-local-variable 'fill-paragraph-function) + (setq fill-paragraph-function 'makefile-fill-paragraph) + + ;; Comment stuff. + (make-local-variable 'comment-start) (setq comment-start "#") + (make-local-variable 'comment-end) (setq comment-end "") - (setq comment-start-skip "#[ \t]*") + (make-local-variable 'comment-start-skip) + (setq comment-start-skip "#+[ \t]*") + + ;; Make sure TAB really inserts \t. + (set (make-local-variable 'indent-line-function) 'indent-to-left-margin) + ;; become the current major mode (setq major-mode 'makefile-mode) - (setq mode-name makefile-mode-name) - ;; activate keymap + (setq mode-name "Makefile") + + ;; Activate keymap and syntax table. (use-local-map makefile-mode-map) (set-syntax-table makefile-mode-syntax-table) - (run-hooks 'makefile-mode-hook)) + ;; Real TABs are important in makefiles + (setq indent-tabs-mode t) + (run-hooks 'makefile-mode-hook)) -(defun makefile-find-file-autopickup () - (if (eq major-mode 'makefile-mode) - (if makefile-find-file-autopickup-p - (makefile-pickup-everything)))) + + +;;; Motion code. (defun makefile-next-dependency () - "Move (point) to the beginning of the next dependency line -below the current position of (point)." + "Move point to the beginning of the next dependency line." (interactive) (let ((here (point))) (end-of-line) (if (re-search-forward makefile-dependency-regex (point-max) t) (progn (beginning-of-line) t) ; indicate success (goto-char here) nil))) - + (defun makefile-previous-dependency () - "Move (point) to the beginning of the next dependency line -above the current position of (point)." + "Move point to the beginning of the previous dependency line." (interactive) (let ((here (point))) (beginning-of-line) @@ -448,59 +664,61 @@ above the current position of (point)." (progn (beginning-of-line) t) ; indicate success (goto-char here) nil))) + -(defun makefile-electric-dot () - "At (bol), offer completion on makefile-special-targets-list. -Anywhere else just insert a dot." - (interactive) +;;; Electric keys. Blech. + +(defun makefile-electric-dot (arg) + "Prompt for the name of a special target to insert. +Only does electric insertion at beginning of line. +Anywhere else just self-inserts." + (interactive "p") (if (bolp) (makefile-insert-special-target) - (insert "."))) - + (self-insert-command arg))) (defun makefile-insert-special-target () - "Offer completion on makefile-special-targets-list and insert -the result at (point)." + "Prompt for and insert a special target name. +Uses `makefile-special-targets' list." (interactive) - (let - ((special-target - (completing-read "Special target: " - makefile-special-targets-list nil nil nil))) + (makefile-pickup-targets) + (let ((special-target + (completing-read "Special target: " + makefile-special-targets-list nil nil nil))) (if (zerop (length special-target)) () - (insert (format ".%s:" special-target)) + (insert "." special-target ":") (makefile-forward-after-target-colon)))) - -(defun makefile-electric-equal () - "At (bol) do makefile-insert-macro. Anywhere else just -self-insert." - (interactive) +(defun makefile-electric-equal (arg) + "Prompt for name of a macro to insert. +Only does prompting if point is at beginning of line. +Anywhere else just self-inserts." + (interactive "p") + (makefile-pickup-macros) (if (bolp) (call-interactively 'makefile-insert-macro) - (insert "="))) + (self-insert-command arg))) (defun makefile-insert-macro (macro-name) "Prepare definition of a new macro." (interactive "sMacro Name: ") + (makefile-pickup-macros) (if (not (zerop (length macro-name))) (progn (beginning-of-line) - (insert (format "%s%s" macro-name makefile-macro-assign)) + (insert macro-name makefile-macro-assign) + (setq makefile-need-macro-pickup t) (makefile-remember-macro macro-name)))) - (defun makefile-insert-macro-ref (macro-name) - "Offer completion on a list of known macros, then -insert complete macro-ref at (point) ." + "Complete on a list of known macros, then insert complete ref at point." (interactive (list - (completing-read "Refer to macro: " makefile-macro-table nil nil nil))) - (if (not (zerop (length macro-name))) - (if (assoc macro-name makefile-runtime-macros-list) - (insert (format "$%s " macro-name)) - (insert (makefile-format-macro-ref macro-name) " ")))) - + (progn + (makefile-pickup-macros) + (completing-read "Refer to macro: " makefile-macro-table nil nil nil)))) + (makefile-do-macro-insertion macro-name)) (defun makefile-insert-target (target-name) "Prepare definition of a new target (dependency line)." @@ -508,48 +726,56 @@ insert complete macro-ref at (point) ." (if (not (zerop (length target-name))) (progn (beginning-of-line) - (insert (format "%s%s" target-name makefile-target-colon)) + (insert target-name makefile-target-colon) (makefile-forward-after-target-colon) (end-of-line) + (setq makefile-need-target-pickup t) (makefile-remember-target target-name)))) - (defun makefile-insert-target-ref (target-name) - "Offer completion on a list of known targets, then -insert complete target-ref at (point) ." + "Complete on a list of known targets, then insert TARGET-NAME at point." (interactive (list - (completing-read "Refer to target: " makefile-target-table nil nil nil))) + (progn + (makefile-pickup-targets) + (completing-read "Refer to target: " makefile-target-table nil nil nil)))) (if (not (zerop (length target-name))) - (progn - (insert (format "%s " target-name))))) + (insert target-name " "))) -(defun makefile-electric-colon () - "At (bol) defines a new target, anywhere else just self-insert ." - (interactive) +(defun makefile-electric-colon (arg) + "Prompt for name of new target. +Prompting only happens at beginning of line. +Anywhere else just self-inserts." + (interactive "p") (if (bolp) (call-interactively 'makefile-insert-target) - (insert ":"))) + (self-insert-command arg))) + ;;; ------------------------------------------------------------ ;;; Extracting targets and macros from an existing makefile ;;; ------------------------------------------------------------ (defun makefile-pickup-targets () - "Scan a buffer that contains a makefile for target definitions (dependencies) -and add them to the list of known targets." + "Notice names of all target definitions in Makefile." (interactive) - (save-excursion - (goto-char (point-min)) - (while (re-search-forward makefile-dependency-regex (point-max) t) - (makefile-add-this-line-targets)))) -; (forward-line 1)))) + (if (not makefile-need-target-pickup) + nil + (setq makefile-need-target-pickup nil) + (setq makefile-target-table nil) + (setq makefile-has-prereqs nil) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward makefile-dependency-regex nil 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)) + (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) @@ -559,54 +785,64 @@ and add them to the list of known targets." (target-name (progn (skip-chars-forward "^ \t:#") - (buffer-substring start-of-target-name (point))))) - (if (makefile-remember-target target-name) - (message "Picked up target \"%s\"" target-name))))))))) - + (buffer-substring start-of-target-name (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))))))))) (defun makefile-pickup-macros () - "Scan a buffer that contains a makefile for macro definitions -and add them to the list of known macros." + "Notice names of all macro definitions in Makefile." (interactive) - (save-excursion - (goto-char (point-min)) - (while (re-search-forward makefile-macroassign-regex (point-max) t) - (makefile-add-this-line-macro) - (forward-line 1)))) + (if (not makefile-need-macro-pickup) + nil + (setq makefile-need-macro-pickup nil) + (setq makefile-macro-table nil) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward makefile-macroassign-regex nil 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)) - (macro-name (progn - (skip-chars-forward "^ \t:#=*") - (buffer-substring start-of-macro-name (point))))) - (if (makefile-remember-macro macro-name) - (message "Picked up macro \"%s\"" macro-name)))))) - - -(defun makefile-pickup-everything () - "Calls makefile-pickup-targets and makefile-pickup-macros. -See their documentation for what they do." - (interactive) + (unless (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))))) + (if (makefile-remember-macro macro-name) + (message "Picked up macro \"%s\" from line %d" + macro-name line-number)))))) + +(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))) (makefile-pickup-macros) (makefile-pickup-targets) (if makefile-pickup-everything-picks-up-filenames-p (makefile-pickup-filenames-as-targets))) - (defun makefile-pickup-filenames-as-targets () - "Scan the current directory for filenames, check each filename -against makefile-ignored-files-in-pickup-regex and add all qualifying -names to the list of known targets." + "Scan the current directory for filenames to use as targets. +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) + (mapcar (lambda (name) (if (and (not (file-directory-p name)) (not (string-match makefile-ignored-files-in-pickup-regex name))) @@ -614,11 +850,256 @@ names to the list of known targets." (message "Picked up file \"%s\" as target" name)))) raw-filename-list))) + + +;;; Completion. + +(defun makefile-complete () + "Perform completion on Makefile construct preceding point. +Can complete variable and target names. +The context determines which are considered." + (interactive) + (let* ((beg (save-excursion + (skip-chars-backward "^$(){}:#= \t\n") + (point))) + (try (buffer-substring beg (point))) + (do-macros nil) + (paren nil)) + + (save-excursion + (goto-char beg) + (let ((pc (preceding-char))) + (cond + ;; Beginning of line means anything. + ((bolp) + ()) + + ;; Preceding "$" means macros only. + ((= pc ?$) + (setq do-macros t)) + + ;; Preceding "$(" or "${" means macros only. + ((and (or (= pc ?{) + (= pc ?\()) + (progn + (setq paren pc) + (backward-char) + (and (not (bolp)) + (= (preceding-char) ?$)))) + (setq do-macros t))))) + + ;; Try completion. + (let* ((table (append (if do-macros + '() + makefile-target-table) + makefile-macro-table)) + (completion (try-completion try table))) + (cond + ;; Exact match, so insert closing paren or colon. + ((eq completion t) + (insert (if do-macros + (if (eq paren ?{) + ?} + ?\)) + (if (save-excursion + (goto-char beg) + (bolp)) + ":" + " ")))) + + ;; No match. + ((null completion) + (message "Can't find completion for \"%s\"" try) + (ding)) + + ;; Partial completion. + ((not (string= try completion)) + ;; FIXME it would be nice to supply the closing paren if an + ;; exact, unambiguous match were found. That is not possible + ;; right now. Ditto closing ":" for targets. + (delete-region beg (point)) + + ;; DO-MACROS means doing macros only. If not that, then check + ;; to see if this completion is a macro. Special insertion + ;; must be done for macros. + (if (or do-macros + (assoc completion makefile-macro-table)) + (let ((makefile-use-curly-braces-for-macros-p + (or (eq paren ?{) + makefile-use-curly-braces-for-macros-p))) + (delete-backward-char 2) + (makefile-do-macro-insertion completion) + (delete-backward-char 1)) + + ;; Just insert targets. + (insert completion))) + + ;; Can't complete any more, so make completion list. FIXME + ;; this doesn't do the right thing when the completion is + ;; actually inserted. I don't think there is an easy way to do + ;; that. + (t + (message "Making completion list...") + (let ((list (all-completions try table))) + (with-output-to-temp-buffer "*Completions*" + (display-completion-list list))) + (message "Making completion list...done")))))) + + + +;; Backslashification. Stolen from cc-mode.el. + +(defun makefile-backslash-region (from to delete-flag) + "Insert, align, or delete end-of-line backslashes on the lines in the region. +With no argument, inserts backslashes and aligns existing backslashes. +With an argument, deletes the backslashes. + +This function does not modify the last line of the region if the region ends +right at the start of the following line; it does not modify blank lines +at the start of the region. So you can put the region around an entire macro +definition and conveniently use this command." + (interactive "r\nP") + (save-excursion + (goto-char from) + (let ((column makefile-backslash-column) + (endmark (make-marker))) + (move-marker endmark to) + ;; Compute the smallest column number past the ends of all the lines. + (if makefile-backslash-align + (progn + (if (not delete-flag) + (while (< (point) to) + (end-of-line) + (if (= (preceding-char) ?\\) + (progn (forward-char -1) + (skip-chars-backward " \t"))) + (setq column (max column (1+ (current-column)))) + (forward-line 1))) + ;; Adjust upward to a tab column, if that doesn't push + ;; past the margin. + (if (> (% column tab-width) 0) + (let ((adjusted (* (/ (+ column tab-width -1) tab-width) + tab-width))) + (if (< adjusted (window-width)) + (setq column adjusted)))))) + ;; Don't modify blank lines at start of region. + (goto-char from) + (while (and (< (point) endmark) (eolp)) + (forward-line 1)) + ;; Add or remove backslashes on all the lines. + (while (and (< (point) endmark) + ;; Don't backslashify the last line + ;; if the region ends right at the start of the next line. + (save-excursion + (forward-line 1) + (< (point) endmark))) + (if (not delete-flag) + (makefile-append-backslash column) + (makefile-delete-backslash)) + (forward-line 1)) + (move-marker endmark nil)))) + +(defun makefile-append-backslash (column) + (end-of-line) + ;; Note that "\\\\" is needed to get one backslash. + (if (= (preceding-char) ?\\) + (progn (forward-char -1) + (delete-horizontal-space) + (indent-to column (if makefile-backslash-align nil 1))) + (indent-to column (if makefile-backslash-align nil 1)) + (insert "\\"))) + +(defun makefile-delete-backslash () + (end-of-line) + (or (bolp) + (progn + (forward-char -1) + (if (looking-at "\\\\") + (delete-region (1+ (point)) + (progn (skip-chars-backward " \t") (point))))))) + + + +;; Filling + +(defun makefile-fill-paragraph (arg) + ;; Fill comments, backslashed lines, and variable definitions + ;; specially. + (save-excursion + (beginning-of-line) + (cond + ((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)) + (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. + ((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) + (let ((beginning + (save-excursion + (end-of-line 0) + (while (= (preceding-char) ?\\) + (end-of-line 0)) + (forward-char) + (point))) + (end + (save-excursion + (while (= (preceding-char) ?\\) + (end-of-line 2)) + (point)))) + (save-restriction + (narrow-to-region beginning end) + (makefile-backslash-region (point-min) (point-max) t) + (let ((fill-paragraph-function nil)) + (fill-paragraph nil)) + (makefile-backslash-region (point-min) (point-max) nil) + (goto-char (point-max)) + (if (< (skip-chars-backward "\n") 0) + (delete-region (point) (point-max)))))) + + ((looking-at makefile-macroassign-regex) + ;; Have a macro assign. Fill just this line, and then backslash + ;; resulting region. + (save-restriction + (narrow-to-region (point) (line-beginning-position 2)) + (let ((fill-paragraph-function nil)) + (fill-paragraph nil)) + (makefile-backslash-region (point-min) (point-max) nil))))) + + ;; Always return non-nil so we don't fill anything else. + t) + + + ;;; ------------------------------------------------------------ -;;; The browser window +;;; Browser mode. ;;; ------------------------------------------------------------ - (defun makefile-browser-format-target-line (target selected) (format (concat (make-string makefile-browser-leftmost-column ?\ ) @@ -637,26 +1118,24 @@ names to the list of known targets." (makefile-format-macro-ref macro)))) (defun makefile-browser-fill (targets macros) - (setq buffer-read-only nil) - (goto-char (point-min)) - (erase-buffer) - (mapconcat - (function - (lambda (item) (insert (makefile-browser-format-target-line (car item) nil) "\n"))) - targets - "") - (mapconcat - (function - (lambda (item) (insert (makefile-browser-format-macro-line (car item) nil) "\n"))) - macros - "") - (sort-lines nil (point-min) (point-max)) - (goto-char (1- (point-max))) - (delete-char 1) ; remove unnecessary newline at eob - (goto-char (point-min)) - (forward-char makefile-browser-cursor-column) - (setq buffer-read-only t)) - + (let ((inhibit-read-only t)) + (goto-char (point-min)) + (erase-buffer) + (mapconcat + (function + (lambda (item) (insert (makefile-browser-format-target-line (car item) nil) "\n"))) + targets + "") + (mapconcat + (function + (lambda (item) (insert (makefile-browser-format-macro-line (car item) nil) "\n"))) + macros + "") + (sort-lines nil (point-min) (point-max)) + (goto-char (1- (point-max))) + (delete-char 1) ; remove unnecessary newline at eob + (goto-char (point-min)) + (forward-char makefile-browser-cursor-column))) ;;; ;;; Moving up and down in the browser @@ -683,13 +1162,12 @@ names to the list of known targets." ;;; (defun makefile-browser-quit () - "Leave the makefile-browser-buffer and return to the buffer -from that it has been entered." + "Leave the browser and return to the makefile buffer." (interactive) (let ((my-client makefile-browser-client)) (setq makefile-browser-client nil) ; we quitted, so NO client! (set-buffer-modified-p nil) - (kill-buffer (current-buffer)) + (quit-window t) (pop-to-buffer my-client))) ;;; @@ -703,22 +1181,21 @@ from that it has been entered." (setq this-line (max 1 this-line)) (makefile-browser-toggle-state-for-line this-line) (goto-line this-line) - (setq buffer-read-only nil) - (beginning-of-line) - (if (makefile-browser-on-macro-line-p) - (let ((macro-name (makefile-browser-this-line-macro-name))) - (kill-line) + (let ((inhibit-read-only t)) + (beginning-of-line) + (if (makefile-browser-on-macro-line-p) + (let ((macro-name (makefile-browser-this-line-macro-name))) + (delete-region (point) (progn (end-of-line) (point))) + (insert + (makefile-browser-format-macro-line + macro-name + (makefile-browser-get-state-for-line this-line)))) + (let ((target-name (makefile-browser-this-line-target-name))) + (delete-region (point) (progn (end-of-line) (point))) (insert - (makefile-browser-format-macro-line - macro-name - (makefile-browser-get-state-for-line this-line)))) - (let ((target-name (makefile-browser-this-line-target-name))) - (kill-line) - (insert - (makefile-browser-format-target-line - target-name - (makefile-browser-get-state-for-line this-line))))) - (setq buffer-read-only t) + (makefile-browser-format-target-line + target-name + (makefile-browser-get-state-for-line this-line)))))) (beginning-of-line) (forward-char makefile-browser-cursor-column) (if makefile-browser-auto-advance-after-selection-p @@ -729,21 +1206,20 @@ from that it has been entered." ;;; (defun makefile-browser-insert-continuation () - "In the browser\'s client buffer, go to (end-of-line), insert a \'\\\' + "Insert a makefile continuation. +In the makefile buffer, go to (end-of-line), insert a \'\\\' character, insert a new blank line, go to that line and indent by one TAB. -This is most useful in the process of creating continued lines when 'sending' large -dependencies from the browser to the client buffer. -(point) advances accordingly in the client buffer." +This is most useful in the process of creating continued lines when copying +large dependencies from the browser to the client buffer. +\(point) advances accordingly in the client buffer." (interactive) - (save-excursion - (set-buffer makefile-browser-client) + (with-current-buffer makefile-browser-client (end-of-line) (insert "\\\n\t"))) (defun makefile-browser-insert-selection () - "Insert all browser-selected targets and/or macros in the browser\'s -client buffer. -Insertion takes place at (point)." + "Insert all selected targets and/or macros in the makefile buffer. +Insertion takes place at point." (interactive) (save-excursion (goto-line 1) @@ -770,12 +1246,10 @@ Insertion takes place at (point)." (set-buffer makefile-browser-client) (insert target-name " "))))) - (defun makefile-browser-start-interaction () (use-local-map makefile-browser-map) (setq buffer-read-only t)) - (defun makefile-browse (targets macros) (interactive) (if (zerop (+ (length targets) (length macros))) @@ -784,27 +1258,28 @@ Insertion takes place at (point)." (message "No macros or targets to browse! Consider running 'makefile-pickup-everything\'")) (let ((browser-buffer (get-buffer-create makefile-browser-buffer-name))) (pop-to-buffer browser-buffer) - (make-variable-buffer-local 'makefile-browser-selection-vector) (makefile-browser-fill targets macros) - (setq makefile-browser-selection-vector - (make-vector (+ (length targets) (length macros)) nil)) + (shrink-window-if-larger-than-buffer) + (set (make-local-variable 'makefile-browser-selection-vector) + (make-vector (+ (length targets) (length macros)) nil)) (makefile-browser-start-interaction)))) - (defun makefile-switch-to-browser () (interactive) (run-hooks 'makefile-browser-hook) (setq makefile-browser-client (current-buffer)) + (makefile-pickup-targets) + (makefile-pickup-macros) (makefile-browse makefile-target-table makefile-macro-table)) + ;;; ------------------------------------------------------------ ;;; Up-to-date overview buffer ;;; ------------------------------------------------------------ (defun makefile-create-up-to-date-overview () - "Create a buffer containing an overview of the state of all -known targets from the makefile that is currently being edited. + "Create a buffer containing an overview of the state of all known targets. Known targets are targets that are explicitly defined in that makefile; in other words, all targets that appear on the left hand side of a dependency in the makefile." @@ -827,14 +1302,15 @@ dependency in the makefile." ;; The 'old' target table will be restored later. ;; (real-targets (progn - (makefile-forget-all-targets) (makefile-pickup-targets) - makefile-target-table))) + makefile-target-table)) + (prereqs makefile-has-prereqs) + ) (set-buffer makefile-up-to-date-buffer) (setq buffer-read-only nil) (erase-buffer) - (makefile-query-targets filename real-targets) + (makefile-query-targets filename real-targets prereqs) (if (zerop (buffer-size)) ; if it did not get us anything (progn (kill-buffer (current-buffer)) @@ -844,10 +1320,9 @@ dependency in the makefile." (if (get-buffer makefile-up-to-date-buffer-name) (progn (pop-to-buffer (get-buffer makefile-up-to-date-buffer-name)) + (shrink-window-if-larger-than-buffer) (sort-lines nil (point-min) (point-max)) (setq buffer-read-only t)))))) - - (defun makefile-save-temporary () "Create a temporary file from the current makefile buffer." @@ -856,9 +1331,9 @@ dependency in the makefile." filename)) ; return the filename (defun makefile-generate-temporary-filename () - "Create a filename suitable for use in makefile-save-temporary. + "Create a filename suitable for use in `makefile-save-temporary'. Be careful to allow brain-dead file systems (DOS, SYSV ...) to cope -with the generated name !" +with the generated name!" (let ((my-name (user-login-name)) (my-uid (int-to-string (user-uid)))) (concat "mktmp" @@ -870,30 +1345,36 @@ with the generated name !" (substring my-uid 0 3) my-uid)))) -(defun makefile-query-targets (filename target-table) - "This function fills the up-to-date-overview-buffer. -It checks each target in target-table using makefile-query-one-target-method +(defun makefile-query-targets (filename target-table prereq-list) + "Fill the up-to-date overview buffer. +Checks each target in TARGET-TABLE using `makefile-query-one-target-method' and generates the overview, one line per target name." (insert - (mapconcat '(lambda (item) - (let ((target-name (car item))) - (makefile-format-up-to-date-buffer-entry - (funcall makefile-query-one-target-method - target-name filename) target-name))) - target-table "\n")) + (mapconcat + (function (lambda (item) + (let* ((target-name (car item)) + (no-prereqs (not (member target-name prereq-list))) + (needs-rebuild (or no-prereqs + (funcall + makefile-query-one-target-method + target-name + filename)))) + (format "\t%s%s" + target-name + (cond (no-prereqs " .. has no prerequisites") + (needs-rebuild " .. NEEDS REBUILD") + (t " .. is up to date")))) + )) + target-table "\n")) (goto-char (point-min)) (delete-file filename)) ; remove the tmpfile (defun makefile-query-by-make-minus-q (target &optional filename) - (not (zerop (call-process makefile-brave-make nil nil nil "-f" filename "-q" target)))) - -(defun makefile-format-up-to-date-buffer-entry (needs-rebuild target) - (format "\t%s%s" - target - (if needs-rebuild - makefile-target-needs-rebuild-mark - makefile-target-up-to-date-mark))) + (not (eq 0 + (call-process makefile-brave-make nil nil nil + "-f" filename "-q" target)))) + ;;; ------------------------------------------------------------ ;;; Continuation cleanup @@ -901,27 +1382,46 @@ 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)))))) + +;;; ------------------------------------------------------------ +;;; Warn of suspicious lines +;;; ------------------------------------------------------------ + +(defun makefile-warn-suspicious-lines () + ;; Returning non-nil cancels the save operation + (if (eq major-mode 'makefile-mode) + (save-excursion + (goto-char (point-min)) + (if (re-search-forward "^\\(\t+$\\| +\t\\)" nil t) + (not (y-or-n-p + (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))))))))) + + ;;; ------------------------------------------------------------ ;;; GNU make function support ;;; ------------------------------------------------------------ (defun makefile-insert-gmake-function () - "This function is intended to help you using the numerous -macro-like \'function calls\' of GNU make. -It will ask you for the name of the function you wish to -use (with completion), then, after you selected the function, -it will prompt you for all required parameters. -This function \'knows\' about the required parameters of every -GNU make function and will use meaningfull prompts for the -various args, making it much easier to take advantage of this -powerfull GNU make feature." + "Insert a GNU make function call. +Asks for the name of the function to use (with completion). +Then prompts for all required parameters." (interactive) (let* ((gm-function-name (completing-read "Function: " @@ -939,44 +1439,34 @@ powerfull GNU make feature." (defun makefile-prompt-for-gmake-funargs (function-name prompt-list) (mapconcat (function (lambda (one-prompt) - (read-string (format "[%s] %s: " function-name one-prompt) nil))) + (read-string (format "[%s] %s: " function-name one-prompt) + nil))) prompt-list ",")) - + ;;; ------------------------------------------------------------ ;;; Utility functions ;;; ------------------------------------------------------------ -(defun makefile-forget-all-targets () - "Clear the target-table for this buffer." - (interactive) - (setq makefile-target-table '())) - -(defun makefile-forget-all-macros () - "Clear the macro-table for this buffer." - (interactive) - (setq makefile-macro-table '())) - - -(defun makefile-forget-everything () - "Clear the macro-table AND the target-table for this buffer." - (interactive) - (if (y-or-n-p "Really forget all macro- and target information ? ") - (progn - (makefile-forget-all-targets) - (makefile-forget-all-macros) - (if (get-buffer makefile-browser-buffer-name) - (kill-buffer makefile-browser-buffer-name)) - (message "Cleared macro- and target tables.")))) +(defun makefile-do-macro-insertion (macro-name) + "Insert a macro reference." + (if (not (zerop (length macro-name))) + (if (assoc macro-name makefile-runtime-macros-list) + (insert "$" macro-name) + (insert (makefile-format-macro-ref macro-name))))) -(defun makefile-remember-target (target-name) +(defun makefile-remember-target (target-name &optional has-prereqs) "Remember a given target if it is not already remembered for this buffer." (if (not (zerop (length target-name))) + (progn (if (not (assoc target-name makefile-target-table)) (setq makefile-target-table - (cons (list target-name) makefile-target-table))))) + (cons (list target-name) makefile-target-table))) + (if has-prereqs + (setq makefile-has-prereqs + (cons target-name makefile-has-prereqs)))))) (defun makefile-remember-macro (macro-name) "Remember a given macro if it is not already remembered for this buffer." @@ -986,9 +1476,8 @@ powerfull GNU make feature." (cons (list macro-name) makefile-macro-table))))) (defun makefile-forward-after-target-colon () -"Move point forward after the terminating colon -of a target has been inserted. -This accts according to the value of makefile-tab-after-target-colon ." + "Move point forward after inserting the terminating colon of a target. +This acts according to the value of `makefile-tab-after-target-colon'." (if makefile-tab-after-target-colon (insert "\t") (insert " "))) @@ -997,30 +1486,33 @@ This accts according to the value of makefile-tab-after-target-colon ." "Determine if point is on a macro line in the browser." (save-excursion (beginning-of-line) - (re-search-forward "\\$[{(]" (makefile-end-of-line-point) t))) + (re-search-forward "\\$[{(]" (line-end-position) t))) (defun makefile-browser-this-line-target-name () "Extract the target name from a line in the browser." (save-excursion (end-of-line) (skip-chars-backward "^ \t") - (buffer-substring (point) (1- (makefile-end-of-line-point))))) + (buffer-substring (point) (1- (line-end-position))))) (defun makefile-browser-this-line-macro-name () "Extract the macro name from a line in the browser." (save-excursion (beginning-of-line) - (re-search-forward "\\$[{(]" (makefile-end-of-line-point) t) + (re-search-forward "\\$[{(]" (line-end-position) t) (let ((macro-start (point))) (skip-chars-forward "^})") (buffer-substring macro-start (point))))) (defun makefile-format-macro-ref (macro-name) - "Format a macro reference according to the value of the -configuration variable makefile-use-curly-braces-for-macros-p ." - (if makefile-use-curly-braces-for-macros-p - (format "${%s}" macro-name) - (format "$(%s)" macro-name))) + "Format a macro reference. +Uses `makefile-use-curly-braces-for-macros-p'." + (if (or (char-equal ?\( (string-to-char macro-name)) + (char-equal ?\{ (string-to-char macro-name))) + (format "$%s" macro-name) + (if makefile-use-curly-braces-for-macros-p + (format "${%s}" macro-name) + (format "$(%s)" macro-name)))) (defun makefile-browser-get-state-for-line (n) (aref makefile-browser-selection-vector (1- n))) @@ -1031,20 +1523,35 @@ configuration variable makefile-use-curly-braces-for-macros-p ." (defun makefile-browser-toggle-state-for-line (n) (makefile-browser-set-state-for-line n (not (makefile-browser-get-state-for-line n)))) -(defun makefile-beginning-of-line-point () - (save-excursion - (beginning-of-line) - (point))) - -(defun makefile-end-of-line-point () - (save-excursion - (end-of-line) - (point))) - (defun makefile-last-line-p () - (= (makefile-end-of-line-point) (point-max))) + (= (line-end-position) (point-max))) (defun makefile-first-line-p () - (= (makefile-beginning-of-line-point) (point-min))) + (= (line-beginning-position) (point-min))) -;; makefile.el ends here + + +;;; Support for other packages, like add-log. + +(defun makefile-add-log-defun () + "Return name of target or variable assignment that point is in. +If it isn't in one, return nil." + (save-excursion + (let (found) + (beginning-of-line) + ;; 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 (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