· 8 years ago · Apr 14, 2018, 03:28 PM
1" File: snippetsEmu.vim
2" Author: Felix Ingram
3" ( f.ingram.lists <AT> gmail.com )
4" Description: An attempt to implement TextMate style Snippets. Features include
5" automatic cursor placement and command execution.
6" $LastChangedDate$
7" Version: 1.1
8" $Revision$
9"
10" This file contains some simple functions that attempt to emulate some of the
11" behaviour of 'Snippets' from the OS X editor TextMate, in particular the
12" variable bouncing and replacement behaviour.
13"
14" {{{ USAGE:
15"
16" Place the file in your plugin directory.
17" Define snippets using the Snippet command.
18" Snippets are best defined in the 'after' subdirectory of your Vim home
19" directory ('~/.vim/after' on Unix). Filetype specific snippets can be defined
20" in '~/.vim/after/ftplugin/<filetype>_snippets.vim. Using the <buffer> argument will
21" By default snippets are buffer specific. To define general snippets available
22" globally use the 'Iabbr' command.
23"
24" Example One:
25" Snippet fori for <{datum}> in <{data}>:<CR><{datum}>.<{}>
26"
27" The above will expand to the following (indenting may differ):
28"
29" for <{datum}> in <{data}>:
30" <{datum}>.<{}>
31"
32" The cursor will be placed after the first '<{' in insert mode.
33" Pressing <Tab> will 'tab' to the next place marker (<{data}>) in
34" insert mode. Adding text between <{ and }> and then hitting <{Tab}> will
35" remove the angle brackets and replace all markers with a similar identifier.
36"
37" Example Two:
38" With the cursor at the pipe, hitting <Tab> will replace:
39" for <{MyVariableName|datum}> in <{data}>:
40" <{datum}>.<{}>
41"
42" with (the pipe shows the cursor placement):
43"
44" for MyVariableName in <{data}>:
45" MyVariableName.<{}>
46"
47" Enjoy.
48"
49" For more information please see the documentation accompanying this plugin.
50"
51" Additional Features:
52"
53" Commands in tags. Anything after a ':' in a tag will be run with Vim's
54" 'execute' command. The value entered by the user (or the tag name if no change
55" has been made) is passed in the @z register (the original contents of the
56" register are restored once the command has been run).
57"
58" Named Tags. Naming a tag (the <{datum}> tag in the example above) and changing
59" the value will cause all other tags with the same name to be changed to the
60" same value (as illustrated in the above example). Not changing the value and
61" hitting <Tab> will cause the tag's name to be used as the default value.
62"
63" Test tags for pattern matching:
64" The following are examples of valid and invalid tags. Whitespace can only be
65" used in a tag name if the name is enclosed in quotes.
66"
67" Valid tags
68" <{}>
69" <{tagName}>
70" <{tagName:command}>
71" <{"Tag Name"}>
72" <{"Tag Name":command}>
73"
74" Invalid tags, random text
75" <{:}>
76" <{:command}>
77" <{Tag Name}>
78" <{Tag Name:command}>
79" <{"Tag Name":}>
80" <{Tag }>
81" <{OpenTag
82"
83" Here's our magic search term (assumes '<{',':' and '}>' as our tag delimiters:
84" <{\([^[:punct:] \t]\{-}\|".\{-}"\)\(:[^}>]\{-1,}\)\?}>
85" }}}
86
87if v:version < 700
88 echomsg "snippetsEmu plugin requires Vim version 7 or later"
89 finish
90endif
91
92if globpath(&rtp, 'plugin/snippetEmu.vim') != ""
93 call confirm("It looks like you've got an old version of snippetsEmu installed. Please delete the file 'snippetEmu.vim' from the plugin directory. Note lack of 's'")
94endif
95
96let s:debug = 0
97let s:Disable = 0
98
99function! s:Debug(func, text)
100 if exists('s:debug') && s:debug == 1
101 echom "Snippy: ".a:func.": ".a:text
102 endif
103endfunction
104
105if (exists('loaded_snippet') || &cp) && !s:debug
106 finish
107endif
108
109call s:Debug("","Started the plugin")
110
111let loaded_snippet=1
112" {{{ Set up variables
113if !exists("g:snip_start_tag")
114 let g:snip_start_tag = "<{"
115endif
116
117if !exists("g:snip_end_tag")
118 let g:snip_end_tag = "}>"
119endif
120
121if !exists("g:snip_elem_delim")
122 let g:snip_elem_delim = ":"
123endif
124
125if !exists("g:snippetsEmu_key")
126 let g:snippetsEmu_key = "<Tab>"
127endif
128
129call s:Debug("", "Set variables")
130
131" }}}
132" {{{ Set up menu
133for def_file in split(globpath(&rtp, "after/ftplugin/*_snippets.vim"), '\n')
134 call s:Debug("","Adding ".def_file." definitions to menu")
135 let snip = substitute(def_file, '.*[\\/]\(.*\)_snippets.vim', '\1', '')
136 exec "nmenu <silent> S&nippets.".snip." :source ".def_file."<CR>"
137endfor
138" }}}
139" {{{ Sort out supertab
140function! s:GetSuperTabSNR()
141 redir @a
142 exec "silent scriptnames"
143 redir END
144 return matchstr(@a, '\(\<\d\+\)\(.*supertab.*\)')[1]
145endfunction
146
147function! s:SetupSupertab()
148 if !exists('s:supInstalled')
149 let s:supInstalled = 0
150 endif
151 if s:supInstalled == 1 || globpath(&rtp, 'plugin/supertab.vim') != ""
152 call s:Debug("SetupSupertab", "Supertab installed")
153 let s:SupSNR = s:GetSuperTabSNR()
154 let s:supInstalled = 1
155 let s:done_remap = 0
156 endif
157endfunction
158
159call s:SetupSupertab()
160" }}}
161" {{{ Map Jumper to the default key if not set already
162function! s:SnipMapKeys()
163 if (!hasmapto('<Plug>Jumper','i'))
164 if s:supInstalled == 1
165 exec 'imap '.g:snippetsEmu_key.' <Plug>Jumper'
166 else
167 exec 'imap <unique> '.g:snippetsEmu_key.' <Plug>Jumper'
168 endif
169 endif
170
171 if (!hasmapto( 'i<BS>'.g:snippetsEmu_key, 's'))
172 exec 'smap <unique> '.g:snippetsEmu_key.' i<BS>'.g:snippetsEmu_key
173 endif
174 imap <silent> <script> <Plug>Jumper <C-R>=<SID>Jumper()<CR>
175endfunction
176
177call s:SnipMapKeys()
178
179call s:Debug("", "Mapped keys")
180
181" }}}
182" {{{ SetLocalTagVars()
183function! s:SetLocalTagVars()
184 if exists("b:snip_end_tag") && exists("b:snip_start_tag") && exists("b:snip_elem_delim")
185 return [b:snip_start_tag, b:snip_elem_delim, b:snip_end_tag]
186 else
187 return [g:snip_start_tag, g:snip_elem_delim, g:snip_end_tag]
188 endif
189endfunction
190" }}}
191" {{{ SetSearchStrings() - Set the search string. Checks for buffer dependence
192function! s:SetSearchStrings()
193 let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars()
194 let b:search_str = snip_start_tag.'\([^'.
195 \snip_start_tag.snip_end_tag.
196 \'[:punct:] \t]\{-}\|".\{-}"\)\('.
197 \snip_elem_delim.
198 \'[^'.snip_end_tag.snip_start_tag.']\{-1,}\)\?'.snip_end_tag
199 let b:search_commandVal = "[^".snip_elem_delim."]*"
200 let b:search_endVal = "[^".snip_end_tag."]*"
201endfunction
202" }}}
203" {{{ SetCom(text, scope) - Set command function
204function! <SID>SetCom(text, scope)
205 let text = substitute(a:text, '\c<CR>\|<Esc>\|<Tab>\|<BS>\|<Space>\|<C-r>\|<Bar>\|\"\|\\','\\&',"g")
206
207 if s:supInstalled == 1
208 call s:SetupSupertab()
209 call s:SnipMapKeys()
210 endif
211
212 let text = substitute(text, "\r$", "","")
213
214 let tokens = split(text, ' ')
215 call filter(tokens, 'v:val != ""')
216 if len(tokens) == 0
217 let output = join(s:ListSnippets("","","",eval(a:scope)) ,"\n")
218 if output == ""
219 echohl Title | echo "No snippets defined" | echohl None
220 else
221 echohl Title | echo "Defined snippets:" | echohl None
222 echo output
223 endif
224 " NOTE - cases such as ":Snippet if " will intentionally(?) be parsed as a
225 " snippet named "if" with contents of " "
226 elseif len(tokens) == 1
227 let snip = s:Hash(tokens[0])
228 if exists(a:scope."trigger_".snip)
229 " FIXME - is there a better approach?
230 " echo doesn't handle ^M correctly
231 let pretty = substitute(eval(a:scope."trigger_".snip), "\r", "\n","g")
232 echo pretty
233 else
234 echohl Error | echo "Undefined snippet: ".snip | echohl None
235 endif
236 else
237 let [lhs, rhs] = [s:Hash(tokens[0]), join(tokens[1:])]
238 call s:SetSearchStrings()
239 let g:search_str = b:search_str
240 exe "let ".a:scope."trigger_".lhs.' = "'.rhs.'"'
241 endif
242endfunction
243" }}}
244" {{{ RestoreSearch()
245" Checks whether more tags exist and restores hlsearch and @/ if not
246function! s:RestoreSearch()
247 if !search(b:search_str, "n")
248 if exists("b:hl_on") && b:hl_on == 1
249 setlocal hlsearch
250 endif
251 if exists("b:search_sav")
252 let @/ = b:search_sav
253 endif
254 endif
255endfunction
256"}}}
257" {{{ DeleteEmptyTag
258function! s:DeleteEmptyTag()
259 let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars()
260 for i in range(s:StrLen(snip_start_tag) + s:StrLen(snip_end_tag))
261 normal x
262 endfor
263endfunction
264" }}}
265" {{{ SetUpTags()
266function! s:SetUpTags()
267 let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars()
268 if (strpart(getline("."), col(".")+strlen(snip_start_tag)-1, strlen(snip_end_tag)) == snip_end_tag)
269 call s:Debug("SetUpTags","Found an empty tag")
270 let b:tag_name = ""
271 if col(".") + s:StrLen(snip_start_tag.snip_end_tag) == s:StrLen(getline("."))
272 " We delete the empty tag here as otherwise we can't determine whether we
273 " need to send 'a' or 'A' as deleting the empty tag will sit us on the
274 " final character either way
275 call s:DeleteEmptyTag()
276 call s:RestoreSearch()
277 if col(".") == s:StrLen(getline("."))
278 return "\<Esc>a"
279 endif
280 else
281 call s:DeleteEmptyTag()
282 call s:RestoreSearch()
283 if col(".") == s:StrLen(getline("."))
284 return "\<Esc>A"
285 endif
286 endif
287 return ''
288 else
289 " Not on an empty tag so it must be a normal tag
290 let b:tag_name = s:ChopTags(matchstr(getline("."),b:search_str,col(".")-1))
291 call s:Debug("SetUpTags","On a tag called: ".b:tag_name)
292
293" Check for exclusive selection mode. If exclusive is not set then we need to
294" move back a character.
295 if &selection == "exclusive"
296 let end_skip = ""
297 else
298 let end_skip = "\<Left>"
299 endif
300
301 let start_skip = repeat("\<Right>",s:StrLen(snip_start_tag)+1)
302 call s:Debug("SetUpTags","Start skip is: ".start_skip)
303 call s:Debug("SetUpTags","Col() is: ".col("."))
304 if col(".") == 1
305 call s:Debug("SetUpTags","We're at the start of the line so don't need to skip the first char of start tag")
306 let start_skip = strpart(start_skip, 0, strlen(start_skip)-strlen("\<Right>"))
307 call s:Debug("SetUpTags","Start skip is now: ".start_skip)
308 endif
309 call s:Debug("SetUpTags","Returning: \<Esc>".start_skip."v/".snip_end_tag."\<CR>".end_skip."\<C-g>")
310 return "\<Esc>".start_skip."v/".snip_end_tag."\<CR>".end_skip."\<C-g>"
311 endif
312endfunction
313" }}}
314" {{{ NextHop() - Jump to the next tag if one is available
315function! <SID>NextHop()
316 let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars()
317 call s:Debug("NextHop", "Col() is: ".col("."))
318 call s:Debug("NextHop", "Position of next match = ".match(getline("."), b:search_str))
319 " First check to see if we have any tags on lines above the current one
320 if search(b:search_str, "bnW") != 0
321 " We have previous tags, so we'll jump to the start
322 normal gg
323 endif
324 " If the first match is after the current cursor position or not on this
325 " line...
326 if match(getline("."), b:search_str) >= col(".") || match(getline("."), b:search_str) == -1
327 " Perform a search to jump to the next tag
328 call s:Debug("NextHop", "Seaching for a tag")
329 if search(b:search_str) != 0
330 return s:SetUpTags()
331 else
332 " there are no more matches
333 call s:Debug("NextHop", "No more tags in the buffer")
334 " Restore hlsarch and @/
335 call s:RestoreSearch()
336 return ''
337 endif
338 else
339 " The match on the current line is on or before the cursor, so we need to
340 " move the cursor back
341 call s:Debug("NextHop", "Moving the cursor back")
342 call s:Debug("NextHop", "Col is: ".col("."))
343 call s:Debug("NextHop", "Moving back to column: ".match(getline("."), b:search_str))
344 while col(".") > match(getline("."), b:search_str) + 1
345 normal h
346 endwhile
347 call s:Debug("NextHop", "Col is now: ".col("."))
348 " Now we just set up the tag as usual
349 return s:SetUpTags()
350 endif
351endfunction
352" }}}
353" {{{ RunCommand() - Execute commands stored in tags
354function! s:RunCommand(command, z)
355 let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars()
356 call s:Debug("RunCommand", "RunCommand was passed this command: ".a:command." and this value: ".a:z)
357 if a:command == ''
358 return a:z
359 endif
360 " Save current value of 'z'
361 let snip_save = @z
362 let @z=a:z
363 " Call the command
364 execute 'let ret = '. a:command
365 " Replace the value
366 let @z = snip_save
367 return ret
368endfunction
369" }}}
370" {{{ MakeChanges() - Search the document making all the changes required
371" This function has been factored out to allow the addition of commands in tags
372function! s:MakeChanges()
373 " Make all the changes
374 " Change all the tags with the same name and no commands defined
375 let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars()
376
377 if b:tag_name == ""
378 call s:Debug("MakeChanges", "Nothing to do: tag_name is empty")
379 return
380 endif
381
382 let tagmatch = '\V'.snip_start_tag.b:tag_name.snip_end_tag
383
384 call s:Debug("MakeChanges", "Matching on this value: ".tagmatch)
385 call s:Debug("MakeChanges", "Replacing with this value: ".s:replaceVal)
386
387 try
388 call s:Debug("MakeChanges", "Running these commands: ".join(b:command_dict[b:tag_name], "', '"))
389 catch /E175/
390 call s:Debug("MakeChanges", "Could not find this key in the dict: ".b:tag_name)
391 endtry
392
393 let ind = 0
394 while search(tagmatch,"w") > 0
395 try
396 let commandResult = s:RunCommand(b:command_dict[b:tag_name][0], s:replaceVal)
397 catch /E175/
398 call s:Debug("MakeChanges", "Could not find this key in the dict: ".b:tag_name)
399 endtry
400 call s:Debug("MakeChanges", "Got this result: ".commandResult)
401 let lines = split(substitute(getline("."), tagmatch, commandResult, ''),'\n')
402 if len(lines) > 1
403 call setline(".", lines[0])
404 call append(".", lines[1:])
405 else
406 call setline(".", lines)
407 endif
408 try
409 unlet b:command_dict[b:tag_name][0]
410 catch /E175/
411 call s:Debug("MakeChanges", "Could not find this key in the dict: ".b:tag_name)
412 endtry
413 endwhile
414endfunction
415" }}}
416" {{{ ChangeVals() - Set up values for MakeChanges()
417function! s:ChangeVals(changed)
418 let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars()
419
420 if a:changed == 1
421 let s:CHANGED_VAL = 1
422 else
423 let s:CHANGED_VAL = 0
424 endif
425
426 call s:Debug("ChangeVals", "CHANGED_VAL: ".s:CHANGED_VAL)
427 call s:Debug("ChangeVals", "b:tag_name: ".b:tag_name)
428 let elem_match = match(s:line, snip_elem_delim, s:curCurs)
429 let tagstart = strridx(getline("."), snip_start_tag,s:curCurs)+strlen(snip_start_tag)
430
431 call s:Debug("ChangeVals", "About to access b:command_dict")
432 try
433 let commandToRun = b:command_dict[b:tag_name][0]
434 call s:Debug("ChangeVals", "Accessed command_dict")
435 call s:Debug("ChangeVals", "Running this command: ".commandToRun)
436 unlet b:command_dict[b:tag_name][0]
437 call s:Debug("ChangeVals", "Command list is now: ".join(b:command_dict[b:tag_name], "', '"))
438 catch /E175/
439 call s:Debug("ChangeVals", "Could not find this key in the dict: ".b:tag_name)
440 endtry
441
442 let commandMatch = substitute(commandToRun, '\', '\\\\', 'g')
443 if s:CHANGED_VAL
444 " The value has changed so we need to grab our current position back
445 " to the start of the tag
446 let replaceVal = strpart(getline("."), tagstart,s:curCurs-tagstart)
447 call s:Debug("ChangeVals", "User entered this value: ".replaceVal)
448 let tagmatch = replaceVal
449 call s:Debug("ChangeVals", "Col is: ".col("."))
450 exec "normal ".s:StrLen(tagmatch)."\<Left>"
451 call s:Debug("ChangeVals", "Col is: ".col("."))
452 else
453 " The value hasn't changed so it's just the tag name
454 " without any quotes that are around it
455 call s:Debug("ChangeVals", "Tag name is: ".b:tag_name)
456 let replaceVal = substitute(b:tag_name, '^"\(.*\)"$', '\1', '')
457 call s:Debug("ChangeVals", "User did not enter a value. Replacing with this value: ".replaceVal)
458 let tagmatch = ''
459 call s:Debug("ChangeVals", "Col is: ".col("."))
460 endif
461
462 let tagmatch = '\V'.snip_start_tag.tagmatch.snip_end_tag
463 call s:Debug("ChangeVals", "Matching on this string: ".tagmatch)
464 let tagsubstitution = s:RunCommand(commandToRun, replaceVal)
465 let lines = split(substitute(getline("."), tagmatch, tagsubstitution, ""),'\n')
466 if len(lines) > 1
467 call setline(".", lines[0])
468 call append(".", lines[1:])
469 else
470 call setline(".", lines)
471 endif
472 " We use replaceVal instead of tagsubsitution as otherwise the command
473 " result will be passed to subsequent tags
474 let s:replaceVal = replaceVal
475 call s:MakeChanges()
476 unlet s:CHANGED_VAL
477endfunction
478" }}}
479"{{{ SID() - Get the SID for the current script
480function! s:SID()
481 return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$')
482endfun
483"}}}
484"{{{ CheckForInTag() - Check whether we're in a tag
485function! s:CheckForInTag()
486 let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars()
487 if snip_start_tag != snip_end_tag
488 " The tags are different so we can check to see whether the
489 " end tag comes before a start tag
490 let s:startMatch = match(s:line, '\V'.snip_start_tag, s:curCurs)
491 let s:endMatch = match(s:line, '\V'.snip_end_tag, s:curCurs)
492
493 if s:endMatch != -1 && ((s:endMatch < s:startMatch) || s:startMatch == -1)
494 " End has come before start so we're in a tag.
495 return 1
496 else
497 return 0
498 endif
499 else
500 " Start and end tags are the same so we need do tag counting to see
501 " whether we're in a tag.
502 let s:count = 0
503 let s:curSkip = s:curCurs
504 while match(strpart(s:line,s:curSkip),snip_start_tag) != -1
505 if match(strpart(s:line,s:curSkip),snip_start_tag) == 0
506 let s:curSkip = s:curSkip + 1
507 else
508 let s:curSkip = s:curSkip + 1 + match(strpart(s:line,s:curSkip),snip_start_tag)
509 endif
510 let s:count = s:count + 1
511 endwhile
512 if (s:count % 2) == 1
513 " Odd number of tags implies we're inside a tag.
514 return 1
515 else
516 " We're not inside a tag.
517 return 0
518 endif
519 endif
520endfunction
521"}}}
522" {{{ SubSpecialVars(text)
523function! s:SubSpecialVars(text)
524 let text = a:text
525 let text = substitute(text, 'SNIP_FILE_NAME', expand('%'), 'g')
526 let text = substitute(text, 'SNIP_ISO_DATE', strftime("%Y-%m-%d"), 'g')
527 return text
528endfunction
529" }}}
530" {{{ SubCommandOutput(text)
531function! s:SubCommandOutput(text)
532 let search = '``.\{-}``'
533 let text = a:text
534 while match(text, search) != -1
535 let command_match = matchstr(text, search)
536 call s:Debug("SubCommandOutput", "Command found: ".command_match)
537 let command = substitute(command_match, '^..\(.*\)..$', '\1', '')
538 call s:Debug("SubCommandOutput", "Command being run: ".command)
539 exec 'let output = '.command
540 let output = escape(output, '\')
541 let text = substitute(text, '\V'.escape(command_match, '\'), output, '')
542 endwhile
543 let text = substitute(text, '\\`\\`\(.\{-}\)\\`\\`','``\1``','g')
544 return text
545endfunction
546" }}}
547" {{{ RemoveAndStoreCommands(text)
548function! s:RemoveAndStoreCommands(text)
549 let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars()
550
551 let text = a:text
552 if !exists("b:command_dict")
553 let b:command_dict = {}
554 endif
555
556 let tmp_command_dict = {}
557 try
558 let ind = match(text, b:search_str)
559 catch /E55: Unmatched \\)/
560 call confirm("SnippetsEmu has caught an error while performing a search. This is most likely caused by setting the start and end tags to special characters. Try setting the 'fileencoding' of the file in which you defined them to 'utf-8'.\n\nThe plugin will be disabled for the remainder of this Vim session.")
561 let s:Disable = 1
562 return ''
563 endtry
564 while ind > -1
565 call s:Debug("RemoveAndStoreCommands", "Text is: ".text)
566 call s:Debug("RemoveAndStoreCommands", "index is: ".ind)
567 let tag = matchstr(text, b:search_str, ind)
568 call s:Debug("RemoveAndStoreCommands", "Tag is: ".tag)
569 let commandToRun = matchstr(tag, snip_elem_delim.".*".snip_end_tag)
570
571 if commandToRun != ''
572 let tag_name = strpart(tag,strlen(snip_start_tag),match(tag,snip_elem_delim)-strlen(snip_start_tag))
573 call s:Debug("RemoveAndStoreCommands", "Got this tag: ".tag_name)
574 call s:Debug("RemoveAndStoreCommands", "Adding this command: ".commandToRun)
575 if tag_name != ''
576 if has_key(tmp_command_dict, tag_name)
577 call add(tmp_command_dict[tag_name], strpart(commandToRun, 1, strlen(commandToRun)-strlen(snip_end_tag)-1))
578 else
579 let tmp_command_dict[tag_name] = [strpart(commandToRun, 1, strlen(commandToRun)-strlen(snip_end_tag)-1)]
580 endif
581 endif
582 let text = substitute(text, '\V'.escape(commandToRun,'\'), snip_end_tag,'')
583 else
584 let tag_name = s:ChopTags(tag)
585 if tag_name != ''
586 if has_key(tmp_command_dict, tag_name)
587 call add(tmp_command_dict[tag_name], '')
588 else
589 let tmp_command_dict[tag_name] = ['']
590 endif
591 endif
592 endif
593 call s:Debug("RemoveAndStoreCommands", "".tag." found at ".ind)
594 let ind = match(text, b:search_str, ind+strlen(snip_end_tag))
595 endwhile
596
597 for key in keys(tmp_command_dict)
598 if has_key(b:command_dict, key)
599 for item in reverse(tmp_command_dict[key])
600 call insert(b:command_dict[key], item)
601 endfor
602 else
603 let b:command_dict[key] = tmp_command_dict[key]
604 endif
605 endfor
606 return text
607endfunction
608" }}}
609" {{{ ReturnKey() - Return our mapped key or Supertab key
610function! s:ReturnKey()
611 if s:supInstalled
612 call s:Debug('ReturnKey', 'Snippy: SuperTab installed. Returning <C-n> instead of <Tab>')
613 return "\<C-R>=".s:SupSNR."SuperTab('n')\<CR>"
614 else
615 " We need this hacky line as the one below doesn't seem to work.
616 " Patches welcome
617 exe "return \"".substitute(g:snippetsEmu_key, '^<', "\\\\<","")."\""
618 "return substitute(g:snippetsEmu_key, '^<', "\\<","")
619 endif
620endfunction
621" }}}
622" {{{ Jumper()
623" We need to rewrite this function to reflect the new behaviour. Every jump
624" will now delete the markers so we need to allow for the following conditions
625" 1. Empty tags e.g. "<{}>". When we land inside then we delete the tags.
626" "<{:}>" is now an invalid tag (use "<{}>" instead) so we don't need to check for
627" this
628" 2. Tag with variable name. Save the variable name for the next jump.
629" 3. Tag with command. Tags no longer have default values. Everything after the
630" centre delimiter until the end tag is assumed to be a command.
631"
632" Jumper is performed when we want to perform a jump. If we've landed in a
633" 1. style tag then we'll be in free form text and just want to jump to the
634" next tag. If we're in a 2. or 3. style tag then we need to look for whether
635" the value has changed and make all the replacements. If we're in a 3.
636" style tag then we need to replace all the occurrences with their command
637" modified values.
638"
639function! <SID>Jumper()
640 if s:Disable == 1
641 return substitute(g:snippetsEmu_key, '^<', "\\<",'')
642 endif
643 let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars()
644
645 " Set up some mapping in case we got called before Supertab
646 if s:supInstalled == 1
647 call s:SetupSupertab()
648 call s:SnipMapKeys()
649 endif
650
651 if !exists('b:search_str') && exists('g:search_str')
652 let b:search_str = g:search_str
653 endif
654
655 if !exists('b:search_str')
656 return s:ReturnKey()
657 endif
658
659 let s:curCurs = col(".") - 1
660 let s:curLine = line(".")
661 let s:line = getline(".")
662 let s:replaceVal = ""
663
664 " First we'll check that the user hasn't just typed a snippet to expand
665 let origword = matchstr(strpart(getline("."), 0, s:curCurs), '\(^\|\s\)\S\{-}$')
666 let origword = substitute(origword, '\s', "", "")
667 call s:Debug("Jumper", "Original word was: ".origword)
668 let word = s:Hash(origword)
669 " The following code is lifted from the imaps.vim script - Many
670 " thanks for the inspiration to add the TextMate compatibility
671 let rhs = ''
672 let found = 0
673 " Check for buffer specific expansions
674 if exists('b:trigger_'.word)
675 exe 'let rhs = b:trigger_'.word
676 let found = 1
677 elseif exists('g:trigger_'.word)
678 " also check for global definitions
679 exe 'let rhs = g:trigger_'.word
680 let found = 1
681 endif
682
683 if found == 0
684 " Check using keyword boundary
685 let origword = matchstr(strpart(getline("."), 0, s:curCurs), '\k\{-}$')
686 call s:Debug("Jumper", "Original word was: ".origword)
687 let word = s:Hash(origword)
688 if exists('b:trigger_'.word)
689 exe 'let rhs = b:trigger_'.word
690 elseif exists('g:trigger_'.word)
691 " also check for global definitions
692 exe 'let rhs = g:trigger_'.word
693 endif
694 endif
695
696 if rhs != ''
697 " Save the value of hlsearch
698 if &hls
699 call s:Debug("Jumper", "Hlsearch set")
700 setlocal nohlsearch
701 let b:hl_on = 1
702 else
703 call s:Debug("Jumper", "Hlsearch not set")
704 let b:hl_on = 0
705 endif
706 " Save the last search value
707 let b:search_sav = @/
708 " If this is a mapping, then erase the previous part of the map
709 " by returning a number of backspaces.
710 let bkspc = substitute(origword, '.', "\<BS>", "g")
711 call s:Debug("Jumper", "Backspacing ".s:StrLen(origword)." characters")
712 let delEndTag = ""
713 if s:CheckForInTag()
714 call s:Debug("Jumper", "We're doing a nested tag")
715 call s:Debug("Jumper", "B:tag_name: ".b:tag_name)
716 if b:tag_name != ''
717 try
718 call s:Debug("Jumper", "Commands for this tag are currently: ".join(b:command_dict[b:tag_name],"', '"))
719 call s:Debug("Jumper", "Removing command for '".b:tag_name."'")
720 unlet b:command_dict[b:tag_name][0]
721 call s:Debug("Jumper", "Commands for this tag are now: ".join(b:command_dict[b:tag_name],"', '"))
722 catch /E175/
723 call s:Debug("Jumper", "Could not find this key in the dict: ".b:tag_name)
724 endtry
725 endif
726 call s:Debug("Jumper", "Deleting start tag")
727 let bkspc = bkspc.substitute(snip_start_tag, '.', "\<BS>", "g")
728 call s:Debug("Jumper", "Deleting end tag")
729 let delEndTag = substitute(snip_end_tag, '.', "\<Del>", "g")
730 call s:Debug("Jumper", "Deleting ".s:StrLen(delEndTag)." characters")
731 endif
732
733 " We've found a mapping so we'll substitute special variables
734 let rhs = s:SubSpecialVars(rhs)
735 let rhs = s:SubCommandOutput(rhs)
736 " Now we'll chop out the commands from tags
737 let rhs = s:RemoveAndStoreCommands(rhs)
738 if s:Disable == 1
739 return substitute(g:snippetsEmu_key, '^<', "\\<",'')
740 endif
741
742 " Save the value of 'backspace'
743 let bs_save = &backspace
744 set backspace=indent,eol,start
745 return bkspc.delEndTag.rhs."\<Esc>:set backspace=".bs_save."\<CR>a\<C-r>=<SNR>".s:SID()."_NextHop()\<CR>"
746 else
747 " No definition so let's check to see whether we're in a tag
748 if s:CheckForInTag()
749 call s:Debug("Jumper", "No mapping and we're in a tag")
750 " We're in a tag so we need to do processing
751 if strpart(s:line, s:curCurs - strlen(snip_start_tag), strlen(snip_start_tag)) == snip_start_tag
752 call s:Debug("Jumper", "Value not changed")
753 call s:ChangeVals(0)
754 else
755 call s:Debug("Jumper", "Value changed")
756 call s:ChangeVals(1)
757 endif
758 return "\<C-r>=<SNR>".s:SID()."_NextHop()\<CR>"
759 else
760 " We're not in a tag so we'll see whether there are more tags
761 if search(b:search_str, "n")
762 " More tags so let's perform nexthop
763 let s:replaceVal = ""
764 return "\<C-r>=<SNR>".s:SID()."_NextHop()\<CR>"
765 else
766 " No more tags so let's return a Tab after restoring hlsearch and @/
767 call s:RestoreSearch()
768 if exists("b:command_dict")
769 unlet b:command_dict
770 endif
771 return s:ReturnKey()
772 endif
773 endif
774 endif
775endfunction
776" }}}
777"{{{ ListSnippets() - Return a list of snippets - used for command completion
778function! s:ListSnippets(ArgLead, CmdLine, CursorPos, scope)
779 " Only allow completion for the second argument
780 " TODO
781 return sort(map(map(filter(keys(a:scope), 'v:val =~ "^trigger_'.a:ArgLead.'"'), 'v:val[8:]'), 's:UnHash(v:val)'))
782endfunction
783
784function! s:ListBufferSnippets(ArgLead, CmdLine, CursorPos)
785 return s:ListSnippets(a:ArgLead, a:CmdLine, a:CursorPos, b:)
786endfunction
787
788function! s:ListGlobalSnippets(ArgLead, CmdLine, CursorPos)
789 return s:ListSnippets(a:ArgLead, a:CmdLine, a:CursorPos, g:)
790endfunction
791" }}}
792" {{{ DelSnippet() - Delete a snippet
793function! s:DelSnippet(snippet, scope)
794 if a:snippet != ""
795 try
796 exec "unlet ".a:scope."trigger_".s:Hash(a:snippet)
797 catch /E108: No such variable:/
798 echom "Snippet '".a:snippet."' does not exist."
799 endtry
800 endif
801endfunction
802" }}}
803" {{{ Set up the 'Iabbr' and 'Snippet' commands
804"command! -nargs=+ Iabbr execute s:SetCom(<q-args>)
805"command! -nargs=+ Snippet execute s:SetCom("<buffer> ".<q-args>)
806command! -complete=customlist,s:ListGlobalSnippets -nargs=*
807 \ Iabbr call <SID>SetCom(<q-args>, "g:")
808command! -complete=customlist,s:ListBufferSnippets -nargs=*
809 \ Snippet call <SID>SetCom(<q-args>, "b:")
810command! -range CreateSnippet <line1>,<line2>call s:CreateSnippet()
811command! -range CreateBundleSnippet <line1>,<line2>call s:CreateBundleSnippet()
812command! -complete=customlist,s:ListBufferSnippets -nargs=*
813 \ DelSnippet call <SID>DelSnippet(<q-args>, "b:")
814command! -complete=customlist,s:ListGlobalSnippets -nargs=*
815 \ DelIabbr call <SID>DelSnippet(<q-args>, "g:")
816"}}}
817" {{{ Utility functions
818
819" This function will convert the selected range into a snippet
820function! s:CreateSnippet() range
821 let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars()
822 let snip = ""
823 if &expandtab
824 let tabs = indent(a:firstline)/&shiftwidth
825 let tabstr = repeat(' ',&shiftwidth)
826 else
827 let tabs = indent(a:firstline)/&tabstop
828 let tabstr = '\t'
829 endif
830 let tab_text = repeat(tabstr,tabs)
831
832 for i in range(a:firstline, a:lastline)
833 "First chop off the indent
834 let text = substitute(getline(i),tab_text,'','')
835 "Now replace 'tabs' with <Tab>s
836 let text = substitute(text, tabstr, '<Tab>','g')
837 "And trim the newlines
838 let text = substitute(text, "\r", '','g')
839 let snip = snip.text.'<CR>'
840 endfor
841 let tag = snip_start_tag.snip_end_tag
842 let split_sav = &swb
843 set swb=useopen
844 if bufexists("Snippets")
845 belowright sb Snippets
846 else
847 belowright sp Snippets
848 endif
849 resize 8
850 setlocal buftype=nofile
851 setlocal bufhidden=hide
852 setlocal noswapfile
853 let @"=tag
854 exe 'set swb='.split_sav
855 let trig = inputdialog("Please enter the trigger word for your snippet: ", "My_snippet")
856 if trig == ""
857 let trig = "YOUR_SNIPPET_NAME_HERE"
858 endif
859 call append("$", "Snippet ".trig." ".snip)
860 if getline(1) == ""
861 normal ggdd
862 endif
863 normal G
864endfunction
865
866" This function will convert the selected range into a snippet suitable for
867" including in a bundle.
868function! s:CreateBundleSnippet() range
869 let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars()
870 let snip = ""
871 if &expandtab
872 let tabs = indent(a:firstline)/&shiftwidth
873 let tabstr = repeat(' ',&shiftwidth)
874 else
875 let tabs = indent(a:firstline)/&tabstop
876 let tabstr = '\t'
877 endif
878 let tab_text = repeat(tabstr,tabs)
879
880 for i in range(a:firstline, a:lastline)
881 let text = substitute(getline(i),tab_text,'','')
882 let text = substitute(text, tabstr, '<Tab>','g')
883 let text = substitute(text, "\r$", '','g')
884 let text = substitute(text, '"', '\\"','g')
885 let text = substitute(text, '|', '<Bar>','g')
886 let snip = snip.text.'<CR>'
887 endfor
888 let tag = '".st.et."'
889 let split_sav = &swb
890 set swb=useopen
891 if bufexists("Snippets")
892 belowright sb Snippets
893 else
894 belowright sp Snippets
895 endif
896 resize 8
897 setlocal buftype=nofile
898 setlocal bufhidden=hide
899 setlocal noswapfile
900 let @"=tag
901 exe 'set swb='.split_sav
902 let trig = inputdialog("Please enter the trigger word for your snippet: ", "My_snippet")
903 if trig == ""
904 let trig = "YOUR_SNIPPET_NAME_HERE"
905 endif
906 call append("$", 'exe "Snippet '.trig." ".snip.'"')
907 if getline(1) == ""
908 normal ggdd
909 endif
910 normal G
911endfunction
912
913" This function will just return what's passed to it unless a change has been
914" made
915fun! D(text)
916 if exists('s:CHANGED_VAL') && s:CHANGED_VAL == 1
917 return @z
918 else
919 return a:text
920 endif
921endfun
922
923" s:Hash allows the use of special characters in snippets
924" This function is lifted straight from the imaps.vim plugin. Please let me know
925" if this is against licensing.
926function! s:Hash(text)
927 return substitute(a:text, '\([^[:alnum:]]\)',
928 \ '\="_".char2nr(submatch(1))."_"', 'g')
929endfunction
930
931" s:UnHash allows the use of special characters in snippets
932" This function is lifted straight from the imaps.vim plugin. Please let me know
933" if this is against licensing.
934function! s:UnHash(text)
935 return substitute(a:text, '_\(\d\+\)_',
936 \ '\=nr2char(submatch(1))', 'g')
937endfunction
938
939" This function chops tags from any text passed to it
940function! s:ChopTags(text)
941 let text = a:text
942 call s:Debug("ChopTags", "ChopTags was passed this text: ".text)
943 let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars()
944 let text = strpart(text, strlen(snip_start_tag))
945 let text = strpart(text, 0, strlen(text)-strlen(snip_end_tag))
946 call s:Debug("ChopTags", "ChopTags is returning this text: ".text)
947 return text
948endfunction
949
950" This function ensures we measure string lengths correctly
951function! s:StrLen(str)
952 call s:Debug("StrLen", "StrLen returned: ".strlen(substitute(a:str, '.', 'x', 'g'))." based on this text: ".a:str)
953 return strlen(substitute(a:str, '.', 'x', 'g'))
954endfunction
955
956" }}}
957" vim: set tw=80 sw=2 sts=2 et foldmethod=marker :