UltiSnips/PySnipEmu.py

471 lines
14 KiB
Python
Raw Normal View History

2009-06-23 08:45:04 -04:00
#!/usr/bin/env python
# encoding: utf-8
import vim
import string
import re
2009-07-01 11:11:33 -04:00
def debug(s):
f = open("/tmp/file.txt","a")
f.write(s+'\n')
f.close()
def _replace_text_in_buffer( start, end, textblock ):
# debug("Got start: %s" % start )
# debug("Got end: %s" % end )
# debug("Got text: '%s'" % textblock )
2009-07-02 05:58:46 -04:00
first_line = vim.current.buffer[start.line][:start.col]
last_line = vim.current.buffer[end.line][end.col:]
2009-07-02 05:58:46 -04:00
# We do not use splitlines() here because it handles cases like 'text\n'
# differently than we want it here
text = textblock.replace('\r','').split('\n')
2009-07-02 05:58:46 -04:00
if not len(text):
new_end = Position(start.line, start.col)
2009-07-02 05:58:46 -04:00
arr = [ first_line + last_line ]
elif len(text) == 1:
arr = [ first_line + text[0] + last_line ]
new_end = Position(start.line, len(arr[0])-len(last_line))
2009-07-02 05:58:46 -04:00
else:
arr = [ first_line + text[0] ] + \
text[1:-1] + \
[ text[-1] + last_line ]
new_end = Position(start.line + len(arr)-1, len(arr[-1])-len(last_line))
2009-07-02 05:58:46 -04:00
# debug("Trying to write:")
# debug("%s" % (arr,))
vim.current.buffer[start.line:end.line+1] = arr
# debug("done writing")
2009-07-02 05:58:46 -04:00
return new_end
2009-07-02 05:58:46 -04:00
class Position(object):
def __init__(self, line, col):
self.line = line
self.col = col
def col():
def fget(self):
return self._col
def fset(self, value):
if value < 0:
raise RuntimeError, "Invalid Column: %i" % col
self._col = value
return locals()
col = property(**col())
def line():
doc = "Zero base line numbers"
def fget(self):
return self._line
def fset(self, value):
if value < 0:
raise RuntimeError, "Invalid Line: %i" % line
self._line = value
return locals()
line = property(**line())
def __add__(self,pos):
if not isinstance(pos,Position):
raise TypeError("unsupported operand type(s) for +: " \
"'Position' and %s" % type(pos))
return Position(self.line + pos.line, self.col + pos.col)
def __sub__(self,pos):
if not isinstance(pos,Position):
raise TypeError("unsupported operand type(s) for +: " \
"'Position' and %s" % type(pos))
return Position(self.line - pos.line, self.col - pos.col)
def __repr__(self):
return "(%i,%i)" % (self._line, self._col)
class TextObject(object):
"""
This base class represents any object in the text
that has a span in any ways
"""
def __init__(self, start, end):
self._parent = None
self._start = start
self._end = end
def parent():
doc = "The parent TextObject this TextObject resides in"
def fget(self):
return self._parent
def fset(self, value):
self._parent = value
return locals()
parent = property(**parent())
@property
def start(self):
return self._start
@property
def end(self):
return self._end
2009-07-01 11:11:33 -04:00
class Mirror(TextObject):
"""
A Mirror object mirrors a TabStop that is, text is repeated here
"""
def __init__(self, ts, idx, start_col):
start = Position(idx,start_col)
end = Position(idx,start_col)
TextObject.__init__(self, start, end)
2009-07-01 11:11:33 -04:00
self._tabstop = ts
2009-07-01 09:20:40 -04:00
@property
def tabstop(self):
return self._tabstop
2009-07-01 09:20:40 -04:00
2009-07-01 14:14:54 -04:00
def update(self,ts):
if ts != self._tabstop:
return 0
new_end = _replace_text_in_buffer(
self._parent.start + self._start,
self._parent.start + self._end,
self._tabstop.current_text
)
new_end -= self._parent.start
2009-07-01 14:14:54 -04:00
oldcolspan = self.end.col - self.start.col
oldlinespan = self.end.line - self.start.line
self._end = new_end
newcolspan = self.end.col - self.start.col
newlinespan = self.end.line - self.start.line
2009-07-01 14:14:54 -04:00
return newlinespan-oldlinespan, newcolspan-oldcolspan
2009-07-01 14:14:54 -04:00
2009-07-01 11:11:33 -04:00
class TabStop(TextObject):
"""
This is the most important TextObject. A TabStop is were the cursor
comes to rest when the user taps through the Snippet.
"""
2009-07-01 14:14:54 -04:00
def __init__(self, idx, span, default_text = ""):
start = Position(idx,span[0])
end = Position(idx,span[1])
TextObject.__init__(self, start, end)
2009-07-01 11:11:33 -04:00
2009-07-01 09:20:40 -04:00
self._ct = default_text
def current_text():
def fget(self):
return self._ct
def fset(self, text):
self._ct = text
text = text.replace('\r','').split('\n')
oldlinespan = self.end.line - self.start.line
oldcolspan = self.end.col - self.start.col
new_end = self._start + Position(len(text) - 1, len(text[-1]))
newlinespan = new_end.line - self.start.line
newcolspan = new_end.col - self.start.col
moved_lines = newlinespan - oldlinespan
moved_cols = newcolspan - oldcolspan
self._parent._move_textobjects_behind(moved_lines, moved_cols, self)
self._end = new_end
self._parent._update_mirrors(self)
2009-07-01 09:20:40 -04:00
return locals()
current_text = property(**current_text())
def select(self):
lineno, col = self._parent.start.line, self._parent.start.col
2009-07-01 14:27:28 -04:00
newline = lineno + self._start.line
newcol = self._start.col
2009-07-01 17:09:20 -04:00
2009-07-01 14:27:28 -04:00
if newline == lineno:
2009-07-01 17:09:20 -04:00
newcol += col
2009-07-01 14:27:28 -04:00
vim.current.window.cursor = newline + 1, newcol
2009-07-01 14:27:28 -04:00
# Select the word
# Depending on the current mode and position, we
# might need to move escape out of the mode and this
# will move our cursor one left
2009-07-01 17:09:20 -04:00
if len(self._ct) > 0:
2009-07-01 14:27:28 -04:00
if newcol != 0 and vim.eval("mode()") == 'i':
move_one_right = "l"
else:
move_one_right = ""
vim.command(r'call feedkeys("\<Esc>%sv%il\<c-g>")'
2009-07-01 17:09:20 -04:00
% (move_one_right, len(self._ct)-1))
2009-07-01 14:27:28 -04:00
2009-07-01 11:11:33 -04:00
class SnippetInstance(TextObject):
"""
A Snippet instance is an instance of a Snippet Definition. That is,
when the user expands a snippet, a SnippetInstance is created to
keep track of the corresponding TextObjects. The Snippet itself is
also a TextObject because it has a start an end
"""
def __init__(self, start, end, ts, mirrors):
2009-07-01 11:11:33 -04:00
TextObject.__init__(self, start, end)
self._tabstops = ts
2009-07-01 09:20:40 -04:00
self._mirrors = mirrors
2009-07-01 11:11:33 -04:00
self._text_objects = ts.values() + mirrors
self._selected_tab = None
self._cts = None
for to in self._text_objects:
to.parent = self
for ts in self._tabstops.values():
self._update_mirrors(ts)
2009-07-01 11:11:33 -04:00
def select_next_tab(self, backwards = False):
if self._cts == 0:
if not backwards:
return False
if backwards:
cts_bf = self._cts
if self._cts == 0:
self._cts = max(self._tabstops.keys())
else:
self._cts -= 1
if self._cts <= 0:
self._cts = cts_bf
else:
# All tabs handled?
if self._cts is None:
self._cts = 1
else:
self._cts += 1
if self._cts not in self._tabstops:
self._cts = 0
if 0 not in self._tabstops:
return False
2009-07-01 11:11:33 -04:00
ts = self._tabstops[self._cts]
ts.select()
2009-07-01 11:11:33 -04:00
2009-07-01 14:27:28 -04:00
self._selected_tab = ts
return True
2009-07-01 09:20:40 -04:00
def _update_mirrors(self,for_ts):
for m in self._mirrors:
moved_lines, moved_cols = m.update(for_ts)
self._move_textobjects_behind(moved_lines, moved_cols, m)
def _move_textobjects_behind(self, lines, cols, obj):
if lines == 0 and cols == 0:
return
debug("Got: %i %i" % (lines,cols))
debug(" %s -> %s" % (obj.start,obj.end))
2009-07-01 11:11:33 -04:00
for m in self._text_objects:
if m == obj:
2009-07-01 09:20:40 -04:00
continue
debug("Considering m at %s -> %s" % (m.start,m.end))
if m.start.line > obj.end.line:
debug(" moving %i lines" % lines)
2009-07-01 09:20:40 -04:00
m.start.line += lines
m.end.line += lines
elif m.start.line == obj.end.line:
if m.start.col >= obj.end.col:
if lines:
debug(" moving %i lines" % lines)
m.start.line += lines
m.end.line += lines
else:
debug(" moving %i cols" % cols)
m.start.col += cols
m.end.col += cols
2009-07-02 05:58:46 -04:00
2009-07-01 09:20:40 -04:00
def backspace(self,count):
cts = self._tabstops[self._cts]
cts.current_text = cts.current_text[:-count]
2009-07-01 09:20:40 -04:00
def chars_entered(self, chars):
cts = self._tabstops[self._cts]
2009-07-01 11:11:33 -04:00
debug("Got chars: %s, %i" % (chars, self._selected_tab is not None))
2009-07-01 11:11:33 -04:00
if self._selected_tab is not None:
cts.current_text = chars
2009-07-01 11:11:33 -04:00
self._selected_tab = None
else:
cts.current_text += chars
2009-07-01 09:20:40 -04:00
self._update_mirrors(cts)
2009-06-23 08:45:04 -04:00
class Snippet(object):
2009-07-01 09:20:40 -04:00
_TABSTOP = re.compile(r'''(?xms)
(?:\${(\d+):(.*?)})| # A simple tabstop with default value
(?:\$(\d+)) # A mirror or a tabstop without default value.
''')
2009-06-23 08:45:04 -04:00
def __init__(self,trigger,value):
self._t = trigger
self._v = value
2009-07-01 14:14:54 -04:00
@property
2009-06-23 08:45:04 -04:00
def trigger(self):
return self._t
2009-07-01 09:20:40 -04:00
def _handle_tabstop(self, m, val, tabstops, mirrors):
no = int(m.group(1))
def_text = m.group(2)
2009-07-01 09:20:40 -04:00
start, end = m.span()
val = val[:start] + def_text + val[end:]
2009-07-01 09:20:40 -04:00
line_idx = val[:start].count('\n')
line_start = val[:start].rfind('\n') + 1
start_in_line = start - line_start
ts = TabStop(line_idx,
(start_in_line,start_in_line+len(def_text)), def_text)
tabstops[no] = ts
2009-07-01 09:20:40 -04:00
return val
2009-07-01 09:20:40 -04:00
def _handle_ts_or_mirror(self, m, val, tabstops, mirrors):
no = int(m.group(3))
2009-07-01 09:20:40 -04:00
start, end = m.span()
val = val[:start] + val[end:]
2009-07-01 09:20:40 -04:00
line_idx = val[:start].count('\n')
line_start = val[:start].rfind('\n') + 1
start_in_line = start - line_start
2009-07-01 09:20:40 -04:00
if no in tabstops:
m = Mirror(tabstops[no], line_idx, start_in_line)
mirrors.append(m)
else:
2009-07-01 14:14:54 -04:00
ts = TabStop(line_idx, (start_in_line,start_in_line))
tabstops[no] = ts
2009-07-01 09:20:40 -04:00
return val
def _find_tabstops(self, val):
tabstops = {}
mirrors = []
while 1:
m = self._TABSTOP.search(val)
if m is not None:
if m.group(1) is not None: # ${1:hallo}
val = self._handle_tabstop(m,val,tabstops,mirrors)
elif m.group(3) is not None: # $1
val = self._handle_ts_or_mirror(m,val,tabstops,mirrors)
else:
break
return tabstops, mirrors, val
2009-06-23 08:45:04 -04:00
def launch(self, before, after):
ts, mirrors, text = self._find_tabstops(self._v)
lineno, col = vim.current.window.cursor
start = Position(lineno-1,col - len(self._t))
end = Position(lineno-1,col)
new_end = _replace_text_in_buffer( start, end, text )
2009-07-01 09:20:40 -04:00
if len(ts) or len(mirrors):
s = SnippetInstance(start, new_end, ts, mirrors)
s.select_next_tab()
return s
else:
vim.current.window.cursor = new_end.line + 1, new_end.col
2009-06-23 08:45:04 -04:00
class SnippetManager(object):
def __init__(self):
self.reset()
2009-07-01 09:20:40 -04:00
self._last_cursor_pos = None
def reset(self):
self._snippets = {}
self._current_snippets = []
2009-06-23 08:45:04 -04:00
def add_snippet(self,trigger,value):
self._snippets[trigger] = Snippet(trigger,value)
def try_expand(self, backwards = False):
if len(self._current_snippets):
cs = self._current_snippets[-1]
if not cs.select_next_tab(backwards):
self._current_snippets.pop()
2009-07-01 11:11:33 -04:00
self._last_cursor_pos = vim.current.window.cursor
return
2009-06-23 08:45:04 -04:00
line = vim.current.line
2009-06-23 08:45:04 -04:00
dummy,col = vim.current.window.cursor
if col > 0 and line[col-1] in string.whitespace:
return
# Get the word to the left of the current edit position
before,after = line[:col], line[col:]
word = before.split()[-1]
if word in self._snippets:
s = self._snippets[word].launch(before.rstrip()[:-len(word)], after)
self._last_cursor_pos = vim.current.window.cursor
if s is not None:
self._current_snippets.append(s)
def cursor_moved(self):
2009-07-01 09:20:40 -04:00
cp = vim.current.window.cursor
if len(self._current_snippets) and self._last_cursor_pos is not None:
2009-07-01 09:20:40 -04:00
lineno,col = cp
llineo,lcol = self._last_cursor_pos
2009-07-02 05:58:46 -04:00
if lineno in \
[ self._last_cursor_pos[0], self._last_cursor_pos[0]+1]:
2009-07-01 09:20:40 -04:00
cs = self._current_snippets[-1]
2009-07-02 05:58:46 -04:00
# Detect a carriage return
if col == 0 and lineno == self._last_cursor_pos[0] + 1:
cs.chars_entered('\n')
elif lcol > col: # Some deleting was going on
2009-07-01 09:20:40 -04:00
cs.backspace(lcol-col)
else:
line = vim.current.line
chars = line[lcol:col]
cs.chars_entered(chars)
2009-07-01 11:11:33 -04:00
2009-07-01 09:20:40 -04:00
self._last_cursor_pos = cp
def entered_insert_mode(self):
pass
2009-06-23 08:45:04 -04:00
PySnipSnippets = SnippetManager()
2009-07-01 14:14:54 -04:00