Trying to fit in Mirrors. Not so easy

This commit is contained in:
Holger Rapp 2009-07-01 15:20:40 +02:00
parent 1e3dd7b4be
commit d19ddc0711
3 changed files with 319 additions and 77 deletions

View File

@ -5,19 +5,81 @@ import vim
import string import string
import re import re
class Mirror(object):
def __init__(self, ts, idx, start):
self._ts = ts
self._start = (idx, start)
self._end = (idx,start)
self._delta_rows = 0
self._delta_cols = 0
def delta_rows():
doc = "The RW foo property."
def fget(self):
return self._delta_rows
def fset(self, value):
self._delta_rows = value
return locals()
delta_rows = property(**delta_rows())
def delta_cols():
doc = "The RW foo property."
def fget(self):
return self._delta_cols
def fset(self, value):
self._delta_cols = value
return locals()
delta_cols = property(**delta_cols())
@property
def tabstop(self):
return self._ts
@property
def number(self):
return self._no
@property
def start(self):
"The RO foo property."
return self._start
@property
def end(self):
"The RO foo property."
return self._end
def update_span(self):
start = self._start
lines = self._ts.current_text.splitlines()
if len(lines) == 1:
self._end = (start[0]+len(lines)-1,len(lines[-1]))
elif len(lines) > 1:
self._end = (start[0],start[1]+len(lines[0]) )
class TabStop(object): class TabStop(object):
def __init__(self, no, idx, span, default_text = ""): def __init__(self, no, idx, span, default_text = ""):
self._no = no self._no = no
self._default_text = default_text self._default_text = default_text
self._span = span self._start = span[0]
self._lineidx = idx self._lineidx = idx
self._ct = default_text
def current_text():
def fget(self):
return self._ct
def fset(self, value):
self._ct = value
return locals()
current_text = property(**current_text())
def line_idx(self): def line_idx(self):
return self._lineidx return self._lineidx
line_idx = property(line_idx) line_idx = property(line_idx)
def span(self): def span(self):
return self._span return (self._start,self._start+len(self._ct))
span = property(span) span = property(span)
def default_text(self): def default_text(self):
@ -29,10 +91,11 @@ class TabStop(object):
number = property(number) number = property(number)
class SnippetInstance(object): class SnippetInstance(object):
def __init__(self,start,end, ts): def __init__(self,start,end, ts, mirrors):
self._start = start self._start = start
self._end = end self._end = end
self._ts = ts self._ts = ts
self._mirrors = mirrors
self._cts = 1 self._cts = 1
@ -59,7 +122,6 @@ class SnippetInstance(object):
vim.current.window.cursor = newline, newcol vim.current.window.cursor = newline, newcol
# Select the word # Select the word
# Depending on the current mode and position, we # Depending on the current mode and position, we
# might need to move escape out of the mode and this # might need to move escape out of the mode and this
# will move our cursor one left # will move our cursor one left
@ -72,8 +134,72 @@ class SnippetInstance(object):
vim.command(r'call feedkeys("\<Esc>%sv%il\<c-g>")' vim.command(r'call feedkeys("\<Esc>%sv%il\<c-g>")'
% (move_one_right, endcol-newcol-1)) % (move_one_right, endcol-newcol-1))
def _update_mirrors(self,for_ts):
for m in self._mirrors:
if m.tabstop == for_ts:
mirror_line = m.start[0]
line = vim.current.buffer[mirror_line]
line = line[:m.start[1]+m.delta_cols] + for_ts.current_text # + line[m.end[1]+m.delta_cols:]
vim.current.buffer[mirror_line] = line
# m.update_span()
# Now update all mirrors and tabstops, that they have moved
# lines = for_ts.current_text.splitlines()
# if len(lines):
# for om in self._mirrors:
# om.update_span()
#
# # if om.start[0] > m.start[0]:
# # om.delta_rows += len(lines)-1
# if om.start[0] == m.start[0]:
# if om.start[1] >= m.start[1]:
# om.delta_cols += len(lines[-1])
def _move_to_on_line(self,amount):
lineno,col = vim.current.window.cursor
lineno -= 1
for m in self._mirrors:
if m.start[0] != lineno:
continue
if m.start[1] > col:
m.delta_cols += amount
def backspace(self,count):
if self._cts not in self._ts:
if 0 in self._ts:
self._cts = 0
else:
self._cts = 1
cts = self._ts[self._cts]
cts.current_text = cts.current_text[:-count]
# self._move_to_on_line(-count)
self._update_mirrors(cts)
def chars_entered(self, chars):
if self._cts not in self._ts:
if 0 in self._ts:
self._cts = 0
else:
self._cts = 1
cts = self._ts[self._cts]
cts.current_text += chars
self._move_to_on_line(len(chars))
self._update_mirrors(cts)
class Snippet(object): class Snippet(object):
_TB_EXPR = re.compile(r'\$(?:(?:{(\d+):(.*?)})|(\d+))') _TABSTOP = re.compile(r'''(?xms)
(?:\${(\d+):(.*?)})| # A simple tabstop with default value
(?:\$(\d+)) # A mirror or a tabstop without default value.
''')
def __init__(self,trigger,value): def __init__(self,trigger,value):
self._t = trigger self._t = trigger
@ -83,47 +209,70 @@ class Snippet(object):
return self._t return self._t
trigger = property(trigger) trigger = property(trigger)
def _find_text_tabstops(self, lines): def _handle_tabstop(self, m, val, tabstops, mirrors):
no = int(m.group(1))
def_text = m.group(2)
start, end = m.span()
val = val[:start] + def_text + val[end:]
line_idx = val[:start].count('\n')
line_start = val[:start].rfind('\n') + 1
start_in_line = start - line_start
ts = TabStop(no, line_idx, (start_in_line,start_in_line+len(def_text)), def_text)
tabstops[ts.number] = ts
return val
def _handle_ts_or_mirror(self, m, val, tabstops, mirrors):
no = int(m.group(3))
start, end = m.span()
val = val[:start] + val[end:]
line_idx = val[:start].count('\n')
line_start = val[:start].rfind('\n') + 1
start_in_line = start - line_start
if no in tabstops:
m = Mirror(tabstops[no], line_idx, start_in_line)
mirrors.append(m)
else:
ts = TabStop(no, line_idx, (start_in_line,start_in_line), "")
tabstops[ts.number] = ts
return val
def _find_tabstops(self, val):
tabstops = {} tabstops = {}
mirrors = []
for idx in range(len(lines)): while 1:
line = lines[idx] m = self._TABSTOP.search(val)
m = self._TB_EXPR.search(line)
while m is not None:
if m.group(1):
no = int(m.group(1))
def_text = m.group(2)
else:
no = int(m.group(3))
def_text = ""
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
start, end = m.span() return tabstops, mirrors, val
line = line[:start] + def_text + line[end:]
ts = TabStop(no, idx, (start,start+len(def_text)), def_text)
lines[idx] = line
tabstops[ts.number] = ts
m = self._TB_EXPR.search(line)
return tabstops
def _replace_tabstops(self): def _replace_tabstops(self):
lines = self._v.split('\n') ts, mirrors, val = self._find_tabstops(self._v)
lines = val.split('\n')
ts = self._find_text_tabstops(lines) return ts, mirrors, lines
return ts, lines
def launch(self, before, after): def launch(self, before, after):
lineno,col = vim.current.window.cursor lineno,col = vim.current.window.cursor
col -= len(self._t) col -= len(self._t)
ts,lines = self._replace_tabstops() ts, mirrors, lines = self._replace_tabstops()
endcol = None endcol = None
newline = 1 newline = 1
@ -142,8 +291,8 @@ class Snippet(object):
vim.current.window.cursor = newline, newcol vim.current.window.cursor = newline, newcol
if len(ts): if len(ts) or len(mirrors):
s = SnippetInstance( (lineno,col), (newline,newcol), ts) s = SnippetInstance( (lineno,col), (newline,newcol), ts, mirrors)
s.select_next_tab() s.select_next_tab()
@ -153,6 +302,7 @@ class Snippet(object):
class SnippetManager(object): class SnippetManager(object):
def __init__(self): def __init__(self):
self.reset() self.reset()
self._last_cursor_pos = None
def reset(self): def reset(self):
self._snippets = {} self._snippets = {}
@ -183,7 +333,24 @@ class SnippetManager(object):
self._current_snippets.append(s) self._current_snippets.append(s)
def cursor_moved(self): def cursor_moved(self):
pass cp = vim.current.window.cursor
if and len(self._current_snippets) and self._last_cursor_pos is not None:
lineno,col = cp
llineo,lcol = self._last_cursor_pos
# If we moved the line, somethings fishy.
if lineno == self._last_cursor_pos[0]:
cs = self._current_snippets[-1]
if lcol > col: # Some deleting was going on
cs.backspace(lcol-col)
else:
line = vim.current.line
chars = line[lcol:col]
cs.chars_entered(chars)
self._last_cursor_pos = cp
def entered_insert_mode(self): def entered_insert_mode(self):
pass pass

