X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/1a06eabd17a8eff0635bc7058349f7c22057864c..f687a8793d0135bf14f64904709b8cd178c1697d:/lisp/progmodes/fortran.el diff --git a/lisp/progmodes/fortran.el b/lisp/progmodes/fortran.el index aad14d232c..bd0d0aad00 100644 --- a/lisp/progmodes/fortran.el +++ b/lisp/progmodes/fortran.el @@ -1,80 +1,16 @@ -;;; fortran.el --- Fortran mode for GNU Emacs (version 1.28.3, July 15, 1991) - -;;; Copyright (c) 1991 Free Software Foundation, Inc. -;;; Written by Michael D. Prange (prange@erl.mit.edu) -;;; Maintained (as of version 1.28) by Stephen A. Wood (saw@hallc1.cebaf.gov) -;;; This version is an update of version 1.21 (Oct 1, 1985). -;;; Updated by Stephen A. Wood (saw@hallc1.cebaf.gov) to use tab format -;;; continuation control and indentation. (Digit after TAB to signify -;;; continuation line. This version also incorporates suggestions from -;;; Richard Stallman, and the ideas in two previous unpublished versions of -;;; fortran .el (version 1.21.1 (from gildea@expo.lcs.mit.edu) and 1.27 -;;; (prange@erl.mit.edu).) - -;;; Notes to fortran-mode version 1.28 -;;; 1. Fortran mode can support either fixed format or tab format. Fixed -;;; format is where statements start in column 6 (first column is 0) -;;; and continuation lines are denoted by a character in column 5. -;;; In tab mode, statements follow a tab character. Continuation lines -;;; are where the first character on a line is a tab and the second is -;;; a digit from 1 to 9. -;;; 2. When fortran mode is called, the buffer is analyzed to determine what -;;; kind of formating is used. Starting at the top of the file, lines -;;; are scanned until a line is found that begins with a tab or 6 spaces. -;;; The mode for that buffer is then set to either tab or fixed format -;;; based on that line. If no line starting with a tab or 6 spaces is -;;; found before the end of the buffer or in the first 100 lines, the -;;; mode is set from the variable `fortran-tab-mode-default'. t is tab -;;; mode, nil is fixed format mode. By default, fixed format mode is used. -;;; To use tabbing mode as the default, put the following line in .emacs -;;; (setq fortran-tab-mode-default t) -;;; This line should not be in the hook since the hook is called only -;;; after the file is analyzed. -;;; To force a particular mode independent of the analysis, attach -;;; (fortran-tab-mode t) or (fortran-tab-mode nil) -;;; to fortran-mode-hook. -;;; 3. The command `fortran-tab-mode' will toggle between fixed and tab -;;; formatting modes. The file will not automatically be reformatted, -;;; but either `indent-region' or `fortran-indent-subprogram' can be -;;; used to reformat portions of the file. -;;; 4. Several abbreviations have been added. Abbreviation mode is turned -;;; on by default. -;;; 5. The routine fortran-blink-matching if has been incorporated (from -;;; experimental version 1.27). If the variable of the same name is set -;;; to t, the the matching if statement is blinked whenever an endif -;;; line is indented. -;;; 6. C-c C-w is now bound to fortran-window-create-momentarily (from -;;; experimental version 1.27.) -;;; 7. LFD is now bound to fortran-reindent-then-newline-and-indent. -;;; 8. fortran-continuation-string (was fortran-continuation-char) is now -;;; a string rather than a character. -;;; 9. Fixed a bug from 1.21 that gave max-lisp-eval-depth exceeded when -;;; Comments lines had !'s in them. -;;; 10. DEC fortran recognizes a & in the first column as a continuation. -;;; character. This mode does not recognize the & as a continuation -;;; character. -;;; 11. fortran-blink-matching-if still is in effect when indenting a region. -;;; Is this a desirable effect? (It looks kind of neat) -;;; 12. If you strike a digit and there are exactly 5 characters, all spaces -;;; to the left of the point, the digit will be inserted in place to -;;; serve as a continuation line marker. Similarly, if the only thing to -;;; the left of the point is a single tab, and the last command issued -;;; was neither fortran-indent-line (TAB) or fortran-reindent-then-newline- -;;; and-indent (LFD), the digit is inserted as a tab format style -;;; continuation character. -;;; 13. Both modes should usually work with tab-width set to other than 8. -;;; However, in tab-mode, if tab-width is less than 6, the column number -;;; for the minimum indentation is set to 6 so that all line numbers will -;;; have have a tab after them. This may be a bit ugly, but why would -;;; you want to use a tab-width other than 8 anyway? -;;; 14. When in tab mode, the fortran column ruler will not be correct if -;;; tab-width is not 8. +;;; fortran.el --- Fortran mode for GNU Emacs + +;; Copyright (c) 1986, 93, 94, 95, 97, 98, 1999 Free Software Foundation, Inc. + +;; Author: Michael D. Prange +;; Maintainer: Dave Love +;; Keywords: languages ;; 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, @@ -83,100 +19,213 @@ ;; 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: + +;; This mode is documented in the Emacs manual. +;; +;; Note that it is for editing Fortran77 or Fortran90 fixed source +;; form. For editing Fortran 90 free format source, use `f90-mode' +;; (f90.el). + +;;; History: + +;; Fortran mode was upgraded by Stephen A. Wood (saw@cebaf.gov). + +;; We acknowledge many contributions and valuable suggestions by +;; Lawrence R. Dodd, Ralf Fassel, Ralph Finch, Stephen Gildea, +;; Dr. Anil Gokhale, Ulrich Mueller, Mark Neale, Eric Prestemon, +;; Gary Sabot and Richard Stallman. + +;;; Code: + +;; Todo: -;;; Author acknowledges help from Stephen Gildea +;; * Tidy it all up! (including renaming non-`fortran' prefixed +;; functions). +;; * Implement insertion and removal of statement continuations in +;; mixed f77/f90 style, with the first `&' past column 72 and the +;; second in column 6. +;; * Support any other extensions to f77 grokked by GNU Fortran. +;; * Change fontification to use font-lock-syntactic-keywords for +;; fixed-form comments. (Done, but doesn't work properly with +;; lazy-lock in pre-20.4.) + +(require 'easymenu) + +(defgroup fortran nil + "Fortran mode for Emacs" + :link '(custom-manual "(emacs)Fortran") + :group 'languages) + +(defgroup fortran-indent nil + "Indentation variables in Fortran mode" + :prefix "fortran-" + :group 'fortran) + +(defgroup fortran-comment nil + "Comment-handling variables in Fortran mode" + :prefix "fortran-" + :group 'fortran) -;;; Bugs to bug-fortran-mode@erl.mit.edu ;;;###autoload -(defvar fortran-tab-mode-default nil - "*Default tabbing/carriage control style for empty files in fortran mode. -t indicates that tab-digit style of continuation control will be used. -nil indicates that continuation lines are marked with a character in -column 6.") +(defcustom fortran-tab-mode-default nil + "*Default tabbing/carriage control style for empty files in Fortran mode. +A value of t 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 + :group 'fortran-indent) -(defvar fortran-do-indent 3 - "*Extra indentation applied to `do' blocks.") +;; Buffer local, used to display mode line. +(defcustom fortran-tab-mode-string nil + "String to appear in mode line when TAB format mode is on." + :type '(choice (const nil) string) + :group 'fortran-indent) +(make-variable-buffer-local 'fortran-tab-mode-string) -(defvar fortran-if-indent 3 - "*Extra indentation applied to `if' blocks.") +(defcustom fortran-do-indent 3 + "*Extra indentation applied to DO blocks." + :type 'integer + :group 'fortran-indent) -(defvar fortran-continuation-indent 5 - "*Extra indentation applied to `continuation' lines.") +(defcustom fortran-if-indent 3 + "*Extra indentation applied to IF blocks." + :type 'integer + :group 'fortran-indent) -(defvar fortran-comment-indent-style 'fixed - "*nil forces comment lines not to be touched, -'fixed produces fixed comment indentation to comment-column, -and 'relative indents to current fortran indentation plus comment-column.") +(defcustom fortran-structure-indent 3 + "*Extra indentation applied to STRUCTURE, UNION, MAP and INTERFACE blocks." + :type 'integer + :group 'fortran-indent) -(defvar fortran-comment-line-column 6 - "*Indentation for text in comment lines.") +(defcustom fortran-continuation-indent 5 + "*Extra indentation applied to Fortran continuation lines." + :type 'integer + :group 'fortran-indent) -(defvar comment-line-start nil - "*Delimiter inserted to start new full-line comment.") +(defcustom fortran-comment-indent-style 'fixed + "*How to indent comments. +nil forces comment lines not to be touched, +'fixed makes fixed comment indentation to `fortran-comment-line-extra-indent' +columns beyond `fortran-minimum-statement-indent-fixed' (for +`indent-tabs-mode' of nil) or `fortran-minimum-statement-indent-tab' (for +`indent-tabs-mode' of t), and 'relative indents to current +Fortran indentation plus `fortran-comment-line-extra-indent'." + :type '(radio (const :tag "Untouched" nil) (const fixed) (const relative)) + :group 'fortran-indent) -(defvar comment-line-start-skip nil - "*Regexp to match the start of a full-line comment.") +(defcustom fortran-comment-line-extra-indent 0 + "*Amount of extra indentation for text within full-line comments." + :type 'integer + :group 'fortran-indent + :group 'fortran-comment) -(defvar fortran-minimum-statement-indent 6 - "*Minimum indentation for fortran statements.") +(defcustom comment-line-start nil + "*Delimiter inserted to start new full-line comment." + :type '(choice string (const nil)) + :group 'fortran-comment) + +(defcustom comment-line-start-skip nil + "*Regexp to match the start of a full-line comment." + :type '(choice string (const nil)) + :group 'fortran-comment) + +(defcustom fortran-minimum-statement-indent-fixed 6 + "*Minimum statement indentation for fixed format continuation style." + :type 'integer + :group 'fortran-indent) + +(defcustom fortran-minimum-statement-indent-tab (max tab-width 6) + "*Minimum statement indentation for TAB format continuation style." + :type 'integer + :group 'fortran-indent) ;; Note that this is documented in the v18 manuals as being a string ;; of length one rather than a single character. ;; The code in this file accepts either format for compatibility. -(defvar fortran-comment-indent-char " " +(defcustom fortran-comment-indent-char " " "*Single-character string inserted for Fortran comment indentation. -Normally a space.") +Normally a space." + :type 'string + :group 'fortran-comment) -(defvar fortran-line-number-indent 1 +(defcustom fortran-line-number-indent 1 "*Maximum indentation for Fortran line numbers. -5 means right-justify them within their five-column field.") +5 means right-justify them within their five-column field." + :type 'integer + :group 'fortran-indent) -(defvar fortran-check-all-num-for-matching-do nil - "*Non-nil causes all numbered lines to be treated as possible do-loop ends.") +(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 + :group 'fortran) -(defvar fortran-blink-matching-if nil - "*From a fortran `endif' statement, blink the matching `if' statement.") +(defcustom fortran-blink-matching-if nil + "*Non-nil causes \\[fortran-indent-line] on ENDIF statement to blink on matching IF. +Also, from an ENDDO statement blink on matching DO [WHILE] statement." + :type 'boolean + :group 'fortran) -(defvar fortran-continuation-string "$" - "*Single-character string used for fortran continuation lines. +(defcustom fortran-continuation-string "$" + "*Single-character string used for Fortran continuation lines. 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 appropriate style. -Normally $.") - -(defvar fortran-comment-region "c$$$" - "*String inserted by \\[fortran-comment-region]\ - at start of each line in region.") +Normally $." + :type 'string + :group 'fortran) -(defvar fortran-electric-line-number t - "*Non-nil causes line number digits to be moved to the correct column as\ - typed.") +(defcustom fortran-comment-region "c$$$" + "*String inserted by \\[fortran-comment-region] at start of each \ +line in region." + :type 'string + :group 'fortran-comment) -(defvar fortran-startup-message t - "*Non-nil displays a startup message when Fortran mode is first called.") +(defcustom fortran-electric-line-number t + "*Non-nil causes line number digits to be moved to the correct \ +column as typed." + :type 'boolean + :group 'fortran) -(defvar fortran-column-ruler " " - "*String displayed above current line by \\[fortran-column-ruler].") +(defvar fortran-column-ruler-fixed + "0 4 6 10 20 30 40 5\ +0 60 70\n\ +\[ ]|{ | | | | | | | | \ +\| | | | |}\n" + "String displayed above current line by \\[fortran-column-ruler]. +This variable used in fixed format mode.") -(defconst bug-fortran-mode "bug-fortran-mode@erl.mit.edu" - "Address of mailing list for Fortran mode bugs.") - -(defconst fortran-mode-version "1.28.3") +(defvar fortran-column-ruler-tab + "0 810 20 30 40 5\ +0 60 70\n\ +\[ ]| { | | | | | | | | \ +\| | | | |}\n" + "String displayed above current line by \\[fortran-column-ruler]. +This variable used in TAB format mode.") (defvar fortran-mode-syntax-table nil "Syntax table in use in Fortran mode buffers.") (defvar fortran-analyze-depth 100 - "Number of lines to scan to determine whether to use fixed or tab format\ - style.") + "Number of lines to scan to determine whether to use fixed or TAB \ +format style.") + +(defcustom fortran-break-before-delimiters t + "*Non-nil causes filling to break lines before delimiters." + :type 'boolean + :group 'fortran) (if fortran-mode-syntax-table () (setq fortran-mode-syntax-table (make-syntax-table)) + ;; We might like `;' to be punctuation (g77 multi-statement lines), + ;; but that screws abbrevs. (modify-syntax-entry ?\; "w" fortran-mode-syntax-table) (modify-syntax-entry ?\r " " fortran-mode-syntax-table) (modify-syntax-entry ?+ "." fortran-mode-syntax-table) @@ -186,29 +235,203 @@ Normally $.") (modify-syntax-entry ?/ "." fortran-mode-syntax-table) (modify-syntax-entry ?\' "\"" fortran-mode-syntax-table) (modify-syntax-entry ?\" "\"" fortran-mode-syntax-table) - (modify-syntax-entry ?\\ "/" fortran-mode-syntax-table) - (modify-syntax-entry ?. "w" fortran-mode-syntax-table) + (modify-syntax-entry ?\\ "\\" fortran-mode-syntax-table) + ;; This might be better as punctuation, as for C, but this way you + ;; can treat floating-point numbers as symbols. + (modify-syntax-entry ?. "_" fortran-mode-syntax-table) ; e.g. `a.ne.b' + (modify-syntax-entry ?_ "_" fortran-mode-syntax-table) + (modify-syntax-entry ?$ "_" fortran-mode-syntax-table) ; esp. VMSisms + (modify-syntax-entry ?\! "<" fortran-mode-syntax-table) (modify-syntax-entry ?\n ">" fortran-mode-syntax-table)) -(defvar fortran-mode-map () - "Keymap used in fortran mode.") +;; Comments are real pain in Fortran because there is no way to +;; represent the standard comment syntax in an Emacs syntax table. +;; (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. + +;; Regexps done by simon@gnu with help from Ulrik Dickow and +;; probably others Si's forgotten about (sorry). + +(defconst fortran-font-lock-keywords-1 nil + "Subdued level highlighting for Fortran mode.") + +(defconst fortran-font-lock-keywords-2 nil + "Medium level highlighting for Fortran mode.") + +(defconst fortran-font-lock-keywords-3 nil + "Gaudy level highlighting for Fortran mode.") + +(defconst fortran-font-lock-syntactic-keywords nil + "`font-lock-syntactic-keywords' for Fortran. +These get fixed-format comments fontified.") + +(let ((comment-chars "cd") ; `d' for `debugging' comments + (fortran-type-types + (eval-when-compile + (let ((re (regexp-opt + (let ((simple-types + '("character" "byte" "integer" "logical" + "none" "real" "complex" + "double precision" "double complex")) + (structured-types '("structure" "union" "map")) + (other-types '("record" "dimension" + "parameter" "common" "save" + "external" "intrinsic" "data" + "equivalence"))) + (append + (mapcar (lambda (x) (concat "implicit " x)) + simple-types) + simple-types + (mapcar (lambda (x) (concat "end " x)) + structured-types) + structured-types + other-types))))) + ;; In the optimized regexp above, replace spaces by regexp + ;; for optional whitespace, which regexp-opt would have + ;; escaped. + (mapconcat #'identity (split-string re) "[ \t]*")))) + (fortran-keywords + (eval-when-compile + (regexp-opt '("continue" "format" "end" "enddo" "if" "then" + "else" "endif" "elseif" "while" "inquire" "stop" + "return" "include" "open" "close" "read" "write" + "format" "print" "select" "case" "cycle" "exit")))) + (fortran-logicals + (eval-when-compile + (regexp-opt '("and" "or" "not" "lt" "le" "eq" "ge" "gt" "ne" + "true" "false"))))) + + (setq fortran-font-lock-syntactic-keywords + ;; Fixed format comments. (!-style handled normally.) + (list + (list (concat "^[" comment-chars "]") 0 '(11)) + (list (concat "^[^" comment-chars "\t\n]" (make-string 71 ?.) + "\\([^\n]+\\)") + 1 '(11)))) + + (setq fortran-font-lock-keywords-1 + (list + ;; + ;; Program, subroutine and function declarations, plus calls. + (list (concat "\\<\\(block[ \t]*data\\|call\\|entry\\|function\\|" + "program\\|subroutine\\)\\>[ \t]*\\(\\sw+\\)?") + '(1 font-lock-keyword-face) + '(2 font-lock-function-name-face nil t)))) + + (setq fortran-font-lock-keywords-2 + (append fortran-font-lock-keywords-1 + (list + ;; + ;; Fontify all type specifiers (must be first; see below). + (cons (concat "\\<\\(" fortran-type-types "\\)\\>") + 'font-lock-type-face) + ;; + ;; Fontify all builtin keywords (except logical, do + ;; and goto; see below). + (concat "\\<\\(" fortran-keywords "\\)\\>") + ;; + ;; Fontify all builtin operators. + (concat "\\.\\(" fortran-logicals "\\)\\.") + ;; + ;; Fontify do/goto keywords and targets, and goto tags. + (list "\\<\\(do\\|go *to\\)\\>[ \t]*\\([0-9]+\\)?" + '(1 font-lock-keyword-face) + '(2 font-lock-constant-face nil t)) + (cons "^ *\\([0-9]+\\)" 'font-lock-constant-face)))) + + (setq fortran-font-lock-keywords-3 + (append + ;; + ;; The list `fortran-font-lock-keywords-1'. + fortran-font-lock-keywords-1 + ;; + ;; Fontify all type specifiers plus their declared items. + (list + (list (concat "\\<\\(" fortran-type-types "\\)\\>[ \t(/]*\\(*\\)?") + ;; Fontify the type specifier. + '(1 font-lock-type-face) + ;; Fontify each declaration item (or just the /.../ block name). + `(font-lock-match-c-style-declaration-item-and-skip-to-next + ;; Start after any *(...) expression. + (condition-case nil + (and (and (match-beginning ,(+ 2 (regexp-opt-depth + fortran-type-types))) + (forward-sexp)) + (forward-sexp)) + (error nil)) + ;; No need to clean up. + nil + ;; Fontify as a variable name, functions are + ;; fontified elsewhere. + (1 font-lock-variable-name-face nil t)))) + ;; + ;; Things extra to `fortran-font-lock-keywords-3' + ;; (must be done first). + (list + ;; + ;; Fontify goto-like `err=label'/`end=label' in read/write + ;; statements. + '(", *\\(e\\(nd\\|rr\\)\\)\\> *\\(= *\\([0-9]+\\)\\)?" + (1 font-lock-keyword-face) (4 font-lock-constant-face nil t)) + ;; + ;; Highlight standard continuation character and in a + ;; TAB-formatted line. + '("^ \\([^ 0]\\)" 1 font-lock-string-face) + '("^\t\\([1-9]\\)" 1 font-lock-string-face)) + ;; + ;; The list `fortran-font-lock-keywords-2' less that for types + ;; (see above). + (cdr (nthcdr (length fortran-font-lock-keywords-1) + fortran-font-lock-keywords-2))))) + +(defvar fortran-font-lock-keywords fortran-font-lock-keywords-1 + "Default expressions to highlight in Fortran mode.") + +(defvar fortran-imenu-generic-expression + ;; These patterns could be confused by sequence nos. in cols 72+ and + ;; don't allow continuations everywhere. + (list + (list + nil + ;; Lines below are: 1. leading whitespace; 2. function + ;; declaration with optional type, e.g. `real', `real*4', + ;; character(*), `double precision' and possible statement + ;; continuation; 3. untyped declarations; 4. the variable to + ;; index. [This will be fooled by `end function' allowed by G77. + ;; Also, it assumes sensible whitespace is employed.] + (concat "^\\s-+\\(\ +\\(\\sw\\|\\s-\\|[*()+]\\)*\ +\\ 0 arg) + (setq arg (- arg)) + (forward-line arg)) + (while (not (zerop arg)) + (beginning-of-line) + (or (fortran-remove-continuation) + (delete-indentation)) + (setq arg (1- arg))) + (fortran-indent-line))) (defun fortran-numerical-continuation-char () - "Return a digit for tab-digit style of continution lines. + "Return a digit for tab-digit style of continuation lines. If, previous line is a tab-digit continuation line, returns that digit plus one. Otherwise return 1. Zero not allowed." (save-excursion @@ -541,78 +892,112 @@ except that ] is never special and \ quotes ^, - or \." (skip-chars-backward chars) (delete-region (point) (progn (skip-chars-forward chars) (point)))) +(put 'fortran-electric-line-number 'delete-selection t) (defun fortran-electric-line-number (arg) "Self insert, but if part of a Fortran line number indent it automatically. -Auto-indent does not happen if a numeric arg is used." -;The use of arg may be superfluous here since there apears to be no way to -;prefix a digit key with an argument. +Auto-indent does not happen if a numeric ARG is used." (interactive "P") (if (or arg (not fortran-electric-line-number)) - (if arg - (self-insert-command arg) + (if arg + (self-insert-command (prefix-numeric-value arg)) (self-insert-command 1)) (if (or (and (= 5 (current-column)) (save-excursion (beginning-of-line) (looking-at " ")));In col 5 with only spaces to left. - (and (= fortran-minimum-statement-indent (current-column)) + (and (= (if indent-tabs-mode + fortran-minimum-statement-indent-tab + fortran-minimum-statement-indent-fixed) (current-column)) (save-excursion (beginning-of-line) (looking-at "\t"));In col 8 with a single tab to the left. (not (or (eq last-command 'fortran-indent-line) (eq last-command - 'fortran-reindent-then-newline-and-indent)))) + 'fortran-indent-new-line)))) (save-excursion (re-search-backward "[^ \t0-9]" (save-excursion (beginning-of-line) (point)) - t)) ;not a line number - (looking-at "[0-9]") ;within a line number - ) - (insert last-command-char) + t)) ;not a line number + (looking-at "[0-9]")) ;within a line number + (self-insert-command (prefix-numeric-value arg)) (skip-chars-backward " \t") (insert last-command-char) (fortran-indent-line)))) +(defvar fortran-end-prog-re1 + "end\ +\\([ \t]*\\(program\\|subroutine\\|function\\|block[ \t]*data\\)\\>\ +\\([ \t]*\\(\\sw\\|\\s_\\)+\\)?\\)?") +(defvar fortran-end-prog-re + (concat "^[ \t0-9]*" fortran-end-prog-re1) + "Regexp possibly marking subprogram end.") + +(defun fortran-check-end-prog-re () + "Check a preliminary match against `fortran-end-prog-re'." + ;; Having got a possible match for the subprogram end, we need a + ;; match of whitespace, avoiding possible column 73+ stuff. + (save-match-data + (string-match "^\\s-*\\(\\'\\|\\s<\\)" + (buffer-substring (match-end 0) + (min (line-end-position) + (+ 72 (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. (defun beginning-of-fortran-subprogram () - "Moves point to the beginning of the current fortran subprogram." + "Moves point to the beginning of the current Fortran subprogram." (interactive) - (let ((case-fold-search t)) - (beginning-of-line -1) - (re-search-backward "^[ \t0-9]*end\\b[ \t]*[^ \t=(a-z]" nil 'move) - (if (looking-at "^[ \t0-9]*end\\b[ \t]*[^ \t=(a-z]") - (forward-line 1)))) + (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))))) (defun end-of-fortran-subprogram () - "Moves point to the end of the current fortran subprogram." + "Moves point to the end of the current Fortran subprogram." (interactive) - (let ((case-fold-search t)) - (beginning-of-line 2) - (re-search-forward "^[ \t0-9]*end\\b[ \t]*[^ \t=(a-z]" nil 'move) - (goto-char (match-beginning 0)) - (forward-line 1))) + (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) + (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))))) (defun mark-fortran-subprogram () - "Put mark at end of fortran subprogram, point at beginning. + "Put mark at end of Fortran subprogram, point at beginning. The marks are pushed." (interactive) (end-of-fortran-subprogram) - (push-mark (point)) + (push-mark (point) nil t) (beginning-of-fortran-subprogram)) - + (defun fortran-previous-statement () - "Moves point to beginning of the previous fortran statement. -Returns 'first-statement if that statement is the first + "Moves point to beginning of the previous Fortran statement. +Returns `first-statement' if that statement is the first non-comment Fortran statement in the file, and nil otherwise." (interactive) (let (not-first-statement continue-test) (beginning-of-line) (setq continue-test - (or (looking-at + (and + (not (looking-at comment-line-start-skip)) + (or (looking-at (concat "[ \t]*" (regexp-quote fortran-continuation-string))) - (or (looking-at " [^ 0\n]") - (looking-at "\t[1-9]")))) + (or (looking-at " [^ 0\n]") + (looking-at "\t[1-9]"))))) (while (and (setq not-first-statement (= (forward-line -1) 0)) (or (looking-at comment-line-start-skip) (looking-at "[ \t]*$") @@ -622,19 +1007,21 @@ non-comment Fortran statement in the file, and nil otherwise." (cond ((and continue-test (not not-first-statement)) (message "Incomplete continuation statement.")) - (continue-test + (continue-test (fortran-previous-statement)) ((not not-first-statement) 'first-statement)))) (defun fortran-next-statement () - "Moves point to beginning of the next fortran statement. + "Moves point to beginning of the next Fortran statement. Returns `last-statement' if that statement is the last non-comment Fortran statement in the file, and nil otherwise." (interactive) (let (not-last-statement) (beginning-of-line) - (while (and (setq not-last-statement (= (forward-line 1) 0)) + (while (and (setq not-last-statement + (and (= (forward-line 1) 0) + (not (eobp)))) (or (looking-at comment-line-start-skip) (looking-at "[ \t]*$") (looking-at " [^ 0\n]") @@ -642,60 +1029,277 @@ non-comment Fortran statement in the file, and nil otherwise." (looking-at (concat "[ \t]*" comment-start-skip))))) (if (not not-last-statement) 'last-statement))) + +(defun fortran-narrow-to-subprogram () + "Make text outside the current subprogram invisible. +The subprogram visible is the one that contains or follows point." + (interactive) + (save-excursion + (mark-fortran-subprogram) + (narrow-to-region (point) (mark)))) + +(defmacro fortran-with-subprogram-narrowing (&rest forms) + "Execute FORMS with buffer temporarily narrowed to current subprogram. +Doesn't push a mark." + `(save-restriction + (save-excursion + (narrow-to-region (progn + (beginning-of-fortran-subprogram) + (point)) + (progn + (end-of-fortran-subprogram) + (point)))) + ,@forms)) (defun fortran-blink-matching-if () - "From a fortran `endif' statement, blink the matching `if' statement." - (let ((count 1) (top-of-window (window-start)) matching-if - (endif-point (point)) message) + "From an ENDIF statement, blink the matching IF statement." + (let ((top-of-window (window-start)) + (endif-point (point)) + (case-fold-search t) + matching-if + message) (if (save-excursion (beginning-of-line) (skip-chars-forward " \t0-9") - (looking-at "end[ \t]*if\\b")) + (looking-at "e\\(nd[ \t]*if\\|lse\\([ \t]*if\\)?\\)\\b")) (progn - (save-excursion - (while (and (not (= count 0)) - (not (eq (fortran-previous-statement) - 'first-statement)) - (not (looking-at - "^[ \t0-9]*end\\b[ \t]*[^ \t=(a-z]"))) - ; Keep local to subprogram - (skip-chars-forward " \t0-9") - (cond ((looking-at "if[ \t]*(") - (save-excursion (if (or (looking-at ".*)[ -\t]*then\\b[ \t]*[^ \t(=a-z0-9]") - (let (then-test);multi-line if-then - (while (and (= (forward-line 1) 0) - ;search forward for then - (or -(looking-at " [^ 0\n]") - -(looking-at "\t[1-9]")) - (not (setq -then-test (looking-at - - ".*then\\b[ \t]*[^ \t(=a-z0-9]"))))) - then-test)) - (setq count (- count 1))))) - ((looking-at "end[ \t]*if\\b") - (setq count (+ count 1))))) - (if (not (= count 0)) - (setq message "No matching if.") - (if (< (point) top-of-window) - (setq message (concat "Matches " (buffer-substring - (progn (beginning-of-line) - (point)) - (progn (end-of-line) - (point))))) - (setq matching-if (point))))) + (if (not (setq matching-if (fortran-beginning-if))) + (setq message "No matching if.") + (if (< matching-if top-of-window) + (save-excursion + (goto-char matching-if) + (beginning-of-line) + (setq message + (concat "Matches " + (buffer-substring + (point) (progn (end-of-line) (point)))))))) (if message - (message message) + (message "%s" message) (goto-char matching-if) (sit-for 1) (goto-char endif-point)))))) + +(defun fortran-blink-matching-do () + "From an ENDDO statement, blink the matching DO or DO WHILE statement." + ;; This is basically copied from fortran-blink-matching-if. + (let ((top-of-window (window-start)) + (enddo-point (point)) + (case-fold-search t) + matching-do + message) + (if (save-excursion (beginning-of-line) + (skip-chars-forward " \t0-9") + (looking-at "end[ \t]*do\\b")) + (progn + (if (not (setq matching-do (fortran-beginning-do))) + (setq message "No matching do.") + (if (< matching-do top-of-window) + (save-excursion + (goto-char matching-do) + (beginning-of-line) + (setq message + (concat "Matches " + (buffer-substring + (point) (progn (end-of-line) (point)))))))) + (if message + (message "%s" message) + (goto-char matching-do) + (sit-for 1) + (goto-char enddo-point)))))) + +(defun fortran-mark-do () + "Put mark at end of Fortran DO [WHILE]-ENDDO construct, point at beginning. +The marks are pushed." + (interactive) + (let (enddo-point do-point) + (if (setq enddo-point (fortran-end-do)) + (if (not (setq do-point (fortran-beginning-do))) + (message "No matching do.") + ;; Set mark, move point. + (goto-char enddo-point) + (push-mark) + (goto-char do-point))))) + +(defun fortran-end-do () + "Search forward for first unmatched ENDDO. +Return point or nil." + (let ((case-fold-search t)) + (if (save-excursion (beginning-of-line) + (skip-chars-forward " \t0-9") + (looking-at "end[ \t]*do\\b")) + ;; Sitting on one. + (match-beginning 0) + ;; Search for one. + (save-excursion + (let ((count 1)) + (while (and (not (= count 0)) + (not (eq (fortran-next-statement) 'last-statement)) + ;; Keep local to subprogram + (not (and (looking-at fortran-end-prog-re) + (fortran-check-end-prog-re)))) + + (skip-chars-forward " \t0-9") + (cond ((looking-at "end[ \t]*do\\b") + (setq count (1- count))) + ((looking-at "\\(\\(\\sw\\|\\s_\\)+:[ \t]*\\)?do[ \t]+[^0-9]") + (setq count (+ count 1))))) + (and (= count 0) + ;; All pairs accounted for. + (point))))))) + +(defun fortran-beginning-do () + "Search backwards for first unmatched DO [WHILE]. +Return point or nil." + (let ((case-fold-search t)) + (if (save-excursion (beginning-of-line) + (skip-chars-forward " \t0-9") + (looking-at "\\(\\(\\sw\\|\\s_\\)+:[ \t]*\\)?do[ \t]+")) + ;; Sitting on one. + (match-beginning 0) + ;; Search for one. + (save-excursion + (let ((count 1)) + (while (and (not (= count 0)) + (not (eq (fortran-previous-statement) 'first-statement)) + ;; Keep local to subprogram + (not (and (looking-at fortran-end-prog-re) + (fortran-check-end-prog-re)))) + + (skip-chars-forward " \t0-9") + (cond ((looking-at "\\(\\(\\sw\\|\\s_\\)+:[ \t]*\\)?do[ \t]+[^0-9]") + (setq count (1- count))) + ((looking-at "end[ \t]*do\\b") + (setq count (1+ count))))) + + (and (= count 0) + ;; All pairs accounted for. + (point))))))) + +(defun fortran-mark-if () + "Put mark at end of Fortran IF-ENDIF construct, point at beginning. +The marks are pushed." + (interactive) + (let (endif-point if-point) + (if (setq endif-point (fortran-end-if)) + (if (not (setq if-point (fortran-beginning-if))) + (message "No matching if.") + ;; Set mark, move point. + (goto-char endif-point) + (push-mark) + (goto-char if-point))))) + +(defvar fortran-if-start-re "\\(\\(\\sw\\|\\s_\\)+:[ \t]*\\)?if[ \t]*(") + +(defun fortran-end-if () + "Search forwards for first unmatched ENDIF. +Return point or nil." + (let ((case-fold-search t)) + (if (save-excursion (beginning-of-line) + (skip-chars-forward " \t0-9") + (looking-at "end[ \t]*if\\b")) + ;; Sitting on one. + (match-beginning 0) + ;; Search for one. The point has been already been moved to first + ;; letter on line but this should not cause troubles. + (save-excursion + (let ((count 1)) + (while (and (not (= count 0)) + (not (eq (fortran-next-statement) 'last-statement)) + ;; Keep local to subprogram. + (not (and (looking-at fortran-end-prog-re) + (fortran-check-end-prog-re)))) + + (skip-chars-forward " \t0-9") + (cond ((looking-at "end[ \t]*if\\b") + (setq count (- count 1))) + + ((looking-at fortran-if-start-re) + (save-excursion + (if (or + (looking-at ".*)[ \t]*then\\b[ \t]*[^ \t(=a-z0-9]") + (let (then-test) ; Multi-line if-then. + (while + (and (= (forward-line 1) 0) + ;; Search forward for then. + (or (looking-at " [^ 0\n]") + (looking-at "\t[1-9]")) + (not + (setq then-test + (looking-at + ".*then\\b[ \t]*[^ \t(=a-z0-9]"))))) + then-test)) + (setq count (+ count 1))))))) + + (and (= count 0) + ;; All pairs accounted for. + (point))))))) + +(defun fortran-beginning-if () + "Search backwards for first unmatched IF-THEN. +Return point or nil." + (let ((case-fold-search t)) + (if (save-excursion + ;; May be sitting on multi-line if-then statement, first move to + ;; beginning of current statement. Note: `fortran-previous-statement' + ;; moves to previous statement *unless* current statement is first + ;; one. Only move forward if not first-statement. + (if (not (eq (fortran-previous-statement) 'first-statement)) + (fortran-next-statement)) + (skip-chars-forward " \t0-9") + (and + (looking-at fortran-if-start-re) + (save-match-data + (or (looking-at ".*)[ \t]*then\\b[ \t]*[^ \t(=a-z0-9]") + ;; Multi-line if-then. + (let (then-test) + (while + (and (= (forward-line 1) 0) + ;; Search forward for then. + (or (looking-at " [^ 0\n]") + (looking-at "\t[1-9]")) + (not + (setq then-test + (looking-at + ".*then\\b[ \t]*[^ \t(=a-z0-9]"))))) + then-test))))) + ;; Sitting on one. + (match-beginning 0) + ;; Search for one. + (save-excursion + (let ((count 1)) + (while (and (not (= count 0)) + (not (eq (fortran-previous-statement) 'first-statement)) + ;; Keep local to subprogram. + (not (and (looking-at fortran-end-prog-re) + (fortran-check-end-prog-re)))) + + (skip-chars-forward " \t0-9") + (cond ((looking-at fortran-if-start-re) + (save-excursion + (if (or + (looking-at ".*)[ \t]*then\\b[ \t]*[^ \t(=a-z0-9]") + (let (then-test) ; Multi-line if-then. + (while + (and (= (forward-line 1) 0) + ;; Search forward for then. + (or (looking-at " [^ 0\n]") + (looking-at "\t[1-9]")) + (not + (setq then-test + (looking-at + ".*then\\b[ \t]*[^ \t(=a-z0-9]"))))) + then-test)) + (setq count (- count 1))))) + ((looking-at "end[ \t]*if\\b") + (setq count (+ count 1))))) + + (and (= count 0) + ;; All pairs accounted for. + (point))))))) (defun fortran-indent-line () - "Indents current fortran line based on its contents and on previous lines." + "Indent current Fortran line based on its contents and on previous lines." (interactive) - (let ((cfi (calculate-fortran-indent))) + (let ((cfi (fortran-calculate-indent))) (save-excursion (beginning-of-line) (if (or (not (= cfi (fortran-current-line-indentation))) @@ -704,33 +1308,40 @@ then-test (looking-at (fortran-indent-to-column cfi) (beginning-of-line) (if (and (not (looking-at comment-line-start-skip)) - (re-search-forward comment-start-skip - (save-excursion (end-of-line) (point)) 'move)) + (fortran-find-comment-start-skip)) (fortran-indent-comment)))) ;; Never leave point in left margin. (if (< (current-column) cfi) (move-to-column cfi)) + (if (and auto-fill-function + (> (save-excursion (end-of-line) (current-column)) fill-column)) + (save-excursion + (end-of-line) + (fortran-fill))) (if fortran-blink-matching-if - (fortran-blink-matching-if)))) + (progn + (fortran-blink-matching-if) + (fortran-blink-matching-do))))) -(defun fortran-reindent-then-newline-and-indent () - "Reindent the current fortran line, insert a newline and indent the newline. -An abbrev before point is expanded if abbrev-mode is non-nil." +(defun fortran-indent-new-line () + "Reindent the current Fortran line, insert a newline and indent the newline. +An abbrev before point is expanded if variable `abbrev-mode' is non-nil." (interactive) (if abbrev-mode (expand-abbrev)) (save-excursion (beginning-of-line) (skip-chars-forward " \t") - (if (or (looking-at "[0-9]") ;Reindent only where it is most - (looking-at "end") ;likely to be necessary - (looking-at "else") - (looking-at (regexp-quote fortran-continuation-string))) - (fortran-indent-line))) - (insert "\n") + (let ((case-fold-search t)) + (if (or (looking-at "[0-9]") ;Reindent only where it is most + (looking-at "end") ;likely to be necessary + (looking-at "else") + (looking-at (regexp-quote fortran-continuation-string))) + (fortran-indent-line)))) + (newline) (fortran-indent-line)) - + (defun fortran-indent-subprogram () - "Properly indents the Fortran subprogram which contains point." + "Properly indent the Fortran subprogram which contains point." (interactive) (save-excursion (mark-fortran-subprogram) @@ -738,11 +1349,13 @@ An abbrev before point is expanded if abbrev-mode is non-nil." (indent-region (point) (mark) nil)) (message "Indenting subprogram...done.")) -(defun calculate-fortran-indent () - "Calculates the fortran indent column based on previous lines." +(defun fortran-calculate-indent () + "Calculates the Fortran indent column based on previous lines." (let (icol first-statement (case-fold-search t) (fortran-minimum-statement-indent - fortran-minimum-statement-indent)) + (if indent-tabs-mode + fortran-minimum-statement-indent-tab + fortran-minimum-statement-indent-fixed))) (save-excursion (setq first-statement (fortran-previous-statement)) (if first-statement @@ -752,38 +1365,57 @@ An abbrev before point is expanded if abbrev-mode is non-nil." (setq icol fortran-minimum-statement-indent) (setq icol (fortran-current-line-indentation))) (skip-chars-forward " \t0-9") - (cond ((looking-at "if[ \t]*(") - (if (or (looking-at ".*)[ \t]*then\\b[ \t]*[^ \t(=a-z0-9]") + (cond ((looking-at "\\(\\(\\sw\\|\\s_\\)+:[ \t]*\\)?if[ \t]*(") + (if (or (looking-at ".*)[ \t]*then\\b[ \t]*[^ \t_$(=a-z0-9]") (let (then-test) ;multi-line if-then (while (and (= (forward-line 1) 0) - ;search forward for then + ;;search forward for then (or (looking-at " [^ 0\n]") (looking-at "\t[1-9]")) (not (setq then-test (looking-at - ".*then\\b[ \t]*[^ \t(=a-z0-9]"))))) + ".*then\\b[ \t]\ +*[^ \t_$(=a-z0-9]"))))) then-test)) (setq icol (+ icol fortran-if-indent)))) - ((looking-at "\\(else\\|elseif\\)\\b") + ((looking-at "else\\(if\\)?\\b") + (setq icol (+ icol fortran-if-indent))) + ((looking-at "select[ \t]*case[ \t](.*)") + (setq icol (+ icol fortran-if-indent))) + ((looking-at "case[ \t]*(.*)") + (setq icol (+ icol fortran-if-indent))) + ((looking-at "case[ \t]*default\\b") + (setq icol (+ icol fortran-if-indent))) + ((looking-at "\\(otherwise\\|else[ \t]*where\\)\\b") + (setq icol (+ icol fortran-if-indent))) + ((looking-at "where[ \t]*(.*)[ \t]*\n") (setq icol (+ icol fortran-if-indent))) ((looking-at "do\\b") (setq icol (+ icol fortran-do-indent))) - ((looking-at "end\\b[ \t]*[^ \t=(a-z]") - ; Previous END resets indent to minimum + ((looking-at + "\\(structure\\|union\\|map\\|interface\\)\\b[ \t]*[^ \t=(a-z]") + (setq icol (+ icol fortran-structure-indent))) + ((and (looking-at fortran-end-prog-re1) + (fortran-check-end-prog-re)) + ;; Previous END resets indent to minimum (setq icol fortran-minimum-statement-indent)))))) (save-excursion (beginning-of-line) (cond ((looking-at "[ \t]*$")) ((looking-at comment-line-start-skip) - (setq fortran-minimum-statement-indent 0) (cond ((eq fortran-comment-indent-style 'relative) - (setq icol (+ icol fortran-comment-line-column))) + (setq icol (+ icol fortran-comment-line-extra-indent))) ((eq fortran-comment-indent-style 'fixed) - (setq icol fortran-comment-line-column)))) + (setq icol (+ fortran-minimum-statement-indent + fortran-comment-line-extra-indent)))) + (setq fortran-minimum-statement-indent 0)) ((or (looking-at (concat "[ \t]*" - (regexp-quote fortran-continuation-string))) + (regexp-quote + fortran-continuation-string))) (looking-at " [^ 0\n]") (looking-at "\t[1-9]")) (setq icol (+ icol fortran-continuation-indent))) + ((looking-at "[ \t]*#") ; Check for cpp directive. + (setq fortran-minimum-statement-indent 0 icol 0)) (first-statement) ((and fortran-check-all-num-for-matching-do (looking-at "[ \t]*[0-9]+") @@ -791,16 +1423,24 @@ An abbrev before point is expanded if abbrev-mode is non-nil." (setq icol (- icol fortran-do-indent))) (t (skip-chars-forward " \t0-9") - (cond ((looking-at "end[ \t]*if\\b") + (cond ((looking-at "end[ \t]*\\(if\\|select\\|where\\)\\b") + (setq icol (- icol fortran-if-indent))) + ((looking-at "else\\(if\\)?\\b") (setq icol (- icol fortran-if-indent))) - ((looking-at "\\(else\\|elseif\\)\\b") + ((looking-at "case[ \t]*\\((.*)\\|default\\>\\)") + (setq icol (- icol fortran-if-indent))) + ((looking-at "\\(otherwise\\|else[ \t]*where\\)\\b") (setq icol (- icol fortran-if-indent))) ((and (looking-at "continue\\b") (fortran-check-for-matching-do)) (setq icol (- icol fortran-do-indent))) ((looking-at "end[ \t]*do\\b") (setq icol (- icol fortran-do-indent))) - ((and (looking-at "end\\b[ \t]*[^ \t=(a-z]") + ((looking-at "end[ \t]*\ +\\(structure\\|union\\|map\\|interface\\)\\b[ \t]*[^ \t=(a-z]") + (setq icol (- icol fortran-structure-indent))) + ((and (looking-at fortran-end-prog-re1) + (fortran-check-end-prog-re) (not (= icol fortran-minimum-statement-indent))) (message "Warning: `end' not in column %d. Probably\ an unclosed block." fortran-minimum-statement-indent)))))) @@ -817,62 +1457,66 @@ non-indentation text within the comment." (cond ((looking-at comment-line-start-skip) (goto-char (match-end 0)) (skip-chars-forward - (if (stringp fortran-comment-indent-char) - fortran-comment-indent-char - (char-to-string fortran-comment-indent-char)))) + (if (stringp fortran-comment-indent-char) + fortran-comment-indent-char + (char-to-string fortran-comment-indent-char)))) ((or (looking-at " [^ 0\n]") - (looking-at "\t[1-9]")) + (looking-at "\t[1-9]")) (goto-char (match-end 0))) (t ;; Move past line number. - (re-search-forward "^[ \t0-9]*" (+ (point) 4) t))) + (skip-chars-forward "[ \t0-9]");From Uli + )) ;; Move past whitespace. (skip-chars-forward " \t") (current-column))) (defun fortran-indent-to-column (col) - "Indents current line with spaces to column COL. + "Indent current line with spaces to column COL. notes: 1) A non-zero/non-blank character in column 5 indicates a continuation line, and this continuation character is retained on indentation; - 2) If fortran-continuation-string is the first non-whitespace character, - this is a continuation line; + 2) If `fortran-continuation-string' is the first non-whitespace + character, this is a continuation line; 3) A non-continuation line which has a number as the first non-whitespace character is a numbered line. - 4) A tab followed by a digit indicates a continuation line." + 4) A TAB followed by a digit indicates a continuation line." (save-excursion (beginning-of-line) (if (looking-at comment-line-start-skip) (if fortran-comment-indent-style (let ((char (if (stringp fortran-comment-indent-char) (aref fortran-comment-indent-char 0) - fortran-comment-indent-char))) + fortran-comment-indent-char))) (goto-char (match-end 0)) (delete-horizontal-regexp (concat " \t" (char-to-string char))) (insert-char char (- col (current-column))))) (if (looking-at "\t[1-9]") - (if fortran-tab-mode + (if indent-tabs-mode (goto-char (match-end 0)) (delete-char 2) (insert " ") (insert fortran-continuation-string)) (if (looking-at " [^ 0\n]") - (if fortran-tab-mode + (if indent-tabs-mode (progn (delete-char 6) (insert "\t") (insert-char (fortran-numerical-continuation-char) 1)) (forward-char 6)) (delete-horizontal-space) - ;; Put line number in columns 0-4 - ;; or put continuation character in column 5. + ;; Put line number in columns 0-4 + ;; or put continuation character in column 5. (cond ((eobp)) ((looking-at (regexp-quote fortran-continuation-string)) - (if fortran-tab-mode + (if indent-tabs-mode (progn - (indent-to fortran-minimum-statement-indent) + (indent-to + (if indent-tabs-mode + fortran-minimum-statement-indent-tab + fortran-minimum-statement-indent-fixed)) (delete-char 1) (insert-char (fortran-numerical-continuation-char) 1)) - (indent-to 5)) - (forward-char 1)) + (indent-to 5) + (forward-char 1))) ((looking-at "[0-9]+") (let ((extra-space (- 5 (- (match-end 0) (point))))) (if (< extra-space 0) @@ -885,8 +1529,7 @@ notes: 1) A non-zero/non-blank character in column 5 indicates a continuation (indent-to col) ;; Indent any comment following code on the same line. (if (and comment-start-skip - (re-search-forward comment-start-skip - (save-excursion (end-of-line) (point)) t)) + (fortran-find-comment-start-skip)) (progn (goto-char (match-beginning 0)) (if (not (= (current-column) (fortran-comment-hook))) (progn (delete-horizontal-space) @@ -904,8 +1547,8 @@ Do not call if there is no line number." (= (current-column) 5)))))) (defun fortran-check-for-matching-do () - "When called from a numbered statement, returns t if matching 'do' is found. -Otherwise return a nil." + "When called from a numbered statement, return t if matching DO is found. +Otherwise return nil." (let (charnum (case-fold-search t)) (save-excursion @@ -918,82 +1561,315 @@ Otherwise return a nil." (progn (skip-chars-forward "0-9") (point)))) (beginning-of-line) - (and (re-search-backward - (concat - "\\(^[ \t0-9]*end\\b[ \t]*[^ \t=(a-z]\\)\\|\\(^[ \t0-9]*do[ \t]*0*" - charnum "\\b\\)\\|\\(^[ \t]*0*" charnum "\\b\\)") - nil t) - (looking-at (concat "^[ \t0-9]*do[ \t]*0*" charnum)))))))) + (fortran-with-subprogram-narrowing + (and (re-search-backward + (concat "\\(^[ \t0-9]*do[ \t]*0*" charnum "\\b\\)\\|" + "\\(^[ \t]*0*" charnum "\\b\\)") + nil t) + (looking-at (concat "^[ \t0-9]*do[ \t]*0*" charnum))))))))) -(defun fortran-analyze-file-format () - "Return 0 if Fixed format is used, 1 if Tab formatting is used. -Use fortran-tab-mode-default if no non-comment statements are found in the -file before the end or the first fortran-analyze-depth lines." +(defun fortran-find-comment-start-skip () + "Move to past `comment-start-skip' found on current line. +Return t if `comment-start-skip' found, nil if not." + ;; In order to move point only if comment-start-skip is found, this + ;; one uses a lot of save-excursions. Note that re-search-forward + ;; moves point even if comment-start-skip is inside a string-constant. + ;; Some code expects certain values for match-beginning and end + (interactive) + (if (save-excursion + (re-search-forward comment-start-skip + (save-excursion (end-of-line) (point)) t)) + (let ((save-match-beginning (match-beginning 0)) + (save-match-end (match-end 0))) + (if (fortran-is-in-string-p (match-beginning 0)) + (save-excursion + (goto-char save-match-end) + (fortran-find-comment-start-skip)) ; recurse for rest of line + (goto-char save-match-beginning) + (re-search-forward comment-start-skip + (save-excursion (end-of-line) (point)) t) + (goto-char (match-end 0)) + t)) + nil)) + +;;From: ralf@up3aud1.gwdg.de (Ralf Fassel) +;; Test if TAB format continuation lines work. +(defun fortran-is-in-string-p (where) + "Return non-nil iff WHERE (a buffer position) is inside a Fortran string." (save-excursion - (goto-char (point-min)) - (setq i 0) - (while (not (or - (eobp) - (looking-at "\t") - (looking-at " ") - (> i fortran-analyze-depth))) - (forward-line) - (setq i (1+ i))) + (goto-char where) (cond - ((looking-at "\t") 1) - ((looking-at " ") 0) - (fortran-tab-mode-default 1) - (t 0)))) - -(defun fortran-tab-mode (arg) - "Toggle fortran-tab-mode which indicates style of continuation lines. -With no argument, toggle on/off the tabbing mode of continuation lines. -If argument is a positive number, or non-nil if not a number, fortran-tab-mode -is turned on. Otherwise -If `fortran-tab-mode' is false" + ((bolp) nil) ; bol is never inside a string + ((save-excursion ; comment lines too + (beginning-of-line) + (looking-at comment-line-start-skip)) nil) + (t (let (;; ok, serious now. Init some local vars: + (parse-state '(0 nil nil nil nil nil 0)) + (quoted-comment-start (if comment-start + (regexp-quote comment-start))) + (not-done t) + parse-limit + end-of-line + ) + ;; move to start of current statement + (fortran-next-statement) + (fortran-previous-statement) + ;; now parse up to WHERE + (while not-done + (if (or ;; skip to next line if: + ;; - comment line? + (looking-at comment-line-start-skip) + ;; - at end of line? + (eolp) + ;; - not in a string and after comment-start? + (and (not (nth 3 parse-state)) + comment-start + (equal comment-start + (char-to-string (preceding-char))))) + (if (> (forward-line) 0) + (setq not-done nil)) + ;; else: + ;; if we are at beginning of code line, skip any + ;; whitespace, labels and tab continuation markers. + (if (bolp) (skip-chars-forward " \t0-9")) + ;; if we are in column <= 5 now, check for continuation char + (cond ((= 5 (current-column)) (forward-char 1)) + ((and (< (current-column) 5) + (equal fortran-continuation-string + (char-to-string (following-char))) + (forward-char 1)))) + ;; find out parse-limit from here + (setq end-of-line (save-excursion (end-of-line)(point))) + (setq parse-limit (min where end-of-line)) + ;; parse max up to comment-start, if non-nil and in current line + (if comment-start + (save-excursion + (if (re-search-forward quoted-comment-start end-of-line t) + (setq parse-limit (min (point) parse-limit))))) + ;; now parse if still in limits + (if (< (point) where) + (setq parse-state (parse-partial-sexp + (point) parse-limit nil nil parse-state)) + (setq not-done nil)) + )) + ;; result is + (nth 3 parse-state)))))) + +(defun fortran-auto-fill-mode (arg) + "Toggle fortran-auto-fill mode. +With ARG, turn `fortran-auto-fill' mode on iff ARG is positive. +In `fortran-auto-fill' mode, inserting a space at a column beyond `fill-column' +automatically breaks the line at a previous space." (interactive "P") - (setq fortran-tab-mode - (if (null arg) (not fortran-tab-mode) - (if (numberp arg) - (> (prefix-numeric-value arg) 0) - (arg)))) - (if fortran-tab-mode - (fortran-setup-tab-format-style) - (fortran-setup-fixed-format-style)) - (set-buffer-modified-p (buffer-modified-p))) ;No-op, but updates mode line. - -(defun fortran-setup-tab-format-style () - "Set up fortran mode to use the TAB-digit mode of continuation lines. -Use the command fortran-tab-mode to toggle between this and fixed format style." - (setq fortran-comment-line-column (max tab-width 6)) - (setq fortran-minimum-statement-indent (max tab-width 6)) - (setq indent-tabs-mode t) - (setq fortran-column-ruler - (concat - "0 810 20 30 40 50 60 70\n" - "[ ]| { | | | | | | | | | | | | |}\n")) - (setq fortran-tab-mode-string " TAB-format") - (set-buffer-modified-p (buffer-modified-p))) - -(defun fortran-setup-fixed-format-style () - "Set up fortran mode to use the column 6 mode of continuation lines. -Use the command fortran-tab-mode to toggle between this and tab -character format style." - (setq fortran-comment-line-column 6) - (setq fortran-minimum-statement-indent 6) - (setq indent-tabs-mode nil) - (setq fortran-column-ruler - (concat - "0 4 6 10 20 30 40 50 60 70\n" - "[ ]|{ | | | | | | | | | | | | |}\n")) - (setq fortran-tab-mode-string " Fixed-format") - (set-buffer-modified-p (buffer-modified-p))) - -(or (assq 'fortran-tab-mode minor-mode-alist) + (prog1 (setq auto-fill-function + (if (if (null arg) + (not auto-fill-function) + (> (prefix-numeric-value arg) 0)) + #'fortran-do-auto-fill + nil)) + (force-mode-line-update))) + +(defun fortran-do-auto-fill () + (if (> (current-column) fill-column) + (fortran-indent-line))) + +(defun fortran-fill () + (interactive) + (let* ((auto-fill-function #'fortran-do-auto-fill) + (opoint (point)) + (bol (save-excursion (beginning-of-line) (point))) + (eol (save-excursion (end-of-line) (point))) + (bos (min eol (+ bol (fortran-current-line-indentation)))) + (quote + (save-excursion + (goto-char bol) + (if (looking-at comment-line-start-skip) + nil ; OK to break quotes on comment lines. + (move-to-column fill-column) + (if (fortran-is-in-string-p (point)) + (save-excursion (re-search-backward "\\S\"\\s\"\\S\"" bol t) + (if fortran-break-before-delimiters + (point) + (1+ (point)))))))) + ;; decide where to split the line. If a position for a quoted + ;; string was found above then use that, else break the line + ;; before the last delimiter. + ;; Delimiters are whitespace, commas, and operators. + ;; Will break before a pair of *'s. + (fill-point + (or quote + (save-excursion + (move-to-column (1+ fill-column)) + (skip-chars-backward "^ \t\n,'+-/*=)" +;;; (if fortran-break-before-delimiters +;;; "^ \t\n,'+-/*=" "^ \t\n,'+-/*=)") + ) + (if (<= (point) (1+ bos)) + (progn + (move-to-column (1+ fill-column)) + ;;what is this doing??? + (if (not (re-search-forward "[\t\n,'+-/*)=]" eol t)) + (goto-char bol)))) + (if (bolp) + (re-search-forward "[ \t]" opoint t) + (backward-char) + (if (looking-at "\\s\"") + (forward-char) + (skip-chars-backward " \t\*"))) + (if fortran-break-before-delimiters + (point) + (1+ (point))))))) + ;; if we are in an in-line comment, don't break unless the + ;; line of code is longer than it should be. Otherwise + ;; break the line at the column computed above. + ;; + ;; Need to use fortran-find-comment-start-skip to make sure that quoted !'s + ;; don't prevent a break. + (if (not (or (save-excursion + (if (and (re-search-backward comment-start-skip bol t) + (not (fortran-is-in-string-p (point)))) + (progn + (skip-chars-backward " \t") + (< (current-column) (1+ fill-column))))) + (save-excursion + (goto-char fill-point) + (bolp)))) + (if (> (save-excursion + (goto-char fill-point) (current-column)) + (1+ fill-column)) + (progn (goto-char fill-point) + (fortran-break-line)) + (save-excursion + (if (> (save-excursion + (goto-char fill-point) + (current-column)) + (+ (fortran-calculate-indent) fortran-continuation-indent)) + (progn + (goto-char fill-point) + (fortran-break-line)))))) + )) +(defun fortran-break-line () + (let ((opoint (point)) + (bol (save-excursion (beginning-of-line) (point))) + (eol (save-excursion (end-of-line) (point))) + (comment-string nil)) + + (save-excursion + (if (and comment-start-skip (fortran-find-comment-start-skip)) + (progn + (re-search-backward comment-start-skip bol t) + (setq comment-string (buffer-substring (point) eol)) + (delete-region (point) eol)))) + ;; Forward line 1 really needs to go to next non white line + (if (save-excursion (forward-line) + (or (looking-at " [^ 0\n]") + (looking-at "\t[1-9]"))) + (progn + (end-of-line) + (delete-region (point) (match-end 0)) + (delete-horizontal-space) + (fortran-fill)) + (fortran-split-line)) + (if comment-string + (save-excursion + (goto-char bol) + (end-of-line) + (delete-horizontal-space) + (indent-to (fortran-comment-hook)) + (insert comment-string))))) + +(defun fortran-analyze-file-format () + "Return nil if fixed format is used, t if TAB formatting is used. +Use `fortran-tab-mode-default' if no non-comment statements are found in the +file before the end or the first `fortran-analyze-depth' lines." + (let ((i 0)) + (save-excursion + (goto-char (point-min)) + (setq i 0) + (while (not (or + (eobp) + (looking-at "\t") + (looking-at " ") + (> i fortran-analyze-depth))) + (forward-line) + (setq i (1+ i))) + (cond + ((looking-at "\t") t) + ((looking-at " ") nil) + (fortran-tab-mode-default t) + (t nil))))) + +(or (assq 'fortran-tab-mode-string minor-mode-alist) (setq minor-mode-alist (cons - '(fortran-tab-mode-string fortran-tab-mode-string) + '(fortran-tab-mode-string + (indent-tabs-mode fortran-tab-mode-string)) minor-mode-alist))) +(defun fortran-fill-paragraph (&optional justify) + "Fill surrounding comment block as paragraphs, else fill statement. + +Intended as the value of `fill-paragraph-function'." + (interactive "P") + (save-excursion + (beginning-of-line) + (if (not (looking-at "[Cc*]")) + (fortran-fill-statement) + ;; We're in a comment block. Find the start and end of a + ;; paragraph, delimited either by non-comment lines or empty + ;; comments. (Get positions as markers, since the + ;; `indent-region' below can shift the block's end). + (let* ((non-empty-comment (concat "\\(" comment-line-start-skip + "\\)" "[^ \t\n]")) + (start (save-excursion + ;; Find (start of) first line. + (while (and (zerop (forward-line -1)) + (looking-at non-empty-comment))) + (or (looking-at non-empty-comment) + (forward-line)) ; overshot + (point-marker))) + (end (save-excursion + ;; Find start of first line past region to fill. + (while (progn (forward-line) + (looking-at non-empty-comment))) + (point-marker)))) + ;; Indent the block, find the string comprising the effective + ;; comment start skip and use that as a fill-prefix for + ;; filling the region. + (indent-region start end nil) + (let ((paragraph-ignore-fill-prefix nil) + (fill-prefix (progn (beginning-of-line) + (looking-at comment-line-start-skip) + (match-string 0)))) + (let (fill-paragraph-function) + (fill-region start end justify))) ; with normal `fill-paragraph' + (set-marker start nil) + (set-marker end nil)))) + t) + +(defun fortran-fill-statement () + "Fill a fortran statement up to `fill-column'." + (interactive) + (let ((auto-fill-function #'fortran-do-auto-fill)) + (if (not (save-excursion + (beginning-of-line) + (or (looking-at "[ \t]*$") + (looking-at comment-line-start-skip) + (and comment-start-skip + (looking-at (concat "[ \t]*" comment-start-skip)))))) + (save-excursion + ;; Find beginning of statement. + (fortran-next-statement) + (fortran-previous-statement) + ;; Re-indent initially. + (fortran-indent-line) + ;; Replace newline plus continuation field plus indentation with + ;; single space. + (while (progn + (forward-line) + (fortran-remove-continuation))) + (fortran-previous-statement))) + (fortran-indent-line))) + (provide 'fortran) ;;; fortran.el ends here