diff --git a/doc/UltiSnips.txt b/doc/UltiSnips.txt index 9a99a38..4ebe29f 100644 --- a/doc/UltiSnips.txt +++ b/doc/UltiSnips.txt @@ -121,17 +121,24 @@ To Update an installation, simply pull the latest revision: > 3.1 Commands *UltiSnips-commands* ------------ -UltiSnips defines two Commands, the first one is UltiSnipsReset, which will + *:UltiSnipsReset* +UltiSnips defines two commands, the first one is UltiSnipsReset, which will reload the snippets definitions and is useful while you are tweaking a snippet. -UltiSnipsEdit is the second one. It opens your private snippet definition file -for the current filetype. You can easily open them manually of course, this is -just a shortcut. There is also a variable called: > + *:UltiSnipsEdit* +The second command is UltiSnipsEdit. It opens or creates your private snippet +definition file for the current filetype. You can easily open them manually of +course, this is just a shortcut. There is also a variable called: > g:UltiSnipsEditSplit - which can be set to "normal" (default), "horizontal" or "vertical" that -defines if a new window should be opened for the edit. +defines if a new window should be opened for the edit. There is also a +variable called: > + g:UltiSnipsSnippetsDir +that, when set to a directory like "~/.vim/mydir/UltiSnips", becomes the base +directory for the snippet file to open. For example, if it is set to +"~/.vim/mydir/UltiSnips" and the current 'filetype' is "cpp", then +:UltiSnipsEdit will open "~/.vim/mydir/UltiSnips/cpp.snippets". 3.2 Triggers *UltiSnips-triggers* ------------ @@ -237,9 +244,11 @@ See |UltiSnips-snippet-search-path| for an explanation of where directories with snippet definitions are expected. While iterating over the snippet definition directories found, files are -looked for called ft.snippets, for example: > +looked for called ft.snippets or *_ft.snippets where "ft" is the current +'filetype', and the "*" matches anything, for example: > ruby.snippets c.snippets + my_c.snippets perl.snippets These files contain the snippet definitions for the various file types. A special file is > @@ -248,7 +257,7 @@ which contains snippets that are always expanded, no matter what file type is defined. For example, I keep mail signatures and date insertion snippets here. The dotted file type syntax of vim is supported. For example, for my cpp or -CUDA files, i keep the file type set to ":set ft=cpp.c" or ":set +CUDA files, I keep the file type set to ":set ft=cpp.c" or ":set ft=cuda.cpp.c". This activates all snippets for each file type in the order specified. diff --git a/plugin/UltiSnips.vim b/plugin/UltiSnips.vim index e742a61..098efbd 100644 --- a/plugin/UltiSnips.vim +++ b/plugin/UltiSnips.vim @@ -17,46 +17,46 @@ endif " The trigger used to expand a snippet. " NOTE: expansion and forward jumping can, but needn't be the same trigger if !exists("g:UltiSnipsExpandTrigger") - let g:UltiSnipsExpandTrigger = "" + let g:UltiSnipsExpandTrigger = "" endif " The trigger used to display all triggers that could possible " match in the current position. if !exists("g:UltiSnipsListSnippets") - let g:UltiSnipsListSnippets = "" + let g:UltiSnipsListSnippets = "" endif " The trigger used to jump forward to the next placeholder. " NOTE: expansion and forward jumping can, but needn't be the same trigger if !exists("g:UltiSnipsJumpForwardTrigger") - let g:UltiSnipsJumpForwardTrigger = "" + let g:UltiSnipsJumpForwardTrigger = "" endif " The trigger to jump backward inside a snippet if !exists("g:UltiSnipsJumpBackwardTrigger") - let g:UltiSnipsJumpBackwardTrigger = "" + let g:UltiSnipsJumpBackwardTrigger = "" endif " Should UltiSnips unmap select mode mappings automagically? if !exists("g:UltiSnipsRemoveSelectModeMappings") - let g:UltiSnipsRemoveSelectModeMappings = 1 + let g:UltiSnipsRemoveSelectModeMappings = 1 end " If UltiSnips should remove Mappings, which should be ignored if !exists("g:UltiSnipsMappingsToIgnore") - let g:UltiSnipsMappingsToIgnore = [] + let g:UltiSnipsMappingsToIgnore = [] endif " UltiSnipsEdit will use this variable to decide if a new window " is opened when editing. default is "normal", allowed are also " "vertical", "horizontal" if !exists("g:UltiSnipsEditSplit") - let g:UltiSnipsEditSplit = 'normal' + let g:UltiSnipsEditSplit = 'normal' endif " A list of directory names that are searched for snippets. if !exists("g:UltiSnipsSnippetDirectories") - let g:UltiSnipsSnippetDirectories = [ "UltiSnips" ] + let g:UltiSnipsSnippetDirectories = [ "UltiSnips" ] endif " }}} @@ -66,35 +66,23 @@ endif command! -nargs=0 UltiSnipsReset :py UltiSnips_Manager.reset() function! UltiSnipsEdit(...) - if a:0 == 1 && a:1 != '' - let type = a:1 - elseif &filetype != '' - let type = split(&filetype, '\.')[0] - else - let type = 'all' - endif + if a:0 == 1 && a:1 != '' + let type = a:1 + else + python vim.command("let type = '%s'" % UltiSnips_Manager.filetype) + endif - if exists('g:UltiSnipsSnippetsDir') - let mode = 'e' - if exists('g:UltiSnipsEditSplit') - if g:UltiSnipsEditSplit == 'vertical' - let mode = 'vs' - elseif g:UltiSnipsEditSplit == 'horizontal' - let mode = 'sp' - endif - endif - exe ':'.mode.' '.g:UltiSnipsSnippetsDir.'/'.type.'.snippets' - else - for dir in g:UltiSnipsSnippetDirectories - for p in reverse(split(&runtimepath, ',')) - if isdirectory(p.'/'.dir) - let g:UltiSnipsSnippetsDir = p.'/'.dir - call UltiSnipsEdit(type) - break - endif - endfor - endfor - endif + python vim.command("let file = '%s'" % UltiSnips_Manager.file_to_edit(vim.eval("type"))) + + let mode = 'e' + if exists('g:UltiSnipsEditSplit') + if g:UltiSnipsEditSplit == 'vertical' + let mode = 'vs' + elseif g:UltiSnipsEditSplit == 'horizontal' + let mode = 'sp' + endif + endif + exe ':'.mode.' '.file endfunction " edit snippets, default of current file type or the specified type @@ -104,46 +92,46 @@ command! -nargs=? UltiSnipsEdit :call UltiSnipsEdit() "" FUNCTIONS {{{ function! CompensateForPUM() - """ The CursorMovedI event is not triggered while the popup-menu is visible, - """ and it's by this event that UltiSnips updates its vim-state. The fix is - """ to explicitly check for the presence of the popup menu, and update - """ the vim-state accordingly. - if pumvisible() - py UltiSnips_Manager.cursor_moved() - endif + """ The CursorMovedI event is not triggered while the popup-menu is visible, + """ and it's by this event that UltiSnips updates its vim-state. The fix is + """ to explicitly check for the presence of the popup menu, and update + """ the vim-state accordingly. + if pumvisible() + py UltiSnips_Manager.cursor_moved() + endif endfunction function! UltiSnips_ExpandSnippet() - py UltiSnips_Manager.expand() - return "" + py UltiSnips_Manager.expand() + return "" endfunction function! UltiSnips_ExpandSnippetOrJump() - call CompensateForPUM() - py UltiSnips_Manager.expand_or_jump() - return "" + call CompensateForPUM() + py UltiSnips_Manager.expand_or_jump() + return "" endfunction function! UltiSnips_ListSnippets() - py UltiSnips_Manager.list_snippets() - return "" + py UltiSnips_Manager.list_snippets() + return "" endfunction function! UltiSnips_JumpBackwards() - call CompensateForPUM() - py UltiSnips_Manager.jump_backwards() - return "" + call CompensateForPUM() + py UltiSnips_Manager.jump_backwards() + return "" endfunction function! UltiSnips_JumpForwards() - call CompensateForPUM() - py UltiSnips_Manager.jump_forwards() - return "" + call CompensateForPUM() + py UltiSnips_Manager.jump_forwards() + return "" endfunction function! UltiSnips_AddSnippet(trigger, value, descr, options, ...) - " Takes the same arguments as SnippetManager.add_snippet: - " (trigger, value, descr, options, ft = "all", globals = None) + " Takes the same arguments as SnippetManager.add_snippet: + " (trigger, value, descr, options, ft = "all", globals = None) py << EOB args = vim.eval("a:000") trigger = vim.eval("a:trigger") @@ -153,38 +141,38 @@ options = vim.eval("a:options") UltiSnips_Manager.add_snippet(trigger, value, descr, options, *args) EOB - return "" + return "" endfunction function! UltiSnips_Anon(value, ...) - " Takes the same arguments as SnippetManager.expand_anon: - " (value, trigger="", descr="", options="", globals = None) + " Takes the same arguments as SnippetManager.expand_anon: + " (value, trigger="", descr="", options="", globals = None) py << EOB args = vim.eval("a:000") value = vim.eval("a:value") UltiSnips_Manager.expand_anon(value, *args) EOB - return "" + return "" endfunction function! UltiSnips_MapKeys() - " Map the keys correctly - if g:UltiSnipsExpandTrigger == g:UltiSnipsJumpForwardTrigger - exec "inoremap " . g:UltiSnipsExpandTrigger . " =UltiSnips_ExpandSnippetOrJump()" - exec "snoremap " . g:UltiSnipsExpandTrigger . " :call UltiSnips_ExpandSnippetOrJump()" - else - exec "inoremap " . g:UltiSnipsExpandTrigger . " =UltiSnips_ExpandSnippet()" - exec "snoremap " . g:UltiSnipsExpandTrigger . " :call UltiSnips_ExpandSnippet()" - exec "inoremap " . g:UltiSnipsJumpForwardTrigger . " =UltiSnips_JumpForwards()" - exec "snoremap " . g:UltiSnipsJumpForwardTrigger . " :call UltiSnips_JumpForwards()" - endif - exec "inoremap " . g:UltiSnipsJumpBackwardTrigger . " =UltiSnips_JumpBackwards()" - exec "snoremap " . g:UltiSnipsJumpBackwardTrigger . " :call UltiSnips_JumpBackwards()" - exec "inoremap " . g:UltiSnipsListSnippets . " =UltiSnips_ListSnippets()" - exec "snoremap " . g:UltiSnipsListSnippets . " :call UltiSnips_ListSnippets()" + " Map the keys correctly + if g:UltiSnipsExpandTrigger == g:UltiSnipsJumpForwardTrigger + exec "inoremap " . g:UltiSnipsExpandTrigger . " =UltiSnips_ExpandSnippetOrJump()" + exec "snoremap " . g:UltiSnipsExpandTrigger . " :call UltiSnips_ExpandSnippetOrJump()" + else + exec "inoremap " . g:UltiSnipsExpandTrigger . " =UltiSnips_ExpandSnippet()" + exec "snoremap " . g:UltiSnipsExpandTrigger . " :call UltiSnips_ExpandSnippet()" + exec "inoremap " . g:UltiSnipsJumpForwardTrigger . " =UltiSnips_JumpForwards()" + exec "snoremap " . g:UltiSnipsJumpForwardTrigger . " :call UltiSnips_JumpForwards()" + endif + exec "inoremap " . g:UltiSnipsJumpBackwardTrigger . " =UltiSnips_JumpBackwards()" + exec "snoremap " . g:UltiSnipsJumpBackwardTrigger . " :call UltiSnips_JumpBackwards()" + exec "inoremap " . g:UltiSnipsListSnippets . " =UltiSnips_ListSnippets()" + exec "snoremap " . g:UltiSnipsListSnippets . " :call UltiSnips_ListSnippets()" - " Do not remap this. - snoremap :py UltiSnips_Manager.backspace_while_selected() + " Do not remap this. + snoremap :py UltiSnips_Manager.backspace_while_selected() endf " }}} @@ -207,7 +195,8 @@ au CursorMovedI * py UltiSnips_Manager.cursor_moved() au InsertEnter * py UltiSnips_Manager.entered_insert_mode() call UltiSnips_MapKeys() - + let did_UltiSnips_vim=1 " }}} +" vim: ts=8 sts=4 sw=4 diff --git a/plugin/UltiSnips/__init__.py b/plugin/UltiSnips/__init__.py index 393bc6d..4bbc25d 100644 --- a/plugin/UltiSnips/__init__.py +++ b/plugin/UltiSnips/__init__.py @@ -556,10 +556,10 @@ class VimState(object): # Check if any mappings where found all_maps = filter(len, vim.eval(r"_tmp_smaps").splitlines()) if (len(all_maps) == 1 and all_maps[0][0] not in " sv"): - # "No maps found". String could be localized. Hopefully - # it doesn't start with any of these letters in any - # language - continue + # "No maps found". String could be localized. Hopefully + # it doesn't start with any of these letters in any + # language + continue # Only keep mappings that should not be ignored maps = [m for m in all_maps if @@ -870,9 +870,10 @@ class SnippetManager(object): feedkeys(feedkey, mode) def _ensure_snippets_loaded(self): - filetypes = vim.eval("&filetype").split(".") + [ "all" ] - for ft in filetypes[::-1]: - if len(ft) and ft not in self._snippets: + filetypes = self._filetypes() + + for ft in filetypes: + if ft not in self._snippets: self._load_snippets_for(ft) return filetypes @@ -898,7 +899,7 @@ class SnippetManager(object): filetypes = self._ensure_snippets_loaded() found_snippets = [] - for ft in filetypes[::-1]: + for ft in filetypes: found_snippets += self._find_snippets(ft, before, possible) # Search if any of the snippets overwrites the previous @@ -1066,17 +1067,108 @@ class SnippetManager(object): def _parse_snippets(self, ft, fn, file_data=None): _SnippetsFileParser(ft, fn, self, file_data).parse() + def base_snippet_files_for(self, ft, default=True): + """ Returns a list of snippet files matching the given filetype (ft). + If default is set to false, it doesn't include shipped files. + + Searches through each path in 'runtimepath' in reverse order, + in each of these, it searches each directory name listed in + 'g:UltiSnipsSnippetDirectories' in order, then looks for files in these + directories called 'ft.snippets' or '*_ft.snippets' replacing ft with + the filetype. + """ + + snippet_dirs = vim.eval("g:UltiSnipsSnippetDirectories") + base_snippets = os.path.realpath(os.path.join(__file__, "../../../UltiSnips")) + ret = [] + + for rtp in vim.eval("&runtimepath").split(',')[::-1]: + for snippet_dir in snippet_dirs: + pth = os.path.realpath(os.path.join(rtp, snippet_dir)) + + patterns = ["%s.snippets", "*_%s.snippets"] + if not default and pth == base_snippets: + patterns.remove("%s.snippets") + + for pattern in patterns: + for fn in glob.glob(os.path.join(pth, pattern % ft)): + if fn not in ret: + ret.append(fn) + + return ret + + def _filetypes(self, dotft=None): + if dotft is None: + dotft = vim.eval("&filetype") + + fts = dotft.split(".") + [ "all" ] + return [ft for ft in fts[::-1] if ft] + + def filetype(self): + """ Property for the current (undotted) filetype. """ + return self._filetypes()[-1] + filetype = property(filetype) + + def file_to_edit(self, ft=None): + """ Gets a file to edit based on the given filetype. + If no filetype is given, uses the current filetype from vim. + + Checks 'g:UltiSnipsSnippetsDir' and uses it if it exists + If a non-shipped file already exists, it uses it. + Otherwise uses a file in ~/.vim/ or ~/vimfiles + """ + if not ft: + ft = self.filetype + + edit = None + existing = self.base_snippet_files_for(ft, False) + filename = ft + ".snippets" + + if vim.eval("exists('g:UltiSnipsSnippetsDir')") == 1: + snipdir = vim.eval("g:UltiSnipsSnippetsDir") + edit = os.path.join(snipdir, filename) + elif existing: + edit = existing[-1] # last sourced/highest priority + else: + home = vim.eval("$HOME") + rtp = vim.eval("&rtp").split(",") + snippet_dirs = ["UltiSnips"] + vim.eval("g:UltiSnipsSnippetDirectories") + us = snippet_dirs[-1] + + path = os.path.join(home, ".vim", us) + for dirname in [".vim", "vimfiles"]: + pth = os.path.join(home, dirname) + if pth in rtp: + path = os.path.join(pth, us) + + if not os.path.isdir(path): + os.mkdir(path) + + edit = os.path.join(path, filename) + + return edit + + + def base_snippet_files(self, dotft=None): + """ Returns a list of all snippet files for the given filetype. + If no filetype is given, uses furrent filetype. + If the filetype is dotted (e.g. 'cuda.cpp.c') then it is split and + each filetype is checked. + """ + ret = [] + filetypes = self._filetypes(dotft) + + for ft in filetypes: + ret += self.base_snippet_files_for(ft) + + return ret + # Loading def _load_snippets_for(self, ft): self._snippets[ft] = _SnippetDictionary() - snippet_dirs = vim.eval("g:UltiSnipsSnippetDirectories") - for p in vim.eval("&runtimepath").split(',')[::-1]: - for snippet_dir in snippet_dirs: - pattern = os.path.join(p, snippet_dir, "*%s.snippets" % ft) - - for fn in glob.glob(pattern): - self._parse_snippets(ft, fn) + for fn in self.base_snippet_files_for(ft): + self._parse_snippets(ft, fn) # Now load for the parents for p in self._snippets[ft].extends: