Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

added support for <shift-tab> to go back a tab stop, and cleaned up t…

…he code a bit
  • Loading branch information...
commit 9e11b093d41d5af30e3a32ee09d0ee9a9839b0d2 1 parent c9d9d3a
Michael Sanders msanders authored
2  after/plugin/snipMate.vim
@@ -7,6 +7,8 @@ let s:did_snips_mappings = 1
7 7
8 8 ino <silent> <tab> <c-r>=TriggerSnippet()<cr>
9 9 snor <silent> <tab> <esc>i<right><c-r>=TriggerSnippet()<cr>
  10 +ino <silent> <s-tab> <c-r>=BackwardsSnippet()<cr>
  11 +snor <silent> <s-tab> <esc>i<right><c-r>=BackwardsSnippet()<cr>
10 12 ino <silent> <c-r><tab> <c-r>=ShowAvailableSnips()<cr>
11 13
12 14 " The default mappings for these are annoying & sometimes break snipMate.
158 autoload/snipMate.vim
@@ -5,7 +5,12 @@ fun! Filename(...)
5 5 endf
6 6
7 7 fun s:RemoveSnippet()
8   - unl g:snipPos s:curPos s:snipLen s:endSnip s:endSnipLine s:prevLen s:lastBuf
  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
9 14 aug! snipMateAutocmds
10 15 endf
11 16
@@ -13,12 +18,15 @@ fun snipMate#expandSnip(snip, col)
13 18 let lnum = line('.') | let col = a:col
14 19
15 20 let snippet = s:ProcessSnippet(a:snip)
  21 + " Avoid error if eval evaluates to nothing
16 22 if snippet == '' | return '' | endif
17 23
  24 + " Expand snippet onto current position with the tab stops removed
18 25 let snipLines = split(substitute(snippet, '$\d\+\|${\d\+.\{-}}', '', 'g'), "\n", 1)
19 26
20 27 let line = getline(lnum)
21 28 let afterCursor = strpart(line, col - 1)
  29 + " Keep text after the cursor
22 30 if afterCursor != "\t" && afterCursor != ' '
23 31 let line = strpart(line, 0, col - 1)
24 32 let snipLines[-1] .= afterCursor
@@ -35,6 +43,8 @@ fun snipMate#expandSnip(snip, col)
35 43 " Autoindent snippet according to previous indentation
36 44 let indent = matchend(line, '^.\{-}\ze\(\S\|$\)') + 1
37 45 call append(lnum, map(snipLines[1:], "'".strpart(line, 0, indent - 1)."'.v:val"))
  46 +
  47 + " Open any folds snippet expands into
38 48 if &fen | sil! exe lnum.','.(lnum + len(snipLines) - 1).'foldopen' | endif
39 49
40 50 let [g:snipPos, s:snipLen] = s:BuildTabStops(snippet, lnum, col - indent, indent)
@@ -44,11 +54,10 @@ fun snipMate#expandSnip(snip, col)
44 54 au CursorMovedI * call s:UpdateChangedSnip(0)
45 55 au InsertEnter * call s:UpdateChangedSnip(1)
46 56 aug END
47   - let s:lastBuf = bufnr(0)
48   -
49   - let s:curPos = 0
50   - let s:endSnip = g:snipPos[s:curPos][1]
51   - let s:endSnipLine = g:snipPos[s:curPos][0]
  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]
52 61
53 62 call cursor(g:snipPos[s:curPos][0], g:snipPos[s:curPos][1])
54 63 let s:prevLen = [line('$'), col('$')]
@@ -63,6 +72,7 @@ fun snipMate#expandSnip(snip, col)
63 72 return ''
64 73 endf
65 74
  75 +" Prepare snippet to be processed by s:BuildTabStops
66 76 fun s:ProcessSnippet(snip)
67 77 let snippet = a:snip
68 78 " Evaluate eval (`...`) expressions.
@@ -99,6 +109,7 @@ fun s:ProcessSnippet(snip)
99 109 return snippet
100 110 endf
101 111
  112 +" Counts occurences of haystack in needle
102 113 fun s:Count(haystack, needle)
103 114 let counter = 0
104 115 let index = stridx(a:haystack, a:needle)
@@ -109,8 +120,7 @@ fun s:Count(haystack, needle)
109 120 return counter
110 121 endf
111 122
112   -" This function builds a list of a list of each tab stop in the
113   -" snippet containing:
  123 +" Builds a list of a list of each tab stop in the snippet containing:
114 124 " 1.) The tab stop's line number.
115 125 " 2.) The tab stop's column number
116 126 " (by getting the length of the string between the last "\n" and the
@@ -156,45 +166,65 @@ fun s:BuildTabStops(snip, lnum, col, indent)
156 166 return [snipPos, i - 1]
157 167 endf
158 168
159   -fun snipMate#jumpTabStop()
  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 +
160 176 if exists('s:update')
161 177 call s:UpdatePlaceholderTabStops()
162 178 else
163 179 call s:UpdateTabStops()
164 180 endif
165 181
166   - let s:curPos += 1
  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 +
167 196 if s:curPos == s:snipLen
168   - let sMode = s:endSnip == g:snipPos[s:curPos-1][1]+g:snipPos[s:curPos-1][2]
  197 + let sMode = s:endCol == g:snipPos[s:curPos-1][1]+g:snipPos[s:curPos-1][2]
169 198 call s:RemoveSnippet()
170 199 return sMode ? "\<tab>" : TriggerSnippet()
171 200 endif
172 201
173 202 call cursor(g:snipPos[s:curPos][0], g:snipPos[s:curPos][1])
174 203
175   - let s:endSnipLine = g:snipPos[s:curPos][0]
176   - let s:endSnip = g:snipPos[s:curPos][1]
177   - let s:prevLen = [line('$'), col('$')]
  204 + let s:endLine = g:snipPos[s:curPos][0]
  205 + let s:endCol = g:snipPos[s:curPos][1]
  206 + let s:prevLen = [line('$'), col('$')]
178 207
179 208 return g:snipPos[s:curPos][2] == -1 ? '' : s:SelectWord()
180 209 endf
181 210
182 211 fun s:UpdatePlaceholderTabStops()
183 212 let changeLen = s:origWordLen - g:snipPos[s:curPos][2]
184   - unl s:startSnip s:origWordLen s:update
185   - if !exists('s:origPos') | return | endif
  213 + unl s:startCol s:origWordLen s:update
  214 + if !exists('s:oldVars') | return | endif
186 215 " Update tab stops in snippet if text has been added via "$#"
187 216 " (e.g., in "${1:foo}bar$1${2}").
188 217 if changeLen != 0
189 218 let curLine = line('.')
190 219
191   - for pos in g:snipPos[s:curPos + 1:]
192   - let changed = pos[0] == curLine && pos[1] > s:origSnipPos
  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
193 223 let changedVars = 0
194 224 let endPlaceholder = pos[2] - 1 + pos[1]
195 225 " Subtract changeLen from each tab stop that was after any of
196 226 " the current tab stop's placeholders.
197   - for [lnum, col] in s:origPos
  227 + for [lnum, col] in s:oldVars
198 228 if lnum > pos[0] | break | endif
199 229 if pos[0] == lnum
200 230 if pos[1] > col || (pos[2] == -1 && pos[1] == col)
@@ -211,8 +241,8 @@ fun s:UpdatePlaceholderTabStops()
211 241 if pos[2] == -1 | continue | endif
212 242 " Do the same to any placeholders in the other tab stops.
213 243 for nPos in pos[3]
214   - let changed = nPos[0] == curLine && nPos[1] > s:origSnipPos
215   - for [lnum, col] in s:origPos
  244 + let changed = nPos[0] == curLine && nPos[1] > s:oldEndCol
  245 + for [lnum, col] in s:oldVars
216 246 if lnum > nPos[0] | break | endif
217 247 if nPos[0] == lnum && nPos[1] > col
218 248 let changed += 1
@@ -222,23 +252,23 @@ fun s:UpdatePlaceholderTabStops()
222 252 endfor
223 253 endfor
224 254 endif
225   - unl s:endSnip s:origPos s:origSnipPos
  255 + unl s:endCol s:oldVars s:oldEndCol
226 256 endf
227 257
228 258 fun s:UpdateTabStops()
229   - let changeLine = s:endSnipLine - g:snipPos[s:curPos][0]
230   - let changeCol = s:endSnip - g:snipPos[s:curPos][1]
  259 + let changeLine = s:endLine - g:snipPos[s:curPos][0]
  260 + let changeCol = s:endCol - g:snipPos[s:curPos][1]
231 261 if exists('s:origWordLen')
232 262 let changeCol -= s:origWordLen
233 263 unl s:origWordLen
234 264 endif
235 265 let lnum = g:snipPos[s:curPos][0]
236   - let col = g:snipPos[s:curPos][1]
  266 + let col = g:snipPos[s:curPos][1]
237 267 " Update the line number of all proceeding tab stops if <cr> has
238 268 " been inserted.
239 269 if changeLine != 0
240 270 let changeLine -= 1
241   - for pos in g:snipPos[s:curPos + 1:]
  271 + for pos in g:snipPos
