"============================================================================= " FILE: parser.vim " AUTHOR: Shougo Matsushita " License: MIT license {{{ " Permission is hereby granted, free of charge, to any person obtaining " a copy of this software and associated documentation files (the " "Software"), to deal in the Software without restriction, including " without limitation the rights to use, copy, modify, merge, publish, " distribute, sublicense, and/or sell copies of the Software, and to " permit persons to whom the Software is furnished to do so, subject to " the following conditions: " " The above copyright notice and this permission notice shall be included " in all copies or substantial portions of the Software. " " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS " OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. " IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY " CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, " TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE " SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. " }}} "============================================================================= let s:save_cpo = &cpo set cpo&vim let s:Cache = neosnippet#util#get_vital().import('System.Cache.Deprecated') function! neosnippet#parser#_parse_snippets(filename) abort "{{{ if !filereadable(a:filename) call neosnippet#util#print_error( \ printf('snippet file "%s" is not found.', a:filename)) return {} endif let cache_dir = neosnippet#variables#data_dir() if s:Cache.check_old_cache(cache_dir, a:filename) let [snippets, sourced] = s:parse(a:filename) if len(snippets) > 5 && !neosnippet#util#is_sudo() && !sourced call s:Cache.writefile( \ cache_dir, a:filename, [string(snippets)]) endif else sandbox let snippets = eval( \ s:Cache.readfile(cache_dir, a:filename)[0]) endif return snippets endfunction"}}} function! neosnippet#parser#_parse_snippet(filename, trigger) abort "{{{ if !filereadable(a:filename) call neosnippet#util#print_error( \ printf('snippet file "%s" is not found.', a:filename)) return {} endif let snippet_dict = { \ 'word' : join(readfile(a:filename), "\n\t"), \ 'name' : a:trigger, \ 'options' : neosnippet#parser#_initialize_snippet_options() \ } return neosnippet#parser#_initialize_snippet( \ snippet_dict, a:filename, 1, '', a:trigger) endfunction"}}} function! s:parse(snippets_file) abort "{{{ let dup_check = {} let snippet_dict = {} let linenr = 1 let snippets = {} let sourced = 0 for line in readfile(a:snippets_file) if line =~ '^\h\w*.*\s$' " Delete spaces. let line = substitute(line, '\s\+$', '', '') endif if line =~ '^#' " Ignore. elseif line =~ '^include' " Include snippets file. for file in split(globpath(join( \ neosnippet#helpers#get_snippets_directory(), ','), \ matchstr(line, '^include\s\+\zs.*$')), '\n') let snippets = extend(snippets, \ neosnippet#parser#_parse_snippets(file)) endfor elseif line =~ '^source' " Source Vim script file. for file in split(globpath(join( \ neosnippet#helpers#get_snippets_directory(), ','), \ matchstr(line, '^source\s\+\zs.*$')), '\n') execute 'source' fnameescape(file) let sourced = 1 endfor elseif line =~ '^delete\s' let name = matchstr(line, '^delete\s\+\zs.*$') if name != '' && has_key(snippets, name) call filter(snippets, 'v:val.real_name !=# name') endif elseif line =~ '^snippet\s' if !empty(snippet_dict) " Set previous snippet. call s:set_snippet_dict(snippet_dict, \ snippets, dup_check, a:snippets_file) endif let snippet_dict = s:parse_snippet_name( \ a:snippets_file, line, linenr, dup_check) elseif !empty(snippet_dict) if line =~ '^\s' || line == '' if snippet_dict.word == '' " Substitute head tab character. let line = substitute(line, '^\t', '', '') endif let snippet_dict.word .= \ substitute(line, '^ *', '', '') . "\n" else call s:add_snippet_attribute( \ a:snippets_file, line, linenr, snippet_dict) endif endif let linenr += 1 endfor if !empty(snippet_dict) " Set previous snippet. call s:set_snippet_dict(snippet_dict, \ snippets, dup_check, a:snippets_file) endif return [snippets, sourced] endfunction"}}} function! s:parse_snippet_name(snippets_file, line, linenr, dup_check) abort "{{{ " Initialize snippet dict. let snippet_dict = { \ 'word' : '', \ 'linenr' : a:linenr, \ 'options' : neosnippet#parser#_initialize_snippet_options() \ } " Try using the name without the description (abbr). let snippet_dict.name = matchstr(a:line, '^snippet\s\+\zs\S\+') " Fall back to using the name and description (abbr) combined. " SnipMate snippets may have duplicate names, but different " descriptions (abbrs). let description = matchstr(a:line, '^snippet\s\+\S\+\s\+\zs.*$') if description != '' && description !=# snippet_dict.name " Convert description. let snippet_dict.name .= '_' . \ substitute(substitute( \ description, '\W\+', '_', 'g'), '_\+$', '', '') endif " Collect the description (abbr) of the snippet, if set on snippet line. " This is for compatibility with SnipMate-style snippets. let snippet_dict.abbr = matchstr(a:line, \ '^snippet\s\+\S\+\s\+\zs.*$') " Check for duplicated names. if has_key(a:dup_check, snippet_dict.name) let dup = a:dup_check[snippet_dict.name] call neosnippet#util#print_error(printf( \ '%s:%d is overriding `%s` from %s:%d', \ a:snippets_file, a:linenr, snippet_dict.name, \ dup.action__path, dup.action__line)) call neosnippet#util#print_error(printf( \ 'Please rename the snippet name or use `delete %s`.', \ snippet_dict.name)) endif return snippet_dict endfunction"}}} function! s:add_snippet_attribute(snippets_file, line, linenr, snippet_dict) abort "{{{ " Allow overriding/setting of the description (abbr) of the snippet. " This will override what was set via the snippet line. if a:line =~ '^abbr\s' let a:snippet_dict.abbr = matchstr(a:line, '^abbr\s\+\zs.*$') elseif a:line =~ '^alias\s' let a:snippet_dict.alias = split(matchstr(a:line, \ '^alias\s\+\zs.*$'), '[,[:space:]]\+') elseif a:line =~ '^prev_word\s' let prev_word = matchstr(a:line, \ '^prev_word\s\+[''"]\zs.*\ze[''"]$') if prev_word == '^' " For backward compatibility. let a:snippet_dict.options.head = 1 else call neosnippet#util#print_error( \ 'prev_word must be "^" character.') endif elseif a:line =~ '^regexp\s' let a:snippet_dict.regexp = matchstr(a:line, \ '^regexp\s\+[''"]\zs.*\ze[''"]$') elseif a:line =~ '^options\s\+' for option in split(matchstr(a:line, \ '^options\s\+\zs.*$'), '[,[:space:]]\+') if !has_key(a:snippet_dict.options, option) call neosnippet#util#print_error( \ printf('%s:%d', a:snippets_file, a:linenr)) call neosnippet#util#print_error( \ printf('Invalid option name : "%s"', option)) else let a:snippet_dict.options[option] = 1 endif endfor else call neosnippet#util#print_error( \ printf('%s:%d', a:snippets_file, a:linenr)) call neosnippet#util#print_error( \ printf('Invalid syntax : "%s"', a:line)) endif endfunction"}}} function! s:set_snippet_dict(snippet_dict, snippets, dup_check, snippets_file) abort "{{{ if empty(a:snippet_dict) return endif let action_pattern = '^snippet\s\+' . a:snippet_dict.name . '$' let snippet = neosnippet#parser#_initialize_snippet( \ a:snippet_dict, a:snippets_file, \ a:snippet_dict.linenr, action_pattern, \ a:snippet_dict.name) let a:snippets[a:snippet_dict.name] = snippet let a:dup_check[a:snippet_dict.name] = snippet for alias in get(a:snippet_dict, 'alias', []) let alias_snippet = copy(snippet) let alias_snippet.word = alias let a:snippets[alias] = alias_snippet let a:dup_check[alias] = alias_snippet endfor endfunction"}}} function! neosnippet#parser#_initialize_snippet(dict, path, line, pattern, name) abort "{{{ let a:dict.word = substitute(a:dict.word, '\n\+$', '', '') if a:dict.word !~ '\n' \ && a:dict.word !~ \ neosnippet#get_placeholder_marker_substitute_pattern().'$' \ && a:dict.word !~ \ neosnippet#get_placeholder_marker_substitute_zero_pattern() " Add placeholder. let a:dict.word .= '${0}' endif if !has_key(a:dict, 'abbr') || a:dict.abbr == '' " Set default abbr. let abbr = substitute(a:dict.word, \ neosnippet#get_placeholder_marker_pattern(). '\|'. \ neosnippet#get_mirror_placeholder_marker_pattern(). \ '\|\s\+\|\n\|TARGET', ' ', 'g') let a:dict.abbr = a:dict.name else let abbr = a:dict.abbr endif let snippet = { \ 'word' : a:dict.name, 'snip' : a:dict.word, \ 'description' : a:dict.word, \ 'menu_template' : abbr, \ 'menu_abbr' : abbr, \ 'options' : a:dict.options, \ 'action__path' : a:path, 'action__line' : a:line, \ 'action__pattern' : a:pattern, 'real_name' : a:name, \} if has_key(a:dict, 'regexp') let snippet.regexp = a:dict.regexp endif return snippet endfunction"}}} function! neosnippet#parser#_initialize_snippet_options() abort "{{{ return { \ 'head' : 0, \ 'word' : \ g:neosnippet#expand_word_boundary, \ 'indent' : 0, \ 'oneshot' : 0, \ } endfunction"}}} function! neosnippet#parser#_get_completed_snippet(completed_item, next_text) abort "{{{ let item = a:completed_item " Set abbr let abbr = (item.abbr != '') ? item.abbr : item.word if len(item.menu) > 5 " Combine menu. let abbr .= ' ' . item.menu endif if item.info != '' let abbr = split(item.info, '\n')[0] endif let pairs = neosnippet#util#get_buffer_config( \ &filetype, '', \ 'g:neosnippet#completed_pairs', 'g:neosnippet#_completed_pairs', {}) let word_pattern = neosnippet#util#escape_pattern(item.word) let angle_pattern = word_pattern . '<.\+>(.*)' let no_key = index(keys(pairs), item.word[-1:]) < 0 if no_key && abbr !~# word_pattern . '\%(<.\+>\)\?(.*)' return '' endif let key = no_key ? '(' : item.word[-1:] if a:next_text[:0] ==# key " Disable auto pair return '' endif let pair = pairs[key] " Make snippet arguments let cnt = 1 let snippet = '' if no_key && abbr !~# angle_pattern " Auto key let snippet .= key endif if empty(filter(values(pairs), 'stridx(abbr, v:val) > 0')) " Pairs not found pattern let snippet .= '${' . cnt . '}' let cnt += 1 endif if abbr =~# angle_pattern " Add angle analysis let snippet .= '<' let args = '' for arg in split(substitute( \ neosnippet#parser#_get_in_paren('<', '>', abbr), \ '<\zs.\{-}\ze>', '', 'g'), '[^[]\zs\s*,\s*') if args != '' && arg !=# '...' let args .= ', ' endif let args .= printf('${%d:#:%s%s}', \ cnt, ((args != '' && arg ==# '...') ? ', ' : ''), \ escape(arg, '{}')) let cnt += 1 endfor let snippet .= args let snippet .= '>' if no_key let snippet .= key endif endif let args = '' for arg in split(substitute( \ neosnippet#parser#_get_in_paren(key, pair, abbr), \ key.'\zs.\{-}\ze'.pair . '\|<\zs.\{-}\ze>', '', 'g'), \ '[^[]\zs\s*,\s*') if key ==# '(' && arg ==# 'self' && &filetype ==# 'python' " Ignore self argument continue endif if args != '' && arg !=# '...' let args .= ', ' endif let args .= printf('${%d:#:%s%s}', \ cnt, ((args != '' && arg ==# '...') ? ', ' : ''), \ escape(arg, '{}')) let cnt += 1 endfor let snippet .= args if key != '(' && snippet == '' let snippet .= '${' . cnt . '}' let cnt += 1 endif let snippet .= pair let snippet .= '${' . cnt . '}' return snippet endfunction"}}} function! neosnippet#parser#_get_in_paren(key, pair, str) abort "{{{ let s = '' let level = 0 for c in split(a:str, '\zs') if c ==# a:key let level += 1 if level == 1 continue endif elseif c ==# a:pair if level == 1 && s != '' return s else let level -= 1 endif endif if level > 0 let s .= c endif endfor return '' endfunction"}}} let &cpo = s:save_cpo unlet s:save_cpo " vim: foldmethod=marker