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:
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
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.current.window.cursor';
'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 -------------------
@ -1463,6 +1480,20 @@ endsnippet
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*
---------------------

View File

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

View File

@ -31,7 +31,8 @@ class SnippetSource(object):
deep_extends = self.get_deep_extends(base_filetypes)
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
parents matching the text 'before'. If 'possible' is true, a partial
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):
snips = self._snippets[ft]
result.extend(snips.get_matching_snippets(before, possible,
autotrigger_only))
autotrigger_only,
visual_content))
return result
def get_clear_priority(self, filetypes):

View File

@ -16,7 +16,8 @@ class SnippetDictionary(object):
"""Add 'snippet' to this dictionary."""
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.
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')]
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:
return [s for s in all_snippets if s.could_match(trigger)]

View File

@ -137,6 +137,7 @@ class SnippetManager(object):
SnipMateFileSource())
self._should_update_textobjects = False
self._should_reset_visual = False
self._reinit()
@ -269,7 +270,7 @@ class SnippetManager(object):
snip = UltiSnipsSnippetDefinition(0, trigger, value, description,
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)
return True
else:
@ -489,6 +490,7 @@ class SnippetManager(object):
def _jump(self, backwards=False):
"""Helper method that does the actual jump."""
if self._should_update_textobjects:
self._should_reset_visual = False
self._cursor_moved()
# 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.end - ntab.start == Position(0, 1)):
ntab_short_and_near = True
if ntab.number == 0:
self._current_snippet_is_done()
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:
# This really shouldn't happen, because a snippet should
# have been popped when its final tabstop was used.
# Cleanup by removing current snippet and recursing.
self._current_snippet_is_done()
jumped = self._jump(backwards)
if jumped:
self._vstate.remember_position()
self._vstate.remember_unnamed_register(self._ctab.current_text)
if self._ctab:
self._vstate.remember_position()
self._vstate.remember_unnamed_register(self._ctab.current_text)
if not ntab_short_and_near:
self._ignore_movements = True
@ -619,7 +634,8 @@ class SnippetManager(object):
filetypes,
before,
partial,
autotrigger_only
autotrigger_only,
self._visual_content
)
for snippet in possible_snippets:
@ -835,6 +851,11 @@ class SnippetManager(object):
finally:
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
vim.eval('g:UltiSnipsExpandTrigger'),

View File

@ -10,6 +10,7 @@ from UltiSnips import _vim
from UltiSnips.compatibility import as_unicode
from UltiSnips.indent_util import IndentUtil
from UltiSnips.text_objects._base import NoneditableTextObject
from UltiSnips.vim_state import _Placeholder
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._visual = _VisualContent(vmode, vtext)
self._initial_indent = self._ind.indent_to_spaces(initial_indent)
self._reset('')
self._context = context
self._start = parent.start
self._end = parent.end
self._parent = parent
def _reset(self, cur):
"""Gets the snippet ready for another update.
@ -207,6 +211,13 @@ class SnippetUtil(object):
"""Content of visual expansions."""
return self._visual
@property
def p(self):
if self._parent.current_placeholder:
return self._parent.current_placeholder
else:
return _Placeholder('', 0, 0)
@property
def context(self):
return self._context
@ -234,6 +245,24 @@ class SnippetUtil(object):
"""Same as shift."""
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):
@ -250,9 +279,9 @@ class PythonCode(NoneditableTextObject):
mode = snippet.visual_content.mode
context = snippet.context
break
except AttributeError:
except AttributeError as e:
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 = ((
'import re, os, vim, string, random',

View File

@ -34,6 +34,7 @@ class SnippetInstance(EditableTextObject):
self.locals = {'match': last_re, 'context': context}
self.globals = globals
self.visual_content = visual_content
self.current_placeholder = None
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."""
from collections import deque
from collections import deque, namedtuple
from UltiSnips import _vim
from UltiSnips.compatibility import as_unicode, byte2col
from UltiSnips.position import Position
_Placeholder = namedtuple('_FrozenPlaceholder', ['current_text', 'start', 'end'])
class VimPosition(Position):
@ -113,6 +114,7 @@ class VisualContentPreserver(object):
"""Forget the preserved state."""
self._mode = ''
self._text = as_unicode('')
self._placeholder = None
def conserve(self):
"""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]
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
def text(self):
"""The conserved text."""
@ -144,3 +156,8 @@ class VisualContentPreserver(object):
def mode(self):
"""The conserved visualmode()."""
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'
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
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
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

View File

@ -154,7 +154,8 @@ from UltiSnips.snippet.source import SnippetSource
from UltiSnips.snippet.definition import UltiSnipsSnippetDefinition
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:
return [
UltiSnipsSnippetDefinition(