]>
Commit | Line | Data |
---|---|---|
1 | " File: snipMate.vim | |
2 | " Author: Michael Sanders | |
3 | " Version: 0.84 | |
4 | " Description: snipMate.vim implements some of TextMate's snippets features in | |
5 | " Vim. A snippet is a piece of often-typed text that you can | |
6 | " insert into your document using a trigger word followed by a "<tab>". | |
7 | " | |
8 | " For more help see snipMate.txt; you can do this by using: | |
9 | " :helptags ~/.vim/doc | |
10 | " :h snipMate.txt | |
11 | ||
12 | if exists('loaded_snips') || &cp || version < 700 | |
13 | finish | |
14 | endif | |
15 | let loaded_snips = 1 | |
16 | if !exists('snips_author') | let snips_author = 'Me' | endif | |
17 | ||
18 | au BufRead,BufNewFile *.snippets\= set ft=snippet | |
19 | au FileType snippet setl noet fdm=indent | |
20 | ||
21 | let s:snippets = {} | let s:multi_snips = {} | |
22 | ||
23 | if !exists('snippets_dir') | |
24 | let snippets_dir = substitute(globpath(&rtp, 'snippets/'), "\n", ',', 'g') | |
25 | endif | |
26 | ||
27 | fun! MakeSnip(scope, trigger, content, ...) | |
28 | let multisnip = a:0 && a:1 != '' | |
29 | let var = multisnip ? 's:multi_snips' : 's:snippets' | |
30 | if !has_key({var}, a:scope) | let {var}[a:scope] = {} | endif | |
31 | if !has_key({var}[a:scope], a:trigger) | |
32 | let {var}[a:scope][a:trigger] = multisnip ? [[a:1, a:content]] : a:content | |
33 | elseif multisnip | let {var}[a:scope][a:trigger] += [[a:1, a:content]] | |
34 | else | |
35 | echom 'Warning in snipMate.vim: Snippet '.a:trigger.' is already defined.' | |
36 | \ .' See :h multi_snip for help on snippets with multiple matches.' | |
37 | endif | |
38 | endf | |
39 | ||
40 | fun! ExtractSnips(dir, ft) | |
41 | for path in split(globpath(a:dir, '*'), "\n") | |
42 | if isdirectory(path) | |
43 | let pathname = fnamemodify(path, ':t') | |
44 | for snipFile in split(globpath(path, '*.snippet'), "\n") | |
45 | call s:ProcessFile(snipFile, a:ft, pathname) | |
46 | endfor | |
47 | elseif fnamemodify(path, ':e') == 'snippet' | |
48 | call s:ProcessFile(path, a:ft) | |
49 | endif | |
50 | endfor | |
51 | endf | |
52 | ||
53 | " Processes a single-snippet file; optionally add the name of the parent | |
54 | " directory for a snippet with multiple matches. | |
55 | fun s:ProcessFile(file, ft, ...) | |
56 | let keyword = fnamemodify(a:file, ':t:r') | |
57 | if keyword == '' | return | endif | |
58 | try | |
59 | let text = join(readfile(a:file), "\n") | |
60 | catch /E484/ | |
61 | echom "Error in snipMate.vim: couldn't read file: ".a:file | |
62 | endtry | |
63 | return a:0 ? MakeSnip(a:ft, a:1, text, keyword) | |
64 | \ : MakeSnip(a:ft, keyword, text) | |
65 | endf | |
66 | ||
67 | fun! ExtractSnipsFile(file, ft) | |
68 | if !filereadable(a:file) | return | endif | |
69 | let text = readfile(a:file) | |
70 | let inSnip = 0 | |
71 | for line in text + ["\n"] | |
72 | if inSnip && (line[0] == "\t" || line == '') | |
73 | let content .= strpart(line, 1)."\n" | |
74 | continue | |
75 | elseif inSnip | |
76 | call MakeSnip(a:ft, trigger, content[:-2], name) | |
77 | let inSnip = 0 | |
78 | endif | |
79 | ||
80 | if line[:6] == 'snippet' | |
81 | let inSnip = 1 | |
82 | let trigger = strpart(line, 8) | |
83 | let name = '' | |
84 | let space = stridx(trigger, ' ') + 1 | |
85 | if space " Process multi snip | |
86 | let name = strpart(trigger, space) | |
87 | let trigger = strpart(trigger, 0, space - 1) | |
88 | endif | |
89 | let content = '' | |
90 | endif | |
91 | endfor | |
92 | endf | |
93 | ||
94 | " Reset snippets for filetype. | |
95 | fun! ResetSnippets(ft) | |
96 | let ft = a:ft == '' ? '_' : a:ft | |
97 | for dict in [s:snippets, s:multi_snips, g:did_ft] | |
98 | if has_key(dict, ft) | |
99 | unlet dict[ft] | |
100 | endif | |
101 | endfor | |
102 | endf | |
103 | ||
104 | " Reset snippets for all filetypes. | |
105 | fun! ResetAllSnippets() | |
106 | let s:snippets = {} | let s:multi_snips = {} | let g:did_ft = {} | |
107 | endf | |
108 | ||
109 | " Reload snippets for filetype. | |
110 | fun! ReloadSnippets(ft) | |
111 | let ft = a:ft == '' ? '_' : a:ft | |
112 | call ResetSnippets(ft) | |
113 | call GetSnippets(g:snippets_dir, ft) | |
114 | endf | |
115 | ||
116 | " Reload snippets for all filetypes. | |
117 | fun! ReloadAllSnippets() | |
118 | for ft in keys(g:did_ft) | |
119 | call ReloadSnippets(ft) | |
120 | endfor | |
121 | endf | |
122 | ||
123 | let g:did_ft = {} | |
124 | fun! GetSnippets(dir, filetypes) | |
125 | for ft in split(a:filetypes, '\.') | |
126 | if has_key(g:did_ft, ft) | continue | endif | |
127 | call s:DefineSnips(a:dir, ft, ft) | |
128 | if ft == 'objc' || ft == 'cpp' || ft == 'cs' | |
129 | call s:DefineSnips(a:dir, 'c', ft) | |
130 | elseif ft == 'xhtml' | |
131 | call s:DefineSnips(a:dir, 'html', 'xhtml') | |
132 | endif | |
133 | let g:did_ft[ft] = 1 | |
134 | endfor | |
135 | endf | |
136 | ||
137 | " Define "aliasft" snippets for the filetype "realft". | |
138 | fun s:DefineSnips(dir, aliasft, realft) | |
139 | for path in split(globpath(a:dir, a:aliasft.'/')."\n". | |
140 | \ globpath(a:dir, a:aliasft.'-*/'), "\n") | |
141 | call ExtractSnips(path, a:realft) | |
142 | endfor | |
143 | for path in split(globpath(a:dir, a:aliasft.'.snippets')."\n". | |
144 | \ globpath(a:dir, a:aliasft.'-*.snippets'), "\n") | |
145 | call ExtractSnipsFile(path, a:realft) | |
146 | endfor | |
147 | endf | |
148 | ||
149 | fun! TriggerSnippet() | |
150 | if exists('g:SuperTabMappingForward') | |
151 | if g:SuperTabMappingForward == "<tab>" | |
152 | let SuperTabKey = "\<c-n>" | |
153 | elseif g:SuperTabMappingBackward == "<tab>" | |
154 | let SuperTabKey = "\<c-p>" | |
155 | endif | |
156 | endif | |
157 | ||
158 | if pumvisible() " Update snippet if completion is used, or deal with supertab | |
159 | if exists('SuperTabKey') | |
160 | call feedkeys(SuperTabKey) | return '' | |
161 | endif | |
162 | call feedkeys("\<esc>a", 'n') " Close completion menu | |
163 | call feedkeys("\<tab>") | return '' | |
164 | endif | |
165 | ||
166 | if exists('g:snipPos') | return snipMate#jumpTabStop(0) | endif | |
167 | ||
168 | let word = matchstr(getline('.'), '\S\+\%'.col('.').'c') | |
169 | for scope in [bufnr('%')] + split(&ft, '\.') + ['_'] | |
170 | let [trigger, snippet] = s:GetSnippet(word, scope) | |
171 | " If word is a trigger for a snippet, delete the trigger & expand | |
172 | " the snippet. | |
173 | if snippet != '' | |
174 | let col = col('.') - len(trigger) | |
175 | sil exe 's/\V'.escape(trigger, '/\.').'\%#//' | |
176 | return snipMate#expandSnip(snippet, col) | |
177 | endif | |
178 | endfor | |
179 | ||
180 | if exists('SuperTabKey') | |
181 | call feedkeys(SuperTabKey) | |
182 | return '' | |
183 | endif | |
184 | return "\<tab>" | |
185 | endf | |
186 | ||
187 | fun! BackwardsSnippet() | |
188 | if exists('g:snipPos') | return snipMate#jumpTabStop(1) | endif | |
189 | ||
190 | if exists('g:SuperTabMappingForward') | |
191 | if g:SuperTabMappingBackward == "<s-tab>" | |
192 | let SuperTabKey = "\<c-p>" | |
193 | elseif g:SuperTabMappingForward == "<s-tab>" | |
194 | let SuperTabKey = "\<c-n>" | |
195 | endif | |
196 | endif | |
197 | if exists('SuperTabKey') | |
198 | call feedkeys(SuperTabKey) | |
199 | return '' | |
200 | endif | |
201 | return "\<s-tab>" | |
202 | endf | |
203 | ||
204 | " Check if word under cursor is snippet trigger; if it isn't, try checking if | |
205 | " the text after non-word characters is (e.g. check for "foo" in "bar.foo") | |
206 | fun s:GetSnippet(word, scope) | |
207 | let word = a:word | let snippet = '' | |
208 | while snippet == '' | |
209 | if exists('s:snippets["'.a:scope.'"]["'.escape(word, '\"').'"]') | |
210 | let snippet = s:snippets[a:scope][word] | |
211 | elseif exists('s:multi_snips["'.a:scope.'"]["'.escape(word, '\"').'"]') | |
212 | let snippet = s:ChooseSnippet(a:scope, word) | |
213 | if snippet == '' | break | endif | |
214 | else | |
215 | if match(word, '\W') == -1 | break | endif | |
216 | let word = substitute(word, '.\{-}\W', '', '') | |
217 | endif | |
218 | endw | |
219 | if word == '' && a:word != '.' && stridx(a:word, '.') != -1 | |
220 | let [word, snippet] = s:GetSnippet('.', a:scope) | |
221 | endif | |
222 | return [word, snippet] | |
223 | endf | |
224 | ||
225 | fun s:ChooseSnippet(scope, trigger) | |
226 | let snippet = [] | |
227 | let i = 1 | |
228 | for snip in s:multi_snips[a:scope][a:trigger] | |
229 | let snippet += [i.'. '.snip[0]] | |
230 | let i += 1 | |
231 | endfor | |
232 | if i == 2 | return s:multi_snips[a:scope][a:trigger][0][1] | endif | |
233 | let num = inputlist(snippet) - 1 | |
234 | return num == -1 ? '' : s:multi_snips[a:scope][a:trigger][num][1] | |
235 | endf | |
236 | ||
237 | fun! ShowAvailableSnips() | |
238 | let line = getline('.') | |
239 | let col = col('.') | |
240 | let word = matchstr(getline('.'), '\S\+\%'.col.'c') | |
241 | let words = [word] | |
242 | if stridx(word, '.') | |
243 | let words += split(word, '\.', 1) | |
244 | endif | |
245 | let matchlen = 0 | |
246 | let matches = [] | |
247 | for scope in [bufnr('%')] + split(&ft, '\.') + ['_'] | |
248 | let triggers = has_key(s:snippets, scope) ? keys(s:snippets[scope]) : [] | |
249 | if has_key(s:multi_snips, scope) | |
250 | let triggers += keys(s:multi_snips[scope]) | |
251 | endif | |
252 | for trigger in triggers | |
253 | for word in words | |
254 | if word == '' | |
255 | let matches += [trigger] " Show all matches if word is empty | |
256 | elseif trigger =~ '^'.word | |
257 | let matches += [trigger] | |
258 | let len = len(word) | |
259 | if len > matchlen | let matchlen = len | endif | |
260 | endif | |
261 | endfor | |
262 | endfor | |
263 | endfor | |
264 | ||
265 | " This is to avoid a bug with Vim when using complete(col - matchlen, matches) | |
266 | " (Issue#46 on the Google Code snipMate issue tracker). | |
267 | call setline(line('.'), substitute(line, repeat('.', matchlen).'\%'.col.'c', '', '')) | |
268 | call complete(col, matches) | |
269 | return '' | |
270 | endf | |
271 | " vim:noet:sw=4:ts=4:ft=vim |