Made trigger keys configurable. Added support for setting forward jump and expand to the same key

This commit is contained in:
Holger Rapp 2009-07-21 10:21:05 +02:00
parent 9292919205
commit d0a50ae9d2
6 changed files with 240 additions and 147 deletions

View File

@ -1,4 +1,7 @@
trunk: trunk:
- Made triggers configurable. You can also use the same trigger for
expanding and tabbing. The TextMate configuration <tab> and <s-tab> is now
possible.
- Conditional Inserts can now be nested - Conditional Inserts can now be nested
- Added support for b option. This only considers a snippet at the beginning - Added support for b option. This only considers a snippet at the beginning
of a line ( *UltiSnips-adding-snippets* ) of a line ( *UltiSnips-adding-snippets* )

View File

@ -10,6 +10,12 @@ ${4:" Last Modified: `!v strftime("%B %d, %Y")`}
$0 $0
endsnippet endsnippet
snippet gvar "Global / configuration variable"
if !exists("g:${1:MyUltraImportantVar}")
let g:$1 = ${2:"${3:<tab>}"}
endif
endsnippet
snippet guard snippet guard
if exists('${1:did_`!p res=fn.replace('.','_')`}') || &cp${2: || version < 700} if exists('${1:did_`!p res=fn.replace('.','_')`}') || &cp${2: || version < 700}
finish finish

View File

@ -106,12 +106,18 @@ To Update an installation, simply pull the latest revision: >
============================================================================= =============================================================================
3. SETTINGS *UltiSnips-settings* 3. SETTINGS *UltiSnips-settings*
Currently, there are no settings configurable for UltiSnips. The following key You can freely define the keys used to expand a snippet and jump forward and
keys are defined for snippets expansion: > backwards in it. The variables with the default values are: >
g:UltiSnipsExpandTrigger <tab>
<TAB> Expands a snippet g:UltiSnipsJumpForwardTrigger <c-j>
<c-j> In insert mode: Jump to the next placeholder g:UltiSnipsJumpBackwardTrigger <c-k>
<c-k> Jump to the previous placeholder
It is possible to set the Expand and Forward trigger to the same value. You
can get a more TextMate like configuration by adding the next three lines to
you vimrc: >
let g:UltiSnipsExpandTrigger="<tab>"
let g:UltiSnipsJumpForwardTrigger="<tab>"
let g:UltiSnipsJumpBackwardTrigger="<s-tab>"
============================================================================= =============================================================================
4. SYNTAX *UltiSnips-syntax* 4. SYNTAX *UltiSnips-syntax*

View File

@ -1,43 +1,64 @@
" File: UltiSnips.vim
" Author: Holger Rapp <SirVer@gmx.de>
" Description: The Ultimate Snippets solution for Vim
" Last Modified: July 21, 2009
"
" Testing Info: {{{
" Running vim + ultisnips with the absolute bar minimum settings inside a screen session:
" $ screen -S vim
" $ vim -u NONE -U NONE -c ':set nocompatible' -c ':set runtimepath+=.'
" $ ./test.py # launch the testsuite
" }}}
"" FUNCTIONS if exists('did_UltiSnips_vim') || &cp || version < 700
finish
function! UltiSnips_ExpandSnippet()
if exists('g:SuperTabMappingForward')
if g:SuperTabMappingForward == "<s-tab>"
let SuperTabKey = "\<c-n>"
elseif g:SuperTabMappingBackward == "<s-tab>"
let SuperTabKey = "\<c-p>"
endif
endif
" Now, really expand something
py << EOF
if not UltiSnips_Manager.try_expand():
vim.command("""if exists('SuperTabKey')
call feedkeys(SuperTabKey)
endif endif
""")
EOF " Global Variables {{{
return "" " 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 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
" }}}
"" FUNCTIONS {{{
function! UltiSnips_ExpandSnippet()
py UltiSnips_Manager.expand()
return ""
endfunction
function! UltiSnips_ExpandSnippetOrJump()
py UltiSnips_Manager.expand_or_jump()
return ""
endfunction endfunction
function! UltiSnips_JumpBackwards() function! UltiSnips_JumpBackwards()
py << EOF py UltiSnips_Manager.jump_backwards()
UltiSnips_Manager.jump(True) return ""
EOF
return ""
endfunction endfunction
function! UltiSnips_JumpForwards() function! UltiSnips_JumpForwards()
py << EOF py UltiSnips_Manager.jump_forwards()
UltiSnips_Manager.jump() return ""
EOF
return ""
endfunction endfunction
" }}}
"" STARTUP CODE "" STARTUP CODE {{{
" Expand our path " Expand our path
python << EOF python << EOF
@ -51,19 +72,30 @@ for p in vim.eval("&runtimepath").split(','):
break break
from UltiSnips import UltiSnips_Manager from UltiSnips import UltiSnips_Manager
UltiSnips_Manager.expand_trigger = vim.eval("g:UltiSnipsExpandTrigger")
UltiSnips_Manager.forward_trigger = vim.eval("g:UltiSnipsJumpForwardTrigger")
UltiSnips_Manager.backward_trigger = vim.eval("g:UltiSnipsJumpBackwardTrigger")
EOF EOF
" You can remap these " Map the keys correctly
inoremap <Tab> <C-R>=UltiSnips_ExpandSnippet()<cr> if g:UltiSnipsExpandTrigger == g:UltiSnipsJumpForwardTrigger
snoremap <Tab> <Esc>:call UltiSnips_ExpandSnippet()<cr> exec "inoremap " . g:UltiSnipsExpandTrigger . " <C-R>=UltiSnips_ExpandSnippetOrJump()<cr>"
inoremap <C-k> <C-R>=UltiSnips_JumpBackwards()<cr> exec "snoremap " . g:UltiSnipsExpandTrigger . " <Esc>:call UltiSnips_ExpandSnippetOrJump()<cr>"
snoremap <C-k> <Esc>:call UltiSnips_JumpBackwards()<cr> else
inoremap <C-j> <C-R>=UltiSnips_JumpForwards()<cr> exec "inoremap " . g:UltiSnipsExpandTrigger . " <C-R>=UltiSnips_ExpandSnippet()<cr>"
snoremap <C-j> <Esc>:call UltiSnips_JumpForwards()<cr> exec "snoremap " . g:UltiSnipsExpandTrigger . " <Esc>:call UltiSnips_ExpandSnippet()<cr>"
exec "inoremap " . g:UltiSnipsJumpForwardTrigger . " <C-R>=UltiSnips_JumpForwards()<cr>"
exec "snoremap " . g:UltiSnipsJumpForwardTrigger . " <Esc>:call UltiSnips_JumpForwards()<cr>"
endif
exec "inoremap " . g:UltiSnipsJumpBackwardTrigger . " <C-R>=UltiSnips_JumpBackwards()<cr>"
exec "snoremap " . g:UltiSnipsJumpBackwardTrigger . " <Esc>:call UltiSnips_JumpBackwards()<cr>"
" Do not remap this. " Do not remap this.
snoremap <BS> <Esc>:py UltiSnips_Manager.backspace_while_selected()<cr> snoremap <BS> <Esc>:py UltiSnips_Manager.backspace_while_selected()<cr>
au CursorMovedI * py UltiSnips_Manager.cursor_moved() au CursorMovedI * py UltiSnips_Manager.cursor_moved()
au InsertEnter * py UltiSnips_Manager.entered_insert_mode() au InsertEnter * py UltiSnips_Manager.entered_insert_mode()
let did_UltiSnips_vim=1
" }}}

View File

@ -11,6 +11,8 @@ from UltiSnips.Geometry import Position
from UltiSnips.TextObjects import * from UltiSnips.TextObjects import *
from UltiSnips.Buffer import VimBuffer from UltiSnips.Buffer import VimBuffer
from UltiSnips.debug import debug
class Snippet(object): class Snippet(object):
_INDENT = re.compile(r"^[ \t]*") _INDENT = re.compile(r"^[ \t]*")
@ -166,20 +168,38 @@ class VimState(object):
class SnippetManager(object): class SnippetManager(object):
def __init__(self): def __init__(self):
self._vstate = VimState() self._vstate = VimState()
self._supertab_keys = None
self.reset() self.reset()
def reset(self): def reset(self):
self._snippets = {} self._snippets = {}
self._csnippets = [] self._csnippets = []
self._reinit() self._reinit()
def _reinit(self): def jump_forwards(self):
self._ctab = None if not self._jump():
self._span_selected = None return self._handle_failure(self.forward_trigger)
self._expect_move_wo_change = False
def jump_backwards(self):
if not self._jump(True):
return self._handle_failure(backward_trigger)
def expand(self):
if not self._try_expand():
self._handle_failure(self.expand_trigger)
def expand_or_jump(self):
"""
This function is used for people who wants to have the same trigger for
expansion and forward jumping. It first tries to expand a snippet, if
this fails, it tries to jump forward.
"""
rv = self._try_expand()
if not rv:
rv = self._jump()
if not rv:
self._handle_failure(self.expand_trigger)
def add_snippet(self, trigger, value, descr, options): def add_snippet(self, trigger, value, descr, options):
if "all" not in self._snippets: if "all" not in self._snippets:
@ -188,7 +208,94 @@ class SnippetManager(object):
l.append(Snippet(trigger,value, descr, options)) l.append(Snippet(trigger,value, descr, options))
self._snippets["all"][trigger] = l self._snippets["all"][trigger] = l
def jump(self, backwards = False):
def backspace_while_selected(self):
"""
This is called when backspace was used while a placeholder was selected.
"""
# BS was called in select mode
if self._cs and (self._span_selected is not None):
# This only happens when a default value is delted using backspace
vim.command(r'call feedkeys("i")')
self._chars_entered('')
else:
vim.command(r'call feedkeys("\<BS>")')
def cursor_moved(self):
self._vstate.update()
if not self._vstate.buf_changed and not self._expect_move_wo_change:
self._check_if_still_inside_snippet()
if not self._ctab:
return
if self._vstate.buf_changed and self._ctab:
# Detect a carriage return
if self._vstate.moved.col <= 0 and self._vstate.moved.line == 1:
# Multiple things might have happened: either the user entered
# a newline character or pasted some text which means we have
# to copy everything he entered on the last line and keep the
# indent vim chose for this line.
lline = vim.current.buffer[self._vstate.ppos.line]
# Another thing that might have happened is that a word
# wrapped, in this case the last line is shortened and we must
# delete what vim deleted there
line_was_shortened = len(self._vstate.last_line) > len(lline)
user_didnt_enter_newline = len(lline) != self._vstate.ppos.col
if line_was_shortened and user_didnt_enter_newline:
cline = vim.current.buffer[self._vstate.pos.line]
self._backspace(len(self._vstate.last_line)-len(lline))
self._chars_entered('\n' + cline, 1)
else:
pentered = lline[self._vstate.ppos.col:]
this_entered = vim.current.line[:self._vstate.pos.col]
self._chars_entered(pentered + '\n' + this_entered)
elif self._vstate.moved.line == 0 and self._vstate.moved.col<0:
# Some deleting was going on
self._backspace(-self._vstate.moved.col)
elif self._vstate.moved.line < 0:
# Backspace over line end
self._backspace(1)
else:
line = vim.current.line
chars = line[self._vstate.pos.col - self._vstate.moved.col:
self._vstate.pos.col]
self._chars_entered(chars)
self._expect_move_wo_change = False
def entered_insert_mode(self):
self._vstate.update()
if self._cs and self._vstate.has_moved:
self._reinit()
self._csnippets = []
###################################
# Private/Protect Functions Below #
###################################
def _reinit(self):
self._ctab = None
self._span_selected = None
self._expect_move_wo_change = False
def _check_if_still_inside_snippet(self):
# Cursor moved without input.
self._ctab = None
# Did we leave the snippet with this movement?
if self._cs and not (self._vstate.pos in self._cs.abs_span):
self._csnippets.pop()
self._reinit()
self._check_if_still_inside_snippet()
def _jump(self, backwards = False):
if self._cs: if self._cs:
self._expect_move_wo_change = True self._expect_move_wo_change = True
self._ctab = self._cs.select_next_tab(backwards) self._ctab = self._cs.select_next_tab(backwards)
@ -198,14 +305,41 @@ class SnippetManager(object):
else: else:
self._csnippets.pop() self._csnippets.pop()
if self._cs: if self._cs:
self.jump(backwards) self._jump(backwards)
return True return True
self._vstate.update() self._vstate.update()
return True return True
return False return False
def try_expand(self, backwards = False):
def _handle_failure(self, trigger):
"""
Mainly make sure that we play well with SuperTab
"""
feedkey = None
if not self._supertab_keys:
if vim.eval("exists('g:SuperTabMappingForward')") != "0":
self._supertab_keys = (
vim.eval("g:SuperTabMappingForward"),
vim.eval("g:SuperTabMappingBackward"),
)
else:
self._supertab_keys = [ '', '' ]
for idx, sttrig in enumerate(self._supertab_keys):
if trigger.lower() == sttrig.lower():
if idx == 0:
feedkey= r"\<c-n>"
elif idx == 1:
feedkey = r"\<c-p>"
break
if feedkey:
vim.command(r'call feedkeys("%s")' % feedkey)
def _try_expand(self):
filetypes = vim.eval("&filetype").split(".") + [ "all" ] filetypes = vim.eval("&filetype").split(".") + [ "all" ]
for ft in filetypes[::-1]: for ft in filetypes[::-1]:
if len(ft) and ft not in self._snippets: if len(ft) and ft not in self._snippets:
@ -279,7 +413,7 @@ class SnippetManager(object):
if si.has_tabs: if si.has_tabs:
self._csnippets.append(si) self._csnippets.append(si)
self.jump() self._jump()
else: else:
self._vb = VimBuffer(text_before, after) self._vb = VimBuffer(text_before, after)
@ -289,88 +423,11 @@ class SnippetManager(object):
self._vb.replace_lines(lineno-1, lineno-1, self._vb.replace_lines(lineno-1, lineno-1,
self._cs._current_text) self._cs._current_text)
self.jump() self._jump()
return True return True
def backspace_while_selected(self):
# BS was called in select mode
if self._cs and (self._span_selected is not None):
# This only happens when a default value is delted using backspace
vim.command(r'call feedkeys("i")')
self._chars_entered('')
else:
vim.command(r'call feedkeys("\<BS>")')
def _check_if_still_inside_snippet(self):
# Cursor moved without input.
self._ctab = None
# Did we leave the snippet with this movement?
if self._cs and not (self._vstate.pos in self._cs.abs_span):
self._csnippets.pop()
self._reinit()
self._check_if_still_inside_snippet()
def cursor_moved(self):
self._vstate.update()
if not self._vstate.buf_changed and not self._expect_move_wo_change:
self._check_if_still_inside_snippet()
if not self._ctab:
return
if self._vstate.buf_changed and self._ctab:
# Detect a carriage return
if self._vstate.moved.col <= 0 and self._vstate.moved.line == 1:
# Multiple things might have happened: either the user entered
# a newline character or pasted some text which means we have
# to copy everything he entered on the last line and keep the
# indent vim chose for this line.
lline = vim.current.buffer[self._vstate.ppos.line]
# Another thing that might have happened is that a word
# wrapped, in this case the last line is shortened and we must
# delete what vim deleted there
line_was_shortened = len(self._vstate.last_line) > len(lline)
user_didnt_enter_newline = len(lline) != self._vstate.ppos.col
if line_was_shortened and user_didnt_enter_newline:
cline = vim.current.buffer[self._vstate.pos.line]
self._backspace(len(self._vstate.last_line)-len(lline))
self._chars_entered('\n' + cline, 1)
else:
pentered = lline[self._vstate.ppos.col:]
this_entered = vim.current.line[:self._vstate.pos.col]
self._chars_entered(pentered + '\n' + this_entered)
elif self._vstate.moved.line == 0 and self._vstate.moved.col<0:
# Some deleting was going on
self._backspace(-self._vstate.moved.col)
elif self._vstate.moved.line < 0:
# Backspace over line end
self._backspace(1)
else:
line = vim.current.line
chars = line[self._vstate.pos.col - self._vstate.moved.col:
self._vstate.pos.col]
self._chars_entered(chars)
self._expect_move_wo_change = False
def entered_insert_mode(self):
self._vstate.update()
if self._cs and self._vstate.has_moved:
self._reinit()
self._csnippets = []
###################################
# Private/Protect Functions Below #
###################################
# Input Handling # Input Handling
def _chars_entered(self, chars, del_more_lines = 0): def _chars_entered(self, chars, del_more_lines = 0):
if (self._span_selected is not None): if (self._span_selected is not None):

23
test.py
View File

@ -1062,24 +1062,13 @@ if __name__ == '__main__':
test_loader = unittest.TestLoader() test_loader = unittest.TestLoader()
all_test_suites = test_loader.loadTestsFromModule(__import__("test")) all_test_suites = test_loader.loadTestsFromModule(__import__("test"))
# Send some mappings to vim # Set the options
send(":imapclear\n", options.session) send(""":let g:UltiSnipsExpandTrigger="<tab>"\n""", options.session)
send(":smapclear\n", options.session) send(""":let g:UltiSnipsJumpForwardTrigger="?"\n""", options.session)
send(""":let g:UltiSnipsJumpBackwardTrigger="+"\n""", options.session)
send(":inoremap <Tab> <C-R>=UltiSnips_ExpandSnippet()<cr>\n", # Now, source our runtime
options.session) send(":so plugin/UltiSnips.vim\n", options.session)
send(":snoremap <Tab> <Esc>:call UltiSnips_ExpandSnippet()<cr>\n",
options.session)
send(":inoremap + <C-R>=UltiSnips_JumpBackwards()<cr>\n", options.session)
send(":snoremap + <Esc>:call UltiSnips_JumpBackwards()<cr>\n",
options.session)
send(":inoremap ? <C-R>=UltiSnips_JumpForwards()<cr>\n", options.session)
send(":snoremap ? <Esc>:call UltiSnips_JumpForwards()<cr>\n",
options.session)
# Mandatory remapping
send(":snoremap <BS> <Esc>:py UltiSnips_Manager." \
"backspace_while_selected()<cr>\n", options.session)
# Inform all test case which screen session to use # Inform all test case which screen session to use
suite = unittest.TestSuite() suite = unittest.TestSuite()