Visual now supports transformations
This commit is contained in:
parent
92209f99d2
commit
c1e5bb6c5f
@ -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>
|
||||||
|
@ -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)" % (
|
||||||
|
@ -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]
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
28
test.py
@ -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 {{{#
|
||||||
|
Loading…
Reference in New Issue
Block a user