" " Template system for Vim " " Copyright (C) 2012 Adrian Perez de Castro " Copyright (C) 2005 Adrian Perez de Castro " " Distributed under terms of the MIT license. " if exists("g:templates_plugin_loaded") finish endif let g:templates_plugin_loaded = 1 if !exists('g:templates_name_prefix') let g:templates_name_prefix = ".vim-template:" endif if !exists('g:templates_global_name_prefix') let g:templates_global_name_prefix = "=template=" endif if !exists('g:templates_debug') let g:templates_debug = 0 endif if !exists('g:templates_tr_in') let g:templates_tr_in = [ '.', '*', '?' ] endif if !exists('g:templates_tr_out') let g:templates_tr_out = [ '\.', '.*', '\?' ] endif if !exists('g:templates_fuzzy_start') let g:templates_fuzzy_start = 1 endif if !exists('g:templates_directory') let g:templates_directory = [] elseif type(g:templates_directory) == type('') " Convert string value to a list with one element. let s:tmp = g:templates_directory unlet g:templates_directory let g:templates_directory = [ s:tmp ] unlet s:tmp endif if !exists('g:templates_no_builtin_templates') let g:templates_no_builtin_templates = 0 endif " Put template system autocommands in their own group. {{{1 if !exists('g:templates_no_autocmd') let g:templates_no_autocmd = 0 endif if !g:templates_no_autocmd augroup Templating autocmd! autocmd BufNewFile * call TLoad() augroup END endif function Debug(mesg) if g:templates_debug echom(a:mesg) endif endfunction " normalize the path " replace the windows path sep \ with / function NormalizePath(path) return substitute(a:path, "\\", "/", "g") endfunction " Template searching. {{{1 " Returns a string containing the path of the parent directory of the given " path. Works like dirname(3). It also simplifies the given path. function DirName(path) let l:tmp = NormalizePath(a:path) return substitute(l:tmp, "[^/][^/]*/*$", "", "") endfunction " Default templates directory let s:default_template_dir = DirName(DirName(expand(""))) . "templates" " Find the target template in windows " " In windows while we clone the symbol link from github " it will turn to normal file, so we use this function " to figure out the destination file function TFindLink(path, template) if !filereadable(a:path . a:template) return a:template endif let l:content = readfile(a:path . a:template, "b") if len(l:content) != 1 return a:template endif if filereadable(a:path . l:content[0]) return TFindLink(a:path, l:content[0]) else return a:template endif endfunction " Translate a template file name into a regular expression to test for matching " against a given filename. As of writing this behavior is something like this: " (with a g:templates_name_prefix set as 'template.') " " template.py -> ^.*py$ " " template.test.py -> ^.*test.py$ " function TemplateToRegex(template, prefix) let l:template_base_name = fnamemodify(a:template,":t") let l:template_glob = strpart(l:template_base_name, len(a:prefix)) " Translate the template's glob into a normal regular expression let l:in_escape_mode = 0 let l:template_regex = "" for l:c in split(l:template_glob, '\zs') if l:in_escape_mode == 1 if l:c == '\' let l:template_regex = l:template_regex . '\\' else let l:template_regex = l:template_regex . l:c endif let l:in_escape_mode = 0 else if l:c == '\' let l:in_escape_mode = 1 else let l:tr_index = index(g:templates_tr_in, l:c) if l:tr_index != -1 let l:template_regex = l:template_regex . g:templates_tr_out[l:tr_index] else let l:template_regex = l:template_regex . l:c endif endif endif endfor if g:templates_fuzzy_start return l:template_regex . '$' else return '^' . l:template_regex . '$' endif endfunction " Given a template and filename, return a score on how well the template matches " the given filename. If the template does not match the file name at all, " return 0 function TemplateBaseNameTest(template, prefix, filename) let l:tregex = TemplateToRegex(a:template, a:prefix) " Ensure that we got a valid regex if l:tregex == "" return 0 endif " For now only use the base of the filename.. this may change later " *Note* we also have to be careful because a:filename may also be the passed " in text from TLoadCmd... let l:filename_chopped = fnamemodify(a:filename,":t") " Check for a match let l:regex_result = match(l:filename_chopped,l:tregex) if l:regex_result != -1 " For a match return a score based on the regex length return len(l:tregex) else " No match return 0 endif endfunction " Returns the most specific / highest scored template file found in the given " path. Template files are found by using a glob operation on the current path " and the setting of g:templates_name_prefix. If no template is found in the " given directory, return an empty string function TDirectorySearch(path, template_prefix, file_name) let l:picked_template = "" let l:picked_template_score = 0 " All template files matching let l:templates = split(glob(a:path . a:template_prefix . "*"), "\n") for template in l:templates " Make sure the template is readable if filereadable(template) let l:current_score = \TemplateBaseNameTest(template, a:template_prefix, a:file_name) call Debug("template: " . template . " got scored: " . l:current_score) " Pick that template only if it beats the currently picked template " (here we make the assumption that template name length ~= template " specifity / score) if l:current_score > l:picked_template_score let l:picked_template = template let l:picked_template_score = l:current_score endif endif endfor if l:picked_template != "" call Debug("Picked template: " . l:picked_template) else call Debug("No template found") endif return l:picked_template endfunction " Searches for a [template] in a given [path]. " " If [upwards] is [1] the template is searched only in the given directory; " if it's zero it is searched all along the directory structure, going to " parent directory whenever a template is *not* found for a given [path]. If " it's greater than zero [upwards] is the maximum depth of directories that " will be traversed. " " If no template is found an empty string is returned. " function TSearch(path, template_prefix, file_name, upwards) " pick a template from the current path let l:picked_template = TDirectorySearch(a:path, a:template_prefix, a:file_name) if l:picked_template != "" if !has("win32") || !has("win64") return l:picked_template else echoerr( "Not yet implemented" ) " TODO " return a:path . TFindLink(a:path, a:template) endif else " File not found/not readable. if (a:upwards == 0) || (a:upwards > 1) " Check wether going upwards results in a different path... let l:pathUp = DirName(a:path) if l:pathUp != a:path " ...and traverse it. return TSearch(l:pathUp, a:template_prefix, a:file_name, a:upwards ? a:upwards-1 : 0) endif endif endif " Ooops, either we cannot go up in the path or [upwards] reached 1 return "" endfunction " Tries to find valid templates using the global g:templates_name_prefix as a glob " matcher for template files. The search is done as follows: " 1. The [path] passed to the function, [upwards] times up. " 2. The g:templates_directory directory, if it exists. " 3. Built-in templates from s:default_template_dir. " Returns an empty string if no template is found. " function TFind(path, name, up) let l:tmpl = TSearch(a:path, g:templates_name_prefix, a:name, a:up) if l:tmpl != '' return l:tmpl endif for l:directory in g:templates_directory let l:directory = NormalizePath(expand(l:directory) . '/') if isdirectory(l:directory) let l:tmpl = TSearch(l:directory, g:templates_global_name_prefix, a:name, 1) if l:tmpl != '' return l:tmpl endif endif endfor if g:templates_no_builtin_templates return '' endif return TSearch(NormalizePath(expand(s:default_template_dir) . '/'), g:templates_global_name_prefix, a:name, 1) endfunction " Template variable expansion. {{{1 " Makes a single [variable] expansion, using [value] as replacement. " function TExpand(variable, value) silent! execute "%s/%" . a:variable . "%/" . a:value . "/g" endfunction " Performs variable expansion in a template once it was loaded {{{2 " function TExpandVars() " Date/time values let l:day = strftime("%d") let l:year = strftime("%Y") let l:month = strftime("%m") let l:time = strftime("%H:%M") let l:date = exists("g:dateformat") ? strftime(g:dateformat) : \ (l:year . "-" . l:month . "-" . l:day) let l:fdate = l:date . " " . l:time let l:filen = expand("%:t:r") let l:filex = expand("%:e") let l:filec = expand("%:t") let l:fdir = expand("%:p:h:t") let l:hostn = hostname() let l:user = exists("g:username") ? g:username : \ (exists("g:user") ? g:user : $USER) let l:email = exists("g:email") ? g:email : (l:user . "@" . l:hostn) let l:guard = toupper(substitute(l:filec, "[^a-zA-Z0-9]", "_", "g")) let l:class = substitute(l:filen, "\\([a-zA-Z]\\+\\)", "\\u\\1\\e", "g") let l:macroclass = toupper(l:class) let l:camelclass = substitute(l:class, "_", "", "g") " Finally, perform expansions call TExpand("DAY", l:day) call TExpand("YEAR", l:year) call TExpand("DATE", l:date) call TExpand("TIME", l:time) call TExpand("USER", l:user) call TExpand("FDATE", l:fdate) call TExpand("MONTH", l:month) call TExpand("FILE", l:filen) call TExpand("FFILE", l:filec) call TExpand("FDIR", l:fdir) call TExpand("EXT", l:filex) call TExpand("MAIL", l:email) call TExpand("HOST", l:hostn) call TExpand("GUARD", l:guard) call TExpand("CLASS", l:class) call TExpand("MACROCLASS", l:macroclass) call TExpand("CAMELCLASS", l:camelclass) call TExpand("LICENSE", exists("g:license") ? g:license : "MIT") endfunction " }}}2 " Puts the cursor either at the first line of the file or in the place of " the template where the %HERE% string is found, removing %HERE% from the " template. " function TPutCursor() 0 " Go to first line before searching if search("%HERE%", "W") let l:column = col(".") let l:lineno = line(".") s/%HERE%// call cursor(l:lineno, l:column) endif endfunction " File name utils " " Ensures that the given file name is safe to be opened and will not be shell " expanded function NeuterFileName(filename) let l:neutered = fnameescape(a:filename) call Debug("Neutered " . a:filename . " to " . l:neutered) return l:neutered endfunction " Template application. {{{1 " Loads a template for the current buffer, substitutes variables and puts " cursor at %HERE%. Used to implement the BufNewFile autocommand. " function TLoad() if !line2byte( line( '$' ) + 1 ) == -1 return endif let l:file_name = expand("%:p") let l:file_dir = DirName(l:file_name) let l:depth = exists("g:template_max_depth") ? g:template_max_depth : 0 let l:tFile = TFind(l:file_dir, l:file_name, l:depth) call TLoadTemplate(l:tFile) endfunction " Like the previous one, TLoad(), but intended to be called with an argument " that either is a filename (so the file is loaded as a template) or " a template suffix (and the template is searched as usual). Of course this " makes variable expansion and cursor positioning. " function TLoadCmd(template) if filereadable(a:template) let l:tFile = a:template else let l:depth = exists("g:template_max_depth") ? g:template_max_depth : 0 let l:tName = g:templates_global_name_prefix . a:template let l:file_name = expand("%:p") let l:file_dir = DirName(l:file_name) let l:tFile = TFind(l:file_dir, a:template, l:depth) endif call TLoadTemplate(l:tFile) endfunction " Load the given file as a template function TLoadTemplate(template) if a:template != "" " Read template file and expand variables in it. let l:safeFileName = NeuterFileName(a:template) execute "keepalt 0r " . l:safeFileName call TExpandVars() " This leaves an extra blank line at the bottom, delete it execute line('$') . "d" call TPutCursor() setlocal nomodified endif endfunction " Commands {{{1 " Just calls the above function, pass either a filename or a template " suffix, as explained before =) " fun ListTemplateSuffixes(A,P,L) let l:templates = split(globpath(s:default_template_dir, g:templates_global_name_prefix . a:A . "*"), "\n") let l:res = [] for t in templates let l:suffix = substitute(t, ".*\\.", "", "") call add(l:res, l:suffix) endfor return l:res endfun command -nargs=1 -complete=customlist,ListTemplateSuffixes Template call TLoadCmd("") " Syntax autocommands {{{1 " " Enable the vim-template syntax for template files " Usually we'd put this in the ftdetect folder, but because " g:templates_name_prefix doesn't get defined early enough we have to add the " template detection from the plugin itself execute "au BufNewFile,BufRead " . g:templates_name_prefix . "* " \. "let b:vim_template_subtype = &filetype | " \. "set ft=vim-template" if !g:templates_no_builtin_templates execute "au BufNewFile,BufRead " \. s:default_template_dir . "/" . g:templates_global_name_prefix . "* " \. "let b:vim_template_subtype = &filetype | " \. "set ft=vim-template" endif for s:directory in g:templates_directory let s:directory = NormalizePath(expand(s:directory) . '/') if isdirectory(s:directory) execute "au BufNewFile,BufRead " \. s:directory . "/" . g:templates_global_name_prefix . "* " \. "let b:vim_template_subtype = &filetype | " \. "set ft=vim-template" endif unlet s:directory endfor " vim: fdm=marker