def _do_snippet(self, snippet, before, after): """ Expands the given snippet, and handles everything that needs to be done with it. 'before' and 'after' should come from _get_before_after. """ lineno, col = vim_cursor() # Adjust before, maybe the trigger is not the complete word text_before = before if snippet.matched: text_before = before[:-len(snippet.matched)] self._unset_offending_vim_options(snippet) self._expect_move_wo_change = True if self._cs: # Determine position pos = self._vstate.pos p_start = self._ctab.abs_start if pos.line == p_start.line: end = Position(0, pos.col - p_start.col) else: end = Position(pos.line - p_start.line, pos.col) start = Position(end.line, end.col - len(snippet.matched)) si = snippet.launch(text_before, self._visual_content, self._ctab, start, end) self._visual_content = "" self._update_vim_buffer() if si.has_tabs: self._csnippets.append(si) self._jump() else: self._vb = VimBuffer(text_before, after) start = Position(lineno-1, len(text_before)) self._csnippets.append(snippet.launch(text_before, self._visual_content, None, start)) self._visual_content = "" self._vb.replace_lines(lineno-1, lineno-1, self._cs._current_text) self._jump()
class SnippetManager(object): def __init__(self): self._vstate = VimState() self._supertab_keys = None self._csnippets = [] self._cached_offending_vim_options = {} self.reset() @err_to_scratch_buffer def reset(self, test_error=False): self._test_error = test_error self._snippets = {} self._visual_content = as_unicode("") while len(self._csnippets): self._current_snippet_is_done() self._reinit() @err_to_scratch_buffer def jump_forwards(self): if not self._jump(): return self._handle_failure(self.forward_trigger) @err_to_scratch_buffer def jump_backwards(self): if not self._jump(True): return self._handle_failure(self.backward_trigger) @err_to_scratch_buffer def expand(self): if not self._try_expand(): self._handle_failure(self.expand_trigger) @err_to_scratch_buffer def list_snippets(self): before, after = self._get_before_after() snippets = self._snips(before, True) # Sort snippets alphabetically snippets.sort(key=lambda x: x.trigger) if not snippets: return True snippet = self._ask_snippets(snippets) if not snippet: return True self._do_snippet(snippet, before, after) return True @err_to_scratch_buffer def expand_or_jump(self): """ This function is used for people who wants to have the same trigger for expansion and forward jumping. It first tries to expand a snippet, if this fails, it tries to jump forward. """ rv = self._try_expand() if not rv: rv = self._jump() if not rv: self._handle_failure(self.expand_trigger) @err_to_scratch_buffer def save_last_visual_selection(self): """ This is called when the expand trigger is pressed in visual mode. Our job is to remember everything between '< and '> and pass it on to ${VISUAL} in case it will be needed. """ sl, sc = map(int, (vim.eval("""line("'<")"""), vim.eval("""virtcol("'<")"""))) el, ec = map(int, (vim.eval("""line("'>")"""), vim.eval("""virtcol("'>")"""))) def _vim_line_with_eol(ln): return as_unicode(vim.current.buffer[ln] + '\n') if sl == el: text = _vim_line_with_eol(sl-1)[sc-1:ec] else: text = _vim_line_with_eol(sl-1)[sc-1:] for cl in range(sl,el-1): text += _vim_line_with_eol(cl) text += _vim_line_with_eol(el-1)[:ec] self._visual_content = text def snippet_dict(self, ft): if ft not in self._snippets: self._snippets[ft] = _SnippetDictionary() return self._snippets[ft] @err_to_scratch_buffer def add_snippet(self, trigger, value, descr, options, ft = "all", globals = None, fn=None): l = self.snippet_dict(ft).add_snippet( Snippet(trigger, value, descr, options, globals or {}), fn ) @err_to_scratch_buffer def add_snippet_file(self, ft, path): sd = self.snippet_dict(ft) sd.addfile(path) @err_to_scratch_buffer def expand_anon(self, value, trigger="", descr="", options="", globals=None): if globals is None: globals = {} before, after = self._get_before_after() snip = Snippet(trigger, value, descr, options, globals) if not trigger or snip.matches(before): self._do_snippet(snip, before, after) return True else: return False @err_to_scratch_buffer def clear_snippets(self, triggers = [], ft = "all"): if ft in self._snippets: self._snippets[ft].clear_snippets(triggers) @err_to_scratch_buffer def add_extending_info(self, ft, parents): sd = self.snippet_dict(ft) for p in parents: if p in sd.extends: continue sd.extends.append(p) @err_to_scratch_buffer def backspace_while_selected(self): """ This is called when backspace was pressed while vim was in select mode. For us this might mean that a TabStop was selected and it's content should be deleted. """ if self._cs and (self._span_selected is not None): # This only happens when a default value is delted using backspace. # This does not change the buffer at all, only moves the cursor. self._vstate.update() feedkeys(r"i") self._chars_entered('') else: # We can't just pass <BS> through, because we took vim # out of SELECT mode, so instead we reselect and replace feedkeys(r"gvc") @err_to_scratch_buffer def cursor_moved(self): self._vstate.update() if not self._vstate.buf_changed and not self._expect_move_wo_change: self._check_if_still_inside_snippet() if not self._ctab: return if self._vstate.buf_changed and self._ctab: # Detect a carriage return if self._vstate.moved.col <= 0 and self._vstate.moved.line == 1: # Multiple things might have happened: either the user entered # a newline character or pasted some text which means we have # to copy everything he entered on the last line and keep the # indent vim chose for this line. lline = as_unicode(vim.current.buffer[self._vstate.ppos.line]) # Another thing that might have happened is that a word # wrapped, in this case the last line is shortened and we must # delete what Vim deleted there line_was_shortened = len(self._vstate.last_line) > len(lline) # Another thing that might have happened is that vim has # adjusted the indent of the last line and therefore the line # effectively got longer. This means a newline was entered and # we quite definitively do not want the indent that vim added line_was_lengthened = len(lline) > len(self._vstate.last_line) user_didnt_enter_newline = len(lline) != self._vstate.ppos.col cline = as_unicode(vim.current.buffer[self._vstate.pos.line]) if line_was_lengthened: this_entered = vim.current.line[:self._vstate.pos.col] self._chars_entered('\n' + cline + this_entered, 1) if line_was_shortened and user_didnt_enter_newline: nchars_deleted_in_lline = self._vstate.ppos.col - len(lline) self._backspace(nchars_deleted_in_lline) nchars_wrapped_from_lline_after_cursor = \ len(self._vstate.last_line) - self._vstate.ppos.col self._chars_entered('\n' + cline [:len(cline)-nchars_wrapped_from_lline_after_cursor], 1) else: pentered = lline[self._vstate.ppos.col:] this_entered = vim.current.line[:self._vstate.pos.col] self._chars_entered(pentered + '\n' + this_entered) elif self._vstate.moved.line == 0 and self._vstate.moved.col<0: # Some deleting was going on self._backspace(-self._vstate.moved.col) elif self._vstate.moved.line < 0: # Backspace over line end self._backspace(1) else: line = as_unicode(vim.current.line) chars = line[self._vstate.pos.col - self._vstate.moved.col: self._vstate.pos.col] self._chars_entered(chars) self._expect_move_wo_change = False @err_to_scratch_buffer def entered_insert_mode(self): self._vstate.update() if self._cs and self._vstate.has_moved: while len(self._csnippets): self._current_snippet_is_done() self._reinit() @err_to_scratch_buffer def leaving_window(self): """ Called when the user switches tabs. It basically means that all snippets must be properly terminated """ self._vstate.update() while len(self._csnippets): self._current_snippet_is_done() self._reinit() ################################### # Private/Protect Functions Below # ################################### def _error(self, msg): msg = vim_string("UltiSnips: " + msg) if self._test_error: msg = msg.replace('"', r'\"') msg = msg.replace('|', r'\|') vim.command("let saved_pos=getpos('.')") vim.command("$:put =%s" % msg) vim.command("call setpos('.', saved_pos)") elif False: vim.command("echohl WarningMsg") vim.command("echomsg %s" % msg) vim.command("echohl None") else: vim.command("echoerr %s" % msg) def _reinit(self): self._ctab = None self._span_selected = None self._expect_move_wo_change = False def _check_if_still_inside_snippet(self): # Cursor moved without input. self._ctab = None # Did we leave the snippet with this movement? if self._cs and not (self._vstate.pos in self._cs.abs_span): self._current_snippet_is_done() self._reinit() self._check_if_still_inside_snippet() def _current_snippet_is_done(self): self._csnippets.pop() if not len(self._csnippets): self._reset_offending_vim_options() def _jump(self, backwards = False): jumped = False if self._cs: self._expect_move_wo_change = True self._ctab = self._cs.select_next_tab(backwards) if self._ctab: self._vstate.select_span(self._ctab.abs_span) self._span_selected = self._ctab.abs_span jumped = True if self._ctab.no == 0: self._ctab = None self._current_snippet_is_done() self._vstate.update() else: # This really shouldn't happen, because a snippet should # have been popped when its final tabstop was used. # Cleanup by removing current snippet and recursing. self._current_snippet_is_done() jumped = self._jump(backwards) return jumped def _handle_failure(self, trigger): """ Mainly make sure that we play well with SuperTab """ if trigger.lower() == "<tab>": feedkey = "\\" + trigger else: feedkey = None mode = "n" if not self._supertab_keys: if vim.eval("exists('g:SuperTabMappingForward')") != "0": self._supertab_keys = ( vim.eval("g:SuperTabMappingForward"), vim.eval("g:SuperTabMappingBackward"), ) else: self._supertab_keys = [ '', '' ] for idx, sttrig in enumerate(self._supertab_keys): if trigger.lower() == sttrig.lower(): if idx == 0: feedkey= r"\<c-n>" elif idx == 1: feedkey = r"\<c-p>" # Use remap mode so SuperTab mappings will be invoked. mode = "m" break if feedkey: feedkeys(feedkey, mode) def _get_before_after(self): """ Returns the text before and after the cursor as a tuple. """ lineno, col = vim.current.window.cursor # Note: we want byte position here line = vim.current.line # Get the word to the left of the current edit position before, after = as_unicode(line[:col]), as_unicode(line[col:]) return before, after def _snips(self, before, possible): """ Returns all the snippets for the given text before the cursor. If possible is True, then get all possible matches. """ filetypes = self._ensure_snippets_loaded() found_snippets = [] for ft in filetypes: found_snippets += self._find_snippets(ft, before, possible) # Search if any of the snippets overwrites the previous # Dictionary allows O(1) access for easy overwrites snippets = {} for s in found_snippets: if (s.trigger not in snippets) or s.overwrites_previous: snippets[s.trigger] = [] snippets[s.trigger].append(s) # Transform dictionary into flat list of snippets selected_snippets = set([item for sublist in snippets.values() for item in sublist]) # Return snippets to their original order snippets = [snip for snip in found_snippets if snip in selected_snippets] return snippets def _ask_snippets(self, snippets): """ Given a list of snippets, ask the user which one they want to use, and return it. """ # make a python list display = [ "%i: %s" % (i+1,s.description) for i,s in enumerate(snippets)] try: # let vim_string format it as a vim list rv = vim.eval(make_suitable_for_vim(as_unicode("inputlist(%s)") % vim_string(display))) if rv is None or rv == '0': return None rv = int(rv) if rv > len(snippets): rv = len(snippets) return snippets[rv-1] except vim.error as e: if str(e) == 'invalid expression': return None raise def _do_snippet(self, snippet, before, after): """ Expands the given snippet, and handles everything that needs to be done with it. 'before' and 'after' should come from _get_before_after. """ lineno, col = vim_cursor() # Adjust before, maybe the trigger is not the complete word text_before = before if snippet.matched: text_before = before[:-len(snippet.matched)] self._unset_offending_vim_options(snippet) self._expect_move_wo_change = True if self._cs: # Determine position pos = self._vstate.pos p_start = self._ctab.abs_start if pos.line == p_start.line: end = Position(0, pos.col - p_start.col) else: end = Position(pos.line - p_start.line, pos.col) start = Position(end.line, end.col - len(snippet.matched)) si = snippet.launch(text_before, self._visual_content, self._ctab, start, end) self._visual_content = "" self._update_vim_buffer() if si.has_tabs: self._csnippets.append(si) self._jump() else: self._vb = VimBuffer(text_before, after) start = Position(lineno-1, len(text_before)) self._csnippets.append(snippet.launch(text_before, self._visual_content, None, start)) self._visual_content = "" self._vb.replace_lines(lineno-1, lineno-1, self._cs._current_text) self._jump() def _try_expand(self): self._expect_move_wo_change = False before, after = self._get_before_after() if not before: return False snippets = self._snips(before, False) if not snippets: # No snippet found return False elif len(snippets) == 1: snippet = snippets[0] else: snippet = self._ask_snippets(snippets) if not snippet: return True self._do_snippet(snippet, before, after) return True # Handling of offending vim options def _unset_offending_vim_options(self, snippet): # Care for textwrapping if not snippet.keep_formatoptions_unchanged: self._cached_offending_vim_options["fo"] = ''.join( c for c in vim.eval("&fo") if c in "ct" ) for c in "ct": vim.command("set fo-=%s" % c) def _reset_offending_vim_options(self): # Textwrapping for c in self._cached_offending_vim_options.pop("fo", []): vim.command("set fo+=%s" % c) # Input Handling def _chars_entered(self, chars, del_more_lines = 0): if (self._span_selected is not None): self._ctab.current_text = chars moved = 0 # If this edit changed the buffer in any ways we might have to # delete more or less lines, according how the cursors has moved if self._vstate.buf_changed: moved = self._span_selected.start.line - \ self._span_selected.end.line self._span_selected = None self._update_vim_buffer(moved + del_more_lines) else: self._ctab.current_text += chars self._update_vim_buffer(del_more_lines) def _backspace(self, count): self._ctab.current_text = self._ctab.current_text[:-count] self._update_vim_buffer() def _update_vim_buffer(self, del_more_lines = 0): if not len(self._csnippets): return s = self._csnippets[0] sline = s.abs_start.line dlines = s.end.line - s.start.line s.update() # Replace if self._vstate.buf_changed: dlines += self._vstate.moved.line dlines += del_more_lines self._vb.replace_lines(sline, sline + dlines, s._current_text) ct_end = self._ctab.abs_end set_vim_cursor(ct_end.line + 1, ct_end.col) self._vstate.update() def _cs(self): if not len(self._csnippets): return None return self._csnippets[-1] _cs = property(_cs) def _parse_snippets(self, ft, fn, file_data=None): self.add_snippet_file(ft, fn) _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 = [] paths = vim.eval("&runtimepath").split(',') if vim.eval("exists('g:UltiSnipsDontReverseSearchPath')") == "0" or \ vim.eval("g:UltiSnipsDontReverseSearchPath") == "0": paths = paths[::-1] for rtp in paths: for snippet_dir in snippet_dirs: pth = os.path.realpath(os.path.expanduser(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.snippet_dict(ft).reset() 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: if p not in self._snippets: self._load_snippets_for(p) def _needs_update(self, ft): do_hash = vim.eval('exists("g:UltiSnipsDoHash")') == "0" \ or vim.eval("g:UltiSnipsDoHash") != "0" if ft not in self._snippets: return True elif do_hash and self.snippet_dict(ft).needs_update(): return True elif do_hash: cur_snips = set(self.base_snippet_files_for(ft)) old_snips = set(self.snippet_dict(ft).files) if cur_snips - old_snips: return True return False def _ensure_loaded(self, ft, checked=None): if not checked: checked = set([ft]) elif ft in checked: return else: checked.add(ft) if self._needs_update(ft): self._load_snippets_for(ft) for parent in self.snippet_dict(ft).extends: self._ensure_loaded(parent, checked) def _ensure_snippets_loaded(self): """ Checks for changes in the list of snippet files or the contents of the snippet files and reloads them if necessary. """ filetypes = self._filetypes() for ft in filetypes: self._ensure_loaded(ft) return filetypes def _find_snippets(self, ft, trigger, potentially = False, seen=None): """ Find snippets matching trigger ft - file type to search trigger - trigger to match against potentially - also returns snippets that could potentially match; that is which triggers start with the current trigger """ snips = self._snippets.get(ft,None) if not snips: return [] if not seen: seen = [] seen.append(ft) parent_results = [] for p in snips.extends: if p not in seen: seen.append(p) parent_results += self._find_snippets(p, trigger, potentially, seen) return parent_results + snips.get_matching_snippets( trigger, potentially)
def list_snippets(self): filetypes = self._ensure_snippets_loaded() # TODO: this code is duplicated below filetypes = vim.eval("&filetype").split(".") + ["all"] lineno, col = vim.current.window.cursor line = vim.current.line before, after = line[:col], line[col:] word = '' if len(before): word = before.split()[-1] found_snippets = [] for ft in filetypes[::-1]: found_snippets += self._find_snippets(ft, word, True) if len(found_snippets) == 0: return True display = [ "%i %s" % (idx + 1, s.description) for idx, s in enumerate(found_snippets) ] # TODO: this code is also mirrored below try: rv = vim.eval("inputlist(%s)" % display) if rv is None or rv == '0': return True rv = int(rv) if rv > len(found_snippets): rv = len(found_snippets) snippet = found_snippets[rv - 1] except: # vim.error, e: if str(e) == 'invalid expression': return True raise # TODO: even more code duplicated below # Adjust before, maybe the trigger is not the complete word text_before = before.rstrip()[:-len(word)] text_before += word[:-len(snippet.trigger)] self._expect_move_wo_change = True if self._cs: # Determine position pos = self._vstate.pos p_start = self._ctab.abs_start if pos.line == p_start.line: end = Position(0, pos.col - p_start.col) else: end = Position(pos.line - p_start.line, pos.col) start = Position(end.line, end.col - len(snippet.trigger)) si = snippet.launch(text_before, self._ctab, start, end) self._update_vim_buffer() if si.has_tabs: self._csnippets.append(si) self._jump() else: self._vb = VimBuffer(text_before, after) start = Position(lineno - 1, len(text_before)) self._csnippets.append(snippet.launch(text_before, None, start)) self._vb.replace_lines(lineno - 1, lineno - 1, self._cs._current_text) self._jump() return True
class SnippetManager(object): def __init__(self): self._vstate = VimState() self._supertab_keys = None self.reset() def reset(self, test_error=False): self._test_error = test_error self._snippets = {} self._csnippets = [] self._reinit() def jump_forwards(self): if not self._jump(): return self._handle_failure(self.forward_trigger) def jump_backwards(self): if not self._jump(True): return self._handle_failure(self.backward_trigger) def expand(self): if not self._try_expand(): self._handle_failure(self.expand_trigger) def list_snippets(self): filetypes = self._ensure_snippets_loaded() # TODO: this code is duplicated below filetypes = vim.eval("&filetype").split(".") + ["all"] lineno, col = vim.current.window.cursor line = vim.current.line before, after = line[:col], line[col:] word = '' if len(before): word = before.split()[-1] found_snippets = [] for ft in filetypes[::-1]: found_snippets += self._find_snippets(ft, word, True) if len(found_snippets) == 0: return True display = [ "%i %s" % (idx + 1, s.description) for idx, s in enumerate(found_snippets) ] # TODO: this code is also mirrored below try: rv = vim.eval("inputlist(%s)" % display) if rv is None or rv == '0': return True rv = int(rv) if rv > len(found_snippets): rv = len(found_snippets) snippet = found_snippets[rv - 1] except: # vim.error, e: if str(e) == 'invalid expression': return True raise # TODO: even more code duplicated below # Adjust before, maybe the trigger is not the complete word text_before = before.rstrip()[:-len(word)] text_before += word[:-len(snippet.trigger)] self._expect_move_wo_change = True if self._cs: # Determine position pos = self._vstate.pos p_start = self._ctab.abs_start if pos.line == p_start.line: end = Position(0, pos.col - p_start.col) else: end = Position(pos.line - p_start.line, pos.col) start = Position(end.line, end.col - len(snippet.trigger)) si = snippet.launch(text_before, self._ctab, start, end) self._update_vim_buffer() if si.has_tabs: self._csnippets.append(si) self._jump() else: self._vb = VimBuffer(text_before, after) start = Position(lineno - 1, len(text_before)) self._csnippets.append(snippet.launch(text_before, None, start)) self._vb.replace_lines(lineno - 1, lineno - 1, self._cs._current_text) self._jump() return True def expand_or_jump(self): """ This function is used for people who wants to have the same trigger for expansion and forward jumping. It first tries to expand a snippet, if this fails, it tries to jump forward. """ rv = self._try_expand() if not rv: rv = self._jump() if not rv: self._handle_failure(self.expand_trigger) def add_snippet(self, trigger, value, descr, options, ft="all"): if ft not in self._snippets: self._snippets[ft] = _SnippetDictionary() l = self._snippets[ft].add_snippet( Snippet(trigger, value, descr, options)) def clear_snippets(self, triggers=[], ft="all"): if ft in self._snippets: self._snippets[ft].clear_snippets(triggers) def add_extending_info(self, ft, parents): if ft not in self._snippets: self._snippets[ft] = _SnippetDictionary() sd = self._snippets[ft] for p in parents: if p in sd.extends: continue sd.extends.append(p) def backspace_while_selected(self): """ This is called when backspace was used while a placeholder was selected. """ # BS was called in select mode if self._cs and (self._span_selected is not None): # This only happens when a default value is delted using backspace vim.command(r'call feedkeys("i")') self._chars_entered('') else: vim.command(r'call feedkeys("\<BS>")') def cursor_moved(self): self._vstate.update() if not self._vstate.buf_changed and not self._expect_move_wo_change: self._check_if_still_inside_snippet() if not self._ctab: return if self._vstate.buf_changed and self._ctab: # Detect a carriage return if self._vstate.moved.col <= 0 and self._vstate.moved.line == 1: # Multiple things might have happened: either the user entered # a newline character or pasted some text which means we have # to copy everything he entered on the last line and keep the # indent vim chose for this line. lline = vim.current.buffer[self._vstate.ppos.line] # Another thing that might have happened is that a word # wrapped, in this case the last line is shortened and we must # delete what Vim deleted there line_was_shortened = len(self._vstate.last_line) > len(lline) # Another thing that might have happened is that vim has # adjusted the indent of the last line and therefore the line # effectivly got longer. This means a newline was entered and # we quite definitivly do not want the indent that vim added line_was_lengthened = len(lline) > len(self._vstate.last_line) user_didnt_enter_newline = len(lline) != self._vstate.ppos.col cline = vim.current.buffer[self._vstate.pos.line] if line_was_lengthened: this_entered = vim.current.line[:self._vstate.pos.col] self._chars_entered('\n' + cline + this_entered, 1) if line_was_shortened and user_didnt_enter_newline: self._backspace(len(self._vstate.last_line) - len(lline)) self._chars_entered('\n' + cline, 1) else: pentered = lline[self._vstate.ppos.col:] this_entered = vim.current.line[:self._vstate.pos.col] self._chars_entered(pentered + '\n' + this_entered) elif self._vstate.moved.line == 0 and self._vstate.moved.col < 0: # Some deleting was going on self._backspace(-self._vstate.moved.col) elif self._vstate.moved.line < 0: # Backspace over line end self._backspace(1) else: line = vim.current.line chars = line[self._vstate.pos.col - self._vstate.moved.col:self._vstate.pos.col] self._chars_entered(chars) self._expect_move_wo_change = False def entered_insert_mode(self): self._vstate.update() if self._cs and self._vstate.has_moved: self._reinit() self._csnippets = [] ################################### # Private/Protect Functions Below # ################################### def _error(self, msg): msg = _vim_quote("UltiSnips: " + msg) if self._test_error: msg = msg.replace('"', r'\"') msg = msg.replace('|', r'\|') vim.command("let saved_pos=getpos('.')") vim.command("$:put =%s" % msg) vim.command("call setpos('.', saved_pos)") elif False: vim.command("echohl WarningMsg") vim.command("echomsg %s" % msg) vim.command("echohl None") else: vim.command("echoerr %s" % msg) def _reinit(self): self._ctab = None self._span_selected = None self._expect_move_wo_change = False def _check_if_still_inside_snippet(self): # Cursor moved without input. self._ctab = None # Did we leave the snippet with this movement? if self._cs and not (self._vstate.pos in self._cs.abs_span): self._csnippets.pop() self._reinit() self._check_if_still_inside_snippet() def _jump(self, backwards=False): jumped = False if self._cs: self._expect_move_wo_change = True self._ctab = self._cs.select_next_tab(backwards) if self._ctab: self._vstate.select_span(self._ctab.abs_span) self._span_selected = self._ctab.abs_span jumped = True if self._ctab.no == 0: self._ctab = None self._csnippets.pop() self._vstate.update() else: # This really shouldn't happen, because a snippet should # have been popped when its final tabstop was used. # Cleanup by removing current snippet and recursing. self._csnippets.pop() jumped = self._jump(backwards) return jumped def _handle_failure(self, trigger): """ Mainly make sure that we play well with SuperTab """ if trigger.lower() == "<tab>": feedkey = "\\" + trigger else: feedkey = None mode = "n" if not self._supertab_keys: if vim.eval("exists('g:SuperTabMappingForward')") != "0": self._supertab_keys = ( vim.eval("g:SuperTabMappingForward"), vim.eval("g:SuperTabMappingBackward"), ) else: self._supertab_keys = ['', ''] for idx, sttrig in enumerate(self._supertab_keys): if trigger.lower() == sttrig.lower(): if idx == 0: feedkey = r"\<c-n>" elif idx == 1: feedkey = r"\<c-p>" # Use remap mode so SuperTab mappings will be invoked. mode = "m" break if feedkey: vim.command(r'call feedkeys("%s", "%s")' % (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: self._load_snippets_for(ft) return filetypes def _try_expand(self): filetypes = self._ensure_snippets_loaded() self._expect_move_wo_change = False lineno, col = vim.current.window.cursor if col == 0: return False line = vim.current.line if col > 0 and line[col - 1] in string.whitespace: return False # Get the word to the left of the current edit position before, after = line[:col], line[col:] word = before.split()[-1] found_snippets = [] for ft in filetypes[::-1]: found_snippets += self._find_snippets(ft, word) # Search if any of the snippets overwrites the previous snippets = [] for s in found_snippets: if s.overwrites_previous: snippets = [] snippets.append(s) # Check if there are any only whitespace in front snippets text_before = before.rstrip()[:-len(word)] if text_before.strip(" \t") != '': snippets = [s for s in snippets if not s.needs_ws_in_front] if not len(snippets): # No snippet found return False elif len(snippets) == 1: snippet, = snippets else: display = repr([ "%i: %s" % (i + 1, s.description) for i, s in enumerate(snippets) ]) try: rv = vim.eval("inputlist(%s)" % display) if rv is None or rv == '0': return True rv = int(rv) if rv > len(snippets): rv = len(snippets) snippet = snippets[rv - 1] except vim.error, e: if str(e) == 'invalid expression': return True raise # Adjust before, maybe the trigger is not the complete word text_before += word[:-len(snippet.trigger)] self._expect_move_wo_change = True if self._cs: # Determine position pos = self._vstate.pos p_start = self._ctab.abs_start if pos.line == p_start.line: end = Position(0, pos.col - p_start.col) else: end = Position(pos.line - p_start.line, pos.col) start = Position(end.line, end.col - len(snippet.trigger)) si = snippet.launch(text_before, self._ctab, start, end) self._update_vim_buffer() if si.has_tabs: self._csnippets.append(si) self._jump() else: self._vb = VimBuffer(text_before, after) start = Position(lineno - 1, len(text_before)) self._csnippets.append(snippet.launch(text_before, None, start)) self._vb.replace_lines(lineno - 1, lineno - 1, self._cs._current_text) self._jump() return True
def _try_expand(self): filetypes = vim.eval("&filetype").split(".") + [ "all" ] for ft in filetypes[::-1]: if len(ft) and ft not in self._snippets: self._load_snippets_for(ft) self._expect_move_wo_change = False lineno,col = vim.current.window.cursor if col == 0: return False line = vim.current.line if col > 0 and line[col-1] in string.whitespace: return False # Get the word to the left of the current edit position before,after = line[:col], line[col:] word = before.split()[-1] found_snippets = [] for ft in filetypes[::-1]: found_snippets += self._find_snippets(ft, word) # Search if any of the snippets overwrites the previous snippets = [] for s in found_snippets: if s.overwrites_previous: snippets = [] snippets.append(s) # Check if there are any only whitespace in front snippets text_before = before.rstrip()[:-len(word)] if text_before.strip(" \t") != '': snippets = [ s for s in snippets if not s.needs_ws_in_front ] if not len(snippets): # No snippet found return False elif len(snippets) == 1: snippet, = snippets else: display = repr( [ "%i: %s" % (i+1,s.description) for i,s in enumerate(snippets) ] ) rv = vim.eval("inputlist(%s)" % display) if rv is None: return True rv = int(rv) snippet = snippets[rv-1] self._expect_move_wo_change = True if self._cs: # Determine position pos = self._vstate.pos p_start = self._ctab.abs_start if pos.line == p_start.line: end = Position(0, pos.col - p_start.col) else: end = Position(pos.line - p_start.line, pos.col) start = Position(end.line, end.col - len(snippet.trigger)) si = snippet.launch(text_before, self._ctab, start, end) self._update_vim_buffer() if si.has_tabs: self._csnippets.append(si) self._jump() else: self._vb = VimBuffer(text_before, after) start = Position(lineno-1, len(text_before)) self._csnippets.append(snippet.launch(text_before, None, start)) self._vb.replace_lines(lineno-1, lineno-1, self._cs._current_text) self._jump() return True
class SnippetManager(object): def __init__(self): self._vstate = VimState() self._supertab_keys = None self.reset() def reset(self): self._snippets = {} self._csnippets = [] self._reinit() def jump_forwards(self): if not self._jump(): return self._handle_failure(self.forward_trigger) def jump_backwards(self): if not self._jump(True): return self._handle_failure(backward_trigger) def expand(self): if not self._try_expand(): self._handle_failure(self.expand_trigger) def expand_or_jump(self): """ This function is used for people who wants to have the same trigger for expansion and forward jumping. It first tries to expand a snippet, if this fails, it tries to jump forward. """ rv = self._try_expand() if not rv: rv = self._jump() if not rv: self._handle_failure(self.expand_trigger) def add_snippet(self, trigger, value, descr, options): if "all" not in self._snippets: self._snippets["all"] = {} l = self._snippets["all"].get(trigger,[]) l.append(Snippet(trigger,value, descr, options)) self._snippets["all"][trigger] = l def backspace_while_selected(self): """ This is called when backspace was used while a placeholder was selected. """ # BS was called in select mode if self._cs and (self._span_selected is not None): # This only happens when a default value is delted using backspace vim.command(r'call feedkeys("i")') self._chars_entered('') else: vim.command(r'call feedkeys("\<BS>")') def cursor_moved(self): self._vstate.update() if not self._vstate.buf_changed and not self._expect_move_wo_change: self._check_if_still_inside_snippet() if not self._ctab: return if self._vstate.buf_changed and self._ctab: # Detect a carriage return if self._vstate.moved.col <= 0 and self._vstate.moved.line == 1: # Multiple things might have happened: either the user entered # a newline character or pasted some text which means we have # to copy everything he entered on the last line and keep the # indent vim chose for this line. lline = vim.current.buffer[self._vstate.ppos.line] # Another thing that might have happened is that a word # wrapped, in this case the last line is shortened and we must # delete what vim deleted there line_was_shortened = len(self._vstate.last_line) > len(lline) user_didnt_enter_newline = len(lline) != self._vstate.ppos.col if line_was_shortened and user_didnt_enter_newline: cline = vim.current.buffer[self._vstate.pos.line] self._backspace(len(self._vstate.last_line)-len(lline)) self._chars_entered('\n' + cline, 1) else: pentered = lline[self._vstate.ppos.col:] this_entered = vim.current.line[:self._vstate.pos.col] self._chars_entered(pentered + '\n' + this_entered) elif self._vstate.moved.line == 0 and self._vstate.moved.col<0: # Some deleting was going on self._backspace(-self._vstate.moved.col) elif self._vstate.moved.line < 0: # Backspace over line end self._backspace(1) else: line = vim.current.line chars = line[self._vstate.pos.col - self._vstate.moved.col: self._vstate.pos.col] self._chars_entered(chars) self._expect_move_wo_change = False def entered_insert_mode(self): self._vstate.update() if self._cs and self._vstate.has_moved: self._reinit() self._csnippets = [] ################################### # Private/Protect Functions Below # ################################### def _reinit(self): self._ctab = None self._span_selected = None self._expect_move_wo_change = False def _check_if_still_inside_snippet(self): # Cursor moved without input. self._ctab = None # Did we leave the snippet with this movement? if self._cs and not (self._vstate.pos in self._cs.abs_span): self._csnippets.pop() self._reinit() self._check_if_still_inside_snippet() def _jump(self, backwards = False): if self._cs: self._expect_move_wo_change = True self._ctab = self._cs.select_next_tab(backwards) if self._ctab: self._vstate.select_span(self._ctab.abs_span) self._span_selected = self._ctab.abs_span else: self._csnippets.pop() if self._cs: self._jump(backwards) return True self._vstate.update() return True return False def _handle_failure(self, trigger): """ Mainly make sure that we play well with SuperTab """ feedkey = None if not self._supertab_keys: if vim.eval("exists('g:SuperTabMappingForward')") != "0": self._supertab_keys = ( vim.eval("g:SuperTabMappingForward"), vim.eval("g:SuperTabMappingBackward"), ) else: self._supertab_keys = [ '', '' ] for idx, sttrig in enumerate(self._supertab_keys): if trigger.lower() == sttrig.lower(): if idx == 0: feedkey= r"\<c-n>" elif idx == 1: feedkey = r"\<c-p>" break if feedkey: vim.command(r'call feedkeys("%s")' % feedkey) def _try_expand(self): filetypes = vim.eval("&filetype").split(".") + [ "all" ] for ft in filetypes[::-1]: if len(ft) and ft not in self._snippets: self._load_snippets_for(ft) self._expect_move_wo_change = False lineno,col = vim.current.window.cursor if col == 0: return False line = vim.current.line if col > 0 and line[col-1] in string.whitespace: return False # Get the word to the left of the current edit position before,after = line[:col], line[col:] word = before.split()[-1] found_snippets = [] for ft in filetypes[::-1]: found_snippets += self._find_snippets(ft, word) # Search if any of the snippets overwrites the previous snippets = [] for s in found_snippets: if s.overwrites_previous: snippets = [] snippets.append(s) # Check if there are any only whitespace in front snippets text_before = before.rstrip()[:-len(word)] if text_before.strip(" \t") != '': snippets = [ s for s in snippets if not s.needs_ws_in_front ] if not len(snippets): # No snippet found return False elif len(snippets) == 1: snippet, = snippets else: display = repr( [ "%i: %s" % (i+1,s.description) for i,s in enumerate(snippets) ] ) rv = vim.eval("inputlist(%s)" % display) if rv is None: return True rv = int(rv) snippet = snippets[rv-1] self._expect_move_wo_change = True if self._cs: # Determine position pos = self._vstate.pos p_start = self._ctab.abs_start if pos.line == p_start.line: end = Position(0, pos.col - p_start.col) else: end = Position(pos.line - p_start.line, pos.col) start = Position(end.line, end.col - len(snippet.trigger)) si = snippet.launch(text_before, self._ctab, start, end) self._update_vim_buffer() if si.has_tabs: self._csnippets.append(si) self._jump() else: self._vb = VimBuffer(text_before, after) start = Position(lineno-1, len(text_before)) self._csnippets.append(snippet.launch(text_before, None, start)) self._vb.replace_lines(lineno-1, lineno-1, self._cs._current_text) self._jump() return True # Input Handling def _chars_entered(self, chars, del_more_lines = 0): if (self._span_selected is not None): self._ctab.current_text = chars moved = self._span_selected.start.line - \ self._span_selected.end.line self._span_selected = None self._update_vim_buffer(moved + del_more_lines) else: self._ctab.current_text += chars self._update_vim_buffer(del_more_lines) def _backspace(self, count): self._ctab.current_text = self._ctab.current_text[:-count] self._update_vim_buffer() def _update_vim_buffer(self, del_more_lines = 0): if not len(self._csnippets): return s = self._csnippets[0] sline = s.abs_start.line dlines = s.end.line - s.start.line s.update() # Replace dlines += self._vstate.moved.line + del_more_lines self._vb.replace_lines(sline, sline + dlines, s._current_text) ct_end = self._ctab.abs_end vim.current.window.cursor = ct_end.line +1, ct_end.col self._vstate.update() def _cs(self): if not len(self._csnippets): return None return self._csnippets[-1] _cs = property(_cs) # Loading def _load_snippets_from(self, ft, fn): cs = None cv = "" cdescr = "" coptions = "" for line in open(fn): if cs is None and line.startswith("#"): continue if line.startswith("snippet"): cs = line.split()[1] left = line.find('"') if left != -1: right = line.rfind('"') cdescr = line[left+1:right] coptions = line[right:].strip() continue if cs != None: if line.startswith("endsnippet"): cv = cv[:-1] # Chop the last newline l = self._snippets[ft].get(cs,[]) l.append(Snippet(cs,cv,cdescr,coptions)) self._snippets[ft][cs] = l cv = cdescr = coptions = "" cs = None continue else: cv += line def _load_snippets_for(self, ft): self._snippets[ft] = {} for p in vim.eval("&runtimepath").split(',')[::-1]: pattern = p + os.path.sep + "UltiSnips" + os.path.sep + \ "%s.snippets" % ft extra_pattern = p + os.path.sep + "UltiSnips" + os.path.sep + \ "*[-_]%s.snippets" % ft for fn in glob.glob(pattern)+glob.glob(extra_pattern): self._load_snippets_from(ft, fn) def _find_snippets(self, ft, trigger): snips = self._snippets.get(ft,None) if not snips: return [] return snips.get(trigger, [])
def list_snippets(self): filetypes = self._ensure_snippets_loaded() # TODO: this code is duplicated below filetypes = vim.eval("&filetype").split(".") + [ "all" ] lineno,col = vim.current.window.cursor line = vim.current.line before,after = line[:col], line[col:] word = '' if len(before): word = before.split()[-1] found_snippets = [] for ft in filetypes[::-1]: found_snippets += self._find_snippets(ft, word, True) if len(found_snippets) == 0: return True display = [ "%i %s" % (idx+1,s.description) for idx,s in enumerate(found_snippets) ] # TODO: this code is also mirrored below try: rv = vim.eval("inputlist(%s)" % display) if rv is None or rv == '0': return True rv = int(rv) if rv > len(found_snippets): rv = len(found_snippets) snippet = found_snippets[rv-1] except: # vim.error, e: if str(e) == 'invalid expression': return True raise # TODO: even more code duplicated below # Adjust before, maybe the trigger is not the complete word text_before = before.rstrip()[:-len(word)] text_before += word[:-len(snippet.trigger)] self._expect_move_wo_change = True if self._cs: # Determine position pos = self._vstate.pos p_start = self._ctab.abs_start if pos.line == p_start.line: end = Position(0, pos.col - p_start.col) else: end = Position(pos.line - p_start.line, pos.col) start = Position(end.line, end.col - len(snippet.trigger)) si = snippet.launch(text_before, self._ctab, start, end) self._update_vim_buffer() if si.has_tabs: self._csnippets.append(si) self._jump() else: self._vb = VimBuffer(text_before, after) start = Position(lineno-1, len(text_before)) self._csnippets.append(snippet.launch(text_before, None, start)) self._vb.replace_lines(lineno-1, lineno-1, self._cs._current_text) self._jump() return True
class SnippetManager(object): def __init__(self): self._vstate = VimState() self._supertab_keys = None self.reset() def reset(self, test_error=False): self._test_error = test_error self._snippets = {} self._csnippets = [] self._reinit() def jump_forwards(self): if not self._jump(): return self._handle_failure(self.forward_trigger) def jump_backwards(self): if not self._jump(True): return self._handle_failure(self.backward_trigger) def expand(self): if not self._try_expand(): self._handle_failure(self.expand_trigger) def list_snippets(self): filetypes = self._ensure_snippets_loaded() # TODO: this code is duplicated below filetypes = vim.eval("&filetype").split(".") + [ "all" ] lineno,col = vim.current.window.cursor line = vim.current.line before,after = line[:col], line[col:] word = '' if len(before): word = before.split()[-1] found_snippets = [] for ft in filetypes[::-1]: found_snippets += self._find_snippets(ft, word, True) if len(found_snippets) == 0: return True display = [ "%i %s" % (idx+1,s.description) for idx,s in enumerate(found_snippets) ] # TODO: this code is also mirrored below try: rv = vim.eval("inputlist(%s)" % display) if rv is None or rv == '0': return True rv = int(rv) if rv > len(found_snippets): rv = len(found_snippets) snippet = found_snippets[rv-1] except: # vim.error, e: if str(e) == 'invalid expression': return True raise # TODO: even more code duplicated below # Adjust before, maybe the trigger is not the complete word text_before = before.rstrip()[:-len(word)] text_before += word[:-len(snippet.trigger)] self._expect_move_wo_change = True if self._cs: # Determine position pos = self._vstate.pos p_start = self._ctab.abs_start if pos.line == p_start.line: end = Position(0, pos.col - p_start.col) else: end = Position(pos.line - p_start.line, pos.col) start = Position(end.line, end.col - len(snippet.trigger)) si = snippet.launch(text_before, self._ctab, start, end) self._update_vim_buffer() if si.has_tabs: self._csnippets.append(si) self._jump() else: self._vb = VimBuffer(text_before, after) start = Position(lineno-1, len(text_before)) self._csnippets.append(snippet.launch(text_before, None, start)) self._vb.replace_lines(lineno-1, lineno-1, self._cs._current_text) self._jump() return True def expand_or_jump(self): """ This function is used for people who wants to have the same trigger for expansion and forward jumping. It first tries to expand a snippet, if this fails, it tries to jump forward. """ rv = self._try_expand() if not rv: rv = self._jump() if not rv: self._handle_failure(self.expand_trigger) def add_snippet(self, trigger, value, descr, options, ft = "all"): if ft not in self._snippets: self._snippets[ft] = _SnippetDictionary() l = self._snippets[ft].add_snippet( Snippet(trigger, value, descr, options) ) def clear_snippets(self, triggers = [], ft = "all"): if ft in self._snippets: self._snippets[ft].clear_snippets(triggers) def add_extending_info(self, ft, parents): if ft not in self._snippets: self._snippets[ft] = _SnippetDictionary() sd = self._snippets[ft] for p in parents: if p in sd.extends: continue sd.extends.append(p) def backspace_while_selected(self): """ This is called when backspace was used while a placeholder was selected. """ # BS was called in select mode if self._cs and (self._span_selected is not None): # This only happens when a default value is delted using backspace vim.command(r'call feedkeys("i")') self._chars_entered('') else: vim.command(r'call feedkeys("\<BS>")') def cursor_moved(self): self._vstate.update() if not self._vstate.buf_changed and not self._expect_move_wo_change: self._check_if_still_inside_snippet() if not self._ctab: return if self._vstate.buf_changed and self._ctab: # Detect a carriage return if self._vstate.moved.col <= 0 and self._vstate.moved.line == 1: # Multiple things might have happened: either the user entered # a newline character or pasted some text which means we have # to copy everything he entered on the last line and keep the # indent vim chose for this line. lline = vim.current.buffer[self._vstate.ppos.line] # Another thing that might have happened is that a word # wrapped, in this case the last line is shortened and we must # delete what Vim deleted there line_was_shortened = len(self._vstate.last_line) > len(lline) # Another thing that might have happened is that vim has # adjusted the indent of the last line and therefore the line # effectivly got longer. This means a newline was entered and # we quite definitivly do not want the indent that vim added line_was_lengthened = len(lline) > len(self._vstate.last_line) user_didnt_enter_newline = len(lline) != self._vstate.ppos.col cline = vim.current.buffer[self._vstate.pos.line] if line_was_lengthened: this_entered = vim.current.line[:self._vstate.pos.col] self._chars_entered('\n' + cline + this_entered, 1) if line_was_shortened and user_didnt_enter_newline: self._backspace(len(self._vstate.last_line)-len(lline)) self._chars_entered('\n' + cline, 1) else: pentered = lline[self._vstate.ppos.col:] this_entered = vim.current.line[:self._vstate.pos.col] self._chars_entered(pentered + '\n' + this_entered) elif self._vstate.moved.line == 0 and self._vstate.moved.col<0: # Some deleting was going on self._backspace(-self._vstate.moved.col) elif self._vstate.moved.line < 0: # Backspace over line end self._backspace(1) else: line = vim.current.line chars = line[self._vstate.pos.col - self._vstate.moved.col: self._vstate.pos.col] self._chars_entered(chars) self._expect_move_wo_change = False def entered_insert_mode(self): self._vstate.update() if self._cs and self._vstate.has_moved: self._reinit() self._csnippets = [] ################################### # Private/Protect Functions Below # ################################### def _error(self, msg): msg = _vim_quote("UltiSnips: " + msg) if self._test_error: msg = msg.replace('"', r'\"') msg = msg.replace('|', r'\|') vim.command("let saved_pos=getpos('.')") vim.command("$:put =%s" % msg) vim.command("call setpos('.', saved_pos)") elif False: vim.command("echohl WarningMsg") vim.command("echomsg %s" % msg) vim.command("echohl None") else: vim.command("echoerr %s" % msg) def _reinit(self): self._ctab = None self._span_selected = None self._expect_move_wo_change = False def _check_if_still_inside_snippet(self): # Cursor moved without input. self._ctab = None # Did we leave the snippet with this movement? if self._cs and not (self._vstate.pos in self._cs.abs_span): self._csnippets.pop() self._reinit() self._check_if_still_inside_snippet() def _jump(self, backwards = False): jumped = False if self._cs: self._expect_move_wo_change = True self._ctab = self._cs.select_next_tab(backwards) if self._ctab: self._vstate.select_span(self._ctab.abs_span) self._span_selected = self._ctab.abs_span jumped = True if self._ctab.no == 0: self._ctab = None self._csnippets.pop() self._vstate.update() else: # This really shouldn't happen, because a snippet should # have been popped when its final tabstop was used. # Cleanup by removing current snippet and recursing. self._csnippets.pop() jumped = self._jump(backwards) return jumped def _handle_failure(self, trigger): """ Mainly make sure that we play well with SuperTab """ if trigger.lower() == "<tab>": feedkey = "\\" + trigger else: feedkey = None mode = "n" if not self._supertab_keys: if vim.eval("exists('g:SuperTabMappingForward')") != "0": self._supertab_keys = ( vim.eval("g:SuperTabMappingForward"), vim.eval("g:SuperTabMappingBackward"), ) else: self._supertab_keys = [ '', '' ] for idx, sttrig in enumerate(self._supertab_keys): if trigger.lower() == sttrig.lower(): if idx == 0: feedkey= r"\<c-n>" elif idx == 1: feedkey = r"\<c-p>" # Use remap mode so SuperTab mappings will be invoked. mode = "m" break if feedkey: vim.command(r'call feedkeys("%s", "%s")' % (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: self._load_snippets_for(ft) return filetypes def _try_expand(self): filetypes = self._ensure_snippets_loaded() self._expect_move_wo_change = False lineno,col = vim.current.window.cursor if col == 0: return False line = vim.current.line if col > 0 and line[col-1] in string.whitespace: return False # Get the word to the left of the current edit position before,after = line[:col], line[col:] word = before.split()[-1] found_snippets = [] for ft in filetypes[::-1]: found_snippets += self._find_snippets(ft, word) # Search if any of the snippets overwrites the previous snippets = [] for s in found_snippets: if s.overwrites_previous: snippets = [] snippets.append(s) # Check if there are any only whitespace in front snippets text_before = before.rstrip()[:-len(word)] if text_before.strip(" \t") != '': snippets = [ s for s in snippets if not s.needs_ws_in_front ] if not len(snippets): # No snippet found return False elif len(snippets) == 1: snippet, = snippets else: display = repr( [ "%i: %s" % (i+1,s.description) for i,s in enumerate(snippets) ] ) try: rv = vim.eval("inputlist(%s)" % display) if rv is None or rv == '0': return True rv = int(rv) if rv > len(snippets): rv = len(snippets) snippet = snippets[rv-1] except vim.error, e: if str(e) == 'invalid expression': return True raise # Adjust before, maybe the trigger is not the complete word text_before += word[:-len(snippet.trigger)] self._expect_move_wo_change = True if self._cs: # Determine position pos = self._vstate.pos p_start = self._ctab.abs_start if pos.line == p_start.line: end = Position(0, pos.col - p_start.col) else: end = Position(pos.line - p_start.line, pos.col) start = Position(end.line, end.col - len(snippet.trigger)) si = snippet.launch(text_before, self._ctab, start, end) self._update_vim_buffer() if si.has_tabs: self._csnippets.append(si) self._jump() else: self._vb = VimBuffer(text_before, after) start = Position(lineno-1, len(text_before)) self._csnippets.append(snippet.launch(text_before, None, start)) self._vb.replace_lines(lineno-1, lineno-1, self._cs._current_text) self._jump() return True