fix expan_anon in pre-action, snip object, fixes

This commit is contained in:
Stanislav Seletskiy 2015-06-10 23:32:36 +06:00
parent 191ebd8e8b
commit 972305725f
11 changed files with 337 additions and 200 deletions

View File

@ -1347,17 +1347,21 @@ be wrapped with double-quotes.
The following python modules are automatically imported into the scope before
'expression' is evaluated: 're', 'os', 'vim', 'string', 'random'.
Also, the following variables are defined:
'window' - alias for 'vim.current.window'
'buffer' - alias for 'vim.current.window.buffer'
'cursor' - alias for 'vim.current.cursor'
'line' and 'column' - aliases for cursor position
Global variable `snip` will be available with following properties:
'snip.window' - alias for 'vim.current.window'
'snip.buffer' - alias for 'vim.current.window.buffer'
'snip.cursor' - cursor object, which behaves like
'vim.current.window.cursor', but zero-indexed and with following
additional methods:
- 'preserve()' - special method for executing pre/post/jump actions;
- 'set(line, column)' - sets cursor to specified line and column;
- '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);
Keep in mind, that lines in vim numbered from 1, and lists in python starts
from 0, so to access the current line you need to use 'line-1'.
------------------- SNIP -------------------
snippet r "return" "re.match('^\s+if err ', buffer[line-2])" be
snippet r "return" "re.match('^\s+if err ', snip.buffer[snip.line-1])" be
return err
endsnippet
------------------- SNAP -------------------
@ -1375,7 +1379,7 @@ if $1 {
}
endsnippet
snippet i "if err != nil" "re.match('^\s+[^=]*err\s*:?=', buffer[line-2])" be
snippet i "if err != nil" "re.match('^\s+[^=]*err\s*:?=', snip.buffer[snip.line-1])" be
if err != nil {
$1
}
@ -1394,7 +1398,7 @@ global !p
import my_utils
endglobal
snippet , "return ..., nil/err" "my_utils.is_return_argument(buffer, line, column)" ie
snippet , "return ..., nil/err" "my_utils.is_return_argument(snip)" ie
, `!p if my_utils.is_in_err_condition():
snip.rv = "err"
else:
@ -1409,19 +1413,19 @@ part of custom python module which is called 'my_utils' in this example.
Context condition can return any value which python can use as condition in
it's 'if' statement, and if it's considered 'True', then snippet will be
expanded. The evaluated value of 'condition' is available in the 'context'
expanded. The evaluated value of 'condition' is available in the 'snip.context'
variable inside the snippet:
------------------- SNIP -------------------
snippet + "var +=" "re.match('\s*(.*?)\s*:?=', buffer[line-2])" ie
`!p snip.rv = context.group(1)` += $1
snippet + "var +=" "re.match('\s*(.*?)\s*:?=', snip.buffer[snip.line-1])" ie
`!p snip.rv = snip.context.group(1)` += $1
endsnippet
------------------- SNAP -------------------
That snippet will expand to 'var1 +=' after line, which begins from 'var1 :='.
4.10 Snippets actions *UltiSnips-snippets-actions*
4.10 Snippets actions *UltiSnips-snippet-actions*
---------------------
Snippet actions is an arbitrary python code which can be executed at specific
@ -1439,7 +1443,7 @@ Specified code will be evaluated at stages defined above and same global
variables and modules will be available that are stated in
the *UltiSnips-context-snippets* section.
Note: special variable called 'buffer' should be used for all buffer
Note: special variable called 'snip.buffer' should be used for all buffer
modifications. Not 'vim.current.buffer' and not 'vim.command("...")', because
of in that case UltiSnips will not be able to track changes buffer from
actions.
@ -1458,14 +1462,14 @@ Pre-expand action declared as follows: >
endsnippet
Buffer can be modified in pre-expand action code through variable called
'buffer', snippet expansion position will be automatically adjusted.
'snip.buffer', snippet expansion position will be automatically adjusted.
If cursor line (where trigger was matched) need to be modified, then special
variable named 'new_cursor' must be set to the desired cursor position. In
that case UltiSnips will not remove any matched trigger text and it should
be done manually in action code.
variable method 'snip.cursor.set(line, column)' must be called with the
desired cursor position. In that case UltiSnips will not remove any matched
trigger text and it should be done manually in action code.
To addition to the scope variables defined above 'visual_content' will be also
To addition to the scope variables defined above 'snip.visual_content' will be also
declared and will contain text that was selected before snippet expansion
(similar to $VISUAL placeholder).
@ -1473,7 +1477,7 @@ Following snippet will be expanded at 4 spaces indentation level no matter
where it was triggered.
------------------- SNIP -------------------
pre_expand "buffer[line] = ' '*4; new_cursor = (line, 4)"
pre_expand "snip.buffer[snip.line] = ' '*4; snip.cursor.set(line, 4)"
snippet d
def $1():
$0
@ -1484,7 +1488,7 @@ Following snippet will move the selected code to the end of file and create
new method definition for it:
------------------- SNIP -------------------
pre_expand "del buffer[line]; buffer.append(''); new_cursor = (len(buffer)-1, 0)"
pre_expand "del snip.buffer[snip.line]; snip.buffer.append(''); snip.cursor.set(len(snip.buffer)-1, 0)"
snippet x
def $1():
${2:${VISUAL}}
@ -1504,13 +1508,13 @@ Post-expand action declared as follows: >
endsnippet
Buffer can be modified in post-expand action code through variable called
'buffer', snippet expansion position will be automatically adjusted.
'snip.buffer', snippet expansion position will be automatically adjusted.
Variables 'snippet_start' and 'snippet_end' will be defined at the action code
scope and will point to positions of the start and end of expanded snippet
accordingly in the form '(line, column)'.
Variables 'snip.snippet_start' and 'snip.snippet_end' will be defined at the
action code scope and will point to positions of the start and end of expanded
snippet accordingly in the form '(line, column)'.
Note: 'snippet_start' and 'snippet_end' will automatically adjust to the
Note: 'snip.snippet_start' and 'snip.snippet_end' will automatically adjust to the
correct positions if post-action will insert or delete lines before expansion.
Following snippet will expand to method definition and automatically insert
@ -1518,7 +1522,7 @@ additional newline after end of the snippet. It's very useful to create a
function that will insert as many newlines as required in specific context.
------------------- SNIP -------------------
post_expand "buffer[snippet_end[0]+1:snippet_end[0]+1] = ['']"
post_expand "snip.buffer[snip.snippet_end[0]+1:snip.snippet_end[0]+1] = ['']"
snippet d "Description" b
def $1():
$2
@ -1538,14 +1542,15 @@ Jump-expand action declared as follows: >
endsnippet
Buffer can be modified in post-expand action code through variable called
'buffer', snippet expansion position will be automatically adjusted.
'snip.buffer', snippet expansion position will be automatically adjusted.
Next variables will be also defined in the action code scope:
* 'tabstop' - number of tabstop jumped onto;
* 'jump_direction' - '1' if jumped forward and '-1' otherwise;
* 'tabstops' - list with tabstop objects, see above;
* 'snippet_start' - (line, column) of start of the expanded snippet;
* 'snippet_end' - (line, column) of end of the expanded snippet;
Next variables and methods will be also defined in the action code scope:
* 'snip.tabstop' - number of tabstop jumped onto;
* 'snip.jump_direction' - '1' if jumped forward and '-1' otherwise;
* 'snip.tabstops' - list with tabstop objects, see above;
* 'snip.snippet_start' - (line, column) of start of the expanded snippet;
* 'snip.snippet_end' - (line, column) of end of the expanded snippet;
* 'snip.expand_anon()' - alias for 'UltiSnips_Manager.expand_anon()';
Tabstop object has several useful properties:
* 'start' - (line, column) of the starting position of the tabstop (also
@ -1557,7 +1562,7 @@ Following snippet will insert section in the Table of Contents in the vim-help
file:
------------------- SNIP -------------------
post_jump "if tabstop == 0: insert_toc_item(tabstops[1], buffer)"
post_jump "if snip.tabstop == 0: insert_toc_item(snip.tabstops[1], snip.buffer)"
snippet s "section" b
`!p insert_delimiter_0(snip, t)`$1`!p insert_section_title(snip, t)`
`!p insert_delimiter_1(snip, t)`
@ -1569,25 +1574,20 @@ endsnippet
section into the TOC for current file.
Note: It is also possible to trigger snippet expansion from the jump action.
In that case 'new_cursor' should be set to special value named '"keep"', so
UltiSnips will know that cursor is already at the required position.
In that case method 'snip.cursor.preserve()' should be called, so UltiSnips
will know that cursor is already at the required position.
Following example will insert method call at the end of file after user jump
out of method declaration snippet.
------------------- SNIP -------------------
global !p
from UltiSnips import UltiSnips_Manager
def insert_method_call(name):
global new_cursor
vim.command('normal G')
UltiSnips_Manager.expand_anon(name + '($1)\n')
new_cursor = 'keep'
snip.expand_anon(name + '($1)\n')
endglobal
post_jump "if tabstop == 0: insert_method_call(tabstops[1].current_text)"
post_jump "if snip.tabstop == 0: insert_method_call(snip.tabstops[1].current_text)"
snippet d "method declaration" b
def $1():
$2

View File

@ -12,6 +12,8 @@ from UltiSnips.compatibility import col2byte, byte2col, \
as_unicode, as_vimencoding
from UltiSnips.position import Position
from contextlib import contextmanager
class VimBuffer(object):
@ -71,6 +73,25 @@ class VimBuffer(object):
vim.current.window.cursor = pos.line + 1, nbyte
buf = VimBuffer() # pylint:disable=invalid-name
@contextmanager
def toggle_opt(name, new_value):
old_value = eval('&' + name)
command('set {}={}'.format(name, new_value))
try:
yield
finally:
command('set {}={}'.format(name, old_value))
@contextmanager
def save_mark(name):
old_pos = get_mark_pos(name)
try:
yield
finally:
if _is_pos_zero(old_pos):
delete_mark(name)
else:
set_mark_from_pos(name, old_pos)
def escape(inp):
"""Creates a vim-friendly string from a group of

View File

@ -1,43 +1,80 @@
# coding=utf8
import vim
import UltiSnips._vim
from UltiSnips.compatibility import as_unicode, as_vimencoding
from UltiSnips.position import Position
from UltiSnips._diff import diff
from UltiSnips import _vim
class VimBufferHelper:
from contextlib import contextmanager
@contextmanager
def use_proxy_buffer(snippets_stack):
buffer_proxy = VimBufferHelper(snippets_stack)
old_buffer = _vim.buf
try:
_vim.buf = buffer_proxy
yield
finally:
_vim.buf = old_buffer
buffer_proxy.validate_buffer()
@contextmanager
def suspend_proxy_edits():
if not isinstance(_vim.buf, VimBufferHelper):
yield
else:
try:
_vim.buf._disable_edits()
yield
finally:
_vim.buf._enable_edits()
class VimBufferHelper(_vim.VimBuffer):
def __init__(self, snippets_stack):
self._snippets_stack = snippets_stack
self._buffer = vim.current.buffer
self._change_tick = int(vim.eval("b:changedtick"))
self._forward_edits = True
def is_buffer_changed_outside(self):
return self._change_tick != int(vim.eval("b:changedtick"))
return self._change_tick < int(vim.eval("b:changedtick"))
def validate_buffer(self):
if self.is_buffer_changed_outside():
raise RuntimeError('buffer was modified using vim.command or ' +
'vim.current.buffer; that changes are untrackable and leads to' +
'errors in snippet expansion; use special variable `buffer` for' +
'buffer modifications')
'vim.current.buffer; that changes are untrackable and leads to ' +
'errors in snippet expansion; use special variable `snip.buffer` '
'for buffer modifications')
def __setitem__(self, key, value):
if isinstance(key, slice):
value = [as_vimencoding(l) for l in value]
changes = list(self._get_diff(key.start, key.stop, value))
self._buffer[key.start:key.stop] = value
else:
value = as_vimencoding(value)
changes = list(self._get_line_diff(key, self._buffer[key], value))
self._buffer[key] = value
self._change_tick += 1
for change in changes:
self._apply_change(change)
if self._forward_edits:
for change in changes:
self._apply_change(change)
def __setslice__(self, i, j, text):
self.__setitem__(slice(i, j), text)
def __getitem__(self, key):
if isinstance(key, slice):
return self._buffer[key.start:key.stop]
return [as_unicode(l) for l in self._buffer[key.start:key.stop]]
else:
return self._buffer[key]
return as_unicode(self._buffer[key])
def __getslice__(self, i, j):
return self.__getitem__(slice(i, j))
def __len__(self):
return len(self._buffer)
@ -47,7 +84,7 @@ class VimBufferHelper:
line_number = len(self)
if not isinstance(line, list):
line = [line]
self[line_number:line_number] = line
self[line_number:line_number] = [as_vimencoding(l) for l in line]
def __delitem__(self, key):
if isinstance(key, slice):
@ -89,3 +126,9 @@ class VimBufferHelper:
)
else:
self._snippets_stack[0]._do_edit(change)
def _disable_edits(self):
self._forward_edits = False
def _enable_edits(self):
self._forward_edits = True

View File

@ -14,7 +14,6 @@ from UltiSnips.text import escape
from UltiSnips.text_objects import SnippetInstance
from UltiSnips.text_objects._python_code import SnippetUtilForAction, SnippetUtilCursor
from UltiSnips.position import Position
from UltiSnips.buffer_helper import VimBufferHelper
__WHITESPACE_SPLIT = re.compile(r"\s")
def split_at_whitespace(string):
@ -130,46 +129,40 @@ class SnippetDefinition(object):
additional_locals={}
):
mark_to_use = '`'
mark_pos = _vim.get_mark_pos(mark_to_use)
with _vim.save_mark(mark_to_use):
_vim.set_mark_from_pos(mark_to_use, _vim.get_cursor_pos())
_vim.set_mark_from_pos(mark_to_use, _vim.get_cursor_pos())
cursor_line_before = _vim.buf.line_till_cursor
cursor_line_before = _vim.buf.line_till_cursor
locals = {
'context': context,
}
locals = {
'context': context,
}
locals.update(additional_locals)
locals.update(additional_locals)
snip = self._eval_code(action, locals)
snip = self._eval_code(action, locals)
if snip.cursor.is_set():
vim.current.window.cursor = snip.cursor.to_vim_cursor()
else:
new_mark_pos = _vim.get_mark_pos(mark_to_use)
cursor_invalid = False
if _vim._is_pos_zero(new_mark_pos):
cursor_invalid = True
if snip.cursor.is_set():
vim.current.window.cursor = snip.cursor.to_vim_cursor()
else:
_vim.set_cursor_from_pos(new_mark_pos)
if cursor_line_before != _vim.buf.line_till_cursor:
new_mark_pos = _vim.get_mark_pos(mark_to_use)
cursor_invalid = False
if _vim._is_pos_zero(new_mark_pos):
cursor_invalid = True
else:
_vim.set_cursor_from_pos(new_mark_pos)
if cursor_line_before != _vim.buf.line_till_cursor:
cursor_invalid = True
if cursor_invalid:
raise RuntimeError(
'line under the cursor was modified, but "snip.cursor" ' +
'variable is not set; either set set "snip.cursor" to ' +
'new cursor position, or do not modify cursor line'
)
# restore original mark position
if _vim._is_pos_zero(mark_pos):
_vim.delete_mark(mark_to_use)
else:
_vim.set_mark_from_pos(mark_to_use, mark_pos)
if cursor_invalid:
raise RuntimeError(
'line under the cursor was modified, but ' +
'"snip.cursor" variable is not set; either set set ' +
'"snip.cursor" to new cursor position, or do not ' +
'modify cursor line'
)
return snip
@ -309,9 +302,8 @@ class SnippetDefinition(object):
raise NotImplementedError()
def do_pre_expand(self, visual_content, snippets_stack):
buffer = VimBufferHelper(snippets_stack)
if 'pre_expand' in self._actions:
locals = {'buffer': buffer, 'visual_content': visual_content}
locals = {'buffer': _vim.buf, 'visual_content': visual_content}
snip = self._execute_action(
self._actions['pre_expand'], self._context, locals
@ -319,55 +311,53 @@ class SnippetDefinition(object):
self._context = snip.context
return buffer, snip.cursor.is_set()
return snip.cursor.is_set()
else:
return buffer, False
return False
def do_post_expand(self, start, end, snippets_stack):
buffer = VimBufferHelper(snippets_stack)
if 'post_expand' in self._actions:
locals = {
'snippet_start': start,
'snippet_end': end,
'buffer': buffer
'buffer': _vim.buf
}
snip = self._execute_action(
self._actions['post_expand'], snippets_stack[0].context, locals
self._actions['post_expand'], snippets_stack[-1].context, locals
)
snippets_stack[0].context = snip.context
snippets_stack[-1].context = snip.context
return buffer, snip.cursor.is_set()
return snip.cursor.is_set()
else:
return buffer, False
return False
def do_post_jump(
self, tabstop_number, jump_direction, snippets_stack
):
buffer = VimBufferHelper(snippets_stack)
if 'post_jump' in self._actions:
start = snippets_stack[0].start
end = snippets_stack[0].end
start = snippets_stack[-1].start
end = snippets_stack[-1].end
locals = {
'tabstop': tabstop_number,
'jump_direction': jump_direction,
'tabstops': snippets_stack[0].get_tabstops(),
'tabstops': snippets_stack[-1].get_tabstops(),
'snippet_start': start,
'snippet_end': end,
'buffer': buffer
'buffer': _vim.buf
}
snip = self._execute_action(
self._actions['post_jump'], snippets_stack[0].context, locals
self._actions['post_jump'], snippets_stack[-1].context, locals
)
snippets_stack[0].context = snip.context
snippets_stack[-1].context = snip.context
return buffer, snip.cursor.is_set()
return snip.cursor.is_set()
else:
return buffer, (False, None)
return False
def launch(self, text_before, visual_content, parent, start, end):

View File

@ -18,6 +18,7 @@ from UltiSnips.snippet.source import UltiSnipsFileSource, SnipMateFileSource, \
find_all_snippet_files, find_snippet_files, AddedSnippetsSource
from UltiSnips.text import escape
from UltiSnips.vim_state import VimState, VisualContentPreserver
from UltiSnips.buffer_helper import use_proxy_buffer, suspend_proxy_edits
def _ask_user(a, formatted):
@ -434,59 +435,61 @@ class SnippetManager(object):
self._teardown_inner_state()
def _jump(self, backwards=False):
"""Helper method that does the actual jump."""
jumped = False
# We need to remember current snippets stack here because of
# post-jump action on the last tabstop should be able to access
# snippet instance which is ended just now.
stack_for_post_jump = self._csnippets[:]
# we need to set 'onemore' there, because of limitations of the vim
# API regarding cursor movements; without that test
# 'CanExpandAnonSnippetInJumpActionWhileSelected' will fail
old_virtualedit = _vim.eval('&ve')
_vim.command('set ve=onemore')
with _vim.toggle_opt('ve', 'onemore'):
"""Helper method that does the actual jump."""
jumped = False
# If next tab has length 1 and the distance between itself and
# self._ctab is 1 then there is 1 less CursorMove events. We
# cannot ignore next movement in such case.
ntab_short_and_near = False
if self._cs:
ntab = self._cs.select_next_tab(backwards)
if ntab:
if self._cs.snippet.has_option('s'):
lineno = _vim.buf.cursor.line
_vim.buf[lineno] = _vim.buf[lineno].rstrip()
_vim.select(ntab.start, ntab.end)
jumped = True
if (self._ctab is not None
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:
# We need to remember current snippets stack here because of
# post-jump action on the last tabstop should be able to access
# snippet instance which is ended just now.
stack_for_post_jump = self._csnippets[:]
# If next tab has length 1 and the distance between itself and
# self._ctab is 1 then there is 1 less CursorMove events. We
# cannot ignore next movement in such case.
ntab_short_and_near = False
if self._cs:
ntab = self._cs.select_next_tab(backwards)
if ntab:
if self._cs.snippet.has_option('s'):
lineno = _vim.buf.cursor.line
_vim.buf[lineno] = _vim.buf[lineno].rstrip()
_vim.select(ntab.start, ntab.end)
jumped = True
if (self._ctab is not None
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
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()
self._ctab = ntab
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 not ntab_short_and_near:
self._ignore_movements = True
jumped = self._jump(backwards)
if jumped:
self._vstate.remember_position()
self._vstate.remember_unnamed_register(self._ctab.current_text)
if not ntab_short_and_near:
self._ignore_movements = True
if len(stack_for_post_jump) > 0 and ntab is not None:
stack_for_post_jump[0].snippet.do_post_jump(
ntab.number,
-1 if backwards else 1,
stack_for_post_jump
)
if len(stack_for_post_jump) > 0 and ntab is not None:
if self._cs:
snippet_for_action = self._cs
else:
snippet_for_action = stack_for_post_jump[-1]
_vim.command('set ve=' + old_virtualedit)
with use_proxy_buffer(stack_for_post_jump):
snippet_for_action.snippet.do_post_jump(
ntab.number,
-1 if backwards else 1,
stack_for_post_jump
)
return jumped
@ -588,54 +591,59 @@ class SnippetManager(object):
if snippet.matched:
text_before = before[:-len(snippet.matched)]
new_buffer, cursor_set_in_action = snippet.do_pre_expand(
self._visual_content.text,
self._csnippets
)
new_buffer.validate_buffer()
with use_proxy_buffer(self._csnippets):
cursor_set_in_action = snippet.do_pre_expand(
self._visual_content.text,
self._csnippets
)
if cursor_set_in_action:
text_before = _vim.buf.line_till_cursor
before = _vim.buf.line_till_cursor
if self._cs:
start = Position(_vim.buf.cursor.line, len(text_before))
end = Position(_vim.buf.cursor.line, len(before))
with suspend_proxy_edits():
if self._cs:
start = Position(_vim.buf.cursor.line, len(text_before))
end = Position(_vim.buf.cursor.line, len(before))
# It could be that our trigger contains the content of TextObjects
# in our containing snippet. If this is indeed the case, we have to
# make sure that those are properly killed. We do this by
# pretending that the user deleted and retyped the text that our
# trigger matched.
edit_actions = [
('D', start.line, start.col, snippet.matched),
('I', start.line, start.col, snippet.matched),
]
self._csnippets[0].replay_user_edits(edit_actions)
# If cursor is set in pre-action, then action was modified
# cursor line, in that case we do not need to do any edits, it
# can break snippet
if not cursor_set_in_action:
# It could be that our trigger contains the content of
# TextObjects in our containing snippet. If this is indeed
# the case, we have to make sure that those are properly
# killed. We do this by pretending that the user deleted
# and retyped the text that our trigger matched.
edit_actions = [
('D', start.line, start.col, snippet.matched),
('I', start.line, start.col, snippet.matched),
]
self._csnippets[0].replay_user_edits(edit_actions)
si = snippet.launch(text_before, self._visual_content,
self._cs.find_parent_for_new_to(start), start, end)
else:
start = Position(_vim.buf.cursor.line, len(text_before))
end = Position(_vim.buf.cursor.line, len(before))
si = snippet.launch(text_before, self._visual_content,
None, start, end)
si = snippet.launch(text_before, self._visual_content,
self._cs.find_parent_for_new_to(start),
start, end
)
else:
start = Position(_vim.buf.cursor.line, len(text_before))
end = Position(_vim.buf.cursor.line, len(before))
si = snippet.launch(text_before, self._visual_content,
None, start, end)
self._visual_content.reset()
self._csnippets.append(si)
self._visual_content.reset()
self._csnippets.append(si)
si.update_textobjects()
si.update_textobjects()
new_buffer, _ = snippet.do_post_expand(
si._start, si._end, self._csnippets
)
with use_proxy_buffer(self._csnippets):
snippet.do_post_expand(
si._start, si._end, self._csnippets
)
new_buffer.validate_buffer()
self._vstate.remember_buffer(self._csnippets[0])
self._vstate.remember_buffer(self._csnippets[0])
self._jump()
self._jump()
def _try_expand(self):
"""Try to expand a snippet in the current place."""

View File

@ -178,6 +178,8 @@ class EditableTextObject(TextObject):
for children in self._editable_children:
if children._start <= pos < children._end:
return children.find_parent_for_new_to(pos)
if children._start == pos == children._end:
return children.find_parent_for_new_to(pos)
return self
###############################
@ -222,7 +224,8 @@ class EditableTextObject(TextObject):
else:
child._do_edit(cmd, ctab)
return
elif ((pos < child._start and child._end <= delend) or
elif ((pos < child._start and child._end <= delend and
child.start < delend) or
(pos <= child._start and child._end < delend)):
# Case: this deletion removes the child
to_kill.add(child)

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
import UltiSnips
class _Tabs(object):
@ -35,6 +36,10 @@ class SnippetUtilForAction(dict):
super(SnippetUtilForAction, self).__init__(*args, **kwargs)
self.__dict__ = self
def expand_anon(self, snippet):
UltiSnips.UltiSnips_Manager.expand_anon(snippet)
self.cursor.preserve()
class SnippetUtilCursor(object):
def __init__(self, cursor):
@ -80,11 +85,12 @@ class SnippetUtil(object):
"""
def __init__(self, initial_indent, vmode, vtext):
def __init__(self, initial_indent, vmode, vtext, context):
self._ind = IndentUtil()
self._visual = _VisualContent(vmode, vtext)
self._initial_indent = self._ind.indent_to_spaces(initial_indent)
self._reset('')
self._context = context
def _reset(self, cur):
"""Gets the snippet ready for another update.
@ -191,6 +197,10 @@ class SnippetUtil(object):
"""Content of visual expansions."""
return self._visual
@property
def context(self):
return self._context
def opt(self, option, default=None): # pylint:disable=no-self-use
"""Gets a Vim variable."""
if _vim.eval("exists('%s')" % option) == '1':
@ -228,10 +238,11 @@ class PythonCode(NoneditableTextObject):
self._locals = snippet.locals
text = snippet.visual_content.text
mode = snippet.visual_content.mode
context = snippet.context
break
except AttributeError:
snippet = snippet._parent # pylint:disable=protected-access
self._snip = SnippetUtil(token.indent, mode, text)
self._snip = SnippetUtil(token.indent, mode, text, context)
self._codes = ((
'import re, os, vim, string, random',

View File

@ -37,5 +37,9 @@ class TabStop(EditableTextObject):
return self._parent is None
def __repr__(self):
try:
text = self.current_text
except IndexError:
text = '<err>'
return 'TabStop(%s,%r->%r,%r)' % (self.number, self._start,
self._end, self.current_text)
self._end, text)

View File

@ -132,7 +132,7 @@ class ContextSnippets_ReportErrorOnIndexOutOfRange(_VimTest):
class ContextSnippets_CursorIsZeroBased(_VimTest):
files = { 'us/all.snippets': r"""
snippet e "desc" "snip.cursor" e
`!p snip.rv = str(context)`
`!p snip.rv = str(snip.context)`
endsnippet
"""}

View File

@ -229,9 +229,7 @@ class SnippetActions_CanExpandAnonSnippetInJumpAction(_VimTest):
global !p
def expand_anon(snip):
if snip.tabstop == 0:
from UltiSnips import UltiSnips_Manager
UltiSnips_Manager.expand_anon("a($2, $1)")
snip.cursor.preserve()
snip.expand_anon("a($2, $1)")
endglobal
post_jump "expand_anon(snip)"
@ -250,9 +248,7 @@ class SnippetActions_CanExpandAnonSnippetInJumpActionWhileSelected(_VimTest):
global !p
def expand_anon(snip):
if snip.tabstop == 0:
from UltiSnips import UltiSnips_Manager
UltiSnips_Manager.expand_anon(" // a($2, $1)")
snip.cursor.preserve()
snip.expand_anon(" // a($2, $1)")
endglobal
post_jump "expand_anon(snip)"
@ -276,3 +272,38 @@ class SnippetActions_CanUseContextFromContextMatch(_VimTest):
keys = "i" + EX
wanted = """some context
body"""
class SnippetActions_CanExpandAnonSnippetOnFirstJump(_VimTest):
files = { 'us/all.snippets': r"""
global !p
def expand_new_snippet_on_first_jump(snip):
if snip.tabstop == 1:
snip.expand_anon("some_check($1, $2, $3)")
endglobal
post_jump "expand_new_snippet_on_first_jump(snip)"
snippet "test" "test new features" "True" bwre
if $1: $2
endsnippet
"""}
keys = "test" + EX + "1" + JF + "2" + JF + "3" + JF + " or 4" + JF + "5"
wanted = """if some_check(1, 2, 3) or 4: 5"""
class SnippetActions_CanExpandAnonOnPreExpand(_VimTest):
files = { 'us/all.snippets': r"""
pre_expand "snip.buffer[snip.line] = ''; snip.expand_anon('totally_different($2, $1)')"
snippet test "test new features" wb
endsnippet
"""}
keys = "test" + EX + "1" + JF + "2" + JF + "3"
wanted = """totally_different(2, 1)3"""
class SnippetActions_CanEvenWrapSnippetInPreAction(_VimTest):
files = { 'us/all.snippets': r"""
pre_expand "snip.buffer[snip.line] = ''; snip.expand_anon('some_wrapper($1): $2')"
snippet test "test new features" wb
wrapme($2, $1)
endsnippet
"""}
keys = "test" + EX + "1" + JF + "2" + JF + "3" + JF + "4"
wanted = """some_wrapper(wrapme(2, 1)3): 4"""

View File

@ -380,3 +380,29 @@ class TabStop_AdjacentTabStopAddText_ExpectCorrectResult(_VimTest):
snippets = ('test', '[ $1$2 ] $1')
keys = 'test' + EX + 'Hello' + JF + 'World' + JF
wanted = '[ HelloWorld ] Hello'
class TabStop_KeepCorrectJumpListOnOverwriteOfPartOfSnippet(_VimTest):
files = { 'us/all.snippets': r"""
snippet i
ia$1: $2
endsnippet
snippet ia
ia($1, $2)
endsnippet"""}
keys = 'i' + EX + EX + '1' + JF + '2' + JF + ' after' + JF + '3'
wanted = 'ia(1, 2) after: 3'
class TabStop_KeepCorrectJumpListOnOverwriteOfPartOfSnippetRE(_VimTest):
files = { 'us/all.snippets': r"""
snippet i
ia$1: $2
endsnippet
snippet "^ia" "regexp" r
ia($1, $2)
endsnippet"""}
keys = 'i' + EX + EX + '1' + JF + '2' + JF + ' after' + JF + '3'
wanted = 'ia(1, 2) after: 3'