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 (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 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 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}. when there is no visual text to be selected: ${VISUAL:default text}. And it
As a small example, let's take this snippet: 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 ------------------- ------------------- SNIP -------------------
snippet t snippet t
<tag>${VISUAL:inside text}</tag> <tag>${VISUAL:inside text/should/is/g}</tag>
endsnippet endsnippet
------------------- SNAP ------------------- ------------------- SNAP -------------------
And starting with this line: > And starting with this line: >
this should be cool this should be cool
position the cursor on the should, then press viw (visual mode -> select inner position the cursor on the should, then press viw (visual mode -> select inner
word). Then press tab, type "t" and press tab again > 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 If you expand this snippet without having been in visual mode before, e.g. by
writing in insert mode t<tab>, you will get > writing in insert mode t<tab>, you will get >
<tag>inside text</tag> <tag>inside text</tag>

View File

@ -87,21 +87,25 @@ def _parse_till_closing_brace(stream):
rv += c rv += c
return rv 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 = "" rv = ""
while True: while True:
if EscapeCharToken.starts_here(stream, char): escaped = False
rv += stream.next() + stream.next() for c in chars:
else: if EscapeCharToken.starts_here(stream, c):
rv += stream.next() + stream.next()
escaped = True
if not escaped:
c = stream.next() c = stream.next()
if c == char: break if c in chars: break
rv += c rv += c
return rv return rv, c
# End: Helper functions }}} # End: Helper functions }}}
# Tokens {{{ # Tokens {{{
@ -135,7 +139,7 @@ class TabStopToken(Token):
) )
class VisualToken(Token): class VisualToken(Token):
CHECK = re.compile(r"^\${VISUAL[:}]") CHECK = re.compile(r"^\${VISUAL[:}/]")
@classmethod @classmethod
def starts_here(klass, stream): def starts_here(klass, stream):
@ -147,7 +151,16 @@ class VisualToken(Token):
if stream.peek() == ":": if stream.peek() == ":":
stream.next() 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): def __repr__(self):
return "VisualToken(%r,%r)" % ( return "VisualToken(%r,%r)" % (
@ -169,8 +182,8 @@ class TransformationToken(Token):
stream.next() # / stream.next() # /
self.search = _parse_till_unescaped_char(stream, '/') self.search = _parse_till_unescaped_char(stream, '/')[0]
self.replace = _parse_till_unescaped_char(stream, '/') self.replace = _parse_till_unescaped_char(stream, '/')[0]
self.options = _parse_till_closing_brace(stream) self.options = _parse_till_closing_brace(stream)
def __repr__(self): def __repr__(self):
@ -217,7 +230,7 @@ class ShellCodeToken(Token):
def _parse(self, stream, indent): def _parse(self, stream, indent):
stream.next() # ` stream.next() # `
self.code = _parse_till_unescaped_char(stream, '`') self.code = _parse_till_unescaped_char(stream, '`')[0]
def __repr__(self): def __repr__(self):
return "ShellCodeToken(%r,%r,%r)" % ( return "ShellCodeToken(%r,%r,%r)" % (
@ -237,7 +250,7 @@ class PythonCodeToken(Token):
if stream.peek() in '\t ': if stream.peek() in '\t ':
stream.next() stream.next()
code = _parse_till_unescaped_char(stream, '`') code = _parse_till_unescaped_char(stream, '`')[0]
# Strip the indent if any # Strip the indent if any
if len(indent): if len(indent):
@ -264,7 +277,7 @@ class VimLCodeToken(Token):
def _parse(self, stream, indent): def _parse(self, stream, indent):
for i in range(4): for i in range(4):
stream.next() # `!v stream.next() # `!v
self.code = _parse_till_unescaped_char(stream, '`') self.code = _parse_till_unescaped_char(stream, '`')[0]
def __repr__(self): def __repr__(self):
return "VimLCodeToken(%r,%r,%r)" % ( return "VimLCodeToken(%r,%r,%r)" % (

View File

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

View File

@ -5,9 +5,10 @@ import re
import UltiSnips._vim as _vim import UltiSnips._vim as _vim
from UltiSnips.util import IndentUtil from UltiSnips.util import IndentUtil
from UltiSnips.text_objects._transformation import TextObjectTransformation
from UltiSnips.text_objects._base import NoneditableTextObject 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 A ${VISUAL} placeholder that will use the text that was last visually
selected and insert it here. If there was no text visually selected, selected and insert it here. If there was no text visually selected,
@ -30,6 +31,7 @@ class Visual(NoneditableTextObject):
self._mode = "v" self._mode = "v"
NoneditableTextObject.__init__(self, parent, token) NoneditableTextObject.__init__(self, parent, token)
TextObjectTransformation.__init__(self, token)
def _update(self, done, not_done): def _update(self, done, not_done):
if self._mode != "v": if self._mode != "v":
@ -48,8 +50,10 @@ class Visual(NoneditableTextObject):
else: else:
text = self._text text = self._text
text = self._transform(text)
self.overwrite(text) self.overwrite(text)
self._parent._del_child(self) self._parent._del_child(self)
return True 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") snippets = ("test", "beg\n\t${0:${VISUAL}}\nend")
keys = "hello\nnice\nworld" + ESC + "Vkk" + EX + "test" + EX keys = "hello\nnice\nworld" + ESC + "Vkk" + EX + "test" + EX
wanted = "beg\n\thello\n\tnice\n\tworld\nend" 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} #}}} # End: ${VISUAL} #}}}
# Recursive (Nested) Snippets {{{# # Recursive (Nested) Snippets {{{#