grant access to visual to context and actions

Grants access to:

* context match condition for context snippets (via snip.visual_text and
    snip.visual_mode);

* pre/post actions (via same variable);

* context match condition to (!) lastly selected placeholder, so it is
    possible now to use autotrigger snippets, that are activated by
    simply typing letter while tabstop is selected;

* python interpolations to lastly selected placeholder;
This commit is contained in:
Stanislav Seletskiy 2016-03-29 16:48:41 +06:00
parent 059c9a9da2
commit 3a3e56a987
12 changed files with 185 additions and 22 deletions

View File

@ -939,6 +939,14 @@ The 'snip' object provides some properties as well: >
snip.ft: snip.ft:
The current filetype. The current filetype.
snip.p:
Last selected placeholder. Will contain placeholder object with
following properties:
'current_text' - text in the placeholder on the moment of selection;
'start' - placeholder start on the moment of selection;
'end' - placeholder end on the moment of selection;
For your convenience, the 'snip' object also provides the following For your convenience, the 'snip' object also provides the following
operators: > operators: >
@ -1397,6 +1405,15 @@ Global variable `snip` will be available with following properties:
- 'to_vim_cursor()' - returns 1-indexed cursor, suitable for assigning - 'to_vim_cursor()' - returns 1-indexed cursor, suitable for assigning
to 'vim.current.window.cursor'; to 'vim.current.window.cursor';
'snip.line' and 'snip.column' - aliases for cursor position (zero-indexed); 'snip.line' and 'snip.column' - aliases for cursor position (zero-indexed);
'snip.visual_mode' - ('v', 'V', '^V', see |visual-mode|);
'snip.visual_text' - last visually-selected text;
'snip.last_placeholder' - last active placeholder from previous snippet
with following properties:
- 'current_text' - text in the placeholder on the moment of selection;
- 'start' - placeholder start on the moment of selection;
- 'end' - placeholder end on the moment of selection;
------------------- SNIP ------------------- ------------------- SNIP -------------------
@ -1463,6 +1480,20 @@ endsnippet
That snippet will expand to 'var1 +=' after line, which begins from 'var1 :='. That snippet will expand to 'var1 +=' after line, which begins from 'var1 :='.
*UltiSnips-capture-placeholder*
You can capture placeholder text from previous snippet by using following
trick:
------------------- SNIP -------------------
snippet = "desc" "snip.last_placeholder" Ae
`!p snip.rv = snip.context.current_text` == nil
endsnippet
------------------- SNAP -------------------
That snippet will be expanded only if you will replace selected tabstop in
other snippet (like, as in 'if ${1:var}') and will replace that tabstop by
tabstop value following by ' == nil'.
4.10 Snippets actions *UltiSnips-snippet-actions* 4.10 Snippets actions *UltiSnips-snippet-actions*
--------------------- ---------------------

View File

