Improve snippets parser

This commit is contained in:
Shougo Matsushita 2014-05-11 17:46:20 +09:00
parent a7c05372ba
commit e8b1af724b
9 changed files with 199 additions and 69 deletions

View File

@ -104,6 +104,7 @@ function! neosnippet#commands#_make_cache(filetype) "{{{
if has_key(snippets, filetype) if has_key(snippets, filetype)
return return
endif endif
let snippets[filetype] = {}
let path = join(neosnippet#helpers#get_snippets_directory(), ',') let path = join(neosnippet#helpers#get_snippets_directory(), ',')
let snippets_files = [] let snippets_files = []
@ -117,19 +118,19 @@ function! neosnippet#commands#_make_cache(filetype) "{{{
let snippets_files += split(globpath(path, glob), '\n') let snippets_files += split(globpath(path, glob), '\n')
endfor endfor
let snippet = {}
call map(reverse(s:get_list().uniq(snippets_files)),
\ "neosnippet#parser#_parse(snippet, v:val)")
let snippets = neosnippet#variables#snippets() let snippets = neosnippet#variables#snippets()
let snippets[filetype] = snippet for snippet_file in reverse(s:get_list().uniq(snippets_files))
let snippets[filetype] = extend(snippets[filetype],
\ neosnippet#parser#_parse(snippet_file))
endfor
endfunction"}}} endfunction"}}}
function! neosnippet#commands#_source(filename) "{{{ function! neosnippet#commands#_source(filename) "{{{
call neosnippet#init#check() call neosnippet#init#check()
let neosnippet = neosnippet#variables#current_neosnippet() let neosnippet = neosnippet#variables#current_neosnippet()
call neosnippet#parser#_parse(neosnippet.snippets, a:filename) let neosnippet.snippets = extend(neosnippet.snippets,
\ neosnippet#parser#_parse(a:filename))
endfunction"}}} endfunction"}}}
function! neosnippet#commands#_clear_markers() "{{{ function! neosnippet#commands#_clear_markers() "{{{

View File

