Download: lang-vim.js
" jptemplate.vim: " " A simple yet powerful interactive templating system for VIM. " " Version 1.5 (released 2008-07-08). " " Copyright (c) 2008 Jannis Pohlmann <jannis@xfce.org>. " " 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 " Reserved variable names let s:reservedVariables = ['date','shell','interactive_shell'] " Variable value history let s:rememberedValues = {} " Characters to be escaped before the substitute() call let s:escapeCharacters = '&~\' function! jp:Initialize() " List for default configuration let defaults = [] " Default configuration values call add(defaults, ['g:jpTemplateDir', $HOME . '/.vim/jptemplate']) call add(defaults, ['g:jpTemplateKey', '<C-Tab>']) call add(defaults, ['g:jpTemplateDateFormat', '%Y-%m-%d']) call add(defaults, ['g:jpTemplateDefaults', {}]) call add(defaults, ['g:jpTemplateVerbose', 0]) " Set default configuration for non-existent variables for var in filter(defaults, '!exists(v:val[0])') exec 'let ' . var[0] . ' = ' . string(var[1]) endfor endfunction 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 empty(info['name']) throw 'No template name found at cursor' endif " Determine directory to load the template from (skip empty directories; " each directory may override the ones before) for dir in filter([ 'general', &ft ], '!empty(v:val)') let filename = g:jpTemplateDir .'/'. dir .'/'. info['name'] if filereadable(filename) let info['filename'] = filename endif endfor " Throw exception if the template file does not exist in any of these " directories (or is not readable) if !has_key(info, 'filename') throw 'Template file not found' endif " Determine indentation let info['indent'] = matchstr(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:UpdateCursorPosition(lines, endColumn) " Define cursorPosition as the column to which the cursor is moved let cursorPosition = -1 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) let cursorPosition = start " We're done break endif endfor " Update cursor position in case no ${cursor} was found in the template " and the resulting template was not empty if cursorPosition == -1 && a:lines >= 0 if a:lines == 0 call cursor(line('.'), a:endColumn + 1) else call cursor(line('.') + a:lines, a:endColumn + 1) endif endif " Return to insert mode (distinguish between 'in the middle of the line' and " 'at the end of the line') if col('.') == len(getline('.')) startinsert! else startinsert endif endfunction function! jp:ParseExpression(expr) " Determine position of the separator between name and value let valuepos = match(a:expr, ':') " Extract name and value strings let name = valuepos >= 0 ? strpart(a:expr, 0, valuepos) : a:expr let value = valuepos >= 0 ? strpart(a:expr, valuepos + 1) : '' " Return list with both strings return [name, value] endfunction function! jp:EvaluateReservedVariable(name, value, variables) let result = '' if a:name == 'date' let result = strftime(empty(a:value) ? g:jpTemplateDateFormat : a:value) elseif a:name == 'shell' if !empty(a:value) let result = system(a:value) endif elseif a:name == 'interactive_shell' let command = input('interactive_shell: ', a:value) if !empty(command) let result = system(command) endif endif return result endfunction function! jp:ExpandTemplate(info, template) " Backup content before and after the template name let before = strpart(getline('.'), 0, a:info['start']) let after = strpart(getline('.'), a:info['end']) " Merge lines of the template and then split them up again. " This makes multi-line variable values possible let mergedTemplate = split(join(a:template, "\n"), "\n") " Define cnt as the number of inserted lines let cnt = 0 " Remove template string if the resulting template is empty if empty(mergedTemplate) call setline(line('.'), before . after) call cursor(line('.'), len(before) + 1) let cnt = -1 else " Insert template between before and after for cnt in range(0, len(mergedTemplate) - 1) if cnt == 0 call setline(line('.'), before . mergedTemplate[cnt]) else call append(line('.') + cnt - 1, a:info['indent'] . mergedTemplate[cnt]) endif if cnt == len(mergedTemplate) - 1 call setline(line('.') + cnt, getline(line('.') + cnt) . after) endif endfor endif " Define start and end columns of the template let startColumn = len(before) if empty(mergedTemplate) let endColumn = startColumn else if cnt == 0 let endColumn = startColumn + len(mergedTemplate[0]) else let endColumn = len(a:info['indent'] . mergedTemplate[len(mergedTemplate) - 1]) endif endif " Return number of inserted lines, start and end columns return [cnt, startColumn, endColumn] endfunction function! jp:ProcessTemplate(info, template) let matchpos = 0 let expressions = [] let variables = {} let reserved = {} " Make a string out of the template lines let s:str = join(a:template, ' ') " Detect all variable names of the template while 1 " Find next variable start and end position let start = match(s:str, '${[^{}]\+}', matchpos) let end = matchend(s:str, '${[^{}]\+}', matchpos) if start < 0 " Stop search if there is no variable left break else " Extract variable expression (remove '${' and '}') let expr = s:str[start+2 : end-2] " Extract variable name and default value */ let [name, value] = jp:ParseExpression(expr) if name == 'cursor' " Skip the ${cursor} variable elseif count(s:reservedVariables, name) > 0 let reserved[expr] = '' else " Only insert variables on their first appearance if !has_key(variables, name) " Add expression to the expression list call add(expressions, expr) " Set variable value to '' let variables[name] = '' endif " Check whether local default value is defined or not if empty(value) " If not, check if variable value is empty if empty(variables[name]) " If so, either set it to the last remembered value or the global " default if it exists if has_key(s:rememberedValues, name) let variables[name] = s:rememberedValues[name] elseif has_key(g:jpTemplateDefaults, name) let variables[name] = g:jpTemplateDefaults[name] endif endif else " Use local default(first occurence in the template only) let variables[name] = value endif endif " Start next search at the end position of this expression let matchpos = end endif endwhile " Ask the user to enter values for all variables for expr in expressions let [name, value] = jp:ParseExpression(expr) let variables[name] = input(name . ': ', variables[name]) let s:rememberedValues[name] = variables[name] endfor " Evaluate reserved variables for expr in keys(reserved) let [name, value] = jp:ParseExpression(expr) let replacement = jp:EvaluateReservedVariable(name, value, variables) let reserved[expr] = replacement endfor " Expand all variables (custom and reserved) for index in range(len(a:template)) for expr in expressions let [name, value] = jp:ParseExpression(expr) let expr = '${' . name . '\(:[^{}]\+\)\?}' let value = escape(variables[name], s:escapeCharacters) let a:template[index] = substitute(a:template[index], expr, value, 'g') endfor for [expr, value] in items(reserved) let expr = '${' . expr . '}' let value = escape(value, s:escapeCharacters) let a:template[index] = substitute(a:template[index], expr, value, 'g') endfor endfor " Insert template into the code line by line let [insertedLines, startColumn, endColumn] = jp:ExpandTemplate(a:info, a:template) " Update the cursor position and return to insert mode call jp:UpdateCursorPosition(insertedLines, endColumn) endfunction function! jp:InsertTemplate() try " Detect bounds of the template name as well as the name itself let info = jp:GetTemplateInfo() " Load the template file let template = jp:ReadTemplate(info['name'], info['filename']) " Do the hard work: Process the template call jp:ProcessTemplate(info, template) catch " Inform the user about errors echo g:jpTemplateVerbose ? v:exception . " (in " . v:throwpoint . ")" : v:exception endtry endfunction " Initialize jptemplate configuration call jp:Initialize() " Map keyboard shortcut to the template system exec 'imap ' . g:jpTemplateKey . ' <Esc>:call jp:InsertTemplate()<CR>'
This test Vim script code is jptemplate.