]> git.madduck.net Git - etc/vim.git/blob - .vim/autoload/snipMate.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

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