242 272 if pos[0] >= lnum
243 273 if pos[0] == lnum | let pos[1] += changeCol | endif
244 274 let pos[0] += changeLine
@@ -254,7 +284,7 @@ fun s:UpdateTabStops()
254 284 elseif changeCol != 0
255 285 " Update the column of all proceeding tab stops if text has
256 286 " been inserted/deleted in the current line.
257   - for pos in g:snipPos[s:curPos + 1:]
  287 + for pos in g:snipPos
258 288 if pos[1] >= col && pos[0] == lnum
259 289 let pos[1] += changeCol
260 290 endif
@@ -271,13 +301,13 @@ endf
271 301
272 302 fun s:SelectWord()
273 303 let s:origWordLen = g:snipPos[s:curPos][2]
274   - let s:oldWord = strpart(getline('.'), g:snipPos[s:curPos][1] - 1,
275   - \ s:origWordLen)
  304 + let s:oldWord = strpart(getline('.'), g:snipPos[s:curPos][1] - 1,
  305 + \ s:origWordLen)
276 306 let s:prevLen[1] -= s:origWordLen
277 307 if !empty(g:snipPos[s:curPos][3])
278   - let s:update = 1
279   - let s:endSnip = -1
280   - let s:startSnip = g:snipPos[s:curPos][1] - 1
  308 + let s:update = 1
  309 + let s:endCol = -1
  310 + let s:startCol = g:snipPos[s:curPos][1] - 1
281 311 endif
282 312 if !s:origWordLen | return '' | endif
283 313 let l = col('.') != 1 ? 'l' : ''
@@ -300,49 +330,53 @@ fun s:UpdateChangedSnip(entering)
300 330 if exists('g:snipPos') && bufnr(0) != s:lastBuf
301 331 call s:RemoveSnippet()
302 332 elseif exists('s:update') " If modifying a placeholder
303   - if !exists('s:origPos') && s:curPos + 1 < s:snipLen
  333 + if !exists('s:oldVars') && s:curPos + 1 < s:snipLen
304 334 " Save the old snippet & word length before it's updated
305   - " s:startSnip must be saved too, in case text is added
  335 + " s:startCol must be saved too, in case text is added
306 336 " before the snippet (e.g. in "foo$1${2}bar${1:foo}").
307   - let s:origSnipPos = s:startSnip
308   - let s:origPos = deepcopy(g:snipPos[s:curPos][3])
  337 + let s:oldEndCol = s:startCol
  338 + let s:oldVars = deepcopy(g:snipPos[s:curPos][3])
309 339 endif
310 340 let col = col('.') - 1
311 341
312   - if s:endSnip != -1
  342 + if s:endCol != -1
313 343 let changeLen = col('$') - s:prevLen[1]
314   - let s:endSnip += changeLen
  344 + let s:endCol += changeLen
315 345 else " When being updated the first time, after leaving select mode
316 346 if a:entering | return | endif
317   - let s:endSnip = col - 1
  347 + let s:endCol = col - 1
318 348 endif
319 349
320 350 " If the cursor moves outside the snippet, quit it
321   - if line('.') != g:snipPos[s:curPos][0] || col < s:startSnip ||
322   - \ col - 1 > s:endSnip
323   - unl! s:startSnip s:origWordLen s:origPos s:update
  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
324 354 return s:RemoveSnippet()
325 355 endif
326 356
327 357 call s:UpdateVars()
328 358 let s:prevLen[1] = col('$')
329 359 elseif exists('g:snipPos')
330   - let col = col('.')
331   - let lnum = line('.')
  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('.')
332 366 let changeLine = line('$') - s:prevLen[0]
333 367
334   - if lnum == s:endSnipLine
335   - let s:endSnip += col('$') - s:prevLen[1]
  368 + if lnum == s:endLine
  369 + let s:endCol += col('$') - s:prevLen[1]
336 370 let s:prevLen = [line('$'), col('$')]
337 371 endif
338 372 if changeLine != 0
339   - let s:endSnipLine += changeLine
340   - let s:endSnip = col
  373 + let s:endLine += changeLine
  374 + let s:endCol = col
341 375 endif
342 376
343 377 " Delete snippet if cursor moves out of it in insert mode
344   - if (lnum == s:endSnipLine && (col > s:endSnip || col < g:snipPos[s:curPos][1]))
345   - \ || lnum > s:endSnipLine || lnum < g:snipPos[s:curPos][0]
  378 + if (lnum == s:endLine && (col > s:endCol || col < g:snipPos[s:curPos][1]))
  379 + \ || lnum > s:endLine || lnum < g:snipPos[s:curPos][0]
346 380 call s:RemoveSnippet()
347 381 endif
348 382 endif
@@ -351,25 +385,25 @@ endf
351 385 " This updates the variables in a snippet when a placeholder has been edited.
352 386 " (e.g., each "$1" in "${1:foo} $1bar $1bar")
353 387 fun s:UpdateVars()
354   - let newWordLen = s:endSnip - s:startSnip + 1
355   - let newWord = strpart(getline('.'), s:startSnip, newWordLen)
  388 + let newWordLen = s:endCol - s:startCol + 1
  389 + let newWord = strpart(getline('.'), s:startCol, newWordLen)
356 390 if newWord == s:oldWord || empty(g:snipPos[s:curPos][3])
357 391 return
358 392 endif
359 393
360   - let changeLen = g:snipPos[s:curPos][2] - newWordLen
361   - let curLine = line('.')
362   - let startCol = col('.')
363   - let oldStartSnip = s:startSnip
  394 + let changeLen = g:snipPos[s:curPos][2] - newWordLen
  395 + let curLine = line('.')
  396 + let startCol = col('.')
  397 + let oldStartSnip = s:startCol
364 398 let updateTabStops = changeLen != 0
365   - let i = 0
  399 + let i = 0
366 400
367 401 for [lnum, col] in g:snipPos[s:curPos][3]
368 402 if updateTabStops
369   - let start = s:startSnip
  403 + let start = s:startCol
370 404 if lnum == curLine && col <= start
371   - let s:startSnip -= changeLen
372   - let s:endSnip -= changeLen
  405 + let s:startCol -= changeLen
  406 + let s:endCol -= changeLen
