fix expan_anon in pre-action, snip object, fixes
This commit is contained in:
parent
191ebd8e8b
commit
972305725f
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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."""
|
||||
|
@ -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)
|
||||
|
@ -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',
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
"""}
|
||||
|
||||
|
@ -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"""
|
||||
|
@ -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'
|
||||
|
Loading…
Reference in New Issue
Block a user