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:
parent
059c9a9da2
commit
3a3e56a987
@ -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*
|
||||
---------------------
|
||||
|
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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)]
|
||||
|
||||
|
@ -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'),
|
||||
|
@ -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',
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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='
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user