@ -26,19 +26,25 @@
let s:save_cpo = &cpo let s:save_cpo = &cpo
set cpo&vim set cpo&vim
function! neosnippet#parser#_parse(snippets, snippets_file) "{{{ let s:Cache = neosnippet#util#get_vital().import('System.Cache')
let dup_check = {}
let snippet_dict = {}
let linenr = 1 function! neosnippet#parser#_parse(snippet_file) "{{{
if !filereadable(a:snippet_file)
if !filereadable(a:snippets_file)
call neosnippet#util#print_error( call neosnippet#util#print_error(
\ printf('snippet file "%s" is not found.', a:snippets_file)) \ printf('snippet file "%s" is not found.', a:snippet_file))
return a:snippets return {}
endif endif
for line in readfile(a:snippets_file) return s:parse(a:snippet_file)
endfunction"}}}
function! s:parse(snippet_file) "{{{
let dup_check = {}
let snippet_dict = {}
let linenr = 1
let snippets = {}
for line in readfile(a:snippet_file)
if line =~ '^\h\w*.*\s$' if line =~ '^\h\w*.*\s$'
" Delete spaces. " Delete spaces.
let line = substitute(line, '\s\+$', '', '') let line = substitute(line, '\s\+$', '', '')
@ -50,25 +56,26 @@ function! neosnippet#parser#_parse(snippets, snippets_file) "{{{
" Include snippets. " Include snippets.
let filename = matchstr(line, '^include\s\+\zs.*$') let filename = matchstr(line, '^include\s\+\zs.*$')
for snippets_file in split(globpath(join( for snippet_file in split(globpath(join(
\ neosnippet#helpers#get_snippets_directory(), ','), \ neosnippet#helpers#get_snippets_directory(), ','),
\ filename), '\n') \ filename), '\n')
call neosnippet#parser#_parse(a:snippets, snippets_file) let snippets = extend(snippets,
\ neosnippet#parser#_parse(snippets, snippet_file))
endfor endfor
elseif line =~ '^delete\s' elseif line =~ '^delete\s'
let name = matchstr(line, '^delete\s\+\zs.*$') let name = matchstr(line, '^delete\s\+\zs.*$')
if name != '' && has_key(a:snippets, name) if name != '' && has_key(snippets, name)
call filter(a:snippets, 'v:val.real_name !=# name') call filter(snippets, 'v:val.real_name !=# name')
endif endif
elseif line =~ '^snippet\s' elseif line =~ '^snippet\s'
if !empty(snippet_dict) if !empty(snippet_dict)
" Set previous snippet. " Set previous snippet.
call s:set_snippet_dict(snippet_dict, call s:set_snippet_dict(snippet_dict,
\ a:snippets, dup_check, a:snippets_file) \ snippets, dup_check, a:snippet_file)
endif endif
let snippet_dict = s:parse_snippet_name( let snippet_dict = s:parse_snippet_name(
\ a:snippets_file, line, linenr, dup_check) \ a:snippet_file, line, linenr, dup_check)
elseif !empty(snippet_dict) elseif !empty(snippet_dict)
if line =~ '^\s' || line == '' if line =~ '^\s' || line == ''
if snippet_dict.word == '' if snippet_dict.word == ''
@ -80,7 +87,7 @@ function! neosnippet#parser#_parse(snippets, snippets_file) "{{{
\ substitute(line, '^ *', '', '') . "\n" \ substitute(line, '^ *', '', '') . "\n"
else else
call s:add_snippet_attribute( call s:add_snippet_attribute(
\ a:snippets_file, line, linenr, snippet_dict) \ a:snippet_file, line, linenr, snippet_dict)
endif endif
endif endif
@ -90,13 +97,13 @@ function! neosnippet#parser#_parse(snippets, snippets_file) "{{{
if !empty(snippet_dict) if !empty(snippet_dict)
" Set previous snippet. " Set previous snippet.
call s:set_snippet_dict(snippet_dict, call s:set_snippet_dict(snippet_dict,
\ a:snippets, dup_check, a:snippets_file) \ snippets, dup_check, a:snippet_file)
endif endif
return a:snippets return snippets
endfunction"}}} endfunction"}}}
function! s:parse_snippet_name(snippets_file, line, linenr, dup_check) "{{{ function! s:parse_snippet_name(snippet_file, line, linenr, dup_check) "{{{
" Initialize snippet dict. " Initialize snippet dict.
let snippet_dict = { 'word' : '', 'linenr' : a:linenr, let snippet_dict = { 'word' : '', 'linenr' : a:linenr,
\ 'options' : neosnippet#parser#_initialize_snippet_options() } \ 'options' : neosnippet#parser#_initialize_snippet_options() }
@ -125,7 +132,7 @@ function! s:parse_snippet_name(snippets_file, line, linenr, dup_check) "{{{
let dup = a:dup_check[snippet_dict.name] let dup = a:dup_check[snippet_dict.name]
call neosnippet#util#print_error(printf( call neosnippet#util#print_error(printf(
\ 'Warning: %s:%d is overriding `%s` from %s:%d', \ 'Warning: %s:%d is overriding `%s` from %s:%d',
\ a:snippets_file, a:linenr, snippet_dict.name, \ a:snippet_file, a:linenr, snippet_dict.name,
\ dup.action__path, dup.action__line)) \ dup.action__path, dup.action__line))
call neosnippet#util#print_error(printf( call neosnippet#util#print_error(printf(
\ 'Please rename the snippet name or use `delete %s`.', \ 'Please rename the snippet name or use `delete %s`.',
@ -135,7 +142,7 @@ function! s:parse_snippet_name(snippets_file, line, linenr, dup_check) "{{{
return snippet_dict return snippet_dict
endfunction"}}} endfunction"}}}
function! s:add_snippet_attribute(snippets_file, line, linenr, snippet_dict) "{{{ function! s:add_snippet_attribute(snippet_file, line, linenr, snippet_dict) "{{{
" Allow overriding/setting of the description (abbr) of the snippet. " Allow overriding/setting of the description (abbr) of the snippet.
" This will override what was set via the snippet line. " This will override what was set via the snippet line.
if a:line =~ '^abbr\s' if a:line =~ '^abbr\s'
@ -161,7 +168,7 @@ function! s:add_snippet_attribute(snippets_file, line, linenr, snippet_dict) "{{
\ '^options\s\+\zs.*$'), '[,[:space:]]\+') \ '^options\s\+\zs.*$'), '[,[:space:]]\+')
if !has_key(a:snippet_dict.options, option) if !has_key(a:snippet_dict.options, option)
call neosnippet#util#print_error( call neosnippet#util#print_error(
\ printf('[neosnippet] %s:%d', a:snippets_file, a:linenr)) \ printf('[neosnippet] %s:%d', a:snippet_file, a:linenr))
call neosnippet#util#print_error( call neosnippet#util#print_error(
\ printf('[neosnippet] Invalid option name : "%s"', option)) \ printf('[neosnippet] Invalid option name : "%s"', option))
else else
@ -170,20 +177,20 @@ function! s:add_snippet_attribute(snippets_file, line, linenr, snippet_dict) "{{
endfor endfor
else else
call neosnippet#util#print_error( call neosnippet#util#print_error(
\ printf('[neosnippet] %s:%d', a:snippets_file, a:linenr)) \ printf('[neosnippet] %s:%d', a:snippet_file, a:linenr))
call neosnippet#util#print_error( call neosnippet#util#print_error(
\ printf('[neosnippet] Invalid syntax : "%s"', a:line)) \ printf('[neosnippet] Invalid syntax : "%s"', a:line))
endif endif
endfunction"}}} endfunction"}}}
function! s:set_snippet_dict(snippet_dict, snippets, dup_check, snippets_file) "{{{ function! s:set_snippet_dict(snippet_dict, snippets, dup_check, snippet_file) "{{{
if empty(a:snippet_dict) if empty(a:snippet_dict)
return return
endif endif
let action_pattern = '^snippet\s\+' . a:snippet_dict.name . '$' let action_pattern = '^snippet\s\+' . a:snippet_dict.name . '$'
let snippet = neosnippet#parser#_initialize_snippet( let snippet = neosnippet#parser#_initialize_snippet(
\ a:snippet_dict, a:snippets_file, \ a:snippet_dict, a:snippet_file,
\ a:snippet_dict.linenr, action_pattern, \ a:snippet_dict.linenr, action_pattern,
\ a:snippet_dict.name) \ a:snippet_dict.name)
let a:snippets[a:snippet_dict.name] = snippet let a:snippets[a:snippet_dict.name] = snippet

View File

@ -59,6 +59,19 @@ function! s:unload()
let s:loaded = {} let s:loaded = {}
endfunction endfunction
function! s:exists(name)
return s:_get_module_path(a:name) !=# ''
endfunction
function! s:search(pattern)
let target = substitute(a:pattern, '\.', '/', 'g')
let tailpath = printf('autoload/vital/%s/%s.vim', s:self_version, target)
let paths = s:_runtime_files(tailpath)
let modules = sort(map(paths, 's:_file2module(v:val)'))
return s:_uniq(modules)
endfunction
function! s:_import(name) function! s:_import(name)
if type(a:name) == type(0) if type(a:name) == type(0)
return s:_build_module(a:name) return s:_build_module(a:name)
@ -89,19 +102,16 @@ function! s:_get_module_path(name)
if a:name ==# '' if a:name ==# ''
let tailpath = printf('autoload/vital/%s.vim', s:self_version) let tailpath = printf('autoload/vital/%s.vim', s:self_version)
elseif a:name =~# '\v^\u\w*%(\.\u\w*)*$' elseif a:name =~# '\v^\u\w*%(\.\u\w*)*$'
let target = '/' . substitute(a:name, '\W\+', '/', 'g') let target = substitute(a:name, '\W\+', '/', 'g')
let tailpath = printf('autoload/vital/%s%s.vim', s:self_version, target) let tailpath = printf('autoload/vital/%s/%s.vim', s:self_version, target)
else else
throw 'vital: Invalid module name: ' . a:name throw 'vital: Invalid module name: ' . a:name
endif endif
if s:globpath_third_arg let paths = s:_runtime_files(tailpath)
let paths = split(globpath(&runtimepath, tailpath, 1), "\n")
else
let paths = split(globpath(&runtimepath, tailpath), "\n")
endif
call filter(paths, 'filereadable(v:val)') call filter(paths, 'filereadable(v:val)')
return s:_unify_path(get(paths, 0, '')) let path = get(paths, 0, '')
return path !=# '' ? s:_unify_path(path) : ''
endfunction endfunction
function! s:_scripts() function! s:_scripts()
@ -116,6 +126,12 @@ function! s:_scripts()
return scripts return scripts
endfunction endfunction
function! s:_file2module(file)
let filename = s:_unify_path(a:file)
let tail = matchstr(filename, 'autoload/vital/_\w\+/\zs.*\ze\.vim$')
return join(split(tail, '[\\/]\+'), '.')
endfunction
if filereadable(expand('<sfile>:r') . '.VIM') if filereadable(expand('<sfile>:r') . '.VIM')
function! s:_unify_path(path) function! s:_unify_path(path)
" Note: On windows, vim can't expand path names from 8.3 formats. " Note: On windows, vim can't expand path names from 8.3 formats.
@ -130,6 +146,16 @@ else
endfunction endfunction
endif endif
if s:globpath_third_arg
function! s:_runtime_files(path)
return split(globpath(&runtimepath, a:path, 1), "\n")
endfunction
else
function! s:_runtime_files(path)
return split(globpath(&runtimepath, a:path), "\n")
endfunction
endif
" Copy from System.Filepath " Copy from System.Filepath
if has('win16') || has('win32') || has('win64') if has('win16') || has('win32') || has('win64')
function! s:_is_absolute_path(path) function! s:_is_absolute_path(path)
@ -188,6 +214,25 @@ else
endfunction endfunction
endif endif
if exists('*uniq')
function! s:_uniq(list)
return uniq(a:list)
endfunction
else
function! s:_uniq(list)
let i = len(a:list) - 1
while 0 < i
if a:list[i] ==# a:list[i - 1]
call remove(a:list, i)
let i -= 2
else
let i -= 1
endif
endwhile
return a:list
endfunction
endif
function! s:_redir(cmd) function! s:_redir(cmd)
let [save_verbose, save_verbosefile] = [&verbose, &verbosefile] let [save_verbose, save_verbosefile] = [&verbose, &verbosefile]
set verbose=0 verbosefile= set verbose=0 verbosefile=

View File

@ -198,6 +198,18 @@ function! s:or(xs)
return s:any('v:val', a:xs) return s:any('v:val', a:xs)
endfunction endfunction
function! s:map_accum(expr, xs, init)
let memo = []
let init = a:init
for x in a:xs
let expr = substitute(a:expr, 'v:memo', init, 'g')
let expr = substitute(expr, 'v:val', x, 'g')
let [tmp, init] = eval(expr)
call add(memo, tmp)
endfor
return memo
endfunction
" similar to Haskell's Prelude.foldl " similar to Haskell's Prelude.foldl
function! s:foldl(f, init, xs) function! s:foldl(f, init, xs)
let memo = a:init let memo = a:init

View File

@ -1,9 +1,6 @@
let s:save_cpo = &cpo let s:save_cpo = &cpo
set cpo&vim set cpo&vim
" glob() wrapper which returns List
" and 'wildignore' does not affect
" this function's return value.
if v:version ># 703 || if v:version ># 703 ||
\ (v:version is 703 && has('patch465')) \ (v:version is 703 && has('patch465'))
function! s:glob(expr) function! s:glob(expr)
@ -16,9 +13,6 @@ else
endfunction endfunction
endif endif
" globpath() wrapper which returns List
" and 'suffixes' and 'wildignore' does not affect
" this function's return value.
function! s:globpath(path, expr) function! s:globpath(path, expr)
let R = globpath(a:path, a:expr, 1) let R = globpath(a:path, a:expr, 1)
return split(R, '\n') return split(R, '\n')
@ -215,19 +209,9 @@ function! s:is_unix()
return s:is_unix return s:is_unix
endfunction endfunction
function! s:_deprecated(fname, newname) function! s:_deprecated2(fname)
echomsg printf("Vital.Prelude.%s is deprecated! Please use %s instead.", echomsg printf("Vital.Prelude.%s is deprecated!",
\ a:fname, a:newname) \ a:fname)
endfunction
function! s:print_error(message)
call s:_deprecated('print_error', 'Vital.Vim.Message.error')
echohl ErrorMsg
for m in split(a:message, "\n")
echomsg m
endfor
echohl None
endfunction endfunction
function! s:smart_execute_command(action, word) function! s:smart_execute_command(action, word)
@ -277,6 +261,8 @@ function! s:set_default(var, val)
endfunction endfunction
function! s:set_dictionary_helper(variable, keys, pattern) function! s:set_dictionary_helper(variable, keys, pattern)
call s:_deprecated2('set_dictionary_helper')
for key in split(a:keys, '\s*,\s*') for key in split(a:keys, '\s*,\s*')
if !has_key(a:variable, key) if !has_key(a:variable, key)
let a:variable[key] = a:pattern let a:variable[key] = a:pattern

View File

@ -119,13 +119,7 @@ function! s:system(str, ...)
let args += [input] + rest let args += [input] + rest
endif endif
if use_vimproc let funcname = use_vimproc ? 'vimproc#system' : 'system'
" vimproc's parser seems to treat # as a comment
let args[0] = escape(args[0], '#')
let funcname = 'vimproc#system'
else
let funcname = 'system'
endif
let output = call(funcname, args) let output = call(funcname, args)
let output = s:iconv(output, 'char', &encoding) let output = s:iconv(output, 'char', &encoding)

View File

@ -0,0 +1,84 @@
" Utilities for output cache.
let s:save_cpo = &cpo
set cpo&vim
function! s:getfilename(cache_dir, filename)
return s:_encode_name(a:cache_dir, a:filename)
endfunction
function! s:filereadable(cache_dir, filename)
let cache_name = s:_encode_name(a:cache_dir, a:filename)
return filereadable(cache_name)
endfunction
function! s:readfile(cache_dir, filename)
let cache_name = s:_encode_name(a:cache_dir, a:filename)
return filereadable(cache_name) ? readfile(cache_name) : []
endfunction
function! s:writefile(cache_dir, filename, list)
let cache_name = s:_encode_name(a:cache_dir, a:filename)
call writefile(a:list, cache_name)
endfunction
function! s:delete(cache_dir, filename)
echoerr 'System.Cache.delete() is obsolete. Use its deletefile() instead.'
return call('s:deletefile', a:cache_dir, a:filename)
endfunction
function! s:deletefile(cache_dir, filename)
let cache_name = s:_encode_name(a:cache_dir, a:filename)
return delete(cache_name)
endfunction
function! s:_encode_name(cache_dir, filename)
" Check cache directory.
if !isdirectory(a:cache_dir)
call mkdir(a:cache_dir, 'p')
endif
let cache_dir = a:cache_dir
if cache_dir !~ '/$'
let cache_dir .= '/'
endif
return cache_dir . s:_create_hash(cache_dir, a:filename)
endfunction
function! s:check_old_cache(cache_dir, filename)
" Check old cache file.
let cache_name = s:_encode_name(a:cache_dir, a:filename)
let ret = getftime(cache_name) == -1
\ || getftime(cache_name) <= getftime(a:filename)
if ret && filereadable(cache_name)
" Delete old cache.
call delete(cache_name)
endif
return ret
endfunction
function! s:_create_hash(dir, str)
if len(a:dir) + len(a:str) < 150
let hash = substitute(substitute(
\ a:str, ':', '=-', 'g'), '[/\\]', '=+', 'g')
elseif exists('*sha256')
let hash = sha256(a:str)
else
" Use simple hash.
let sum = 0
for i in range(len(a:str))
let sum += char2nr(a:str[i]) * (i + 1)
endfor
let hash = printf('%x', sum)
endif
return hash
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo
" vim:set et ts=2 sts=2 sw=2 tw=0:

View File

@ -1,6 +1,7 @@
neosnippet neosnippet
450727f d3554a5
Prelude Prelude
Data.List Data.List
Process Process
System.Cache

View File

@ -612,8 +612,8 @@ Or if you want to include a whole directory with file type snippets.
< <
If you include snippet files it can happen that the same snippet name is used If you include snippet files it can happen that the same snippet name is used
multiple times in different snippet files. Neosnippet produces a warning if it multiple times in snippet files. Neosnippet produces a warning if it detects
detects this. If you want to overwrite a snippet explicitly, please use: this. If you want to overwrite a snippet explicitly, please use:
> >
delete snippets_name delete snippets_name