373 407 endif
374 408 for nPos in g:snipPos[s:curPos][3][(i):]
375 409 " This list is in ascending order, so quit if we've gone too far.
@@ -389,8 +423,8 @@ fun s:UpdateVars()
389 423 call setline(lnum, substitute(getline(lnum), '\%'.col.'c\V'.
390 424 \ escape(s:oldWord, '\'), escape(newWord, '\&'), ''))
391 425 endfor
392   - if oldStartSnip != s:startSnip
393   - call cursor(0, startCol + s:startSnip - oldStartSnip)
  426 + if oldStartSnip != s:startCol
  427 + call cursor(0, startCol + s:startCol - oldStartSnip)
394 428 endif
395 429
396 430 let s:oldWord = newWord
20 doc/snipMate.txt
... ... @@ -1,7 +1,7 @@
1 1 *snipMate.txt* Plugin for using TextMate-style snippets in Vim.
2 2
3 3 snipMate *snippet* *snippets* *snipMate*
4   -Last Change: May 8, 2009
  4 +Last Change: July 12, 2009
5 5
6 6 |snipMate-description| Description
7 7 |snipMate-syntax| Snippet syntax
@@ -33,7 +33,7 @@ you type "for<tab>" in insert mode, it will expand a typical for loop in C: >
33 33 To go to the next item in the loop, simply <tab> over to it; if there is
34 34 repeated code, such as the "i" variable in this example, you can simply
35 35 start typing once it's highlighted and all the matches specified in the
36   -snippet will be updated.
  36 +snippet will be updated. To go in reverse, use <shift-tab>.
37 37
38 38 ==============================================================================
39 39 SYNTAX *snippet-syntax*
@@ -62,7 +62,7 @@ only be used outside of a snippet declaration. E.g.: >
62 62 snippet trigger
63 63 expanded text
64 64 snippet another_trigger
65   - # this doesn't work!
  65 + # this isn't a comment!
66 66 expanded text
67 67 <
68 68 This should hopefully be obvious with the included syntax highlighting.
@@ -221,14 +221,14 @@ spaces. If 'softtabstop' is not set, 'shiftwidth' is used instead.
221 221 snipMate does not come with a setting to customize the trigger key, but you
222 222 can remap it easily in the two lines it's defined in the 'after' directory
223 223 under 'plugin/snipMate.vim'. For instance, to change the trigger key
224   -to shift-tab, just change this: >
  224 +to CTRL-J, just change this: >
225 225
226 226 ino <tab> <c-r>=TriggerSnippet()<cr>
227 227 snor <tab> <esc>i<right><c-r>=TriggerSnippet()<cr>
228 228
229 229 to this: >
230   - ino <s-tab> <c-r>=TriggerSnippet()<cr>
231   - snor <s-tab> <esc>i<right><c-r>=TriggerSnippet()<cr>
  230 + ino <c-j> <c-r>=TriggerSnippet()<cr>
  231 + snor <c-j> <esc>i<right><c-r>=TriggerSnippet()<cr>
232 232
233 233 ==============================================================================
234 234 FEATURES *snipMate-features*
@@ -243,15 +243,15 @@ snipMate.vim has the following features among others:
243 243 - Snippets can have multiple matches.
244 244 - Snippets can be out of order. For instance, in a do...while loop, the
245 245 condition can be added before the code.
246   - - (New) File-based snippets are supported.
247   - - (New) Triggers after non-word delimiters are expanded, e.g. "foo"
  246 + - [New] File-based snippets are supported.
  247 + - [New] Triggers after non-word delimiters are expanded, e.g. "foo"
248 248 in "bar.foo".
  249 + - [New] <shift-tab> can now be used to jump tab stops in reverse order.
249 250
250 251 ==============================================================================
251 252 DISADVANTAGES *snipMate-disadvantages*
252 253
253 254 snipMate.vim currently has the following disadvantages to TextMate's snippets:
254   - - There is no way to go back a tab stop, like shift-tab in TextMate.
255 255 - There is no $0; the order of tab stops must be explicitly stated.
256 256 - Placeholders within placeholders are not possible. E.g.: >
257 257
@@ -276,4 +276,6 @@ To contact the author (Michael Sanders), please email:
276 276
277 277 I greatly appreciate any suggestions or improvements offered for the script.
278 278
  279 +==============================================================================
  280 +
279 281 vim:tw=78:ts=8:ft=help:norl:
26 plugin/snipMate.vim
... ... @@ -1,6 +1,6 @@
1 1 " File: snipMate.vim
2 2 " Author: Michael Sanders
3   -" Version: 0.82
  3 +" Version: 0.83
4 4 " Description: snipMate.vim implements some of TextMate's snippets features in
5 5 " Vim. A snippet is a piece of often-typed text that you can
6 6 " insert into your document using a trigger word followed by a "<tab>".
@@ -138,7 +138,7 @@ fun! TriggerSnippet()
138 138 call feedkeys("\<tab>") | return ''
139 139 endif
140 140
141   - if exists('g:snipPos') | return snipMate#jumpTabStop() | endif
  141 + if exists('g:snipPos') | return snipMate#jumpTabStop(0) | endif
142 142
143 143 let word = matchstr(getline('.'), '\S\+\%'.col('.').'c')
144 144 for scope in [bufnr('%')] + split(&ft, '\.') + ['_']
@@ -147,7 +147,7 @@ fun! TriggerSnippet()
147 147 " the snippet.
148 148 if snippet != ''
149 149 let col = col('.') - len(trigger)
150   - sil exe 's/\V'.escape(trigger, '/').'\%#//'
  150 + sil exe 's/\V'.escape(trigger, '/.').'\%#//'
151 151 return snipMate#expandSnip(snippet, col)
152 152 endif
153 153 endfor
@@ -159,6 +159,23 @@ fun! TriggerSnippet()
159 159 return "\<tab>"
160 160 endf
161 161
  162 +fun! BackwardsSnippet()
  163 + if exists('g:snipPos') | return snipMate#jumpTabStop(1) | endif
  164 +
  165 + if exists('g:SuperTabMappingForward')
  166 + if g:SuperTabMappingBackward == "<s-tab>"
  167 + let SuperTabKey = "\<c-p>"
  168 + elseif g:SuperTabMappingForward == "<s-tab>"
  169 + let SuperTabKey = "\<c-n>"
  170 + endif
  171 + endif
  172 + if exists('SuperTabKey')
  173 + call feedkeys(SuperTabKey)
  174 + return ''
  175 + endif
  176 + return "\<s-tab>"
  177 +endf
  178 +
162 179 " Check if word under cursor is snippet trigger; if it isn't, try checking if
163 180 " the text after non-word characters is (e.g. check for "foo" in "bar.foo")
164 181 fun s:GetSnippet(word, scope)
@@ -174,6 +191,9 @@ fun s:GetSnippet(word, scope)
174 191 let word = substitute(word, '.\{-}\W', '', '')
175 192 endif
176 193 endw
  194 + if word == '' && a:word != '.' && stridx(a:word, '.') != -1
  195 + let [word, snippet] = s:GetSnippet('.', a:scope)
  196 + endif
177 197 return [word, snippet]
178 198 endf
179 199

0 comments on commit 9e11b09

Please sign in to comment.
Something went wrong with that request. Please try again.