Emacs plugin for source code indexing of modern Fortran
Fortran-tags is a Fortran source code indexing tool with the main purpose of finding the definition of any variable or procedure. It is able to correctly locate all global and local variables and is expected to work with any Fortran 2008 conforming code with some minor exceptions as mentioned below. Fortran-tags is designed to be used with the Emacs text editor in a Linux environment. A prerequisite for using is Python 3.
- The official versioned releases are available here.
- The latest stable development code is available here.
- For importing the repository, issue
git clone https://github.com/raullaasner/fortran-tags.git
.
-
Include
fortran-tags.el
in your Emacs configuration file,(load-file "<path-to>/fortran-tags.el")
Alternatively, if
fortran-tags.el
is in a standard location such as~/.emacs.d/lisp
,;;(add-to-list 'load-path "~/.emacs.d/lisp") (require 'fortran-tags)
-
Include
fortran-tags.py
in your PATH. If Emacs can't findfortran-tags.py
, try(setenv "PATH" (concat "<dir-containing-fortran-tags.py>:" (getenv "PATH")))
-
One must first generate a tags file with
fortran-tags.py -g FILE [FILE ...]
Default is to create a file named
FORTAGS
in the current directory but this can be changed with the option-o
. For including all the source directories and subdirectories of the project, one can issue, for example,find -name '*.f90' | xargs fortran-tags.py -g
(Why not create an alias for this?) When adding, removing, or modifying the source files only minimal changes are made to the tags file. The structure of the tags file is explained at the beginning of
fortran-tags.py
. -
The Elisp command
fortran-find-tag
tries to find the definition of the word under the cursor. It first searches for the definition in the present scope. If not found, it expands the search scope, tries again, and if still not found, keeps expanding the scope until searching at the module level. If the definition is not found in the current module, the search expands to the module wide variables and procedures of all other modules. If more than 1 match was found,fortran-goto-next
can be used to cycle through all the matches. If a match was found in the current module, then the other modules are not searched in because, in principle, this has to be the correct match. An exception can occur when using operator overloading (see below). If one wishes, a global search can always be forced by issuingfortran-goto-next
even if just 1 match was found. -
The location of the tags file is determined with the first issuance of
fortran-find-tag
or by putting(setq fortran-tags-path "~/my-project/FORTAGS")
into a project related configuration file. The tags file is read from the hard drive each time
fortran-find-tag
is invoked (searches are based ongrep
). Thus, when the tags file is regenerated the definitions are instantly up to date without restarting the editor.A simple elisp function to regenerate the tags file is
(defun generate-fortran-tags (path) (compile (concat "find " path " -name \"*.f90\" | xargs fortran-tags.py -o " path "/FORTAGS -g")) (setq fortran-tags-path (concat path "/FORTAGS")))
where path is the root directory of a project. A call to this function could be included in a project related configuration file that is always run when a project is opened.
-
fortran-pop-tag-mark
goes back to the previous position (works repeatedly). -
fortran-find-proc-calls
finds all calls to the procedure under the cursor. The results can then be cycled through usingfortran-goto-next
. This function is not perfect and can return some garbage along with the correct results. The reasons are that it- also searches in strings and comments;
- is unable to find a match if
&
is used in highly unusal ways; - is unable to distinguish between type-bound arrays and procedures.
If the code is well written with a clean style and the user knows whether they are searching for a subroutine or a function, the search can be sped up using the following functions, which employ a simpler regex pattern and search specifically for a subroutine or a function:
fortran-find-proc-calls-sub
- searches only for subroutine calls using the search pattern^ *call X *([(&]|$)
, whereX
is the word under the cursor.fortran-find-proc-calls-func
- searches only for function calls, excluding type-bound procedures, using the search pattern[=+/*(&-] *X *[(&]
.fortran-find-proc-calls-type
- searches only for type-bound procedures using%X *[(&]
.
These specialized searches are all case sensitive, while the general search with
fortran-find-proc-calls
is case insensitive and uses the search patterns(^|[;&]) *call +X *([(&;!]|$)
(for subroutines) and([=+/*(%&-]|^) *X *[(&]
(for functions). -
In order to specify the search term from keyboard, place the cursor so that it's not covering any character (
_A-Za-z0-9
), and invoke any search function, e.g.,fortran-find-tag
. All functions exceptfortran-find-proc-calls
are case sensitive. -
fortran-procedures-in-buffer
lists all subroutines and functions in the current buffer. -
The default key-bindings are
M-. fortran-find-tag M-* fortran-pop-tag-mark M-n fortran-goto-next M-s g fortran-find-proc-calls M-s s fortran-find-proc-calls-sub M-s f fortran-find-proc-calls-func M-s t fortran-find-proc-calls-type M-s d fortran-procedures-in-buffer
These can be disabled by turning off
fortran-tags-mode
.
-
If the definition is found not in the present module but elsewhere,
fortran-find-tag
may return more than one match because it is unable to determine the exact origin of the word under the cursor. -
It is assumed that the semicolon and ampersand are not used in highly non-standard ways.
-
If the initialization of a variable spans several lines, then only the first line is processed. In this example,
integer :: a(4) = [ 1, 2, & & 3, 4], b integer :: c = 5
a
andc
are correctly written into the tags file, whileb
is not found.a
could also be a scalar with some complex initialization expression. -
Submodules are not supported.
-
If there are two or more
associate
constructs within the same scope that contain an identical keyword, then the duplicate definitions are not found. -
If the result variable of a function is defined using both the
result
keyword and a dummy variable, only the former counts as a definition. It is assumed that the return type is declared on the same line as function declaration. -
If a procedure is overloaded using multiple interface blocks which are scattered over different modules, one of which is the current module, then only the definition in the current module is reached. In such a situation, one can invoke
fortran-goto-next
, which always performs a global search. If no match was found in the current module, thenfortran-find-tag
always finds all of them. -
It is recommended not to end subprograms with a bare
end
(not followed by, e.g.,subroutine
). It will often work but we can't guarantee all cases. -
Using
*
for specifying the kind is not recommended, but it will work withreal*8
andcomplex*16
. Seeexceptions
in fortran-tags.py. Other nonstandard syntax can be included by modifying this variable, e.g., by settingexceptions = 'real[*]8|complex[*]16|integer*8|'
. When dealing with clean code only, one can setexceptions = ''
. -
Fortran-tags is unable to correctly handle the
#include
andinclude
directives. The generated tags file might not have the correct contents.
A good place to bring up any issues is https://github.com/raullaasner/fortran-tags/issues.
Any help for porting this to other platforms is most welcome.
This project is distributed under the terms of the GNU General Public License, see LICENSE in the root directory of the present distribution or http://gnu.org/copyleft/gpl.txt.