SMIE: Reliably distinguish openers/closers in smie-prec2-levels
[bpt/emacs.git] / lisp / progmodes / fortran.el
index d5b551b..91cfb64 100644 (file)
@@ -1,8 +1,8 @@
 ;;; fortran.el --- Fortran mode for GNU Emacs
 
 ;; Copyright (C) 1986, 1993, 1994, 1995, 1997, 1998, 1999, 2000, 2001,
 ;;; fortran.el --- Fortran mode for GNU Emacs
 
 ;; Copyright (C) 1986, 1993, 1994, 1995, 1997, 1998, 1999, 2000, 2001,
-;;               2002, 2003, 2004, 2005, 2006, 2007, 2008
-;;               Free Software Foundation, Inc.
+;;   2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+;;   Free Software Foundation, Inc.
 
 ;; Author: Michael D. Prange <prange@erl.mit.edu>
 ;; Maintainer: Glenn Morris <rgm@gnu.org>
 
 ;; Author: Michael D. Prange <prange@erl.mit.edu>
 ;; Maintainer: Glenn Morris <rgm@gnu.org>
@@ -83,8 +83,8 @@ A non-nil value specifies tab-digit style of continuation control.
 A value of nil specifies that continuation lines are marked
 with a character in column 6."
   :type  'boolean
 A value of nil specifies that continuation lines are marked
 with a character in column 6."
   :type  'boolean
+  :safe  'booleanp
   :group 'fortran-indent)
   :group 'fortran-indent)
