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 The following python modules are automatically imported into the scope before
'expression' is evaluated: 're', 'os', 'vim', 'string', 'random'. 'expression' is evaluated: 're', 'os', 'vim', 'string', 'random'.
Also, the following variables are defined: Global variable `snip` will be available with following properties:
'window' - alias for 'vim.current.window' 'snip.window' - alias for 'vim.current.window'
'buffer' - alias for 'vim.current.window.buffer' 'snip.buffer' - alias for 'vim.current.window.buffer'
'cursor' - alias for 'vim.current.cursor' 'snip.cursor' - cursor object, which behaves like
'line' and 'column' - aliases for cursor position '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 ------------------- ------------------- 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 return err
endsnippet endsnippet
------------------- SNAP ------------------- ------------------- SNAP -------------------
@ -1375,7 +1379,7 @@ if $1 {
} }
endsnippet 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 { if err != nil {
$1 $1
} }
@ -1394,7 +1398,7 @@ global !p
import my_utils import my_utils
endglobal 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(): , `!p if my_utils.is_in_err_condition():
snip.rv = "err" snip.rv = "err"
else: 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 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 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: variable inside the snippet:
------------------- SNIP ------------------- ------------------- SNIP -------------------
snippet + "var +=" "re.match('\s*(.*?)\s*:?=', buffer[line-2])" ie snippet + "var +=" "re.match('\s*(.*?)\s*:?=', snip.buffer[snip.line-1])" ie
`!p snip.rv = context.group(1)` += $1 `!p snip.rv = snip.context.group(1)` += $1
endsnippet endsnippet
------------------- SNAP ------------------- ------------------- SNAP -------------------
That snippet will expand to 'var1 +=' after line, which begins from 'var1 :='. 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 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 variables and modules will be available that are stated in
the *UltiSnips-context-snippets* section. 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 modifications. Not 'vim.current.buffer' and not 'vim.command("...")', because
of in that case UltiSnips will not be able to track changes buffer from of in that case UltiSnips will not be able to track changes buffer from
actions. actions.
@ -1458,14 +1462,14 @@ Pre-expand action declared as follows: >
endsnippet endsnippet
Buffer can be modified in pre-expand action code through variable called 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 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 variable method 'snip.cursor.set(line, column)' must be called with the
that case UltiSnips will not remove any matched trigger text and it should desired cursor position. In that case UltiSnips will not remove any matched
be done manually in action code. 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 declared and will contain text that was selected before snippet expansion
(similar to $VISUAL placeholder). (similar to $VISUAL placeholder).
@ -1473,7 +1477,7 @@ Following snippet will be expanded at 4 spaces indentation level no matter
where it was triggered. where it was triggered.
------------------- SNIP ------------------- ------------------- SNIP -------------------
pre_expand "buffer[line] = ' '*4; new_cursor = (line, 4)" pre_expand "snip.buffer[snip.line] = ' '*4; snip.cursor.set(line, 4)"
snippet d snippet d
def $1(): def $1():
$0 $0
@ -1484,7 +1488,7 @@ Following snippet will move the selected code to the end of file and create
new method definition for it: new method definition for it:
------------------- SNIP ------------------- ------------------- 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 snippet x
def $1(): def $1():
${2:${VISUAL}} ${2:${VISUAL}}
@ -1504,13 +1508,13 @@ Post-expand action declared as follows: >
endsnippet endsnippet
Buffer can be modified in post-expand action code through variable called 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 Variables 'snip.snippet_start' and 'snip.snippet_end' will be defined at the
scope and will point to positions of the start and end of expanded snippet action code scope and will point to positions of the start and end of expanded
accordingly in the form '(line, column)'. 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. correct positions if post-action will insert or delete lines before expansion.
Following snippet will expand to method definition and automatically insert 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. function that will insert as many newlines as required in specific context.
------------------- SNIP ------------------- ------------------- 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 snippet d "Description" b
def $1(): def $1():
$2 $2
@ -1538,14 +1542,15 @@ Jump-expand action declared as follows: >
endsnippet endsnippet
Buffer can be modified in post-expand action code through variable called 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: Next variables and methods will be also defined in the action code scope:
* 'tabstop' - number of tabstop jumped onto; * 'snip.tabstop' - number of tabstop jumped onto;
* 'jump_direction' - '1' if jumped forward and '-1' otherwise; * 'snip.jump_direction' - '1' if jumped forward and '-1' otherwise;
* 'tabstops' - list with tabstop objects, see above; * 'snip.tabstops' - list with tabstop objects, see above;
* 'snippet_start' - (line, column) of start of the expanded snippet; * 'snip.snippet_start' - (line, column) of start of the expanded snippet;
* 'snippet_end' - (line, column) of end 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: Tabstop object has several useful properties:
* 'start' - (line, column) of the starting position of the tabstop (also * '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: file:
------------------- SNIP ------------------- ------------------- 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 snippet s "section" b
`!p insert_delimiter_0(snip, t)`$1`!p insert_section_title(snip, t)` `!p insert_delimiter_0(snip, t)`$1`!p insert_section_title(snip, t)`
`!p insert_delimiter_1(snip, t)` `!p insert_delimiter_1(snip, t)`
@ -1569,25 +1574,20 @@ endsnippet
section into the TOC for current file. section into the TOC for current file.
Note: It is also possible to trigger snippet expansion from the jump action. 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 In that case method 'snip.cursor.preserve()' should be called, so UltiSnips
UltiSnips will know that cursor is already at the required position. will know that cursor is already at the required position.
Following example will insert method call at the end of file after user jump Following example will insert method call at the end of file after user jump
out of method declaration snippet. out of method declaration snippet.
------------------- SNIP ------------------- ------------------- SNIP -------------------
global !p global !p
from UltiSnips import UltiSnips_Manager
def insert_method_call(name): def insert_method_call(name):
global new_cursor
vim.command('normal G') vim.command('normal G')
UltiSnips_Manager.expand_anon(name + '($1)\n') snip.expand_anon(name + '($1)\n')
new_cursor = 'keep'
endglobal 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 snippet d "method declaration" b
def $1(): def $1():
$2 $2

View File

@ -12,6 +12,8 @@ from UltiSnips.compatibility import col2byte, byte2col, \
as_unicode, as_vimencoding as_unicode, as_vimencoding
from UltiSnips.position import Position from UltiSnips.position import Position
from contextlib import contextmanager
class VimBuffer(object): class VimBuffer(object):
@ -71,6 +73,25 @@ class VimBuffer(object):
vim.current.window.cursor = pos.line + 1, nbyte vim.current.window.cursor = pos.line + 1, nbyte
buf = VimBuffer() # pylint:disable=invalid-name 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): def escape(inp):
"""Creates a vim-friendly string from a group of """Creates a vim-friendly string from a group of

View File

@ -1,43 +1,80 @@
# coding=utf8 # coding=utf8
import vim import vim
import UltiSnips._vim
from UltiSnips.compatibility import as_unicode, as_vimencoding
from UltiSnips.position import Position from UltiSnips.position import Position
from UltiSnips._diff import diff 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): def __init__(self, snippets_stack):
self._snippets_stack = snippets_stack self._snippets_stack = snippets_stack
self._buffer = vim.current.buffer self._buffer = vim.current.buffer
self._change_tick = int(vim.eval("b:changedtick")) self._change_tick = int(vim.eval("b:changedtick"))
self._forward_edits = True
def is_buffer_changed_outside(self): 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): def validate_buffer(self):
if self.is_buffer_changed_outside(): if self.is_buffer_changed_outside():
raise RuntimeError('buffer was modified using vim.command or ' + raise RuntimeError('buffer was modified using vim.command or ' +
'vim.current.buffer; that changes are untrackable and leads to' + 'vim.current.buffer; that changes are untrackable and leads to ' +
'errors in snippet expansion; use special variable `buffer` for' + 'errors in snippet expansion; use special variable `snip.buffer` '
'buffer modifications') 'for buffer modifications')
def __setitem__(self, key, value): def __setitem__(self, key, value):
if isinstance(key, slice): if isinstance(key, slice):
value = [as_vimencoding(l) for l in value]
changes = list(self._get_diff(key.start, key.stop, value)) changes = list(self._get_diff(key.start, key.stop, value))
self._buffer[key.start:key.stop] = value self._buffer[key.start:key.stop] = value
else: else:
value = as_vimencoding(value)
changes = list(self._get_line_diff(key, self._buffer[key], value)) changes = list(self._get_line_diff(key, self._buffer[key], value))
self._buffer[key] = value self._buffer[key] = value
self._change_tick += 1 self._change_tick += 1
for change in changes: if self._forward_edits:
self._apply_change(change) for change in changes:
self._apply_change(change)
def __setslice__(self, i, j, text):
self.__setitem__(slice(i, j), text)
def __getitem__(self, key): def __getitem__(self, key):
if isinstance(key, slice): 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: 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): def __len__(self):
return len(self._buffer) return len(self._buffer)
@ -47,7 +84,7 @@ class VimBufferHelper:
line_number = len(self) line_number = len(self)
if not isinstance(line, list): if not isinstance(line, list):
line = [line] line = [line]
self[line_number:line_number] = line self[line_number:line_number] = [as_vimencoding(l) for l in line]
def __delitem__(self, key): def __delitem__(self, key):
if isinstance(key, slice): if isinstance(key, slice):
@ -89,3 +126,9 @@ class VimBufferHelper:
) )
else: else:
self._snippets_stack[0]._do_edit(change) 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 import SnippetInstance
from UltiSnips.text_objects._python_code import SnippetUtilForAction, SnippetUtilCursor from UltiSnips.text_objects._python_code import SnippetUtilForAction, SnippetUtilCursor
from UltiSnips.position import Position from UltiSnips.position import Position
from UltiSnips.buffer_helper import VimBufferHelper
__WHITESPACE_SPLIT = re.compile(r"\s") __WHITESPACE_SPLIT = re.compile(r"\s")
def split_at_whitespace(string): def split_at_whitespace(string):
@ -130,46 +129,40 @@ class SnippetDefinition(object):
additional_locals={} additional_locals={}
): ):
mark_to_use = '`' 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 = { locals.update(additional_locals)
'context': context,
}
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()
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
else: else:
_vim.set_cursor_from_pos(new_mark_pos) new_mark_pos = _vim.get_mark_pos(mark_to_use)
if cursor_line_before != _vim.buf.line_till_cursor:
cursor_invalid = False
if _vim._is_pos_zero(new_mark_pos):
cursor_invalid = True 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: if cursor_invalid:
raise RuntimeError( raise RuntimeError(
'line under the cursor was modified, but "snip.cursor" ' + 'line under the cursor was modified, but ' +
'variable is not set; either set set "snip.cursor" to ' + '"snip.cursor" variable is not set; either set set ' +
'new cursor position, or do not modify cursor line' '"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)
return snip return snip
@ -309,9 +302,8 @@ class SnippetDefinition(object):
raise NotImplementedError() raise NotImplementedError()
def do_pre_expand(self, visual_content, snippets_stack): def do_pre_expand(self, visual_content, snippets_stack):
buffer = VimBufferHelper(snippets_stack)
if 'pre_expand' in self._actions: 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( snip = self._execute_action(
self._actions['pre_expand'], self._context, locals self._actions['pre_expand'], self._context, locals
@ -319,55 +311,53 @@ class SnippetDefinition(object):
self._context = snip.context self._context = snip.context
return buffer, snip.cursor.is_set() return snip.cursor.is_set()
else: else:
return buffer, False return False
def do_post_expand(self, start, end, snippets_stack): def do_post_expand(self, start, end, snippets_stack):
buffer = VimBufferHelper(snippets_stack)
if 'post_expand' in self._actions: if 'post_expand' in self._actions:
locals = { locals = {
'snippet_start': start, 'snippet_start': start,
'snippet_end': end, 'snippet_end': end,
'buffer': buffer 'buffer': _vim.buf
} }
snip = self._execute_action( 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: else:
return buffer, False return False
def do_post_jump( def do_post_jump(
self, tabstop_number, jump_direction, snippets_stack self, tabstop_number, jump_direction, snippets_stack
): ):
buffer = VimBufferHelper(snippets_stack)
if 'post_jump' in self._actions: if 'post_jump' in self._actions:
start = snippets_stack[0].start start = snippets_stack[-1].start
end = snippets_stack[0].end end = snippets_stack[-1].end
locals = { locals = {
'tabstop': tabstop_number, 'tabstop': tabstop_number,
'jump_direction': jump_direction, 'jump_direction': jump_direction,
'tabstops': snippets_stack[0].get_tabstops(), 'tabstops': snippets_stack[-1].get_tabstops(),
'snippet_start': start, 'snippet_start': start,
'snippet_end': end, 'snippet_end': end,
'buffer': buffer 'buffer': _vim.buf
} }
snip = self._execute_action( 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: else:
return buffer, (False, None) return False
def launch(self, text_before, visual_content, parent, start, end): 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 find_all_snippet_files, find_snippet_files, AddedSnippetsSource
from UltiSnips.text import escape from UltiSnips.text import escape
from UltiSnips.vim_state import VimState, VisualContentPreserver from UltiSnips.vim_state import VimState, VisualContentPreserver
from UltiSnips.buffer_helper import use_proxy_buffer, suspend_proxy_edits
def _ask_user(a, formatted): def _ask_user(a, formatted):
@ -434,59 +435,61 @@ class SnippetManager(object):
self._teardown_inner_state() self._teardown_inner_state()
def _jump(self, backwards=False): 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 # we need to set 'onemore' there, because of limitations of the vim
# API regarding cursor movements; without that test # API regarding cursor movements; without that test
# 'CanExpandAnonSnippetInJumpActionWhileSelected' will fail # 'CanExpandAnonSnippetInJumpActionWhileSelected' will fail
old_virtualedit = _vim.eval('&ve') with _vim.toggle_opt('ve', 'onemore'):
_vim.command('set ve=onemore') """Helper method that does the actual jump."""
jumped = False
# If next tab has length 1 and the distance between itself and # We need to remember current snippets stack here because of
# self._ctab is 1 then there is 1 less CursorMove events. We # post-jump action on the last tabstop should be able to access
# cannot ignore next movement in such case. # snippet instance which is ended just now.
ntab_short_and_near = False stack_for_post_jump = self._csnippets[:]
if self._cs:
ntab = self._cs.select_next_tab(backwards) # If next tab has length 1 and the distance between itself and
if ntab: # self._ctab is 1 then there is 1 less CursorMove events. We
if self._cs.snippet.has_option('s'): # cannot ignore next movement in such case.
lineno = _vim.buf.cursor.line ntab_short_and_near = False
_vim.buf[lineno] = _vim.buf[lineno].rstrip() if self._cs:
_vim.select(ntab.start, ntab.end) ntab = self._cs.select_next_tab(backwards)
jumped = True if ntab:
if (self._ctab is not None if self._cs.snippet.has_option('s'):
and ntab.start - self._ctab.end == Position(0, 1) lineno = _vim.buf.cursor.line
and ntab.end - ntab.start == Position(0, 1)): _vim.buf[lineno] = _vim.buf[lineno].rstrip()
ntab_short_and_near = True _vim.select(ntab.start, ntab.end)
if ntab.number == 0: 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._current_snippet_is_done()
self._ctab = ntab jumped = self._jump(backwards)
else: if jumped:
# This really shouldn't happen, because a snippet should self._vstate.remember_position()
# have been popped when its final tabstop was used. self._vstate.remember_unnamed_register(self._ctab.current_text)
# Cleanup by removing current snippet and recursing. if not ntab_short_and_near:
self._current_snippet_is_done() 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: if len(stack_for_post_jump) > 0 and ntab is not None:
stack_for_post_jump[0].snippet.do_post_jump( if self._cs:
ntab.number, snippet_for_action = self._cs
-1 if backwards else 1, else:
stack_for_post_jump 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 return jumped
@ -588,54 +591,59 @@ class SnippetManager(object):
if snippet.matched: if snippet.matched:
text_before = before[:-len(snippet.matched)] text_before = before[:-len(snippet.matched)]
new_buffer, cursor_set_in_action = snippet.do_pre_expand( with use_proxy_buffer(self._csnippets):
self._visual_content.text, cursor_set_in_action = snippet.do_pre_expand(
self._csnippets self._visual_content.text,
) self._csnippets
)
new_buffer.validate_buffer()
if cursor_set_in_action: if cursor_set_in_action:
text_before = _vim.buf.line_till_cursor text_before = _vim.buf.line_till_cursor
before = _vim.buf.line_till_cursor before = _vim.buf.line_till_cursor
if self._cs: with suspend_proxy_edits():
start = Position(_vim.buf.cursor.line, len(text_before)) if self._cs:
end = Position(_vim.buf.cursor.line, len(before)) 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 # If cursor is set in pre-action, then action was modified
# in our containing snippet. If this is indeed the case, we have to # cursor line, in that case we do not need to do any edits, it
# make sure that those are properly killed. We do this by # can break snippet
# pretending that the user deleted and retyped the text that our if not cursor_set_in_action:
# trigger matched. # It could be that our trigger contains the content of
edit_actions = [ # TextObjects in our containing snippet. If this is indeed
('D', start.line, start.col, snippet.matched), # the case, we have to make sure that those are properly
('I', start.line, start.col, snippet.matched), # killed. We do this by pretending that the user deleted
] # and retyped the text that our trigger matched.
self._csnippets[0].replay_user_edits(edit_actions) 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, si = snippet.launch(text_before, self._visual_content,
self._cs.find_parent_for_new_to(start), start, end) self._cs.find_parent_for_new_to(start),
else: start, end
start = Position(_vim.buf.cursor.line, len(text_before)) )
end = Position(_vim.buf.cursor.line, len(before)) else:
si = snippet.launch(text_before, self._visual_content, start = Position(_vim.buf.cursor.line, len(text_before))
None, start, end) end = Position(_vim.buf.cursor.line, len(before))
si = snippet.launch(text_before, self._visual_content,
None, start, end)
self._visual_content.reset() self._visual_content.reset()
self._csnippets.append(si) self._csnippets.append(si)
si.update_textobjects() si.update_textobjects()
new_buffer, _ = snippet.do_post_expand( with use_proxy_buffer(self._csnippets):
si._start, si._end, 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): def _try_expand(self):
"""Try to expand a snippet in the current place.""" """Try to expand a snippet in the current place."""

View File

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

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

View File

@ -37,5 +37,9 @@ class TabStop(EditableTextObject):
return self._parent is None return self._parent is None
def __repr__(self): def __repr__(self):
try:
text = self.current_text
except IndexError:
text = '<err>'
return 'TabStop(%s,%r->%r,%r)' % (self.number, self._start, 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): class ContextSnippets_CursorIsZeroBased(_VimTest):
files = { 'us/all.snippets': r""" files = { 'us/all.snippets': r"""
snippet e "desc" "snip.cursor" e snippet e "desc" "snip.cursor" e
`!p snip.rv = str(context)` `!p snip.rv = str(snip.context)`
endsnippet endsnippet
"""} """}

View File

@ -229,9 +229,7 @@ class SnippetActions_CanExpandAnonSnippetInJumpAction(_VimTest):
global !p global !p
def expand_anon(snip): def expand_anon(snip):
if snip.tabstop == 0: if snip.tabstop == 0:
from UltiSnips import UltiSnips_Manager snip.expand_anon("a($2, $1)")
UltiSnips_Manager.expand_anon("a($2, $1)")
snip.cursor.preserve()
endglobal endglobal
post_jump "expand_anon(snip)" post_jump "expand_anon(snip)"
@ -250,9 +248,7 @@ class SnippetActions_CanExpandAnonSnippetInJumpActionWhileSelected(_VimTest):
global !p global !p
def expand_anon(snip): def expand_anon(snip):
if snip.tabstop == 0: if snip.tabstop == 0:
from UltiSnips import UltiSnips_Manager snip.expand_anon(" // a($2, $1)")
UltiSnips_Manager.expand_anon(" // a($2, $1)")
snip.cursor.preserve()
endglobal endglobal
post_jump "expand_anon(snip)" post_jump "expand_anon(snip)"
@ -276,3 +272,38 @@ class SnippetActions_CanUseContextFromContextMatch(_VimTest):
keys = "i" + EX keys = "i" + EX
wanted = """some context wanted = """some context
body""" 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') snippets = ('test', '[ $1$2 ] $1')
keys = 'test' + EX + 'Hello' + JF + 'World' + JF keys = 'test' + EX + 'Hello' + JF + 'World' + JF
wanted = '[ HelloWorld ] Hello' 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'