Many default settings in Vim only work when programming in C which means you
can’t use gf
on a package to go to its file definition in Ada. The following
settings will fix these shortcomings
setlocal suffixesadd=.ads
let projprefix = '/path/to/project'
" Change casing of file to lowercase and replace subpackage '.' with '-'
" Note, the file naming scheme must follow what the GNAT compiler expects for
" this to work.
setlocal includeexpr=tolower(substitute(v:fname,'\\.','-','g'))
" Set the path variable to include the directories 'src', 'test', and all their
" subdirectories
let &l:path .= join([projprefix . '/src/**', projprefix . '/test/**'], ',')
It would also be nice to be able to search for files in Vim. Setting the path option will enable that built-in functionality. First, define the root repository directory. Then use globs to find every source directory in that root directory.
let projprefix = '/path/to/project/root'
" Set path for system includes
let sysincludes = '/path/to/gcc/adainclude'
let &l:path = sysincludes
" The following creates a list of all the files in the 'src' and 'test'
" directories.
let &l:path .= ',' . join([projprefix . '/src/**', projprefix . '/test/**'], ',')
Check out :h ft_ada
for the complete summary of options. First, let’s set
g:gnat.Make_Command
to use gprbuild
instead of gnatmake
. We want the
gprbuild
command to compile the project file we are using.
call g:gnat.Set_Project_File("project.gpr")
let g:gnat.Make_Command = '"gprbuild -P " . self.Project_File'
The latest GNAT compiler has a new error format that we need to add to. Below, I set the default error formats and append the new error format to the end. This is to prevent the error format string from growing each time my Vim configuration script is sourced.
let g:gnat.Error_Format = '%f:%l:%c: %trror: %m,'
let g:gnat.Error_Format .= '%f:%l:%c: %tarning: %m,'
let g:gnat.Error_Format .= '%f:%l:%c: (%ttyle) %m,'
" The following is the new error format
let g:gnat.Error_Format .= '%f:%l:%c: %m'
As of the time of writing, ALE only supports GCC for linting Ada code. I am working on adding support for GPRBuild. In addition I want to add a fixer and language server.
First, let’s enable the ALE linter for Ada.
let b:ale_linters = ['gcc']
Let’s also enable some generic fixers that ALE provides. These fix common whitespace issues.
let b:ale_fixers = ['remove_trailing_lines', 'trim_whitespace']
When using ALE to lint Ada files, you’ll find that it won’t search for include
directories outside of the current directory. Search for the ALE documentation
in Vim and you’ll find ale-ada-gcc
which lists multiple variables which can be
modified. The variable we’re interested in is b:ale_ada_gcc_options
which we
will append our include search paths to.
It would be nice to either 1) determine your include directories from your gpr
file, or 2) recursively add all source directories in your project to the search
path. While I couldn’t figure out the former, I could leverage what I did above
to define the path
option.
" Join the include directories prefixing each directory with the '-I' flag
let incdirs = ' -I ''' . join(srcdirs, ''' -I ''')
" Add third party include libraries to search path where the text in angle
brackets are placeholders for the actual library paths.
let b:ale_ada_gcc_options = '-I /<inc_prefix>/<lib1> -I /<inc_prefix>/<lib2> '
" Add project source directores to search path
let b:ale_ada_gcc_options .= incdirs
I added support for GPRBuild because more complex builds at work required many GCC flags to correctly check the syntax and semantics of source files. Instead of determining the correct flags to pass to GCC, it would be easier to use GPRBuild to lint code. I currently use this in place of GCC for linting. The following variables need to be configured:
let b:ale_linters = ['gprbuild']
let b:ale_ada_gprbuild_project = '/path/to/project.gpr'
We do not want to load project specific settings for every Ada file we open. Let’s only setup project specific settings when we’re editing Ada source files in the project we’re interested in. The entire Vimscript is below.
let g:gnat.Make_Command = '"gprbuild -P " . self.Project_File'
let g:gnat.Error_Format = '%f:%l:%c: %trror: %m,'
let g:gnat.Error_Format .= '%f:%l:%c: %tarning: %m,'
let g:gnat.Error_Format .= '%f:%l:%c: (%ttyle) %m,'
" The following is the new error format
let g:gnat.Error_Format .= '%f:%l:%c: %m'
let b:ale_linters = ['gcc']
setlocal suffixesadd=.ads
setlocal include=^\s*with
function! GetAdaInclude(fname, prefix)
" Change casing of file to lowercase and replace subpackage '.' to '-'
" Note, this depends on the naming scheme being used. This works with
" GNAT's recommended naming scheme.
let file = tolower(substitute(a:fname,'\\.','-','g'))
" Search for the file in all subdirectories of the project, appending
" ".ads" to the search string
return findfile(file, a:prefix . '/**/*')
endfunction
" Project specific settings go in here
if match(expand('%:p'), '/path/to/project/root') != -1
let projprefix = '/path/to/project/root'
let &l:includeexpr = GetAdaInclude(v:fname, projprefix)
" Find all files in the src and test subdirectories
let srcdirs = glob(projprefix . '/src/**', 1, 1) + glob(projprefix . '/test/**', 1, 1)
" Remove file names from paths. Sort the list of paths and remove
" duplicates.
call uniq(sort(map(srcdirs, 'fnamemodify(v:val, ":h")')))
" Set the path variable
let &l:path = join(srcdirs, ',')
" Set the GPR file
call g:gnat.Set_Project_File("project.gpr")
" Join the include directories prefixing each directory with the '-I' flag
let incdirs = ' -I ''' . join(srcdirs, ''' -I ''')
" Add third party include libraries to search path
let b:ale_ada_gcc_options = '-I /<inc_prefix>/<lib1> -I /<inc_prefix>/<lib2> '
" Add project source directores to search path
let b:ale_ada_gcc_options .= incdirs
" Add generic fixers
let b:ale_fixers = ['remove_trailing_lines', 'trim_whitespace']
endif