Exemplo n.º 1
0
    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()
Exemplo n.º 2
0
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)
Exemplo n.º 3
0
    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
Exemplo n.º 4
0
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
Exemplo n.º 5
0
    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
Exemplo n.º 6
0
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, [])
Exemplo n.º 7
0
    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
Exemplo n.º 8
0
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