@ -87,14 +87,25 @@ class SnippetDefinition(object):
return match return match
return False return False
def _context_match(self): def _context_match(self, visual_content):
# skip on empty buffer # skip on empty buffer
if len(vim.current.buffer) == 1 and vim.current.buffer[0] == "": if len(vim.current.buffer) == 1 and vim.current.buffer[0] == "":
return return
return self._eval_code('snip.context = ' + self._context_code, { locals = {
'context': None 'context': None,
}).context 'visual_mode': '',
'visual_text': '',
'last_placeholder': None
}
if visual_content:
locals['visual_mode'] = visual_content.mode
locals['visual_text'] = visual_content.text
locals['last_placeholder'] = visual_content.placeholder
return self._eval_code('snip.context = ' + self._context_code,
locals).context
def _eval_code(self, code, additional_locals={}): def _eval_code(self, code, additional_locals={}):
code = "\n".join([ code = "\n".join([
@ -110,7 +121,7 @@ class SnippetDefinition(object):
'buffer': current.buffer, 'buffer': current.buffer,
'line': current.window.cursor[0]-1, 'line': current.window.cursor[0]-1,
'column': current.window.cursor[1]-1, 'column': current.window.cursor[1]-1,
'cursor': SnippetUtilCursor(current.window.cursor) 'cursor': SnippetUtilCursor(current.window.cursor),
} }
locals.update(additional_locals) locals.update(additional_locals)
@ -225,7 +236,7 @@ class SnippetDefinition(object):
"""The matched context.""" """The matched context."""
return self._context return self._context
def matches(self, before): def matches(self, before, visual_content=None):
"""Returns True if this snippet matches 'before'.""" """Returns True if this snippet matches 'before'."""
# If user supplies both "w" and "i", it should perhaps be an # If user supplies both "w" and "i", it should perhaps be an
# error, but if permitted it seems that "w" should take precedence # error, but if permitted it seems that "w" should take precedence
@ -267,7 +278,7 @@ class SnippetDefinition(object):
self._context = None self._context = None
if match and self._context_code: if match and self._context_code:
self._context = self._context_match() self._context = self._context_match(visual_content)
if not self.context: if not self.context:
match = False match = False

View File

@ -31,7 +31,8 @@ class SnippetSource(object):
deep_extends = self.get_deep_extends(base_filetypes) deep_extends = self.get_deep_extends(base_filetypes)
return [ft for ft in deep_extends if ft in self._snippets] return [ft for ft in deep_extends if ft in self._snippets]
def get_snippets(self, filetypes, before, possible, autotrigger_only): def get_snippets(self, filetypes, before, possible, autotrigger_only,
visual_content):
"""Returns the snippets for all 'filetypes' (in order) and their """Returns the snippets for all 'filetypes' (in order) and their
parents matching the text 'before'. If 'possible' is true, a partial parents matching the text 'before'. If 'possible' is true, a partial
match is enough. Base classes can override this method to provide means match is enough. Base classes can override this method to provide means
@ -44,7 +45,8 @@ class SnippetSource(object):
for ft in self._get_existing_deep_extends(filetypes): for ft in self._get_existing_deep_extends(filetypes):
snips = self._snippets[ft] snips = self._snippets[ft]
result.extend(snips.get_matching_snippets(before, possible, result.extend(snips.get_matching_snippets(before, possible,
autotrigger_only)) autotrigger_only,
visual_content))
return result return result
def get_clear_priority(self, filetypes): def get_clear_priority(self, filetypes):

View File

@ -16,7 +16,8 @@ class SnippetDictionary(object):
"""Add 'snippet' to this dictionary.""" """Add 'snippet' to this dictionary."""
self._snippets.append(snippet) self._snippets.append(snippet)
def get_matching_snippets(self, trigger, potentially, autotrigger_only): def get_matching_snippets(self, trigger, potentially, autotrigger_only,
visual_content):
"""Returns all snippets matching the given trigger. """Returns all snippets matching the given trigger.
If 'potentially' is true, returns all that could_match(). If 'potentially' is true, returns all that could_match().
@ -34,7 +35,8 @@ class SnippetDictionary(object):
all_snippets = [s for s in all_snippets if s.has_option('A')] all_snippets = [s for s in all_snippets if s.has_option('A')]
if not potentially: if not potentially:
return [s for s in all_snippets if s.matches(trigger)] return [s for s in all_snippets if s.matches(trigger,
visual_content)]
else: else:
return [s for s in all_snippets if s.could_match(trigger)] return [s for s in all_snippets if s.could_match(trigger)]

View File

@ -137,6 +137,7 @@ class SnippetManager(object):
SnipMateFileSource()) SnipMateFileSource())
self._should_update_textobjects = False self._should_update_textobjects = False
self._should_reset_visual = False
self._reinit() self._reinit()
@ -269,7 +270,7 @@ class SnippetManager(object):
snip = UltiSnipsSnippetDefinition(0, trigger, value, description, snip = UltiSnipsSnippetDefinition(0, trigger, value, description,
options, {}, '', context, actions) options, {}, '', context, actions)
if not trigger or snip.matches(before): if not trigger or snip.matches(before, self._visual_content):
self._do_snippet(snip, before) self._do_snippet(snip, before)
return True return True
else: else:
@ -489,6 +490,7 @@ class SnippetManager(object):
def _jump(self, backwards=False): def _jump(self, backwards=False):
"""Helper method that does the actual jump.""" """Helper method that does the actual jump."""
if self._should_update_textobjects: if self._should_update_textobjects:
self._should_reset_visual = False
self._cursor_moved() self._cursor_moved()
# we need to set 'onemore' there, because of limitations of the vim # we need to set 'onemore' there, because of limitations of the vim
@ -526,18 +528,31 @@ class SnippetManager(object):
and ntab.start - self._ctab.end == Position(0, 1) and ntab.start - self._ctab.end == Position(0, 1)
and ntab.end - ntab.start == Position(0, 1)): and ntab.end - ntab.start == Position(0, 1)):
ntab_short_and_near = True ntab_short_and_near = True
if ntab.number == 0:
self._current_snippet_is_done()
self._ctab = ntab self._ctab = ntab
# Run interpolations again to update new placeholder
# values, binded to currently newly jumped placeholder.
self._visual_content.conserve_placeholder(self._ctab)
self._cs.current_placeholder = \
self._visual_content.placeholder
self._should_reset_visual = False
self._csnippets[0].update_textobjects()
self._vstate.remember_buffer(self._csnippets[0])
if ntab.number == 0 and self._csnippets:
self._current_snippet_is_done()
else: else:
# This really shouldn't happen, because a snippet should # This really shouldn't happen, because a snippet should
# have been popped when its final tabstop was used. # have been popped when its final tabstop was used.
# Cleanup by removing current snippet and recursing. # Cleanup by removing current snippet and recursing.
self._current_snippet_is_done() self._current_snippet_is_done()
jumped = self._jump(backwards) jumped = self._jump(backwards)
if jumped: if jumped:
self._vstate.remember_position() if self._ctab:
self._vstate.remember_unnamed_register(self._ctab.current_text) self._vstate.remember_position()
self._vstate.remember_unnamed_register(self._ctab.current_text)
if not ntab_short_and_near: if not ntab_short_and_near:
self._ignore_movements = True self._ignore_movements = True
@ -619,7 +634,8 @@ class SnippetManager(object):
filetypes, filetypes,
before, before,
partial, partial,
autotrigger_only autotrigger_only,
self._visual_content
) )
for snippet in possible_snippets: for snippet in possible_snippets:
@ -835,6 +851,11 @@ class SnippetManager(object):
finally: finally:
self._last_inserted_char = inserted_char self._last_inserted_char = inserted_char
if self._should_reset_visual and self._visual_content.mode == '':
self._visual_content.reset()
self._should_reset_visual = True
UltiSnips_Manager = SnippetManager( # pylint:disable=invalid-name UltiSnips_Manager = SnippetManager( # pylint:disable=invalid-name
vim.eval('g:UltiSnipsExpandTrigger'), vim.eval('g:UltiSnipsExpandTrigger'),

View File

@ -10,6 +10,7 @@ from UltiSnips import _vim
from UltiSnips.compatibility import as_unicode from UltiSnips.compatibility import as_unicode
from UltiSnips.indent_util import IndentUtil from UltiSnips.indent_util import IndentUtil
from UltiSnips.text_objects._base import NoneditableTextObject from UltiSnips.text_objects._base import NoneditableTextObject
from UltiSnips.vim_state import _Placeholder
import UltiSnips.snippet_manager import UltiSnips.snippet_manager
@ -95,12 +96,15 @@ class SnippetUtil(object):
""" """
def __init__(self, initial_indent, vmode, vtext, context): def __init__(self, initial_indent, vmode, vtext, context, parent):
self._ind = IndentUtil() self._ind = IndentUtil()
self._visual = _VisualContent(vmode, vtext) self._visual = _VisualContent(vmode, vtext)
self._initial_indent = self._ind.indent_to_spaces(initial_indent) self._initial_indent = self._ind.indent_to_spaces(initial_indent)
self._reset('') self._reset('')
self._context = context self._context = context
self._start = parent.start
self._end = parent.end
self._parent = parent
def _reset(self, cur): def _reset(self, cur):
"""Gets the snippet ready for another update. """Gets the snippet ready for another update.
@ -207,6 +211,13 @@ class SnippetUtil(object):
"""Content of visual expansions.""" """Content of visual expansions."""
return self._visual return self._visual
@property
def p(self):
if self._parent.current_placeholder:
return self._parent.current_placeholder
else:
return _Placeholder('', 0, 0)
@property @property
def context(self): def context(self):
return self._context return self._context
@ -234,6 +245,24 @@ class SnippetUtil(object):
"""Same as shift.""" """Same as shift."""
self.shift(other) self.shift(other)
@property
def snippet_start(self):
"""
Returns start of the snippet in format (line, column).
"""
return self._start
@property
def snippet_end(self):
"""
Returns end of the snippet in format (line, column).
"""
return self._end
@property
def buffer(self):
return _vim.buf
class PythonCode(NoneditableTextObject): class PythonCode(NoneditableTextObject):
@ -250,9 +279,9 @@ class PythonCode(NoneditableTextObject):
mode = snippet.visual_content.mode mode = snippet.visual_content.mode
context = snippet.context context = snippet.context
break break
except AttributeError: except AttributeError as e:
snippet = snippet._parent # pylint:disable=protected-access snippet = snippet._parent # pylint:disable=protected-access
self._snip = SnippetUtil(token.indent, mode, text, context) self._snip = SnippetUtil(token.indent, mode, text, context, snippet)
self._codes = (( self._codes = ((
'import re, os, vim, string, random', 'import re, os, vim, string, random',

View File

@ -34,6 +34,7 @@ class SnippetInstance(EditableTextObject):
self.locals = {'match': last_re, 'context': context} self.locals = {'match': last_re, 'context': context}
self.globals = globals self.globals = globals
self.visual_content = visual_content self.visual_content = visual_content
self.current_placeholder = None
EditableTextObject.__init__(self, parent, start, end, initial_text) EditableTextObject.__init__(self, parent, start, end, initial_text)

View File

@ -3,12 +3,13 @@
"""Some classes to conserve Vim's state for comparing over time.""" """Some classes to conserve Vim's state for comparing over time."""
from collections import deque from collections import deque, namedtuple
from UltiSnips import _vim from UltiSnips import _vim
from UltiSnips.compatibility import as_unicode, byte2col from UltiSnips.compatibility import as_unicode, byte2col
from UltiSnips.position import Position from UltiSnips.position import Position
_Placeholder = namedtuple('_FrozenPlaceholder', ['current_text', 'start', 'end'])
class VimPosition(Position): class VimPosition(Position):
@ -113,6 +114,7 @@ class VisualContentPreserver(object):
"""Forget the preserved state.""" """Forget the preserved state."""
self._mode = '' self._mode = ''
self._text = as_unicode('') self._text = as_unicode('')
self._placeholder = None
def conserve(self): def conserve(self):
"""Save the last visual selection ond the mode it was made in.""" """Save the last visual selection ond the mode it was made in."""
@ -135,6 +137,16 @@ class VisualContentPreserver(object):
text += _vim_line_with_eol(el - 1)[:ec + 1] text += _vim_line_with_eol(el - 1)[:ec + 1]
self._text = text self._text = text
def conserve_placeholder(self, placeholder):
if placeholder:
self._placeholder = _Placeholder(
placeholder.current_text,
placeholder.start,
placeholder.end
)
else:
self._placeholder = None
@property @property
def text(self): def text(self):
"""The conserved text.""" """The conserved text."""
@ -144,3 +156,8 @@ class VisualContentPreserver(object):
def mode(self): def mode(self):
"""The conserved visualmode().""" """The conserved visualmode()."""
return self._mode return self._mode
@property
def placeholder(self):
"""Returns latest selected placeholder."""
return self._placeholder

View File

@ -54,3 +54,16 @@ class Autotrigger_WillProduceNoExceptionWithVimLowerThan214(_VimTest):
"""} """}
keys = 'abc' keys = 'abc'
wanted = 'abc' wanted = 'abc'
class Autotrigger_CanMatchPreviouslySelectedPlaceholder(_VimTest):
files = { 'us/all.snippets': r"""
snippet if "desc"
if ${1:var}: pass
endsnippet
snippet = "desc" "snip.last_placeholder" Ae
`!p snip.rv = snip.context.current_text` == nil
endsnippet
"""}
keys = 'if' + EX + '=' + ESC + 'o='
wanted = 'if var == nil: pass\n='

View File

@ -149,3 +149,18 @@ class ContextSnippets_ContextIsClearedBeforeExpand(_VimTest):
keys = "e" + EX + " " + "e" + EX keys = "e" + EX + " " + "e" + EX
wanted = "1 1" wanted = "1 1"
class ContextSnippets_ContextHasAccessToVisual(_VimTest):
files = { 'us/all.snippets': r"""
snippet test "desc" "snip.visual_text == '123'" we
Yes
endsnippet
snippet test "desc" w
No
endsnippet
"""}
keys = "123" + ESC + "vhh" + EX + "test" + EX + " zzz" + ESC + \
"vhh" + EX + "test" + EX
wanted = "Yes No"

View File

@ -458,6 +458,26 @@ class PythonVisual_LineSelect_Simple(_VimTest):
keys = 'hello\nnice\nworld' + ESC + 'Vkk' + EX + 'test' + EX keys = 'hello\nnice\nworld' + ESC + 'Vkk' + EX + 'test' + EX
wanted = 'hVhello\nnice\nworld\nb' wanted = 'hVhello\nnice\nworld\nb'
class PythonVisual_HasAccessToSelectedPlaceholders(_VimTest):
snippets = (
'test',
"""${1:first} ${2:second} (`!p
snip.rv = "placeholder: " + snip.p.current_text`)"""
)
keys = 'test' + EX + ESC + "otest" + EX + JF + ESC
wanted = """first second (placeholder: first)
first second (placeholder: second)"""
class PythonVisual_HasAccessToZeroPlaceholders(_VimTest):
snippets = (
'test',
"""${1:first} ${2:second} (`!p
snip.rv = "placeholder: " + snip.p.current_text`)"""
)
keys = 'test' + EX + ESC + "otest" + EX + JF + JF + JF + JF
wanted = """first second (placeholder: first second (placeholder: ))
first second (placeholder: )"""
# Tests for https://bugs.launchpad.net/bugs/1259349 # Tests for https://bugs.launchpad.net/bugs/1259349

View File

@ -154,7 +154,8 @@ from UltiSnips.snippet.source import SnippetSource
from UltiSnips.snippet.definition import UltiSnipsSnippetDefinition from UltiSnips.snippet.definition import UltiSnipsSnippetDefinition
class MySnippetSource(SnippetSource): class MySnippetSource(SnippetSource):
def get_snippets(self, filetypes, before, possible, autotrigger_only): def get_snippets(self, filetypes, before, possible, autotrigger_only,
visual_content):
if before.endswith('blumba') and autotrigger_only == False: if before.endswith('blumba') and autotrigger_only == False:
return [ return [
UltiSnipsSnippetDefinition( UltiSnipsSnippetDefinition(