diff options
Diffstat (limited to 'vim/plugin/snippetsEmu.vim')
-rw-r--r-- | vim/plugin/snippetsEmu.vim | 973 |
1 files changed, 973 insertions, 0 deletions
diff --git a/vim/plugin/snippetsEmu.vim b/vim/plugin/snippetsEmu.vim new file mode 100644 index 0000000..d8d23dc --- /dev/null +++ b/vim/plugin/snippetsEmu.vim @@ -0,0 +1,973 @@ +" File: snippetsEmu.vim +" Author: Felix Ingram +" ( f.ingram.lists <AT> gmail.com ) +" Description: An attempt to implement TextMate style Snippets. Features include +" automatic cursor placement and command execution. +" $LastChangedDate$ +" Version: 1.1 +" $Revision$ +" +" This file contains some simple functions that attempt to emulate some of the +" behaviour of 'Snippets' from the OS X editor TextMate, in particular the +" variable bouncing and replacement behaviour. +" +" {{{ USAGE: +" +" Place the file in your plugin directory. +" Define snippets using the Snippet command. +" Snippets are best defined in the 'after' subdirectory of your Vim home +" directory ('~/.vim/after' on Unix). Filetype specific snippets can be defined +" in '~/.vim/after/ftplugin/<filetype>_snippets.vim. Using the <buffer> argument will +" By default snippets are buffer specific. To define general snippets available +" globally use the 'Iabbr' command. +" +" Example One: +" Snippet fori for <{datum}> in <{data}>:<CR><{datum}>.<{}> +" +" The above will expand to the following (indenting may differ): +" +" for <{datum}> in <{data}>: +" <{datum}>.<{}> +" +" The cursor will be placed after the first '<{' in insert mode. +" Pressing <Tab> will 'tab' to the next place marker (<{data}>) in +" insert mode. Adding text between <{ and }> and then hitting <{Tab}> will +" remove the angle brackets and replace all markers with a similar identifier. +" +" Example Two: +" With the cursor at the pipe, hitting <Tab> will replace: +" for <{MyVariableName|datum}> in <{data}>: +" <{datum}>.<{}> +" +" with (the pipe shows the cursor placement): +" +" for MyVariableName in <{data}>: +" MyVariableName.<{}> +" +" Enjoy. +" +" For more information please see the documentation accompanying this plugin. +" +" Additional Features: +" +" Commands in tags. Anything after a ':' in a tag will be run with Vim's +" 'execute' command. The value entered by the user (or the tag name if no change +" has been made) is passed in the @z register (the original contents of the +" register are restored once the command has been run). +" +" Named Tags. Naming a tag (the <{datum}> tag in the example above) and changing +" the value will cause all other tags with the same name to be changed to the +" same value (as illustrated in the above example). Not changing the value and +" hitting <Tab> will cause the tag's name to be used as the default value. +" +" Test tags for pattern matching: +" The following are examples of valid and invalid tags. Whitespace can only be +" used in a tag name if the name is enclosed in quotes. +" +" Valid tags +" <{}> +" <{tagName}> +" <{tagName:command}> +" <{"Tag Name"}> +" <{"Tag Name":command}> +" +" Invalid tags, random text +" <{:}> +" <{:command}> +" <{Tag Name}> +" <{Tag Name:command}> +" <{"Tag Name":}> +" <{Tag }> +" <{OpenTag +" +" Here's our magic search term (assumes '<{',':' and '}>' as our tag delimiters: +" <{\([^[:punct:] \t]\{-}\|".\{-}"\)\(:[^}>]\{-1,}\)\?}> +" }}} + +if v:version < 700 + echomsg "snippetsEmu plugin requires Vim version 7 or later" + finish +endif + +if globpath(&rtp, 'plugin/snippetEmu.vim') != "" + 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'") +endif + +let s:debug = 0 +let s:Disable = 0 + +function! s:Debug(func, text) + if exists('s:debug') && s:debug == 1 + echom "Snippy: ".a:func.": ".a:text + endif +endfunction + +if (exists('loaded_snippet') || &cp) && !s:debug + finish +endif + +"call s:Debug("","Started the plugin") + +let loaded_snippet=1 +" {{{ Set up variables +if !exists("g:snip_start_tag") + let g:snip_start_tag = "<{" +endif + +if !exists("g:snip_end_tag") + let g:snip_end_tag = "}>" +endif + +if !exists("g:snip_elem_delim") + let g:snip_elem_delim = ":" +endif + +if !exists("g:snippetsEmu_key") + let g:snippetsEmu_key = "<Tab>" +endif + +"call s:Debug("", "Set variables") + +" }}} +" {{{ Set up menu +for def_file in split(globpath(&rtp, "after/ftplugin/*_snippets.vim"), '\n') + "call s:Debug("","Adding ".def_file." definitions to menu") + let snip = substitute(def_file, '.*[\\/]\(.*\)_snippets.vim', '\1', '') + exec "nmenu <silent> S&nippets.".snip." :source ".def_file."<CR>" +endfor +" }}} +" {{{ Sort out supertab +function! s:GetSuperTabSNR() + let a_sav = @a + redir @a + exec "silent function" + redir END + let funclist = @a + let @a = a_sav + try + let func = split(split(matchstr(funclist,'.SNR.\{-}SuperTab(command)'),'\n')[-1])[1] + return matchlist(func, '\(.*\)S')[1] + catch /E684/ + endtry + return "" +endfunction + +function! s:SetupSupertab() + if !exists('s:supInstalled') + let s:supInstalled = 0 + endif + if s:supInstalled == 1 || globpath(&rtp, 'plugin/supertab.vim') != "" + "call s:Debug("SetupSupertab", "Supertab installed") + let s:SupSNR = s:GetSuperTabSNR() + let s:supInstalled = 1 + if s:SupSNR != "" + let s:done_remap = 1 + else + let s:done_remap = 0 + endif + endif +endfunction + +call s:SetupSupertab() +" }}} +" {{{ Map Jumper to the default key if not set already +function! s:SnipMapKeys() + if (!hasmapto('<Plug>Jumper','i')) + if s:supInstalled == 1 + exec 'imap '.g:snippetsEmu_key.' <Plug>Jumper' + else + exec 'imap <unique> '.g:snippetsEmu_key.' <Plug>Jumper' + endif + endif + + if (!hasmapto( 'i<BS>'.g:snippetsEmu_key, 's')) + exec 'smap <unique> '.g:snippetsEmu_key.' i<BS>'.g:snippetsEmu_key + endif + imap <silent> <script> <Plug>Jumper <C-R>=<SID>Jumper()<CR> +endfunction + +call s:SnipMapKeys() + +"call s:Debug("", "Mapped keys") + +" }}} +" {{{ SetLocalTagVars() +function! s:SetLocalTagVars() + if exists("b:snip_end_tag") && exists("b:snip_start_tag") && exists("b:snip_elem_delim") + return [b:snip_start_tag, b:snip_elem_delim, b:snip_end_tag] + else + return [g:snip_start_tag, g:snip_elem_delim, g:snip_end_tag] + endif +endfunction +" }}} +" {{{ SetSearchStrings() - Set the search string. Checks for buffer dependence +function! s:SetSearchStrings() + let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars() + let b:search_str = snip_start_tag.'\([^'. + \snip_start_tag.snip_end_tag. + \'[:punct:] \t]\{-}\|".\{-}"\)\('. + \snip_elem_delim. + \'[^'.snip_end_tag.snip_start_tag.']\{-1,}\)\?'.snip_end_tag + let b:search_commandVal = "[^".snip_elem_delim."]*" + let b:search_endVal = "[^".snip_end_tag."]*" +endfunction +" }}} +" {{{ SetCom(text, scope) - Set command function +function! <SID>SetCom(text, scope) + let text = substitute(a:text, '\c<CR>\|<Esc>\|<Tab>\|<BS>\|<Space>\|<C-r>\|<Bar>\|\"\|\\','\\&',"g") + + if s:supInstalled == 1 + call s:SetupSupertab() + call s:SnipMapKeys() + endif + + let text = substitute(text, "\r$", "","") + + let tokens = split(text, ' ') + call filter(tokens, 'v:val != ""') + if len(tokens) == 0 + let output = join(s:ListSnippets("","","",eval(a:scope)) ,"\n") + if output == "" + echohl Title | echo "No snippets defined" | echohl None + else + echohl Title | echo "Defined snippets:" | echohl None + echo output + endif + " NOTE - cases such as ":Snippet if " will intentionally(?) be parsed as a + " snippet named "if" with contents of " " + elseif len(tokens) == 1 + let snip = s:Hash(tokens[0]) + if exists(a:scope."trigger_".snip) + " FIXME - is there a better approach? + " echo doesn't handle ^M correctly + let pretty = substitute(eval(a:scope."trigger_".snip), "\r", "\n","g") + echo pretty + else + echohl Error | echo "Undefined snippet: ".snip | echohl None + endif + else + let [lhs, rhs] = [s:Hash(tokens[0]), join(tokens[1:])] + call s:SetSearchStrings() + let g:search_str = b:search_str + exe "let ".a:scope."trigger_".lhs.' = "'.rhs.'"' + endif +endfunction +" }}} +" {{{ RestoreSearch() +" Checks whether more tags exist and restores hlsearch and @/ if not +function! s:RestoreSearch() + if !search(b:search_str, "n") + if exists("b:hl_on") && b:hl_on == 1 + setlocal hlsearch + endif + if exists("b:search_sav") + let @/ = b:search_sav + endif + endif +endfunction +"}}} +" {{{ DeleteEmptyTag +function! s:DeleteEmptyTag() + let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars() + exec "normal zv".(s:StrLen(snip_start_tag) + s:StrLen(snip_end_tag))."x" +endfunction +" }}} +" {{{ SetUpTags() +function! s:SetUpTags() + let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars() + if (strpart(getline("."), col(".")+strlen(snip_start_tag)-1, strlen(snip_end_tag)) == snip_end_tag) + "call s:Debug("SetUpTags","Found an empty tag") + let b:tag_name = "" + if col(".") + s:StrLen(snip_start_tag.snip_end_tag) == s:StrLen(getline(".")) + " We delete the empty tag here as otherwise we can't determine whether we + " need to send 'a' or 'A' as deleting the empty tag will sit us on the + " final character either way + call s:DeleteEmptyTag() + call s:RestoreSearch() + if col(".") == s:StrLen(getline(".")) + return "\<Esc>a" + endif + else + call s:DeleteEmptyTag() + call s:RestoreSearch() + if col(".") == s:StrLen(getline(".")) + return "\<Esc>A" + endif + endif + return '' + else + " Not on an empty tag so it must be a normal tag + let b:tag_name = s:ChopTags(matchstr(getline("."),b:search_str,col(".")-1)) + "call s:Debug("SetUpTags","On a tag called: ".b:tag_name) + +" Check for exclusive selection mode. If exclusive is not set then we need to +" move back a character. + if &selection == "exclusive" + let end_skip = "" + else + let end_skip = "\<Left>" + endif + + let start_skip = repeat("\<Right>",s:StrLen(snip_start_tag)+1) + "call s:Debug("SetUpTags","Start skip is: ".start_skip) + "call s:Debug("SetUpTags","Col() is: ".col(".")) + if col(".") == 1 + "call s:Debug("SetUpTags","We're at the start of the line so don't need to skip the first char of start tag") + let start_skip = strpart(start_skip, 0, strlen(start_skip)-strlen("\<Right>")) + "call s:Debug("SetUpTags","Start skip is now: ".start_skip) + endif + "call s:Debug("SetUpTags","Returning: \<Esc>".start_skip."v/".snip_end_tag."\<CR>".end_skip."\<C-g>") + return "\<Esc>".start_skip."v/".snip_end_tag."\<CR>".end_skip."\<C-g>" + endif +endfunction +" }}} +" {{{ NextHop() - Jump to the next tag if one is available +function! <SID>NextHop() + let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars() + "call s:Debug("NextHop", "Col() is: ".col(".")) + "call s:Debug("NextHop", "Position of next match = ".match(getline("."), b:search_str)) + " First check to see if we have any tags on lines above the current one + " If the first match is after the current cursor position or not on this + " line... + if match(getline("."), b:search_str) >= col(".") || match(getline("."), b:search_str) == -1 + " Perform a search to jump to the next tag + "call s:Debug("NextHop", "Seaching for a tag") + if search(b:search_str) != 0 + return s:SetUpTags() + else + " there are no more matches + "call s:Debug("NextHop", "No more tags in the buffer") + " Restore hlsarch and @/ + call s:RestoreSearch() + return '' + endif + else + " The match on the current line is on or before the cursor, so we need to + " move the cursor back + "call s:Debug("NextHop", "Moving the cursor back") + "call s:Debug("NextHop", "Col is: ".col(".")) + "call s:Debug("NextHop", "Moving back to column: ".match(getline("."), b:search_str)) + while col(".") > match(getline("."), b:search_str) + 1 + call cursor(0,col('.')-1) + endwhile + "call s:Debug("NextHop", "Col is now: ".col(".")) + " Now we just set up the tag as usual + return s:SetUpTags() + endif +endfunction +" }}} +" {{{ RunCommand() - Execute commands stored in tags +function! s:RunCommand(command, z) + let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars() + "call s:Debug("RunCommand", "RunCommand was passed this command: ".a:command." and this value: ".a:z) + if a:command == '' + return a:z + endif + " Save current value of 'z' + let snip_save = @z + let @z=a:z + " Call the command + execute 'let ret = '. a:command + " Replace the value + let @z = snip_save + return ret +endfunction +" }}} +" {{{ MakeChanges() - Search the document making all the changes required +" This function has been factored out to allow the addition of commands in tags +function! s:MakeChanges() + " Make all the changes + " Change all the tags with the same name and no commands defined + let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars() + + if b:tag_name == "" + "call s:Debug("MakeChanges", "Nothing to do: tag_name is empty") + return + endif + + let tagmatch = '\V'.snip_start_tag.b:tag_name.snip_end_tag + + "call s:Debug("MakeChanges", "Matching on this value: ".tagmatch) + "call s:Debug("MakeChanges", "Replacing with this value: ".s:replaceVal) + + try + "call s:Debug("MakeChanges", "Running these commands: ".join(b:command_dict[b:tag_name], "', '")) + catch /E175/ + "call s:Debug("MakeChanges", "Could not find this key in the dict: ".b:tag_name) + endtry + + let ind = 0 + while search(tagmatch,"w") > 0 + try + let commandResult = s:RunCommand(b:command_dict[b:tag_name][0], s:replaceVal) + catch /E175/ + "call s:Debug("MakeChanges", "Could not find this key in the dict: ".b:tag_name) + endtry + "call s:Debug("MakeChanges", "Got this result: ".commandResult) + let lines = split(substitute(getline("."), tagmatch, commandResult, ''),'\n') + if len(lines) > 1 + call setline(".", lines[0]) + call append(".", lines[1:]) + else + call setline(".", lines) + endif + try + unlet b:command_dict[b:tag_name][0] + catch /E175/ + "call s:Debug("MakeChanges", "Could not find this key in the dict: ".b:tag_name) + endtry + endwhile +endfunction +" }}} +" {{{ ChangeVals() - Set up values for MakeChanges() +function! s:ChangeVals(changed) + let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars() + + if a:changed == 1 + let s:CHANGED_VAL = 1 + else + let s:CHANGED_VAL = 0 + endif + + "call s:Debug("ChangeVals", "CHANGED_VAL: ".s:CHANGED_VAL) + "call s:Debug("ChangeVals", "b:tag_name: ".b:tag_name) + let elem_match = match(s:line, snip_elem_delim, s:curCurs) + let tagstart = strridx(getline("."), snip_start_tag,s:curCurs)+strlen(snip_start_tag) + + "call s:Debug("ChangeVals", "About to access b:command_dict") + try + let commandToRun = b:command_dict[b:tag_name][0] + "call s:Debug("ChangeVals", "Accessed command_dict") + "call s:Debug("ChangeVals", "Running this command: ".commandToRun) + unlet b:command_dict[b:tag_name][0] + "call s:Debug("ChangeVals", "Command list is now: ".join(b:command_dict[b:tag_name], "', '")) + catch /E175/ + "call s:Debug("ChangeVals", "Could not find this key in the dict: ".b:tag_name) + endtry + + let commandMatch = substitute(commandToRun, '\', '\\\\', 'g') + if s:CHANGED_VAL + " The value has changed so we need to grab our current position back + " to the start of the tag + let replaceVal = strpart(getline("."), tagstart,s:curCurs-tagstart) + "call s:Debug("ChangeVals", "User entered this value: ".replaceVal) + let tagmatch = replaceVal + "call s:Debug("ChangeVals", "Col is: ".col(".")) + call cursor(0,col('.')-s:StrLen(tagmatch)) + "call s:Debug("ChangeVals", "Col is: ".col(".")) + else + " The value hasn't changed so it's just the tag name + " without any quotes that are around it + "call s:Debug("ChangeVals", "Tag name is: ".b:tag_name) + let replaceVal = substitute(b:tag_name, '^"\(.*\)"$', '\1', '') + "call s:Debug("ChangeVals", "User did not enter a value. Replacing with this value: ".replaceVal) + let tagmatch = '' + "call s:Debug("ChangeVals", "Col is: ".col(".")) + endif + + let tagmatch = '\V'.snip_start_tag.tagmatch.snip_end_tag + "call s:Debug("ChangeVals", "Matching on this string: ".tagmatch) + let tagsubstitution = s:RunCommand(commandToRun, replaceVal) + let lines = split(substitute(getline("."), tagmatch, tagsubstitution, ""),'\n') + if len(lines) > 1 + call setline(".", lines[0]) + call append(".", lines[1:]) + else + call setline(".", lines) + endif + " We use replaceVal instead of tagsubsitution as otherwise the command + " result will be passed to subsequent tags + let s:replaceVal = replaceVal + let line = line('.') + let col = col('.') + call s:MakeChanges() + call cursor(line, col) + unlet s:CHANGED_VAL +endfunction +" }}} +"{{{ SID() - Get the SID for the current script +function! s:SID() + return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$') +endfun +"}}} +"{{{ CheckForInTag() - Check whether we're in a tag +function! s:CheckForInTag() + let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars() + if snip_start_tag != snip_end_tag + " The tags are different so we can check to see whether the + " end tag comes before a start tag + let s:startMatch = match(s:line, '\V'.snip_start_tag, s:curCurs) + let s:endMatch = match(s:line, '\V'.snip_end_tag, s:curCurs) + + if s:endMatch != -1 && ((s:endMatch < s:startMatch) || s:startMatch == -1) + " End has come before start so we're in a tag. + return 1 + else + return 0 + endif + else + " Start and end tags are the same so we need do tag counting to see + " whether we're in a tag. + let s:count = 0 + let s:curSkip = s:curCurs + while match(strpart(s:line,s:curSkip),snip_start_tag) != -1 + if match(strpart(s:line,s:curSkip),snip_start_tag) == 0 + let s:curSkip = s:curSkip + 1 + else + let s:curSkip = s:curSkip + 1 + match(strpart(s:line,s:curSkip),snip_start_tag) + endif + let s:count = s:count + 1 + endwhile + if (s:count % 2) == 1 + " Odd number of tags implies we're inside a tag. + return 1 + else + " We're not inside a tag. + return 0 + endif + endif +endfunction +"}}} +" {{{ SubSpecialVars(text) +function! s:SubSpecialVars(text) + let text = a:text + let text = substitute(text, 'SNIP_FILE_NAME', expand('%'), 'g') + let text = substitute(text, 'SNIP_ISO_DATE', strftime("%Y-%m-%d"), 'g') + return text +endfunction +" }}} +" {{{ SubCommandOutput(text) +function! s:SubCommandOutput(text) + let search = '``.\{-}``' + let text = a:text + while match(text, search) != -1 + let command_match = matchstr(text, search) + "call s:Debug("SubCommandOutput", "Command found: ".command_match) + let command = substitute(command_match, '^..\(.*\)..$', '\1', '') + "call s:Debug("SubCommandOutput", "Command being run: ".command) + exec 'let output = '.command + let output = escape(output, '\') + let text = substitute(text, '\V'.escape(command_match, '\'), output, '') + endwhile + let text = substitute(text, '\\`\\`\(.\{-}\)\\`\\`','``\1``','g') + return text +endfunction +" }}} +" {{{ RemoveAndStoreCommands(text) +function! s:RemoveAndStoreCommands(text) + let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars() + + let text = a:text + if !exists("b:command_dict") + let b:command_dict = {} + endif + + let tmp_command_dict = {} + try + let ind = match(text, b:search_str) + catch /E55: Unmatched \\)/ + 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.") + let s:Disable = 1 + return '' + endtry + while ind > -1 + "call s:Debug("RemoveAndStoreCommands", "Text is: ".text) + "call s:Debug("RemoveAndStoreCommands", "index is: ".ind) + let tag = matchstr(text, b:search_str, ind) + "call s:Debug("RemoveAndStoreCommands", "Tag is: ".tag) + let commandToRun = matchstr(tag, snip_elem_delim.".*".snip_end_tag) + + if commandToRun != '' + let tag_name = strpart(tag,strlen(snip_start_tag),match(tag,snip_elem_delim)-strlen(snip_start_tag)) + "call s:Debug("RemoveAndStoreCommands", "Got this tag: ".tag_name) + "call s:Debug("RemoveAndStoreCommands", "Adding this command: ".commandToRun) + if tag_name != '' + if has_key(tmp_command_dict, tag_name) + call add(tmp_command_dict[tag_name], strpart(commandToRun, 1, strlen(commandToRun)-strlen(snip_end_tag)-1)) + else + let tmp_command_dict[tag_name] = [strpart(commandToRun, 1, strlen(commandToRun)-strlen(snip_end_tag)-1)] + endif + endif + let text = substitute(text, '\V'.escape(commandToRun,'\'), snip_end_tag,'') + else + let tag_name = s:ChopTags(tag) + if tag_name != '' + if has_key(tmp_command_dict, tag_name) + call add(tmp_command_dict[tag_name], '') + else + let tmp_command_dict[tag_name] = [''] + endif + endif + endif + "call s:Debug("RemoveAndStoreCommands", "".tag." found at ".ind) + let ind = match(text, b:search_str, ind+strlen(snip_end_tag)) + endwhile + + for key in keys(tmp_command_dict) + if has_key(b:command_dict, key) + for item in reverse(tmp_command_dict[key]) + call insert(b:command_dict[key], item) + endfor + else + let b:command_dict[key] = tmp_command_dict[key] + endif + endfor + return text +endfunction +" }}} +" {{{ ReturnKey() - Return our mapped key or Supertab key +function! s:ReturnKey() + if s:supInstalled + "call s:Debug('ReturnKey', 'Snippy: SuperTab installed. Returning <C-n> instead of <Tab>') + return "\<C-R>=".s:SupSNR."SuperTab('n')\<CR>" + else + " We need this hacky line as the one below doesn't seem to work. + " Patches welcome + exe "return \"".substitute(g:snippetsEmu_key, '^<', "\\\\<","")."\"" + "return substitute(g:snippetsEmu_key, '^<', "\\<","") + endif +endfunction +" }}} +" {{{ Jumper() +" We need to rewrite this function to reflect the new behaviour. Every jump +" will now delete the markers so we need to allow for the following conditions +" 1. Empty tags e.g. "<{}>". When we land inside then we delete the tags. +" "<{:}>" is now an invalid tag (use "<{}>" instead) so we don't need to check for +" this +" 2. Tag with variable name. Save the variable name for the next jump. +" 3. Tag with command. Tags no longer have default values. Everything after the +" centre delimiter until the end tag is assumed to be a command. +" +" Jumper is performed when we want to perform a jump. If we've landed in a +" 1. style tag then we'll be in free form text and just want to jump to the +" next tag. If we're in a 2. or 3. style tag then we need to look for whether +" the value has changed and make all the replacements. If we're in a 3. +" style tag then we need to replace all the occurrences with their command +" modified values. +" +function! <SID>Jumper() + if s:Disable == 1 + return substitute(g:snippetsEmu_key, '^<', "\\<",'') + endif + let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars() + + " Set up some mapping in case we got called before Supertab + if s:supInstalled == 1 && s:done_remap != 1 + call s:SetupSupertab() + call s:SnipMapKeys() + endif + + if !exists('b:search_str') && exists('g:search_str') + let b:search_str = g:search_str + endif + + if !exists('b:search_str') + return s:ReturnKey() + endif + + let s:curCurs = col(".") - 1 + let s:curLine = line(".") + let s:line = getline(".") + let s:replaceVal = "" + + " First we'll check that the user hasn't just typed a snippet to expand + let origword = matchstr(strpart(getline("."), 0, s:curCurs), '\(^\|\s\)\S\{-}$') + let origword = substitute(origword, '\s', "", "") + "call s:Debug("Jumper", "Original word was: ".origword) + let word = s:Hash(origword) + " The following code is lifted from the imaps.vim script - Many + " thanks for the inspiration to add the TextMate compatibility + let rhs = '' + let found = 0 + " Check for buffer specific expansions + if exists('b:trigger_'.word) + exe 'let rhs = b:trigger_'.word + let found = 1 + elseif exists('g:trigger_'.word) + " also check for global definitions + exe 'let rhs = g:trigger_'.word + let found = 1 + endif + + if found == 0 + " Check using keyword boundary + let origword = matchstr(strpart(getline("."), 0, s:curCurs), '\k\{-}$') + "call s:Debug("Jumper", "Original word was: ".origword) + let word = s:Hash(origword) + if exists('b:trigger_'.word) + exe 'let rhs = b:trigger_'.word + elseif exists('g:trigger_'.word) + " also check for global definitions + exe 'let rhs = g:trigger_'.word + endif + endif + + if rhs != '' + " Save the value of hlsearch + if &hls + "call s:Debug("Jumper", "Hlsearch set") + setlocal nohlsearch + let b:hl_on = 1 + else + "call s:Debug("Jumper", "Hlsearch not set") + let b:hl_on = 0 + endif + " Save the last search value + let b:search_sav = @/ + " Count the number of lines in the rhs + let move_up = "" + if len(split(rhs, "\<CR>")) - 1 != 0 + let move_up = len(split(rhs, "\<CR>")) - 1 + let move_up = move_up."\<Up>" + endif + + " If this is a mapping, then erase the previous part of the map + " by returning a number of backspaces. + let bkspc = substitute(origword, '.', "\<BS>", "g") + "call s:Debug("Jumper", "Backspacing ".s:StrLen(origword)." characters") + let delEndTag = "" + if s:CheckForInTag() + "call s:Debug("Jumper", "We're doing a nested tag") + "call s:Debug("Jumper", "B:tag_name: ".b:tag_name) + if b:tag_name != '' + try + "call s:Debug("Jumper", "Commands for this tag are currently: ".join(b:command_dict[b:tag_name],"', '")) + "call s:Debug("Jumper", "Removing command for '".b:tag_name."'") + unlet b:command_dict[b:tag_name][0] + "call s:Debug("Jumper", "Commands for this tag are now: ".join(b:command_dict[b:tag_name],"', '")) + catch /E175/ + "call s:Debug("Jumper", "Could not find this key in the dict: ".b:tag_name) + endtry + endif + "call s:Debug("Jumper", "Deleting start tag") + let bkspc = bkspc.substitute(snip_start_tag, '.', "\<BS>", "g") + "call s:Debug("Jumper", "Deleting end tag") + let delEndTag = substitute(snip_end_tag, '.', "\<Del>", "g") + "call s:Debug("Jumper", "Deleting ".s:StrLen(delEndTag)." characters") + endif + + " We've found a mapping so we'll substitute special variables + let rhs = s:SubSpecialVars(rhs) + let rhs = s:SubCommandOutput(rhs) + " Now we'll chop out the commands from tags + let rhs = s:RemoveAndStoreCommands(rhs) + if s:Disable == 1 + return substitute(g:snippetsEmu_key, '^<', "\\<",'') + endif + + " Save the value of 'backspace' + let bs_save = &backspace + set backspace=indent,eol,start + return bkspc.delEndTag.rhs."\<Esc>".move_up."^:set backspace=".bs_save."\<CR>a\<C-r>=<SNR>".s:SID()."_NextHop()\<CR>" + else + " No definition so let's check to see whether we're in a tag + if s:CheckForInTag() + "call s:Debug("Jumper", "No mapping and we're in a tag") + " We're in a tag so we need to do processing + if strpart(s:line, s:curCurs - strlen(snip_start_tag), strlen(snip_start_tag)) == snip_start_tag + "call s:Debug("Jumper", "Value not changed") + call s:ChangeVals(0) + else + "call s:Debug("Jumper", "Value changed") + call s:ChangeVals(1) + endif + return "\<C-r>=<SNR>".s:SID()."_NextHop()\<CR>" + else + " We're not in a tag so we'll see whether there are more tags + if search(b:search_str, "n") + " More tags so let's perform nexthop + let s:replaceVal = "" + return "\<C-r>=<SNR>".s:SID()."_NextHop()\<CR>" + else + " No more tags so let's return a Tab after restoring hlsearch and @/ + call s:RestoreSearch() + if exists("b:command_dict") + unlet b:command_dict + endif + return s:ReturnKey() + endif + endif + endif +endfunction +" }}} +"{{{ ListSnippets() - Return a list of snippets - used for command completion +function! s:ListSnippets(ArgLead, CmdLine, CursorPos, scope) + " Only allow completion for the second argument + " TODO + return sort(map(map(filter(keys(a:scope), 'v:val =~ "^trigger_'.a:ArgLead.'"'), 'v:val[8:]'), 's:UnHash(v:val)')) +endfunction + +function! s:ListBufferSnippets(ArgLead, CmdLine, CursorPos) + return s:ListSnippets(a:ArgLead, a:CmdLine, a:CursorPos, b:) +endfunction + +function! s:ListGlobalSnippets(ArgLead, CmdLine, CursorPos) + return s:ListSnippets(a:ArgLead, a:CmdLine, a:CursorPos, g:) +endfunction +" }}} +" {{{ DelSnippet() - Delete a snippet +function! s:DelSnippet(snippet, scope) + if a:snippet != "" + try + exec "unlet ".a:scope."trigger_".s:Hash(a:snippet) + catch /E108: No such variable:/ + echom "Snippet '".a:snippet."' does not exist." + endtry + endif +endfunction +" }}} +" {{{ Set up the 'Iabbr' and 'Snippet' commands +"command! -nargs=+ Iabbr execute s:SetCom(<q-args>) +"command! -nargs=+ Snippet execute s:SetCom("<buffer> ".<q-args>) +command! -complete=customlist,s:ListGlobalSnippets -nargs=* + \ Iabbr call <SID>SetCom(<q-args>, "g:") +command! -complete=customlist,s:ListBufferSnippets -nargs=* + \ Snippet call <SID>SetCom(<q-args>, "b:") +command! -range CreateSnippet <line1>,<line2>call s:CreateSnippet() +command! -range CreateBundleSnippet <line1>,<line2>call s:CreateBundleSnippet() +command! -complete=customlist,s:ListBufferSnippets -nargs=* + \ DelSnippet call <SID>DelSnippet(<q-args>, "b:") +command! -complete=customlist,s:ListGlobalSnippets -nargs=* + \ DelIabbr call <SID>DelSnippet(<q-args>, "g:") +"}}} +" {{{ Utility functions + +" This function will convert the selected range into a snippet +function! s:CreateSnippet() range + let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars() + let snip = "" + if &expandtab + let tabs = indent(a:firstline)/&shiftwidth + let tabstr = repeat(' ',&shiftwidth) + else + let tabs = indent(a:firstline)/&tabstop + let tabstr = '\t' + endif + let tab_text = repeat(tabstr,tabs) + + for i in range(a:firstline, a:lastline) + "First chop off the indent + let text = substitute(getline(i),tab_text,'','') + "Now replace 'tabs' with <Tab>s + let text = substitute(text, tabstr, '<Tab>','g') + "And trim the newlines + let text = substitute(text, "\r", '','g') + let snip = snip.text.'<CR>' + endfor + let tag = snip_start_tag.snip_end_tag + let split_sav = &swb + set swb=useopen + if bufexists("Snippets") + belowright sb Snippets + else + belowright sp Snippets + endif + resize 8 + setlocal buftype=nofile + setlocal bufhidden=hide + setlocal noswapfile + let @"=tag + exe 'set swb='.split_sav + let trig = inputdialog("Please enter the trigger word for your snippet: ", "My_snippet") + if trig == "" + let trig = "YOUR_SNIPPET_NAME_HERE" + endif + call append("$", "Snippet ".trig." ".snip) + if getline(1) == "" + 1 delete _ + endif + call cursor(line('$'),1) +endfunction + +" This function will convert the selected range into a snippet suitable for +" including in a bundle. +function! s:CreateBundleSnippet() range + let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars() + let snip = "" + if &expandtab + let tabs = indent(a:firstline)/&shiftwidth + let tabstr = repeat(' ',&shiftwidth) + else + let tabs = indent(a:firstline)/&tabstop + let tabstr = '\t' + endif + let tab_text = repeat(tabstr,tabs) + + for i in range(a:firstline, a:lastline) + let text = substitute(getline(i),tab_text,'','') + let text = substitute(text, tabstr, '<Tab>','g') + let text = substitute(text, "\r$", '','g') + let text = substitute(text, '"', '\\"','g') + let text = substitute(text, '|', '<Bar>','g') + let snip = snip.text.'<CR>' + endfor + let tag = '".st.et."' + let split_sav = &swb + set swb=useopen + if bufexists("Snippets") + belowright sb Snippets + else + belowright sp Snippets + endif + resize 8 + setlocal buftype=nofile + setlocal bufhidden=hide + setlocal noswapfile + let @"=tag + exe 'set swb='.split_sav + let trig = inputdialog("Please enter the trigger word for your snippet: ", "My_snippet") + if trig == "" + let trig = "YOUR_SNIPPET_NAME_HERE" + endif + call append("$", 'exe "Snippet '.trig." ".snip.'"') + if getline(1) == "" + 1 delete _ + endif + call cursor(line('$'),1) +endfunction + +" This function will just return what's passed to it unless a change has been +" made +fun! D(text) + if exists('s:CHANGED_VAL') && s:CHANGED_VAL == 1 + return @z + else + return a:text + endif +endfun + +" s:Hash allows the use of special characters in snippets +" This function is lifted straight from the imaps.vim plugin. Please let me know +" if this is against licensing. +function! s:Hash(text) + return substitute(a:text, '\([^[:alnum:]]\)', + \ '\="_".char2nr(submatch(1))."_"', 'g') +endfunction + +" s:UnHash allows the use of special characters in snippets +" This function is lifted straight from the imaps.vim plugin. Please let me know +" if this is against licensing. +function! s:UnHash(text) + return substitute(a:text, '_\(\d\+\)_', + \ '\=nr2char(submatch(1))', 'g') +endfunction + +" This function chops tags from any text passed to it +function! s:ChopTags(text) + let text = a:text + "call s:Debug("ChopTags", "ChopTags was passed this text: ".text) + let [snip_start_tag, snip_elem_delim, snip_end_tag] = s:SetLocalTagVars() + let text = strpart(text, strlen(snip_start_tag)) + let text = strpart(text, 0, strlen(text)-strlen(snip_end_tag)) + "call s:Debug("ChopTags", "ChopTags is returning this text: ".text) + return text +endfunction + +" This function ensures we measure string lengths correctly +function! s:StrLen(str) + "call s:Debug("StrLen", "StrLen returned: ".strlen(substitute(a:str, '.', 'x', 'g'))." based on this text: ".a:str) + return strlen(substitute(a:str, '.', 'x', 'g')) +endfunction + +" }}} +" vim: set tw=80 sw=2 sts=2 et foldmethod=marker : |