def file_to_edit(self, ft): """ 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 """ 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 = "C:\\vim\\vimfiles\\bundle\\ultisnips" snippet_dirs = ["UltiSnips"] + _vim.eval("g:UltiSnipsSnippetDirectories") us = snippet_dirs[-1] path = os.path.join(home, us) if not os.path.isdir(path): os.mkdir(path) edit = os.path.join(path, filename) return edit
def _handle_failure(self, trigger): """Mainly make sure that we play well with SuperTab.""" if trigger.lower() == "<tab>": feedkey = "\\" + trigger elif trigger.lower() == "<s-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"\<Plug>SuperTabForward" mode = "n" elif idx == 1: feedkey = r"\<Plug>SuperTabBackward" mode = "p" # Use remap mode so SuperTab mappings will be invoked. break if (feedkey == r"\<Plug>SuperTabForward" or feedkey == r"\<Plug>SuperTabBackward"): _vim.command("return SuperTab(%s)" % _vim.escape(mode)) elif feedkey: _vim.command("return %s" % _vim.escape(feedkey))
def guess_edit(initial_line, lt, ct, vs): """ Try to guess what the user might have done by heuristically looking at cursor movement number of changed lines and if they got longer or shorter. This will detect most simple movements like insertion, deletion of a line or carriage return. """ if not len(lt) and not len(ct): return True, () pos = vs.pos ppos = vs.ppos if len(lt) and (not ct or (len(ct) == 1 and not ct[0])): # All text deleted? es = [] if not ct: ct = [''] for i in lt: es.append(("D", initial_line, 0, i)) es.append(("D", initial_line, 0, "\n")) es.pop() # Remove final \n because it is not really removed if is_complete_edit(initial_line, lt, ct, es): return True, es if ppos.mode == 'v': # Maybe selectmode? sv = list(map(int, _vim.eval("""getpos("'<")"""))); sv = Position(sv[1]-1,sv[2]-1) ev = list(map(int, _vim.eval("""getpos("'>")"""))); ev = Position(ev[1]-1,ev[2]-1) if "exclusive" in _vim.eval("&selection"): ppos.col -= 1 # We want to be inclusive, sorry. ev.col -= 1 es = [] if sv.line == ev.line: es.append(("D", sv.line, sv.col, lt[sv.line - initial_line][sv.col:ev.col+1])) if sv != pos and sv.line == pos.line: es.append(("I", sv.line, sv.col, ct[sv.line - initial_line][sv.col:pos.col+1])) if is_complete_edit(initial_line, lt, ct, es): return True, es if pos.line == ppos.line: if len(lt) == len(ct): # Movement only in one line llen = len(lt[ppos.line - initial_line]) clen = len(ct[pos.line - initial_line]) if ppos < pos and clen > llen: # Likely that only characters have been added es = ( ("I", ppos.line, ppos.col, ct[ppos.line - initial_line][ppos.col:pos.col]), ) if is_complete_edit(initial_line, lt, ct, es): return True, es if clen < llen: if ppos == pos: # 'x' or DEL or dt or something es = ( ("D", pos.line, pos.col, lt[ppos.line - initial_line][ppos.col:ppos.col + (llen - clen)]), ) if is_complete_edit(initial_line, lt, ct, es): return True, es if pos < ppos: # Backspacing or dT dF? es = ( ("D", pos.line, pos.col, lt[pos.line - initial_line][pos.col:pos.col + llen - clen]), ) if is_complete_edit(initial_line, lt, ct, es): return True, es elif len(ct) < len(lt): # Maybe some lines were deleted? (dd or so) es = [] for i in range(len(lt)-len(ct)): es.append( ("D", pos.line, 0, lt[pos.line - initial_line + i])) es.append( ("D", pos.line, 0, '\n')) if is_complete_edit(initial_line, lt, ct, es): return True, es else: # Movement in more than one line if ppos.line + 1 == pos.line and pos.col == 0: # Carriage return? es = (("I", ppos.line, ppos.col, "\n"),) if is_complete_edit(initial_line, lt, ct, es): return True, es return False, None
def __init__(self, expand_trigger, forward_trigger, backward_trigger): self.expand_trigger = expand_trigger self.forward_trigger = forward_trigger self.backward_trigger = backward_trigger self._inner_state_up = False self._supertab_keys = None self._csnippets = [] self._buffer_filetypes = defaultdict(lambda: ["all"]) self._vstate = VimState() self._visual_content = VisualContentPreserver() self._snippet_sources = [] self._snip_expanded_in_action = False self._inside_action = False self._last_inserted_char = "" self._added_snippets_source = AddedSnippetsSource() self.register_snippet_source("ultisnips_files", UltiSnipsFileSource()) self.register_snippet_source("added", self._added_snippets_source) enable_snipmate = "1" if _vim.eval("exists('g:UltiSnipsEnableSnipMate')") == "1": enable_snipmate = _vim.eval("g:UltiSnipsEnableSnipMate") if enable_snipmate == "1": self.register_snippet_source("snipmate_files", SnipMateFileSource()) self._reinit()
def __init__(self, expand_trigger, forward_trigger, backward_trigger): self.expand_trigger = expand_trigger self.forward_trigger = forward_trigger self.backward_trigger = backward_trigger self._inner_state_up = False self._supertab_keys = None self._csnippets = [] self._buffer_filetypes = defaultdict(lambda: ['all']) self._vstate = VimState() self._visual_content = VisualContentPreserver() self._snippet_sources = [] self._snip_expanded_in_action = False self._inside_action = False self._last_inserted_char = '' self._added_snippets_source = AddedSnippetsSource() self.register_snippet_source('ultisnips_files', UltiSnipsFileSource()) self.register_snippet_source('added', self._added_snippets_source) enable_snipmate = '1' if _vim.eval("exists('g:UltiSnipsEnableSnipMate')") == '1': enable_snipmate = _vim.eval('g:UltiSnipsEnableSnipMate') if enable_snipmate == '1': self.register_snippet_source('snipmate_files', SnipMateFileSource()) self._should_update_textobjects = False self._should_reset_visual = False self._reinit()
def __init__(self, expand_trigger, forward_trigger, backward_trigger): self.expand_trigger = expand_trigger self.forward_trigger = forward_trigger self.backward_trigger = backward_trigger self._inner_mappings_in_place = False self._supertab_keys = None self._csnippets = [] self._buffer_filetypes = defaultdict(lambda: ['all']) self._vstate = VimState() self._visual_content = VisualContentPreserver() self._snippet_sources = [] self._added_snippets_source = AddedSnippetsSource() self.register_snippet_source('ultisnips_files', UltiSnipsFileSource()) self.register_snippet_source('added', self._added_snippets_source) enable_snipmate = True if _vim.eval("exists('g:UltiSnipsEnableSnipMate')") == '1': enable_snipmate = _vim.eval('g:UltiSnipsEnableSnipMate') if enable_snipmate == '1': self.register_snippet_source('snipmate_files', SnipMateFileSource()) self._reinit()
def __init__(self, expand_trigger, forward_trigger, backward_trigger): self.expand_trigger = expand_trigger self.forward_trigger = forward_trigger self.backward_trigger = backward_trigger self._inner_mappings_in_place = False self._supertab_keys = None self._csnippets = [] self._buffer_filetypes = defaultdict(lambda: ['all']) self._vstate = VimState() self._visual_content = VisualContentPreserver() self._snippet_sources = [] self._added_snippets_source = AddedSnippetsSource() self.register_snippet_source("ultisnips_files", UltiSnipsFileSource()) self.register_snippet_source("added", self._added_snippets_source) if _vim.eval("exists('g:UltiSnipsDisableSnipMate')") == "1": disable_snipmate = _vim.eval("g:UltiSnipsDisableSnipMate") else: disable_snipmate = "0" if disable_snipmate != "1": self.register_snippet_source("snipmate_files", SnipMateFileSource()) self._reinit()
def find_all_snippet_files(ft): """Returns all snippet files matching 'ft' in the given runtime path directory.""" if _vim.eval("exists('b:UltiSnipsSnippetDirectories')") == "1": snippet_dirs = _vim.eval("b:UltiSnipsSnippetDirectories") else: snippet_dirs = _vim.eval("g:UltiSnipsSnippetDirectories") if len(snippet_dirs) == 1 and os.path.isabs(snippet_dirs[0]): check_dirs = [''] else: check_dirs = _vim.eval("&runtimepath").split(',') patterns = ["%s.snippets", "%s_*.snippets", os.path.join("%s", "*")] ret = set() for rtp in check_dirs: for snippet_dir in snippet_dirs: if snippet_dir == "snippets": raise RuntimeError( "You have 'snippets' in UltiSnipsSnippetDirectories. This " "directory is reserved for snipMate snippets. Use another " "directory for UltiSnips snippets.") pth = os.path.realpath(os.path.expanduser( os.path.join(rtp, snippet_dir))) for pattern in patterns: for fn in glob.glob(os.path.join(pth, pattern % ft)): ret.add(fn) return ret
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", os.path.join("%s","*")] 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 _find_all_snippet_directories(): """Returns a list of the absolute path of all snippet directories to search.""" if _vim.eval("exists('b:UltiSnipsSnippetDirectories')") == '1': snippet_dirs = _vim.eval('b:UltiSnipsSnippetDirectories') else: snippet_dirs = _vim.eval('g:UltiSnipsSnippetDirectories') if len(snippet_dirs) == 1 and os.path.isabs(snippet_dirs[0]): return snippet_dirs all_dirs = [] check_dirs = _vim.eval('&runtimepath').split(',') for rtp in check_dirs: for snippet_dir in snippet_dirs: if snippet_dir == 'snippets': raise RuntimeError( "You have 'snippets' in UltiSnipsSnippetDirectories. This " 'directory is reserved for snipMate snippets. Use another ' 'directory for UltiSnips snippets.') pth = os.path.realpath(os.path.expanduser( os.path.join(rtp, snippet_dir))) if os.path.isdir(pth): all_dirs.append(pth) return all_dirs
def file_to_edit(self, ft): """ 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 """ 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 = [ os.path.realpath(os.path.expanduser(p)) for p in _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_for(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. """ if _vim.eval("exists('b:UltiSnipsSnippetDirectories')") == "1": snippet_dirs = _vim.eval("b:UltiSnipsSnippetDirectories") else: snippet_dirs = _vim.eval("g:UltiSnipsSnippetDirectories") base_snippets = os.path.realpath(os.path.join(_plugin_dir(), "UltiSnips")) ret = [] for rtp in _vim.eval("&runtimepath").split(','): for snippet_dir in snippet_dirs: if snippet_dir == "snippets": raise RuntimeError( "You have 'snippets' in UltiSnipsSnippetDirectories. This " "directory is reserved for snipMate snippets. Use another " "directory for UltiSnips snippets.") pth = os.path.realpath(os.path.expanduser( os.path.join(rtp, snippet_dir))) patterns = ["%s.snippets", "%s_*.snippets", os.path.join("%s", "*")] 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 _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.feedkeys(feedkey, mode)
def opt(self, option, default=None): # pylint:disable=no-self-use """Gets a Vim variable.""" if _vim.eval("exists('%s')" % option) == '1': try: return _vim.eval(option) except _vim.error: pass return default
def opt(self, option, default=None): """ Gets a Vim variable. """ if _vim.eval("exists('%s')" % option) == "1": try: return _vim.eval(option) except _vim.error: pass return default
def reset(self): """ Gets the spacing properties from Vim. """ self.sw = int(_vim.eval("&sw")) self.sts = int(_vim.eval("&sts")) self.et = _vim.eval("&expandtab") == "1" self.ts = int(_vim.eval("&ts")) # The amount added when pressing tab in insert mode self.ind_len = self.sts or self.ts
def _should_reverse_search_path(): """ If the user defined g:UltiSnipsDontReverseSearchPath then return True or False based on the value of that variable, else defer to _snippets_dir_is_before_plugin_dir to determine whether this is True or False. """ if _vim.eval("exists('g:UltiSnipsDontReverseSearchPath')") != "0": return _vim.eval("g:UltiSnipsDontReverseSearchPath") != "0" return not _snippets_dir_is_before_plugin_dir()
def _file_to_edit(self, requested_ft, bang): # pylint: disable=no-self-use """Returns a file to be edited for the given requested_ft. If 'bang' is empty only private files in g:UltiSnipsSnippetsDir are considered, otherwise all files are considered and the user gets to choose. """ # This method is not using self, but is called by UltiSnips.vim and is # therefore in this class because it is the facade to Vim. potentials = set() if _vim.eval("exists('g:UltiSnipsSnippetsDir')") == '1': snippet_dir = _vim.eval('g:UltiSnipsSnippetsDir') else: home = _vim.eval('$HOME') if platform.system() == 'Windows': snippet_dir = os.path.join(home, 'vimfiles', 'UltiSnips') elif _vim.eval("has('nvim')") == '1': xdg_home_config = _vim.eval('$XDG_CONFIG_HOME') or os.path.join(home, ".config") snippet_dir = os.path.join(xdg_home_config, 'nvim', 'UltiSnips') else: snippet_dir = os.path.join(home, '.vim', 'UltiSnips') filetypes = [] if requested_ft: filetypes.append(requested_ft) else: if bang: filetypes.extend(self._buffer_filetypes[_vim.buf.number]) else: filetypes.append(self._buffer_filetypes[_vim.buf.number][0]) for ft in filetypes: potentials.update(find_snippet_files(ft, snippet_dir)) potentials.add(os.path.join(snippet_dir, ft + '.snippets')) if bang: potentials.update(find_all_snippet_files(ft)) potentials = set(os.path.realpath(os.path.expanduser(p)) for p in potentials) if len(potentials) > 1: files = sorted(potentials) formatted = [as_unicode('%i: %s') % (i, escape(fn, '\\')) for i, fn in enumerate(files, 1)] file_to_edit = _ask_user(files, formatted) if file_to_edit is None: return '' else: file_to_edit = potentials.pop() dirname = os.path.dirname(file_to_edit) if not os.path.exists(dirname): os.makedirs(dirname) return file_to_edit
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 _parse_snippets(self, ft, filename): """Parse the 'filename' for the given 'ft' and watch it for changes in the future.""" self._file_hashes[filename] = _hash_file(filename) file_data = compatibility.open_ascii_file(filename, "r").read() for event, data in self._parse_snippet_file(file_data, filename): if event == "error": msg, line_index = data filename = _vim.eval("""fnamemodify(%s, ":~:.")""" % _vim.escape(filename)) raise SnippetSyntaxError(filename, line_index, msg) elif event == "clearsnippets": # TODO(sirver): clear snippets should clear for # more sources, not only ultisnips files. triggers, = data self._snippets[ft].clear_snippets(triggers) elif event == "extends": # TODO(sirver): extends information is more global # than one snippet source. filetypes, = data self._add_extending_info(ft, filetypes) elif event == "snippet": snippet, = data self._snippets[ft].add_snippet(snippet) else: assert False, "Unhandled %s: %r" % (event, data)
def _update(self, done, not_done): path = _vim.eval('expand("%")') if path is None: path = "" fn = os.path.basename(path) ct = self.current_text self._snip._reset(ct) local_d = self._locals local_d.update({ 't': _Tabs(self._parent), 'fn': fn, 'path': path, 'cur': ct, 'res': ct, 'i': self._snip.i, 'snip' : self._snip, }) compatible_exec(self._code, self._globals, local_d) rv = as_unicode(self._snip.rv if self._snip._rv_changed else as_unicode(local_d['res'])) if ct != rv: self.overwrite(rv) return False return True
def _track_change(self): self._should_update_textobjects = True try: inserted_char = _vim.as_unicode(_vim.eval('v:char')) except UnicodeDecodeError: return if sys.version_info >= (3, 0): if isinstance(inserted_char, bytes): return else: if not isinstance(inserted_char, unicode): return try: if inserted_char == '': before = _vim.buf.line_till_cursor if before and before[-1] == self._last_change[0] or \ self._last_change[1] != vim.current.window.cursor[0]: self._try_expand(autotrigger_only=True) finally: self._last_change = (inserted_char, vim.current.window.cursor[0]) if self._should_reset_visual and self._visual_content.mode == '': self._visual_content.reset() self._should_reset_visual = True
def _parse_snippets(self, ft, filename): """Parse the file 'filename' for the given 'ft' and watch it for changes in the future. 'file_data' can be injected in tests.""" current_snippet_priority = 0 self._snippets[ft].addfile(filename) file_data = open(filename, "r").read() for event, data in parse_snippets_file(file_data): if event == "error": msg, line_index = data filename = _vim.eval("""fnamemodify(%s, ":~:.")""" % _vim.escape(filename)) raise SnippetSyntaxError(filename, line_index, msg) elif event == "clearsnippets": # TODO(sirver): clear snippets should clear for # more providers, not only ultisnips files. triggers, = data self._snippets[ft].clear_snippets(triggers) elif event == "extends": # TODO(sirver): extends information is more global # than one snippet provider. filetypes, = data self._add_extending_info(ft, filetypes) elif event == "snippet": trigger, value, description, options, global_pythons = data self._snippets[ft].add_snippet( SnippetDefinition(current_snippet_priority, trigger, value, description, options, global_pythons), filename ) elif event == "priority": priority, = data current_snippet_priority = priority else: assert False, "Unhandled %s: %r" % (event, data)
def _update(self, done, buf): path = _vim.eval('expand("%")') or '' ct = self.current_text self._locals.update({ 't': _Tabs(self._parent), 'fn': os.path.basename(path), 'path': path, 'cur': ct, 'res': ct, 'snip': self._snip, }) self._snip._reset(ct) # pylint:disable=protected-access for code in self._codes: try: exec(code, self._locals) # pylint:disable=exec-used except Exception as e: e.snippet_code = code raise rv = as_unicode( self._snip.rv if self._snip._rv_changed # pylint:disable=protected-access else as_unicode(self._locals['res']) ) if ct != rv: self.overwrite(buf, rv) return False return True
def _handle_failure(self, trigger): """ Mainly make sure that we play well with SuperTab or pre-existing keymaps """ if trigger.lower() == _vim.eval("g:UltiSnipsExpandTrigger").lower() and _vim.eval("g:UltiSnipsExpandTriggerOverrides") != "": feedkey = "" + _vim.eval("g:UltiSnipsExpandTriggerOverrides") mode = "i" elif trigger.lower() == _vim.eval("g:UltiSnipsJumpForwardTrigger").lower() and _vim.eval("g:UltiSnipsJumpForwardTriggerOverrides") != "": feedkey = "" + _vim.eval("g:UltiSnipsJumpForwardTriggerOverrides") mode = "i" elif trigger.lower() == _vim.eval("g:UltiSnipsJumpBackwardTrigger").lower() and _vim.eval("g:UltiSnipsJumpBackwardTriggerOverrides") != "": feedkey = "" + _vim.eval("g:UltiSnipsJumpBackwardTriggerOverrides") mode = "i" elif trigger.lower() == "<tab>": feedkey = "\\" + trigger mode = "n" elif trigger.lower() == "<s-tab>": feedkey = "\\" + trigger mode = "n" 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"\<Plug>SuperTabForward" mode = 'n' elif idx == 1: feedkey = r"\<Plug>SuperTabBackward" mode = 'p' # Use remap mode so SuperTab mappings will be invoked. break if (feedkey == r"\<Plug>SuperTabForward" or feedkey == r"\<Plug>SuperTabBackward"): _vim.command('return SuperTab(%s)' % _vim.escape(mode)) elif feedkey: _vim.command('return %s' % _vim.escape(feedkey))
def conserve(self): sl, sc = map(int, (_vim.eval("""line("'<")"""), _vim.eval("""virtcol("'<")"""))) el, ec = map(int, (_vim.eval("""line("'>")"""), _vim.eval("""virtcol("'>")"""))) self._mode = _vim.eval("visualmode()") def _vim_line_with_eol(ln): return _vim.buf[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._text = text
def conserve(self): """Save the last visual selection ond the mode it was made in.""" sl, sbyte = map(int, (_vim.eval("""line("'<")"""), _vim.eval("""col("'<")"""))) el, ebyte = map(int, (_vim.eval("""line("'>")"""), _vim.eval("""col("'>")"""))) sc = byte2col(sl, sbyte - 1) ec = byte2col(el, ebyte - 1) self._mode = _vim.eval("visualmode()") _vim_line_with_eol = lambda ln: _vim.buf[ln] + "\n" if sl == el: text = _vim_line_with_eol(sl - 1)[sc : ec + 1] else: text = _vim_line_with_eol(sl - 1)[sc:] for cl in range(sl, el - 1): text += _vim_line_with_eol(cl) text += _vim_line_with_eol(el - 1)[: ec + 1] self._text = text
def _track_change(self): inserted_char = _vim.eval("v:char") try: if inserted_char == "": before = _vim.buf.line_till_cursor if before and before[-1] == self._last_inserted_char: self._try_expand(autotrigger_only=True) finally: self._last_inserted_char = inserted_char
def cursor_moved(self): self._vstate.remember_position() if _vim.eval("mode()") not in 'in': return if self._ignore_movements: self._ignore_movements = False return if self._csnippets: cstart = self._csnippets[0].start.line cend = self._csnippets[0].end.line + self._vstate.diff_in_buffer_length ct = _vim.buf[cstart:cend + 1] lt = self._vstate.remembered_buffer pos = _vim.buf.cursor lt_span = [0, len(lt)] ct_span = [0, len(ct)] initial_line = cstart # Cut down on lines searched for changes. Start from behind and # remove all equal lines. Then do the same from the front. if lt and ct: while (lt[lt_span[1]-1] == ct[ct_span[1]-1] and self._vstate.ppos.line < initial_line + lt_span[1]-1 and pos.line < initial_line + ct_span[1]-1 and (lt_span[0] < lt_span[1]) and (ct_span[0] < ct_span[1])): ct_span[1] -= 1 lt_span[1] -= 1 while (lt_span[0] < lt_span[1] and ct_span[0] < ct_span[1] and lt[lt_span[0]] == ct[ct_span[0]] and self._vstate.ppos.line >= initial_line and pos.line >= initial_line): ct_span[0] += 1 lt_span[0] += 1 initial_line += 1 ct_span[0] = max(0, ct_span[0] - 1) lt_span[0] = max(0, lt_span[0] - 1) initial_line = max(cstart, initial_line - 1) lt = lt[lt_span[0]:lt_span[1]] ct = ct[ct_span[0]:ct_span[1]] try: rv, es = guess_edit(initial_line, lt, ct, self._vstate) if not rv: lt = '\n'.join(lt) ct = '\n'.join(ct) es = diff(lt, ct, initial_line) self._csnippets[0].replay_user_edits(es) except IndexError: pass # Rather do nothing than throwing an error. It will be correct most of the time self._check_if_still_inside_snippet() if self._csnippets: self._csnippets[0].update_textobjects() self._vstate.remember_buffer(self._csnippets[0])
def _snippets_dir_is_before_plugin_dir(): """ Returns True if the snippets directory comes before the plugin directory in Vim's runtime path. False otherwise. """ paths = [ os.path.realpath(os.path.expanduser(p)).rstrip(os.path.sep) for p in _vim.eval("&runtimepath").split(',') ] home = _vim.eval("$HOME") def vim_path_index(suffix): path = os.path.realpath(os.path.join(home, suffix)).rstrip(os.path.sep) try: return paths.index(path) except ValueError: return -1 try: real_vim_path_index = max(vim_path_index(".vim"), vim_path_index("vimfiles")) plugin_path_index = paths.index(_plugin_dir()) return plugin_path_index < real_vim_path_index except ValueError: return False
def matches(self, before): """Returns True if this snippet matches 'before'.""" # If user supplies both "w" and "i", it should perhaps be an # error, but if permitted it seems that "w" should take precedence # (since matching at word boundary and within a word == matching at word # boundary). self._matched = '' words = _words_for_line(self._trigger, before) if 'r' in self._opts: match = self._re_match(before) elif 'w' in self._opts: words_len = len(self._trigger) words_prefix = words[:-words_len] words_suffix = words[-words_len:] match = (words_suffix == self._trigger) if match and words_prefix: # Require a word boundary between prefix and suffix. boundary_chars = escape(words_prefix[-1:] + words_suffix[:1], r'\"') match = _vim.eval( '"%s" =~# "\\\\v.<."' % boundary_chars) != '0' elif 'i' in self._opts: match = words.endswith(self._trigger) else: match = (words == self._trigger) # By default, we match the whole trigger if match and not self._matched: self._matched = self._trigger # Ensure the match was on a word boundry if needed if 'b' in self._opts and match: text_before = before.rstrip()[:-len(self._matched)] if text_before.strip(' \t') != '': self._matched = '' return False self._context = None if match and self._context_code: self._context = self._context_match() if not self.context: match = False return match
def could_match(self, trigger): """Return True if this snippet could match the (partial) 'trigger'.""" self._matched = '' # List all on whitespace. if trigger and trigger[-1] in (' ', '\t'): trigger = '' if trigger and trigger.rstrip() is not trigger: return False words = _words_for_line(self._trigger, trigger) if 'r' in self._opts: # Test for full match only match = self._re_match(trigger) elif 'w' in self._opts: # Trim non-empty prefix up to word boundary, if present. qwords = escape(words, r'\"') words_suffix = _vim.eval( 'substitute("%s", "\\\\v^.+<(.+)", "\\\\1", "")' % qwords) match = self._trigger.startswith(words_suffix) self._matched = words_suffix # TODO: list_snippets() function cannot handle partial-trigger # matches yet, so for now fail if we trimmed the prefix. if words_suffix != words: match = False elif 'i' in self._opts: # TODO: It is hard to define when a inword snippet could match, # therefore we check only for full-word trigger. match = self._trigger.startswith(words) else: match = self._trigger.startswith(words) # By default, we match the words from the trigger if match and not self._matched: self._matched = words # Ensure the match was on a word boundry if needed if 'b' in self._opts and match: text_before = trigger.rstrip()[:-len(self._matched)] if text_before.strip(' \t') != '': self._matched = '' return False return match
def _ask_user(a, formatted): """Asks the user using inputlist() and returns the selected element or None.""" try: rv = _vim.eval('inputlist(%s)' % _vim.escape(formatted)) if rv is None or rv == '0': return None rv = int(rv) if rv > len(a): rv = len(a) return a[rv - 1] except _vim.error: # Likely "invalid expression", but might be translated. We have no way # of knowing the exact error, therefore, we ignore all errors silently. return None except KeyboardInterrupt: return None
def could_match(self, trigger): self._matched = "" # List all on whitespace. if trigger and trigger[-1] in (" ", "\t"): trigger = "" if trigger and trigger.rstrip() is not trigger: return False words = self._words_for_line(trigger) if "r" in self._opts: # Test for full match only match = self._re_match(trigger) elif "w" in self._opts: # Trim non-empty prefix up to word boundary, if present. qwords = words.replace('"', '\\"') words_suffix = _vim.eval( 'substitute("%s", "\\\\v^.+<(.+)", "\\\\1", "")' % qwords) match = self._t.startswith(words_suffix) self._matched = words_suffix # TODO: list_snippets() function cannot handle partial-trigger # matches yet, so for now fail if we trimmed the prefix. if words_suffix != words: match = False elif "i" in self._opts: # TODO: It is hard to define when a inword snippet could match, # therefore we check only for full-word trigger. match = self._t.startswith(words) else: match = self._t.startswith(words) # By default, we match the words from the trigger if match and not self._matched: self._matched = words # Ensure the match was on a word boundry if needed if "b" in self._opts and match: text_before = trigger.rstrip()[:-len(self._matched)] if text_before.strip(" \t") != '': self._matched = "" return False return match
def snipmate_files_for(ft): """Returns all snipMate files we need to look at for 'ft'.""" if ft == "all": ft = "_" patterns = [ "%s.snippets" % ft, os.path.join(ft, "*.snippets"), os.path.join(ft, "*.snippet"), os.path.join(ft, "*/*.snippet"), ] ret = set() for rtp in _vim.eval("&runtimepath").split(','): path = os.path.realpath(os.path.expanduser( os.path.join(rtp, "snippets"))) for pattern in patterns: for fn in glob.glob(os.path.join(path, pattern)): ret.add(fn) return ret
def snipmate_files_for(ft): """Returns all snipMate files we need to look at for 'ft'.""" if ft == 'all': ft = '_' patterns = [ '%s.snippets' % ft, os.path.join(ft, '*.snippets'), os.path.join(ft, '*.snippet'), os.path.join(ft, '*/*.snippet'), ] ret = set() for rtp in _vim.eval('&runtimepath').split(','): path = os.path.realpath( os.path.expanduser(os.path.join(rtp, 'snippets'))) for pattern in patterns: for fn in glob.glob(os.path.join(path, pattern)): ret.add(fn) return ret
def _track_change(self): self._should_update_textobjects = True inserted_char = _vim.eval('v:char') try: if inserted_char == '': before = _vim.buf.line_till_cursor if before and before[-1] == self._last_change[0] or \ self._last_change[1] != vim.current.window.cursor[0]: self._try_expand(autotrigger_only=True) finally: self._last_change = (inserted_char, vim.current.window.cursor[0]) if self._should_reset_visual and self._visual_content.mode == '': self._visual_content.reset() self._should_reset_visual = True
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 = [ as_unicode("%i: %s") % (i+1,s.description) for i,s in enumerate(snippets)] try: rv = _vim.eval("inputlist(%s)" % _vim.escape(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: # Likely "invalid expression", but might be translated. We have no way # of knowing the exact error, therefore, we ignore all errors silently. return None
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 = [ as_unicode("%i: %s") % (i+1,s.description) for i,s in enumerate(snippets)] try: rv = _vim.eval("inputlist(%s)" % _vim.escape(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 matches(self, trigger): """Returns True if this snippet matches 'trigger'.""" # If user supplies both "w" and "i", it should perhaps be an # error, but if permitted it seems that "w" should take precedence # (since matching at word boundary and within a word == matching at word # boundary). self._matched = "" # Don't expand on whitespace if trigger and trigger.rstrip() != trigger: return False words = _words_for_line(self._trigger, trigger) if "r" in self._opts: match = self._re_match(trigger) elif "w" in self._opts: words_len = len(self._trigger) words_prefix = words[:-words_len] words_suffix = words[-words_len:] match = (words_suffix == self._trigger) if match and words_prefix: # Require a word boundary between prefix and suffix. boundary_chars = escape(words_prefix[-1:] + \ words_suffix[:1], r'\"') match = _vim.eval( '"%s" =~# "\\\\v.<."' % boundary_chars) != '0' elif "i" in self._opts: match = words.endswith(self._trigger) else: match = (words == self._trigger) # By default, we match the whole trigger if match and not self._matched: self._matched = self._trigger # Ensure the match was on a word boundry if needed if "b" in self._opts and match: text_before = trigger.rstrip()[:-len(self._matched)] if text_before.strip(" \t") != '': self._matched = "" return False return match
def snippet_sources(self): # a snippet source is anything having a .snippets method returning a # list of snippets list = [] snippet_files_by_type = _vim.eval('call(g:UltiSnips.SnippetFilesForCurrentExpansion, [&filetype])') # allow UltiSnips overriding snipmate : TODO which order is correct? for type_ in ["UltiSnips", "snipmate"]: if type_ in snippet_files_by_type: for file in snippet_files_by_type[type_]: if not file in self.snippet_file_cache: self.snippet_file_cache[file] = SnippetFileCache(file, type_) list.append(self.snippet_file_cache[file]) # TODO: add additional snippet sources # TODO: allow post processing? # now if we have errors tell the user by populating quickfix or error list return list
def _parse_snippets(self, ft, filename): """Parse the 'filename' for the given 'ft'.""" file_data = compatibility.open_ascii_file(filename, 'r').read() self._snippets[ft] # Make sure the dictionary exists for event, data in self._parse_snippet_file(file_data, filename): if event == 'error': msg, line_index = data filename = _vim.eval("""fnamemodify(%s, ":~:.")""" % _vim.escape(filename)) raise SnippetSyntaxError(filename, line_index, msg) elif event == 'clearsnippets': priority, triggers = data self._snippets[ft].clear_snippets(priority, triggers) elif event == 'extends': # TODO(sirver): extends information is more global # than one snippet source. filetypes, = data self.update_extends(ft, filetypes) elif event == 'snippet': snippet, = data self._snippets[ft].add_snippet(snippet) else: assert False, 'Unhandled %s: %r' % (event, data)
def _update(self, done): path = _vim.eval('expand("%")') or '' ct = self.current_text self._locals.update({ 't': _Tabs(self._parent), 'fn': os.path.basename(path), 'path': path, 'cur': ct, 'res': ct, 'snip': self._snip, }) self._snip._reset(ct) # pylint:disable=protected-access for code in self._codes: exec(code, self._locals) # pylint:disable=exec-used rv = as_unicode(self._snip.rv if self._snip._rv_changed # pylint:disable=protected-access else as_unicode(self._locals['res'])) if ct != rv: self.overwrite(rv) return False return True
def add_buffer_filetypes(self, ft): """ Checks for changes in the list of snippet files or the contents of the snippet files and reloads them if necessary. """ buf_fts = self._filetypes[_vim.buf.nr] idx = -1 fts = ft.split(".") # eruby subfiletype if 'eruby' in fts: subft = str(_vim.eval('get(b:,"eruby_subtype", "")')) if subft != '': fts.append(subft) for ft in fts: ft = ft.strip() if not ft: continue try: idx = buf_fts.index(ft) except ValueError: self._filetypes[_vim.buf.nr].insert(idx + 1, ft) idx += 1 self._ensure_all_loaded()
def _parse_snippets(self, ft, filename): """Parse the 'filename' for the given 'ft' and watch it for changes in the future.""" self._file_hashes[filename] = _hash_file(filename) file_data = compatibility.open_ascii_file(filename, "r").read() for event, data in self._parse_snippet_file(file_data, filename): if event == "error": msg, line_index = data filename = _vim.eval("""fnamemodify(%s, ":~:.")""" % _vim.escape(filename)) raise SnippetSyntaxError(filename, line_index, msg) elif event == "clearsnippets": priority, triggers = data self._snippets[ft].clear_snippets(priority, triggers) elif event == "extends": # TODO(sirver): extends information is more global # than one snippet source. filetypes, = data self.update_extends(ft, filetypes) elif event == "snippet": snippet, = data self._snippets[ft].add_snippet(snippet) else: assert False, "Unhandled %s: %r" % (event, data)
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. """ if _vim.eval("exists('b:UltiSnipsSnippetDirectories')") == "1": snippet_dirs = _vim.eval("b:UltiSnipsSnippetDirectories") else: 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", os.path.join("%s", "*") ] 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 __init__(self): pos = _vim.buf.cursor self._mode = _vim.eval('mode()') Position.__init__(self, pos.line, pos.col)
def _cursor_moved(self): """Called whenever the cursor moved.""" self._should_update_textobjects = False if not self._csnippets and self._inner_state_up: self._teardown_inner_state() self._vstate.remember_position() if _vim.eval('mode()') not in 'in': return if self._ignore_movements: self._ignore_movements = False return if self._csnippets: cstart = self._csnippets[0].start.line cend = self._csnippets[0].end.line + \ self._vstate.diff_in_buffer_length ct = _vim.buf[cstart:cend + 1] lt = self._vstate.remembered_buffer pos = _vim.buf.cursor lt_span = [0, len(lt)] ct_span = [0, len(ct)] initial_line = cstart # Cut down on lines searched for changes. Start from behind and # remove all equal lines. Then do the same from the front. if lt and ct: while (lt[lt_span[1] - 1] == ct[ct_span[1] - 1] and self._vstate.ppos.line < initial_line + lt_span[1] - 1 and pos.line < initial_line + ct_span[1] - 1 and (lt_span[0] < lt_span[1]) and (ct_span[0] < ct_span[1])): ct_span[1] -= 1 lt_span[1] -= 1 while (lt_span[0] < lt_span[1] and ct_span[0] < ct_span[1] and lt[lt_span[0]] == ct[ct_span[0]] and self._vstate.ppos.line >= initial_line and pos.line >= initial_line): ct_span[0] += 1 lt_span[0] += 1 initial_line += 1 ct_span[0] = max(0, ct_span[0] - 1) lt_span[0] = max(0, lt_span[0] - 1) initial_line = max(cstart, initial_line - 1) lt = lt[lt_span[0]:lt_span[1]] ct = ct[ct_span[0]:ct_span[1]] try: rv, es = guess_edit(initial_line, lt, ct, self._vstate) if not rv: lt = '\n'.join(lt) ct = '\n'.join(ct) es = diff(lt, ct, initial_line) self._csnippets[0].replay_user_edits(es, self._ctab) except IndexError: # Rather do nothing than throwing an error. It will be correct # most of the time pass self._check_if_still_inside_snippet() if self._csnippets: self._csnippets[0].update_textobjects() self._vstate.remember_buffer(self._csnippets[0])
def guess_edit(initial_line, last_text, current_text, vim_state): """ Try to guess what the user might have done by heuristically looking at cursor movement, number of changed lines and if they got longer or shorter. This will detect most simple movements like insertion, deletion of a line or carriage return. 'initial_text' is the index of where the comparison starts, 'last_text' is the last text of the snippet, 'current_text' is the current text of the snippet and 'vim_state' is the cached vim state. Returns (True, edit_cmds) when the edit could be guessed, (False, None) otherwise. """ if not len(last_text) and not len(current_text): return True, () pos = vim_state.pos ppos = vim_state.ppos # All text deleted? if (len(last_text) and (not current_text or (len(current_text) == 1 and not current_text[0]))): es = [] if not current_text: current_text = [''] for i in last_text: es.append(("D", initial_line, 0, i)) es.append(("D", initial_line, 0, "\n")) es.pop() # Remove final \n because it is not really removed if is_complete_edit(initial_line, last_text, current_text, es): return True, es if ppos.mode == 'v': # Maybe selectmode? sv = list(map(int, _vim.eval("""getpos("'<")"""))) sv = Position(sv[1] - 1, sv[2] - 1) ev = list(map(int, _vim.eval("""getpos("'>")"""))) ev = Position(ev[1] - 1, ev[2] - 1) if "exclusive" in _vim.eval("&selection"): ppos.col -= 1 # We want to be inclusive, sorry. ev.col -= 1 es = [] if sv.line == ev.line: es.append(("D", sv.line, sv.col, last_text[sv.line - initial_line][sv.col:ev.col + 1])) if sv != pos and sv.line == pos.line: es.append( ("I", sv.line, sv.col, current_text[sv.line - initial_line][sv.col:pos.col + 1])) if is_complete_edit(initial_line, last_text, current_text, es): return True, es if pos.line == ppos.line: if len(last_text) == len(current_text): # Movement only in one line llen = len(last_text[ppos.line - initial_line]) clen = len(current_text[pos.line - initial_line]) if ppos < pos and clen > llen: # maybe only chars have been added es = (("I", ppos.line, ppos.col, current_text[ppos.line - initial_line][ppos.col:pos.col]), ) if is_complete_edit(initial_line, last_text, current_text, es): return True, es if clen < llen: if ppos == pos: # 'x' or DEL or dt or something es = (("D", pos.line, pos.col, last_text[ppos.line - initial_line][ppos.col:ppos.col + (llen - clen)]), ) if is_complete_edit(initial_line, last_text, current_text, es): return True, es if pos < ppos: # Backspacing or dT dF? es = (("D", pos.line, pos.col, last_text[pos.line - initial_line][pos.col:pos.col + llen - clen]), ) if is_complete_edit(initial_line, last_text, current_text, es): return True, es elif len(current_text) < len(last_text): # where some lines deleted? (dd or so) es = [] for i in range(len(last_text) - len(current_text)): es.append( ("D", pos.line, 0, last_text[pos.line - initial_line + i])) es.append(("D", pos.line, 0, '\n')) if is_complete_edit(initial_line, last_text, current_text, es): return True, es else: # Movement in more than one line if ppos.line + 1 == pos.line and pos.col == 0: # Carriage return? es = (("I", ppos.line, ppos.col, "\n"), ) if is_complete_edit(initial_line, last_text, current_text, es): return True, es return False, None
def basename(self): """ The filename without extension. """ return _vim.eval('expand("%:t:r")') or ""
def path(self): """The path to the file""" return _vim.eval('expand("%")') or ''
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 fn(self): """ The filename. """ return _vim.eval('expand("%:t")') or ""
def basename(self): # pylint:disable=no-self-use """The filename without extension.""" return _vim.eval('expand("%:t:r")') or ''
def reset(self): """ Gets the spacing properties from Vim. """ self.sw = int(_vim.eval("&sw")) self.sts = int(_vim.eval("&sts")) self.et = (_vim.eval("&expandtab") == "1") self.ts = int(_vim.eval("&ts"))
def fn(self): # pylint:disable=no-self-use,invalid-name """The filename.""" return _vim.eval('expand("%:t")') or ''
def __init__(self): pos = _vim.buf.cursor self._mode = _vim.eval("mode()") self._visualmode = _vim.eval("visualmode()") Position.__init__(self, pos.line, pos.col)
def _error(self, msg): fn = _vim.eval("""fnamemodify(%s, ":~:.")""" % _vim.escape(self._fn)) self._sm._error("%s in %s(%d)" % (msg, fn, self._idx + 1))
def reset(self): """ Gets the spacing properties from Vim. """ self.shiftwidth = int( _vim.eval("exists('*shiftwidth') ? shiftwidth() : &shiftwidth")) self._expandtab = (_vim.eval("&expandtab") == "1") self._tabstop = int(_vim.eval("&tabstop"))
def _update(self, done, not_done): self.overwrite(_vim.eval(self._code)) return True