View File

@ -26,19 +26,6 @@ python from PySnipEmu import PySnipSnippets
inoremap <Tab> <C-R>=PyVimSnips_ExpandSnippet()<cr> inoremap <Tab> <C-R>=PyVimSnips_ExpandSnippet()<cr>
snoremap <Tab> <Esc>:call PyVimSnips_ExpandSnippet()<cr> snoremap <Tab> <Esc>:call PyVimSnips_ExpandSnippet()<cr>
python PySnipSnippets.add_snippet("echo","echo ${1:Hallo}")
python PySnipSnippets.add_snippet("hello", "Hallo Welt!\nUnd Wie gehts?")
python PySnipSnippets.add_snippet("hallo", "hallo ${0:End} ${1:Beginning}")
python << EOF
PySnipSnippets.add_snippet("if",
"""if(${1:/* condition */}) {
${2:/* code */}
}
""")
EOF
au CursorMovedI * py PySnipSnippets.cursor_moved() au CursorMovedI * py PySnipSnippets.cursor_moved()
au InsertEnter * py PySnipSnippets.entered_insert_mode() au InsertEnter * py PySnipSnippets.entered_insert_mode()

144
test.py
View File

@ -14,7 +14,7 @@ class _VimTest(unittest.TestCase):
expansion can take place expansion can take place
""" """
def _send(s): def _send(s):
os.system("screen -X stuff '%s'" % s) os.system("screen -x %s -X stuff '%s'" % (self.session,s))
splits = str.split('\t') splits = str.split('\t')
for w in splits[:-1]: for w in splits[:-1]:
@ -35,27 +35,28 @@ class _VimTest(unittest.TestCase):
PySnipSnippets.add_snippet("%s","""%s""") PySnipSnippets.add_snippet("%s","""%s""")
EOF EOF
''' % (sv,content)) ''' % (sv,content))
# Clear the buffer # Clear the buffer
self.type("bggVGd") self.type("bggVGd")
# Enter insert mode if not self.interrupt:
self.type("i") # Enter insert mode
self.type("i")
# Execute the command # Execute the command
self.cmd() self.cmd()
handle, fn = tempfile.mkstemp(prefix="PySnipEmuTest",suffix=".txt") handle, fn = tempfile.mkstemp(prefix="PySnipEmuTest",suffix=".txt")
os.close(handle) os.close(handle)
self.escape() self.escape()
self.type(":w! %s\n" % fn) self.type(":w! %s\n" % fn)
# Give screen a chance to send the cmd and vim to write the file # Give screen a chance to send the cmd and vim to write the file
time.sleep(.01) time.sleep(.01)
# Read the output, chop the trailing newline # Read the output, chop the trailing newline
self.output = open(fn,"r").read()[:-1] self.output = open(fn,"r").read()[:-1]
def cmd(self): def cmd(self):
"""Overwrite these in the children""" """Overwrite these in the children"""
@ -170,22 +171,109 @@ class TextTabStopSimpleReplace_ExceptCorrectResult(_VimTest):
def runTest(self): def runTest(self):
self.assertEqual(self.output,"hallo Du Nase na") self.assertEqual(self.output,"hallo Du Nase na")
class TextTabStopSimpleReplace_ExceptCorrectResult(_VimTest):
snippets = (
("hallo", "hallo ${0:End} ${1:Beginning}"),
)
def cmd(self):
self.type("hallo\tna\tDu Nase")
def runTest(self):
self.assertEqual(self.output,"hallo Du Nase na")
# TODO: multiline mirrors
class TextTabStopSimpleMirror_ExceptCorrectResult(_VimTest):
snippets = (
("test", "$1\n$1"),
)
def cmd(self):
self.type("test\thallo")
def runTest(self):
self.assertEqual(self.output,"hallo\nhallo")
class TextTabStopSimpleMirrorDelete_ExceptCorrectResult(_VimTest):
snippets = (
("test", "$1\n$1"),
)
def cmd(self):
self.type("test\thallo")
self.type("\b\b")
def runTest(self):
self.assertEqual(self.output,"hal\nhal")
class TextTabStopSimpleMirrorSameLine_ExceptCorrectResult(_VimTest):
snippets = (
("test", "$1 $1"),
)
def cmd(self):
self.type("test\thallo")
def runTest(self):
self.assertEqual(self.output,"hal\nhal")
class TextTabStopSimpleMirrorDeleteSomeEnterSome_ExceptCorrectResult(_VimTest):
snippets = (
("test", "$1\n$1"),
)
def cmd(self):
self.type("test\thallo\b\bhups")
def runTest(self):
self.assertEqual(self.output,"halhups\nhalhups")
class TextTabStopMirrorMoreInvolved_ExceptCorrectResult(_VimTest):
snippets = (
("for", "for(size_t ${2:i} = 0; $2 < ${1:count}; ${3:++$2})\n{\n\t${0:/* code */}\n}"),
)
def cmd(self):
self.type("for\t")
def runTest(self):
self.assertEqual(self.output,"hallo Du Nase na")
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys
import optparse
tests = [ def parse_args():
SimpleExpand_ExceptCorrectResult(), p = optparse.OptionParser("%prog [OPTIONS] <test case names to run>")
SimpleExpandTypeAfterExpand_ExceptCorrectResult(),
SimpleExpandTypeAfterExpand1_ExceptCorrectResult(), p.set_defaults(session="vim", interrupt=False)
DoNotExpandAfterSpace_ExceptCorrectResult(),
ExpandInTheMiddleOfLine_ExceptCorrectResult(), p.add_option("-s", "--session", dest="session", metavar="SESSION",
MultilineExpand_ExceptCorrectResult(), help="send commands to screen session SESSION [%default]")
MultilineExpandTestTyping_ExceptCorrectResult(), p.add_option("-i", "--interrupt", dest="interrupt",
ExitTabStop_ExceptCorrectResult(), action="store_true",
TextTabStopNoReplace_ExceptCorrectResult(), help="Stop after defining the snippet. This allows the user" \
TextTabStopSimpleReplace_ExceptCorrectResult(), "to interactively test the snippet in vim. You must give exactly" \
] "one test case on the cmdline. The test will always fail."
# suite = unittest.TestLoader(.loadTestsFromModule(__import__("test")) )
o, args = p.parse_args()
return o, args
options,selected_tests = parse_args()
# The next line doesn't work in python 2.3
all_test_suites = unittest.TestLoader().loadTestsFromModule(__import__("test"))
# Inform all test case which screen session to use
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTests(tests) for s in all_test_suites:
for test in s:
test.session = options.session
test.interrupt = options.interrupt
if len(selected_tests):
id = test.id().split('.')[1]
if id not in selected_tests:
continue
suite.addTest(test)
res = unittest.TextTestRunner().run(suite) res = unittest.TextTestRunner().run(suite)