├── .gitignore ├── Contributors.md ├── README.md ├── addon-info.json ├── after └── plugin │ └── snipMate.vim ├── autoload ├── snipMate.vim ├── snipMate_python_demo.vim └── snipmate │ ├── jumping.vim │ ├── legacy.vim │ ├── parse.vim │ └── util.vim ├── doc └── SnipMate.txt ├── ftplugin ├── html_snip_helper.vim └── snippets.vim ├── indent └── snippets.vim ├── plugin └── snipMate.vim ├── syntax ├── snippet.vim └── snippets.vim └── t ├── jumping.vim ├── parser.vim └── tests.sh /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | *.swp 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /Contributors.md: -------------------------------------------------------------------------------- 1 | # Contributors # 2 | 3 | SnipMate was originally authored by Michael Sanders 4 | ([Vim](http://www.vim.org/account/profile.php?user_id=16544), 5 | [GitHub](https://github.com/msanders)). 6 | 7 | It is currently maintained by [Rok Garbas](rok@garbas.si), [Marc 8 | Weber](marco-oweber@gmx.de), and [Adnan Zafar](https://github.com/ajzafar) with 9 | additional contributions from: 10 | 11 | * [907th](https://github.com/907th) 12 | * [adkron](https://github.com/adkron) 13 | * [alderz](https://github.com/alderz) 14 | * [asymmetric](https://github.com/asymmetric) 15 | * [bpugh](https://github.com/bpugh) 16 | * [bruno-](https://github.com/bruno-) 17 | * [CharlesGueunet](https://github.com/CharlesGueunet) 18 | * [darkwise](https://github.com/darkwise) 19 | * [dreviejo](https://github.com/dreviejo) 20 | * [fish-face](https://github.com/fish-face) 21 | * [henrik](https://github.com/henrik) 22 | * [holizz](https://github.com/holizz) 23 | * [honza](https://github.com/honza) 24 | * [hpesoj](https://github.com/hpesoj) 25 | * [ironcamel](https://github.com/ironcamel) 26 | * [jb55](https://github.com/jb55) 27 | * [jbernard](https://github.com/jbernard) 28 | * [jherdman](https://github.com/jherdman) 29 | * [kozo2](https://github.com/kozo2) 30 | * [lilydjwg](https://github.com/lilydjwg) 31 | * [lpil](https://github.com/lpil) 32 | * [marutanm](https://github.com/marutanm) 33 | * [MicahElliott](https://github.com/MicahElliott) 34 | * [mikeastock](https://github.com/mikeastock) 35 | * [muffinresearch](https://github.com/muffinresearch) 36 | * [munyari](https://github.com/munyari) 37 | * [nickelization](https://github.com/nickelization) 38 | * [pielgrzym](https://github.com/pielgrzym) 39 | * [pose](https://github.com/pose) 40 | * [r00k](https://github.com/r00k) 41 | * [radicalbit](https://github.com/radicalbit) 42 | * [redpill](https://github.com/redpill) 43 | * [rglassett](http://github.com/rglassett) 44 | * [robhudson](https://github.com/robhudson) 45 | * [roccomao](https://github.com/roccomao) 46 | * [shinymayhem](https://github.com/shinymayhem) 47 | * [Shraymonks](https://github.com/shraymonks) 48 | * [sickill](https://github.com/sickill) 49 | * [statik](https://github.com/statik) 50 | * [steveno](https://github.com/steveno) 51 | * [taq](https://github.com/taq) 52 | * [thisgeek](https://github.com/thisgeek) 53 | * [tomushkin](https://github.com/tomushkin) 54 | * [trusktr](https://github.com/trusktr) 55 | * [Xandaros](https://github.com/Xandaros) 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SnipMate # 2 | 3 | SnipMate aims to provide support for textual snippets, similar to TextMate or 4 | other Vim plugins like [UltiSnips][ultisnips]. For 5 | example, in C, typing `for` could be expanded to 6 | 7 | for (i = 0; i < count; i++) { 8 | /* code */ 9 | } 10 | 11 | with successive presses of tab jumping around the snippet. 12 | 13 | Originally authored by [Michael Sanders][msanders], SnipMate was forked in 2011 14 | after a stagnation in development. This fork is currently maintained by [Rok 15 | Garbas][garbas], [Marc Weber][marcweber], and [Adnan Zafar][ajzafar]. 16 | 17 | ## Installing SnipMate ## 18 | 19 | SnipMate can be installed using a package manager or using Vim's built-in 20 | package handling. It does depend on [vim-addon-mw-utils][mw-utils] and 21 | optionally [tlib][tlib]. For example, to use Vim's built-in support, 22 | 23 | % mkdir -p ~/.vim/pack/SnipMate/start 24 | % cd ~/.vim/pack/SnipMate/start 25 | % git clone https://github.com/garbas/vim-snipmate.git 26 | % git clone https://github.com/MarcWeber/vim-addon-mw-utils.git 27 | 28 | # Optional: 29 | % git clone https://github.com/tomtom/tlib_vim.git 30 | % git clone https://github.com/honza/vim-snippets.git 31 | 32 | > **NOTE:** SnipMate does not ship with any snippets out of the box. We suggest 33 | looking at the [vim-snippets][vim-snippets] repository. 34 | 35 | If tlib is enabled, it is used for multisnip (`:h SnipMate-multisnip`). It's 36 | also required for the `:SnipMateOpenSnippetFiles` command. 37 | 38 | Remember to run `:helptags ALL` once your Vim has loaded SnipMate! 39 | 40 | ## Using SnipMate ## 41 | 42 | Install and create some snippets (see `:h SnipMate-snippets`). Then type in the 43 | trigger for one in the correct filetype and hit the expansion key (by default 44 | bound to ``). 45 | 46 | ## FAQ ## 47 | 48 | > SnipMate doesn't work / My snippets aren't triggering 49 | 50 | Try all of the following: 51 | 52 | * Check that SnipMate is loaded. This can be done by looking for 53 | `snipMateTrigger` and similar maps in the output of `:imap`. 54 | Additionally make sure either `snipMateTrigger` or 55 | `snipMateNextOrTrigger` is mapped to the key you expect. 56 | 57 | * Check that the snippets file you mean to use exists, and that it contains the 58 | snippet you're trying to expand. 59 | 60 | * Check that your snippets file is located inside a `foo/snippets` directory, 61 | where `foo` is a path listed in your `runtimepath`. 62 | 63 | * Check that your snippets file is in scope by either the filetype matching the 64 | path of the snippet file or the scope explicitly loaded. 65 | 66 | * Check if any snippets from your snippets file are available. This can be done 67 | with the "show available snips" map, by default bound to `` in 68 | insert mode. 69 | 70 | If all of the above check out, please open an issue stating your Vim version, 71 | a sample snippet, and a description of exactly what happens when you try to 72 | trigger a snippet. 73 | 74 | > How does SnipMate determine which snippets to load? How can I separate, for 75 | > example, my Rails snippets from my Ruby snippets? 76 | 77 | Primarily SnipMate looks at the `'filetype'` and `'syntax'` settings. Taking 78 | "scopes" from these options, it looks in each `snippets/` directory in 79 | `'runtimepath'` for files named `scope.snippets`, `scope/*.snippets`, or 80 | `scope_*.snippets`. 81 | 82 | However we understand this may not allow for the flexibility desired by some 83 | languages. For this we provide two options: scope aliases and the 84 | `:SnipMateLoadScope` command. Scope aliases simply say "whenever this scope is 85 | loaded, also load this other scope: 86 | 87 | let g:snipMate = get(g:, 'snipMate', {}) " Allow for vimrc re-sourcing 88 | let g:snipMate.scope_aliases = {} 89 | let g:snipMate.scope_aliases['ruby'] = 'ruby,rails' 90 | 91 | will load the `ruby-rails` scope whenever the `ruby` scope is active. The 92 | `:SnipMateLoadScope foo` command will always load the foo scope in the current 93 | buffer. The [vim-rails](https://github.com/tpope/vim-rails) plugin automatically 94 | does `:SnipMateLoadScope rails` when editing a Rails project for example. 95 | 96 | > What are the snippet parser versions and what's the difference between them? 97 | 98 | Originally SnipMate used regex to parse a snippet. Determining where stops were, 99 | what the placeholders were, where mirrors were, etc. were all done with regex. 100 | Needless to say this was a little fragile. When the time came for a rewritten 101 | parser, some incompatibilities were a little necessary. Rather than break 102 | everyone's snippets everywhere, we provided both the new (version 1) and the old 103 | (version 0) and let the user choose between them. 104 | 105 | Version 0 is considered legacy and not a lot of effort is going to go into 106 | improving or even maintaining it. Version 1 is the future, and one can expect 107 | new features to only exist for version 1 users. A full list of differences can 108 | be found in the docs at `:h SnipMate-parser-versions`. 109 | 110 | ## Release Notes ## 111 | 112 | Some changes listed here were contributed by non-maintainers. A full list can be 113 | found at [Contributors.md](Contributors.md). 114 | 115 | ### Current ### 116 | 117 | - Make tlib an optional dependency. 118 | - Add SnipLookupPre and SnipLookupPost autocommand events 119 | - Make version 1 of the snippet parser the default with no message 120 | 121 | ### 0.90 - 2023-12-29 ### 122 | 123 | - Remove empty lines at the end of a `${VISUAL}` expansion 124 | - Fix code for opening folds when expanding a snippet 125 | - Deprecate legacy snippet parser 126 | - Fix jumps when `&sel == 'exclusive'` 127 | 128 | ### 0.89 - 2016-05-29 ### 129 | 130 | * Various regex updates to legacy parser 131 | * Addition of double bang syntax to completely remove a snippet from lookup 132 | * Group various SnipMate autocommands 133 | * Support setting 'shiftwidth' to 0 134 | * Parser now operates linewise, adding some flexibility 135 | * Mirror substitutions are more literal 136 | * Mirror length is calculated correctly when substitutions occur 137 | 138 | ### 0.88 - 2015-04-04 ### 139 | 140 | * Implement simple caching 141 | * Remove expansion guards 142 | * Add `:SnipMateLoadScope` command and buffer-local scope aliases 143 | * Load `_*.snippets` files 144 | * Use CursorMoved autocmd events entirely 145 | 146 | * The nested branch has been merged 147 | * A new snippet parser has been added. The g:snipmate.version as well as 148 | version lines in snippet files determines which is used 149 | * The new parser supports tab stops placed within placeholders, 150 | substitutions, non-consecutive stop numbers, and fewer ambiguities 151 | * The stop jumping code has been updated 152 | * Tests have been added for the jumping code and the new parser 153 | 154 | * The override branch has been merged 155 | * The g:snipMate.override option is added. When enabled, if two snippets 156 | share the same name, the later-loaded one is kept and the other discarded 157 | * Override behavior can be enabled on a per-snippet basis with a bang (!) in 158 | the snippet file 159 | * Otherwise, SnipMate tries to preserve all snippets loaded 160 | 161 | * Fix bug with mirrors in the first column 162 | * Fix bug with tabs in indents ([#143][143]) 163 | * Fix bug with mirrors in placeholders 164 | * Fix reading single snippet files 165 | * Fix the use of the visual map at the end of a line 166 | * Fix expansion of stops containing only the zero tab stop 167 | * Remove select mode mappings 168 | * Indent visual placeholder expansions and remove extraneous lines ([#177][177] 169 | and [#178][178]) 170 | 171 | ### 0.87 - 2014-01-04 ### 172 | 173 | * Stop indenting empty lines when expanding snippets 174 | * Support extends keyword in .snippets files 175 | * Fix visual placeholder support 176 | * Add zero tabstop support 177 | * Support negative 'softtabstop' 178 | * Add g:snipMate_no_default_aliases option 179 | * Add snipMateTrigger for triggering an expansion inside a snippet 180 | * Add snipMate#CanBeTriggered() function 181 | 182 | [ultisnips]: https://github.com/sirver/ultisnips 183 | [msanders]: https://github.com/msanders 184 | [garbas]: https://github.com/garbas 185 | [marcweber]: https://github.com/marcweber 186 | [ajzafar]: https://github.com/ajzafar 187 | [mw-utils]: https://github.com/marcweber/vim-addon-mw-utils 188 | [tlib]: https://github.com/tomtom/tlib_vim 189 | [vim-snippets]: https://github.com/honza/vim-snippets 190 | [vam]: https://github.com/marcweber/vim-addon-manager 191 | [pathogen]: https://github.com/tpope/vim-pathogen 192 | [vundle]: https://github.com/gmarik/vundle 193 | 194 | [143]: https://github.com/garbas/vim-snipmate/issues/143 195 | [177]: https://github.com/garbas/vim-snipmate/issues/177 196 | [178]: https://github.com/garbas/vim-snipmate/issues/178 197 | -------------------------------------------------------------------------------- /addon-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "snipMate", 3 | "version" : "dev", 4 | "author" : "Michael Sanders -> original project http://github.com/msanders/snipmate.vim", 5 | "maintainer" : "Rok Garbas / Marc Weber", 6 | "repository" : {"type": "git", "url": "git://github.com/garbas/vim-snipmate.git"}, 7 | "dependencies" : { 8 | "vim-addon-mw-utils": {}, 9 | "tlib": {} 10 | }, 11 | "description" : "snipMate.vim aims to be a concise vim script that implements some of TextMate's snippets features in Vim. See README.md to learn about the features this fork adds" 12 | } 13 | -------------------------------------------------------------------------------- /after/plugin/snipMate.vim: -------------------------------------------------------------------------------- 1 | " snipMate maps 2 | " These maps are created here in order to make sure we can reliably create maps 3 | " after SuperTab. 4 | 5 | let s:save_cpo = &cpo 6 | set cpo&vim 7 | 8 | function! s:map_if_not_mapped(lhs, rhs, mode) abort 9 | let l:unique = s:overwrite ? '' : ' ' 10 | if !hasmapto(a:rhs, a:mode) 11 | silent! exe a:mode . 'map' . l:unique a:lhs a:rhs 12 | endif 13 | endfunction 14 | 15 | if !exists('g:snips_no_mappings') || !g:snips_no_mappings 16 | if exists('g:snips_trigger_key') 17 | echom 'g:snips_trigger_key is deprecated. See :h snipMate-mappings' 18 | exec 'imap ' g:snips_trigger_key 'snipMateTrigger' 19 | exec 'smap ' g:snips_trigger_key 'snipMateSNext' 20 | exec 'xmap ' g:snips_trigger_key 'snipMateVisual' 21 | else 22 | " Remove SuperTab map if it exists 23 | let s:overwrite = maparg('', 'i') ==? 'SuperTabForward' 24 | call s:map_if_not_mapped('', 'snipMateNextOrTrigger', 'i') 25 | call s:map_if_not_mapped('', 'snipMateNextOrTrigger', 's') 26 | let s:overwrite = 0 27 | call s:map_if_not_mapped('', 'snipMateVisual', 'x') 28 | endif 29 | 30 | if exists('g:snips_trigger_key_backwards') 31 | echom 'g:snips_trigger_key_backwards is deprecated. See :h snipMate-mappings' 32 | exec 'imap ' g:snips_trigger_key_backwards 'snipMateIBack' 33 | exec 'smap ' g:snips_trigger_key_backwards 'snipMateSBack' 34 | else 35 | let s:overwrite = maparg('', 'i') ==? 'SuperTabBackward' 36 | call s:map_if_not_mapped('', 'snipMateBack', 'i') 37 | call s:map_if_not_mapped('', 'snipMateBack', 's') 38 | let s:overwrite = 0 39 | endif 40 | 41 | call s:map_if_not_mapped('', 'snipMateShow', 'i') 42 | endif 43 | 44 | let &cpo = s:save_cpo 45 | 46 | " vim:noet: 47 | -------------------------------------------------------------------------------- /autoload/snipMate.vim: -------------------------------------------------------------------------------- 1 | " config which can be overridden (shared lines) 2 | if !exists('g:snipMate') 3 | let g:snipMate = {} 4 | endif 5 | 6 | fun! Filename(...) abort 7 | let filename = expand('%:t:r') 8 | if filename == '' | return a:0 == 2 ? a:2 : '' | endif 9 | return !a:0 || a:1 == '' ? filename : substitute(a:1, '$1', filename, 'g') 10 | endf 11 | 12 | let s:cache = {} 13 | 14 | function! snipMate#expandSnip(snip, version, col) abort 15 | let lnum = line('.') 16 | let col = a:col 17 | let line = getline(lnum) 18 | let indent = match(line, '\S\|$') + 1 19 | let b:snip_state = snipmate#jumping#state() 20 | 21 | if a:version == 1 22 | let [snippet, b:snip_state.stops] = snipmate#parse#snippet(a:snip) 23 | " only if zero stop doesn't exist 24 | call s:add_zero_stop(snippet, b:snip_state.stops) 25 | " Build stop/mirror info 26 | let b:snip_state.stop_count = s:build_stops(snippet, b:snip_state.stops, lnum, col, indent) 27 | else 28 | let snippet = snipmate#legacy#process_snippet(a:snip) 29 | let [b:snip_state.stops, b:snip_state.stop_count] = snipmate#legacy#build_stops(snippet, lnum, col - indent, indent) 30 | endif 31 | 32 | " Abort if the snippet is empty 33 | if empty(snippet) 34 | return '' 35 | endif 36 | 37 | let col = s:insert_snippet_text(snippet, lnum, col, indent) 38 | 39 | " Open any folds snippet expands into 40 | if &foldenable 41 | silent! exec lnum . 'foldopen!' 42 | endif 43 | 44 | aug snipmate_changes 45 | au CursorMoved,CursorMovedI if exists('b:snip_state') | 46 | \ call b:snip_state.update_changes() | 47 | \ else | 48 | \ silent! au! snipmate_changes * | 49 | \ endif 50 | aug END 51 | 52 | let b:snip_state.stop_no = 0 53 | return b:snip_state.set_stop(0) 54 | endfunction 55 | 56 | function! s:add_zero_stop(snippet, stops) abort 57 | if !exists("a:stops['0']") 58 | let zero_stop = {'mirrors': [], 'placeholder': []} 59 | call extend(a:snippet[-1], [[0, '', zero_stop]]) 60 | call extend(a:stops, {'0': zero_stop}, 'keep') 61 | endif 62 | endfunction 63 | 64 | function! s:insert_snippet_text(snippet, lnum, col, indent) 65 | let line = getline(a:lnum) 66 | let col = a:col 67 | let snippet = type(a:snippet) == type([]) ? a:snippet : split(a:snippet, "\n", 1) 68 | let lnum = a:lnum 69 | 70 | " Keep text after the cursor 71 | let afterCursor = strpart(line, col - 1) 72 | if afterCursor != "\t" && afterCursor != ' ' 73 | let line = strpart(line, 0, col - 1) 74 | else 75 | let afterCursor = '' 76 | " For some reason the cursor needs to move one right after this 77 | if line != '' && col == 1 && &ve != 'all' && &ve != 'onemore' 78 | let col += 1 79 | endif 80 | endif 81 | 82 | call setline(lnum, '') 83 | call append(lnum, repeat([''], len(snippet) - 1)) 84 | 85 | for item in snippet 86 | let add = lnum == a:lnum ? line : strpart(line, 0, a:indent - 1) 87 | 88 | if !(empty(item) || (type(item) == type([]) && empty(item[0]))) 89 | if type(item) == type([]) 90 | call setline(lnum, add . 91 | \ snipMate#sniplist_str(item, b:snip_state.stops)) 92 | else 93 | call setline(lnum, add . 94 | \ substitute(item, printf('%s\d\+\|%s{\d\+.\{-}}', 95 | \ g:snipmate#legacy#sigil, g:snipmate#legacy#sigil), 96 | \ '', 'g')) 97 | endif 98 | endif 99 | 100 | let lnum += 1 101 | endfor 102 | 103 | call setline(lnum - 1, getline(lnum - 1) . afterCursor) 104 | 105 | return col 106 | endfunction 107 | 108 | function! snipMate#placeholder_str(num, stops) abort 109 | return snipMate#sniplist_str(get(get(a:stops, a:num, {}), 'placeholder', []), a:stops) 110 | endfunction 111 | 112 | function! snipMate#sniplist_str(snippet, stops) abort 113 | let str = '' 114 | let pos = 0 115 | let add_to = 1 116 | let seen_stops = [] 117 | 118 | while pos < len(a:snippet) 119 | let item = a:snippet[pos] 120 | 121 | if type(item) == type('') 122 | let str .= item 123 | elseif type(item) == type([]) 124 | let placeholder = snipMate#placeholder_str(item[0], a:stops) 125 | if len(item) > 1 && type(item[1]) == type({}) 126 | let placeholder = substitute(placeholder, 127 | \ get(item[1], 'pat', ''), 128 | \ get(item[1], 'sub', ''), 129 | \ get(item[1], 'flags', '')) 130 | endif 131 | let str .= placeholder 132 | endif 133 | 134 | let pos += 1 135 | unlet item " avoid E706 136 | endwhile 137 | 138 | return str 139 | endfunction 140 | 141 | function! s:build_stops(snippet, stops, lnum, col, indent) abort 142 | let stops = a:stops 143 | let lnum = a:lnum 144 | let col = a:col 145 | 146 | for line in a:snippet 147 | let col = s:build_loc_info(line, stops, lnum, col, []) 148 | if line isnot a:snippet[-1] 149 | let lnum += 1 150 | let col = a:indent 151 | endif 152 | endfor 153 | 154 | let stop_count = max(keys(stops)) + 2 155 | let stops[stop_count - 1] = stops[0] 156 | 157 | return stop_count 158 | endfunction 159 | 160 | function! s:build_loc_info(snippet, stops, lnum, col, seen_items) abort 161 | let stops = a:stops 162 | let lnum = a:lnum 163 | let col = a:col 164 | let pos = 0 165 | let in_text = 0 166 | let seen_items = a:seen_items 167 | 168 | for item in a:snippet 169 | if type(item) == type('') 170 | let col += len(item) 171 | elseif type(item) == type([]) 172 | let id = item[0] 173 | let stub = item[-1] 174 | let stub.line = lnum 175 | let stub.col = col 176 | call s:add_update_objects(stub, seen_items) 177 | 178 | " if we've found a stop? 179 | if len(item) > 2 && type(item[1]) != type({}) && !exists('stub.items') 180 | let col = s:build_loc_info(item[1:-2], stops, lnum, col, seen_items) 181 | else 182 | let col += len(snipMate#placeholder_str(id, stops)) 183 | endif 184 | 185 | let in_text = 0 186 | endif 187 | unlet item " avoid E706 188 | endfor 189 | 190 | return col 191 | endfunction 192 | 193 | function! s:add_update_objects(object, targets) abort 194 | let targets = a:targets 195 | 196 | for item in targets 197 | let item.update_objects = get(item, 'update_objects', []) 198 | call add(item.update_objects, a:object) 199 | endfor 200 | 201 | call add(targets, a:object) 202 | endfunction 203 | 204 | " reads a .snippets file 205 | " returns list of 206 | " ['triggername', 'name', 'contents'] 207 | " if triggername is not set 'default' is assumed 208 | " TODO: better error checking 209 | fun! snipMate#ReadSnippetsFile(file) abort 210 | let result = [] 211 | let new_scopes = [] 212 | if !filereadable(a:file) | return [result, new_scopes] | endif 213 | let inSnip = 0 214 | let line_no = 0 215 | let snipversion = get(g:snipMate, 'snippet_version', 1) 216 | for line in readfile(a:file) + ["\n"] 217 | let line_no += 1 218 | 219 | if inSnip && (line[0] == "\t" || line == '') 220 | let content .= strpart(line, 1)."\n" 221 | continue 222 | elseif inSnip 223 | call add(result, [trigger, name, 224 | \ content[:-2], bang, snipversion]) 225 | let inSnip = 0 226 | endif 227 | 228 | if line[:6] == 'snippet' 229 | let inSnip = 1 230 | let bang = (line[7] == '!') 231 | if bang 232 | let bang += line[8] == '!' 233 | endif 234 | let trigger = strpart(line, 8 + bang) 235 | let name = '' 236 | let space = stridx(trigger, ' ') + 1 237 | if space " Process multi snip 238 | let name = strpart(trigger, space) 239 | let trigger = strpart(trigger, 0, space - 1) 240 | endif 241 | let content = '' 242 | if trigger =~ '^\s*$' " discard snippets with empty triggers 243 | echom 'Invalid snippet in' a:file 'near line' line_no 244 | let inSnip = 0 245 | endif 246 | elseif line[:6] == 'extends' 247 | call extend(new_scopes, map(split(strpart(line, 8)), 248 | \ "substitute(v:val, ',*$', '', '')")) 249 | elseif line[:6] == 'version' 250 | let snipversion = +strpart(line, 8) 251 | endif 252 | endfor 253 | return [result, new_scopes] 254 | endf 255 | 256 | function! s:GetScopes() abort 257 | let ret = exists('b:snipMate.scope_aliases') ? copy(b:snipMate.scope_aliases) : {} 258 | let global = get(g:snipMate, 'scope_aliases', {}) 259 | for alias in keys(global) 260 | if has_key(ret, alias) 261 | let ret[alias] = join(split(ret[alias], ',') 262 | \ + split(global[alias], ','), ',') 263 | else 264 | let ret[alias] = global[alias] 265 | endif 266 | endfor 267 | return ret 268 | endfunction 269 | 270 | " adds scope aliases to list. 271 | " returns new list 272 | " the aliases of aliases are added recursively 273 | fun! s:AddScopeAliases(list) abort 274 | let did = {} 275 | let scope_aliases = s:GetScopes() 276 | let new = a:list 277 | let new2 = [] 278 | while !empty(new) 279 | for i in new 280 | if !has_key(did, i) 281 | let did[i] = 1 282 | call extend(new2, split(get(scope_aliases,i,''),',')) 283 | endif 284 | endfor 285 | let new = new2 286 | let new2 = [] 287 | endwhile 288 | return keys(did) 289 | endf 290 | 291 | augroup SnipMateSource 292 | au SourceCmd *.snippet,*.snippets call s:source_snippet() 293 | augroup END 294 | 295 | function! s:info_from_filename(file) abort 296 | let parts = split(fnamemodify(a:file, ':r'), '/') 297 | let snipidx = len(parts) - index(reverse(copy(parts)), 'snippets') - 1 298 | let rtp_prefix = join(parts[(snipidx - 299 | \ (parts[snipidx - 1] == 'after' ? 3 : 2)):snipidx - 1], '/') 300 | let trigger = get(parts, snipidx + 2, '') 301 | let desc = get(parts, snipidx + 3, get(g:snipMate, 'override', 0) ? 302 | \ '' : fnamemodify(a:file, ':t')) 303 | return [rtp_prefix, trigger, desc] 304 | endfunction 305 | 306 | function! s:source_snippet() abort 307 | let file = expand(':p') 308 | let [rtp_prefix, trigger, desc] = s:info_from_filename(file) 309 | let new_snips = [] 310 | if fnamemodify(file, ':e') == 'snippet' 311 | call add(new_snips, [trigger, desc, join(readfile(file), "\n"), 0, 312 | \ get(g:snipMate, 'snippet_version', 1)]) 313 | else 314 | let [snippets, extends] = s:CachedSnips(file) 315 | let new_snips = deepcopy(snippets) 316 | call extend(s:lookup_state.extends, extends) 317 | endif 318 | for snip in new_snips 319 | if get(g:snipMate, 'override', 0) 320 | let snip[1] = join([s:lookup_state.scope, snip[1]]) 321 | else 322 | let snip[1] = join([s:lookup_state.scope, rtp_prefix, 323 | \ empty(snip[1]) ? desc : snip[1]]) 324 | endif 325 | endfor 326 | call extend(s:lookup_state.snips, new_snips) 327 | endfunction 328 | 329 | function! s:CachedSnips(file) abort 330 | let mtime = getftime(a:file) 331 | if has_key(s:cache, a:file) && s:cache[a:file].mtime >= mtime 332 | return s:cache[a:file].contents 333 | endif 334 | let s:cache[a:file] = {} 335 | let s:cache[a:file].mtime = mtime 336 | let s:cache[a:file].contents = snipMate#ReadSnippetsFile(a:file) 337 | return s:cache[a:file].contents 338 | endfunction 339 | 340 | function! s:snippet_filenames(scope, trigger) abort 341 | let mid = ['', '_*', '/*'] 342 | let mid += map(copy(mid), "'/' . a:trigger . '*' . v:val") 343 | call map(mid, "'snippets/' . a:scope . v:val . '.snippet'") 344 | return map(mid[:2], 'v:val . "s"') + mid[3:] 345 | endfunction 346 | 347 | function! snipMate#SetByPath(dict, trigger, path, snippet, bang, snipversion) abort 348 | let d = a:dict 349 | if a:bang == 2 350 | unlet! d[a:trigger] 351 | return 352 | elseif !has_key(d, a:trigger) || a:bang == 1 353 | let d[a:trigger] = {} 354 | endif 355 | let d[a:trigger][a:path] = [a:snippet, a:snipversion] 356 | endfunction 357 | 358 | if v:version < 704 || has('win32') 359 | function! s:Glob(path, expr) 360 | let res = [] 361 | for p in split(a:path, ',') 362 | let h = split(fnamemodify(a:expr, ':h'), '/')[0] 363 | if isdirectory(p . '/' . h) 364 | call extend(res, split(glob(p . '/' . a:expr), "\n")) 365 | endif 366 | endfor 367 | return filter(res, 'filereadable(v:val)') 368 | endfunction 369 | else 370 | function! s:Glob(path, expr) 371 | return split(globpath(a:path, a:expr), "\n") 372 | endfunction 373 | endif 374 | 375 | " default triggers based on paths 376 | function! snipMate#DefaultPool(scopes, trigger, result) abort 377 | let scopes = s:AddScopeAliases(a:scopes) 378 | let scopes_done = [] 379 | let s:lookup_state = {} 380 | let s:lookup_state.snips = [] 381 | 382 | while !empty(scopes) 383 | let scope = remove(scopes, 0) 384 | let s:lookup_state.scope = scope 385 | let s:lookup_state.extends = [] 386 | 387 | for expr in s:snippet_filenames(scope, escape(a:trigger, "*[]?{}`'$|#%")) 388 | for path in s:snippet_dirs() 389 | for file in s:Glob(path, expr) 390 | source `=file` 391 | endfor 392 | endfor 393 | endfor 394 | 395 | call add(scopes_done, scope) 396 | call extend(scopes, s:lookup_state.extends) 397 | call filter(scopes, 'index(scopes_done, v:val) == -1') 398 | endwhile 399 | 400 | for [trigger, desc, contents, bang, snipversion] in s:lookup_state.snips 401 | if trigger =~ '\V\^' . escape(a:trigger, '\') 402 | call snipMate#SetByPath(a:result, trigger, desc, contents, bang, snipversion) 403 | endif 404 | endfor 405 | endfunction 406 | 407 | " return a dict of snippets found in runtimepath matching trigger 408 | " scopes: list of scopes. usually this is the filetype. eg ['c','cpp'] 409 | " trigger may contain glob patterns. Thus use '*' to get all triggers 410 | " 411 | fun! snipMate#GetSnippets(scopes, trigger) abort 412 | let result = {} 413 | 414 | for F in values(g:snipMateSources) 415 | call funcref#Call(F, [a:scopes, a:trigger, result]) 416 | endfor 417 | return result 418 | endf 419 | 420 | function! s:snippet_dirs() abort 421 | return get(g:snipMate, 'snippet_dirs', split(&rtp, ',')) 422 | endfunction 423 | 424 | function! snipMate#OpenSnippetFiles() abort 425 | if !exists('g:loaded_tlib') || g:loaded_tlib < 41 426 | echom 'tlib is required for this command. ' 427 | \ . 'Remember to run :packadd if necessary.' 428 | return 429 | endif 430 | 431 | let files = [] 432 | let scopes_done = [] 433 | let exists = [] 434 | let notexists = [] 435 | for scope in s:AddScopeAliases(snipMate#ScopesByFile()) 436 | let files += s:snippet_filenames(scope, '') 437 | endfor 438 | call filter(files, "v:val !~# '\\*'") 439 | for path in s:snippet_dirs() 440 | let fullpaths = map(copy(files), 'printf("%s/%s", path, v:val)') 441 | let exists += filter(copy(fullpaths), 'filereadable(v:val)') 442 | let notexists += map(filter(copy(fullpaths), 443 | \ 'v:val =~# "\.snippets" && !filereadable(v:val)'), 444 | \ '"does not exist: " . v:val') 445 | endfor 446 | let all = exists + notexists 447 | let select = tlib#input#List('mi', 'select files to be opened in splits', all) 448 | for idx in select 449 | exec 'sp' all[idx - 1] 450 | endfor 451 | endfunction 452 | 453 | fun! snipMate#ScopesByFile() abort 454 | " duplicates are removed in AddScopeAliases 455 | return filter(funcref#Call(g:snipMate.get_scopes), "v:val != ''") 456 | endf 457 | 458 | function! snipMate#flatten_filter_empty(list) abort 459 | let result = [] 460 | for item in a:list 461 | if type(item) == type([]) 462 | call extend(result, snipMate#flatten_filter_empty(item)) 463 | elseif !empty(item) 464 | call extend(result, [item]) 465 | endif 466 | unlet item " Avoid E706 467 | endfor 468 | return result 469 | endf 470 | 471 | function! s:determine_lookups(word) abort 472 | let b:snip_word = a:word 473 | 474 | " gather any lookups from the Pre au 475 | if exists('#User#SnipLookupPre') 476 | doautocmd User SnipLookupPre 477 | endif 478 | 479 | " If none are found, add the standard lookups 480 | if !exists('b:snip_lookups') || empty(b:snip_lookups) 481 | let b:snip_lookups = s:standard_lookups(b:snip_word) 482 | endif 483 | 484 | " Run the Post au 485 | if exists('#User#SnipLookupPost') 486 | doautocmd User SnipLookupPost 487 | endif 488 | 489 | " return the appropriate data, deleting buffer variables. 490 | let ret = b:snip_lookups 491 | unlet! b:snip_lookups b:snip_word 492 | return ret 493 | endfunction 494 | 495 | function! s:standard_lookups(word) abort 496 | " Split non-word characters into their own piece 497 | " so 'foo.bar..baz' becomes ['foo', '.', 'bar', '.', '.', 'baz'] 498 | " First split just after a \W and then split each resultant string just 499 | " before a \W 500 | let parts = snipMate#flatten_filter_empty( 501 | \ map(split(a:word, '\W\zs'), 'split(v:val, "\\ze\\W")')) 502 | " Only look at the last few possibilities. Too many can be slow. 503 | if len(parts) > 5 504 | let parts = parts[-5:] 505 | endif 506 | let lookups = [a:word] 507 | let lookup = '' 508 | for w in reverse(parts) 509 | let lookup = w . lookup 510 | if index(lookups, lookup) == -1 511 | call add(lookups, lookup) 512 | endif 513 | endfor 514 | return lookups 515 | endfunction 516 | 517 | " used by both: completion and insert snippet 518 | fun! snipMate#GetSnippetsForWordBelowCursor(word, exact) abort 519 | let lookups = s:determine_lookups(a:word) 520 | " Remove empty lookup entries, but only if there are other nonempty lookups 521 | if len(lookups) > 1 522 | call filter(lookups, 'v:val != ""') 523 | endif 524 | 525 | let matching_snippets = [] 526 | let snippet = '' 527 | " prefer longest word 528 | for word in lookups 529 | let g:snipMate.word = word 530 | for [k,snippetD] in items(funcref#Call(g:snipMate['get_snippets'], [snipMate#ScopesByFile(), word])) 531 | " hack: require exact match 532 | if a:exact && k !=# word 533 | continue 534 | endif 535 | call add(matching_snippets, [k, snippetD]) 536 | if a:exact 537 | break 538 | endif 539 | endfor 540 | endfor 541 | return matching_snippets 542 | endf 543 | 544 | " snippets: dict containing snippets by name 545 | " usually this is just {'default' : snippet_contents } 546 | fun! s:ChooseSnippet(snippets) abort 547 | let snippet = [] 548 | let keys = keys(a:snippets) 549 | let i = 1 550 | for snip in keys 551 | let snippet += [i.'. '.snip] 552 | let i += 1 553 | endfor 554 | let i = 0 555 | if len(snippet) > 1 && get(g:snipMate, 'always_choose_first', 0) != 1 556 | if exists('g:loaded_tlib') && g:loaded_tlib >= 41 557 | let i = tlib#input#List('si','select snippet by name',snippet) - 1 558 | else 559 | let i = inputlist(snippet + ['Select a snippet by number']) - 1 560 | endif 561 | endif 562 | " if a:snippets[..] is a String Call returns it 563 | " If it's a function or a function string the result is returned 564 | return (i == -1) ? '' : funcref#Call(a:snippets[keys(a:snippets)[i]]) 565 | endf 566 | 567 | fun! snipMate#WordBelowCursor() abort 568 | return matchstr(getline('.'), '\S\+\%' . col('.') . 'c') 569 | endf 570 | 571 | fun! snipMate#GetSnippetsForWordBelowCursorForComplete(word) abort 572 | let matches = snipMate#GetSnippetsForWordBelowCursor(a:word, 0) 573 | let snippets = [] 574 | for [trigger, dict] in matches 575 | if get(g:snipMate, 'description_in_completion', 0) 576 | call extend(snippets, map(keys(dict), 577 | \ '{ "word" : trigger, "menu" : v:val, "dup" : 1 }')) 578 | else 579 | call add(snippets, { "word" : trigger }) 580 | endif 581 | endfor 582 | return filter(snippets, 583 | \ 'v:val.word =~# "\\V\\^' . escape(a:word, '"\') . '"') 584 | endf 585 | 586 | fun! snipMate#CanBeTriggered() abort 587 | let word = snipMate#WordBelowCursor() 588 | let matches = snipMate#GetSnippetsForWordBelowCursorForComplete(word) 589 | return len(matches) > 0 590 | endf 591 | 592 | fun! snipMate#ShowAvailableSnips() abort 593 | let col = col('.') 594 | let word = snipMate#WordBelowCursor() 595 | let matches = snipMate#GetSnippetsForWordBelowCursorForComplete(word) 596 | 597 | " Pretty hacky, but really can't have the tab swallowed! 598 | if len(matches) == 0 599 | call feedkeys(g:snipMate['no_match_completion_feedkeys_chars'], 'n') 600 | return "" 601 | endif 602 | 603 | call complete(col - len(word), sort(matches)) 604 | return '' 605 | endf 606 | 607 | " Pass an argument to force snippet expansion instead of triggering or jumping 608 | function! snipMate#TriggerSnippet(...) abort 609 | if exists('g:SuperTabMappingForward') 610 | if g:SuperTabMappingForward == "" 611 | let SuperTabPlug = maparg('SuperTabForward', 'i') 612 | if SuperTabPlug == "" 613 | let SuperTabKey = "\" 614 | else 615 | exec "let SuperTabKey = \"" . escape(SuperTabPlug, '<') . "\"" 616 | endif 617 | elseif g:SuperTabMappingBackward == "" 618 | let SuperTabPlug = maparg('SuperTabBackward', 'i') 619 | if SuperTabPlug == "" 620 | let SuperTabKey = "\" 621 | else 622 | exec "let SuperTabKey = \"" . escape(SuperTabPlug, '<') . "\"" 623 | endif 624 | endif 625 | endif 626 | 627 | if pumvisible() " Update snippet if completion is used, or deal with supertab 628 | if exists('SuperTabKey') 629 | call feedkeys(SuperTabKey) | return '' 630 | endif 631 | call feedkeys("\a", 'n') " Close completion menu 632 | " Once we've dismissed the completion menu, we have to cause this 633 | " function to be executed over again, so that we actually get the 634 | " snippet triggered. (Simply continuing to execute fails because 635 | " we have to finish this function before the results of feedkeys take 636 | " effect and dismiss the completion menu. Recursing also fails for 637 | " similar reasons.) 638 | if a:0 == 0 639 | " Would be nice to have a more robust solution than manually 640 | " branching on the arguments. I tried to do something like: 641 | " call call(function('snipMate#TriggerSnippet'), a:000) 642 | " But I couldn't quite get it working. Maybe somebody else with 643 | " better vimscript skills can find a way to make it work, though? 644 | call feedkeys("\snipMateNextOrTrigger") | return '' 645 | else 646 | call feedkeys("\snipMateTrigger") | return '' 647 | endif 648 | endif 649 | 650 | if exists('b:snip_state') && a:0 == 0 " Jump only if no arguments 651 | let jump = b:snip_state.jump_stop(0) 652 | if type(jump) == 1 " returned a string 653 | return jump 654 | endif 655 | endif 656 | 657 | let word = matchstr(getline('.'), '\S\+\%'.col('.').'c') 658 | let list = snipMate#GetSnippetsForWordBelowCursor(word, 1) 659 | if empty(list) 660 | let snippet = '' 661 | else 662 | let [trigger, snippetD] = list[0] 663 | let snippet = s:ChooseSnippet(snippetD) 664 | " Before expanding snippet, create new undo point |i_CTRL-G| 665 | let &undolevels = &undolevels 666 | let col = col('.') - len(trigger) 667 | sil exe 's/\V'.escape(trigger, '/\.').'\%#//' 668 | return snipMate#expandSnip(snippet[0], snippet[1], col) 669 | endif 670 | 671 | " should allow other plugins to register hooks instead (duplicate code) 672 | if exists('SuperTabKey') 673 | call feedkeys(SuperTabKey) 674 | return '' 675 | endif 676 | return word == '' 677 | \ ? "\" 678 | \ : "\=snipMate#ShowAvailableSnips()\" 679 | endfunction 680 | 681 | fun! snipMate#BackwardsSnippet() abort 682 | if exists('b:snip_state') | return b:snip_state.jump_stop(1) | endif 683 | 684 | if exists('g:SuperTabMappingForward') 685 | if g:SuperTabMappingForward == "" 686 | let SuperTabPlug = maparg('SuperTabForward', 'i') 687 | if SuperTabPlug == "" 688 | let SuperTabKey = "\" 689 | else 690 | exec "let SuperTabKey = \"" . escape(SuperTabPlug, '<') . "\"" 691 | endif 692 | elseif g:SuperTabMappingBackward == "" 693 | let SuperTabPlug = maparg('SuperTabBackward', 'i') 694 | if SuperTabPlug == "" 695 | let SuperTabKey = "\" 696 | else 697 | exec "let SuperTabKey = \"" . escape(SuperTabPlug, '<') . "\"" 698 | endif 699 | endif 700 | endif 701 | " should allow other plugins to register hooks instead (duplicate code) 702 | if exists('SuperTabKey') 703 | call feedkeys(SuperTabKey) 704 | return '' 705 | endif 706 | return "\" 707 | endf 708 | 709 | " vim:noet:sw=4:ts=4:ft=vim 710 | -------------------------------------------------------------------------------- /autoload/snipMate_python_demo.vim: -------------------------------------------------------------------------------- 1 | " This file demonstrates 2 | " - how to register your own snippet sources (call snipMate_python_demo#Activate() in ftplugin/python.vim) 3 | " - implents a source which creates snippets based on python function 4 | " definitions found in the current file 5 | " 6 | " Example: 7 | " 8 | " def abc(a,b,c=None) 9 | " will create a snippet on the fly which looks like this: 10 | " abc(${1:a}, ${2:b}, ${3:c=None}) 11 | 12 | fun! snipMate_python_demo#Activate() abort 13 | if !exists('g:snipMateSources') 14 | let g:snipMateSources = {} 15 | endif 16 | 17 | let g:snipMateSources['python'] = funcref#Function('snipMate_python_demo#FunctionsFromCurrentFileAndTags') 18 | endf 19 | 20 | fun! s:Add(dict, line, source, trigger) abort 21 | let matched = matchlist(a:line,'def\s\+\([^( \t]\+\)[ \t]*(\([^)]*\)') 22 | if len(matched) > 2 23 | let name = matched[1] 24 | " TODO: is this a glob? 25 | if name !~ a:trigger | return | endif 26 | let a:dict[name] = get(a:dict, name, {}) 27 | let sd = a:dict[name] 28 | let args = [] 29 | let nr=1 30 | for arg in split(matched[2], '\s*,\s*') 31 | call add(args, '${'.nr.':'.arg.'}') 32 | let nr+=1 33 | endfor 34 | let sd[a:source] = name.'('.join(args,', ').')' 35 | endif 36 | endf 37 | fun! snipMate_python_demo#FunctionsFromCurrentFileAndTags(scopes, trigger, result) abort 38 | " getting all might be too much 39 | if a:trigger == '*' | return | endif 40 | if index(a:scopes, 'python') < 0 | return | endif 41 | for t in taglist('^'.a:trigger) 42 | call s:Add(a:result, t.cmd, 'tags-' . t.filename, a:trigger) 43 | endfor 44 | for l in getline(0, line('$')) 45 | call s:Add(a:result, l, 'current-file', a:trigger) 46 | endfor 47 | endf 48 | -------------------------------------------------------------------------------- /autoload/snipmate/jumping.vim: -------------------------------------------------------------------------------- 1 | function! s:sfile() abort 2 | return expand('') 3 | endfunction 4 | 5 | let s:state_proto = {} 6 | 7 | function! snipmate#jumping#state() abort 8 | return copy(s:state_proto) 9 | endfunction 10 | 11 | function! s:listize_mirror(mirrors) abort 12 | return map(copy(a:mirrors), '[v:val.line, v:val.col]') 13 | endfunction 14 | 15 | " Removes snippet state info 16 | function! s:state_remove() dict abort 17 | " Remove all autocmds in group snipmate_changes in the current buffer 18 | unlet! b:snip_state 19 | silent! au! snipmate_changes * 20 | endfunction 21 | 22 | function! s:state_find_next_stop(backwards) dict abort 23 | let self.stop_no += a:backwards? -1 : 1 24 | while !has_key(self.stops, self.stop_no) 25 | if self.stop_no == self.stop_count 26 | let self.stop_no = 0 27 | endif 28 | if self.stop_no <= 0 && a:backwards 29 | let self.stop_no = self.stop_count - 1 30 | endif 31 | let self.stop_no += a:backwards? -1 : 1 32 | endwhile 33 | endfunction 34 | 35 | " Update state information to correspond to the given tab stop 36 | function! s:state_set_stop(backwards) dict abort 37 | call self.find_next_stop(a:backwards) 38 | 39 | let self.cur_stop = self.stops[self.stop_no] 40 | let self.stop_len = (type(self.cur_stop.placeholder) == type(0)) 41 | \ ? self.cur_stop.placeholder 42 | \ : len(snipMate#placeholder_str(self.stop_no, self.stops)) 43 | let self.start_col = self.cur_stop.col 44 | let self.end_col = self.start_col + self.stop_len 45 | let self.mirrors = get(self.cur_stop, 'mirrors', []) 46 | let self.old_mirrors = deepcopy(self.mirrors) 47 | 48 | call cursor(self.cur_stop.line, self.cur_stop.col) 49 | 50 | let self.prev_len = col('$') 51 | let self.changed = 0 52 | 53 | for mirror in self.mirrors 54 | let mirror.oldSize = self.stop_len 55 | endfor 56 | 57 | if exists("self.cur_stop.items") 58 | let ret = self.select_item() 59 | else 60 | let ret = self.select_word() 61 | endif 62 | 63 | if (self.stop_no == 0 || self.stop_no == self.stop_count - 1) && !a:backwards 64 | call self.remove() 65 | endif 66 | 67 | return ret 68 | endfunction 69 | 70 | " Jump to the next/previous tab stop 71 | function! s:state_jump_stop(backwards) dict abort 72 | " Update changes just in case 73 | " This seems to be only needed because insert completion does not trigger 74 | " the CursorMovedI event 75 | call self.update_changes() 76 | 77 | " Store placeholder/location changes 78 | let self.cur_stop.col = self.start_col 79 | unlet! self.cur_stop.placeholder " avoid type error for old parsing version 80 | let self.cur_stop.placeholder = [strpart(getline('.'), 81 | \ self.start_col - 1, self.end_col - self.start_col)] 82 | if self.changed 83 | call self.remove_nested() 84 | 85 | " Remove selection items if the stop has changed and the new placeholder 86 | " is not one of the selection items 87 | if exists('self.cur_stop.items') && 88 | \ !count(self.cur_stop.items, self.cur_stop.placeholder) 89 | call remove(self.cur_stop, 'items') 90 | endif 91 | endif 92 | 93 | return self.set_stop(a:backwards) 94 | endfunction 95 | 96 | function! s:state_remove_nested(...) dict abort 97 | let id = a:0 ? a:1 : self.stop_no 98 | if type(self.stops[id].placeholder) == type([]) 99 | for i in self.stops[id].placeholder 100 | if type(i) == type([]) 101 | if type(i[1]) != type({}) 102 | call self.remove_nested(i[0]) 103 | call remove(self.stops, i[0]) 104 | else 105 | call filter(self.stops[i[0]].mirrors, 'v:val isnot i[1]') 106 | endif 107 | endif 108 | unlet i " Avoid E706 109 | endfor 110 | endif 111 | endfunction 112 | 113 | " Select the placeholder for the current tab stop 114 | function! s:state_select_word() dict abort 115 | let len = self.stop_len 116 | if !len | return '' | endif 117 | let l = col('.') != 1 ? 'l' : '' 118 | if &sel == 'exclusive' 119 | return "\".l.'v'.len."l\" 120 | endif 121 | return len == 1 ? "\".l.'gh' : "\".l.'v'.(len - 1)."l\" 122 | endfunction 123 | 124 | " Update the snippet as text is typed. The self.update_mirrors() function does 125 | " the actual work. 126 | " If the cursor moves outside of a placeholder, call self.remove() 127 | function! s:state_update_changes() dict abort 128 | let change_len = col('$') - self.prev_len 129 | let self.changed = self.changed || change_len != 0 130 | let self.end_col += change_len 131 | let col = col('.') 132 | 133 | " Increase the endpoint by 1 for &sel = exclusive 134 | if line('.') != self.cur_stop.line || col < self.start_col 135 | \ || col > (self.end_col + (&sel == 'exclusive')) 136 | return self.remove() 137 | endif 138 | 139 | call self.update(self.cur_stop, change_len, change_len) 140 | if !empty(self.mirrors) 141 | call self.update_mirrors(change_len) 142 | endif 143 | 144 | let self.prev_len = col('$') 145 | endfunction 146 | 147 | " Actually update the mirrors for any changed text 148 | function! s:state_update_mirrors(change) dict abort 149 | let newWordLen = self.end_col - self.start_col 150 | let newWord = strpart(getline('.'), self.start_col - 1, newWordLen) 151 | let changeLen = a:change 152 | let curLine = line('.') 153 | let curCol = col('.') 154 | let oldStartSnip = self.start_col 155 | let i = 0 156 | 157 | for mirror in self.mirrors 158 | for stop in values(filter(copy(self.stops), 'v:key != 0')) 159 | if type(stop.placeholder) == type(0) 160 | if mirror.line == stop.line && mirror.col > stop.col 161 | \ && mirror.col < stop.col + stop.placeholder 162 | let stop.placeholder += changeLen 163 | endif 164 | endif 165 | endfor 166 | 167 | if has_key(mirror, 'oldSize') 168 | " recover the old size deduce the endline 169 | let oldSize = mirror.oldSize 170 | else 171 | " first time, we use the intitial size 172 | let oldSize = strlen(newWord) 173 | endif 174 | 175 | " current mirror transformation, and save size 176 | let wordMirror= substitute(newWord, get(mirror, 'pat', ''), get(mirror, 'sub', ''), get(mirror, 'flags', '')) 177 | let mirror.oldSize = strlen(wordMirror) 178 | 179 | " Update other objects on the line 180 | call self.update(mirror, changeLen, mirror.oldSize - oldSize) 181 | 182 | call s:set_line(mirror.line, mirror.col, oldSize, wordMirror) 183 | endfor 184 | 185 | " Reposition the cursor in case a var updates on the same line but before 186 | " the current tabstop 187 | if oldStartSnip != self.start_col || mode() == 'i' 188 | call cursor(0, curCol + self.start_col - oldStartSnip) 189 | endif 190 | endfunction 191 | 192 | function! s:state_find_update_objects(item) dict abort 193 | let item = a:item 194 | let item.update_objects = [] 195 | 196 | " Filter the zeroth stop because it's duplicated as the last 197 | for stop in values(filter(copy(self.stops), 'v:key != 0')) 198 | if stop.line == item.line && stop.col > item.col 199 | call add(item.update_objects, stop) 200 | endif 201 | 202 | let placeholder_len = len(snipMate#sniplist_str(stop.placeholder, b:snip_state.stops)) 203 | for mirror in get(stop, 'mirrors', []) 204 | let mirror.oldSize = placeholder_len 205 | if mirror.line == item.line && mirror.col > item.col 206 | call add(item.update_objects, mirror) 207 | endif 208 | endfor 209 | endfor 210 | 211 | return item.update_objects 212 | endfunction 213 | 214 | function! s:state_update(item, change_len, mirror_change) dict abort 215 | let item = a:item 216 | if !exists('item.update_objects') 217 | let item.update_objects = self.find_update_objects(a:item) 218 | endif 219 | let to_update = item.update_objects 220 | 221 | for obj in to_update 222 | " object does not necessarly have the same decalage 223 | " than mirrors if mirrors use regexp 224 | let obj.col += a:mirror_change 225 | if obj is self.cur_stop 226 | let self.start_col += a:change_len 227 | let self.end_col += a:change_len 228 | endif 229 | endfor 230 | endfunction 231 | 232 | " Split the line into three parts: the mirror, what's before it, and 233 | " what's after it. Then combine them using the new mirror string. 234 | " Subtract one to go from column index to byte index 235 | function! s:set_line(line, col, len, word) 236 | let theline = getline(a:line) 237 | let begin = strpart(theline, 0, a:col - 1) 238 | let end = strpart(theline, a:col + a:len - 1) 239 | call setline(a:line, begin . a:word . end) 240 | endfunction 241 | 242 | " If &cotl contains at least one of these three, we need to add one to our menu 243 | " selection hack in s:state_select_item 244 | function! s:cot_count() 245 | let cotl = split(&cot, ',') 246 | let c = (has('patch-9.0.0567') && count(cotl, 'longest')) + count(cotl, 'noinsert') + count(cotl, 'noselect') 247 | return min([1, c]) 248 | endfunction 249 | 250 | function! s:state_select_item() dict abort 251 | let items = map(copy(self.cur_stop.items), 'snipMate#sniplist_str(v:val, b:snip_state.stops)') 252 | call s:set_line(line('.'), self.start_col, self.end_col - self.start_col, '') 253 | call complete(self.start_col, items) 254 | for i in range(index(self.cur_stop.items, self.cur_stop.placeholder) + s:cot_count()) 255 | call feedkeys("\") 256 | endfor 257 | return '' 258 | endfunction 259 | 260 | call extend(s:state_proto, snipmate#util#add_methods(s:sfile(), 'state', 261 | \ [ 'remove', 'set_stop', 'jump_stop', 'remove_nested', 262 | \ 'select_word', 'update_changes', 'update_mirrors', 'select_item', 263 | \ 'find_next_stop', 'find_update_objects', 'update' ]), 'error') 264 | 265 | " vim:noet:sw=4:ts=4:ft=vim 266 | -------------------------------------------------------------------------------- /autoload/snipmate/legacy.vim: -------------------------------------------------------------------------------- 1 | let s:sigil = nr2char(31) 2 | let snipmate#legacy#sigil = s:sigil 3 | 4 | " Prepare snippet to be processed by s:BuildTabStops 5 | function! snipmate#legacy#process_snippet(snip) abort 6 | let snippet = a:snip 7 | let esc_bslash = '\%(\\\@ a:lnum 108 | \ ? len(matchstr(beforeMark, '.*\n\zs.*')) 109 | \ : a:col + len(beforeMark)) 110 | call add(stops[i].mirrors, { 'line' : line, 'col' : col }) 111 | let withoutOthers = substitute(withoutOthers, ''.s:sigil .''.i.'\ze\(\D\|$\)', '', '') 112 | endw 113 | endif 114 | let i += 1 115 | endw 116 | let stops[i] = stops[0] 117 | return [stops, i + 1] 118 | endfunction 119 | 120 | function! s:substitute_visual(snippet, visual) abort 121 | let lines = [] 122 | for line in split(a:snippet, "\n") 123 | let indent = matchstr(line, '^\t\+') 124 | call add(lines, substitute(line, '{VISUAL}', 125 | \ substitute(escape(a:visual, '%\'), "\n", "\n" . indent, 'g'), 'g')) 126 | endfor 127 | return join(lines, "\n") 128 | endfunction 129 | 130 | " Counts occurences of haystack in needle 131 | function! s:count(haystack, needle) abort 132 | let counter = 0 133 | let index = stridx(a:haystack, a:needle) 134 | while index != -1 135 | let index = stridx(a:haystack, a:needle, index+1) 136 | let counter += 1 137 | endw 138 | return counter 139 | endfunction 140 | -------------------------------------------------------------------------------- /autoload/snipmate/parse.vim: -------------------------------------------------------------------------------- 1 | " Snippet definition parsing code 2 | 3 | function! s:sfile() abort 4 | return expand('') 5 | endfunction 6 | 7 | let s:parser_proto = {} 8 | let s:special_chars = "$`\n" 9 | 10 | function! s:new_parser(text) abort 11 | let ret = copy(s:parser_proto) 12 | let ret.input = a:text 13 | let ret.len = strlen(ret.input) 14 | let ret.pos = -1 15 | let ret.indent = 0 16 | let ret.value = [] 17 | let ret.vars = {} 18 | let ret.stored_lines = [] 19 | call ret.advance() 20 | return ret 21 | endfunction 22 | 23 | function! s:parser_advance(...) dict abort 24 | let self.pos += a:0 ? a:1 : 1 25 | let self.next = self.input[self.pos] 26 | endfunction 27 | 28 | function! s:parser_same(tok) dict abort 29 | if self.next == a:tok 30 | call self.advance() 31 | return 1 32 | else 33 | return 0 34 | endif 35 | endfunction 36 | 37 | function! s:parser_id() dict abort 38 | if self.input[(self.pos):(self.pos+5)] == 'VISUAL' 39 | call self.advance(6) 40 | return 'VISUAL' 41 | elseif self.next =~ '\d' 42 | let end = matchend(self.input, '\d\+', self.pos) 43 | let res = strpart(self.input, self.pos, end - self.pos) 44 | call self.advance(end - self.pos) 45 | return +res " force conversion to Number 46 | endif 47 | return -1 48 | endfunction 49 | 50 | function! s:parser_add_var(var) dict abort 51 | let id = a:var[0] 52 | if !has_key(self.vars, id) 53 | let self.vars[id] = { 'instances' : [] } 54 | endif 55 | call add(self.vars[id].instances, a:var) 56 | endfunction 57 | 58 | function! s:parser_var() dict abort 59 | let ret = [] 60 | if self.same('{') 61 | let id = self.id() 62 | if id >= 0 63 | call add(ret, id) 64 | call extend(ret, self.varend()) 65 | endif 66 | else 67 | let id = self.id() 68 | if id >= 0 69 | call add(ret, id) 70 | endif 71 | endif 72 | return ret 73 | endfunction 74 | 75 | function! s:parser_varend() dict abort 76 | let ret = [] 77 | if self.same(':') 78 | call extend(ret, self.placeholder()) 79 | elseif self.same('/') 80 | call add(ret, self.subst()) 81 | elseif self.next == '|' 82 | call add(ret, self.select()) 83 | endif 84 | call self.same('}') 85 | return ret 86 | endfunction 87 | 88 | function! s:parser_select() dict abort 89 | let items = [] 90 | while self.same('|') 91 | let str = self.text('|}') 92 | call s:mark_vars_in_select(str) 93 | call add(items, str) 94 | endwhile 95 | return ['select'] + items 96 | endfunction 97 | 98 | function! s:mark_vars_in_select(str) 99 | for item in a:str 100 | if type(item) == type([]) 101 | call add(item, { 'select' : 1 }) 102 | endif 103 | unlet! item " avoid E706 104 | endfor 105 | endfunction 106 | 107 | function! s:parser_placeholder() dict abort 108 | let ret = self.text('}') 109 | return empty(ret) ? [''] : ret 110 | endfunction 111 | 112 | function! s:parser_subst() dict abort 113 | let ret = {} 114 | let ret.pat = self.pat() 115 | if self.same('/') 116 | let ret.sub = self.pat(1) 117 | endif 118 | if self.same('/') 119 | let ret.flags = self.pat(1) 120 | endif 121 | return ret 122 | endfunction 123 | 124 | function! s:parser_pat(...) dict abort 125 | let val = '' 126 | 127 | while self.pos < self.len 128 | if self.same('\') 129 | if self.next == '/' 130 | let val .= '/' 131 | call self.advance() 132 | elseif a:0 && self.next == '}' 133 | let val .= '}' 134 | call self.advance() 135 | else 136 | let val .= '\' 137 | endif 138 | elseif self.next == '/' || a:0 && self.next == '}' 139 | break 140 | else 141 | let val .= self.next 142 | call self.advance() 143 | endif 144 | endwhile 145 | 146 | return val 147 | endfunction 148 | 149 | function! s:parser_expr() dict abort 150 | let str = self.string('`') 151 | call self.same('`') 152 | return snipmate#util#eval(str) 153 | endfunction 154 | 155 | function! s:parser_string(till, ...) dict abort 156 | let val = '' 157 | let till = '\V\[' . escape(a:till, '\') . ']' 158 | 159 | while self.pos < self.len 160 | if self.same('\') 161 | if self.next != "\n" 162 | let val .= self.next 163 | endif 164 | call self.advance() 165 | elseif self.next =~# till 166 | break 167 | elseif self.next == "\t" 168 | let self.indent += 1 169 | let val .= s:indent(1) 170 | call self.advance() 171 | else 172 | let val .= self.next 173 | call self.advance() 174 | endif 175 | endwhile 176 | 177 | return val 178 | endfunction 179 | 180 | function! s:join_consecutive_strings(list) abort 181 | let list = a:list 182 | let pos = 0 183 | while pos + 1 < len(list) 184 | if type(list[pos]) == type('') && type(list[pos+1]) == type('') 185 | let list[pos] .= list[pos+1] 186 | call remove(list, pos + 1) 187 | else 188 | let pos += 1 189 | endif 190 | endwhile 191 | endfunction 192 | 193 | function! s:parser_text(till) dict abort 194 | let ret = [] 195 | let till = '\V\[' . escape(a:till, '\') . ']' 196 | let target = ret 197 | 198 | while self.pos < self.len 199 | let lines = [] 200 | 201 | if self.same('$') 202 | let var = self.var() 203 | if !empty(var) 204 | if var[0] is# 'VISUAL' 205 | let lines = s:visual_placeholder(var, self.indent) 206 | " Remove trailing newline. See #245 207 | if lines[-1] =~ '^\s*$' && self.next == "\n" 208 | call remove(lines, -1) 209 | endif 210 | elseif var[0] >= 0 211 | call add(target, var) 212 | call self.add_var(var) 213 | endif 214 | endif 215 | elseif self.same('`') 216 | let lines = split(self.expr(), "\n", 1) 217 | else 218 | let lines = [self.string(a:till . s:special_chars)] 219 | endif 220 | 221 | if !empty(lines) 222 | call add(target, lines[0]) 223 | call extend(self.stored_lines, lines[1:-2]) 224 | " Don't change targets if there's only one line 225 | if exists("lines[1]") 226 | let target = [lines[-1]] 227 | endif 228 | endif 229 | 230 | " Empty lines are ignored if this is tested at the start of an iteration 231 | if self.next =~# till 232 | break 233 | endif 234 | endwhile 235 | 236 | call s:join_consecutive_strings(ret) 237 | if target isnot ret 238 | call s:join_consecutive_strings(target) 239 | call extend(self.stored_lines, target) 240 | endif 241 | 242 | return ret 243 | endfunction 244 | 245 | function! s:parser_line() dict abort 246 | let ret = [] 247 | if !empty(self.stored_lines) 248 | call add(ret, remove(self.stored_lines, 0)) 249 | else 250 | call extend(ret, self.text("\n")) 251 | call self.same("\n") 252 | endif 253 | let self.indent = 0 254 | return ret 255 | endfunction 256 | 257 | function! s:parser_parse() dict abort 258 | while self.pos < self.len || !empty(self.stored_lines) 259 | let line = self.line() 260 | call add(self.value, line) 261 | endwhile 262 | endfunction 263 | 264 | function! s:indent(count) abort 265 | if &expandtab 266 | let shift = repeat(' ', snipmate#util#tabwidth()) 267 | else 268 | let shift = "\t" 269 | endif 270 | return repeat(shift, a:count) 271 | endfunction 272 | 273 | function! s:visual_placeholder(var, indent) abort 274 | let arg = get(a:var, 1, {}) 275 | if type(arg) == type({}) 276 | let pat = get(arg, 'pat', '') 277 | let sub = get(arg, 'sub', '') 278 | let flags = get(arg, 'flags', '') 279 | let content = split(substitute(get(b:, 'snipmate_visual', ''), pat, sub, flags), "\n", 1) 280 | else 281 | let content = split(get(b:, 'snipmate_visual', arg), "\n", 1) 282 | endif 283 | 284 | let indent = s:indent(a:indent) 285 | call map(content, '(v:key != 0) ? indent . v:val : v:val') 286 | 287 | return content 288 | endfunction 289 | 290 | function! s:parser_create_stubs() dict abort 291 | 292 | for [id, dict] in items(self.vars) 293 | 294 | " only instance is in a selection, so remove it 295 | if len(dict.instances) == 1 && type(dict.instances[0][-1]) == type({}) 296 | \ && dict.instances[0][-1] == { 'select' : 1 } 297 | call remove(self.vars, id) 298 | continue 299 | endif 300 | 301 | for i in dict.instances 302 | if len(i) > 1 && type(i[1]) != type({}) 303 | if !has_key(dict, 'placeholder') 304 | if type(i[1]) == type([]) && i[1][0] == 'select' 305 | let dict.placeholder = i[1][1] 306 | let dict.items = i[1][1:] 307 | let i[1] = dict.placeholder 308 | call add(i, dict) 309 | else 310 | let dict.placeholder = i[1:] 311 | call add(i, dict) 312 | endif 313 | else 314 | unlet i[1:] 315 | call s:create_mirror_stub(i, dict) 316 | endif 317 | else 318 | call s:create_mirror_stub(i, dict) 319 | endif 320 | endfor 321 | 322 | if !has_key(dict, 'placeholder') 323 | let dict.placeholder = [] 324 | let j = 0 325 | while len(dict.instances[j]) > 2 326 | let j += 1 327 | endwhile 328 | let oldstub = remove(dict.instances[j], 1, -1)[-1] 329 | call add(dict.instances[j], '') 330 | call add(dict.instances[j], dict) 331 | call filter(dict.mirrors, 'v:val isnot oldstub') 332 | endif 333 | 334 | unlet dict.instances 335 | endfor 336 | 337 | endfunction 338 | 339 | function! s:create_mirror_stub(mirror, dict) 340 | let mirror = a:mirror 341 | let dict = a:dict 342 | let stub = get(mirror, 1, {}) 343 | if stub == { 'select' : 1 } 344 | unlet mirror[1:] 345 | else 346 | call add(mirror, stub) 347 | let dict.mirrors = get(dict, 'mirrors', []) 348 | call add(dict.mirrors, stub) 349 | endif 350 | endfunction 351 | 352 | function! snipmate#parse#snippet(text, ...) abort 353 | let parser = s:new_parser(a:text) 354 | call parser.parse() 355 | if !(a:0 && a:1) 356 | call parser.create_stubs() 357 | endif 358 | unlet! b:snipmate_visual 359 | return [parser.value, parser.vars] 360 | endfunction 361 | 362 | call extend(s:parser_proto, snipmate#util#add_methods(s:sfile(), 'parser', 363 | \ [ 'advance', 'same', 'id', 'add_var', 'var', 'varend', 364 | \ 'line', 'string', 'create_stubs', 'pat', 'select', 365 | \ 'placeholder', 'subst', 'expr', 'text', 'parse', 366 | \ ]), 'error') 367 | -------------------------------------------------------------------------------- /autoload/snipmate/util.vim: -------------------------------------------------------------------------------- 1 | " The next function was based on s:function and s:add_methods in fugitive 2 | " 3 | function! snipmate#util#add_methods(sfile, namespace, methods) abort 4 | let dict = {} 5 | for name in a:methods 6 | let dict[name] = function(join([matchstr(a:sfile, '\d\+'), 7 | \ a:namespace, name], '_')) 8 | endfor 9 | return dict 10 | endfunction 11 | 12 | function! snipmate#util#eval(arg) 13 | try 14 | let ret = eval(a:arg) 15 | catch 16 | echohl ErrorMsg 17 | echom 'SnipMate:Expression: ' . v:exception 18 | echohl None 19 | let ret = '' 20 | endtry 21 | return type(ret) == type('') ? ret : string(ret) 22 | endfunction 23 | 24 | function! snipmate#util#tabwidth() 25 | if &sts > 0 26 | return &sts 27 | else 28 | return exists('*shiftwidth') ? shiftwidth() : &sw 29 | endif 30 | endfunction 31 | -------------------------------------------------------------------------------- /doc/SnipMate.txt: -------------------------------------------------------------------------------- 1 | *SnipMate.txt* Plugin for using TextMate-style snippets in Vim. 2 | 3 | SnipMate *snippet* *snippets* *SnipMate* 4 | 5 | 1. Description |SnipMate-description| 6 | 2. Usage |SnipMate-usage| 7 | 3. Interface and Settings |SnipMate-interface| |SnipMate-settings| 8 | 4. Snippets |SnipMate-snippets| 9 | - Snippet files |SnipMate-snippet-files| 10 | - Snippet syntax |SnipMate-syntax| 11 | 5. Snippet sources |SnipMate-snippet-sources| 12 | 6. Disadvantages to TextMate |SnipMate-disadvantages| 13 | 7. Contact |SnipMate-contact| 14 | 8. License |SnipMate-license| 15 | 16 | For Vim version 7.0 or later. 17 | This plugin only works if 'compatible' is not set. 18 | {Vi does not have any of these features.} 19 | 20 | SnipMate depends on vim-addon-mw-utils and optionally on tlib. 21 | 22 | ============================================================================== 23 | DESCRIPTION *SnipMate-description* 24 | 25 | SnipMate implements snippet features in Vim. A snippet is like a template, 26 | reducing repetitive insertion of pieces of text. Snippets can contain 27 | placeholders for modifying the text if necessary or interpolated code for 28 | evaluation. For example, in C, typing "for" then pushing could expand 29 | to: > 30 | 31 | for (i = 0; i < count; i++) { 32 | /* code */ 33 | } 34 | 35 | SnipMate is inspired by TextMate's snippet features. 36 | 37 | ============================================================================== 38 | USAGE *SnipMate-usage* 39 | 40 | Every snippet consists of an expansion and a trigger. Typing a trigger into 41 | your buffer and then hitting your trigger key ( by default, see 42 | |SnipMate-mappings|) will replace the trigger with the expansion text. 43 | 44 | The expansion text can optionally include tab stops. When it does, upon 45 | expansion of the snippet, the cursor is placed at the first one, and the user 46 | can jump between each tab stop. Each of these tab stops can be represented by 47 | default placeholder text. If such a placeholder is provided, then the text of 48 | the placeholder can be repeated in the snippet at specified mirrors. Any edits 49 | to the placeholder are instantly updated at every mirror. 50 | 51 | SnipMate allows multiple snippets to use the same trigger. When triggered, 52 | a list of all snippets with that trigger is provided and prompts for which 53 | snippet to use. 54 | 55 | *SnipMate-scopes* 56 | SnipMate searches for snippets inside a directory named "snippets" inside each 57 | entry in 'runtimepath'. Which files are loaded depends on 'filetype' and 58 | 'syntax'; see |SnipMate-syntax| for more information. Snippets are loaded and 59 | refreshed automatically on demand. 60 | 61 | Note: SnipMate does not ship with any snippets. In order to use it, the user 62 | must either write their own snippets or obtain some from a repository like 63 | https://github.com/honza/vim-snippets 64 | 65 | ============================================================================== 66 | INTERFACE AND SETTINGS *SnipMate-interface* *SnipMate-settings* 67 | 68 | *SnipMate-commands* 69 | Commands~ 70 | 71 | *:SnipMateOpenSnippetFiles* 72 | :SnipMateOpenSnippetFiles Opens a list of all valid snippet locations 73 | based on the current scope |SnipMate-scopes|. 74 | Only existing files and non-existing .snippets 75 | files will be shown, with the existing files 76 | shown first. 77 | 78 | The optional dependency tlib is required for 79 | this command to work. 80 | 81 | :SnipMateLoadScope[!] scope [scope ...] 82 | Load snippets from additional scopes. Without 83 | [!] the additional scopes are loaded only in 84 | the current buffer. For example > 85 | :SnipMateLoadScopes rails 86 | < will load all rails.snippets in the current 87 | buffer. 88 | 89 | *SnipMate-options* 90 | Options~ 91 | 92 | g:snips_author A variable used in some snippets in place of 93 | the author's (your) name. Similar to 94 | $TM_FULLNAME in TextMate. For example, > 95 | snippet name 96 | `g:snips_author` 97 | < creates a snippet "name" that expands to your 98 | name. 99 | 100 | g:snipMate This |Dictionary| contains other SnipMate 101 | options. In short add > 102 | let g:snipMate = {} 103 | < to your .vimrc before setting other SnipMate 104 | options. 105 | 106 | g:snipMate.scope_aliases A |Dictionary| associating certain filetypes 107 | with other scopes |SnipMate-scopes|. The 108 | entries consist of a filetype as the key and 109 | a comma-separated list of aliases as the 110 | value. For example, > 111 | let g:snipMate.scope_aliases = {} 112 | let g:snipMate.scope_aliases['ruby'] 113 | \ = 'ruby,ruby-rails' 114 | < tells SnipMate that "ruby-rails" snippets in 115 | addition to "ruby" snippets should be loaded 116 | when editing files with 'filetype' set to 117 | "ruby" or contains "ruby" as an entry in the 118 | case of dotted filetypes. A buffer local 119 | variant b:snipMate_scope_aliases is merged 120 | with the global variant. 121 | 122 | g:snipMate_no_default_aliases Note: This has been renamed to the following. 123 | 124 | g:snipMate.no_default_aliases 125 | When set to 1, prevents SnipMate from loading 126 | default scope aliases. The defaults are: 127 | Filetype Alias ~ 128 | cpp c 129 | cu c 130 | eruby eruby-rails,html 131 | html javascript 132 | mxml actionscript 133 | objc c 134 | php php,html,javascript 135 | ur html,javascript 136 | xhtml html 137 | Individual defaults can be disabled by setting 138 | them to an empty value: > 139 | let g:snipMate.scope_aliases.php = '' 140 | < will disable the default PHP alias. 141 | Note: Setting this option does not disable 142 | scope aliases entirely, only those made by 143 | SnipMate itself. Any scope aliases created by 144 | the user or someone else will still be in 145 | effect. 146 | 147 | g:snipMate.snippet_version 148 | The snippet parser version to use. The 149 | possible values are: 150 | 0 Use the older parser 151 | 1 Use the newer parser 152 | If unset, SnipMate defaults to version 0. The 153 | value of this option is also used for all 154 | .snippet files. See |SnipMate-parser-versions| 155 | for more information. 156 | 157 | g:snipMate.override 158 | As detailed below, when two snippets with the 159 | same name and description are loaded, both are 160 | kept and differentiated by the location of the 161 | file they were in. When this option is enabled 162 | (set to 1), the snippet originating in the 163 | last loaded file is kept, similar to how Vim 164 | maps and other settings work. Note: Load order 165 | is determined by 'runtimepath'. 166 | 167 | Duplicates are only dropped after reading one 168 | snippet file. If multiple files contain a 169 | snippet see always_choose_first 170 | g:snipMate.always_choose_first 171 | Always choose first snippet if there are 172 | multiple left 173 | 174 | g:snipMate.description_in_completion 175 | If set to 1 (default is 0), snippet 176 | descriptions will be included in the popup 177 | menu used for snippet completion, like with 178 | snipMateShow. 179 | 180 | g:snipMate['no_match_completion_feedkeys_chars'] 181 | A string inserted when no match for a trigger 182 | is found. By default a tab is inserted 183 | according to 'expandtab', 'tabstop', and 184 | 'softtabstop'. Set it to the empty string to 185 | prevent anything from being inserted. 186 | 187 | *SnipMate-mappings* 188 | Mappings~ 189 | 190 | The mappings SnipMate uses can be customized with the |:map| commands. For 191 | example, to change the key that triggers snippets and moves to the next 192 | tab stop, > 193 | 194 | :imap snipMateNextOrTrigger 195 | :smap snipMateNextOrTrigger 196 | 197 | Note: The noremap variants of the map commands must NOT be used. 198 | 199 | The list of possible mappings is as follows: 200 | 201 | snipMateNextOrTrigger Default: Mode: Insert, Select 202 | Jumps to the next tab stop or, if none exists, 203 | try to expand a snippet. Use in both insert 204 | and select modes. 205 | 206 | snipMateTrigger Default: unmapped Mode: Insert 207 | Try to expand a snippet regardless of any 208 | existing snippet expansion. If done within an 209 | expanded snippet, the outer snippet's tab 210 | stops are lost, unless expansion failed. 211 | 212 | snipMateBack Default: Mode: Insert, Select 213 | Jump to the previous tab stop, if it exists. 214 | Use in both insert and select modes. 215 | 216 | snipMateShow Default: Mode: Insert 217 | Show all available snippets (that start with 218 | the previous text, if it exists). Use in 219 | insert mode. 220 | 221 | snipMateVisual Default: Mode: Visual 222 | See |SnipMate-visual|. 223 | 224 | Additionally, is mapped in visual mode in .snippets files for retabbing 225 | snippets. 226 | 227 | 228 | *SnipMate-autocmds* 229 | Autocommands~ 230 | 231 | Autocommands allow code written by the user to be executed automatically at 232 | certain points within SnipMate's normal execution. Here is a list of events 233 | available to the User. All of these are subject to change. 234 | 235 | SnipLookupPre *SnipMate-SnipLookupPre* 236 | SnipLookupPost *SnipMate-SnipLookupPost* 237 | 238 | These two events are run when determining what text to use as 239 | the trigger. The Pre version can be used to forgo SnipMate's 240 | normal lookups entirely, and the Post version can be used to 241 | add to whatever lookups already exist. 242 | 243 | Both events have access to b:snip_word, a variable 244 | containining the WORD before the cursor when snippet 245 | triggering began, and b:snip_lookups, a |List| containing all 246 | possible lookups that SnipMate should try as possible 247 | triggers. They are tried in order from the beginning. 248 | 249 | If SnipLookupPre runs and puts anything in b:snip_lookups, 250 | SnipMate's normal lookups will not be tried. SnipLookupPost 251 | runs regardless. 252 | 253 | The following example only allows a '.' as a possible trigger 254 | if the WORD before the cursor is not "self.": > 255 | 256 | au User SnipLookupPost call My_func() 257 | 258 | function My_func() abort 259 | if match(b:snip_word, 'self\.$') != -1 260 | call filter(b:snip_lookups, "v:val !~# '^\\.$'") 261 | endif 262 | endfunction 263 | 264 | 265 | ============================================================================== 266 | SNIPPETS *SnipMate-snippets* 267 | 268 | *SnipMate-snippet-files* 269 | Snippet Files ~ 270 | 271 | Note: SnipMate does not ship with any snippets. 272 | 273 | SnipMate looks inside of each entry of 'rtp' (or |SnipMate-snippet-sources|) 274 | for a directory named /snippets/. Based on the 'filetype' and 'syntax' 275 | settings (dotted filetypes are parsed), the following files are read for 276 | snippets: > 277 | 278 | .../snippets/.snippets 279 | .../snippets/_.snippets 280 | .../snippets//.snippets 281 | .../snippets//.snippet 282 | .../snippets///.snippet 283 | 284 | where is a scope or 'filetype' or 'syntax', is an arbitrary 285 | name, is the trigger for a snippet, and is 286 | a description used for |SnipMate-multisnip|. Snippets in the `_` scope (for 287 | example `.../snippets/_.snippets`) are loaded for all filetypes. 288 | 289 | A .snippet file defines a single snippet with the trigger (and description) 290 | determined by the filename. The entire contents of the file are used as the 291 | snippet expansion text. 292 | 293 | Multiple snippets can be defined in *.snippets files. Each snippet definition 294 | looks something like: > 295 | 296 | snippet trigger optional description 297 | expanded text 298 | more expanded text 299 | 300 | < *SnipMate-multisnip* 301 | The description is optional. If it is left out, the description "default" is 302 | used. When two snippets in the same scope have the same name and the same 303 | description, SnipMate will try to preserve both. The g:snipMate.override 304 | option disables this, in favor of keeping the last-loaded snippet. This can be 305 | overridden on a per-snippet basis by defining the snippet with a bang (!): > 306 | 307 | snippet! trigger optional description 308 | expanded text 309 | more expanded text 310 | 311 | Two bangs will remove the trigger entirely from SnipMate's lookup. In this 312 | case any snippet text is unused. 313 | 314 | Note: Hard tabs in the expansion text are required. When the snippet is 315 | expanded in the text and 'expandtab' is set, each tab will be replaced with 316 | spaces based on 'softtabstop' if nonzero or 'shiftwidth' otherwise. 317 | 318 | 319 | SnipMate currently provides two versions for the snippet parser. The 320 | differences between them can be found at |SnipMate-parser-versions|. Which 321 | version parser the snippets in a file should be used with can be specified 322 | with a version line, e.g.: > 323 | 324 | version 1 325 | 326 | Specification of a version applies to the snippets following it. Multiple 327 | version specifications can appear in a single file to intermix version 0 and 328 | version 1 snippets. The default is determined by the 329 | g:snipMate.snippet_version option. |SnipMate-options| 330 | 331 | Comments can be made in .snippets files by starting a line with a # character. 332 | However these can't be used inside of snippet definitions: > 333 | 334 | # this is a correct comment 335 | snippet trigger 336 | expanded text 337 | snippet another_trigger 338 | # this isn't a comment! 339 | expanded text 340 | 341 | This should hopefully be clear with the included syntax highlighting. 342 | 343 | *SnipMate-extends* 344 | Borrowing from UltiSnips, .snippets files can also contain an extends 345 | directive, for example: > 346 | 347 | extends html, javascript, css 348 | 349 | will tell SnipMate to also read html, javascript, and css snippets. 350 | 351 | SNIPPET SYNTAX *snippet-syntax* *SnipMate-syntax* 352 | 353 | As mentioned above, there are two versions of the snippet parser. They are 354 | selected by the g:snipMate.snippet_version option (|SnipMate-options|) or the 355 | version directive in .snippets files. Differences will be mentioned throughout 356 | with a summary at |SnipMate-parser-versions|. 357 | 358 | Anywhere in a snippet, a backslash escapes the character following it, 359 | regardless of whether that character is special or not. That is, '\a' will 360 | always result in an 'a' in the output. A single backslash can be output by 361 | using '\\'. 362 | 363 | *SnipMate-tabstops* 364 | Tab stops~ 365 | 366 | When triggering a snippet, SnipMate will by default jump to the very end of 367 | the snippet text. This can be changed through the use of tab stops: $1, $2, 368 | and so on. After expansion, SnipMate will jump to the first tab stop. From 369 | then on, the snipMateNextOrTrigger map will jump to the next higher 370 | numbered tabs top. 371 | 372 | In the case of an ambiguity, for example if a stop occurs just before 373 | a literal number, braces may be placed around the stop number to resolve it: 374 | ${3}79 is the third tab stop followed by the string "79". 375 | 376 | NOTE: In the version 0 snippet parser, the braces are mandatory. 377 | 378 | *SnipMate-zero-tabstop* 379 | SnipMate will always stop at the special zero tab stop $0. Once it jumps to 380 | the zero tab stop, snippet expansion is finished. If the zero tab stop is not 381 | present in a definition, it will be put at the end. 382 | 383 | For example, to place the cursor first on the id of a
tag, then on its 384 | class, and finally end editing its contents: > 385 | 386 | snippet div 387 |
388 | $0 389 |
390 | 391 | < *SnipMate-placeholders* 392 | In addition to being simply a location, each tab stop contains a placeholder, 393 | or some default text. The placeholder can be specified for every tab stop 394 | (including the zero tab stop) with a colon after the stop ID, as in 395 | ${1:default text}. The braces are required only when specifying a placeholder. 396 | Once a tab stop with a placeholder is reached, the placeholder will be 397 | selected in |Select-mode|. For example, > 398 | 399 | snippet div 400 |
401 | $0 402 |
403 | 404 | Finally, placeholders can contain mirrors and evaluations (detailed below) 405 | and, in version 1 of the snippet parser, even entire other tab stops. If the 406 | placeholder is edited, then these nested tab stops are removed and skipped 407 | entirely. 408 | NOTE: Version 1 of the snippet parser must be used! See 409 | |SnipMate-parser-versions|. 410 | For example, > 411 | 412 | snippet div 413 | 414 | $0 415 |
416 | 417 | When expanded, this snippet selects the entirety of the id attribute. If this 418 | stop is edited, then the second tab stop is removed and the third tab stop 419 | becomes the next one. If the first tab stop is left unedited, then SnipMate 420 | jumps to the second tab stop. This allows the user to use a single div snippet 421 | that can be used for instances where the id or class attributes are desired 422 | and those where they are not. 423 | 424 | *SnipMate-mirrors* 425 | Mirrors~ 426 | 427 | A mirror is simply a copy of a tab stop's text, updated as the tab stop is 428 | edited. These look like a tab stop without a placeholder; $1 for example. In 429 | the event that no placeholder is specified for a certain tab stop--say $1--the 430 | first instance becomes the tab stop and the rest become mirrors. 431 | 432 | Additionally, in version 1 of the parser, substitutions similar to 433 | |:substitute| can be performed. For instance ${1/foo/bar/g} will replace all 434 | instances of "foo" in the $1 mirror with "bar". This uses |substitute()| 435 | behind the scenes. 436 | 437 | Note: Just like with tab stops, braces can be used to avoid ambiguities: ${1}2 438 | is a mirror of the first tab stop followed by a 2. Version 0 of the snippet 439 | parser offers no way to resolve such ambiguities. Version 0 also requires that 440 | a tabstop have a placeholder before its mirrors work. 441 | 442 | As an example, > 443 | 444 | snippet for 445 | for ($1 = ${2:start}; ${1:i} < ${3:end}; $1${4:++}) { 446 | ${0:/* code */} 447 | } 448 | 449 | < *SnipMate-eval* 450 | Expression Evaluation~ 451 | 452 | Snippets can contain Vim script expressions that are evaluated as the snippet 453 | is expanded. Expressions are specified inside backticks: > 454 | 455 | snippet date 456 | `strftime("%Y-%m-%d")` 457 | 458 | If the expression results in any Vim error, the error will be displayed (or 459 | found in :messages) and the result of the expression will be the empty string. 460 | 461 | Filename([{expr}] [, {defaultText}]) *SnipMate-Filename()* 462 | 463 | Since the current filename is used often in snippets, a default function 464 | has been defined for it in SnipMate.vim, appropriately called Filename(). 465 | 466 | With no arguments, the default filename without an extension is returned; 467 | the first argument specifies what to place before or after the filename, 468 | and the second argument supplies the default text to be used if the file 469 | has not been named. "$1" in the first argument is replaced with the filename; 470 | if you only want the filename to be returned, the first argument can be left 471 | blank. Examples: > 472 | 473 | snippet filename 474 | `Filename()` 475 | snippet filename_with_default 476 | `Filename('', 'name')` 477 | snippet filename_foo 478 | `Filename('$1_foo')` 479 | 480 | The first example returns the filename if it the file has been named, and an 481 | empty string if it hasn't. The second returns the filename if it's been named, 482 | and "name" if it hasn't. The third returns the filename followed by "_foo" if 483 | it has been named, and an empty string if it hasn't. 484 | 485 | *SnipMate-selections* 486 | Selections~ 487 | 488 | Note: Version 1 of the parser is required for selections. 489 | 490 | In addition to providing sample text as a placeholder, you can specify a list 491 | of options that the user can choose from via |popupmenu-completion|: 492 | > 493 | snippet todo 494 | # ${1|TODO|FIXME|BUG}: $2 495 | < Notice that the : is replaced by the pipe |. Currently mirrors and 496 | placeholders are not supported within selection text. If alterations are made 497 | to the selected text, the stop becomes a regular stop, losing the selections. 498 | 499 | *SnipMate-visual* 500 | The VISUAL Stop~ 501 | 502 | While tab stops have numeric IDs, a special one exists with the ID 'VISUAL'. 503 | When a snippet is expanded, if any text had been grabbed with the 504 | snipMateVisual mapping (see |SnipMate-mappings|), all instances of the VISUAL 505 | stop will be replaced with it. Both transformations as well as a default 506 | placeholder can be used with the VISUAL stop. 507 | 508 | Note: Both $VISUAL and ${VISUAL} are valid in version 1 of the snippet parser. 509 | In version 0, only {VISUAL} is valid (without the $), and neither 510 | transformations nor a default placeholder can be used. 511 | 512 | Example: > 513 | 514 | snippet div 515 |
516 | ${0:${VISUAL:}} 517 |
518 | < 519 | *SnipMate-parser-versions* 520 | Parser Versions~ 521 | 522 | SnipMate provides two versions for its snippet parser. Version 0 is the legacy 523 | regex based version and is updated sparingly. Version 1 is the revamped 524 | version with new features. Any newly developed features will likely only be 525 | available to version 1 users. 526 | 527 | Which version is used is determined by version directives in snippet files 528 | (|SnipMate-snippet-files|) and by the g:snipMate.snippet_version option 529 | (|SnipMate-options|). 530 | 531 | A complete list of current differences is as follows: 532 | - Version 0 does not support nested placeholders such as ${1:"${2:foo}"} at 533 | all. 534 | - Backslash escaping is guaranteed to work in version 1. In certain edge cases 535 | this may not work in version 0. 536 | - Certain syntactic errors, such as a missing closing brace for a tabstop, are 537 | more gracefully handled in version 1. In most cases, the parser will either 538 | discard the error or, as in the previous example, end an item at the end of 539 | line. Version 0 may not be predictable in this regard. 540 | - Braces are not mandatory in version 1. SnipMate will determine which 541 | instance of a stop ID to use based on the presence of a placeholder, or 542 | whichever instance occurs first. Braces can therefore be used to 543 | disambiguate between stop 12, $12, and stop 1 followed by a 2: ${1}2. In 544 | other words, version 0 makes a distinction between a mirror and a stop while 545 | version 1 resolves the differences for you. 546 | - Placeholders are not mandatory to enable mirror support in version 1. 547 | - Version 0 uses the confusing syntax {VISUAL} to refer to visual content. 548 | Version 1 treats it as just another stop ID, so both $VISUAL and ${VISUAL} 549 | work. Plus version 1 allows a default value in case no visual selection has 550 | been made. 551 | - Transformations similar to |:substitute| can be preformed on any mirror, 552 | including visual content. 553 | 554 | *SnipMate-deprecate* 555 | Deprecation~ 556 | 557 | The legacy parser, version 0, is deprecated. It is currently still the default 558 | parser, but that will be changing. NOTE that switching which parser you use 559 | could require changes to your snippets--see the previous section. 560 | 561 | To continue using the old parser, set g:snipMate.snippet_version (see 562 | |SnipMate-options|) to 0 in your |vimrc|. 563 | 564 | Setting g:snipMate.snippet_version to either 0 or 1 will remove the start up 565 | message. One way this can be done--to use the new parser--is as follows: 566 | > 567 | let g:snipMate = { 'snippet_version' : 1 } 568 | < 569 | ============================================================================== 570 | SNIPPET SOURCES *SnipMate-snippet-sources* 571 | 572 | SnipMate is configurable. 573 | 574 | plugin/SnipMate.vim assigns a couple important keys: > 575 | 576 | " default implementation collecting snippets by handlers 577 | let g:SnipMate['get_snippets'] = SnipMate#GetSnippets 578 | " default handler: 579 | let g:SnipMateSources['default'] = SnipMate#DefaultPool 580 | 581 | You can override both of those settings. 582 | 583 | You can see that the default set of snippets is determined by Vim's 'rtp'. 584 | 585 | Example 1:~ 586 | autoload/SnipMate_python_demo.vim shows how you can register additional 587 | sources such as creating snippets on the fly representing python function 588 | definitions found in the current file. 589 | 590 | Example 2:~ 591 | Add to your ~/.vimrc: For each new snippet add a second version ending in _ 592 | adding folding markers > 593 | 594 | let g:commentChar = { 595 | \ 'vim': '"', 596 | \ 'c': '//', 597 | \ 'cpp': '//', 598 | \ 'sh': '#', 599 | \ 'python': '#' 600 | \ } 601 | " url https://github.com/garbas/vim-snipmate/issues/49 602 | fun! AddFolding(text) 603 | return substitute(a:text,'\n'," ".g:commentChar[&ft]." {{{\n",1)."\n".g:commentChar[&ft]." }}}" 604 | endf 605 | 606 | fun! SnippetsWithFolding(scopes, trigger, result) 607 | " hacky: temporarely remove this function to prevent infinite recursion: 608 | call remove(g:SnipMateSources, 'with_folding') 609 | " get list of snippets: 610 | let result = SnipMate#GetSnippets(a:scopes, substitute(a:trigger,'_\(\*\)\?$','\1','')) 611 | let g:SnipMateSources['with_folding'] = funcref#Function('SnippetsWithFolding') 612 | 613 | " add folding: 614 | for k in keys(result) 615 | let a:result[k.'_'] = map(result[k],'AddFolding(v:val)') 616 | endfor 617 | endf 618 | 619 | " force setting default: 620 | runtime plugin/SnipMate.vim 621 | " add our own source 622 | let g:SnipMateSources['with_folding'] = funcref#Function('SnippetsWithFolding') 623 | 624 | See |SnipMate-syntax| for more details about all possible relative locations 625 | to 'rtp' can be found in. 626 | 627 | ============================================================================== 628 | KNOWN ISSUES *SnipMate-known-issues* 629 | 630 | SnipMate.vim currently has the following disadvantages to TextMate's snippets: 631 | - Placeholders cannot span multiple lines. 632 | - Activating snippets in different scopes of the same file is 633 | not possible. 634 | - Vim formatting with fo=t or fo=a can mess up SnipMate. 635 | 636 | Perhaps some of these features will be added in a later release. 637 | 638 | ============================================================================== 639 | CHANGELOG *SnipMate-changelog* 640 | 641 | 0.90 - 2023-12-29 642 | ----------------- 643 | 644 | - Remove empty lines at the end of a `${VISUAL}` expansion 645 | - Fix code for opening folds when expanding a snippet 646 | - Deprecate legacy snippet parser 647 | - Fix jumps when `&sel == 'exclusive'` 648 | 649 | 0.89 - 2016-05-29 650 | ----------------- 651 | 652 | * Various regex updates to legacy parser 653 | * Addition of double bang syntax to completely remove a snippet from lookup 654 | * Group various SnipMate autocommands 655 | * Support setting 'shiftwidth' to 0 656 | * Parser now operates linewise, adding some flexibility 657 | * Mirror substitutions are more literal 658 | * Mirror length is calculated correctly when substitutions occur 659 | 660 | 0.88 - 2015-04-04 661 | ----------------- 662 | 663 | * Implement simple caching 664 | * Remove expansion guards 665 | * Add `:SnipMateLoadScope` command and buffer-local scope aliases 666 | * Load `_*.snippets` files 667 | * Use CursorMoved autocmd events entirely 668 | 669 | * The nested branch has been merged 670 | * A new snippet parser has been added. The g:snipmate.version as well as 671 | version lines in snippet files determines which is used 672 | * The new parser supports tab stops placed within placeholders, 673 | substitutions, non-consecutive stop numbers, and fewer ambiguities 674 | * The stop jumping code has been updated 675 | * Tests have been added for the jumping code and the new parser 676 | 677 | * The override branch has been merged 678 | * The g:snipMate.override option is added. When enabled, if two snippets 679 | share the same name, the later-loaded one is kept and the other discarded 680 | * Override behavior can be enabled on a per-snippet basis with a bang (!) in 681 | the snippet file 682 | * Otherwise, SnipMate tries to preserve all snippets loaded 683 | 684 | * Fix bug with mirrors in the first column 685 | * Fix bug with tabs in indents 686 | 687 | * Fix bug with mirrors in placeholders 688 | * Fix reading single snippet files 689 | * Fix the use of the visual map at the end of a line 690 | * Fix expansion of stops containing only the zero tab stop 691 | * Remove select mode mappings 692 | * Indent visual placeholder expansions and remove extraneous lines 693 | 694 | 695 | 696 | 0.87 - 2014-01-04 697 | ----------------- 698 | 699 | * Stop indenting empty lines when expanding snippets 700 | * Support extends keyword in .snippets files 701 | * Fix visual placeholder support 702 | * Add zero tabstop support 703 | * Support negative 'softtabstop' 704 | * Add g:snipMate_no_default_aliases option 705 | * Add snipMateTrigger for triggering an expansion inside a snippet 706 | * Add snipMate#CanBeTriggered() function 707 | 708 | 0.86 - 2013-06-15 709 | ----------------- 710 | * Use more idiomatic maps 711 | * Remove most select mode mappings 712 | 713 | * Fix disappearing variables bug (hpesoj) 714 | * Fix cursor position bug when a variable is on the same line as the stop 715 | * Fix undo point creation causing problems with Supertab 716 | * Fix bug where SnipMate would use a typed trigger as a regular expression 717 | 718 | 0.85 - 2013-04-03 719 | ----------------- 720 | 721 | * Allow trigger key customization 722 | * Enable undoing of snippet expansion 723 | * Support backslash escaping in snippets 724 | * Add support for {VISUAL} 725 | * Expand filetype extension with scope_aliases 726 | * Add expansion guards 727 | * Enable per-buffer expansion of snippets 728 | * Fix 'cpo' compatibility 729 | * Update supertab compatibility 730 | * Enable customization of various things through g:SnipMate 731 | 732 | * Disable spelling in snippet files 733 | * Highlight trigger names in .snippets files 734 | 735 | * Update many snippets 736 | * Separate sample snippets into separate repository 737 | 738 | 0.84 739 | ---- 740 | 741 | * Unreleased version by Michael Sanders, available on his GitHub, 742 | 743 | 744 | 0.83 - 2009-07-13 745 | ----------------- 746 | 747 | * Last release done by Michael Sanders, available at 748 | 749 | 750 | ============================================================================== 751 | CONTACT *SnipMate-contact* *SnipMate-author* 752 | 753 | SnipMate is currently maintained by: 754 | - Rok Garbas 755 | - Marc Weber (marco-oweber@gmx.de) 756 | - Adnan Zafar 757 | 758 | For bug reports, issues, or questions, check out the Issues page on GitHub: 759 | https://github.com/garbas/vim-snipmate/issues 760 | 761 | The original author, Michael Sanders, can be reached at: 762 | msanders42+snipmate gmail com 763 | 764 | 765 | ============================================================================== 766 | LICENSE *SnipMate-license* 767 | 768 | SnipMate is released under the MIT license: 769 | 770 | Copyright 2009-2010 Michael Sanders. All rights reserved. 771 | 772 | Permission is hereby granted, free of charge, to any person obtaining a copy 773 | of this software and associated documentation files (the "Software"), to deal 774 | in the Software without restriction, including without limitation the rights 775 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 776 | copies of the Software, and to permit persons to whom the Software is 777 | furnished to do so, subject to the following conditions: 778 | 779 | The above copyright notice and this permission notice shall be included in all 780 | copies or substantial portions of the Software. 781 | 782 | The software is provided "as is", without warranty of any kind, express or 783 | implied, including but not limited to the warranties of merchantability, 784 | fitness for a particular purpose and noninfringement. In no event shall the 785 | authors or copyright holders be liable for any claim, damages or other 786 | liability, whether in an action of contract, tort or otherwise, arising from, 787 | out of or in connection with the software or the use or other dealings in the 788 | software. 789 | 790 | ============================================================================== 791 | vim:tw=78:ts=8:ft=help:norl: 792 | -------------------------------------------------------------------------------- /ftplugin/html_snip_helper.vim: -------------------------------------------------------------------------------- 1 | " Helper function for (x)html snippets 2 | if exists('s:did_snip_helper') || &cp || !exists('loaded_snips') 3 | finish 4 | endif 5 | let s:did_snip_helper = 1 6 | 7 | " Automatically closes tag if in xhtml 8 | fun! Close() abort 9 | return stridx(&ft, 'xhtml') == -1 ? '' : ' /' 10 | endf 11 | -------------------------------------------------------------------------------- /ftplugin/snippets.vim: -------------------------------------------------------------------------------- 1 | " Vim filetype plugin for SnipMate snippets (.snippets and .snippet files) 2 | 3 | if exists("b:did_ftplugin") 4 | finish 5 | endif 6 | let b:did_ftplugin = 1 7 | 8 | let b:undo_ftplugin = "setl et< sts< cms< fdm< fde<" 9 | 10 | " Use hard tabs 11 | setlocal noexpandtab softtabstop=0 12 | 13 | setlocal foldmethod=expr foldexpr=getline(v:lnum)!~'^\\t\\\\|^$'?'>1':1 14 | 15 | setlocal commentstring=#\ %s 16 | setlocal nospell 17 | 18 | command! -buffer -range=% RetabSnip 19 | \ echom "This command is deprecated. Use :retab and = instead. Doing that now." 20 | \ | ,retab! | ,normal = 21 | -------------------------------------------------------------------------------- /indent/snippets.vim: -------------------------------------------------------------------------------- 1 | " Simple indent support for SnipMate snippets files 2 | 3 | if exists('b:did_indent') 4 | finish 5 | endif 6 | let b:did_indent = 1 7 | 8 | setlocal nosmartindent 9 | setlocal indentkeys=!^F,o,O,=snippet,=version,=extends 10 | setlocal indentexpr=GetSnippetIndent() 11 | 12 | if exists("*GetSnippetIndent") 13 | finish 14 | endif 15 | 16 | function! GetSnippetIndent() 17 | let line = getline(v:lnum) 18 | let prev_lnum = v:lnum - 1 19 | let prev_line = prev_lnum != 0 ? getline(prev_lnum) : "" 20 | 21 | if line =~# '\v^(snippet|extends|version) ' 22 | return 0 23 | elseif indent(v:lnum) > 0 24 | return indent(v:lnum) 25 | elseif prev_line =~# '^snippet ' 26 | return &sw 27 | elseif indent(prev_lnum) > 0 28 | return indent(prev_lnum) 29 | endif 30 | 31 | return 0 32 | endfunction 33 | -------------------------------------------------------------------------------- /plugin/snipMate.vim: -------------------------------------------------------------------------------- 1 | " File: snipMate.vim 2 | " Description: snipMate.vim implements some of TextMate's snippets features in 3 | " Vim. A snippet is a piece of often-typed text that you can 4 | " insert into your document using a trigger word followed by a "". 5 | " 6 | " For more help see snipMate.txt; you can do this by using: 7 | " :helptags ~/.vim/doc 8 | " :h SnipMate 9 | 10 | if exists('loaded_snips') || &cp || version < 700 11 | finish 12 | endif 13 | let loaded_snips = 1 14 | 15 | " Save and reset 'cpo' 16 | let s:save_cpo = &cpo 17 | set cpo&vim 18 | 19 | try 20 | call funcref#Function('') 21 | catch /.*/ 22 | echoe "you're missing vim-addon-mw-utils. See install instructions at ".expand(':h:h').'/README.md' 23 | endtry 24 | 25 | if (!exists('g:snipMateSources')) 26 | let g:snipMateSources = {} 27 | " Default source: get snippets based on runtimepath 28 | let g:snipMateSources['default'] = funcref#Function('snipMate#DefaultPool') 29 | endif 30 | 31 | augroup SnipMateDetect 32 | au BufRead,BufNewFile *.snippet,*.snippets setlocal filetype=snippets 33 | au FileType snippets if expand(':e') =~# 'snippet$' 34 | \ | setlocal syntax=snippet 35 | \ | else 36 | \ | setlocal syntax=snippets 37 | \ | endif 38 | augroup END 39 | 40 | inoremap snipMateNextOrTrigger =snipMate#TriggerSnippet() 41 | snoremap snipMateNextOrTrigger a=snipMate#TriggerSnippet() 42 | inoremap snipMateTrigger =snipMate#TriggerSnippet(1) 43 | inoremap snipMateBack =snipMate#BackwardsSnippet() 44 | snoremap snipMateBack a=snipMate#BackwardsSnippet() 45 | inoremap snipMateShow =snipMate#ShowAvailableSnips() 46 | xnoremap snipMateVisual :call grab_visual()gv"_c 47 | 48 | " config variables 49 | if !exists('g:snips_author') 50 | let g:snips_author = 'Me' 51 | endif 52 | if !exists('g:snipMate') 53 | let g:snipMate = {} 54 | endif 55 | 56 | " SnipMate inserts this string when no snippet expansion can be done 57 | let g:snipMate['no_match_completion_feedkeys_chars'] = 58 | \ get(g:snipMate, 'no_match_completion_feedkeys_chars', "\t") 59 | 60 | " Add default scope aliases, without overriding user settings 61 | let g:snipMate.scope_aliases = get(g:snipMate, 'scope_aliases', {}) 62 | if exists('g:snipMate_no_default_aliases') 63 | echom 'The g:snipMate_no_default_aliases option has been renamed.' 64 | \ 'See :h snipMate-options.' 65 | endif 66 | if (!exists('g:snipMate_no_default_aliases') || !g:snipMate_no_default_aliases) 67 | \ && (!exists('g:snipMate.no_default_aliases') 68 | \ || !g:snipMate.no_default_aliases) 69 | let g:snipMate.scope_aliases.objc = 70 | \ get(g:snipMate.scope_aliases, 'objc', 'c') 71 | let g:snipMate.scope_aliases.cpp = 72 | \ get(g:snipMate.scope_aliases, 'cpp', 'c') 73 | let g:snipMate.scope_aliases.cu = 74 | \ get(g:snipMate.scope_aliases, 'cu', 'c') 75 | let g:snipMate.scope_aliases.xhtml = 76 | \ get(g:snipMate.scope_aliases, 'xhtml', 'html') 77 | let g:snipMate.scope_aliases.html = 78 | \ get(g:snipMate.scope_aliases, 'html', 'javascript') 79 | let g:snipMate.scope_aliases.php = 80 | \ get(g:snipMate.scope_aliases, 'php', 'php,html,javascript') 81 | let g:snipMate.scope_aliases.ur = 82 | \ get(g:snipMate.scope_aliases, 'ur', 'html,javascript') 83 | let g:snipMate.scope_aliases.mxml = 84 | \ get(g:snipMate.scope_aliases, 'mxml', 'actionscript') 85 | let g:snipMate.scope_aliases.eruby = 86 | \ get(g:snipMate.scope_aliases, 'eruby', 'eruby-rails,html') 87 | let g:snipMate.scope_aliases.scss = 88 | \ get(g:snipMate.scope_aliases, 'scss', 'css') 89 | let g:snipMate.scope_aliases.less = 90 | \ get(g:snipMate.scope_aliases, 'less', 'css') 91 | endif 92 | 93 | let g:snipMate['get_snippets'] = get(g:snipMate, 'get_snippets', funcref#Function("snipMate#GetSnippets")) 94 | 95 | " List of paths where snippets/ dirs are located 96 | if exists('g:snipMate.snippet_dirs') && type(g:snipMate['snippet_dirs']) != type([]) 97 | echohl WarningMsg 98 | echom "g:snipMate['snippet_dirs'] must be a List" 99 | echohl None 100 | endif 101 | 102 | " _ is default scope added always 103 | " 104 | " &ft honors multiple filetypes and syntax such as in set ft=html.javascript syntax=FOO 105 | let g:snipMate['get_scopes'] = get(g:snipMate, 'get_scopes', funcref#Function('return split(&ft,"\\.")+[&syntax, "_"]')) 106 | 107 | " Modified from Luc Hermitte's function on StackOverflow 108 | " 109 | function! s:grab_visual() abort 110 | let a_save = @a 111 | try 112 | normal! gv"ay 113 | let b:snipmate_visual = @a 114 | finally 115 | let @a = a_save 116 | endtry 117 | endfunction 118 | 119 | " TODO: Allow specifying an arbitrary snippets file 120 | function! s:load_scopes(bang, ...) abort 121 | let gb = a:bang ? g: : b: 122 | let gb.snipMate = get(gb, 'snipMate', {}) 123 | let gb.snipMate.scope_aliases = get(gb.snipMate, 'scope_aliases', {}) 124 | let gb.snipMate.scope_aliases['_'] = join(split(get(gb.snipMate.scope_aliases, '_', ''), ',') + a:000, ',') 125 | endfunction 126 | 127 | command! -bang -bar -nargs=+ SnipMateLoadScope 128 | \ call s:load_scopes(0, ) 129 | 130 | " Edit snippet files 131 | command! SnipMateOpenSnippetFiles call snipMate#OpenSnippetFiles() 132 | 133 | " restore 'cpo' 134 | let &cpo = s:save_cpo 135 | 136 | " vim:noet:sw=4:ts=4:ft=vim 137 | -------------------------------------------------------------------------------- /syntax/snippet.vim: -------------------------------------------------------------------------------- 1 | " Syntax highlighting for .snippet files (used for snipMate.vim) 2 | " Hopefully this should make snippets a bit nicer to write! 3 | syn match placeHolder '\${\d\+\(:.\{-}\)\=}' contains=snipCommand 4 | syn match tabStop '\$\d\+' 5 | syn match snipEscape '\\\\\|\\`' 6 | syn match snipCommand '\%(\\\@ autocmds 38 | 0verbose au snipmate_changes * 39 | redir END 40 | return split(autocmds, "\n") 41 | endfunction 42 | aug snipmate_changes 43 | au CursorMoved,CursorMovedI echo 'event' 44 | aug END 45 | 46 | Expect len(ReadAutocmds()) > 1 47 | call b:snip_state.remove() 48 | Expect len(ReadAutocmds()) == 1 49 | end 50 | 51 | end 52 | 53 | describe '.find_next_stop()' 54 | 55 | it 'increments/decrements the stop_no' 56 | let b:snip_state.stops = { 1 : {}, 2 : {} } 57 | let b:snip_state.stop_no = 1 58 | let b:snip_state.stop_count = 4 59 | 60 | call b:snip_state.find_next_stop(0) 61 | Expect b:snip_state.stop_no == 2 62 | call b:snip_state.find_next_stop(1) 63 | Expect b:snip_state.stop_no == 1 64 | end 65 | 66 | it 'continues iterating if the next/previous stop does not exist' 67 | let b:snip_state.stops = { 3 : {} } 68 | let b:snip_state.stop_count = 6 69 | let b:snip_state.stop_no = 1 70 | call b:snip_state.find_next_stop(0) 71 | Expect b:snip_state.stop_no == 3 72 | let b:snip_state.stop_no = 5 73 | call b:snip_state.find_next_stop(1) 74 | Expect b:snip_state.stop_no == 3 75 | end 76 | 77 | it 'wraps around when going before the first or after the last stop' 78 | let b:snip_state.stops = { 0 : {}, 1 : {}, 2 : {}, 3 : {} } 79 | let b:snip_state.stop_count = 4 80 | let b:snip_state.stop_no = 3 81 | call b:snip_state.find_next_stop(0) 82 | Expect b:snip_state.stop_no == 1 83 | let b:snip_state.stop_no = 1 84 | call b:snip_state.find_next_stop(1) 85 | Expect b:snip_state.stop_no == 3 86 | end 87 | 88 | end 89 | 90 | describe '.remove_nested()' 91 | 92 | it 'removes nested mirrors and only nested mirrors' 93 | let mirror = { 'line' : 0 } 94 | let b:snip_state.stops = { 1 : { 'placeholder' : [[2, mirror]] }, 95 | \ 2 : { 'mirrors' : [mirror, {}] } } 96 | 97 | call b:snip_state.remove_nested(1) 98 | Expect len(b:snip_state.stops[2].mirrors) == 1 99 | Expect b:snip_state.stops[2].mirrors[0] isnot mirror 100 | end 101 | 102 | it 'removes nested stops' 103 | let stop = [2, 'abc'] 104 | let b:snip_state.stops = { 1 : { 'placeholder' : [stop] }, 105 | \ 2 : { 'placeholder' : stop[1:1] } } 106 | 107 | call b:snip_state.remove_nested(1) 108 | Expect len(b:snip_state.stops) == 1 109 | Expect keys(b:snip_state.stops) == ['1'] 110 | end 111 | 112 | end 113 | 114 | describe '.find_update_objects()' 115 | 116 | it 'finds mirrors/stops on the same line and after cur_stop' 117 | let b:snip_state.stops = { 118 | \ 1 : { 'line' : 1, 'col' : 5, 119 | \ 'placeholder' : ['x'] }, 120 | \ 2 : { 'line' : 1, 'col' : 7, 121 | \ 'mirrors' : [{ 'line' : 1, 'col' : 7 }] } 122 | \ } 123 | let stop = b:snip_state.stops[1] 124 | 125 | call b:snip_state.find_update_objects(stop) 126 | for obj in stop.update_objects 127 | Expect obj to_be_in [ b:snip_state.stops[2], 128 | \ b:snip_state.stops[2].mirrors[0] ] 129 | endfor 130 | end 131 | 132 | it 'finds mirrors/stops on the same line and after cur_stop mirrors' 133 | let b:snip_state.stops = { 134 | \ 1 : { 'line' : 1, 'col' : 5, 135 | \ 'mirrors' : [{ 'line' : 2, 'col' : 5 }], 136 | \ 'placeholder' : ['x'] }, 137 | \ 2 : { 'line' : 2, 'col' : 7, 138 | \ 'mirrors' : [{ 'line' : 2, 'col' : 7 }] } 139 | \ } 140 | let stop = b:snip_state.stops[1] 141 | 142 | call b:snip_state.find_update_objects(stop) 143 | for obj in stop.update_objects 144 | Expect obj to_be_in [ b:snip_state.stops[2], 145 | \ b:snip_state.stops[2].mirrors[0] ] 146 | endfor 147 | end 148 | 149 | it 'ignores mirrors/stops on other lines' 150 | let b:snip_state.stops = { 151 | \ 1 : { 'line' : 2, 'col' : 5, 152 | \ 'placeholder' : ['x'] }, 153 | \ 2 : { 'line' : 1, 'col' : 7, 154 | \ 'mirrors' : [{ 'line' : 1, 'col' : 7 }] }, 155 | \ 3 : { 'line' : 3, 'col' : 7, 156 | \ 'mirrors' : [{ 'line' : 3, 'col' : 7 }] } 157 | \ } 158 | let stop = b:snip_state.stops[1] 159 | 160 | call b:snip_state.find_update_objects(stop) 161 | Expect empty(stop.update_objects) to_be_true 162 | end 163 | 164 | it 'ignores mirrors/stops on the same line but before cur_stop/mirrors' 165 | let b:snip_state.stops = { 166 | \ 1 : { 'line' : 1, 'col' : 5, 167 | \ 'mirrors' : [{ 'line' : 2, 'col' : 5 }], 168 | \ 'placeholder' : ['x'] }, 169 | \ 2 : { 'line' : 1, 'col' : 1, 170 | \ 'mirrors' : [{ 'line' : 2, 'col' : 1 }] }, 171 | \ 3 : { 'line' : 2, 'col' : 3, 172 | \ 'mirrors' : [{ 'line' : 1, 'col' : 3 }] }, 173 | \ } 174 | let stop = b:snip_state.stops[1] 175 | 176 | call b:snip_state.find_update_objects(stop) 177 | Expect empty(stop.update_objects) to_be_true 178 | end 179 | 180 | end 181 | 182 | end 183 | -------------------------------------------------------------------------------- /t/parser.vim: -------------------------------------------------------------------------------- 1 | describe 'snippet parser' 2 | 3 | before 4 | " two optional arguments: 5 | " first one: whether or not to create the stop stubs 6 | " second one: whether or not to return the stops 7 | function! Parse(snippet, ...) 8 | let [snip, stops] = snipmate#parse#snippet(a:snippet, (a:0 ? a:1 : 1)) 9 | return (a:0 > 1 && a:2) ? [snip, stops] : snip 10 | endfunction 11 | let b:snipmate_visual = 'testvisual' 12 | end 13 | 14 | it 'parses numeric $id and ${id} vars as [id] lists' 15 | let expect = [[[1234567890]]] 16 | Expect Parse('$1234567890') == expect 17 | Expect Parse('${1234567890}') == expect 18 | end 19 | 20 | it 'disregards $ or ${ followed by a non-id' 21 | Expect Parse('$x1') == [['x1']] 22 | Expect Parse('${x}1') == [['x}1']] 23 | Expect Parse('$VISUA1') == [['VISUA1']] 24 | Expect Parse('${VISUA}1') == [['VISUA}1']] 25 | end 26 | 27 | it 'gathers references to each instance of each stop id' 28 | let [snip, b:stops] = Parse('x$1x${2:x$1x}x$1x${1/a/b}x$VISUALx', 1, 1) 29 | function! InstanceFound(list) 30 | return !empty(filter(copy(b:stops[a:list[0]].instances), 31 | \ 'v:val is a:list')) 32 | endfunction 33 | function! CheckList(list) 34 | for item in a:list 35 | if type(item) == type([]) 36 | Expect InstanceFound(item) to_be_true 37 | call CheckList(item) 38 | endif 39 | unlet item " E732 40 | endfor 41 | endfunction 42 | call CheckList(snip[0]) 43 | end 44 | 45 | it 'parses mirror substitutions ${n/pat/sub} as [n, {...}]' 46 | let expect = [[[1, { 'pat' : 'abc', 'sub' : 'def' }]]] 47 | Expect Parse('${1/abc/def}') == expect 48 | let expect[0][0][1].flags = '' 49 | Expect Parse('${1/abc/def/}') == expect 50 | let expect[0][0][1].flags = 'g' 51 | Expect Parse('${1/abc/def/g}') == expect 52 | end 53 | 54 | it 'reads patterns literally except for "\/"' 55 | Expect Parse('${1/\a\/b/\c\/d\}}') == [[[1, { 'pat' : '\a/b', 'sub' : '\c/d}' }]]] 56 | end 57 | 58 | it 'parses vars with placeholders as [id, placeholder] lists' 59 | Expect Parse('${1:abc}') == [[[1, 'abc']]] 60 | end 61 | 62 | it 'evaluates backtick expressions' 63 | Expect Parse('`fnamemodify("x.y", ":r")`') == [['x']] 64 | end 65 | 66 | it 'parses placeholders for vars and other specials' 67 | let text = 'a `fnamemodify("x.y", ":r")` ${2:(${3/a/b})}' 68 | let expect = ['a x ', [2, '(', [3, { 'pat' : 'a', 'sub' : 'b' }], ')']] 69 | Expect Parse(text) == [expect] 70 | Expect Parse(printf('${1:%s}', text)) == [[[1] + expect]] 71 | end 72 | 73 | it 'converts tabs according to &et, &sts, &sw, &ts' 74 | " &noet -> leave tabs alone 75 | setl noet 76 | Expect Parse("abc\tdef\n\t\tghi") == [["abc\tdef"], ["\t\tghi"]] 77 | 78 | " &et -> &sts or &sw 79 | setl et sts=2 sw=3 80 | Expect Parse("abc\tdef\n\t\tghi") == [["abc def"], [" ghi"]] 81 | 82 | setl et sts=0 sw=3 83 | Expect Parse("abc\tdef\n\t\tghi") == [["abc def"], [" ghi"]] 84 | 85 | setl et sts=-1 sw=3 86 | Expect Parse("abc\tdef\n\t\tghi") == [["abc def"], [" ghi"]] 87 | 88 | " See #227 89 | if exists('*shiftwidth') 90 | setl et sts=0 sw=0 ts=3 91 | Expect Parse("abc\tdef\n\t\tghi") == [["abc def"], [" ghi"]] 92 | endif 93 | end 94 | 95 | it 'parses backslashes as escaping the next character or joining lines' 96 | Expect Parse('x\x') == [['xx']] 97 | Expect Parse('x\\x') == [['x\x']] 98 | Expect Parse("x\\\nx") == [['xx']] 99 | Expect Parse('x\$1') == [['x$1']] 100 | Expect Parse('${1:\}}') == [[[1, '}']]] 101 | Expect Parse('`fnamemodify("\`.x", ":r")`') == [['`']] 102 | Expect Parse('\`x\`') == [['`x`']] 103 | end 104 | 105 | it 'splits text at newlines' 106 | Expect Parse("x\nx") == [['x'], ['x']] 107 | end 108 | 109 | it 'joins evaluated expressions to surrounding text on the same line' 110 | let g:foo = 'bar' 111 | Expect Parse("x`g:foo`x") == [['xbarx']] 112 | Expect Parse("x`g:foo`\nx") == [['xbar'], ['x']] 113 | Expect Parse("x\n`g:foo`x") == [['x'], ['barx']] 114 | end 115 | 116 | it 'expands $VISUAL placeholders with any indents' 117 | Expect Parse("x$VISUALx") == [['xtestvisualx']] 118 | let b:snipmate_visual = " foo\nbar\n baz" 119 | setl noet 120 | Expect Parse("\tx\n\t$VISUAL\nx") == [["\tx"], ["\t foo"], ["\tbar"], 121 | \ ["\t baz"], ["x"]] 122 | end 123 | 124 | it 'removes newlines from the end of VISUALs if before an end of line' 125 | let b:snipmate_visual = "1\n2\n" 126 | Expect Parse("x\n$VISUAL\nx") == [['x'], ['1'], ['2'], ['x']] 127 | end 128 | 129 | it 'splits the before and after a $VISUAL if it is multiline' 130 | let b:snipmate_visual = "1\n2\n3" 131 | Expect Parse("foo $VISUAL bar") == [['foo 1'], ['2'], ['3 bar']] 132 | end 133 | 134 | it 'determines which var with an id is the stop' 135 | let [snip, stops] = Parse("$1$1$1", 0, 1) 136 | Expect snip == [[[1, "", stops[1]], [1, {}], [1, {}]]] 137 | 138 | let [snip, stops] = Parse("$1${1}$1", 0, 1) 139 | Expect snip == [[[1, "", stops[1]], [1, {}], [1, {}]]] 140 | 141 | let [snip, stops] = Parse("$1${1:}$1", 0, 1) 142 | Expect snip == [[[1, {}], [1, "", stops[1]], [1, {}]]] 143 | 144 | end 145 | 146 | it 'picks the first of many possible stops' 147 | let [snip, stops] = Parse("$1${1:foo}${1:bar}", 0, 1) 148 | Expect snip == [[[1, {}], [1, "foo", stops[1]], [1, {}]]] 149 | end 150 | 151 | it 'represents empty lines as an empty string' 152 | Expect Parse("foo\n\nbar") == [['foo'], [''], ['bar']] 153 | end 154 | 155 | it 'parses a selection as a special var named "select" with each item' 156 | Expect Parse("${1|foo|bar|baz|select}") == 157 | \ [[[1, ['select', 'foo', 'bar', 'baz', 'select']]]] 158 | end 159 | 160 | it 'stores selection items in the var dictionary' 161 | let [snip, stops] = Parse("${1|foo|bar|baz|select}", 0, 1) 162 | Expect stops[1].items == ['foo', 'bar', 'baz', 'select'] 163 | end 164 | 165 | it 'sets a selections placeholder to the first item' 166 | let [snip, stops] = Parse("${1|foo|bar|baz|select}", 0, 1) 167 | Expect stops[1].placeholder == 'foo' 168 | end 169 | 170 | end 171 | -------------------------------------------------------------------------------- /t/tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | tmp="$(mktemp || tmpfile)" 4 | vim -Es $tmp <<- EOF 5 | source ~/.vimrc 6 | %delete _ 7 | call append(0, split(&rtp, ',')) 8 | delete _ 9 | wq 10 | EOF 11 | 12 | rtp="$(grep -iE 'vspec|snipmate|tlib|mw-utils' < $tmp | grep -v after)" 13 | vspec="$(grep -iE 'vspec' < $tmp | grep -v after)" 14 | test_files="${*:-parser jumping}" 15 | 16 | for test in $test_files; do 17 | $vspec/bin/vspec $rtp ${test%%.vim}.vim 18 | done 19 | 20 | rm $tmp 21 | --------------------------------------------------------------------------------