]> git.r.bdr.sh - rbdr/dotfiles/blame - vim/autoload/snipMate.vim
Add vim-json bundle
[rbdr/dotfiles] / vim / autoload / snipMate.vim
CommitLineData
0d23b6e5
BB
1fun! Filename(...)
2 let filename = expand('%:t:r')
3 if filename == '' | return a:0 == 2 ? a:2 : '' | endif
4 return !a:0 || a:1 == '' ? filename : substitute(a:1, '$1', filename, 'g')
5endf
6
7fun s:RemoveSnippet()
8 unl! g:snipPos s:curPos s:snipLen s:endCol s:endLine s:prevLen
9 \ s:lastBuf s:oldWord
10 if exists('s:update')
11 unl s:startCol s:origWordLen s:update
12 if exists('s:oldVars') | unl s:oldVars s:oldEndCol | endif
13 endif
14 aug! snipMateAutocmds
15endf
16
17fun snipMate#expandSnip(snip, col)
18 let lnum = line('.') | let col = a:col
19
20 let snippet = s:ProcessSnippet(a:snip)
21 " Avoid error if eval evaluates to nothing
22 if snippet == '' | return '' | endif
23
24 " Expand snippet onto current position with the tab stops removed
25 let snipLines = split(substitute(snippet, '$\d\+\|${\d\+.\{-}}', '', 'g'), "\n", 1)
26
27 let line = getline(lnum)
28 let afterCursor = strpart(line, col - 1)
29 " Keep text after the cursor
30 if afterCursor != "\t" && afterCursor != ' '
31 let line = strpart(line, 0, col - 1)
32 let snipLines[-1] .= afterCursor
33 else
34 let afterCursor = ''
35 " For some reason the cursor needs to move one right after this
36 if line != '' && col == 1 && &ve != 'all' && &ve != 'onemore'
37 let col += 1
38 endif
39 endif
40
41 call setline(lnum, line.snipLines[0])
42
43 " Autoindent snippet according to previous indentation
44 let indent = matchend(line, '^.\{-}\ze\(\S\|$\)') + 1
45 call append(lnum, map(snipLines[1:], "'".strpart(line, 0, indent - 1)."'.v:val"))
46
47 " Open any folds snippet expands into
48 if &fen | sil! exe lnum.','.(lnum + len(snipLines) - 1).'foldopen' | endif
49
50 let [g:snipPos, s:snipLen] = s:BuildTabStops(snippet, lnum, col - indent, indent)
51
52 if s:snipLen
53 aug snipMateAutocmds
54 au CursorMovedI * call s:UpdateChangedSnip(0)
55 au InsertEnter * call s:UpdateChangedSnip(1)
56 aug END
57 let s:lastBuf = bufnr(0) " Only expand snippet while in current buffer
58 let s:curPos = 0
59 let s:endCol = g:snipPos[s:curPos][1]
60 let s:endLine = g:snipPos[s:curPos][0]
61
62 call cursor(g:snipPos[s:curPos][0], g:snipPos[s:curPos][1])
63 let s:prevLen = [line('$'), col('$')]
64 if g:snipPos[s:curPos][2] != -1 | return s:SelectWord() | endif
65 else
66 unl g:snipPos s:snipLen
67 " Place cursor at end of snippet if no tab stop is given
68 let newlines = len(snipLines) - 1
69 call cursor(lnum + newlines, indent + len(snipLines[-1]) - len(afterCursor)
70 \ + (newlines ? 0: col - 1))
71 endif
72 return ''
73endf
74
75" Prepare snippet to be processed by s:BuildTabStops
76fun s:ProcessSnippet(snip)
77 let snippet = a:snip
78 " Evaluate eval (`...`) expressions.
79 " Backquotes prefixed with a backslash "\" are ignored.
80 " Using a loop here instead of a regex fixes a bug with nested "\=".
81 if stridx(snippet, '`') != -1
82 while match(snippet, '\(^\|[^\\]\)`.\{-}[^\\]`') != -1
83 let snippet = substitute(snippet, '\(^\|[^\\]\)\zs`.\{-}[^\\]`\ze',
84 \ substitute(eval(matchstr(snippet, '\(^\|[^\\]\)`\zs.\{-}[^\\]\ze`')),
85 \ "\n\\%$", '', ''), '')
86 endw
87 let snippet = substitute(snippet, "\r", "\n", 'g')
88 let snippet = substitute(snippet, '\\`', '`', 'g')
89 endif
90
91 " Place all text after a colon in a tab stop after the tab stop
92 " (e.g. "${#:foo}" becomes "${:foo}foo").
93 " This helps tell the position of the tab stops later.
94 let snippet = substitute(snippet, '${\d\+:\(.\{-}\)}', '&\1', 'g')
95
96 " Update the a:snip so that all the $# become the text after
97 " the colon in their associated ${#}.
98 " (e.g. "${1:foo}" turns all "$1"'s into "foo")
99 let i = 1
100 while stridx(snippet, '${'.i) != -1
101 let s = matchstr(snippet, '${'.i.':\zs.\{-}\ze}')
102 if s != ''
103 let snippet = substitute(snippet, '$'.i, s.'&', 'g')
104 endif
105 let i += 1
106 endw
107
108 if &et " Expand tabs to spaces if 'expandtab' is set.
109 return substitute(snippet, '\t', repeat(' ', &sts ? &sts : &sw), 'g')
110 endif
111 return snippet
112endf
113
114" Counts occurences of haystack in needle
115fun s:Count(haystack, needle)
116 let counter = 0
117 let index = stridx(a:haystack, a:needle)
118 while index != -1
119 let index = stridx(a:haystack, a:needle, index+1)
120 let counter += 1
121 endw
122 return counter
123endf
124
125" Builds a list of a list of each tab stop in the snippet containing:
126" 1.) The tab stop's line number.
127" 2.) The tab stop's column number
128" (by getting the length of the string between the last "\n" and the
129" tab stop).
130" 3.) The length of the text after the colon for the current tab stop
131" (e.g. "${1:foo}" would return 3). If there is no text, -1 is returned.
132" 4.) If the "${#:}" construct is given, another list containing all
133" the matches of "$#", to be replaced with the placeholder. This list is
134" composed the same way as the parent; the first item is the line number,
135" and the second is the column.
136fun s:BuildTabStops(snip, lnum, col, indent)
137 let snipPos = []
138 let i = 1
139 let withoutVars = substitute(a:snip, '$\d\+', '', 'g')
140 while stridx(a:snip, '${'.i) != -1
141 let beforeTabStop = matchstr(withoutVars, '^.*\ze${'.i.'\D')
142 let withoutOthers = substitute(withoutVars, '${\('.i.'\D\)\@!\d\+.\{-}}', '', 'g')
143
144 let j = i - 1
145 call add(snipPos, [0, 0, -1])
146 let snipPos[j][0] = a:lnum + s:Count(beforeTabStop, "\n")
147 let snipPos[j][1] = a:indent + len(matchstr(withoutOthers, '.*\(\n\|^\)\zs.*\ze${'.i.'\D'))
148 if snipPos[j][0] == a:lnum | let snipPos[j][1] += a:col | endif
149
150 " Get all $# matches in another list, if ${#:name} is given
151 if stridx(withoutVars, '${'.i.':') != -1
152 let snipPos[j][2] = len(matchstr(withoutVars, '${'.i.':\zs.\{-}\ze}'))
153 let dots = repeat('.', snipPos[j][2])
154 call add(snipPos[j], [])
155 let withoutOthers = substitute(a:snip, '${\d\+.\{-}}\|$'.i.'\@!\d\+', '', 'g')
156 while match(withoutOthers, '$'.i.'\(\D\|$\)') != -1
157 let beforeMark = matchstr(withoutOthers, '^.\{-}\ze'.dots.'$'.i.'\(\D\|$\)')
158 call add(snipPos[j][3], [0, 0])
159 let snipPos[j][3][-1][0] = a:lnum + s:Count(beforeMark, "\n")
160 let snipPos[j][3][-1][1] = a:indent + (snipPos[j][3][-1][0] > a:lnum
161 \ ? len(matchstr(beforeMark, '.*\n\zs.*'))
162 \ : a:col + len(beforeMark))
163 let withoutOthers = substitute(withoutOthers, '$'.i.'\ze\(\D\|$\)', '', '')
164 endw
165 endif
166 let i += 1
167 endw
168 return [snipPos, i - 1]
169endf
170
171fun snipMate#jumpTabStop(backwards)
172 let leftPlaceholder = exists('s:origWordLen')
173 \ && s:origWordLen != g:snipPos[s:curPos][2]
174 if leftPlaceholder && exists('s:oldEndCol')
175 let startPlaceholder = s:oldEndCol + 1
176 endif
177
178 if exists('s:update')
179 call s:UpdatePlaceholderTabStops()
180 else
181 call s:UpdateTabStops()
182 endif
183
184 " Don't reselect placeholder if it has been modified
185 if leftPlaceholder && g:snipPos[s:curPos][2] != -1
186 if exists('startPlaceholder')
187 let g:snipPos[s:curPos][1] = startPlaceholder
188 else
189 let g:snipPos[s:curPos][1] = col('.')
190 let g:snipPos[s:curPos][2] = 0
191 endif
192 endif
193
194 let s:curPos += a:backwards ? -1 : 1
195 " Loop over the snippet when going backwards from the beginning
196 if s:curPos < 0 | let s:curPos = s:snipLen - 1 | endif
197
198 if s:curPos == s:snipLen
199 let sMode = s:endCol == g:snipPos[s:curPos-1][1]+g:snipPos[s:curPos-1][2]
200 call s:RemoveSnippet()
201 return sMode ? "\<tab>" : TriggerSnippet()
202 endif
203
204 call cursor(g:snipPos[s:curPos][0], g:snipPos[s:curPos][1])
205
206 let s:endLine = g:snipPos[s:curPos][0]
207 let s:endCol = g:snipPos[s:curPos][1]
208 let s:prevLen = [line('$'), col('$')]
209
210 return g:snipPos[s:curPos][2] == -1 ? '' : s:SelectWord()
211endf
212
213fun s:UpdatePlaceholderTabStops()
214 let changeLen = s:origWordLen - g:snipPos[s:curPos][2]
215 unl s:startCol s:origWordLen s:update
216 if !exists('s:oldVars') | return | endif
217 " Update tab stops in snippet if text has been added via "$#"
218 " (e.g., in "${1:foo}bar$1${2}").
219 if changeLen != 0
220 let curLine = line('.')
221
222 for pos in g:snipPos
223 if pos == g:snipPos[s:curPos] | continue | endif
224 let changed = pos[0] == curLine && pos[1] > s:oldEndCol
225 let changedVars = 0
226 let endPlaceholder = pos[2] - 1 + pos[1]
227 " Subtract changeLen from each tab stop that was after any of
228 " the current tab stop's placeholders.
229 for [lnum, col] in s:oldVars
230 if lnum > pos[0] | break | endif
231 if pos[0] == lnum
232 if pos[1] > col || (pos[2] == -1 && pos[1] == col)
233 let changed += 1
234 elseif col < endPlaceholder
235 let changedVars += 1
236 endif
237 endif
238 endfor
239 let pos[1] -= changeLen * changed
240 let pos[2] -= changeLen * changedVars " Parse variables within placeholders
241 " e.g., "${1:foo} ${2:$1bar}"
242
243 if pos[2] == -1 | continue | endif
244 " Do the same to any placeholders in the other tab stops.
245 for nPos in pos[3]
246 let changed = nPos[0] == curLine && nPos[1] > s:oldEndCol
247 for [lnum, col] in s:oldVars
248 if lnum > nPos[0] | break | endif
249 if nPos[0] == lnum && nPos[1] > col
250 let changed += 1
251 endif
252 endfor
253 let nPos[1] -= changeLen * changed
254 endfor
255 endfor
256 endif
257 unl s:endCol s:oldVars s:oldEndCol
258endf
259
260fun s:UpdateTabStops()
261 let changeLine = s:endLine - g:snipPos[s:curPos][0]
262 let changeCol = s:endCol - g:snipPos[s:curPos][1]
263 if exists('s:origWordLen')
264 let changeCol -= s:origWordLen
265 unl s:origWordLen
266 endif
267 let lnum = g:snipPos[s:curPos][0]
268 let col = g:snipPos[s:curPos][1]
269 " Update the line number of all proceeding tab stops if <cr> has
270 " been inserted.
271 if changeLine != 0
272 let changeLine -= 1
273 for pos in g:snipPos
274 if pos[0] >= lnum
275 if pos[0] == lnum | let pos[1] += changeCol | endif
276 let pos[0] += changeLine
277 endif
278 if pos[2] == -1 | continue | endif
279 for nPos in pos[3]
280 if nPos[0] >= lnum
281 if nPos[0] == lnum | let nPos[1] += changeCol | endif
282 let nPos[0] += changeLine
283 endif
284 endfor
285 endfor
286 elseif changeCol != 0
287 " Update the column of all proceeding tab stops if text has
288 " been inserted/deleted in the current line.
289 for pos in g:snipPos
290 if pos[1] >= col && pos[0] == lnum
291 let pos[1] += changeCol
292 endif
293 if pos[2] == -1 | continue | endif
294 for nPos in pos[3]
295 if nPos[0] > lnum | break | endif
296 if nPos[0] == lnum && nPos[1] >= col
297 let nPos[1] += changeCol
298 endif
299 endfor
300 endfor
301 endif
302endf
303
304fun s:SelectWord()
305 let s:origWordLen = g:snipPos[s:curPos][2]
306 let s:oldWord = strpart(getline('.'), g:snipPos[s:curPos][1] - 1,
307 \ s:origWordLen)
308 let s:prevLen[1] -= s:origWordLen
309 if !empty(g:snipPos[s:curPos][3])
310 let s:update = 1
311 let s:endCol = -1
312 let s:startCol = g:snipPos[s:curPos][1] - 1
313 endif
314 if !s:origWordLen | return '' | endif
315 let l = col('.') != 1 ? 'l' : ''
316 if &sel == 'exclusive'
317 return "\<esc>".l.'v'.s:origWordLen."l\<c-g>"
318 endif
319 return s:origWordLen == 1 ? "\<esc>".l.'gh'
320 \ : "\<esc>".l.'v'.(s:origWordLen - 1)."l\<c-g>"
321endf
322
323" This updates the snippet as you type when text needs to be inserted
324" into multiple places (e.g. in "${1:default text}foo$1bar$1",
325" "default text" would be highlighted, and if the user types something,
326" UpdateChangedSnip() would be called so that the text after "foo" & "bar"
327" are updated accordingly)
328"
329" It also automatically quits the snippet if the cursor is moved out of it
330" while in insert mode.
331fun s:UpdateChangedSnip(entering)
332 if exists('g:snipPos') && bufnr(0) != s:lastBuf
333 call s:RemoveSnippet()
334 elseif exists('s:update') " If modifying a placeholder
335 if !exists('s:oldVars') && s:curPos + 1 < s:snipLen
336 " Save the old snippet & word length before it's updated
337 " s:startCol must be saved too, in case text is added
338 " before the snippet (e.g. in "foo$1${2}bar${1:foo}").
339 let s:oldEndCol = s:startCol
340 let s:oldVars = deepcopy(g:snipPos[s:curPos][3])
341 endif
342 let col = col('.') - 1
343
344 if s:endCol != -1
345 let changeLen = col('$') - s:prevLen[1]
346 let s:endCol += changeLen
347 else " When being updated the first time, after leaving select mode
348 if a:entering | return | endif
349 let s:endCol = col - 1
350 endif
351
352 " If the cursor moves outside the snippet, quit it
353 if line('.') != g:snipPos[s:curPos][0] || col < s:startCol ||
354 \ col - 1 > s:endCol
355 unl! s:startCol s:origWordLen s:oldVars s:update
356 return s:RemoveSnippet()
357 endif
358
359 call s:UpdateVars()
360 let s:prevLen[1] = col('$')
361 elseif exists('g:snipPos')
362 if !a:entering && g:snipPos[s:curPos][2] != -1
363 let g:snipPos[s:curPos][2] = -2
364 endif
365
366 let col = col('.')
367 let lnum = line('.')
368 let changeLine = line('$') - s:prevLen[0]
369
370 if lnum == s:endLine
371 let s:endCol += col('$') - s:prevLen[1]
372 let s:prevLen = [line('$'), col('$')]
373 endif
374 if changeLine != 0
375 let s:endLine += changeLine
376 let s:endCol = col
377 endif
378
379 " Delete snippet if cursor moves out of it in insert mode
380 if (lnum == s:endLine && (col > s:endCol || col < g:snipPos[s:curPos][1]))
381 \ || lnum > s:endLine || lnum < g:snipPos[s:curPos][0]
382 call s:RemoveSnippet()
383 endif
384 endif
385endf
386
387" This updates the variables in a snippet when a placeholder has been edited.
388" (e.g., each "$1" in "${1:foo} $1bar $1bar")
389fun s:UpdateVars()
390 let newWordLen = s:endCol - s:startCol + 1
391 let newWord = strpart(getline('.'), s:startCol, newWordLen)
392 if newWord == s:oldWord || empty(g:snipPos[s:curPos][3])
393 return
394 endif
395
396 let changeLen = g:snipPos[s:curPos][2] - newWordLen
397 let curLine = line('.')
398 let startCol = col('.')
399 let oldStartSnip = s:startCol
400 let updateTabStops = changeLen != 0
401 let i = 0
402
403 for [lnum, col] in g:snipPos[s:curPos][3]
404 if updateTabStops
405 let start = s:startCol
406 if lnum == curLine && col <= start
407 let s:startCol -= changeLen
408 let s:endCol -= changeLen
409 endif
410 for nPos in g:snipPos[s:curPos][3][(i):]
411 " This list is in ascending order, so quit if we've gone too far.
412 if nPos[0] > lnum | break | endif
413 if nPos[0] == lnum && nPos[1] > col
414 let nPos[1] -= changeLen
415 endif
416 endfor
417 if lnum == curLine && col > start
418 let col -= changeLen
419 let g:snipPos[s:curPos][3][i][1] = col
420 endif
421 let i += 1
422 endif
423
424 " "Very nomagic" is used here to allow special characters.
425 call setline(lnum, substitute(getline(lnum), '\%'.col.'c\V'.
426 \ escape(s:oldWord, '\'), escape(newWord, '\&'), ''))
427 endfor
428 if oldStartSnip != s:startCol
429 call cursor(0, startCol + s:startCol - oldStartSnip)
430 endif
431
432 let s:oldWord = newWord
433 let g:snipPos[s:curPos][2] = newWordLen
434endf
435" vim:noet:sw=4:ts=4:ft=vim