Visual now supports transformations

This commit is contained in:
Holger Rapp 2012-02-12 12:15:54 +01:00
parent 92209f99d2
commit c1e5bb6c5f
5 changed files with 85 additions and 27 deletions

View File

@ -375,19 +375,23 @@ some text in visual mode and press the key to expand a trigger
(see g:UltiSnipsExpandTrigger) the selected text will get deleted and you will
drop to insert mode. The next snippet you expand will have the selected text
in the place of the ${VISUAL}. The place holder can also contain default text
when there is no visual text to be selected: ${VISUAL:default text}.
As a small example, let's take this snippet:
when there is no visual text to be selected: ${VISUAL:default text}. And it
can also contain transformations on the contents of the visual (See
*UltiSnips-transformations* ). This will take the form
${VISUAL:default/search/replace/option}.
As a small example, let's take this snippet. It will take the visual text,
replace all "should" via "is" and put it in.
------------------- SNIP -------------------
snippet t
<tag>${VISUAL:inside text}</tag>
<tag>${VISUAL:inside text/should/is/g}</tag>
endsnippet
------------------- SNAP -------------------
And starting with this line: >
this should be cool
position the cursor on the should, then press viw (visual mode -> select inner
word). Then press tab, type "t" and press tab again >
-> this <tag>should</tag> be cool
-> this <tag>is</tag> be cool
If you expand this snippet without having been in visual mode before, e.g. by
writing in insert mode t<tab>, you will get >
<tag>inside text</tag>

View File

