1" Vim indent file generic utility functions
2" Language:    * (various)
3" Maintainer:  Dave Silvia <dsilvia@mchsi.com>
4" Date:        6/30/2004
5
6" SUMMARY:  To use GenericIndent, indent/<your_filename>.vim would have the
7"           following general format:
8"
9"      if exists("b:did_indent") | finish | endif
10"      let b:did_indent = 1
11"      runtime indent/GenericIndent.vim
12"      let b:indentStmts=''
13"      let b:dedentStmts=''
14"      let b:allStmts=''
15"      setlocal indentexpr=GenericIndent()
16"      setlocal indentkeys=<your_keys>
17"      call GenericIndentStmts(<your_stmts>)
18"      call GenericDedentStmts(<your_stmts>)
19"      call GenericAllStmts()
20"
21" END SUMMARY:
22
23" NOTE:  b:indentStmts, b:dedentStmts, and b:allStmts need to be initialized
24"        to '' before callin the functions because 'indent.vim' explicitly
25"        'unlet's b:did_indent.  This means that the lists will compound if
26"        you change back and forth between buffers.  This is true as of
27"        version 6.3, 6/23/2004.
28"
29" NOTE:  By default, GenericIndent is case sensitive.
30"        let b:case_insensitive=1 if you want to ignore case, e.g. DOS batch files
31
32" The function 'GenericIndent' is data driven and handles most all cases of
33" indent checking if you first set up the data.  To use this function follow
34" the example below (taken from the file indent/MuPAD_source.vim)
35"
36" Before you start, source this file in indent/<your_script>.vim to have it
37" define functions for your use.
38"
39"runtime indent/GenericIndent.vim
40"
41" The data is in 5 sets:
42"
43" First, set the data set 'indentexpr' to GenericIndent().
44"
45"setlocal indentexpr=GenericIndent()
46"
47" Second, set the data set 'indentkeys' to the keywords/expressions that need
48" to be checked for 'indenting' _as_ they typed.
49"
50"setlocal indentkeys==end_proc,=else,=then,=elif,=end_if,=end_case,=until,=end_repeat,=end_domain,=end_for,=end_while,=end,o,O
51"
52" NOTE: 'o,O' at the end of the previous line says you wish to be called
53" whenever a newline is placed in the buffer.  This allows the previous line
54" to be checked for indentation parameters.
55"
56" Third, set the data set 'b:indentStmts' to the keywords/expressions that, when
57" they are on a line  _when_  you  _press_  the  _<Enter>_  key,
58" you wish to have the next line indented.
59"
60"call GenericIndentStmts('begin,if,then,else,elif,case,repeat,until,domain,do')
61"
62" Fourth, set the data set 'b:dedentStmts' to the keywords/expressions that, when
63" they are on a line you are currently typing, you wish to have that line
64" 'dedented' (having already been indented because of the previous line's
65" indentation).
66"
67"call GenericDedentStmts('end_proc,then,else,elif,end_if,end_case,until,end_repeat,end_domain,end_for,end_while,end')
68"
69" Fifth, set the data set 'b:allStmts' to the concatenation of the third and
70" fourth data sets, used for checking when more than one keyword/expression
71" is on a line.
72"
73"call GenericAllStmts()
74"
75" NOTE:  GenericIndentStmts uses two variables: 'b:indentStmtOpen' and
76" 'b:indentStmtClose' which default to '\<' and '\>' respectively.  You can
77" set (let) these to any value you wish before calling GenericIndentStmts with
78" your list.  Similarly, GenericDedentStmts uses 'b:dedentStmtOpen' and
79" 'b:dedentStmtClose'.
80"
81" NOTE:  Patterns may be used in the lists passed to Generic[In|De]dentStmts
82" since each element in the list is copied verbatim.
83"
84" Optionally, you can set the DEBUGGING flag within your script to have the
85" debugging messages output.  See below for description.  This can also be set
86" (let) from the command line within your editing buffer.
87"
88"let b:DEBUGGING=1
89"
90" See:
91"      :h runtime
92"      :set runtimepath ?
93" to familiarize yourself with how this works and where you should have this
94" file and your file(s) installed.
95"
96" For help with setting 'indentkeys' see:
97"      :h indentkeys
98" Also, for some good examples see 'indent/sh.vim' and 'indent/vim.vim' as
99" well as files for other languages you may be familiar with.
100"
101"
102" Alternatively, if you'd rather specify yourself, you can enter
103" 'b:indentStmts', 'b:dedentStmts', and 'b:allStmts' 'literally':
104"
105"let b:indentStmts='\<begin\>\|\<if\>\|\<then\>\|\<else\>\|\<elif\>\|\<case\>\|\<repeat\>\|\<until\>\|\<domain\>\|\<do\>'
106"let b:dedentStmts='\<end_proc\>\|\<else\>\|\<elif\>\|\<end_if\>\|\<end_case\>\|\<until\>\|\<end_repeat\>\|\<end_domain\>\|\<end_for\>\|\<end_while\>\|\<end\>'
107"let b:allStmts=b:indentStmts.'\|'.b:dedentStmts
108"
109" This is only useful if you have particularly different parameters for
110" matching each statement.
111
112" RECAP:  From indent/MuPAD_source.vim
113"
114"if exists("b:did_indent") | finish | endif
115"
116"let b:did_indent = 1
117"
118"runtime indent/GenericIndent.vim
119"
120"setlocal indentexpr=GenericIndent()
121"setlocal indentkeys==end_proc,=then,=else,=elif,=end_if,=end_case,=until,=end_repeat,=end_domain,=end_for,=end_while,=end,o,O
122"call GenericIndentStmts('begin,if,then,else,elif,case,repeat,until,domain,do')
123"call GenericDedentStmts('end_proc,then,else,elif,end_if,end_case,until,end_repeat,end_domain,end_for,end_while,end')
124"call GenericAllStmts()
125"
126" END RECAP:
127
128let s:hit=0
129let s:lastVlnum=0
130let s:myScriptName=expand("<sfile>:t")
131
132if exists("*GenericIndent")
133	finish
134endif
135
136function GenericAllStmts()
137	let b:allStmts=b:indentStmts.'\|'.b:dedentStmts
138	call DebugGenericIndent(expand("<sfile>").": "."b:indentStmts: ".b:indentStmts.", b:dedentStmts: ".b:dedentStmts.", b:allStmts: ".b:allStmts)
139endfunction
140
141function GenericIndentStmts(stmts)
142	let Stmts=a:stmts
143	let Comma=match(Stmts,',')
144	if Comma == -1 || Comma == strlen(Stmts)-1
145		echoerr "Must supply a comma separated list of at least 2 entries."
146		echoerr "Supplied list: <".Stmts.">"
147		return
148	endif
149
150	if !exists("b:indentStmtOpen")
151		let b:indentStmtOpen='\<'
152	endif
153	if !exists("b:indentStmtClose")
154		let b:indentStmtClose='\>'
155	endif
156	if !exists("b:indentStmts")
157		let b:indentStmts=''
158	endif
159	if b:indentStmts != ''
160		let b:indentStmts=b:indentStmts.'\|'
161	endif
162	call DebugGenericIndent(expand("<sfile>").": "."b:indentStmtOpen: ".b:indentStmtOpen.", b:indentStmtClose: ".b:indentStmtClose.", b:indentStmts: ".b:indentStmts.", Stmts: ".Stmts)
163	let stmtEntryBegin=0
164	let stmtEntryEnd=Comma
165	let stmtEntry=strpart(Stmts,stmtEntryBegin,stmtEntryEnd-stmtEntryBegin)
166	let Stmts=strpart(Stmts,Comma+1)
167	let Comma=match(Stmts,',')
168	let b:indentStmts=b:indentStmts.b:indentStmtOpen.stmtEntry.b:indentStmtClose
169	while Comma != -1
170		let stmtEntryEnd=Comma
171		let stmtEntry=strpart(Stmts,stmtEntryBegin,stmtEntryEnd-stmtEntryBegin)
172		let Stmts=strpart(Stmts,Comma+1)
173		let Comma=match(Stmts,',')
174		let b:indentStmts=b:indentStmts.'\|'.b:indentStmtOpen.stmtEntry.b:indentStmtClose
175	endwhile
176	let stmtEntry=Stmts
177	let b:indentStmts=b:indentStmts.'\|'.b:indentStmtOpen.stmtEntry.b:indentStmtClose
178endfunction
179
180function GenericDedentStmts(stmts)
181	let Stmts=a:stmts
182	let Comma=match(Stmts,',')
183	if Comma == -1 || Comma == strlen(Stmts)-1
184		echoerr "Must supply a comma separated list of at least 2 entries."
185		echoerr "Supplied list: <".Stmts.">"
186		return
187	endif
188
189	if !exists("b:dedentStmtOpen")
190		let b:dedentStmtOpen='\<'
191	endif
192	if !exists("b:dedentStmtClose")
193		let b:dedentStmtClose='\>'
194	endif
195	if !exists("b:dedentStmts")
196		let b:dedentStmts=''
197	endif
198	if b:dedentStmts != ''
199		let b:dedentStmts=b:dedentStmts.'\|'
200	endif
201	call DebugGenericIndent(expand("<sfile>").": "."b:dedentStmtOpen: ".b:dedentStmtOpen.", b:dedentStmtClose: ".b:dedentStmtClose.", b:dedentStmts: ".b:dedentStmts.", Stmts: ".Stmts)
202	let stmtEntryBegin=0
203	let stmtEntryEnd=Comma
204	let stmtEntry=strpart(Stmts,stmtEntryBegin,stmtEntryEnd-stmtEntryBegin)
205	let Stmts=strpart(Stmts,Comma+1)
206	let Comma=match(Stmts,',')
207	let b:dedentStmts=b:dedentStmts.b:dedentStmtOpen.stmtEntry.b:dedentStmtClose
208	while Comma != -1
209		let stmtEntryEnd=Comma
210		let stmtEntry=strpart(Stmts,stmtEntryBegin,stmtEntryEnd-stmtEntryBegin)
211		let Stmts=strpart(Stmts,Comma+1)
212		let Comma=match(Stmts,',')
213		let b:dedentStmts=b:dedentStmts.'\|'.b:dedentStmtOpen.stmtEntry.b:dedentStmtClose
214	endwhile
215	let stmtEntry=Stmts
216	let b:dedentStmts=b:dedentStmts.'\|'.b:dedentStmtOpen.stmtEntry.b:dedentStmtClose
217endfunction
218
219" Debugging function.  Displays messages in the command area which can be
220" reviewed using ':messages'.  To turn it on use ':let b:DEBUGGING=1'.  Once
221" on, turn off by using ':let b:DEBUGGING=0.  If you don't want it at all and
222" feel it's slowing down your editing (you must have an _awfully_ slow
223" machine!;-> ), you can just comment out the calls to it from 'GenericIndent'
224" below.  No need to remove the function or the calls, tho', as you never can
225" tell when they might come in handy!;-)
226function DebugGenericIndent(msg)
227  if exists("b:DEBUGGING") && b:DEBUGGING
228		echomsg '['.s:hit.']'.s:myScriptName."::".a:msg
229	endif
230endfunction
231
232function GenericIndent()
233	" save ignore case option.  Have to set noignorecase for the match
234	" functions to do their job the way we want them to!
235	" NOTE: if you add a return to this function be sure you do
236	"           if IgnoreCase | set ignorecase | endif
237	"       before returning.  You can just cut and paste from here.
238	let IgnoreCase=&ignorecase
239	" let b:case_insensitive=1 if you want to ignore case, e.g. DOS batch files
240	if !exists("b:case_insensitive")
241		set noignorecase
242	endif
243	" this is used to let DebugGenericIndent display which invocation of the
244	" function goes with which messages.
245	let s:hit=s:hit+1
246  let lnum=v:lnum
247	let cline=getline(lnum)
248	let lnum=prevnonblank(lnum)
249	if lnum==0 | if IgnoreCase | set ignorecase | endif | return 0 | endif
250	let pline=getline(lnum)
251  let ndnt=indent(lnum)
252	if !exists("b:allStmts")
253		call GenericAllStmts()
254	endif
255
256	call DebugGenericIndent(expand("<sfile>").": "."cline=<".cline.">, pline=<".pline.">, lnum=".lnum.", v:lnum=".v:lnum.", ndnt=".ndnt)
257	if lnum==v:lnum
258		" current line, only check dedent
259		"
260		" just dedented this line, don't need to do it again.
261		" another dedentStmts was added or an end%[_*] was completed.
262		if s:lastVlnum==v:lnum
263 			if IgnoreCase | set ignorecase | endif
264			return ndnt
265		endif
266		let s:lastVlnum=v:lnum
267		call DebugGenericIndent(expand("<sfile>").": "."Checking dedent")
268		let srcStr=cline
269		let dedentKeyBegin=match(srcStr,b:dedentStmts)
270		if dedentKeyBegin != -1
271			let dedentKeyEnd=matchend(srcStr,b:dedentStmts)
272			let dedentKeyStr=strpart(srcStr,dedentKeyBegin,dedentKeyEnd-dedentKeyBegin)
273			"only dedent if it's the beginning of the line
274			if match(srcStr,'^\s*\<'.dedentKeyStr.'\>') != -1
275				call DebugGenericIndent(expand("<sfile>").": "."It's the beginning of the line, dedent")
276				let ndnt=ndnt-&shiftwidth
277			endif
278		endif
279		call DebugGenericIndent(expand("<sfile>").": "."dedent - returning ndnt=".ndnt)
280	else
281		" previous line, only check indent
282		call DebugGenericIndent(expand("<sfile>").": "."Checking indent")
283		let srcStr=pline
284		let indentKeyBegin=match(srcStr,b:indentStmts)
285		if indentKeyBegin != -1
286			" only indent if it's the last indentStmts in the line
287			let allKeyBegin=match(srcStr,b:allStmts)
288			let allKeyEnd=matchend(srcStr,b:allStmts)
289			let allKeyStr=strpart(srcStr,allKeyBegin,allKeyEnd-allKeyBegin)
290			let srcStr=strpart(srcStr,allKeyEnd)
291			let allKeyBegin=match(srcStr,b:allStmts)
292			if allKeyBegin != -1
293				" not the end of the line, check what is and only indent if
294				" it's an indentStmts
295				call DebugGenericIndent(expand("<sfile>").": "."Multiple words in line, checking if last is indent")
296				while allKeyBegin != -1
297					let allKeyEnd=matchend(srcStr,b:allStmts)
298					let allKeyStr=strpart(srcStr,allKeyBegin,allKeyEnd-allKeyBegin)
299					let srcStr=strpart(srcStr,allKeyEnd)
300					let allKeyBegin=match(srcStr,b:allStmts)
301				endwhile
302				if match(b:indentStmts,allKeyStr) != -1
303					call DebugGenericIndent(expand("<sfile>").": "."Last word in line is indent")
304					let ndnt=ndnt+&shiftwidth
305				endif
306			else
307				" it's the last indentStmts in the line, go ahead and indent
308				let ndnt=ndnt+&shiftwidth
309			endif
310		endif
311		call DebugGenericIndent(expand("<sfile>").": "."indent - returning ndnt=".ndnt)
312	endif
313	if IgnoreCase | set ignorecase | endif
314	return ndnt
315endfunction
316
317
318" TODO:  I'm open!
319"
320" BUGS:  You tell me!  Probably.  I just haven't found one yet or haven't been
321"        told about one.
322"
323