add submodule update --rebase
[~madcoder/dotfiles.git] / vim / plugin / jptemplate.vim
1 " jptemplate.vim: 
2
3 " A comfortable, interactive templating system for VIM.
4
5 " Copyright (c) 2008 Jannis Pohlmann <jannis@xfce.org>.
6 "
7 " This program is free software; you can redistribute it and/or modify 
8 " it under the terms of the GNU General Public License as published by 
9 " the Free Software Foundation; either version 2 of the License, or (at 
10 " your option) any later version.
11 "
12 " This program is distributed in the hope that it will be useful, but 
13 " WITHOUT ANY WARRANTY; without even the implied warranty of 
14 " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
15 " General Public License for more details.
16 "
17 " You should have received a copy of the GNU General Public License 
18 " along with this program; if not, write to the Free Software 
19 " Foundation, Inc., 59 Temple Place, Suite 330, Boston, 
20 " MA  02111-1307  USA
21 "
22 " TODO:
23 " - Code cleanup
24 "
25
26
27 " Default template dir used if g:jpTemplateDir is not set
28 let s:defaultTemplateDir = $HOME . '/.vim/jptemplate'
29
30 " Debug mode
31 let s:debug = 1
32
33
34 function! jp:GetTemplateInfo ()
35
36   " Prepare info dictionary
37   let info = {}
38
39   " Get part of the line before the cursor
40   let part = getline  ('.')[0 : getpos ('.')[2]-1]
41
42   " Get start and end position of the template name
43   let info['start'] = match    (part, '\(\w*\)$')
44   let info['end']   = matchend (part, '\(\w*\)$')
45
46   " Get template name
47   let info['name']  = part[info['start'] : info['end']]
48
49   " Throw exception if no template name could be found
50   if info['name'] == ''
51     throw 'No template name found at cursor'
52   endif
53
54   " Calculate line indentation
55   let info['indent'] = max ([0, matchend (part, '^\s\+')])
56
57   " Return template name information
58   return info
59
60 endfunction
61
62
63 function! jp:ReadTemplate (name, filename)
64   
65   " Try to read the template file and throw exception if that fails
66   try
67     return readfile (a:filename)
68   catch
69     throw 'Template "' . a:name . '" could not be found.'
70   endtry
71
72 endfunction
73
74
75 function! jp:SetCursorPosition (lines)
76
77   for cnt in range (0, a:lines)
78     " Search for ${cursor} in the current line
79     let str    = getline  (line ('.') + cnt)
80     let start  = match    (str, '${cursor}')
81     let end    = matchend (str, '${cursor}') 
82     let before = strpart  (str, 0, start)
83     let after  = strpart  (str, end)
84
85     if start >= 0
86       " Remove ${cursor} and move the cursor to the desired position
87       call setline (line ('.') + cnt, before . after)
88       call cursor (line ('.') + cnt, start+1)
89
90       " We're done
91       break
92     endif
93   endfor
94
95 endfunction
96
97
98 function! jp:ProcessTemplate (info, template)
99   
100   let matchpos  = 0
101   let variables = {}
102
103   let str = join (a:template, ' ')
104
105   " Detect all variable names of the template
106   while 1
107     " Find next variable start and end position
108     let start = match    (str, '${\(\w\|\s\)\+}', matchpos)
109     let end   = matchend (str, '${\(\w\|\s\)\+}', matchpos)
110
111     if start < 0
112       " Stop search if there is no variable left
113       break
114     else
115       if str[start+2 : end-2] == 'cursor'
116         let matchpos = end
117       else
118         " Add variable name (without ${}) to the dictionary
119         let variables[str[start+2 : end-2]] = 0
120     
121         " Start next search at the end position of this variable
122         let matchpos = end
123       endif
124     endif
125   endwhile
126
127   " Ask the user to enter values for all variables
128   for name in keys (variables)
129     let variables[name] = input (name . ": ")
130   endfor
131
132   " Expand all variables
133   let index = 0
134   while index < len (a:template)
135     for var in items (variables)
136       let expr = '${' . var[0] . '}'
137       let a:template[index] = substitute (a:template[index], expr, var[1], 'g')
138     endfor
139     let index = index + 1
140   endwhile
141
142   " Backup characters before and after the template name
143   let before = strpart (getline ('.'), 0, a:info['start'])
144   let after  = strpart (getline ('.'), a:info['end'])
145
146   " Generate indentation
147   let indent = repeat (' ', a:info['indent'])
148
149   " Insert template into the code line by line
150   for cnt in range (0, len (a:template)-1)
151     if cnt == 0 
152       call setline (line ('.'), before . a:template[cnt])
153     else
154       call append (line ('.') + cnt - 1, indent . a:template[cnt])
155     endif
156     if cnt == len (a:template)-1
157       call setline (line ('.') + cnt, getline (line ('.') + cnt) . after)
158
159       " Move cursor to the end of the inserted template. ${cursor} may
160       " overwrite this
161       call cursor(line ('.'), len (getline (line ('.') + cnt)))
162     endif
163   endfor
164
165   " Set the cursor position
166   call jp:SetCursorPosition (cnt)
167
168   " Return to insert mode
169   startinsert!
170
171 endfunction
172
173
174 function! jp:InsertTemplate ()
175
176   " Determine the template directory
177   let templateDir = exists ('g:jpTemplateDir') ? g:jpTemplateDir : s:defaultTemplateDir
178
179   " Determine the filetype subdirectory
180   let ftDir = &ft == '' ? 'general' : &ft
181
182   try
183     " Detect bounds of the template name as well as the name itself
184     let info = jp:GetTemplateInfo ()
185
186     " Generate the full template filename
187     let templateFile = templateDir .'/'. ftDir . '/' . info['name']
188
189     " Load the template file
190     let template = jp:ReadTemplate (info['name'], templateFile)
191   
192     " Do the hard work: Process the template
193     call jp:ProcessTemplate (info, template)
194   catch
195     " Inform the user about errors
196     echo s:debug ? v:exception . " (in " . v:throwpoint . ")" : v:exception
197   endtry
198
199 endfunction
200
201
202 " Map <Ctrl>+<Tab> to the template system
203 imap <Esc><Space> <Esc>:call jp:InsertTemplate()<CR>