@ -87,21 +87,25 @@ def _parse_till_closing_brace(stream):
rv += c
return rv
def _parse_till_unescaped_char(stream, char):
def _parse_till_unescaped_char(stream, chars):
"""
Returns all chars till a non-escaped `char` is found.
Returns all chars till a non-escaped char is found.
Will also consume the closing `char`, but not return it
Will also consume the closing char, but and return it as second
return value
"""
rv = ""
while True:
if EscapeCharToken.starts_here(stream, char):
rv += stream.next() + stream.next()
else:
escaped = False
for c in chars:
if EscapeCharToken.starts_here(stream, c):
rv += stream.next() + stream.next()
escaped = True
if not escaped:
c = stream.next()
if c == char: break
if c in chars: break
rv += c
return rv
return rv, c
# End: Helper functions }}}
# Tokens {{{
@ -135,7 +139,7 @@ class TabStopToken(Token):
)
class VisualToken(Token):
CHECK = re.compile(r"^\${VISUAL[:}]")
CHECK = re.compile(r"^\${VISUAL[:}/]")
@classmethod
def starts_here(klass, stream):
@ -147,7 +151,16 @@ class VisualToken(Token):
if stream.peek() == ":":
stream.next()
self.alternative_text = _parse_till_closing_brace(stream)
self.alternative_text, c = _parse_till_unescaped_char(stream, '/}')
if c == '/': # Transformation going on
self.search = _parse_till_unescaped_char(stream, '/')[0]
self.replace = _parse_till_unescaped_char(stream, '/')[0]
self.options = _parse_till_closing_brace(stream)
else:
self.search = None
self.replace = None
self.options = None
def __repr__(self):
return "VisualToken(%r,%r)" % (
@ -169,8 +182,8 @@ class TransformationToken(Token):
stream.next() # /
self.search = _parse_till_unescaped_char(stream, '/')
self.replace = _parse_till_unescaped_char(stream, '/')
self.search = _parse_till_unescaped_char(stream, '/')[0]
self.replace = _parse_till_unescaped_char(stream, '/')[0]
self.options = _parse_till_closing_brace(stream)
def __repr__(self):
@ -217,7 +230,7 @@ class ShellCodeToken(Token):
def _parse(self, stream, indent):
stream.next() # `
self.code = _parse_till_unescaped_char(stream, '`')
self.code = _parse_till_unescaped_char(stream, '`')[0]
def __repr__(self):
return "ShellCodeToken(%r,%r,%r)" % (
@ -237,7 +250,7 @@ class PythonCodeToken(Token):
if stream.peek() in '\t ':
stream.next()
code = _parse_till_unescaped_char(stream, '`')
code = _parse_till_unescaped_char(stream, '`')[0]
# Strip the indent if any
if len(indent):
@ -264,7 +277,7 @@ class VimLCodeToken(Token):
def _parse(self, stream, indent):
for i in range(4):
stream.next() # `!v
self.code = _parse_till_unescaped_char(stream, '`')
self.code = _parse_till_unescaped_char(stream, '`')[0]
def __repr__(self):
return "VimLCodeToken(%r,%r,%r)" % (

View File

@ -100,10 +100,11 @@ class _CleverReplace(object):
return self._unescape(self._schar_escape(tv))
class Transformation(Mirror):
def __init__(self, parent, ts, token):
Mirror.__init__(self, parent, ts, token)
class TextObjectTransformation(object):
def __init__(self, token):
self._find = None
if token.search is None:
return
flags = 0
self._match_this_many = 1
@ -116,9 +117,17 @@ class Transformation(Mirror):
self._find = re.compile(token.search, flags | re.DOTALL)
self._replace = _CleverReplace(token.replace)
def _transform(self, text):
if self._find is None:
return text
return self._find.subn(self._replace.replace, text, self._match_this_many)[0]
class Transformation(Mirror, TextObjectTransformation):
def __init__(self, parent, ts, token):
Mirror.__init__(self, parent, ts, token)
TextObjectTransformation.__init__(self, token)
def _get_text(self):
return self._find.subn(
self._replace.replace, self._ts.current_text, self._match_this_many
)[0]
return self._transform(self._ts.current_text)

View File

@ -5,9 +5,10 @@ import re
import UltiSnips._vim as _vim
from UltiSnips.util import IndentUtil
from UltiSnips.text_objects._transformation import TextObjectTransformation
from UltiSnips.text_objects._base import NoneditableTextObject
class Visual(NoneditableTextObject):
class Visual(NoneditableTextObject,TextObjectTransformation):
"""
A ${VISUAL} placeholder that will use the text that was last visually
selected and insert it here. If there was no text visually selected,
@ -30,6 +31,7 @@ class Visual(NoneditableTextObject):
self._mode = "v"
NoneditableTextObject.__init__(self, parent, token)
TextObjectTransformation.__init__(self, token)
def _update(self, done, not_done):
if self._mode != "v":
@ -48,8 +50,10 @@ class Visual(NoneditableTextObject):
else:
text = self._text
text = self._transform(text)
self.overwrite(text)
self._parent._del_child(self)
return True

28
test.py
View File

@ -1607,6 +1607,34 @@ class Visual_LineSelect_CheckIndentWithTS_NoOverwrite(_VimTest):
snippets = ("test", "beg\n\t${0:${VISUAL}}\nend")
keys = "hello\nnice\nworld" + ESC + "Vkk" + EX + "test" + EX
wanted = "beg\n\thello\n\tnice\n\tworld\nend"
class VisualTransformation_SelectOneWord(_VimTest):
snippets = ("test", r"h${VISUAL/./\U$0\E/g}b")
keys = "blablub" + ESC + "0v6l" + EX + "test" + EX
wanted = "hBLABLUBb"
# TODO: python code access to visual
# TODO: document the changes
class VisualTransformationWithDefault_ExpandWithoutVisual(_VimTest):
snippets = ("test", "h${VISUAL:world/./\U$0\E/g}b")
keys = "test" + EX + "hi"
wanted = "hWORLDbhi"
class VisualTransformationWithDefault_ExpandWithVisual(_VimTest):
snippets = ("test", "h${VISUAL:world/./\U$0\E/g}b")
keys = "blablub" + ESC + "0v6l" + EX + "test" + EX
wanted = "hBLABLUBb"
class VisualTransformation_LineSelect_Simple(_VimTest):
snippets = ("test", r"h${VISUAL/./\U$0\E/g}b")
keys = "hello\nnice\nworld" + ESC + "Vkk" + EX + "test" + EX
wanted = "hHELLO\n NICE\n WORLDb"
class VisualTransformation_InDefaultText_LineSelect_NoOverwrite(_VimTest):
snippets = ("test", "h${1:bef${VISUAL/./\U$0\E/g}aft}b")
keys = "hello\nnice\nworld" + ESC + "Vkk" + EX + "test" + EX + JF + "hi"
wanted = "hbefHELLO\n NICE\n WORLDaftbhi"
class VisualTransformation_InDefaultText_LineSelect_Overwrite(_VimTest):
snippets = ("test", "h${1:bef${VISUAL/./\U$0\E/g}aft}b")
keys = "hello\nnice\nworld" + ESC + "Vkk" + EX + "test" + EX + "jup" + JF + "hi"
wanted = "hjupbhi"
# End: ${VISUAL} #}}}
# Recursive (Nested) Snippets {{{#