1"------------------------------------------------------------------------------
2"  Description: Vim Ada indent file
3"     Language: Ada (2005)
4"	   $Id: ada.vim 887 2008-07-08 14:29:01Z krischik $
5"    Copyright: Copyright (C) 2006 Martin Krischik
6"   Maintainer: Martin Krischik <krischik@users.sourceforge.net>
7"		Neil Bird <neil@fnxweb.com>
8"		Ned Okie <nokie@radford.edu>
9"      $Author: krischik $
10"	 $Date: 2008-07-08 16:29:01 +0200 (Di, 08 Jul 2008) $
11"      Version: 4.6
12"    $Revision: 887 $
13"     $HeadURL: https://gnuada.svn.sourceforge.net/svnroot/gnuada/trunk/tools/vim/indent/ada.vim $
14"      History: 24.05.2006 MK Unified Headers
15"		16.07.2006 MK Ada-Mode as vim-ball
16"		15.10.2006 MK Bram's suggestion for runtime integration
17"		05.11.2006 MK Bram suggested to save on spaces
18"		19.09.2007 NO g: missing before ada#Comment
19"    Help Page: ft-vim-indent
20"------------------------------------------------------------------------------
21" ToDo:
22"  Verify handling of multi-line exprs. and recovery upon the final ';'.
23"  Correctly find comments given '"' and "" ==> " syntax.
24"  Combine the two large block-indent functions into one?
25"------------------------------------------------------------------------------
26
27" Only load this indent file when no other was loaded.
28if exists("b:did_indent") || version < 700
29   finish
30endif
31
32let b:did_indent = 45
33
34setlocal indentexpr=GetAdaIndent()
35setlocal indentkeys-=0{,0}
36setlocal indentkeys+=0=~then,0=~end,0=~elsif,0=~when,0=~exception,0=~begin,0=~is,0=~record
37
38" Only define the functions once.
39if exists("*GetAdaIndent")
40   finish
41endif
42let s:keepcpo= &cpo
43set cpo&vim
44
45if exists("g:ada_with_gnat_project_files")
46   let s:AdaBlockStart = '^\s*\(if\>\|while\>\|else\>\|elsif\>\|loop\>\|for\>.*\<\(loop\|use\)\>\|declare\>\|begin\>\|type\>.*\<is\>[^;]*$\|\(type\>.*\)\=\<record\>\|procedure\>\|function\>\|accept\>\|do\>\|task\>\|package\>\|project\>\|then\>\|when\>\|is\>\)'
47else
48   let s:AdaBlockStart = '^\s*\(if\>\|while\>\|else\>\|elsif\>\|loop\>\|for\>.*\<\(loop\|use\)\>\|declare\>\|begin\>\|type\>.*\<is\>[^;]*$\|\(type\>.*\)\=\<record\>\|procedure\>\|function\>\|accept\>\|do\>\|task\>\|package\>\|then\>\|when\>\|is\>\)'
49endif
50
51" Section: s:MainBlockIndent {{{1
52"
53" Try to find indent of the block we're in
54" prev_indent = the previous line's indent
55" prev_lnum   = previous line (to start looking on)
56" blockstart  = expr. that indicates a possible start of this block
57" stop_at     = if non-null, if a matching line is found, gives up!
58" No recursive previous block analysis: simply look for a valid line
59" with a lesser or equal indent than we currently (on prev_lnum) have.
60" This shouldn't work as well as it appears to with lines that are currently
61" nowhere near the correct indent (e.g., start of line)!
62" Seems to work OK as it 'starts' with the indent of the /previous/ line.
63function s:MainBlockIndent (prev_indent, prev_lnum, blockstart, stop_at)
64   let lnum = a:prev_lnum
65   let line = substitute( getline(lnum), g:ada#Comment, '', '' )
66   while lnum > 1
67      if a:stop_at != ''  &&  line =~ '^\s*' . a:stop_at  &&  indent(lnum) < a:prev_indent
68	 return a:prev_indent
69      elseif line =~ '^\s*' . a:blockstart
70	 let ind = indent(lnum)
71	 if ind < a:prev_indent
72	    return ind
73	 endif
74      endif
75
76      let lnum = prevnonblank(lnum - 1)
77      " Get previous non-blank/non-comment-only line
78      while 1
79	 let line = substitute( getline(lnum), g:ada#Comment, '', '' )
80	 if line !~ '^\s*$' && line !~ '^\s*#'
81	    break
82	 endif
83	 let lnum = prevnonblank(lnum - 1)
84	 if lnum <= 0
85	    return a:prev_indent
86	 endif
87      endwhile
88   endwhile
89   " Fallback - just move back one
90   return a:prev_indent - shiftwidth()
91endfunction MainBlockIndent
92
93" Section: s:EndBlockIndent {{{1
94"
95" Try to find indent of the block we're in (and about to complete),
96" including handling of nested blocks. Works on the 'end' of a block.
97" prev_indent = the previous line's indent
98" prev_lnum   = previous line (to start looking on)
99" blockstart  = expr. that indicates a possible start of this block
100" blockend    = expr. that indicates a possible end of this block
101function s:EndBlockIndent( prev_indent, prev_lnum, blockstart, blockend )
102   let lnum = a:prev_lnum
103   let line = getline(lnum)
104   let ends = 0
105   while lnum > 1
106      if getline(lnum) =~ '^\s*' . a:blockstart
107	 let ind = indent(lnum)
108	 if ends <= 0
109	    if ind < a:prev_indent
110	       return ind
111	    endif
112	 else
113	    let ends = ends - 1
114	 endif
115      elseif getline(lnum) =~ '^\s*' . a:blockend
116	 let ends = ends + 1
117      endif
118
119      let lnum = prevnonblank(lnum - 1)
120      " Get previous non-blank/non-comment-only line
121      while 1
122	 let line = getline(lnum)
123	 let line = substitute( line, g:ada#Comment, '', '' )
124	 if line !~ '^\s*$'
125	    break
126	 endif
127	 let lnum = prevnonblank(lnum - 1)
128	 if lnum <= 0
129	    return a:prev_indent
130	 endif
131      endwhile
132   endwhile
133   " Fallback - just move back one
134   return a:prev_indent - shiftwidth()
135endfunction EndBlockIndent
136
137" Section: s:StatementIndent {{{1
138"
139" Return indent of previous statement-start
140" (after we've indented due to multi-line statements).
141" This time, we start searching on the line *before* the one given (which is
142" the end of a statement - we want the previous beginning).
143function s:StatementIndent( current_indent, prev_lnum )
144   let lnum  = a:prev_lnum
145   while lnum > 0
146      let prev_lnum = lnum
147      let lnum = prevnonblank(lnum - 1)
148      " Get previous non-blank/non-comment-only line
149      while 1
150	 let line = substitute( getline(lnum), g:ada#Comment, '', '' )
151
152	 if line !~ '^\s*$' && line !~ '^\s*#'
153	    break
154	 endif
155	 let lnum = prevnonblank(lnum - 1)
156	 if lnum <= 0
157	    return a:current_indent
158	 endif
159      endwhile
160      " Leave indent alone if our ';' line is part of a ';'-delineated
161      " aggregate (e.g., procedure args.) or first line after a block start.
162      if line =~ s:AdaBlockStart || line =~ '(\s*$'
163	 return a:current_indent
164      endif
165      if line !~ '[.=(]\s*$'
166	 let ind = indent(prev_lnum)
167	 if ind < a:current_indent
168	    return ind
169	 endif
170      endif
171   endwhile
172   " Fallback - just use current one
173   return a:current_indent
174endfunction StatementIndent
175
176
177" Section: GetAdaIndent {{{1
178"
179" Find correct indent of a new line based upon what went before
180"
181function GetAdaIndent()
182   " Find a non-blank line above the current line.
183   let lnum = prevnonblank(v:lnum - 1)
184   let ind = indent(lnum)
185   let package_line = 0
186
187   " Get previous non-blank/non-comment-only/non-cpp line
188   while 1
189      let line = substitute( getline(lnum), g:ada#Comment, '', '' )
190      if line !~ '^\s*$' && line !~ '^\s*#'
191	 break
192      endif
193      let lnum = prevnonblank(lnum - 1)
194      if lnum <= 0
195	 return ind
196      endif
197   endwhile
198
199   " Get default indent (from prev. line)
200   let ind = indent(lnum)
201   let initind = ind
202
203   " Now check what's on the previous line
204   if line =~ s:AdaBlockStart  ||  line =~ '(\s*$'
205      " Check for false matches to AdaBlockStart
206      let false_match = 0
207      if line =~ '^\s*\(procedure\|function\|package\)\>.*\<is\s*new\>'
208	 " Generic instantiation
209	 let false_match = 1
210      elseif line =~ ')\s*;\s*$'  ||  line =~ '^\([^(]*([^)]*)\)*[^(]*;\s*$'
211	 " forward declaration
212	 let false_match = 1
213      endif
214      " Move indent in
215      if ! false_match
216	 let ind = ind + shiftwidth()
217      endif
218   elseif line =~ '^\s*\(case\|exception\)\>'
219      " Move indent in twice (next 'when' will move back)
220      let ind = ind + 2 * shiftwidth()
221   elseif line =~ '^\s*end\s*record\>'
222      " Move indent back to tallying 'type' preceding the 'record'.
223      " Allow indent to be equal to 'end record's.
224      let ind = s:MainBlockIndent( ind+shiftwidth(), lnum, 'type\>', '' )
225   elseif line =~ '\(^\s*new\>.*\)\@<!)\s*[;,]\s*$'
226      " Revert to indent of line that started this parenthesis pair
227      exe lnum
228      exe 'normal! $F)%'
229      if getline('.') =~ '^\s*('
230	 " Dire layout - use previous indent (could check for g:ada#Comment here)
231	 let ind = indent( prevnonblank( line('.')-1 ) )
232      else
233	 let ind = indent('.')
234      endif
235      exe v:lnum
236   elseif line =~ '[.=(]\s*$'
237      " A statement continuation - move in one
238      let ind = ind + shiftwidth()
239   elseif line =~ '^\s*new\>'
240      " Multiple line generic instantiation ('package blah is\nnew thingy')
241      let ind = s:StatementIndent( ind - shiftwidth(), lnum )
242   elseif line =~ ';\s*$'
243      " Statement end (but not 'end' ) - try to find current statement-start indent
244      let ind = s:StatementIndent( ind, lnum )
245   endif
246
247   " Check for potential argument list on next line
248   let continuation = (line =~ '[A-Za-z0-9_]\s*$')
249
250
251   " Check current line; search for simplistic matching start-of-block
252   let line = getline(v:lnum)
253   if line =~ '^\s*#'
254      " Start of line for ada-pp
255      let ind = 0
256   elseif continuation && line =~ '^\s*('
257      " Don't do this if we've already indented due to the previous line
258      if ind == initind
259	 let ind = ind + shiftwidth()
260      endif
261   elseif line =~ '^\s*\(begin\|is\)\>'
262      let ind = s:MainBlockIndent( ind, lnum, '\(procedure\|function\|declare\|package\|task\)\>', 'begin\>' )
263   elseif line =~ '^\s*record\>'
264      let ind = s:MainBlockIndent( ind, lnum, 'type\>\|for\>.*\<use\>', '' ) + shiftwidth()
265   elseif line =~ '^\s*\(else\|elsif\)\>'
266      let ind = s:MainBlockIndent( ind, lnum, 'if\>', '' )
267   elseif line =~ '^\s*when\>'
268      " Align 'when' one /in/ from matching block start
269      let ind = s:MainBlockIndent( ind, lnum, '\(case\|exception\)\>', '' ) + shiftwidth()
270   elseif line =~ '^\s*end\>\s*\<if\>'
271      " End of if statements
272      let ind = s:EndBlockIndent( ind, lnum, 'if\>', 'end\>\s*\<if\>' )
273   elseif line =~ '^\s*end\>\s*\<loop\>'
274      " End of loops
275      let ind = s:EndBlockIndent( ind, lnum, '\(\(while\|for\)\>.*\)\?\<loop\>', 'end\>\s*\<loop\>' )
276   elseif line =~ '^\s*end\>\s*\<record\>'
277      " End of records
278      let ind = s:EndBlockIndent( ind, lnum, '\(type\>.*\)\=\<record\>', 'end\>\s*\<record\>' )
279   elseif line =~ '^\s*end\>\s*\<procedure\>'
280      " End of procedures
281      let ind = s:EndBlockIndent( ind, lnum, 'procedure\>.*\<is\>', 'end\>\s*\<procedure\>' )
282   elseif line =~ '^\s*end\>\s*\<case\>'
283      " End of case statement
284      let ind = s:EndBlockIndent( ind, lnum, 'case\>.*\<is\>', 'end\>\s*\<case\>' )
285   elseif line =~ '^\s*end\>'
286      " General case for end
287      let ind = s:MainBlockIndent( ind, lnum, '\(if\|while\|for\|loop\|accept\|begin\|record\|case\|exception\|package\)\>', '' )
288   elseif line =~ '^\s*exception\>'
289      let ind = s:MainBlockIndent( ind, lnum, 'begin\>', '' )
290   elseif line =~ '^\s*then\>'
291      let ind = s:MainBlockIndent( ind, lnum, 'if\>', '' )
292   endif
293
294   return ind
295endfunction GetAdaIndent
296
297let &cpo = s:keepcpo
298unlet s:keepcpo
299
300finish " 1}}}
301
302"------------------------------------------------------------------------------
303"   Copyright (C) 2006	Martin Krischik
304"
305"   Vim is Charityware - see ":help license" or uganda.txt for licence details.
306"------------------------------------------------------------------------------
307" vim: textwidth=78 wrap tabstop=8 shiftwidth=3 softtabstop=3 noexpandtab
308" vim: foldmethod=marker
309