1
0
mirror of https://github.com/amix/vimrc synced 2025-07-20 02:25:01 +08:00
This commit is contained in:
huangqundl
2017-03-17 23:12:53 +08:00
parent 47b213d974
commit cba39b7326
855 changed files with 59981 additions and 35298 deletions

View File

@ -14,7 +14,9 @@ additional contributions from:
* [asymmetric](https://github.com/asymmetric)
* [bpugh](https://github.com/bpugh)
* [bruno-](https://github.com/bruno-)
* [CharlesGueunet](https://github.com/CharlesGueunet)
* [darkwise](https://github.com/darkwise)
* [dreviejo](https://github.com/dreviejo)
* [fish-face](https://github.com/fish-face)
* [henrik](https://github.com/henrik)
* [holizz](https://github.com/holizz)
@ -29,17 +31,22 @@ additional contributions from:
* [lpil](https://github.com/lpil)
* [marutanm](https://github.com/marutanm)
* [MicahElliott](https://github.com/MicahElliott)
* [mikeastock](https://github.com/mikeastock)
* [muffinresearch](https://github.com/muffinresearch)
* [munyari](https://github.com/munyari)
* [pielgrzym](https://github.com/pielgrzym)
* [pose](https://github.com/pose)
* [r00k](https://github.com/r00k)
* [radicalbit](https://github.com/radicalbit)
* [redpill](https://github.com/redpill)
* [rglassett](http://github.com/rglassett)
* [robhudson](https://github.com/robhudson)
* [shinymayhem](https://github.com/shinymayhem)
* [Shraymonks](https://github.com/shraymonks)
* [sickill](https://github.com/sickill)
* [statik](https://github.com/statik)
* [steveno](https://github.com/steveno)
* [taq](https://github.com/taq)
* [thisgeek](https://github.com/thisgeek)
* [trusktr](https://github.com/trusktr)
* [Xandaros](https://github.com/Xandaros)

View File

@ -19,7 +19,9 @@ Garbas][garbas], [Marc Weber][marcweber], and [Adnan Zafar][ajzafar].
We recommend one of the following methods for installing SnipMate and its
dependencies. SnipMate depends on [vim-addon-mw-utils][mw-utils] and
[tlib][tlib]. Since SnipMate does not ship with any snippets, we suggest
[tlib][tlib].
> **NOTE:** SnipMate does not ship with any snippets out of the box. We suggest
looking at the [vim-snippets][vim-snippets] repository.
* Using [VAM][vam], add `vim-snippets` to the list of packages to be installed.
@ -35,17 +37,43 @@ looking at the [vim-snippets][vim-snippets] repository.
% git clone https://github.com/honza/vim-snippets.git
* Using [Vundle][vundle], add the following to your `vimrc` then run
`:BundleInstall`
`:PluginInstall`
Bundle "MarcWeber/vim-addon-mw-utils"
Bundle "tomtom/tlib_vim"
Bundle "garbas/vim-snipmate"
Plugin 'MarcWeber/vim-addon-mw-utils'
Plugin 'tomtom/tlib_vim'
Plugin 'garbas/vim-snipmate'
" Optional:
Bundle "honza/vim-snippets"
Plugin 'honza/vim-snippets'
## FAQ ##
> SnipMate doesn't work / My snippets aren't triggering
Try all of the following:
* Check that SnipMate is loaded. This can be done by looking for
`<Plug>snipMateTrigger` and similar maps in the output of `:imap`.
Additionally make sure either `<Plug>snipMateTrigger` or
`<Plug>snipMateNextOrTrigger` is mapped to the key you expect.
* Check that the snippets file you mean to use exists, and that it contains the
snippet you're trying to expand.
* Check that your snippets file is located inside a `foo/snippets` directory,
where `foo` is a path listed in your `runtimepath`.
* Check that your snippets file is in scope by either the filetype matching the
path of the snippet file or the scope explicitly loaded.
* Check if any snippets from your snippets file are available. This can be done
with the "show available snips" map, by default bound to `<C-R><Tab>` in
insert mode.
If all of the above check out, please open an issue stating your Vim version,
a sample snippet, and a description of exactly what happens when you try to
trigger a snippet.
> How does SnipMate determine which snippets to load? How can I separate, for
> example, my Rails snippets from my Ruby snippets?
@ -59,7 +87,7 @@ languages. For this we provide two options: scope aliases and the
`:SnipMateLoadScope` command. Scope aliases simply say "whenever this scope is
loaded, also load this other scope:
let g:snipMate = {}
let g:snipMate = get(g:, 'snipMate', {}) " Allow for vimrc re-sourcing
let g:snipMate.scope_aliases = {}
let g:snipMate.scope_aliases['ruby'] = 'ruby,rails'
@ -70,17 +98,48 @@ does `:SnipMateLoadScope rails` when editing a Rails project for example.
## Release Notes ##
### Master ###
### 0.89 - 2016-05-29 ###
* Various regex updates to legacy parser
* Addition of double bang syntax to completely remove a snippet from lookup
* Group various SnipMate autocommands
* Support setting 'shiftwidth' to 0
* Parser now operates linewise, adding some flexibility
* Mirror substitutions are more literal
* Mirror length is calculated correctly when substitutions occur
### 0.88 - 2015-04-04 ###
* Implement simple caching
* Remove expansion guards
* Add `:SnipMateLoadScope` command and buffer-local scope aliases
* Load `<scope>_*.snippets` files
* Use CursorMoved autocmd events entirely
* The nested branch has been merged
* A new snippet parser has been added. The g:snipmate.version as well as
version lines in snippet files determines which is used
* The new parser supports tab stops placed within placeholders,
substitutions, non-consecutive stop numbers, and fewer ambiguities
* The stop jumping code has been updated
* Tests have been added for the jumping code and the new parser
* The override branch has been merged
* The g:snipMate.override option is added. When enabled, if two snippets
share the same name, the later-loaded one is kept and the other discarded
* Override behavior can be enabled on a per-snippet basis with a bang (!) in
the snippet file
* Otherwise, SnipMate tries to preserve all snippets loaded
* Fix bug with mirrors in the first column
* Fix bug with tabs in indents ([#143][143])
* Fix bug with mirrors in placeholders
* Fix reading single snippet files
* Fix the use of the visual map at the end of a line
* Add `:SnipMateLoadScope` command and buffer-local scope aliases
* Load `<scope>_*.snippets` files
* Fix expansion of stops containing only the zero tab stop
* Remove select mode mappings
* Indent visual placeholder expansions and remove extraneous lines ([#177][177]
and [#178][178])
### 0.87 - 2014-01-04 ###
@ -106,3 +165,5 @@ does `:SnipMateLoadScope rails` when editing a Rails project for example.
[vundle]: https://github.com/gmarik/vundle
[143]: https://github.com/garbas/vim-snipmate/issues/143
[177]: https://github.com/garbas/vim-snipmate/issues/177
[178]: https://github.com/garbas/vim-snipmate/issues/178

View File

@ -5,7 +5,7 @@
let s:save_cpo = &cpo
set cpo&vim
function! s:map_if_not_mapped(lhs, rhs, mode)
function! s:map_if_not_mapped(lhs, rhs, mode) abort
let l:unique = s:overwrite ? '' : ' <unique>'
if !hasmapto(a:rhs, a:mode)
silent! exe a:mode . 'map' . l:unique a:lhs a:rhs

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
" will create a snippet on the fly which looks like this:
" abc(${1:a}, ${2:b}, ${3:c=None})
fun! snipMate_python_demo#Activate()
fun! snipMate_python_demo#Activate() abort
if !exists('g:snipMateSources')
let g:snipMateSources = {}
endif
@ -17,7 +17,7 @@ fun! snipMate_python_demo#Activate()
let g:snipMateSources['python'] = funcref#Function('snipMate_python_demo#FunctionsFromCurrentFileAndTags')
endf
fun! s:Add(dict, line, source, trigger)
fun! s:Add(dict, line, source, trigger) abort
let matched = matchlist(a:line,'def\s\+\([^( \t]\+\)[ \t]*(\([^)]*\)')
if len(matched) > 2
let name = matched[1]
@ -34,7 +34,7 @@ fun! s:Add(dict, line, source, trigger)
let sd[a:source] = name.'('.join(args,', ').')'
endif
endf
fun! snipMate_python_demo#FunctionsFromCurrentFileAndTags(scopes, trigger, result)
fun! snipMate_python_demo#FunctionsFromCurrentFileAndTags(scopes, trigger, result) abort
" getting all might be too much
if a:trigger == '*' | return | endif
if index(a:scopes, 'python') < 0 | return | endif

View File

@ -0,0 +1,228 @@
function! s:sfile() abort
return expand('<sfile>')
endfunction
let s:state_proto = {}
function! snipmate#jumping#state() abort
return copy(s:state_proto)
endfunction
function! s:listize_mirror(mirrors) abort
return map(copy(a:mirrors), '[v:val.line, v:val.col]')
endfunction
" Removes snippet state info
function! s:state_remove() dict abort
" Remove all autocmds in group snipmate_changes in the current buffer
unlet! b:snip_state
silent! au! snipmate_changes * <buffer>
endfunction
function! s:state_find_next_stop(backwards) dict abort
let self.stop_no += a:backwards? -1 : 1
while !has_key(self.stops, self.stop_no)
if self.stop_no == self.stop_count
let self.stop_no = 0
endif
if self.stop_no <= 0 && a:backwards
let self.stop_no = self.stop_count - 1
endif
let self.stop_no += a:backwards? -1 : 1
endwhile
endfunction
" Update state information to correspond to the given tab stop
function! s:state_set_stop(backwards) dict abort
call self.find_next_stop(a:backwards)
let self.cur_stop = self.stops[self.stop_no]
let self.stop_len = (type(self.cur_stop.placeholder) == type(0))
\ ? self.cur_stop.placeholder
\ : len(snipMate#placeholder_str(self.stop_no, self.stops))
let self.start_col = self.cur_stop.col
let self.end_col = self.start_col + self.stop_len
let self.mirrors = get(self.cur_stop, 'mirrors', [])
let self.old_mirrors = deepcopy(self.mirrors)
call cursor(self.cur_stop.line, self.cur_stop.col)
let self.prev_len = col('$')
let self.changed = 0
let ret = self.select_word()
if (self.stop_no == 0 || self.stop_no == self.stop_count - 1) && !a:backwards
call self.remove()
endif
return ret
endfunction
" Jump to the next/previous tab stop
function! s:state_jump_stop(backwards) dict abort
" Update changes just in case
" This seems to be only needed because insert completion does not trigger
" the CursorMovedI event
call self.update_changes()
" Store placeholder/location changes
let self.cur_stop.col = self.start_col
if self.changed
call self.remove_nested()
unlet! self.cur_stop.placeholder " avoid type error for old parsing version
let self.cur_stop.placeholder = [strpart(getline('.'),
\ self.start_col - 1, self.end_col - self.start_col)]
endif
return self.set_stop(a:backwards)
endfunction
function! s:state_remove_nested(...) dict abort
let id = a:0 ? a:1 : self.stop_no
if type(self.stops[id].placeholder) == type([])
for i in self.stops[id].placeholder
if type(i) == type([])
if type(i[1]) != type({})
call self.remove_nested(i[0])
call remove(self.stops, i[0])
else
call filter(self.stops[i[0]].mirrors, 'v:val isnot i[1]')
endif
endif
unlet i " Avoid E706
endfor
endif
endfunction
" Select the placeholder for the current tab stop
function! s:state_select_word() dict abort
let len = self.stop_len
if !len | return '' | endif
let l = col('.') != 1 ? 'l' : ''
if &sel == 'exclusive'
return "\<esc>".l.'v'.len."l\<c-g>"
endif
return len == 1 ? "\<esc>".l.'gh' : "\<esc>".l.'v'.(len - 1)."l\<c-g>"
endfunction
" Update the snippet as text is typed. The self.update_mirrors() function does
" the actual work.
" If the cursor moves outside of a placeholder, call self.remove()
function! s:state_update_changes() dict abort
let change_len = col('$') - self.prev_len
let self.changed = self.changed || change_len != 0
let self.end_col += change_len
let col = col('.')
if line('.') != self.cur_stop.line || col < self.start_col || col > self.end_col
return self.remove()
endif
call self.update(self.cur_stop, change_len, change_len)
if !empty(self.mirrors)
call self.update_mirrors(change_len)
endif
let self.prev_len = col('$')
endfunction
" Actually update the mirrors for any changed text
function! s:state_update_mirrors(change) dict abort
let newWordLen = self.end_col - self.start_col
let newWord = strpart(getline('.'), self.start_col - 1, newWordLen)
let changeLen = a:change
let curLine = line('.')
let curCol = col('.')
let oldStartSnip = self.start_col
let i = 0
for mirror in self.mirrors
for stop in values(filter(copy(self.stops), 'v:key != 0'))
if type(stop.placeholder) == type(0)
if mirror.line == stop.line && mirror.col > stop.col
\ && mirror.col < stop.col + stop.placeholder
let stop.placeholder += changeLen
endif
endif
endfor
if has_key(mirror, 'oldSize')
" recover the old size deduce the endline
let oldSize = mirror.oldSize
else
" first time, we use the intitial size
let oldSize = strlen(newWord)
endif
" Split the line into three parts: the mirror, what's before it, and
" what's after it. Then combine them using the new mirror string.
" Subtract one to go from column index to byte index
let theline = getline(mirror.line)
" part before the current mirror
let beginline = strpart(theline, 0, mirror.col - 1)
" current mirror transformation, and save size
let wordMirror= substitute(newWord, get(mirror, 'pat', ''), get(mirror, 'sub', ''), get(mirror, 'flags', ''))
let mirror.oldSize = strlen(wordMirror)
" end of the line, use the oldSize because with the transformation,
" the size of the mirror can be different from those of the snippet
let endline = strpart(theline, mirror.col + oldSize -1)
" Update other object on the line
call self.update(mirror, changeLen, mirror.oldSize - oldSize)
" reconstruct the line
let update = beginline.wordMirror.endline
call setline(mirror.line, update)
endfor
" Reposition the cursor in case a var updates on the same line but before
" the current tabstop
if oldStartSnip != self.start_col || mode() == 'i'
call cursor(0, curCol + self.start_col - oldStartSnip)
endif
endfunction
function! s:state_find_update_objects(item) dict abort
let item = a:item
let item.update_objects = []
" Filter the zeroth stop because it's duplicated as the last
for stop in values(filter(copy(self.stops), 'v:key != 0'))
if stop.line == item.line && stop.col > item.col
call add(item.update_objects, stop)
endif
for mirror in get(stop, 'mirrors', [])
if mirror.line == item.line && mirror.col > item.col
call add(item.update_objects, mirror)
endif
endfor
endfor
return item.update_objects
endfunction
function! s:state_update(item, change_len, mirror_change) dict abort
let item = a:item
if !exists('item.update_objects')
let item.update_objects = self.find_update_objects(a:item)
endif
let to_update = item.update_objects
for obj in to_update
" object does not necessarly have the same decalage
" than mirrors if mirrors use regexp
let obj.col += a:mirror_change
if obj is self.cur_stop
let self.start_col += a:change_len
let self.end_col += a:change_len
endif
endfor
endfunction
call extend(s:state_proto, snipmate#util#add_methods(s:sfile(), 'state',
\ [ 'remove', 'set_stop', 'jump_stop', 'remove_nested',
\ 'select_word', 'update_changes', 'update_mirrors',
\ 'find_next_stop', 'find_update_objects', 'update' ]), 'error')
" vim:noet:sw=4:ts=4:ft=vim

View File

@ -0,0 +1,139 @@
let s:sigil = nr2char(31)
let snipmate#legacy#sigil = s:sigil
" Prepare snippet to be processed by s:BuildTabStops
function! snipmate#legacy#process_snippet(snip) abort
let snippet = a:snip
let esc_bslash = '\%(\\\@<!\%(\\\\\)*\)\@<='
if exists('b:snipmate_visual')
let visual = substitute(b:snipmate_visual, "\n$", '', '')
unlet b:snipmate_visual
else
let visual = ''
endif
let snippet = s:substitute_visual(snippet, visual)
" Evaluate eval (`...`) expressions.
" Backquotes prefixed with a backslash "\" are ignored.
" And backslash can be escaped by doubling it.
" Using a loop here instead of a regex fixes a bug with nested "\=".
if stridx(snippet, '`') != -1
let new = []
let snip = split(snippet, esc_bslash . '`', 1)
let isexp = 0
for i in snip
if isexp
call add(new, substitute(snipmate#util#eval(i),
\ "\n\\%$", '', ''))
else
call add(new, i)
endif
let isexp = !isexp
endfor
let snippet = join(new, '')
let snippet = substitute(snippet, "\r", "\n", 'g')
let snippet = substitute(snippet, '\\`', "`", 'g')
endif
" Place all text after a colon in a tab stop after the tab stop
" (e.g. "${#:foo}" becomes "${:foo}foo").
" This helps tell the position of the tab stops later.
let snippet = substitute(snippet, esc_bslash . '\$\({\d\+:\(.\{-}\)}\|{\d\+}\)', s:sigil . '\1\2', 'g')
let snippet = substitute(snippet, esc_bslash . '\$\(\d\+\)', s:sigil . '\1', 'g')
let snippet = substitute(snippet, esc_bslash . '\\\$', '$', 'g')
let snippet = substitute(snippet, '\\\\', "\\", 'g')
" Update the a:snip so that all the $# become the text after
" the colon in their associated ${#}.
" (e.g. "${1:foo}" turns all "$1"'s into "foo")
let i = 0
if snippet !~ s:sigil . '{0'
let snippet .= s:sigil . '{0}'
endif
while snippet =~ s:sigil.'{'.i
let s = matchstr(snippet, s:sigil . '{' . i . ':\zs.\{-}\ze}')
if s != ''
let snippet = substitute(snippet, s:sigil . i, s.'&', 'g')
endif
let i += 1
endw
if &et " Expand tabs to spaces if 'expandtab' is set.
return substitute(snippet, '\t', repeat(' ', snipmate#util#tabwidth()), 'g')
endif
return snippet
endfunction
" Builds a list of a list of each tab stop in the snippet containing:
" 1.) The tab stop's line number.
" 2.) The tab stop's column number
" (by getting the length of the string between the last "\n" and the
" tab stop).
" 3.) The length of the text after the colon for the current tab stop
" (e.g. "${1:foo}" would return 3).
" 4.) If the "${#:}" construct is given, another list containing all
" the matches of "$#", to be replaced with the placeholder. This list is
" composed the same way as the parent; the first item is the line number,
" and the second is the column.
function! snipmate#legacy#build_stops(snip, lnum, col, indent) abort
let stops = {}
let i = 0
let withoutVars = substitute(a:snip, s:sigil . '\d\+', '', 'g')
while a:snip =~ s:sigil . '{' . i
let beforeTabStop = matchstr(withoutVars, '^.*\ze'.s:sigil .'{'.i.'\D')
let withoutOthers = substitute(withoutVars, ''.s:sigil .'{\('.i.'\D\)\@!\d\+.\{-}}', '', 'g')
let stops[i] = {}
let stops[i].line = a:lnum + s:count(beforeTabStop, "\n")
let stops[i].col = a:indent + len(matchstr(withoutOthers, '[^\n]\{-}\ze'.s:sigil .'{'.i.'\D'))
let stops[i].placeholder = 0
let stops[i].mirrors = []
if stops[i].line == a:lnum
let stops[i].col += a:col
endif
" Get all $# matches in another list, if ${#:name} is given
if withoutVars =~ printf('%s{%d:', s:sigil, i)
let stops[i].placeholder = len(matchstr(withoutVars, ''.s:sigil .'{'.i.':\zs.\{-}\ze}'))
let withoutOthers = substitute(a:snip, ''.s:sigil .'{\d\+.\{-}}\|'.s:sigil .''.i.'\@!\d\+', '', 'g')
while match(withoutOthers, ''.s:sigil .''.i.'\(\D\|$\)') != -1
let stops[i].mirrors = get(stops[i], 'mirrors', [])
let beforeMark = matchstr(withoutOthers,
\ printf('^.\{-}\ze%s%s%d\(\D\|$\)',
\ repeat('.', stops[i].placeholder), s:sigil, i))
let line = a:lnum + s:count(beforeMark, "\n")
let col = a:indent + (line > a:lnum
\ ? len(matchstr(beforeMark, '.*\n\zs.*'))
\ : a:col + len(beforeMark))
call add(stops[i].mirrors, { 'line' : line, 'col' : col })
let withoutOthers = substitute(withoutOthers, ''.s:sigil .''.i.'\ze\(\D\|$\)', '', '')
endw
endif
let i += 1
endw
let stops[i] = stops[0]
return [stops, i + 1]
endfunction
function! s:substitute_visual(snippet, visual) abort
let lines = []
for line in split(a:snippet, "\n")
let indent = matchstr(line, '^\t\+')
call add(lines, substitute(line, '{VISUAL}',
\ substitute(escape(a:visual, '%\'), "\n", "\n" . indent, 'g'), 'g'))
endfor
return join(lines, "\n")
endfunction
" Counts occurences of haystack in needle
function! s:count(haystack, needle) abort
let counter = 0
let index = stridx(a:haystack, a:needle)
while index != -1
let index = stridx(a:haystack, a:needle, index+1)
let counter += 1
endw
return counter
endfunction

View File

@ -0,0 +1,323 @@
" Snippet definition parsing code
function! s:sfile() abort
return expand('<sfile>')
endfunction
let s:parser_proto = {}
let s:special_chars = "$`\n"
function! s:new_parser(text) abort
let ret = copy(s:parser_proto)
let ret.input = a:text
let ret.len = strlen(ret.input)
let ret.pos = -1
let ret.indent = 0
let ret.value = []
let ret.vars = {}
let ret.stored_lines = []
call ret.advance()
return ret
endfunction
function! s:parser_advance(...) dict abort
let self.pos += a:0 ? a:1 : 1
let self.next = self.input[self.pos]
endfunction
function! s:parser_same(tok) dict abort
if self.next == a:tok
call self.advance()
return 1
else
return 0
endif
endfunction
function! s:parser_id() dict abort
if self.input[(self.pos):(self.pos+5)] == 'VISUAL'
call self.advance(6)
return 'VISUAL'
elseif self.next =~ '\d'
let end = matchend(self.input, '\d\+', self.pos)
let res = strpart(self.input, self.pos, end - self.pos)
call self.advance(end - self.pos)
return +res " force conversion to Number
endif
return -1
endfunction
function! s:parser_add_var(var) dict abort
let id = a:var[0]
if !has_key(self.vars, id)
let self.vars[id] = { 'instances' : [] }
endif
call add(self.vars[id].instances, a:var)
endfunction
function! s:parser_var() dict abort
let ret = []
if self.same('{')
let id = self.id()
if id >= 0
call add(ret, id)
call extend(ret, self.varend())
endif
else
let id = self.id()
if id >= 0
call add(ret, id)
endif
endif
return ret
endfunction
function! s:parser_varend() dict abort
let ret = []
if self.same(':')
call extend(ret, self.placeholder())
elseif self.same('/')
call add(ret, self.subst())
endif
call self.same('}')
return ret
endfunction
function! s:parser_placeholder() dict abort
let ret = self.text('}')
return empty(ret) ? [''] : ret
endfunction
function! s:parser_subst() dict abort
let ret = {}
let ret.pat = self.pat()
if self.same('/')
let ret.sub = self.pat(1)
endif
if self.same('/')
let ret.flags = self.pat(1)
endif
return ret
endfunction
function! s:parser_pat(...) dict abort
let val = ''
while self.pos < self.len
if self.same('\')
if self.next == '/'
let val .= '/'
call self.advance()
elseif a:0 && self.next == '}'
let val .= '}'
call self.advance()
else
let val .= '\'
endif
elseif self.next == '/' || a:0 && self.next == '}'
break
else
let val .= self.next
call self.advance()
endif
endwhile
return val
endfunction
function! s:parser_expr() dict abort
let str = self.string('`')
call self.same('`')
return snipmate#util#eval(str)
endfunction
function! s:parser_string(till, ...) dict abort
let val = ''
let till = '\V\[' . escape(a:till, '\') . ']'
while self.pos < self.len
if self.same('\')
if self.next != "\n"
let val .= self.next
endif
call self.advance()
elseif self.next =~# till
break
elseif self.next == "\t"
let self.indent += 1
let val .= s:indent(1)
call self.advance()
else
let val .= self.next
call self.advance()
endif
endwhile
return val
endfunction
function! s:join_consecutive_strings(list) abort
let list = a:list
let pos = 0
while pos + 1 < len(list)
if type(list[pos]) == type('') && type(list[pos+1]) == type('')
let list[pos] .= list[pos+1]
call remove(list, pos + 1)
else
let pos += 1
endif
endwhile
endfunction
function! s:parser_text(till) dict abort
let ret = []
let target = ret
while self.pos < self.len
let lines = []
if self.same('$')
let var = self.var()
if !empty(var)
if var[0] is# 'VISUAL'
let lines = s:visual_placeholder(var, self.indent)
" Remove trailing newline. See #245
if lines[-1] == '' && self.next == "\n"
call remove(lines, -1)
endif
elseif var[0] >= 0
call add(target, var)
call self.add_var(var)
endif
endif
elseif self.same('`')
let lines = split(self.expr(), "\n", 1)
else
let lines = [self.string(a:till . s:special_chars)]
endif
if !empty(lines)
call add(target, lines[0])
call extend(self.stored_lines, lines[1:-2])
" Don't change targets if there's only one line
if exists("lines[1]")
let target = [lines[-1]]
endif
endif
" Empty lines are ignored if this is tested at the start of an iteration
if self.next ==# a:till
break
endif
endwhile
call s:join_consecutive_strings(ret)
if target isnot ret
call s:join_consecutive_strings(target)
call extend(self.stored_lines, target)
endif
return ret
endfunction
function! s:parser_line() dict abort
let ret = []
if !empty(self.stored_lines)
call add(ret, remove(self.stored_lines, 0))
else
call extend(ret, self.text("\n"))
call self.same("\n")
endif
let self.indent = 0
return ret
endfunction
function! s:parser_parse() dict abort
while self.pos < self.len || !empty(self.stored_lines)
let line = self.line()
call add(self.value, line)
endwhile
endfunction
function! s:indent(count) abort
if &expandtab
let shift = repeat(' ', snipmate#util#tabwidth())
else
let shift = "\t"
endif
return repeat(shift, a:count)
endfunction
function! s:visual_placeholder(var, indent) abort
let arg = get(a:var, 1, {})
if type(arg) == type({})
let pat = get(arg, 'pat', '')
let sub = get(arg, 'sub', '')
let flags = get(arg, 'flags', '')
let content = split(substitute(get(b:, 'snipmate_visual', ''), pat, sub, flags), "\n", 1)
else
let content = split(get(b:, 'snipmate_visual', arg), "\n", 1)
endif
let indent = s:indent(a:indent)
call map(content, '(v:key != 0) ? indent . v:val : v:val')
return content
endfunction
function! s:parser_create_stubs() dict abort
for [id, dict] in items(self.vars)
for i in dict.instances
if len(i) > 1 && type(i[1]) != type({})
if !has_key(dict, 'placeholder')
let dict.placeholder = i[1:]
call add(i, dict)
else
unlet i[1:]
call s:create_mirror_stub(i, dict)
endif
else
call s:create_mirror_stub(i, dict)
endif
endfor
if !has_key(dict, 'placeholder')
let dict.placeholder = []
let j = 0
while len(dict.instances[j]) > 2
let j += 1
endwhile
let oldstub = remove(dict.instances[j], 1, -1)[-1]
call add(dict.instances[j], '')
call add(dict.instances[j], dict)
call filter(dict.mirrors, 'v:val isnot oldstub')
endif
unlet dict.instances
endfor
endfunction
function! s:create_mirror_stub(mirror, dict)
let mirror = a:mirror
let dict = a:dict
let stub = get(mirror, 1, {})
call add(mirror, stub)
let dict.mirrors = get(dict, 'mirrors', [])
call add(dict.mirrors, stub)
endfunction
function! snipmate#parse#snippet(text, ...) abort
let parser = s:new_parser(a:text)
call parser.parse()
if !(a:0 && a:1)
call parser.create_stubs()
endif
unlet! b:snipmate_visual
return [parser.value, parser.vars]
endfunction
call extend(s:parser_proto, snipmate#util#add_methods(s:sfile(), 'parser',
\ [ 'advance', 'same', 'id', 'add_var', 'var', 'varend',
\ 'line', 'string', 'create_stubs', 'pat',
\ 'placeholder', 'subst', 'expr', 'text', 'parse',
\ ]), 'error')

View File

@ -0,0 +1,30 @@
" The next function was based on s:function and s:add_methods in fugitive
" <https://github.com/tpope/vim-fugitive/blob/master/plugin/fugitive.vim>
function! snipmate#util#add_methods(sfile, namespace, methods) abort
let dict = {}
for name in a:methods
let dict[name] = function(join([matchstr(a:sfile, '<SNR>\d\+'),
\ a:namespace, name], '_'))
endfor
return dict
endfunction
function! snipmate#util#eval(arg)
try
let ret = eval(a:arg)
catch
echohl ErrorMsg
echom 'SnipMate:Expression: ' . v:exception
echohl None
let ret = ''
endtry
return type(ret) == type('') ? ret : string(ret)
endfunction
function! snipmate#util#tabwidth()
if &sts > 0
return &sts
else
return exists('*shiftwidth') ? shiftwidth() : &sw
endif
endfunction

View File

@ -1,12 +1,13 @@
*SnipMate.txt* Plugin for using TextMate-style snippets in Vim.
SnipMate *snippet* *snippets* *SnipMate*
Last Change: December 27, 2009
1. Description |SnipMate-description|
2. Usage |SnipMate-usage|
3. Interface and Settings |SnipMate-interface| |SnipMate-settings|
4. Snippet syntax |SnipMate-syntax|
4. Snippets |SnipMate-snippets|
- Snippet files |SnipMate-snippet-files|
- Snippet syntax |SnipMate-syntax|
5. Snippet sources |SnipMate-snippet-sources|
6. Disadvantages to TextMate |SnipMate-disadvantages|
7. Contact |SnipMate-contact|
@ -140,6 +141,31 @@ g:snipMate.no_default_aliases
the user or someone else will still be in
effect.
g:snipMate.snippet_version
The snippet parser version to use. The
possible values are:
0 Use the older parser
1 Use the newer parser
If unset, SnipMate defaults to version 0. The
value of this option is also used for all
.snippet files.
g:snipMate.override
As detailed below, when two snippets with the
same name and description are loaded, both are
kept and differentiated by the location of the
file they were in. When this option is enabled
(set to 1), the snippet originating in the
last loaded file is kept, similar to how Vim
maps and other settings work. Note: Load order
is determined by 'runtimepath'.
g:snipMate.description_in_completion
If set to 1 (default is 0), snippet
descriptions will be included in the popup
menu used for snippet completion, like with
<Plug>snipMateShow.
g:snipMate['no_match_completion_feedkeys_chars']
A string inserted when no match for a trigger
is found. By default a tab is inserted
@ -152,11 +178,13 @@ Mappings~
The mappings SnipMate uses can be customized with the |:map| commands. For
example, to change the key that triggers snippets and moves to the next
tabstop, >
tab stop, >
:imap <C-J> <Plug>snipMateNextOrTrigger
:smap <C-J> <Plug>snipMateNextOrTrigger
Note: The noremap variants of the map commands must NOT be used.
The list of possible <Plug> mappings is as follows:
<Plug>snipMateNextOrTrigger Default: <Tab> Mode: Insert, Select
@ -186,12 +214,17 @@ Additionally, <CR> is mapped in visual mode in .snippets files for retabbing
snippets.
==============================================================================
SYNTAX *snippet-syntax* *SnipMate-syntax*
SNIPPETS *SnipMate-snippets*
*SnipMate-snippet-files*
Snippet Files ~
Note: SnipMate does not ship with any snippets.
SnipMate looks inside of each entry of 'rtp' (or |SnipMate-snippet-sources|)
for a directory named /snippets/. Based on the 'filetype' and 'syntax'
settings (taking into account the dotted syntax), the following files are read
for snippets: >
settings (dotted filetypes are parsed), the following files are read for
snippets: >
.../snippets/<scope>.snippets
.../snippets/<scope>_<name>.snippets
@ -199,7 +232,7 @@ for snippets: >
.../snippets/<scope>/<trigger>.snippet
.../snippets/<scope>/<trigger>/<description>.snippet
where <scope> is an entry in 'filetype' or 'syntax', <name> is an arbitrary
where <scope> is a scope or 'filetype' or 'syntax', <name> is an arbitrary
name, <trigger> is the trigger for a snippet, and <description> is
a description used for |SnipMate-multisnip|.
@ -215,14 +248,32 @@ looks something like: >
more expanded text
< *SnipMate-multisnip*
The description is optional. If it is left out and a second snippet inside the
same .snippets file uses the same trigger, the second one will overwrite the
first. Otherwise multisnip is used.
The description is optional. If it is left out, the description "default" is
used. When two snippets in the same scope have the same name and the same
description, SnipMate will try to preserve both. The g:snipMate.override
option disables this, in favor of keeping the last-loaded snippet. This can be
overridden on a per-snippet basis by defining the snippet with a bang (!): >
snippet! trigger optional description
expanded text
more expanded text
Two bangs will remove the trigger entirely from SnipMate's lookup. In this
case any snippet text is unused.
Note: Hard tabs in the expansion text are required. When the snippet is
expanded in the text and 'expandtab' is set, each tab will be replaced with
spaces based on 'softtabstop' if nonzero or 'shiftwidth' otherwise.
Which version parser the snippets in a file should be used with can be
specified with a version line, e.g.: >
version 1
Specification of a version applies to the snippets following it. Multiple
version specifications can appear in a single file to intermix version 0 and
version 1 snippets.
Comments can be made in .snippets files by starting a line with a # character.
However these can't be used inside of snippet definitions: >
@ -243,74 +294,104 @@ directive, for example: >
will tell SnipMate to also read html, javascript, and css snippets.
SNIPPET SYNTAX *snippet-syntax* *SnipMate-syntax*
Anywhere in a snippet, a backslash escapes the character following it,
regardless of whether that character is special or not. That is, '\a' will
always result in an 'a' in the output. A single backslash can be output by
using '\\'.
*SnipMate-tabstops*
Tab stops~
A tab stop, specified by ${#} where # is a number, tells SnipMate where to
position the cursor next. The special tab stop ${0} denotes the last cursor
position; in its absence, the cursor is placed at the end of the snippet.
When triggering a snippet, SnipMate will by default jump to the very end of
the snippet text. This can be changed through the use of tab stops: $1, $2,
and so on. After expansion, SnipMate will jump to the first tab stop. From
then on, the <Plug>snipMateNextOrTrigger map will jump to the next higher
numbered tabs top.
For example, to place the cursor first on the id of a <div> tag, allow
the user to press <tab> to go to the middle of it, and finally end after
</div>: >
In the case of an ambiguity, for example if a stop occurs just before
a literal number, braces may be placed around the stop number to resolve it:
${3}79 is the third tab stop followed by the string "79".
NOTE: In the version 0 snippet parser, the braces are mandatory.
*SnipMate-zero-tabstop*
SnipMate will always stop at the special zero tab stop $0. Once it jumps to
the zero tab stop, snippet expansion is finished. If the zero tab stop is not
present in a definition, it will be put at the end.
For example, to place the cursor first on the id of a <div> tag, then on its
class, and finally end editing its contents: >
snippet div
<div id="${1}">
${2}
<div id="$1" class="$2">
$0
</div>
< *SnipMate-placeholders* *SnipMate-mirrors*
Placeholders and Mirrors~
< *SnipMate-placeholders*
In addition to being simply a location, each tab stop contains a placeholder,
or some default text. The placeholder can be specified for every tab stop
(including the zero tab stop) with a colon after the stop ID, as in
${1:default text}. The braces are required only when specifying a placeholder.
Once a tab stop with a placeholder is reached, the placeholder will be
selected in |Select-mode|. For example, >
Placeholder text can be supplied using "${#:text}", where # is the number of
the tab stop. This text then can be copied throughout the snippet using "$#",
given # is the same number as used before. So, to make a C for loop: >
snippet div
<div id="${1:id}" class="${2:class}">
$0
</div>
Finally, placeholders can contain mirrors and evaluations (detailed below) and
even entire other tab stops. If the placeholder is edited, then these nested
tab stops are removed and skipped entirely. For example, >
snippet div
<div${1: id="${2:id}"}${3: class="${4:class}"}>
$0
</div>
When expanded, this snippet selects the entirety of the id attribute. If this
stop is edited, then the second tab stop is removed and the third tab stop
becomes the next one. If the first tab stop is left unedited, then SnipMate
jumps to the second tab stop. This allows the user to use a single div snippet
that can be used for instances where the id or class attributes are desired
and those where they are not.
*SnipMate-mirrors*
Mirrors~
A mirror is simply a copy of a tab stop's text, updated as the tab stop is
edited. These look like a tab stop without a placeholder; $1 for example. In
the event that no placeholder is specified for a certain tab stop--say $1--the
first instance becomes the tab stop and the rest become mirrors.
Additionally substitutions similar to |:substitute| can be performed. For
instance ${1/foo/bar/g} will replace all instances of "foo" in the $1 mirror
with "bar". This uses |substitute()| behind the scenes.
Note: Just like with tab stops, braces can be used to avoid ambiguities: ${1}2
is a mirror of the first tab stop followed by a 2. Version 0 of the snippet
parser offers no way to resolve such ambiguities.
As an example, >
snippet for
for (${2:i}=0; $2 < ${1:count}; $2++) {
${4}
for ($1 = ${2:start}; ${1:i} < ${3:end}; $1${4:++}) {
${0:/* code */}
}
This will cause "count" to first be selected and change if the user starts
typing. When <tab> is pressed, the "i" in ${2}'s position will be selected;
all $2 variables will default to "i" and automatically be updated if the user
starts typing.
< *SnipMate-eval*
Expression Evaluation~
NOTE: "$#" syntax is used only for mirrors, not for tab stops as in TextMate.
Mirrors can also be used inside of placeholders. For instance: >
snippet opt
<option value="${1:option}">${2:$1}</option>
Will, as usual, cause "option" to first be selected and update all the $1
variables if the user starts typing. Since one of these variables is inside of
${2}, this text will then be used as a placeholder for the next tab stop,
allowing the user to change it if he wishes.
To copy a value throughout a snippet without supplying default text, simply
use the "${#:}" construct without the text, e.g.: >
snippet foo
${1:}bar$1
< *SnipMate-visual*
There is a special placeholder called {VISUAL}. If you visually select text,
then press <Tab> Vim switches to insert mode. The next snippet you'll expand
will replace {VISUAL} by the text which was selected previously.
*SnipMate-eval*
Interpolated Vim Script~
Snippets can also contain Vim script commands that are executed (via |eval()|)
when the snippet is inserted. Commands are given inside backticks (`...`); for
TextMates's functionality, use the |system()| function. E.g.: >
Snippets can contain Vim script expressions that are evaluated as the snippet
is expanded. Expressions are specified inside backticks: >
snippet date
`system("date +%Y-%m-%d")`
`strftime("%Y-%m-%d")`
will insert the current date, assuming you are on a Unix system. Note that you
can also (and should) use |strftime()| for this example.
If the expression results in any Vim error, the error will be displayed (or
found in :messages) and the result of the expression will be the empty string.
Filename([{expr}] [, {defaultText}]) *SnipMate-Filename()*
@ -336,22 +417,39 @@ empty string if it hasn't. The second returns the filename if it's been named,
and "name" if it hasn't. The third returns the filename followed by "_foo" if
it has been named, and an empty string if it hasn't.
*SnipMate-visual*
The VISUAL Stop~
While tab stops have numeric IDs, a special one exists with the ID 'VISUAL'.
When a snippet is expanded, if any text had been grabbed with the
snipMateVisual mapping (see |SnipMate-mappings|), all instances of the VISUAL
stop will be replaced with it. Both transformations as well as a default
placeholder can be used with the VISUAL stop.
Note: Both $VISUAL and ${VISUAL} are valid in version 1 of the snippet parser.
In version 0, only {VISUAL} is valid (without the $), and neither
transformations nor a default placeholder can be used.
Example: >
snippet div
<div>
${0:${VISUAL:<!-- content -->}}
</div>
==============================================================================
SNIPPET SOURCES *SnipMate-snippet-sources*
SnipMate is configurable.
plugin/SnipMate.vim assigns three important keys: >
plugin/SnipMate.vim assigns a couple important keys: >
" default implementation collecting snippets by handlers
let g:SnipMate['get_snippets'] = SnipMate#GetSnippets
" default handler:
let g:SnipMateSources['default'] = SnipMate#DefaultPool
" default directories containing snippets:
let g:SnipMate['snippet_dirs']
\ = funcref#Function('return split(&runtimepath,",")')
You can override all of those settings.
You can override both of those settings.
You can see that the default set of snippets is determined by Vim's 'rtp'.
@ -398,25 +496,64 @@ See |SnipMate-syntax| for more details about all possible relative locations
to 'rtp' can be found in.
==============================================================================
DISADVANTAGES *SnipMate-disadvantages*
KNOWN ISSUES *SnipMate-known-issues*
SnipMate.vim currently has the following disadvantages to TextMate's snippets:
- Nested placeholders are not currently possible. E.g.: >
'<div${1: id="${2:some_id}}">${3}</div>'
< In TextMate this would first highlight ' id="some_id"', and if
you hit delete it would automatically skip ${2} and go to ${3}
on the next <tab>, but if you didn't delete it it would highlight
"some_id" first. You cannot do this in SnipMate.vim.
- Regex cannot be performed on variables, such as "${1/.*/\U&}"
- Placeholders cannot span multiple lines.
- Activating snippets in different scopes of the same file is
not possible.
- Vim formatting with fo=t or fo=a can mess up SnipMate.
Perhaps some of these features will be added in a later release.
==============================================================================
CHANGELOG *SnipMate-changelog*
0.89 - 2016-05-29
-----------------
* Various regex updates to legacy parser Addition of double bang syntax to
* completely remove a snippet from lookup Group various SnipMate autocommands
* Support setting 'shiftwidth' to 0 Parser now operates linewise, adding some
* flexibility Mirror substitutions are more literal Mirror length is
* calculated correctly when substitutions occur
0.88 - 2015-04-04
-----------------
* Implement simple caching
* Remove expansion guards
* Add `:SnipMateLoadScope` command and buffer-local scope aliases
* Load `<scope>_*.snippets` files
* Use CursorMoved autocmd events entirely
* The nested branch has been merged
* A new snippet parser has been added. The g:snipmate.version as well as
version lines in snippet files determines which is used
* The new parser supports tab stops placed within placeholders,
substitutions, non-consecutive stop numbers, and fewer ambiguities
* The stop jumping code has been updated
* Tests have been added for the jumping code and the new parser
* The override branch has been merged
* The g:snipMate.override option is added. When enabled, if two snippets
share the same name, the later-loaded one is kept and the other discarded
* Override behavior can be enabled on a per-snippet basis with a bang (!) in
the snippet file
* Otherwise, SnipMate tries to preserve all snippets loaded
* Fix bug with mirrors in the first column
* Fix bug with tabs in indents
<http://github.com/garbas/vim-snipmate/issues/143>
* Fix bug with mirrors in placeholders
* Fix reading single snippet files
* Fix the use of the visual map at the end of a line
* Fix expansion of stops containing only the zero tab stop
* Remove select mode mappings
* Indent visual placeholder expansions and remove extraneous lines
<http://github.com/garbas/vim-snipmate/issues/177>
<http://github.com/garbas/vim-snipmate/issues/178>
0.87 - 2014-01-04
-----------------

View File

@ -5,6 +5,6 @@ endif
let s:did_snip_helper = 1
" Automatically closes tag if in xhtml
fun! Close()
fun! Close() abort
return stridx(&ft, 'xhtml') == -1 ? '' : ' /'
endf

View File

@ -1,8 +0,0 @@
command! -buffer -range=% RetabSnip <line1>,<line2>call snipMate#RetabSnip()
vnoremap <buffer> <cr> :RetabSnip<cr>
if !exists('g:snippet_no_indentation_settings')
setlocal sw=4
setlocal tabstop=4
setlocal noexpandtab
endif

View File

@ -1 +1,20 @@
runtime! ftplugin/snippet.vim
" Vim filetype plugin for SnipMate snippets (.snippets and .snippet files)
if exists("b:did_ftplugin")
finish
endif
let b:did_ftplugin = 1
let b:undo_ftplugin = "setl et< sts< cms< fdm< fde<"
" Use hard tabs
setlocal noexpandtab softtabstop=0
setlocal foldmethod=expr foldexpr=getline(v:lnum)!~'^\\t\\\\|^$'?'>1':1
setlocal commentstring=#\ %s
setlocal nospell
command! -buffer -range=% RetabSnip
\ echom "This command is deprecated. Use :retab and = instead. Doing that now."
\ | <line1>,<line2>retab! | <line1>,<line2>normal =

View File

@ -0,0 +1,32 @@
" Simple indent support for SnipMate snippets files
if exists('b:did_indent')
finish
endif
let b:did_indent = 1
setlocal nosmartindent
setlocal indentkeys=!^F,o,O,=snippet,=version,=extends
setlocal indentexpr=GetSnippetIndent()
if exists("*GetSnippetIndent")
finish
endif
function! GetSnippetIndent()
let line = getline(v:lnum)
let prev_lnum = v:lnum - 1
let prev_line = prev_lnum != 0 ? getline(prev_lnum) : ""
if line =~# '\v^(snippet|extends|version) '
return 0
elseif indent(v:lnum) > 0
return indent(v:lnum)
elseif prev_line =~# '^snippet '
return &sw
elseif indent(prev_lnum) > 0
return indent(prev_lnum)
endif
return 0
endfunction

View File

@ -28,11 +28,14 @@ if (!exists('g:snipMateSources'))
let g:snipMateSources['default'] = funcref#Function('snipMate#DefaultPool')
endif
au BufRead,BufNewFile *.snippet set ft=snippet
au FileType snippet setl noet nospell
au BufRead,BufNewFile *.snippets set ft=snippets
au FileType snippets setl noet nospell fdm=expr fde=getline(v:lnum)!~'^\\t\\\\|^$'?'>1':1
augroup SnipMateDetect
au BufRead,BufNewFile *.snippet,*.snippets setlocal filetype=snippets
au FileType snippets if expand('<afile>:e') =~# 'snippet$'
\ | setlocal syntax=snippet
\ | else
\ | setlocal syntax=snippets
\ | endif
augroup END
inoremap <silent> <Plug>snipMateNextOrTrigger <C-R>=snipMate#TriggerSnippet()<CR>
snoremap <silent> <Plug>snipMateNextOrTrigger <Esc>a<C-R>=snipMate#TriggerSnippet()<CR>
@ -89,11 +92,12 @@ endif
let g:snipMate['get_snippets'] = get(g:snipMate, 'get_snippets', funcref#Function("snipMate#GetSnippets"))
" List of paths where snippets/ dirs are located, or a function returning such
" a list
let g:snipMate['snippet_dirs'] = get(g:snipMate, 'snippet_dirs', funcref#Function('return split(&runtimepath,",")'))
if type(g:snipMate['snippet_dirs']) == type([])
call map(g:snipMate['snippet_dirs'], 'expand(v:val)')
" List of paths where snippets/ dirs are located
let g:snipMate['snippet_dirs'] = get(g:snipMate, 'snippet_dirs', split(&rtp, ','))
if type(g:snipMate['snippet_dirs']) != type([])
echohl WarningMsg
echom "g:snipMate['snippet_dirs'] must be a List"
echohl None
endif
" _ is default scope added always
@ -103,18 +107,18 @@ let g:snipMate['get_scopes'] = get(g:snipMate, 'get_scopes', funcref#Function('r
" Modified from Luc Hermitte's function on StackOverflow
" <http://stackoverflow.com/a/1534347>
function! s:grab_visual()
function! s:grab_visual() abort
let a_save = @a
try
normal! gv"ay
let b:snipmate_content_visual = @a
let b:snipmate_visual = @a
finally
let @a = a_save
endtry
endfunction
" TODO: Allow specifying an arbitrary snippets file
function! s:load_scopes(bang, ...)
function! s:load_scopes(bang, ...) abort
let gb = a:bang ? g: : b:
let gb.snipMate = get(gb, 'snipMate', {})
let gb.snipMate.scope_aliases = get(gb.snipMate, 'scope_aliases', {})

View File

@ -7,10 +7,10 @@ syn match snipEscape '\\\\\|\\`'
syn match snipCommand '\%(\\\@<!\%(\\\\\)*\)\@<=`.\{-}\%(\\\@<!\%(\\\\\)*\)\@<=`'
syn match snippet '^snippet.*' contains=multiSnipText,snipKeyword
syn match snippet '^extends.*' contains=snipKeyword
syn match snippet '^guard\s\+.*' contains=multiSnipText,snipKeyword
syn match snippet '^version.*' contains=snipKeyword
syn match multiSnipText '\S\+ \zs.*' contained
syn match snipKeyword '^(snippet|extends)'me=s+8 contained
syn match snipError "^[^#se\t].*$"
syn match snipKeyword '^(snippet|extends|version)'me=s+8 contained
syn match snipError "^[^#vse\t].*$"
hi link snippet Identifier
hi link snipComment Comment

View File

@ -0,0 +1,175 @@
function! Setup(snip) abort
return snipMate#expandSnip(join(a:snip, "\n"), 1)
endfunction
function! s:to_be_file(expected) abort
return a:expected == getline(1,'$')
endfunction
function! s:to_be_in(item, list) abort
return !empty(filter(copy(a:list), 'v:val is a:item'))
endfunction
call vspec#customize_matcher('to_be_file', function('s:to_be_file'))
call vspec#customize_matcher('to_be_in', function('s:to_be_in'))
describe 'snippet state'
before
enew
let b:snip_state = snipmate#jumping#state()
end
after
bwipeout!
end
describe '.remove()'
it 'removes the state object'
Expect exists('b:snip_state') to_be_true
call b:snip_state.remove()
Expect exists('b:snip_state') to_be_false
end
it 'removes snippet related autocommands'
function! ReadAutocmds()
redir => autocmds
0verbose au snipmate_changes * <buffer>
redir END
return split(autocmds, "\n")
endfunction
aug snipmate_changes
au CursorMoved,CursorMovedI <buffer> echo 'event'
aug END
Expect len(ReadAutocmds()) > 1
call b:snip_state.remove()
Expect len(ReadAutocmds()) == 1
end
end
describe '.find_next_stop()'
it 'increments/decrements the stop_no'
let b:snip_state.stops = { 1 : {}, 2 : {} }
let b:snip_state.stop_no = 1
let b:snip_state.stop_count = 4
call b:snip_state.find_next_stop(0)
Expect b:snip_state.stop_no == 2
call b:snip_state.find_next_stop(1)
Expect b:snip_state.stop_no == 1
end
it 'continues iterating if the next/previous stop does not exist'
let b:snip_state.stops = { 3 : {} }
let b:snip_state.stop_count = 6
let b:snip_state.stop_no = 1
call b:snip_state.find_next_stop(0)
Expect b:snip_state.stop_no == 3
let b:snip_state.stop_no = 5
call b:snip_state.find_next_stop(1)
Expect b:snip_state.stop_no == 3
end
it 'does something at the ends'
"
end
end
describe '.remove_nested()'
it 'removes nested mirrors and only nested mirrors'
let mirror = { 'line' : 0 }
let b:snip_state.stops = { 1 : { 'placeholder' : [[2, mirror]] },
\ 2 : { 'mirrors' : [mirror, {}] } }
call b:snip_state.remove_nested(1)
Expect len(b:snip_state.stops[2].mirrors) == 1
Expect b:snip_state.stops[2].mirrors[0] isnot mirror
end
it 'removes nested stops'
let stop = [2, 'abc']
let b:snip_state.stops = { 1 : { 'placeholder' : [stop] },
\ 2 : { 'placeholder' : stop[1:1] } }
call b:snip_state.remove_nested(1)
Expect len(b:snip_state.stops) == 1
Expect keys(b:snip_state.stops) == ['1']
end
end
describe '.find_update_objects()'
it 'finds mirrors/stops on the same line and after cur_stop'
let b:snip_state.stops = {
\ 1 : { 'line' : 1, 'col' : 5,
\ 'placeholder' : ['x'] },
\ 2 : { 'line' : 1, 'col' : 7,
\ 'mirrors' : [{ 'line' : 1, 'col' : 7 }] }
\ }
let stop = b:snip_state.stops[1]
call b:snip_state.find_update_objects(stop)
for obj in stop.update_objects
Expect obj to_be_in [ b:snip_state.stops[2],
\ b:snip_state.stops[2].mirrors[0] ]
endfor
end
it 'finds mirrors/stops on the same line and after cur_stop mirrors'
let b:snip_state.stops = {
\ 1 : { 'line' : 1, 'col' : 5,
\ 'mirrors' : [{ 'line' : 2, 'col' : 5 }],
\ 'placeholder' : ['x'] },
\ 2 : { 'line' : 2, 'col' : 7,
\ 'mirrors' : [{ 'line' : 2, 'col' : 7 }] }
\ }
let stop = b:snip_state.stops[1]
call b:snip_state.find_update_objects(stop)
for obj in stop.update_objects
Expect obj to_be_in [ b:snip_state.stops[2],
\ b:snip_state.stops[2].mirrors[0] ]
endfor
end
it 'ignores mirrors/stops on other lines'
let b:snip_state.stops = {
\ 1 : { 'line' : 2, 'col' : 5,
\ 'placeholder' : ['x'] },
\ 2 : { 'line' : 1, 'col' : 7,
\ 'mirrors' : [{ 'line' : 1, 'col' : 7 }] },
\ 3 : { 'line' : 3, 'col' : 7,
\ 'mirrors' : [{ 'line' : 3, 'col' : 7 }] }
\ }
let stop = b:snip_state.stops[1]
call b:snip_state.find_update_objects(stop)
Expect empty(stop.update_objects) to_be_true
end
it 'ignores mirrors/stops on the same line but before cur_stop/mirrors'
let b:snip_state.stops = {
\ 1 : { 'line' : 1, 'col' : 5,
\ 'mirrors' : [{ 'line' : 2, 'col' : 5 }],
\ 'placeholder' : ['x'] },
\ 2 : { 'line' : 1, 'col' : 1,
\ 'mirrors' : [{ 'line' : 2, 'col' : 1 }] },
\ 3 : { 'line' : 2, 'col' : 3,
\ 'mirrors' : [{ 'line' : 1, 'col' : 3 }] },
\ }
let stop = b:snip_state.stops[1]
call b:snip_state.find_update_objects(stop)
Expect empty(stop.update_objects) to_be_true
end
end
end

View File

@ -0,0 +1,152 @@
describe 'snippet parser'
before
function! Parse(snippet, ...)
let [snip, stops] = snipmate#parse#snippet(a:snippet, (a:0 ? a:1 : 1))
return (a:0 > 1 && a:2) ? [snip, stops] : snip
endfunction
let b:snipmate_visual = 'testvisual'
end
it 'parses numeric $id and ${id} vars as [id] lists'
let expect = [[[1234567890]]]
Expect Parse('$1234567890') == expect
Expect Parse('${1234567890}') == expect
end
it 'disregards $ or ${ followed by a non-id'
Expect Parse('$x1') == [['x1']]
Expect Parse('${x}1') == [['x}1']]
Expect Parse('$VISUA1') == [['VISUA1']]
Expect Parse('${VISUA}1') == [['VISUA}1']]
end
it 'gathers references to each instance of each stop id'
let [snip, b:stops] = Parse('x$1x${2:x$1x}x$1x${1/a/b}x$VISUALx', 1, 1)
function! InstanceFound(list)
return !empty(filter(copy(b:stops[a:list[0]].instances),
\ 'v:val is a:list'))
endfunction
function! CheckList(list)
for item in a:list
if type(item) == type([])
Expect InstanceFound(item) to_be_true
call CheckList(item)
endif
unlet item " E732
endfor
endfunction
call CheckList(snip[0])
end
it 'parses mirror substitutions ${n/pat/sub} as [n, {...}]'
let expect = [[[1, { 'pat' : 'abc', 'sub' : 'def' }]]]
Expect Parse('${1/abc/def}') == expect
let expect[0][0][1].flags = ''
Expect Parse('${1/abc/def/}') == expect
let expect[0][0][1].flags = 'g'
Expect Parse('${1/abc/def/g}') == expect
end
it 'reads patterns literally except for "\/"'
Expect Parse('${1/\a\/b/\c\/d\}}') == [[[1, { 'pat' : '\a/b', 'sub' : '\c/d}' }]]]
end
it 'parses vars with placeholders as [id, placeholder] lists'
Expect Parse('${1:abc}') == [[[1, 'abc']]]
end
it 'evaluates backtick expressions'
Expect Parse('`fnamemodify("x.y", ":r")`') == [['x']]
end
it 'parses placeholders for vars and other specials'
let text = 'a `fnamemodify("x.y", ":r")` ${2:(${3/a/b})}'
let expect = ['a x ', [2, '(', [3, { 'pat' : 'a', 'sub' : 'b' }], ')']]
Expect Parse(text) == [expect]
Expect Parse(printf('${1:%s}', text)) == [[[1] + expect]]
end
it 'converts tabs according to &et, &sts, &sw, &ts'
" &noet -> leave tabs alone
setl noet
Expect Parse("abc\tdef\n\t\tghi") == [["abc\tdef"], ["\t\tghi"]]
" &et -> &sts or &sw
setl et sts=2 sw=3
Expect Parse("abc\tdef\n\t\tghi") == [["abc def"], [" ghi"]]
setl et sts=0 sw=3
Expect Parse("abc\tdef\n\t\tghi") == [["abc def"], [" ghi"]]
setl et sts=-1 sw=3
Expect Parse("abc\tdef\n\t\tghi") == [["abc def"], [" ghi"]]
" See #227
if exists('*shiftwidth')
setl et sts=0 sw=0 ts=3
Expect Parse("abc\tdef\n\t\tghi") == [["abc def"], [" ghi"]]
endif
end
it 'parses backslashes as escaping the next character or joining lines'
Expect Parse('x\x') == [['xx']]
Expect Parse('x\\x') == [['x\x']]
Expect Parse("x\\\nx") == [['xx']]
Expect Parse('x\$1') == [['x$1']]
Expect Parse('${1:\}}') == [[[1, '}']]]
Expect Parse('`fnamemodify("\`.x", ":r")`') == [['`']]
Expect Parse('\`x\`') == [['`x`']]
end
it 'splits text at newlines'
Expect Parse("x\nx") == [['x'], ['x']]
end
it 'joins evaluated expressions to surrounding text on the same line'
let g:foo = 'bar'
Expect Parse("x`g:foo`x") == [['xbarx']]
Expect Parse("x`g:foo`\nx") == [['xbar'], ['x']]
Expect Parse("x\n`g:foo`x") == [['x'], ['barx']]
end
it 'expands $VISUAL placeholders with any indents'
Expect Parse("x$VISUALx") == [['xtestvisualx']]
let b:snipmate_visual = " foo\nbar\n baz"
setl noet
Expect Parse("\tx\n\t$VISUAL\nx") == [["\tx"], ["\t foo"], ["\tbar"],
\ ["\t baz"], ["x"]]
end
it 'removes newlines from the end of VISUALs if before an end of line'
let b:snipmate_visual = "1\n2\n"
Expect Parse("x\n$VISUAL\nx") == [['x'], ['1'], ['2'], ['x']]
end
it 'splits the before and after a $VISUAL if it is multiline'
let b:snipmate_visual = "1\n2\n3"
Expect Parse("foo $VISUAL bar") == [['foo 1'], ['2'], ['3 bar']]
end
it 'determines which var with an id is the stop'
let [snip, stops] = Parse("$1$1$1", 0, 1)
Expect snip == [[[1, "", stops[1]], [1, {}], [1, {}]]]
let [snip, stops] = Parse("$1${1}$1", 0, 1)
Expect snip == [[[1, "", stops[1]], [1, {}], [1, {}]]]
let [snip, stops] = Parse("$1${1:}$1", 0, 1)
Expect snip == [[[1, {}], [1, "", stops[1]], [1, {}]]]
end
it 'picks the first of many possible stops'
let [snip, stops] = Parse("$1${1:foo}${1:bar}", 0, 1)
Expect snip == [[[1, {}], [1, "foo", stops[1]], [1, {}]]]
end
it 'represents empty lines as an empty string'
Expect Parse("foo\n\nbar") == [['foo'], [''], ['bar']]
end
end

View File

@ -0,0 +1,20 @@
#!/bin/sh
tmp="$(mktemp || tmpfile)"
vim -Es $tmp <<- EOF
source ~/.vimrc
%delete _
call append(0, split(&rtp, ','))
delete _
wq
EOF
rtp="$(grep -iE 'vspec|snipmate|tlib|mw-utils' < $tmp | grep -v after)"
vspec="$(grep -iE 'vspec' < $tmp | grep -v after)"
test_files="${*:-parser jumping}"
for test in $test_files; do
$vspec/bin/vspec $rtp ${test%%.vim}.vim
done
rm $tmp