" jptemplate.vim: " " A comfortable, interactive templating system for VIM. " " Copyright (c) 2008 Jannis Pohlmann . " " This program is free software; you can redistribute it and/or modify " it under the terms of the GNU General Public License as published by " the Free Software Foundation; either version 2 of the License, or (at " your option) any later version. " " This program is distributed in the hope that it will be useful, but " WITHOUT ANY WARRANTY; without even the implied warranty of " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU " General Public License for more details. " " You should have received a copy of the GNU General Public License " along with this program; if not, write to the Free Software " Foundation, Inc., 59 Temple Place, Suite 330, Boston, " MA 02111-1307 USA " " TODO: " - Code cleanup " " Default template dir used if g:jpTemplateDir is not set let s:defaultTemplateDir = $HOME . '/.vim/jptemplate' " Debug mode let s:debug = 1 function! jp:GetTemplateInfo () " Prepare info dictionary let info = {} " Get part of the line before the cursor let part = getline ('.')[0 : getpos ('.')[2]-1] " Get start and end position of the template name let info['start'] = match (part, '\(\w*\)$') let info['end'] = matchend (part, '\(\w*\)$') " Get template name let info['name'] = part[info['start'] : info['end']] " Throw exception if no template name could be found if info['name'] == '' throw 'No template name found at cursor' endif " Calculate line indentation let info['indent'] = max ([0, matchend (part, '^\s\+')]) " Return template name information return info endfunction function! jp:ReadTemplate (name, filename) " Try to read the template file and throw exception if that fails try return readfile (a:filename) catch throw 'Template "' . a:name . '" could not be found.' endtry endfunction function! jp:SetCursorPosition (lines) for cnt in range (0, a:lines) " Search for ${cursor} in the current line let str = getline (line ('.') + cnt) let start = match (str, '${cursor}') let end = matchend (str, '${cursor}') let before = strpart (str, 0, start) let after = strpart (str, end) if start >= 0 " Remove ${cursor} and move the cursor to the desired position call setline (line ('.') + cnt, before . after) call cursor (line ('.') + cnt, start+1) " We're done break endif endfor endfunction function! jp:ProcessTemplate (info, template) let matchpos = 0 let variables = {} let str = join (a:template, ' ') " Detect all variable names of the template while 1 " Find next variable start and end position let start = match (str, '${\(\w\|\s\)\+}', matchpos) let end = matchend (str, '${\(\w\|\s\)\+}', matchpos) if start < 0 " Stop search if there is no variable left break else if str[start+2 : end-2] == 'cursor' let matchpos = end else " Add variable name (without ${}) to the dictionary let variables[str[start+2 : end-2]] = 0 " Start next search at the end position of this variable let matchpos = end endif endif endwhile " Ask the user to enter values for all variables for name in keys (variables) let variables[name] = input (name . ": ") endfor " Expand all variables let index = 0 while index < len (a:template) for var in items (variables) let expr = '${' . var[0] . '}' let a:template[index] = substitute (a:template[index], expr, var[1], 'g') endfor let index = index + 1 endwhile " Backup characters before and after the template name let before = strpart (getline ('.'), 0, a:info['start']) let after = strpart (getline ('.'), a:info['end']) " Generate indentation let indent = repeat (' ', a:info['indent']) " Insert template into the code line by line for cnt in range (0, len (a:template)-1) if cnt == 0 call setline (line ('.'), before . a:template[cnt]) else call append (line ('.') + cnt - 1, indent . a:template[cnt]) endif if cnt == len (a:template)-1 call setline (line ('.') + cnt, getline (line ('.') + cnt) . after) " Move cursor to the end of the inserted template. ${cursor} may " overwrite this call cursor(line ('.'), len (getline (line ('.') + cnt))) endif endfor " Set the cursor position call jp:SetCursorPosition (cnt) " Return to insert mode startinsert! endfunction function! jp:InsertTemplate () " Determine the template directory let templateDir = exists ('g:jpTemplateDir') ? g:jpTemplateDir : s:defaultTemplateDir " Determine the filetype subdirectory let ftDir = &ft == '' ? 'general' : &ft try " Detect bounds of the template name as well as the name itself let info = jp:GetTemplateInfo () " Generate the full template filename let templateFile = templateDir .'/'. ftDir . '/' . info['name'] " Load the template file let template = jp:ReadTemplate (info['name'], templateFile) " Do the hard work: Process the template call jp:ProcessTemplate (info, template) catch " Inform the user about errors echo s:debug ? v:exception . " (in " . v:throwpoint . ")" : v:exception endtry endfunction " Map + to the template system imap :call jp:InsertTemplate()