-(put 'fortran-tab-mode-default 'safe-local-variable 'booleanp)
 
 ;; TODO add more detail of what tab mode is to doc string.
 (defcustom fortran-tab-mode-string
 
 ;; TODO add more detail of what tab mode is to doc string.
 (defcustom fortran-tab-mode-string
@@ -96,34 +96,35 @@ with a character in column 6."
                                           (interactive)
                                           (describe-variable
                                            'fortran-tab-mode-string))))
                                           (interactive)
                                           (describe-variable
                                            'fortran-tab-mode-string))))
-  "String to appear in mode line in TAB format buffers."
+  "String to appear in mode line in TAB format buffers.
+See Info node `(emacs)ForIndent Cont'."
   :type  'string
   :type  'string
+  :risky t
   :group 'fortran-indent)
   :group 'fortran-indent)
-(put 'fortran-tab-mode-string 'risky-local-variable t)
 
 (defcustom fortran-do-indent 3
   "Extra indentation applied to DO blocks."
   :type  'integer
 
 (defcustom fortran-do-indent 3
   "Extra indentation applied to DO blocks."
   :type  'integer
+  :safe  'integerp
   :group 'fortran-indent)
   :group 'fortran-indent)
-(put 'fortran-do-indent 'safe-local-variable 'integerp)
 
 (defcustom fortran-if-indent 3
   "Extra indentation applied to IF, SELECT CASE and WHERE blocks."
   :type  'integer
 
 (defcustom fortran-if-indent 3
   "Extra indentation applied to IF, SELECT CASE and WHERE blocks."
   :type  'integer
+  :safe  'integerp
   :group 'fortran-indent)
   :group 'fortran-indent)
-(put 'fortran-if-indent 'safe-local-variable 'integerp)
 
 (defcustom fortran-structure-indent 3
   "Extra indentation applied to STRUCTURE, UNION, MAP and INTERFACE blocks."
   :type  'integer
 
 (defcustom fortran-structure-indent 3
   "Extra indentation applied to STRUCTURE, UNION, MAP and INTERFACE blocks."
   :type  'integer
+  :safe  'integerp
   :group 'fortran-indent)
   :group 'fortran-indent)
-(put 'fortran-structure-indent 'safe-local-variable 'integerp)
 
 (defcustom fortran-continuation-indent 5
   "Extra indentation applied to continuation lines."
   :type  'integer
 
 (defcustom fortran-continuation-indent 5
   "Extra indentation applied to continuation lines."
   :type  'integer
+  :safe  'integerp
   :group 'fortran-indent)
   :group 'fortran-indent)
-(put 'fortran-continuation-indent 'safe-local-variable 'integerp)
 
 (defcustom fortran-comment-indent-style 'fixed
   "How to indent comments.
 
 (defcustom fortran-comment-indent-style 'fixed
   "How to indent comments.
@@ -134,24 +135,24 @@ nil forces comment lines not to be touched;
 `relative' indents to current Fortran indentation plus
   `fortran-comment-line-extra-indent'."
   :type  '(radio (const :tag "Untouched" nil) (const fixed) (const relative))
 `relative' indents to current Fortran indentation plus
   `fortran-comment-line-extra-indent'."
   :type  '(radio (const :tag "Untouched" nil) (const fixed) (const relative))
+  :safe  (lambda (value) (memq value '(nil fixed relative)))
   :group 'fortran-indent)
   :group 'fortran-indent)
-(put 'fortran-comment-indent 'safe-local-variable
-     (lambda (value) (memq value '(nil fixed relative))))
 
 (defcustom fortran-comment-line-extra-indent 0
   "Amount of extra indentation for text within full-line comments."
   :type  'integer
 
 (defcustom fortran-comment-line-extra-indent 0
   "Amount of extra indentation for text within full-line comments."
   :type  'integer
+  :safe  'integerp
   :group 'fortran-indent
   :group 'fortran-comment)
   :group 'fortran-indent
   :group 'fortran-comment)
-(put 'fortran-comment-line-extra-indent 'safe-local-variable 'integerp)
 
 (defcustom fortran-comment-line-start "C"
   "Delimiter inserted to start new full-line comment.
 
 (defcustom fortran-comment-line-start "C"
   "Delimiter inserted to start new full-line comment.
-You might want to change this to \"*\", for instance."
+You might want to change this to \"*\", for instance; or \"!\" to
+allow trailing comments on a line."
   :version "21.1"
   :type    'string
   :version "21.1"
   :type    'string
+  :safe    'stringp
   :group   'fortran-comment)
   :group   'fortran-comment)
-(put 'fortran-comment-line-start 'safe-local-variable 'stringp)
 
 ;; This used to match preprocessor lines too, but that messes up
 ;; filling and doesn't seem to be necessary.
 
 ;; This used to match preprocessor lines too, but that messes up
 ;; filling and doesn't seem to be necessary.
@@ -160,8 +161,8 @@ You might want to change this to \"*\", for instance."
   "Regexp to match the start of a full-line comment."
   :version "21.1"
   :type    'regexp
   "Regexp to match the start of a full-line comment."
   :version "21.1"
   :type    'regexp
+  :safe    'stringp
   :group   'fortran-comment)
   :group   'fortran-comment)
-(put 'fortran-comment-line-start-skip 'safe-local-variable 'stringp)
 
 (defcustom fortran-directive-re
   "^[ \t]*#.*"
 
 (defcustom fortran-directive-re
   "^[ \t]*#.*"
@@ -170,20 +171,20 @@ The matching text will be fontified with `font-lock-keyword-face'.
 The matching line will be given zero indentation."
   :version "22.1"
   :type    'regexp
 The matching line will be given zero indentation."
   :version "22.1"
   :type    'regexp
+  :safe    'stringp
   :group   'fortran-indent)
   :group   'fortran-indent)
-(put 'fortran-directive-re 'safe-local-variable 'stringp)
 
 (defcustom fortran-minimum-statement-indent-fixed 6
   "Minimum statement indentation for fixed format continuation style."
   :type  'integer
 
 (defcustom fortran-minimum-statement-indent-fixed 6
   "Minimum statement indentation for fixed format continuation style."
   :type  'integer
+  :safe  'integerp
   :group 'fortran-indent)
   :group 'fortran-indent)
-(put 'fortran-minimum-statement-indent-fixed 'safe-local-variable 'integerp)
 
 (defcustom fortran-minimum-statement-indent-tab (max tab-width 6)
   "Minimum statement indentation for TAB format continuation style."
   :type  'integer
 
 (defcustom fortran-minimum-statement-indent-tab (max tab-width 6)
   "Minimum statement indentation for TAB format continuation style."
   :type  'integer
+  :safe  'integerp
   :group 'fortran-indent)
   :group 'fortran-indent)
-(put 'fortran-minimum-statement-indent-tab 'safe-local-variable 'integerp)
 
 ;; Note that this is documented in the v18 manuals as being a string
 ;; of length one rather than a single character.
 
 ;; Note that this is documented in the v18 manuals as being a string
 ;; of length one rather than a single character.
@@ -192,31 +193,29 @@ The matching line will be given zero indentation."
   "Single-character string inserted for Fortran comment indentation.
 Normally a space."
   :type  'string
   "Single-character string inserted for Fortran comment indentation.
 Normally a space."
   :type  'string
+  :safe  (lambda (value) (or (characterp value)
+                             (and (stringp value) (= (length value) 1))))
   :group 'fortran-comment)
   :group 'fortran-comment)
-(put 'fortran-comment-indent-char 'safe-local-variable
-     (lambda (value) (or (characterp value)
-                         (and (stringp value)
-                              (= (length value) 1)))))
 
 (defcustom fortran-line-number-indent 1
   "Maximum indentation for Fortran line numbers.
 5 means right-justify them within their five-column field."
   :type  'integer
 
 (defcustom fortran-line-number-indent 1
   "Maximum indentation for Fortran line numbers.
 5 means right-justify them within their five-column field."
   :type  'integer
+  :safe  'integerp
   :group 'fortran-indent)
   :group 'fortran-indent)
-(put 'fortran-line-number-indent 'safe-local-variable 'integerp)
 
 (defcustom fortran-check-all-num-for-matching-do nil
   "Non-nil causes all numbered lines to be treated as possible DO loop ends."
   :type  'boolean
 
 (defcustom fortran-check-all-num-for-matching-do nil
   "Non-nil causes all numbered lines to be treated as possible DO loop ends."
   :type  'boolean
+  :safe  'booleanp
   :group 'fortran)
   :group 'fortran)
-(put 'fortran-check-all-num-for-matching-do 'safe-local-variable 'booleanp)
 
 (defcustom fortran-blink-matching-if nil
   "Non-nil causes \\[fortran-indent-line] on ENDIF to blink on matching IF.
 Also, from an ENDDO statement blink on matching DO [WHILE] statement."
   :type  'boolean
 
 (defcustom fortran-blink-matching-if nil
   "Non-nil causes \\[fortran-indent-line] on ENDIF to blink on matching IF.
 Also, from an ENDDO statement blink on matching DO [WHILE] statement."
   :type  'boolean
+  :safe  'booleanp
   :group 'fortran)
   :group 'fortran)
-(put 'fortran-blink-matching-if 'safe-local-variable 'booleanp)
 
 (defcustom fortran-continuation-string "$"
   "Single-character string used for Fortran continuation lines.
 
 (defcustom fortran-continuation-string "$"
   "Single-character string used for Fortran continuation lines.
@@ -224,25 +223,23 @@ In fixed format continuation style, this character is inserted in
 column 6 by \\[fortran-split-line] to begin a continuation line.
 Also, if \\[fortran-indent-line] finds this at the beginning of a
 line, it will convert the line into a continuation line of the
 column 6 by \\[fortran-split-line] to begin a continuation line.
 Also, if \\[fortran-indent-line] finds this at the beginning of a
 line, it will convert the line into a continuation line of the
-appropriate style. Normally $."
+appropriate style.  Normally \"$\"."
   :type  'string
   :type  'string
+  :safe  (lambda (value) (and (stringp value) (= (length value) 1)))
   :group 'fortran)
   :group 'fortran)
-(put 'fortran-continuation-string 'safe-local-variable
-     (lambda (value) (and (stringp value)
-                          (= (length value) 1))))
 
 (defcustom fortran-comment-region "c$$$"
   "String inserted by \\[fortran-comment-region] at start of each \
 line in region."
   :type  'string
 
 (defcustom fortran-comment-region "c$$$"
   "String inserted by \\[fortran-comment-region] at start of each \
 line in region."
   :type  'string
+  :safe  'stringp
   :group 'fortran-comment)
   :group 'fortran-comment)
-(put 'fortran-comment-region 'safe-local-variable 'stringp)
 
 (defcustom fortran-electric-line-number t
   "Non-nil causes line numbers to be moved to the correct column as typed."
   :type  'boolean
 
 (defcustom fortran-electric-line-number t
   "Non-nil causes line numbers to be moved to the correct column as typed."
   :type  'boolean
+  :safe  'booleanp
   :group 'fortran)
   :group 'fortran)
-(put 'fortran-electric-line-number 'safe-local-variable 'booleanp)
 
 ;; TODO use fortran-line-length, somehow.
 (defcustom fortran-column-ruler-fixed
 
 ;; TODO use fortran-line-length, somehow.
 (defcustom fortran-column-ruler-fixed
@@ -254,8 +251,8 @@ line in region."
 This variable is used in fixed format mode.
 See the variable `fortran-column-ruler-tab' for TAB format mode."
   :type  'string
 This variable is used in fixed format mode.
 See the variable `fortran-column-ruler-tab' for TAB format mode."
   :type  'string
+  :safe  'stringp
   :group 'fortran)
   :group 'fortran)
-(put 'fortran-column-ruler-fixed 'safe-local-variable 'stringp)
 
 ;; TODO use fortran-line-length, somehow.
 (defcustom fortran-column-ruler-tab
 
 ;; TODO use fortran-line-length, somehow.
 (defcustom fortran-column-ruler-tab
@@ -267,21 +264,21 @@ See the variable `fortran-column-ruler-tab' for TAB format mode."
 This variable is used in TAB format mode.
 See the variable `fortran-column-ruler-fixed' for fixed format mode."
   :type  'string
 This variable is used in TAB format mode.
 See the variable `fortran-column-ruler-fixed' for fixed format mode."
   :type  'string
+  :safe  'stringp
   :group 'fortran)
   :group 'fortran)
-(put 'fortran-column-ruler-tab 'safe-local-variable 'stringp)
 
 (defcustom fortran-analyze-depth 100
   "Number of lines to scan to identify fixed or TAB format style."
   :type  'integer
 
 (defcustom fortran-analyze-depth 100
   "Number of lines to scan to identify fixed or TAB format style."
   :type  'integer
+  :safe  'integerp
   :group 'fortran)
   :group 'fortran)
-(put 'fortran-analyze-depth 'safe-local-variable 'integerp)
 
 (defcustom fortran-break-before-delimiters t
   "Non-nil causes filling to break lines before delimiters.
 Delimiters are characters matching the regexp `fortran-break-delimiters-re'."
   :type  'boolean
 
 (defcustom fortran-break-before-delimiters t
   "Non-nil causes filling to break lines before delimiters.
 Delimiters are characters matching the regexp `fortran-break-delimiters-re'."
   :type  'boolean
+  :safe  'booleanp
   :group 'fortran)
   :group 'fortran)
-(put 'fortran-break-before-delimiters 'safe-local-variable 'booleanp)
 
 ;; TODO 0 as no-limit, as per g77.
 (defcustom fortran-line-length 72
 
 ;; TODO 0 as no-limit, as per g77.
 (defcustom fortran-line-length 72
@@ -294,6 +291,7 @@ buffers and the default) or the function
 buffer).  This corresponds to the g77 compiler option
 `-ffixed-line-length-N'."
   :type 'integer
 buffer).  This corresponds to the g77 compiler option
 `-ffixed-line-length-N'."
   :type 'integer
+  :safe 'integerp
   :initialize 'custom-initialize-default
   :set (lambda (symbol value)
          ;; Do all fortran buffers, and the default.
   :initialize 'custom-initialize-default
   :set (lambda (symbol value)
          ;; Do all fortran buffers, and the default.
@@ -301,7 +299,6 @@ buffer).  This corresponds to the g77 compiler option
   :version "23.1"
   :group 'fortran)
 
   :version "23.1"
   :group 'fortran)
 
-(put 'fortran-line-length 'safe-local-variable 'integerp)
 (make-variable-buffer-local 'fortran-line-length)
 
 (defcustom fortran-mode-hook nil
 (make-variable-buffer-local 'fortran-line-length)
 
 (defcustom fortran-mode-hook nil
@@ -328,6 +325,13 @@ characters long.")
 (defconst fortran-if-start-re "\\(\\(\\sw\\|\\s_\\)+:[ \t]*\\)?if[ \t]*("
   "Regexp matching the start of an IF statement.")
 
 (defconst fortran-if-start-re "\\(\\(\\sw\\|\\s_\\)+:[ \t]*\\)?if[ \t]*("
   "Regexp matching the start of an IF statement.")
 
+;; Note fortran-current-defun uses the subgroups.
+(defconst fortran-start-prog-re
+  "^[ \t]*\\(program\\|subroutine\\|function\
+\\|[ \ta-z0-9*()]*[ \t]+function\\|\
+\\(block[ \t]*data\\)\\)"
+  "Regexp matching the start of a subprogram, from the line start.")
+
 (defconst fortran-end-prog-re1
   "end\
 \\([ \t]*\\(program\\|subroutine\\|function\\|block[ \t]*data\\)\\>\
 (defconst fortran-end-prog-re1
   "end\
 \\([ \t]*\\(program\\|subroutine\\|function\\|block[ \t]*data\\)\\>\
@@ -399,6 +403,28 @@ program\\|subroutine\\)\\>[ \t]*\\(\\sw+\\)?"
            '("^ *\\([0-9]+\\)" . font-lock-constant-face)))
   "Medium level highlighting for Fortran mode.")
 
            '("^ *\\([0-9]+\\)" . font-lock-constant-face)))
   "Medium level highlighting for Fortran mode.")
 
+;; See bug#1385. Never really looked into _why_ this matters...
+(defun fortran-match-and-skip-declaration (limit)
+  "Like `font-lock-match-c-style-declaration-item-and-skip-to-next'.
+The only difference is, it returns t in a case when the default returns nil."
+  (when (looking-at "[ \n\t*]*\\(\\sw+\\)[ \t\n]*\\(((?\\)?")
+    (when (and (match-end 2) (> (- (match-end 2) (match-beginning 2)) 1))
+      (let ((pos (point)))
+       (skip-chars-backward " \t\n")
+       (skip-syntax-backward "w")
+       (unless (looking-at "\\(\\sw+\\)[ \t\n]*\\sw+[ \t\n]*\\(((?\\)?")
+         (goto-char pos)
+         (looking-at "[ \n\t*]*\\(\\sw+\\)[ \t\n]*\\(((?\\)?"))))
+    (save-match-data
+      (condition-case nil
+         (save-restriction
+           (narrow-to-region (point-min) limit)
+           (goto-char (match-end 1))
+           (while (not (looking-at "[ \t\n]*\\(\\(,\\)\\|;\\|\\'\\)"))
+             (goto-char (or (scan-sexps (point) 1) (point-max))))
+            (goto-char (match-end 2)))
+       (error t)))))
+
 (defvar fortran-font-lock-keywords-3
   (append
    fortran-font-lock-keywords-1
 (defvar fortran-font-lock-keywords-3
   (append
    fortran-font-lock-keywords-1
@@ -408,7 +434,7 @@ program\\|subroutine\\)\\>[ \t]*\\(\\sw+\\)?"
           ;; Type specifier.
           '(1 font-lock-type-face)
           ;; Declaration item (or just /.../ block name).
           ;; Type specifier.
           '(1 font-lock-type-face)
           ;; Declaration item (or just /.../ block name).
-          `(font-lock-match-c-style-declaration-item-and-skip-to-next
+          `(fortran-match-and-skip-declaration
             ;; Start after any *(...) expression.
             (condition-case nil
                 (and (match-beginning ,(1+ (regexp-opt-depth
             ;; Start after any *(...) expression.
             (condition-case nil
                 (and (match-beginning ,(1+ (regexp-opt-depth
@@ -462,13 +488,22 @@ Consists of level 3 plus all other intrinsics not already highlighted.")
 ;; (We can do so for F90-style).  Therefore an unmatched quote in a
 ;; standard comment will throw fontification off on the wrong track.
 ;; So we do syntactic fontification with regexps.
 ;; (We can do so for F90-style).  Therefore an unmatched quote in a
 ;; standard comment will throw fontification off on the wrong track.
 ;; So we do syntactic fontification with regexps.
-(defun fortran-font-lock-syntactic-keywords ()
-  "Return a value for `font-lock-syntactic-keywords' in Fortran mode.
-This varies according to the value of `fortran-line-length'.
+(defun fortran-make-syntax-propertize-function (line-length)
+  "Return a value for `syntax-propertize-function' in Fortran mode.
+This varies according to the value of LINE-LENGTH.
 This is used to fontify fixed-format Fortran comments."
 This is used to fontify fixed-format Fortran comments."
-  `(("^[cd\\*]" 0 (11))
-    (,(format "^[^cd\\*\t\n].\\{%d\\}\\([^\n]+\\)" (1- fortran-line-length))
-     1 (11))))
+  ;; This results in a non-byte-compiled function.  We could pass it through
+  ;; `byte-compile', but simple benchmarks indicate that it's probably not
+  ;; worth the trouble (about Â½% of slow down).
+  (eval                         ;I hate `eval', but it's hard to avoid it here.
+   `(syntax-propertize-rules
+     ("^[cd\\*]" (0 "<"))
+     ;; We mark all chars after line-length as "comment-start", rather than
+     ;; just the first one.  This is so that a closing ' that's past the
+     ;; line-length will indeed be ignored (and will result in a string that
+     ;; leaks into subsequent lines).
+     ((format "^[^cd\\*\t\n].\\{%d\\}\\(.+\\)" (1- line-length))
+      (1 "<")))))
 
 (defvar fortran-font-lock-keywords fortran-font-lock-keywords-1
   "Default expressions to highlight in Fortran mode.")
 
 (defvar fortran-font-lock-keywords fortran-font-lock-keywords-1
   "Default expressions to highlight in Fortran mode.")
@@ -617,14 +652,21 @@ Used in the Fortran entry in `hs-special-modes-alist'.")
 
     (easy-menu-define fortran-menu map "Menu for Fortran mode."
       `("Fortran"
 
     (easy-menu-define fortran-menu map "Menu for Fortran mode."
       `("Fortran"
-        ["Manual" (info "(emacs)Fortran")]
+        ["Manual" (info "(emacs)Fortran") :active t
+         :help "Read the Emacs manual chapter on Fortran mode"]
         ("Customization"
          ,(custom-menu-create 'fortran)
         ("Customization"
          ,(custom-menu-create 'fortran)
-         ["Set"  Custom-set t]
-         ["Save" Custom-save t]
-         ["Reset to Current" Custom-reset-current t]
-         ["Reset to Saved"   Custom-reset-saved t]
-         ["Reset to Standard Settings" Custom-reset-standard t]
+         ;; FIXME useless?
+         ["Set"  Custom-set :active t
+          :help "Set current value of all edited settings in the buffer"]
+         ["Save" Custom-save :active t
+          :help "Set and save all edited settings"]
+         ["Reset to Current" Custom-reset-current :active t
+          :help "Reset all edited settings to current"]
+         ["Reset to Saved" Custom-reset-saved :active t
+          :help "Reset all edited or set settings to saved"]
+         ["Reset to Standard Settings" Custom-reset-standard :active t
+          :help "Erase all cusomizations in buffer"]
          )
         "--"
         ["Comment Region" fortran-comment-region mark-active]
          )
         "--"
         ["Comment Region" fortran-comment-region mark-active]
@@ -634,9 +676,12 @@ Used in the Fortran entry in `hs-special-modes-alist'.")
         ["Indent Region"     indent-region mark-active]
         ["Indent Subprogram" fortran-indent-subprogram t]
         "--"
         ["Indent Region"     indent-region mark-active]
         ["Indent Subprogram" fortran-indent-subprogram t]
         "--"
-        ["Beginning of Subprogram" fortran-beginning-of-subprogram t]
-        ["End of Subprogram"       fortran-end-of-subprogram       t]
+        ["Beginning of Subprogram" fortran-beginning-of-subprogram :active t
+         :help "Move point to the start of the current subprogram"]
+        ["End of Subprogram" fortran-end-of-subprogram :active t
+         :help "Move point to the end of the current subprogram"]
         ("Mark"
         ("Mark"
+         :help "Mark a region of code"
          ["Subprogram" mark-defun      t]
          ["IF Block"   fortran-mark-if t]
          ["DO Block"   fortran-mark-do t]
          ["Subprogram" mark-defun      t]
          ["IF Block"   fortran-mark-if t]
          ["DO Block"   fortran-mark-do t]
@@ -644,26 +689,34 @@ Used in the Fortran entry in `hs-special-modes-alist'.")
         ["Narrow to Subprogram" narrow-to-defun t]
         ["Widen" widen t]
         "--"
         ["Narrow to Subprogram" narrow-to-defun t]
         ["Widen" widen t]
         "--"
-        ["Temporary column ruler" fortran-column-ruler  t]
+        ["Temporary Column Ruler" fortran-column-ruler :active t
+         :help "Briefly display Fortran column numbers"]
         ;; May not be '72', depending on fortran-line-length, but this
         ;; seems ok for a menu item.
         ;; May not be '72', depending on fortran-line-length, but this
         ;; seems ok for a menu item.
-        ["72-column window"       fortran-window-create t]
+        ["72-column Window" fortran-window-create :active t
+         :help "Set window width to Fortran line length"]
         ["Full Width Window"
          (enlarge-window-horizontally (- (frame-width) (window-width)))
         ["Full Width Window"
          (enlarge-window-horizontally (- (frame-width) (window-width)))
-         (not (window-full-width-p))]
-        ["Momentary 72-column window" fortran-window-create-momentarily t]
+         :active (not (window-full-width-p))
+         :help "Make window full width"]
+        ["Momentary 72-Column Window" fortran-window-create-momentarily
+         :active t :help "Briefly set window width to Fortran line length"]
         "--"
         "--"
-        ["Break Line at Point"    fortran-split-line t]
-        ["Join Line"              fortran-join-line  t]
-        ["Fill Statement/Comment" fill-paragraph     t]
+        ["Break Line at Point" fortran-split-line :active t
+         :help "Break the current line at point"]
+        ["Join Line" fortran-join-line :active t
+         :help "Join the current line to the previous one"]
+        ["Fill Statement/Comment" fill-paragraph t]
         "--"
         "--"
-        ["Toggle auto-fill"   auto-fill-mode :selected auto-fill-function
-         :style toggle]
-        ["Toggle abbrev-mode" abbrev-mode    :selected abbrev-mode
-         :style toggle]
-        ["Add imenu Menu" imenu-add-menubar-index
+        ["Toggle Auto Fill" auto-fill-mode :selected auto-fill-function
+         :style toggle
+         :help "Automatically fill text while typing in this buffer"]
+        ["Toggle Abbrev Mode" abbrev-mode :selected abbrev-mode
+         :style toggle :help "Expand abbreviations while typing in this buffer"]
+        ["Add Imenu Menu" imenu-add-menubar-index
          :active   (not (lookup-key (current-local-map) [menu-bar index]))
          :active   (not (lookup-key (current-local-map) [menu-bar index]))
-         :included (fboundp 'imenu-add-to-menubar)]))
+         :included (fboundp 'imenu-add-to-menubar)
+         :help "Add an index menu to the menu-bar"]))
     map)
   "Keymap used in Fortran mode.")
 
     map)
   "Keymap used in Fortran mode.")
 
@@ -734,7 +787,7 @@ Used in the Fortran entry in `hs-special-modes-alist'.")
 
 \f
 ;;;###autoload
 
 \f
 ;;;###autoload
-(defun fortran-mode ()
+(define-derived-mode fortran-mode prog-mode "Fortran"
   "Major mode for editing Fortran code in fixed format.
 For free format code, use `f90-mode'.
 
   "Major mode for editing Fortran code in fixed format.
 For free format code, use `f90-mode'.
 
@@ -804,13 +857,9 @@ Variables controlling indentation style and extra features:
 
 Turning on Fortran mode calls the value of the variable `fortran-mode-hook'
 with no args, if that value is non-nil."
 
 Turning on Fortran mode calls the value of the variable `fortran-mode-hook'
 with no args, if that value is non-nil."
-  (interactive)
-  (kill-all-local-variables)
-  (setq major-mode 'fortran-mode
-        mode-name "Fortran"
-        local-abbrev-table fortran-mode-abbrev-table)
-  (set-syntax-table fortran-mode-syntax-table)
-  (use-local-map fortran-mode-map)
+  :group 'fortran
+  :syntax-table fortran-mode-syntax-table
+  :abbrev-table fortran-mode-abbrev-table
   (set (make-local-variable 'indent-line-function) 'fortran-indent-line)
   (set (make-local-variable 'indent-region-function)
        (lambda (start end)
   (set (make-local-variable 'indent-line-function) 'fortran-indent-line)
   (set (make-local-variable 'indent-region-function)
        (lambda (start end)
@@ -847,9 +896,9 @@ with no args, if that value is non-nil."
           fortran-font-lock-keywords-3
           fortran-font-lock-keywords-4)
          nil t ((?/ . "$/") ("_$" . "w"))
           fortran-font-lock-keywords-3
           fortran-font-lock-keywords-4)
          nil t ((?/ . "$/") ("_$" . "w"))
-         fortran-beginning-of-subprogram
-         (font-lock-syntactic-keywords
-          . fortran-font-lock-syntactic-keywords)))
+         fortran-beginning-of-subprogram))
+  (set (make-local-variable 'syntax-propertize-function)
+       (fortran-make-syntax-propertize-function fortran-line-length))
   (set (make-local-variable 'imenu-case-fold-search) t)
   (set (make-local-variable 'imenu-generic-expression)
        fortran-imenu-generic-expression)
   (set (make-local-variable 'imenu-case-fold-search) t)
   (set (make-local-variable 'imenu-generic-expression)
        fortran-imenu-generic-expression)
@@ -862,33 +911,37 @@ with no args, if that value is non-nil."
        #'fortran-current-defun)
   (set (make-local-variable 'dabbrev-case-fold-search) 'case-fold-search)
   (set (make-local-variable 'gud-find-expr-function) 'fortran-gud-find-expr)
        #'fortran-current-defun)
   (set (make-local-variable 'dabbrev-case-fold-search) 'case-fold-search)
   (set (make-local-variable 'gud-find-expr-function) 'fortran-gud-find-expr)
-  (add-hook 'hack-local-variables-hook 'fortran-hack-local-variables nil t)
-  (run-mode-hooks 'fortran-mode-hook))
+  (add-hook 'hack-local-variables-hook 'fortran-hack-local-variables nil t))
 
 \f
 (defun fortran-line-length (nchars &optional global)
   "Set the length of fixed-form Fortran lines to NCHARS.
 This normally only affects the current buffer, which must be in
 Fortran mode.  If the optional argument GLOBAL is non-nil, it
 
 \f
 (defun fortran-line-length (nchars &optional global)
   "Set the length of fixed-form Fortran lines to NCHARS.
 This normally only affects the current buffer, which must be in
 Fortran mode.  If the optional argument GLOBAL is non-nil, it
-affects all Fortran buffers, and also the default."
-  (interactive "p")
-  (let (new)
-    (mapc (lambda (buff)
-            (with-current-buffer buff
-              (when (eq major-mode 'fortran-mode)
-                (setq fortran-line-length nchars
-                      fill-column fortran-line-length
-                      new (fortran-font-lock-syntactic-keywords))
-                ;; Refontify only if necessary.
-                (unless (equal new font-lock-syntactic-keywords)
-                  (setq font-lock-syntactic-keywords
-                        (fortran-font-lock-syntactic-keywords))
-                  (if font-lock-mode (font-lock-mode 1))))))
+affects all Fortran buffers, and also the default.
+If a numeric prefix argument is specified, it will be used as NCHARS,
+otherwise is a non-numeric prefix arg is specified, the length will be
+provided via the minibuffer, and otherwise the current column is used."
+  (interactive
+   (list (cond
+          ((numberp current-prefix-arg) current-prefix-arg)
+          (current-prefix-arg
+           (read-number "Line length: " (default-value 'fortran-line-length)))
+          (t (current-column)))))
+  (dolist (buff (if global
+                    (buffer-list)
+                  (list (current-buffer))))
+    (with-current-buffer buff
+      (when (derived-mode-p 'fortran-mode)
+        (unless (eq fortran-line-length nchars)
+          (setq fortran-line-length nchars
+                fill-column fortran-line-length
+                syntax-propertize-function
+                (fortran-make-syntax-propertize-function nchars))
+          (syntax-ppss-flush-cache (point-min))
+          (if font-lock-mode (font-lock-mode 1))))))
           (if global
           (if global
-              (buffer-list)
-            (list (current-buffer))))
-    (if global
-        (setq-default fortran-line-length nchars))))
+      (setq-default fortran-line-length nchars)))
 
 (defun fortran-hack-local-variables ()
   "Fortran mode adds this to `hack-local-variables-hook'."
 
 (defun fortran-hack-local-variables ()
   "Fortran mode adds this to `hack-local-variables-hook'."
@@ -986,7 +1039,7 @@ With non-nil ARG, uncomments the region."
   "Typing ;\\[help-command] or ;? lists all the Fortran abbrevs.
 Any other key combination is executed normally."
   (interactive "*")
   "Typing ;\\[help-command] or ;? lists all the Fortran abbrevs.
 Any other key combination is executed normally."
   (interactive "*")
-  (insert last-command-char)
+  (insert last-command-event)
   (let* ((event (if (fboundp 'next-command-event) ; XEmacs
                     (next-command-event)
                   (read-event)))
   (let* ((event (if (fboundp 'next-command-event) ; XEmacs
                     (next-command-event)
                   (read-event)))
@@ -1147,7 +1200,7 @@ Auto-indent does not happen if a numeric ARG is used."
             (looking-at "[0-9]"))       ; within a line number
         (self-insert-command (prefix-numeric-value arg))
       (skip-chars-backward " \t")
             (looking-at "[0-9]"))       ; within a line number
         (self-insert-command (prefix-numeric-value arg))
       (skip-chars-backward " \t")
-      (insert last-command-char)
+      (insert last-command-event)
       (fortran-indent-line))))
 
 \f
       (fortran-indent-line))))
 
 \f
@@ -1162,37 +1215,47 @@ Auto-indent does not happen if a numeric ARG is used."
                                          (+ fortran-line-length
                                             (line-beginning-position)))))))
 
                                          (+ fortran-line-length
                                             (line-beginning-position)))))))
 
-;; Note that you can't just check backwards for `subroutine' &c in
-;; case of un-marked main programs not at the start of the file.
+;; This is more complex than first expected because the beginning of a
+;; main program may be implicit (ie not marked by a PROGRAM statement).
+;; This would be fine (we could just go to bob in the absence of a match),
+;; except it need not even be the first subprogram in the file (eg it
+;; could follow a subroutine).  Hence we have to search for END
+;; statements instead.
+;; cf fortran-beginning-of-block, f90-beginning-of-subprogram
+;; Note that unlike the latter, we don't have to worry about nested
+;; subprograms (?).
+;; FIXME push-mark?
 (defun fortran-beginning-of-subprogram ()
   "Move point to the beginning of the current Fortran subprogram."
   (interactive)
 (defun fortran-beginning-of-subprogram ()
   "Move point to the beginning of the current Fortran subprogram."
   (interactive)
-  (save-match-data
-    (let ((case-fold-search t))
-      (beginning-of-line -1)
-      (if (catch 'ok
-            (while (re-search-backward fortran-end-prog-re nil 'move)
-              (if (fortran-check-end-prog-re)
-                  (throw 'ok t))))
-          (forward-line)))))
-
+  (let ((case-fold-search t))
+    ;; If called already at the start of subprogram, go to the previous.
+    (beginning-of-line (if (bolp) 0 1))
+    (save-match-data
+      (or (looking-at fortran-start-prog-re)
+          ;; This leaves us at bob if before the first subprogram.
+          (eq (fortran-previous-statement) 'first-statement)
+          (if (or (catch 'ok
+                    (while (re-search-backward fortran-end-prog-re nil 'move)
+                      (if (fortran-check-end-prog-re) (throw 'ok t))))
+                  ;; If the search failed, must be at bob.
+                  ;; First code line is the start of the subprogram.
+                  ;; FIXME use a more rigorous test, cf fortran-next-statement?
+                  ;; Though that needs to handle continuations too.
+                  (not (looking-at "^\\([ \t]*[0-9]\\|[ \t]+[^!#]\\)")))
+              (fortran-next-statement))))))
+
+;; This is simpler than f-beginning-of-s because the end of a
+;; subprogram is never implicit.
 (defun fortran-end-of-subprogram ()
   "Move point to the end of the current Fortran subprogram."
   (interactive)
 (defun fortran-end-of-subprogram ()
   "Move point to the end of the current Fortran subprogram."
   (interactive)
-  (save-match-data
-    (let ((case-fold-search t))
-      (if (save-excursion               ; on END
-            (beginning-of-line)
-            (and (looking-at fortran-end-prog-re)
-                 (fortran-check-end-prog-re)))
-          (forward-line)
-        (beginning-of-line 2)
-        (when (catch 'ok
-                (while (re-search-forward fortran-end-prog-re nil 'move)
-                  (if (fortran-check-end-prog-re)
-                      (throw 'ok t))))
-          (goto-char (match-beginning 0))
-          (forward-line))))))
+  (let ((case-fold-search t))
+    (beginning-of-line)
+    (save-match-data
+      (while (and (re-search-forward fortran-end-prog-re nil 'move)
+                  (not (fortran-check-end-prog-re))))
+      (forward-line))))
 
 (defun fortran-previous-statement ()
   "Move point to beginning of the previous Fortran statement.
 
 (defun fortran-previous-statement ()
   "Move point to beginning of the previous Fortran statement.
@@ -1263,7 +1326,7 @@ If NUM is negative, go backward to the start of a block.  Does
 not check for consistency of block types.  Interactively, pushes
 mark before moving point."
   (interactive "p")
 not check for consistency of block types.  Interactively, pushes
 mark before moving point."
   (interactive "p")
-  (if (interactive-p) (push-mark (point) t))
+  (if (called-interactively-p 'any) (push-mark (point) t))
   (and num (< num 0) (fortran-beginning-of-block (- num)))
   (let ((case-fold-search t)
         (count (or num 1)))
   (and num (< num 0) (fortran-beginning-of-block (- num)))
   (let ((case-fold-search t)
         (count (or num 1)))
@@ -1296,7 +1359,7 @@ blocks.  If NUM is negative, go forward to the end of a block.
 Does not check for consistency of block types.  Interactively,
 pushes mark before moving point."
   (interactive "p")
 Does not check for consistency of block types.  Interactively,
 pushes mark before moving point."
   (interactive "p")
-  (if (interactive-p) (push-mark (point) t))
+  (if (called-interactively-p 'any) (push-mark (point) t))
   (and num (< num 0) (fortran-end-of-block (- num)))
   (let ((case-fold-search t)
         (count (or num 1)))
   (and num (< num 0) (fortran-end-of-block (- num)))
   (let ((case-fold-search t)
         (count (or num 1)))
@@ -2117,19 +2180,16 @@ arg DO-SPACE prevents stripping the whitespace."
       (replace-match "" nil nil nil 1)
       (unless do-space (delete-horizontal-space)))))
 
       (replace-match "" nil nil nil 1)
       (unless do-space (delete-horizontal-space)))))
 
-;; This code used to live in add-log.el, but this is a better place
-;; for it.
+;; This code used to live in add-log.el, but this is a better place for it.
 (defun fortran-current-defun ()
   "Function to use for `add-log-current-defun-function' in Fortran mode."
   (save-excursion
     ;; We must be inside function body for this to work.
     (fortran-beginning-of-subprogram)
 (defun fortran-current-defun ()
   "Function to use for `add-log-current-defun-function' in Fortran mode."
   (save-excursion
     ;; We must be inside function body for this to work.
     (fortran-beginning-of-subprogram)
-    (let ((case-fold-search t))         ; case-insensitive
+    (let ((case-fold-search t))
       ;; Search for fortran subprogram start.
       (if (re-search-forward
       ;; Search for fortran subprogram start.
       (if (re-search-forward
-           (concat "^[ \t]*\\(program\\|subroutine\\|function"
-                   "\\|[ \ta-z0-9*()]*[ \t]+function\\|"
-                   "\\(block[ \t]*data\\)\\)")
+           fortran-start-prog-re
            (save-excursion (fortran-end-of-subprogram)
                            (point))
            t)
            (save-excursion (fortran-end-of-subprogram)
                            (point))
            t)