def __init__(self, parent, indent, initial_text, start=None, end=None): if start is None: start = Position(0, 0) if end is None: end = Position(0, 0) TextObject.__init__(self, parent, start, end, initial_text) _TOParser(self, initial_text, indent).parse() # Check if we have a zero Tab, if not, add one at the end if isinstance(parent, TabStop): if not parent.no == 0: # We are recursively called, if we have a zero tab, remove it. if 0 in self._tabstops: self._tabstops[0].current_text = "" del self._tabstops[0] else: self.update() if 0 not in self._tabstops: delta = self._end - self._start col = self.end.col if delta.line == 0: col -= self.start.col start = Position(delta.line, col) end = Position(delta.line, col) ts = TabStop(0, self, start, end, "") self._add_tabstop(0, ts) self.update()
def calc_end(self, start): text = self._lines[:] if len(text) == 1: new_end = start + Position(0, len(text[0])) else: new_end = Position(start.line + len(text) - 1, len(text[-1])) return new_end
def _replace(self, start, end, content, first_line, last_line): text = content[:] if len(text) == 1: arr = [first_line + text[0] + last_line] new_end = start + Position(0, len(text[0])) else: arr = [ first_line + text[0] ] + \ text[1:-1] + \ [ text[-1] + last_line ] new_end = Position(start.line + len(text) - 1, len(text[-1])) self[start.line:end.line + 1] = arr return new_end
def update(self): line, col = vim_cursor() line -= 1 abs_pos = Position(line,col) if self._abs_pos: self._moved = abs_pos - self._abs_pos self._abs_pos = abs_pos # Update buffer infos cols = len(as_unicode(vim.current.buffer[line])) if self._cols: self._dcols = cols - self._cols self._cols = cols lines = len(vim.current.buffer) if self._lines: self._dlines = lines - self._lines self._lines = lines # Check if the buffer has changed in any ways self._text_changed = False # does it have more lines? if self._dlines: self._text_changed = True # did we stay in the same line and it has more columns now? elif not self.moved.line and self._dcols: self._text_changed = True # If the length didn't change but we moved a column, check if # the char under the cursor has changed (might be one char tab). elif self.moved.col == 1: self._text_changed = self._cline != as_unicode(vim.current.buffer[line]) self._lline = self._cline self._cline = as_unicode(vim.current.buffer[line])
def abs_start(self): if self._parent: ps = self._parent.abs_start if self._start.line == 0: return ps + self._start else: return Position(ps.line + self._start.line, self._start.col) return self._start
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 __init__(self): self._abs_pos = None self._moved = Position(0,0) self._lines = None self._dlines = None self._cols = None self._dcols = None self._cline = None self._lline = None self._text_changed = None
def _update_childs(childs): for idx, c in childs: oldend = Position(c.end.line, c.end.col) new_end = c.update() moved_lines = new_end.line - oldend.line moved_cols = new_end.col - oldend.col self._current_text.replace_text(c.start, oldend, c._current_text) self._move_textobjects_behind(c.start, oldend, moved_lines, moved_cols, idx)
def update(self): for idx, c in enumerate(self._childs): oldend = Position(c.end.line, c.end.col) new_end = c.update() moved_lines = new_end.line - oldend.line moved_cols = new_end.col - oldend.col self._current_text.replace_text(c.start, oldend, c._current_text) self._move_textobjects_behind(c.start, oldend, moved_lines, moved_cols, idx) self._do_update() new_end = self._current_text.calc_end(self._start) self._end = new_end return new_end
def list_snippets(self): filetypes = self._ensure_snippets_loaded() # TODO: this code is duplicated below filetypes = vim.eval("&filetype").split(".") + ["all"] lineno, col = vim.current.window.cursor line = vim.current.line before, after = line[:col], line[col:] word = '' if len(before): word = before.split()[-1] found_snippets = [] for ft in filetypes[::-1]: found_snippets += self._find_snippets(ft, word, True) if len(found_snippets) == 0: return True display = [ "%i %s" % (idx + 1, s.description) for idx, s in enumerate(found_snippets) ] # TODO: this code is also mirrored below try: rv = vim.eval("inputlist(%s)" % display) if rv is None or rv == '0': return True rv = int(rv) if rv > len(found_snippets): rv = len(found_snippets) snippet = found_snippets[rv - 1] except: # vim.error, e: if str(e) == 'invalid expression': return True raise # TODO: even more code duplicated below # Adjust before, maybe the trigger is not the complete word text_before = before.rstrip()[:-len(word)] text_before += word[:-len(snippet.trigger)] self._expect_move_wo_change = True if self._cs: # Determine position pos = self._vstate.pos p_start = self._ctab.abs_start if pos.line == p_start.line: end = Position(0, pos.col - p_start.col) else: end = Position(pos.line - p_start.line, pos.col) start = Position(end.line, end.col - len(snippet.trigger)) si = snippet.launch(text_before, self._ctab, start, end) self._update_vim_buffer() if si.has_tabs: self._csnippets.append(si) self._jump() else: self._vb = VimBuffer(text_before, after) start = Position(lineno - 1, len(text_before)) self._csnippets.append(snippet.launch(text_before, None, start)) self._vb.replace_lines(lineno - 1, lineno - 1, self._cs._current_text) self._jump() return True
class SnippetManager(object): def __init__(self): self._vstate = VimState() self._supertab_keys = None self.reset() def reset(self, test_error=False): self._test_error = test_error self._snippets = {} self._csnippets = [] self._reinit() def jump_forwards(self): if not self._jump(): return self._handle_failure(self.forward_trigger) def jump_backwards(self): if not self._jump(True): return self._handle_failure(self.backward_trigger) def expand(self): if not self._try_expand(): self._handle_failure(self.expand_trigger) def list_snippets(self): filetypes = self._ensure_snippets_loaded() # TODO: this code is duplicated below filetypes = vim.eval("&filetype").split(".") + ["all"] lineno, col = vim.current.window.cursor line = vim.current.line before, after = line[:col], line[col:] word = '' if len(before): word = before.split()[-1] found_snippets = [] for ft in filetypes[::-1]: found_snippets += self._find_snippets(ft, word, True) if len(found_snippets) == 0: return True display = [ "%i %s" % (idx + 1, s.description) for idx, s in enumerate(found_snippets) ] # TODO: this code is also mirrored below try: rv = vim.eval("inputlist(%s)" % display) if rv is None or rv == '0': return True rv = int(rv) if rv > len(found_snippets): rv = len(found_snippets) snippet = found_snippets[rv - 1] except: # vim.error, e: if str(e) == 'invalid expression': return True raise # TODO: even more code duplicated below # Adjust before, maybe the trigger is not the complete word text_before = before.rstrip()[:-len(word)] text_before += word[:-len(snippet.trigger)] self._expect_move_wo_change = True if self._cs: # Determine position pos = self._vstate.pos p_start = self._ctab.abs_start if pos.line == p_start.line: end = Position(0, pos.col - p_start.col) else: end = Position(pos.line - p_start.line, pos.col) start = Position(end.line, end.col - len(snippet.trigger)) si = snippet.launch(text_before, self._ctab, start, end) self._update_vim_buffer() if si.has_tabs: self._csnippets.append(si) self._jump() else: self._vb = VimBuffer(text_before, after) start = Position(lineno - 1, len(text_before)) self._csnippets.append(snippet.launch(text_before, None, start)) self._vb.replace_lines(lineno - 1, lineno - 1, self._cs._current_text) self._jump() return True def expand_or_jump(self): """ This function is used for people who wants to have the same trigger for expansion and forward jumping. It first tries to expand a snippet, if this fails, it tries to jump forward. """ rv = self._try_expand() if not rv: rv = self._jump() if not rv: self._handle_failure(self.expand_trigger) def add_snippet(self, trigger, value, descr, options, ft="all"): if ft not in self._snippets: self._snippets[ft] = _SnippetDictionary() l = self._snippets[ft].add_snippet( Snippet(trigger, value, descr, options)) def clear_snippets(self, triggers=[], ft="all"): if ft in self._snippets: self._snippets[ft].clear_snippets(triggers) def add_extending_info(self, ft, parents): if ft not in self._snippets: self._snippets[ft] = _SnippetDictionary() sd = self._snippets[ft] for p in parents: if p in sd.extends: continue sd.extends.append(p) def backspace_while_selected(self): """ This is called when backspace was used while a placeholder was selected. """ # BS was called in select mode if self._cs and (self._span_selected is not None): # This only happens when a default value is delted using backspace vim.command(r'call feedkeys("i")') self._chars_entered('') else: vim.command(r'call feedkeys("\<BS>")') def cursor_moved(self): self._vstate.update() if not self._vstate.buf_changed and not self._expect_move_wo_change: self._check_if_still_inside_snippet() if not self._ctab: return if self._vstate.buf_changed and self._ctab: # Detect a carriage return if self._vstate.moved.col <= 0 and self._vstate.moved.line == 1: # Multiple things might have happened: either the user entered # a newline character or pasted some text which means we have # to copy everything he entered on the last line and keep the # indent vim chose for this line. lline = vim.current.buffer[self._vstate.ppos.line] # Another thing that might have happened is that a word # wrapped, in this case the last line is shortened and we must # delete what Vim deleted there line_was_shortened = len(self._vstate.last_line) > len(lline) # Another thing that might have happened is that vim has # adjusted the indent of the last line and therefore the line # effectivly got longer. This means a newline was entered and # we quite definitivly do not want the indent that vim added line_was_lengthened = len(lline) > len(self._vstate.last_line) user_didnt_enter_newline = len(lline) != self._vstate.ppos.col cline = vim.current.buffer[self._vstate.pos.line] if line_was_lengthened: this_entered = vim.current.line[:self._vstate.pos.col] self._chars_entered('\n' + cline + this_entered, 1) if line_was_shortened and user_didnt_enter_newline: self._backspace(len(self._vstate.last_line) - len(lline)) self._chars_entered('\n' + cline, 1) else: pentered = lline[self._vstate.ppos.col:] this_entered = vim.current.line[:self._vstate.pos.col] self._chars_entered(pentered + '\n' + this_entered) elif self._vstate.moved.line == 0 and self._vstate.moved.col < 0: # Some deleting was going on self._backspace(-self._vstate.moved.col) elif self._vstate.moved.line < 0: # Backspace over line end self._backspace(1) else: line = vim.current.line chars = line[self._vstate.pos.col - self._vstate.moved.col:self._vstate.pos.col] self._chars_entered(chars) self._expect_move_wo_change = False def entered_insert_mode(self): self._vstate.update() if self._cs and self._vstate.has_moved: self._reinit() self._csnippets = [] ################################### # Private/Protect Functions Below # ################################### def _error(self, msg): msg = _vim_quote("UltiSnips: " + msg) if self._test_error: msg = msg.replace('"', r'\"') msg = msg.replace('|', r'\|') vim.command("let saved_pos=getpos('.')") vim.command("$:put =%s" % msg) vim.command("call setpos('.', saved_pos)") elif False: vim.command("echohl WarningMsg") vim.command("echomsg %s" % msg) vim.command("echohl None") else: vim.command("echoerr %s" % msg) def _reinit(self): self._ctab = None self._span_selected = None self._expect_move_wo_change = False def _check_if_still_inside_snippet(self): # Cursor moved without input. self._ctab = None # Did we leave the snippet with this movement? if self._cs and not (self._vstate.pos in self._cs.abs_span): self._csnippets.pop() self._reinit() self._check_if_still_inside_snippet() def _jump(self, backwards=False): jumped = False if self._cs: self._expect_move_wo_change = True self._ctab = self._cs.select_next_tab(backwards) if self._ctab: self._vstate.select_span(self._ctab.abs_span) self._span_selected = self._ctab.abs_span jumped = True if self._ctab.no == 0: self._ctab = None self._csnippets.pop() self._vstate.update() else: # This really shouldn't happen, because a snippet should # have been popped when its final tabstop was used. # Cleanup by removing current snippet and recursing. self._csnippets.pop() jumped = self._jump(backwards) return jumped def _handle_failure(self, trigger): """ Mainly make sure that we play well with SuperTab """ if trigger.lower() == "<tab>": feedkey = "\\" + trigger else: feedkey = None mode = "n" if not self._supertab_keys: if vim.eval("exists('g:SuperTabMappingForward')") != "0": self._supertab_keys = ( vim.eval("g:SuperTabMappingForward"), vim.eval("g:SuperTabMappingBackward"), ) else: self._supertab_keys = ['', ''] for idx, sttrig in enumerate(self._supertab_keys): if trigger.lower() == sttrig.lower(): if idx == 0: feedkey = r"\<c-n>" elif idx == 1: feedkey = r"\<c-p>" # Use remap mode so SuperTab mappings will be invoked. mode = "m" break if feedkey: vim.command(r'call feedkeys("%s", "%s")' % (feedkey, mode)) def _ensure_snippets_loaded(self): filetypes = vim.eval("&filetype").split(".") + ["all"] for ft in filetypes[::-1]: if len(ft) and ft not in self._snippets: self._load_snippets_for(ft) return filetypes def _try_expand(self): filetypes = self._ensure_snippets_loaded() self._expect_move_wo_change = False lineno, col = vim.current.window.cursor if col == 0: return False line = vim.current.line if col > 0 and line[col - 1] in string.whitespace: return False # Get the word to the left of the current edit position before, after = line[:col], line[col:] word = before.split()[-1] found_snippets = [] for ft in filetypes[::-1]: found_snippets += self._find_snippets(ft, word) # Search if any of the snippets overwrites the previous snippets = [] for s in found_snippets: if s.overwrites_previous: snippets = [] snippets.append(s) # Check if there are any only whitespace in front snippets text_before = before.rstrip()[:-len(word)] if text_before.strip(" \t") != '': snippets = [s for s in snippets if not s.needs_ws_in_front] if not len(snippets): # No snippet found return False elif len(snippets) == 1: snippet, = snippets else: display = repr([ "%i: %s" % (i + 1, s.description) for i, s in enumerate(snippets) ]) try: rv = vim.eval("inputlist(%s)" % display) if rv is None or rv == '0': return True rv = int(rv) if rv > len(snippets): rv = len(snippets) snippet = snippets[rv - 1] except vim.error, e: if str(e) == 'invalid expression': return True raise # Adjust before, maybe the trigger is not the complete word text_before += word[:-len(snippet.trigger)] self._expect_move_wo_change = True if self._cs: # Determine position pos = self._vstate.pos p_start = self._ctab.abs_start if pos.line == p_start.line: end = Position(0, pos.col - p_start.col) else: end = Position(pos.line - p_start.line, pos.col) start = Position(end.line, end.col - len(snippet.trigger)) si = snippet.launch(text_before, self._ctab, start, end) self._update_vim_buffer() if si.has_tabs: self._csnippets.append(si) self._jump() else: self._vb = VimBuffer(text_before, after) start = Position(lineno - 1, len(text_before)) self._csnippets.append(snippet.launch(text_before, None, start)) self._vb.replace_lines(lineno - 1, lineno - 1, self._cs._current_text) self._jump() return True
def replace_lines(self, fline, eline, content): start = Position(fline, 0) end = Position(eline, 100000) return self._replace(start, end, content, self._bf, self._af)
def __init__(self, start): end = Position(start.line, start.col) TextObject.__init__(self, None, start, end)
def _get_pos(s, pos): line_idx = s[:pos].count('\n') line_start = s[:pos].rfind('\n') + 1 start_in_line = pos - line_start return Position(line_idx, start_in_line)
def pos(self): return Position(self._line, self._col)