Merge support for snippet-actions.
See *UltiSnips-snippet-actions*.
This commit is contained in:
commit
c12867d1b9
99
autoload/UltiSnips/bootstrap.vim
Normal file
99
autoload/UltiSnips/bootstrap.vim
Normal file
@ -0,0 +1,99 @@
|
||||
let s:SourcedFile=expand("<sfile>")
|
||||
|
||||
function! UltiSnips#bootstrap#Bootstrap()
|
||||
if exists('s:did_UltiSnips_bootstrap')
|
||||
return
|
||||
endif
|
||||
let s:did_UltiSnips_bootstrap=1
|
||||
|
||||
if !exists("g:UltiSnipsUsePythonVersion")
|
||||
let g:_uspy=":py3 "
|
||||
if !has("python3")
|
||||
if !has("python")
|
||||
if !exists("g:UltiSnipsNoPythonWarning")
|
||||
echohl WarningMsg
|
||||
echom "UltiSnips requires py >= 2.7 or any py3"
|
||||
echohl None
|
||||
endif
|
||||
unlet g:_uspy
|
||||
return
|
||||
endif
|
||||
let g:_uspy=":py "
|
||||
endif
|
||||
let g:UltiSnipsUsePythonVersion = "<tab>"
|
||||
else
|
||||
" Use user-provided value, but check if it's available.
|
||||
" This uses `has()`, because e.g. `exists(":python3")` is always 2.
|
||||
if g:UltiSnipsUsePythonVersion == 2 && has('python')
|
||||
let g:_uspy=":python "
|
||||
elseif g:UltiSnipsUsePythonVersion == 3 && has('python3')
|
||||
let g:_uspy=":python3 "
|
||||
endif
|
||||
if !exists('g:_uspy')
|
||||
echohl WarningMsg
|
||||
echom "UltiSnips: the Python version from g:UltiSnipsUsePythonVersion (".g:UltiSnipsUsePythonVersion.") is not available."
|
||||
echohl None
|
||||
return
|
||||
endif
|
||||
endif
|
||||
|
||||
" Expand our path
|
||||
exec g:_uspy "import vim, os, sys"
|
||||
exec g:_uspy "sourced_file = vim.eval('s:SourcedFile')"
|
||||
exec g:_uspy "while not os.path.exists(os.path.join(sourced_file, 'pythonx')):
|
||||
\ sourced_file = os.path.dirname(sourced_file)"
|
||||
exec g:_uspy "module_path = os.path.join(sourced_file, 'pythonx')"
|
||||
exec g:_uspy "vim.command(\"let g:UltiSnipsPythonPath = '%s'\" % module_path)"
|
||||
exec g:_uspy "if not hasattr(vim, 'VIM_SPECIAL_PATH'): sys.path.append(module_path)"
|
||||
exec g:_uspy "from UltiSnips.snippet_manager import UltiSnips_Manager"
|
||||
endfunction
|
||||
|
||||
" The trigger used to expand a snippet.
|
||||
" NOTE: expansion and forward jumping can, but needn't be the same trigger
|
||||
if !exists("g:UltiSnipsExpandTrigger")
|
||||
let g:UltiSnipsExpandTrigger = "<tab>"
|
||||
endif
|
||||
|
||||
" The trigger used to display all triggers that could possible
|
||||
" match in the current position.
|
||||
if !exists("g:UltiSnipsListSnippets")
|
||||
let g:UltiSnipsListSnippets = "<c-tab>"
|
||||
endif
|
||||
|
||||
" The trigger used to jump forward to the next placeholder.
|
||||
" NOTE: expansion and forward jumping can, but needn't be the same trigger
|
||||
if !exists("g:UltiSnipsJumpForwardTrigger")
|
||||
let g:UltiSnipsJumpForwardTrigger = "<c-j>"
|
||||
endif
|
||||
|
||||
" The trigger to jump backward inside a snippet
|
||||
if !exists("g:UltiSnipsJumpBackwardTrigger")
|
||||
let g:UltiSnipsJumpBackwardTrigger = "<c-k>"
|
||||
endif
|
||||
|
||||
" Should UltiSnips unmap select mode mappings automagically?
|
||||
if !exists("g:UltiSnipsRemoveSelectModeMappings")
|
||||
let g:UltiSnipsRemoveSelectModeMappings = 1
|
||||
end
|
||||
|
||||
" If UltiSnips should remove Mappings, which should be ignored
|
||||
if !exists("g:UltiSnipsMappingsToIgnore")
|
||||
let g:UltiSnipsMappingsToIgnore = []
|
||||
endif
|
||||
|
||||
" UltiSnipsEdit will use this variable to decide if a new window
|
||||
" is opened when editing. default is "normal", allowed are also
|
||||
" "vertical", "horizontal"
|
||||
if !exists("g:UltiSnipsEditSplit")
|
||||
let g:UltiSnipsEditSplit = 'normal'
|
||||
endif
|
||||
|
||||
" A list of directory names that are searched for snippets.
|
||||
if !exists("g:UltiSnipsSnippetDirectories")
|
||||
let g:UltiSnipsSnippetDirectories = [ "UltiSnips" ]
|
||||
endif
|
||||
|
||||
" Enable or Disable snipmate snippet expansion.
|
||||
if !exists("g:UltiSnipsEnableSnipMate")
|
||||
let g:UltiSnipsEnableSnipMate = 1
|
||||
endif
|
@ -38,6 +38,10 @@ UltiSnips *snippet* *snippets* *UltiSnips*
|
||||
4.7.2 Demos |UltiSnips-demos|
|
||||
4.8 Clearing snippets |UltiSnips-clearing-snippets|
|
||||
4.9 Context snippets |UltiSnips-context-snippets|
|
||||
4.10 Snippet actions |UltiSnips-snippet-actions|
|
||||
4.10.1 Pre-expand actions |UltiSnips-pre-expand-actions|
|
||||
4.10.2 Post-expand actions |UltiSnips-post-expand-actions|
|
||||
4.10.3 Post-jump actions |UltiSnips-post-jump-actions|
|
||||
5. UltiSnips and Other Plugins |UltiSnips-other-plugins|
|
||||
5.1 Existing Integrations |UltiSnips-integrations|
|
||||
5.2 Extending UltiSnips |UltiSnips-extending|
|
||||
@ -1343,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 -------------------
|
||||
@ -1371,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
|
||||
}
|
||||
@ -1390,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:
|
||||
@ -1405,18 +1413,192 @@ 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-snippet-actions*
|
||||
---------------------
|
||||
|
||||
Snippet actions is an arbitrary python code which can be executed at specific
|
||||
points in lifetime of the snippet.
|
||||
|
||||
There are three types of actions:
|
||||
|
||||
* Pre-expand - invoked just after trigger condition was matched, but before
|
||||
snippet actually expanded;
|
||||
* Post-expand - invoked after snippet was expanded and interpolations
|
||||
was applied for the first time, but before jump on the first placeholder.
|
||||
* Jump - invoked just after jump to the next/prev placeholder.
|
||||
|
||||
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.
|
||||
|
||||
*UltiSnips-buffer-proxy*
|
||||
|
||||
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.
|
||||
|
||||
'snip.buffer' has the same interface as 'vim.current.window.buffer'.
|
||||
|
||||
4.10.1 Pre-expand actions *UltiSnips-pre-expand-actions*
|
||||
|
||||
Pre-expand actions can be used to match snippet in one location and then
|
||||
expand it in the different location. Some useful cases are: correcting
|
||||
indentation for snippet; expanding snippet for function declaration in another
|
||||
function body with moving expansion point beyond initial function; performing
|
||||
extract method refactoring via expanding snippet in different place.
|
||||
|
||||
Pre-expand action declared as follows: >
|
||||
pre_expand "python code here"
|
||||
snippet ...
|
||||
endsnippet
|
||||
|
||||
Buffer can be modified in pre-expand action code through variable called
|
||||
'snip.buffer', snippet expansion position will be automatically adjusted.
|
||||
|
||||
If cursor line (where trigger was matched) need to be modified, then special
|
||||
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 'snip.visual_content' will be
|
||||
also declared and will contain text that was selected before snippet expansion
|
||||
(similar to $VISUAL placeholder).
|
||||
|
||||
Following snippet will be expanded at 4 spaces indentation level no matter
|
||||
where it was triggered.
|
||||
|
||||
------------------- SNIP -------------------
|
||||
pre_expand "snip.buffer[snip.line] = ' '*4; snip.cursor.set(line, 4)"
|
||||
snippet d
|
||||
def $1():
|
||||
$0
|
||||
endsnippet
|
||||
------------------- SNAP -------------------
|
||||
|
||||
Following snippet will move the selected code to the end of file and create
|
||||
new method definition for it:
|
||||
|
||||
------------------- SNIP -------------------
|
||||
pre_expand "del snip.buffer[snip.line]; snip.buffer.append(''); snip.cursor.set(len(snip.buffer)-1, 0)"
|
||||
snippet x
|
||||
def $1():
|
||||
${2:${VISUAL}}
|
||||
endsnippet
|
||||
------------------- SNAP -------------------
|
||||
|
||||
4.10.2 Post-expand actions *UltiSnips-post-expand-actions*
|
||||
|
||||
Post-expand actions can be used to perform some actions based on the expanded
|
||||
snippet text. Some cases are: code style formatting (e.g. inserting newlines
|
||||
before and after method declaration), apply actions depending on python
|
||||
interpolation result.
|
||||
|
||||
Post-expand action declared as follows: >
|
||||
post_expand "python code here"
|
||||
snippet ...
|
||||
endsnippet
|
||||
|
||||
Buffer can be modified in post-expand action code through variable called
|
||||
'snip.buffer', snippet expansion position will be automatically adjusted.
|
||||
|
||||
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: '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
|
||||
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 "snip.buffer[snip.snippet_end[0]+1:snip.snippet_end[0]+1] = ['']"
|
||||
snippet d "Description" b
|
||||
def $1():
|
||||
$2
|
||||
endsnippet
|
||||
------------------- SNAP -------------------
|
||||
|
||||
4.10.3 Post-jump actions *UltiSnips-post-jump-actions*
|
||||
|
||||
Post-jump actions can be used to trigger some code based on user input into
|
||||
the placeholders. Notable use cases: expand another snippet after jump or
|
||||
anonymous snippet after last jump (e.g. perform move method refactoring and
|
||||
then insert new method invokation); insert heading into TOC after last jump.
|
||||
|
||||
Jump-expand action declared as follows: >
|
||||
post_jump "python code here"
|
||||
snippet ...
|
||||
endsnippet
|
||||
|
||||
Buffer can be modified in post-expand action code through variable called
|
||||
'snip.buffer', snippet expansion position will be automatically adjusted.
|
||||
|
||||
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
|
||||
accessible as 'tabstop.line' and 'tabstop.col').
|
||||
* 'end' - (line, column) of the ending position;
|
||||
* 'current_text' - text inside the tabstop.
|
||||
|
||||
Following snippet will insert section in the Table of Contents in the vim-help
|
||||
file:
|
||||
|
||||
------------------- SNIP -------------------
|
||||
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)`
|
||||
$0
|
||||
endsnippet
|
||||
------------------- SNAP -------------------
|
||||
|
||||
'insert_toc_item' will be called after first jump and will add newly entered
|
||||
section into the TOC for current file.
|
||||
|
||||
Note: It is also possible to trigger snippet expansion from the jump action.
|
||||
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
|
||||
def insert_method_call(name):
|
||||
vim.command('normal G')
|
||||
snip.expand_anon(name + '($1)\n')
|
||||
endglobal
|
||||
|
||||
post_jump "if snip.tabstop == 0: insert_method_call(snip.tabstops[1].current_text)"
|
||||
snippet d "method declaration" b
|
||||
def $1():
|
||||
$2
|
||||
endsnippet
|
||||
------------------- SNAP -------------------
|
||||
|
||||
==============================================================================
|
||||
5. UltiSnips and Other Plugins *UltiSnips-other-plugins*
|
||||
|
||||
|
@ -3,11 +3,4 @@
|
||||
|
||||
"""Entry point for all thinks UltiSnips."""
|
||||
|
||||
import vim # pylint:disable=import-error
|
||||
|
||||
from UltiSnips.snippet_manager import SnippetManager
|
||||
|
||||
UltiSnips_Manager = SnippetManager( # pylint:disable=invalid-name
|
||||
vim.eval('g:UltiSnipsExpandTrigger'),
|
||||
vim.eval('g:UltiSnipsJumpForwardTrigger'),
|
||||
vim.eval('g:UltiSnipsJumpBackwardTrigger'))
|
||||
from UltiSnips.snippet_manager import UltiSnips_Manager
|
||||
|
@ -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
|
||||
@ -108,6 +129,17 @@ def feedkeys(keys, mode='n'):
|
||||
Mainly for convenience.
|
||||
|
||||
"""
|
||||
if eval('mode()') == 'n':
|
||||
if keys == 'a':
|
||||
cursor_pos = get_cursor_pos()
|
||||
cursor_pos[2] = int(cursor_pos[2]) + 1
|
||||
set_cursor_from_pos(cursor_pos)
|
||||
if keys in 'ai':
|
||||
keys = 'startinsert'
|
||||
|
||||
if keys == 'startinsert':
|
||||
command('startinsert')
|
||||
else:
|
||||
command(as_unicode(r'call feedkeys("%s", "%s")') % (keys, mode))
|
||||
|
||||
|
||||
@ -137,13 +169,15 @@ def select(start, end):
|
||||
col = col2byte(start.line + 1, start.col)
|
||||
vim.current.window.cursor = start.line + 1, col
|
||||
|
||||
mode = eval('mode()')
|
||||
|
||||
move_cmd = ''
|
||||
if eval('mode()') != 'n':
|
||||
if mode != 'n':
|
||||
move_cmd += r"\<Esc>"
|
||||
|
||||
if start == end:
|
||||
# Zero Length Tabstops, use 'i' or 'a'.
|
||||
if col == 0 or eval('mode()') not in 'i' and \
|
||||
if col == 0 or mode not in 'i' and \
|
||||
col < len(buf[start.line]):
|
||||
move_cmd += 'i'
|
||||
else:
|
||||
@ -164,6 +198,32 @@ def select(start, end):
|
||||
start.line + 1, start.col + 1)
|
||||
feedkeys(move_cmd)
|
||||
|
||||
def set_mark_from_pos(name, pos):
|
||||
return _set_pos("'" + name, pos)
|
||||
|
||||
def get_mark_pos(name):
|
||||
return _get_pos("'" + name)
|
||||
|
||||
def set_cursor_from_pos(pos):
|
||||
return _set_pos('.', pos)
|
||||
|
||||
def get_cursor_pos():
|
||||
return _get_pos('.')
|
||||
|
||||
def delete_mark(name):
|
||||
try:
|
||||
return command('delma ' + name)
|
||||
except:
|
||||
return False
|
||||
|
||||
def _set_pos(name, pos):
|
||||
return eval("setpos(\"{}\", {})".format(name, pos))
|
||||
|
||||
def _get_pos(name):
|
||||
return eval("getpos(\"{}\")".format(name))
|
||||
|
||||
def _is_pos_zero(pos):
|
||||
return ['0'] * 4 == pos or [0] == pos
|
||||
|
||||
def _unmap_select_mode_mapping():
|
||||
"""This function unmaps select mode mappings if so wished by the user.
|
||||
|
224
pythonx/UltiSnips/buffer_proxy.py
Normal file
224
pythonx/UltiSnips/buffer_proxy.py
Normal file
@ -0,0 +1,224 @@
|
||||
# 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
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
@contextmanager
|
||||
def use_proxy_buffer(snippets_stack, vstate):
|
||||
"""
|
||||
Forward all changes made in the buffer to the current snippet stack while
|
||||
function call.
|
||||
"""
|
||||
buffer_proxy = VimBufferProxy(snippets_stack, vstate)
|
||||
old_buffer = _vim.buf
|
||||
try:
|
||||
_vim.buf = buffer_proxy
|
||||
yield
|
||||
finally:
|
||||
_vim.buf = old_buffer
|
||||
buffer_proxy.validate_buffer()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def suspend_proxy_edits():
|
||||
"""
|
||||
Prevents changes being applied to the snippet stack while function call.
|
||||
"""
|
||||
if not isinstance(_vim.buf, VimBufferProxy):
|
||||
yield
|
||||
else:
|
||||
try:
|
||||
_vim.buf._disable_edits()
|
||||
yield
|
||||
finally:
|
||||
_vim.buf._enable_edits()
|
||||
|
||||
|
||||
class VimBufferProxy(_vim.VimBuffer):
|
||||
"""
|
||||
Proxy object used for tracking changes that made from snippet actions.
|
||||
|
||||
Unfortunately, vim by itself lacks of the API for changing text in
|
||||
trackable maner.
|
||||
|
||||
Vim marks offers limited functionality for tracking line additions and
|
||||
deletions, but nothing offered for tracking changes withing single line.
|
||||
|
||||
Instance of this class is passed to all snippet actions and behaves as
|
||||
internal vim.current.window.buffer.
|
||||
|
||||
All changes that are made by user passed to diff algorithm, and resulting
|
||||
diff applied to internal snippet structures to ensure they are in sync with
|
||||
actual buffer contents.
|
||||
"""
|
||||
|
||||
def __init__(self, snippets_stack, vstate):
|
||||
"""
|
||||
Instantiate new object.
|
||||
|
||||
snippets_stack is a slice of currently active snippets.
|
||||
"""
|
||||
self._snippets_stack = snippets_stack
|
||||
self._buffer = vim.current.buffer
|
||||
self._change_tick = int(vim.eval("b:changedtick"))
|
||||
self._forward_edits = True
|
||||
self._vstate = vstate
|
||||
|
||||
def is_buffer_changed_outside(self):
|
||||
"""
|
||||
Returns true, if buffer was changed without using proxy object, like
|
||||
with vim.command() or through internal vim.current.window.buffer.
|
||||
"""
|
||||
return self._change_tick < int(vim.eval("b:changedtick"))
|
||||
|
||||
def validate_buffer(self):
|
||||
"""
|
||||
Raises exception if buffer is changes beyound proxy object.
|
||||
"""
|
||||
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 `snip.buffer` '
|
||||
'for buffer modifications.\n\n' +
|
||||
'See :help UltiSnips-buffer-proxy for more info.')
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""
|
||||
Behaves as vim.current.window.buffer.__setitem__ except it tracks
|
||||
changes and applies them to the current snippet stack.
|
||||
"""
|
||||
if isinstance(key, slice):
|
||||
value = [as_vimencoding(line) for line in value]
|
||||
changes = list(self._get_diff(key.start, key.stop, value))
|
||||
self._buffer[key.start:key.stop] = [
|
||||
line.strip('\n') for line in 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
|
||||
|
||||
if self._forward_edits:
|
||||
for change in changes:
|
||||
self._apply_change(change)
|
||||
if self._snippets_stack:
|
||||
self._vstate.remember_buffer(self._snippets_stack[0])
|
||||
|
||||
def __setslice__(self, i, j, text):
|
||||
"""
|
||||
Same as __setitem__.
|
||||
"""
|
||||
self.__setitem__(slice(i, j), text)
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""
|
||||
Just passing call to the vim.current.window.buffer.__getitem__.
|
||||
"""
|
||||
if isinstance(key, slice):
|
||||
return [as_unicode(l) for l in self._buffer[key.start:key.stop]]
|
||||
else:
|
||||
return as_unicode(self._buffer[key])
|
||||
|
||||
def __getslice__(self, i, j):
|
||||
"""
|
||||
Same as __getitem__.
|
||||
"""
|
||||
return self.__getitem__(slice(i, j))
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Same as len(vim.current.window.buffer).
|
||||
"""
|
||||
return len(self._buffer)
|
||||
|
||||
def append(self, line, line_number=-1):
|
||||
"""
|
||||
Same as vim.current.window.buffer.append(), but with tracking changes.
|
||||
"""
|
||||
if line_number < 0:
|
||||
line_number = len(self)
|
||||
if not isinstance(line, list):
|
||||
line = [line]
|
||||
self[line_number:line_number] = [as_vimencoding(l) for l in line]
|
||||
|
||||
def __delitem__(self, key):
|
||||
if isinstance(key, slice):
|
||||
self.__setitem__(key, [])
|
||||
else:
|
||||
self.__setitem__(slice(key, key+1), [])
|
||||
|
||||
def _get_diff(self, start, end, new_value):
|
||||
"""
|
||||
Very fast diffing algorithm when changes are across many lines.
|
||||
"""
|
||||
for line_number in range(start, end):
|
||||
if line_number < 0:
|
||||
line_number = len(self._buffer) + line_number
|
||||
yield ('D', line_number, 0, self._buffer[line_number])
|
||||
|
||||
if start < 0:
|
||||
start = len(self._buffer) + start
|
||||
for line_number in range(0, len(new_value)):
|
||||
yield ('I', start+line_number, 0, new_value[line_number])
|
||||
|
||||
def _get_line_diff(self, line_number, before, after):
|
||||
"""
|
||||
Use precise diffing for tracking changes in single line.
|
||||
"""
|
||||
if before == '':
|
||||
for change in self._get_diff(line_number, line_number+1, [after]):
|
||||
yield change
|
||||
else:
|
||||
for change in diff(before, after):
|
||||
yield (change[0], line_number, change[2], change[3])
|
||||
|
||||
def _apply_change(self, change):
|
||||
"""
|
||||
Apply changeset to current snippets stack, correctly moving around
|
||||
snippet itself or its child.
|
||||
"""
|
||||
if not self._snippets_stack:
|
||||
return
|
||||
|
||||
line_number = change[1]
|
||||
column_number = change[2]
|
||||
line_before = line_number <= self._snippets_stack[0]._start.line
|
||||
column_before = column_number <= self._snippets_stack[0]._start.col
|
||||
if line_before and column_before:
|
||||
direction = 1
|
||||
if change[0] == 'D':
|
||||
direction = -1
|
||||
|
||||
self._snippets_stack[0]._move(
|
||||
Position(line_number, 0),
|
||||
Position(direction, 0)
|
||||
)
|
||||
else:
|
||||
if line_number > self._snippets_stack[0]._end.line:
|
||||
return
|
||||
if column_number > self._snippets_stack[0]._end.col:
|
||||
return
|
||||
self._snippets_stack[0]._do_edit(change)
|
||||
|
||||
def _disable_edits(self):
|
||||
"""
|
||||
Temporary disable applying changes to snippets stack. Should be done
|
||||
while expanding anonymous snippet in the middle of jump to prevent
|
||||
double tracking.
|
||||
"""
|
||||
self._forward_edits = False
|
||||
|
||||
def _enable_edits(self):
|
||||
"""
|
||||
Enables changes forwarding back.
|
||||
"""
|
||||
self._forward_edits = True
|
@ -65,3 +65,13 @@ class Position(object):
|
||||
|
||||
def __repr__(self):
|
||||
return '(%i,%i)' % (self.line, self.col)
|
||||
|
||||
def __getitem__(self, index):
|
||||
if index > 1:
|
||||
raise IndexError(
|
||||
'position can be indexed only 0 (line) and 1 (column)'
|
||||
)
|
||||
if index == 0:
|
||||
return self.line
|
||||
else:
|
||||
return self.col
|
||||
|
@ -10,8 +10,10 @@ import vim
|
||||
from UltiSnips import _vim
|
||||
from UltiSnips.compatibility import as_unicode
|
||||
from UltiSnips.indent_util import IndentUtil
|
||||
from UltiSnips.position import Position
|
||||
from UltiSnips.text import escape
|
||||
from UltiSnips.text_objects import SnippetInstance
|
||||
from UltiSnips.text_objects._python_code import SnippetUtilCursor, SnippetUtilForAction
|
||||
|
||||
__WHITESPACE_SPLIT = re.compile(r"\s")
|
||||
def split_at_whitespace(string):
|
||||
@ -46,7 +48,7 @@ class SnippetDefinition(object):
|
||||
_TABS = re.compile(r"^\t*")
|
||||
|
||||
def __init__(self, priority, trigger, value, description,
|
||||
options, globals, location, context):
|
||||
options, globals, location, context, actions):
|
||||
self._priority = int(priority)
|
||||
self._trigger = as_unicode(trigger)
|
||||
self._value = as_unicode(value)
|
||||
@ -58,6 +60,7 @@ class SnippetDefinition(object):
|
||||
self._location = location
|
||||
self._context_code = context
|
||||
self._context = None
|
||||
self._actions = actions
|
||||
|
||||
# Make sure that we actually match our trigger in case we are
|
||||
# immediately expanded.
|
||||
@ -84,29 +87,82 @@ class SnippetDefinition(object):
|
||||
return False
|
||||
|
||||
def _context_match(self):
|
||||
current = vim.current
|
||||
# skip on empty buffer
|
||||
if len(current.buffer) == 1 and current.buffer[0] == "":
|
||||
if len(vim.current.buffer) == 1 and vim.current.buffer[0] == "":
|
||||
return
|
||||
|
||||
return self._eval_code('snip.context = ' + self._context_code, {
|
||||
'context': None
|
||||
}).context
|
||||
|
||||
def _eval_code(self, code, additional_locals={}):
|
||||
code = "\n".join([
|
||||
'import re, os, vim, string, random',
|
||||
'\n'.join(self._globals.get('!p', [])).replace('\r\n', '\n'),
|
||||
'context["match"] = ' + self._context_code,
|
||||
''
|
||||
code
|
||||
])
|
||||
|
||||
context = {'match': False}
|
||||
current = vim.current
|
||||
|
||||
locals = {
|
||||
'context': context,
|
||||
'window': current.window,
|
||||
'buffer': current.buffer,
|
||||
'line': current.window.cursor[0],
|
||||
'column': current.window.cursor[1],
|
||||
'cursor': current.window.cursor,
|
||||
'line': current.window.cursor[0]-1,
|
||||
'column': current.window.cursor[1]-1,
|
||||
'cursor': SnippetUtilCursor(current.window.cursor)
|
||||
}
|
||||
exec(code, locals)
|
||||
return context["match"]
|
||||
|
||||
locals.update(additional_locals)
|
||||
|
||||
snip = SnippetUtilForAction(locals)
|
||||
|
||||
exec(code, {'snip': snip})
|
||||
|
||||
return snip
|
||||
|
||||
def _execute_action(
|
||||
self,
|
||||
action,
|
||||
context,
|
||||
additional_locals={}
|
||||
):
|
||||
mark_to_use = '`'
|
||||
with _vim.save_mark(mark_to_use):
|
||||
_vim.set_mark_from_pos(mark_to_use, _vim.get_cursor_pos())
|
||||
|
||||
cursor_line_before = _vim.buf.line_till_cursor
|
||||
|
||||
locals = {
|
||||
'context': context,
|
||||
}
|
||||
|
||||
locals.update(additional_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
|
||||
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'
|
||||
)
|
||||
|
||||
return snip
|
||||
|
||||
def has_option(self, opt):
|
||||
"""Check if the named option is set."""
|
||||
@ -184,6 +240,7 @@ class SnippetDefinition(object):
|
||||
self._matched = ''
|
||||
return False
|
||||
|
||||
self._context = None
|
||||
if match and self._context_code:
|
||||
self._context = self._context_match()
|
||||
if not self.context:
|
||||
@ -243,6 +300,65 @@ class SnippetDefinition(object):
|
||||
objects alive inside of Vim."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_pre_expand(self, visual_content, snippets_stack):
|
||||
if 'pre_expand' in self._actions:
|
||||
locals = {'buffer': _vim.buf, 'visual_content': visual_content}
|
||||
|
||||
snip = self._execute_action(
|
||||
self._actions['pre_expand'], self._context, locals
|
||||
)
|
||||
|
||||
self._context = snip.context
|
||||
|
||||
return snip.cursor.is_set()
|
||||
else:
|
||||
return False
|
||||
|
||||
def do_post_expand(self, start, end, snippets_stack):
|
||||
if 'post_expand' in self._actions:
|
||||
locals = {
|
||||
'snippet_start': start,
|
||||
'snippet_end': end,
|
||||
'buffer': _vim.buf
|
||||
}
|
||||
|
||||
snip = self._execute_action(
|
||||
self._actions['post_expand'], snippets_stack[-1].context, locals
|
||||
)
|
||||
|
||||
snippets_stack[-1].context = snip.context
|
||||
|
||||
return snip.cursor.is_set()
|
||||
else:
|
||||
return False
|
||||
|
||||
def do_post_jump(
|
||||
self, tabstop_number, jump_direction, snippets_stack, current_snippet
|
||||
):
|
||||
if 'post_jump' in self._actions:
|
||||
start = current_snippet.start
|
||||
end = current_snippet.end
|
||||
|
||||
locals = {
|
||||
'tabstop': tabstop_number,
|
||||
'jump_direction': jump_direction,
|
||||
'tabstops': current_snippet.get_tabstops(),
|
||||
'snippet_start': start,
|
||||
'snippet_end': end,
|
||||
'buffer': _vim.buf
|
||||
}
|
||||
|
||||
snip = self._execute_action(
|
||||
self._actions['post_jump'], current_snippet.context, locals
|
||||
)
|
||||
|
||||
current_snippet.context = snip.context
|
||||
|
||||
return snip.cursor.is_set()
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def launch(self, text_before, visual_content, parent, start, end):
|
||||
"""Launch this snippet, overwriting the text 'start' to 'end' and
|
||||
keeping the 'text_before' on the launch line.
|
||||
|
@ -16,7 +16,7 @@ class SnipMateSnippetDefinition(SnippetDefinition):
|
||||
def __init__(self, trigger, value, description, location):
|
||||
SnippetDefinition.__init__(self, self.SNIPMATE_SNIPPET_PRIORITY,
|
||||
trigger, value, description, '', {}, location,
|
||||
None)
|
||||
None, {})
|
||||
|
||||
def instantiate(self, snippet_instance, initial_text, indent):
|
||||
parse_and_instantiate(snippet_instance, initial_text, indent)
|
||||
|
@ -10,3 +10,12 @@ def handle_extends(tail, line_index):
|
||||
return 'extends', ([p.strip() for p in tail.split(',')],)
|
||||
else:
|
||||
return 'error', ("'extends' without file types", line_index)
|
||||
|
||||
|
||||
def handle_action(head, tail, line_index):
|
||||
if tail:
|
||||
action = tail.strip('"').replace(r'\"', '"').replace(r'\\\\', r'\\')
|
||||
return head, (action,)
|
||||
else:
|
||||
return 'error', ("'{}' without specified action".format(head),
|
||||
line_index)
|
||||
|
@ -10,7 +10,8 @@ import os
|
||||
from UltiSnips import _vim
|
||||
from UltiSnips.snippet.definition import UltiSnipsSnippetDefinition
|
||||
from UltiSnips.snippet.source.file._base import SnippetFileSource
|
||||
from UltiSnips.snippet.source.file._common import handle_extends
|
||||
from UltiSnips.snippet.source.file._common import handle_extends, \
|
||||
handle_action
|
||||
from UltiSnips.text import LineIterator, head_tail
|
||||
|
||||
|
||||
@ -53,7 +54,9 @@ def find_all_snippet_files(ft):
|
||||
return ret
|
||||
|
||||
|
||||
def _handle_snippet_or_global(filename, line, lines, python_globals, priority):
|
||||
def _handle_snippet_or_global(
|
||||
filename, line, lines, python_globals, priority, pre_expand
|
||||
):
|
||||
"""Parses the snippet that begins at the current line."""
|
||||
start_line_index = lines.line_index
|
||||
descr = ''
|
||||
@ -114,7 +117,7 @@ def _handle_snippet_or_global(filename, line, lines, python_globals, priority):
|
||||
priority, trig, content,
|
||||
descr, opts, python_globals,
|
||||
'%s:%i' % (filename, start_line_index),
|
||||
context)
|
||||
context, pre_expand)
|
||||
return 'snippet', (definition,)
|
||||
else:
|
||||
return 'error', ("Invalid snippet type: '%s'" % snip, lines.line_index)
|
||||
@ -130,14 +133,21 @@ def _parse_snippets_file(data, filename):
|
||||
python_globals = defaultdict(list)
|
||||
lines = LineIterator(data)
|
||||
current_priority = 0
|
||||
actions = {}
|
||||
for line in lines:
|
||||
if not line.strip():
|
||||
continue
|
||||
|
||||
head, tail = head_tail(line)
|
||||
if head in ('snippet', 'global'):
|
||||
snippet = _handle_snippet_or_global(filename, line, lines,
|
||||
python_globals, current_priority)
|
||||
snippet = _handle_snippet_or_global(
|
||||
filename, line, lines,
|
||||
python_globals,
|
||||
current_priority,
|
||||
actions
|
||||
)
|
||||
|
||||
actions = {}
|
||||
if snippet is not None:
|
||||
yield snippet
|
||||
elif head == 'extends':
|
||||
@ -149,6 +159,12 @@ def _parse_snippets_file(data, filename):
|
||||
current_priority = int(tail.split()[0])
|
||||
except (ValueError, IndexError):
|
||||
yield 'error', ('Invalid priority %r' % tail, lines.line_index)
|
||||
elif head in ['pre_expand', 'post_expand', 'post_jump']:
|
||||
head, tail = handle_action(head, tail, lines.line_index)
|
||||
if head == 'error':
|
||||
yield (head, tail)
|
||||
else:
|
||||
actions[head], = tail
|
||||
elif head and not head.startswith('#'):
|
||||
yield 'error', ('Invalid line %r' % line.rstrip(), lines.line_index)
|
||||
|
||||
|
@ -8,6 +8,8 @@ from functools import wraps
|
||||
import os
|
||||
import platform
|
||||
import traceback
|
||||
import vim
|
||||
from contextlib import contextmanager
|
||||
|
||||
from UltiSnips import _vim
|
||||
from UltiSnips._diff import diff, guess_edit
|
||||
@ -18,6 +20,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_proxy import use_proxy_buffer, suspend_proxy_edits
|
||||
|
||||
|
||||
def _ask_user(a, formatted):
|
||||
@ -94,6 +97,9 @@ class SnippetManager(object):
|
||||
|
||||
self._snippet_sources = []
|
||||
|
||||
self._snip_expanded_in_action = False
|
||||
self._inside_action = False
|
||||
|
||||
self._added_snippets_source = AddedSnippetsSource()
|
||||
self.register_snippet_source('ultisnips_files', UltiSnipsFileSource())
|
||||
self.register_snippet_source('added', self._added_snippets_source)
|
||||
@ -205,19 +211,22 @@ class SnippetManager(object):
|
||||
|
||||
@err_to_scratch_buffer
|
||||
def add_snippet(self, trigger, value, description,
|
||||
options, ft='all', priority=0, context=None):
|
||||
options, ft='all', priority=0, context=None, actions={}):
|
||||
"""Add a snippet to the list of known snippets of the given 'ft'."""
|
||||
self._added_snippets_source.add_snippet(ft,
|
||||
UltiSnipsSnippetDefinition(priority, trigger, value,
|
||||
description, options, {}, 'added',
|
||||
context))
|
||||
context, actions))
|
||||
|
||||
@err_to_scratch_buffer
|
||||
def expand_anon(self, value, trigger='', description='', options='', context=None):
|
||||
def expand_anon(
|
||||
self, value, trigger='', description='', options='',
|
||||
context=None, actions={}
|
||||
):
|
||||
"""Expand an anonymous snippet right here."""
|
||||
before = _vim.buf.line_till_cursor
|
||||
snip = UltiSnipsSnippetDefinition(0, trigger, value, description,
|
||||
options, {}, '', context)
|
||||
options, {}, '', context, actions)
|
||||
|
||||
if not trigger or snip.matches(before):
|
||||
self._do_snippet(snip, before)
|
||||
@ -275,6 +284,7 @@ class SnippetManager(object):
|
||||
if _vim.eval('mode()') not in 'in':
|
||||
return
|
||||
|
||||
|
||||
if self._ignore_movements:
|
||||
self._ignore_movements = False
|
||||
return
|
||||
@ -430,12 +440,30 @@ class SnippetManager(object):
|
||||
self._teardown_inner_state()
|
||||
|
||||
def _jump(self, backwards=False):
|
||||
# we need to set 'onemore' there, because of limitations of the vim
|
||||
# API regarding cursor movements; without that test
|
||||
# 'CanExpandAnonSnippetInJumpActionWhileSelected' will fail
|
||||
with _vim.toggle_opt('ve', 'onemore'):
|
||||
"""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[:]
|
||||
|
||||
# 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:
|
||||
snippet_for_action = self._cs
|
||||
elif stack_for_post_jump:
|
||||
snippet_for_action = stack_for_post_jump[-1]
|
||||
else:
|
||||
snippet_for_action = None
|
||||
|
||||
if self._cs:
|
||||
ntab = self._cs.select_next_tab(backwards)
|
||||
if ntab:
|
||||
@ -450,18 +478,28 @@ class SnippetManager(object):
|
||||
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()
|
||||
jumped = self._jump(backwards)
|
||||
self._ctab = ntab
|
||||
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:
|
||||
with use_proxy_buffer(stack_for_post_jump, self._vstate):
|
||||
snippet_for_action.snippet.do_post_jump(
|
||||
ntab.number,
|
||||
-1 if backwards else 1,
|
||||
stack_for_post_jump,
|
||||
snippet_for_action
|
||||
)
|
||||
|
||||
return jumped
|
||||
|
||||
def _leaving_insert_mode(self):
|
||||
@ -557,20 +595,38 @@ class SnippetManager(object):
|
||||
done with it."""
|
||||
self._setup_inner_state()
|
||||
|
||||
self._snip_expanded_in_action = False
|
||||
|
||||
# Adjust before, maybe the trigger is not the complete word
|
||||
text_before = before
|
||||
if snippet.matched:
|
||||
text_before = before[:-len(snippet.matched)]
|
||||
|
||||
with use_proxy_buffer(self._csnippets, self._vstate):
|
||||
with self._action_context():
|
||||
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
|
||||
|
||||
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.
|
||||
# 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),
|
||||
@ -578,7 +634,9 @@ class SnippetManager(object):
|
||||
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)
|
||||
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))
|
||||
@ -590,9 +648,24 @@ class SnippetManager(object):
|
||||
|
||||
si.update_textobjects()
|
||||
|
||||
with use_proxy_buffer(self._csnippets, self._vstate):
|
||||
with self._action_context():
|
||||
snippet.do_post_expand(
|
||||
si._start, si._end, self._csnippets
|
||||
)
|
||||
|
||||
self._vstate.remember_buffer(self._csnippets[0])
|
||||
|
||||
if not self._snip_expanded_in_action:
|
||||
self._jump()
|
||||
elif self._cs.current_text != '':
|
||||
self._jump()
|
||||
else:
|
||||
self._current_snippet_is_done()
|
||||
|
||||
if self._inside_action:
|
||||
self._snip_expanded_in_action = True
|
||||
|
||||
|
||||
def _try_expand(self):
|
||||
"""Try to expand a snippet in the current place."""
|
||||
@ -682,3 +755,17 @@ class SnippetManager(object):
|
||||
if not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
return file_to_edit
|
||||
|
||||
@contextmanager
|
||||
def _action_context(self):
|
||||
try:
|
||||
old_flag = self._inside_action
|
||||
self._inside_action = True
|
||||
yield
|
||||
finally:
|
||||
self._inside_action = old_flag
|
||||
|
||||
UltiSnips_Manager = SnippetManager( # pylint:disable=invalid-name
|
||||
vim.eval('g:UltiSnipsExpandTrigger'),
|
||||
vim.eval('g:UltiSnipsJumpForwardTrigger'),
|
||||
vim.eval('g:UltiSnipsJumpBackwardTrigger'))
|
||||
|
@ -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 and 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.snippet_manager
|
||||
|
||||
|
||||
class _Tabs(object):
|
||||
@ -30,6 +31,54 @@ class _Tabs(object):
|
||||
_VisualContent = namedtuple('_VisualContent', ['mode', 'text'])
|
||||
|
||||
|
||||
class SnippetUtilForAction(dict):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SnippetUtilForAction, self).__init__(*args, **kwargs)
|
||||
self.__dict__ = self
|
||||
|
||||
def expand_anon(self, *args, **kwargs):
|
||||
UltiSnips.snippet_manager.UltiSnips_Manager.expand_anon(
|
||||
*args, **kwargs
|
||||
)
|
||||
self.cursor.preserve()
|
||||
|
||||
|
||||
class SnippetUtilCursor(object):
|
||||
def __init__(self, cursor):
|
||||
self._cursor = [cursor[0] - 1, cursor[1]]
|
||||
self._set = False
|
||||
|
||||
def preserve(self):
|
||||
self._set = True
|
||||
self._cursor = [
|
||||
_vim.buf.cursor[0],
|
||||
_vim.buf.cursor[1],
|
||||
]
|
||||
|
||||
def is_set(self):
|
||||
return self._set
|
||||
|
||||
def set(self, line, column):
|
||||
self.__setitem__(0, line)
|
||||
self.__setitem__(1, column)
|
||||
|
||||
def to_vim_cursor(self):
|
||||
return (self._cursor[0] + 1, self._cursor[1])
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self._cursor[index]
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
self._set = True
|
||||
self._cursor[index] = value
|
||||
|
||||
def __len__(self):
|
||||
return 2
|
||||
|
||||
def __str__(self):
|
||||
return str((self._cursor[0], self._cursor[1]))
|
||||
|
||||
|
||||
class SnippetUtil(object):
|
||||
|
||||
"""Provides easy access to indentation, etc.
|
||||
@ -38,11 +87,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.
|
||||
@ -149,6 +199,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':
|
||||
@ -186,10 +240,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',
|
||||
|
@ -30,6 +30,7 @@ class SnippetInstance(EditableTextObject):
|
||||
self.snippet = snippet
|
||||
self._cts = 0
|
||||
|
||||
self.context = context
|
||||
self.locals = {'match': last_re, 'context': context}
|
||||
self.globals = globals
|
||||
self.visual_content = visual_content
|
||||
@ -130,6 +131,9 @@ class SnippetInstance(EditableTextObject):
|
||||
self._parent = cached_parent
|
||||
return rv
|
||||
|
||||
def get_tabstops(self):
|
||||
return self._tabstops
|
||||
|
||||
|
||||
class _VimCursor(NoneditableTextObject):
|
||||
|
||||
|
@ -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)
|
||||
|
@ -151,6 +151,12 @@ syn match snipPriority "^priority\%(\s.*\|$\)" contains=snipPriorityKeyword disp
|
||||
syn match snipPriorityKeyword "^priority" contained nextgroup=snipPriorityValue skipwhite display
|
||||
syn match snipPriorityValue "-\?\d\+" contained display
|
||||
|
||||
" Actions {{{3
|
||||
|
||||
syn match snipAction "^\(pre_expand\|post_expand\|post_jump\)\%(\s.*\|$\)" contains=snipActionKeyword display
|
||||
syn match snipActionKeyword "^\(pre_expand\|post_expand\|post_jump\)" contained nextgroup=snipActionValue skipwhite display
|
||||
syn match snipActionValue '".*"' contained display
|
||||
|
||||
" Snippt Clearing {{{2
|
||||
|
||||
syn match snipClear "^clearsnippets\%(\s.*\|$\)" contains=snipClearKeyword display
|
||||
@ -201,6 +207,9 @@ hi def link snipTransformationOptions Operator
|
||||
hi def link snipPriorityKeyword Keyword
|
||||
hi def link snipPriorityValue Number
|
||||
|
||||
hi def link snipActionKeyword Keyword
|
||||
hi def link snipActionValue String
|
||||
|
||||
hi def link snipClearKeyword Keyword
|
||||
|
||||
" }}}1
|
||||
|
@ -49,7 +49,7 @@ class ContextSnippets_UseContext(_VimTest):
|
||||
return "< " + ins + " >"
|
||||
endglobal
|
||||
|
||||
snippet a "desc" "wrap(buffer[line-1])" e
|
||||
snippet a "desc" "wrap(snip.buffer[snip.line])" e
|
||||
{ `!p snip.rv = context` }
|
||||
endsnippet
|
||||
"""}
|
||||
@ -59,7 +59,7 @@ class ContextSnippets_UseContext(_VimTest):
|
||||
|
||||
class ContextSnippets_SnippetPriority(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
snippet i "desc" "re.search('err :=', buffer[line-2])" e
|
||||
snippet i "desc" "re.search('err :=', snip.buffer[snip.line-1])" e
|
||||
if err != nil {
|
||||
${1:// pass}
|
||||
}
|
||||
@ -119,7 +119,7 @@ class ContextSnippets_ReportErrorOnIndexOutOfRange(_VimTest):
|
||||
skip_if = lambda self: 'Bug in Neovim.' \
|
||||
if self.vim_flavor == 'neovim' else None
|
||||
files = { 'us/all.snippets': r"""
|
||||
snippet e "desc" "buffer[123]" e
|
||||
snippet e "desc" "snip.buffer[123]" e
|
||||
error
|
||||
endsnippet
|
||||
"""}
|
||||
@ -127,3 +127,25 @@ class ContextSnippets_ReportErrorOnIndexOutOfRange(_VimTest):
|
||||
keys = 'e' + EX
|
||||
wanted = 'e' + EX
|
||||
expected_error = r"IndexError: line number out of range"
|
||||
|
||||
|
||||
class ContextSnippets_CursorIsZeroBased(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
snippet e "desc" "snip.cursor" e
|
||||
`!p snip.rv = str(snip.context)`
|
||||
endsnippet
|
||||
"""}
|
||||
|
||||
keys = "e" + EX
|
||||
wanted = "(2, 1)"
|
||||
|
||||
class ContextSnippets_ContextIsClearedBeforeExpand(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
pre_expand "snip.context = 1 if snip.context is None else 2"
|
||||
snippet e "desc" w
|
||||
`!p snip.rv = str(snip.context)`
|
||||
endsnippet
|
||||
"""}
|
||||
|
||||
keys = "e" + EX + " " + "e" + EX
|
||||
wanted = "1 1"
|
||||
|
@ -69,6 +69,11 @@ class DeleteSnippetInsertion1(_VimTest):
|
||||
snippets = ('test', r"$1${1/(.*)/(?0::.)/}")
|
||||
keys = 'test' + EX + ESC + 'u'
|
||||
wanted = 'test'
|
||||
|
||||
class DoNotCrashOnUndoAndJumpInNestedSnippet(_VimTest):
|
||||
snippets = ('test', r"if $1: $2")
|
||||
keys = 'test' + EX + 'a' + JF + 'test' + EX + ESC + 'u' + JF
|
||||
wanted = 'if a: test'
|
||||
# End: Undo of Snippet insertion #}}}
|
||||
|
||||
# Normal mode editing {{{#
|
||||
|
348
test/test_SnippetActions.py
Normal file
348
test/test_SnippetActions.py
Normal file
@ -0,0 +1,348 @@
|
||||
from test.vim_test_case import VimTestCase as _VimTest
|
||||
from test.constant import *
|
||||
|
||||
|
||||
class SnippetActions_PreActionModifiesBuffer(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
pre_expand "snip.buffer[snip.line:snip.line] = ['\n']"
|
||||
snippet a "desc" "True" e
|
||||
abc
|
||||
endsnippet
|
||||
"""}
|
||||
keys = 'a' + EX
|
||||
wanted = '\nabc'
|
||||
|
||||
|
||||
class SnippetActions_PostActionModifiesBuffer(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
post_expand "snip.buffer[snip.line+1:snip.line+1] = ['\n']"
|
||||
snippet a "desc" "True" e
|
||||
abc
|
||||
endsnippet
|
||||
"""}
|
||||
keys = 'a' + EX
|
||||
wanted = 'abc\n'
|
||||
|
||||
class SnippetActions_ErrorOnBufferModificationThroughCommand(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
pre_expand "vim.command('normal O')"
|
||||
snippet a "desc" "True" e
|
||||
abc
|
||||
endsnippet
|
||||
"""}
|
||||
keys = 'a' + EX
|
||||
expected_error = 'changes are untrackable'
|
||||
|
||||
|
||||
class SnippetActions_ErrorOnModificationSnippetLine(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
post_expand "vim.command('normal dd')"
|
||||
snippet i "desc" "True" e
|
||||
if:
|
||||
$1
|
||||
endsnippet
|
||||
"""}
|
||||
keys = 'i' + EX
|
||||
expected_error = 'line under the cursor was modified'
|
||||
|
||||
|
||||
class SnippetActions_EnsureIndent(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
pre_expand "snip.buffer[snip.line] = ' '*4; snip.cursor[1] = 4"
|
||||
snippet i "desc" "True" e
|
||||
if:
|
||||
$1
|
||||
endsnippet
|
||||
"""}
|
||||
keys = '\ni' + EX + 'i' + EX + 'x'
|
||||
wanted = """
|
||||
if:
|
||||
if:
|
||||
x"""
|
||||
|
||||
|
||||
class SnippetActions_PostActionCanUseSnippetRange(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
global !p
|
||||
def ensure_newlines(start, end):
|
||||
snip.buffer[start[0]:start[0]] = ['\n'] * 2
|
||||
snip.buffer[end[0]+1:end[0]+1] = ['\n'] * 1
|
||||
endglobal
|
||||
|
||||
post_expand "ensure_newlines(snip.snippet_start, snip.snippet_end)"
|
||||
snippet i "desc"
|
||||
if
|
||||
$1
|
||||
else
|
||||
$2
|
||||
end
|
||||
endsnippet
|
||||
"""}
|
||||
keys = '\ni' + EX + 'x' + JF + 'y'
|
||||
wanted = """
|
||||
|
||||
|
||||
if
|
||||
x
|
||||
else
|
||||
y
|
||||
end
|
||||
"""
|
||||
|
||||
|
||||
class SnippetActions_CanModifyParentBody(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
global !p
|
||||
def ensure_newlines(start, end):
|
||||
snip.buffer[start[0]:start[0]] = ['\n'] * 2
|
||||
endglobal
|
||||
|
||||
post_expand "ensure_newlines(snip.snippet_start, snip.snippet_end)"
|
||||
snippet i "desc"
|
||||
if
|
||||
$1
|
||||
else
|
||||
$2
|
||||
end
|
||||
endsnippet
|
||||
"""}
|
||||
keys = '\ni' + EX + 'i' + EX + 'x' + JF + 'y' + JF + JF + 'z'
|
||||
wanted = """
|
||||
|
||||
|
||||
if
|
||||
|
||||
|
||||
if
|
||||
x
|
||||
else
|
||||
y
|
||||
end
|
||||
else
|
||||
z
|
||||
end"""
|
||||
|
||||
|
||||
class SnippetActions_MoveParentSnippetFromChildInPreAction(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
global !p
|
||||
def insert_import():
|
||||
snip.buffer[2:2] = ['import smthing', '']
|
||||
endglobal
|
||||
|
||||
pre_expand "insert_import()"
|
||||
snippet p "desc"
|
||||
print(smthing.traceback())
|
||||
endsnippet
|
||||
|
||||
snippet i "desc"
|
||||
if
|
||||
$1
|
||||
else
|
||||
$2
|
||||
end
|
||||
endsnippet
|
||||
"""}
|
||||
keys = 'i' + EX + 'p' + EX + JF + 'z'
|
||||
wanted = """import smthing
|
||||
|
||||
if
|
||||
print(smthing.traceback())
|
||||
else
|
||||
z
|
||||
end"""
|
||||
|
||||
|
||||
class SnippetActions_CanExpandSnippetInDifferentPlace(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
global !p
|
||||
def expand_after_if(snip):
|
||||
snip.buffer[snip.line] = snip.buffer[snip.line][:snip.column] + \
|
||||
snip.buffer[snip.line][snip.column+1:]
|
||||
snip.cursor[1] = snip.buffer[snip.line].index('if ')+3
|
||||
endglobal
|
||||
|
||||
pre_expand "expand_after_if(snip)"
|
||||
snippet n "append not to if" w
|
||||
not $0
|
||||
endsnippet
|
||||
|
||||
snippet i "if cond" w
|
||||
if $1: $2
|
||||
endsnippet
|
||||
"""}
|
||||
keys = 'i' + EX + 'blah' + JF + 'n' + EX + JF + 'pass'
|
||||
wanted = """if not blah: pass"""
|
||||
|
||||
|
||||
class SnippetActions_MoveVisual(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
global !p
|
||||
def extract_method(snip):
|
||||
del snip.buffer[snip.line]
|
||||
snip.buffer[len(snip.buffer)-1:len(snip.buffer)-1] = ['']
|
||||
snip.cursor.set(len(snip.buffer)-2, 0)
|
||||
endglobal
|
||||
|
||||
pre_expand "extract_method(snip)"
|
||||
snippet n "append not to if" w
|
||||
def $1:
|
||||
${VISUAL}
|
||||
|
||||
endsnippet
|
||||
"""}
|
||||
|
||||
keys = """
|
||||
def a:
|
||||
x()
|
||||
y()
|
||||
z()""" + ESC + 'kVk' + EX + 'n' + EX + 'b'
|
||||
|
||||
wanted = """
|
||||
def a:
|
||||
z()
|
||||
|
||||
def b:
|
||||
x()
|
||||
y()"""
|
||||
|
||||
|
||||
class SnippetActions_CanMirrorTabStopsOutsideOfSnippet(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
post_jump "snip.buffer[2] = 'debug({})'.format(snip.tabstops[1].current_text)"
|
||||
snippet i "desc"
|
||||
if $1:
|
||||
$2
|
||||
endsnippet
|
||||
"""}
|
||||
keys = """
|
||||
---
|
||||
i""" + EX + "test(some(complex(cond(a))))" + JF + "x"
|
||||
wanted = """debug(test(some(complex(cond(a)))))
|
||||
---
|
||||
if test(some(complex(cond(a)))):
|
||||
x"""
|
||||
|
||||
|
||||
class SnippetActions_CanExpandAnonSnippetInJumpAction(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
global !p
|
||||
def expand_anon(snip):
|
||||
if snip.tabstop == 0:
|
||||
snip.expand_anon("a($2, $1)")
|
||||
endglobal
|
||||
|
||||
post_jump "expand_anon(snip)"
|
||||
snippet i "desc"
|
||||
if ${1:cond}:
|
||||
$0
|
||||
endsnippet
|
||||
"""}
|
||||
keys = "i" + EX + "x" + JF + "1" + JF + "2" + JF + ";"
|
||||
wanted = """if x:
|
||||
a(2, 1);"""
|
||||
|
||||
|
||||
class SnippetActions_CanExpandAnonSnippetInJumpActionWhileSelected(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
global !p
|
||||
def expand_anon(snip):
|
||||
if snip.tabstop == 0:
|
||||
snip.expand_anon(" // a($2, $1)")
|
||||
endglobal
|
||||
|
||||
post_jump "expand_anon(snip)"
|
||||
snippet i "desc"
|
||||
if ${1:cond}:
|
||||
${2:pass}
|
||||
endsnippet
|
||||
"""}
|
||||
keys = "i" + EX + "x" + JF + JF + "1" + JF + "2" + JF + ";"
|
||||
wanted = """if x:
|
||||
pass // a(2, 1);"""
|
||||
|
||||
|
||||
class SnippetActions_CanUseContextFromContextMatch(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
pre_expand "snip.buffer[snip.line:snip.line] = [snip.context]"
|
||||
snippet i "desc" "'some context'" e
|
||||
body
|
||||
endsnippet
|
||||
"""}
|
||||
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"""
|
||||
|
||||
class SnippetActions_CanVisuallySelectFirstPlaceholderInAnonSnippetInPre(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
pre_expand "snip.buffer[snip.line] = ''; snip.expand_anon('${1:asd}, ${2:blah}')"
|
||||
snippet test "test new features" wb
|
||||
endsnippet
|
||||
"""}
|
||||
keys = "test" + EX + "1" + JF + "2"
|
||||
wanted = """1, 2"""
|
||||
|
||||
class SnippetActions_UseCorrectJumpActions(_VimTest):
|
||||
files = { 'us/all.snippets': r"""
|
||||
post_jump "snip.buffer[-2:-2]=['a' + str(snip.tabstop)]"
|
||||
snippet a "a" wb
|
||||
$1 {
|
||||
$2
|
||||
}
|
||||
endsnippet
|
||||
|
||||
snippet b "b" wb
|
||||
bbb
|
||||
endsnippet
|
||||
|
||||
post_jump "snip.buffer[-2:-2]=['c' + str(snip.tabstop)]"
|
||||
snippet c "c" w
|
||||
$1 : $2 : $3
|
||||
endsnippet
|
||||
"""}
|
||||
keys = "a" + EX + "1" + JF + "b" + EX + " c" + EX + "2" + JF + "3" + JF + "4" + JF + JF
|
||||
wanted = """1 {
|
||||
bbb 2 : 3 : 4
|
||||
}
|
||||
a1
|
||||
a2
|
||||
c1
|
||||
c2
|
||||
c3
|
||||
c0
|
||||
a0"""
|
@ -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'
|
||||
|
@ -159,7 +159,7 @@ class MySnippetSource(SnippetSource):
|
||||
return [
|
||||
UltiSnipsSnippetDefinition(
|
||||
-100, "blumba", "this is a dynamic snippet", "", "", {}, "blub",
|
||||
None)
|
||||
None, {})
|
||||
]
|
||||
return []
|
||||
""")
|
||||
|
@ -46,16 +46,16 @@ class VimTestCase(unittest.TestCase, TempFileManager):
|
||||
|
||||
# Only checks the output. All work is done in setUp().
|
||||
wanted = self.text_before + self.wanted + self.text_after
|
||||
if self.expected_error:
|
||||
for i in range(self.retries):
|
||||
if self.output and self.expected_error:
|
||||
self.assertRegexpMatches(self.output, self.expected_error)
|
||||
return
|
||||
for i in range(self.retries):
|
||||
if self.output != wanted:
|
||||
if self.output != wanted or self.output is None:
|
||||
# Redo this, but slower
|
||||
self.sleeptime += 0.15
|
||||
self.tearDown()
|
||||
self.setUp()
|
||||
self.assertEqual(self.output, wanted)
|
||||
self.assertMultiLineEqual(self.output, wanted)
|
||||
|
||||
def _extra_vim_config(self, vim_config):
|
||||
"""Adds extra lines to the vim_config list."""
|
||||
|
Loading…
x
Reference in New Issue
Block a user