1
0
mirror of https://github.com/gryf/snipmate.vim.git synced 2025-12-19 04:20:18 +01:00

added support for <shift-tab> to go back a tab stop, and cleaned up the code a bit

This commit is contained in:
Michael Sanders
2009-07-12 19:59:04 -04:00
parent c9d9d3a85d
commit 9e11b093d4
4 changed files with 132 additions and 74 deletions

View File

@@ -7,6 +7,8 @@ let s:did_snips_mappings = 1
ino <silent> <tab> <c-r>=TriggerSnippet()<cr>
snor <silent> <tab> <esc>i<right><c-r>=TriggerSnippet()<cr>
ino <silent> <s-tab> <c-r>=BackwardsSnippet()<cr>
snor <silent> <s-tab> <esc>i<right><c-r>=BackwardsSnippet()<cr>
ino <silent> <c-r><tab> <c-r>=ShowAvailableSnips()<cr>
" The default mappings for these are annoying & sometimes break snipMate.

View File

@@ -5,7 +5,12 @@ fun! Filename(...)
endf
fun s:RemoveSnippet()
unl g:snipPos s:curPos s:snipLen s:endSnip s:endSnipLine s:prevLen s:lastBuf
unl! g:snipPos s:curPos s:snipLen s:endCol s:endLine s:prevLen
\ s:lastBuf s:oldWord
if exists('s:update')
unl s:startCol s:origWordLen s:update
if exists('s:oldVars') | unl s:oldVars s:oldEndCol | endif
endif
aug! snipMateAutocmds
endf
@@ -13,12 +18,15 @@ fun snipMate#expandSnip(snip, col)
let lnum = line('.') | let col = a:col
let snippet = s:ProcessSnippet(a:snip)
" Avoid error if eval evaluates to nothing
if snippet == '' | return '' | endif
" Expand snippet onto current position with the tab stops removed
let snipLines = split(substitute(snippet, '$\d\+\|${\d\+.\{-}}', '', 'g'), "\n", 1)
let line = getline(lnum)
let afterCursor = strpart(line, col - 1)
" Keep text after the cursor
if afterCursor != "\t" && afterCursor != ' '
let line = strpart(line, 0, col - 1)
let snipLines[-1] .= afterCursor
@@ -35,6 +43,8 @@ fun snipMate#expandSnip(snip, col)
" Autoindent snippet according to previous indentation
let indent = matchend(line, '^.\{-}\ze\(\S\|$\)') + 1
call append(lnum, map(snipLines[1:], "'".strpart(line, 0, indent - 1)."'.v:val"))
" Open any folds snippet expands into
if &fen | sil! exe lnum.','.(lnum + len(snipLines) - 1).'foldopen' | endif
let [g:snipPos, s:snipLen] = s:BuildTabStops(snippet, lnum, col - indent, indent)
@@ -44,11 +54,10 @@ fun snipMate#expandSnip(snip, col)
au CursorMovedI * call s:UpdateChangedSnip(0)
au InsertEnter * call s:UpdateChangedSnip(1)
aug END
let s:lastBuf = bufnr(0)
let s:curPos = 0
let s:endSnip = g:snipPos[s:curPos][1]
let s:endSnipLine = g:snipPos[s:curPos][0]
let s:lastBuf = bufnr(0) " Only expand snippet while in current buffer
let s:curPos = 0
let s:endCol = g:snipPos[s:curPos][1]
let s:endLine = g:snipPos[s:curPos][0]
call cursor(g:snipPos[s:curPos][0], g:snipPos[s:curPos][1])
let s:prevLen = [line('$'), col('$')]
@@ -63,6 +72,7 @@ fun snipMate#expandSnip(snip, col)
return ''
endf
" Prepare snippet to be processed by s:BuildTabStops
fun s:ProcessSnippet(snip)
let snippet = a:snip
" Evaluate eval (`...`) expressions.
@@ -99,6 +109,7 @@ fun s:ProcessSnippet(snip)
return snippet
endf
" Counts occurences of haystack in needle
fun s:Count(haystack, needle)
let counter = 0
let index = stridx(a:haystack, a:needle)
@@ -109,8 +120,7 @@ fun s:Count(haystack, needle)
return counter
endf
" This function builds a list of a list of each tab stop in the
" snippet containing:
" Builds a list of a list of each tab stop in the snippet containing:
" 1.) The tab stop's line number.
" 2.) The tab stop's column number
" (by getting the length of the string between the last "\n" and the
@@ -156,45 +166,65 @@ fun s:BuildTabStops(snip, lnum, col, indent)
return [snipPos, i - 1]
endf
fun snipMate#jumpTabStop()
fun snipMate#jumpTabStop(backwards)
let leftPlaceholder = exists('s:origWordLen')
\ && s:origWordLen != g:snipPos[s:curPos][2]
if leftPlaceholder && exists('s:oldEndCol')
let startPlaceholder = s:oldEndCol + 1
endif
if exists('s:update')
call s:UpdatePlaceholderTabStops()
else
call s:UpdateTabStops()
endif
let s:curPos += 1
" Don't reselect placeholder if it has been modified
if leftPlaceholder && g:snipPos[s:curPos][2] != -1
if exists('startPlaceholder')
let g:snipPos[s:curPos][1] = startPlaceholder
else
let g:snipPos[s:curPos][1] = col('.')
let g:snipPos[s:curPos][2] = 0
endif
endif
let s:curPos += a:backwards ? -1 : 1
" Loop over the snippet when going backwards from the beginning
if s:curPos < 0 | let s:curPos = s:snipLen - 1 | endif
if s:curPos == s:snipLen
let sMode = s:endSnip == g:snipPos[s:curPos-1][1]+g:snipPos[s:curPos-1][2]
let sMode = s:endCol == g:snipPos[s:curPos-1][1]+g:snipPos[s:curPos-1][2]
call s:RemoveSnippet()
return sMode ? "\<tab>" : TriggerSnippet()
endif
call cursor(g:snipPos[s:curPos][0], g:snipPos[s:curPos][1])
let s:endSnipLine = g:snipPos[s:curPos][0]
let s:endSnip = g:snipPos[s:curPos][1]
let s:prevLen = [line('$'), col('$')]
let s:endLine = g:snipPos[s:curPos][0]
let s:endCol = g:snipPos[s:curPos][1]
let s:prevLen = [line('$'), col('$')]
return g:snipPos[s:curPos][2] == -1 ? '' : s:SelectWord()
endf
fun s:UpdatePlaceholderTabStops()
let changeLen = s:origWordLen - g:snipPos[s:curPos][2]
unl s:startSnip s:origWordLen s:update
if !exists('s:origPos') | return | endif
unl s:startCol s:origWordLen s:update
if !exists('s:oldVars') | return | endif
" Update tab stops in snippet if text has been added via "$#"
" (e.g., in "${1:foo}bar$1${2}").
if changeLen != 0
let curLine = line('.')
for pos in g:snipPos[s:curPos + 1:]
let changed = pos[0] == curLine && pos[1] > s:origSnipPos
for pos in g:snipPos
if pos == g:snipPos[s:curPos] | continue | endif
let changed = pos[0] == curLine && pos[1] > s:oldEndCol
let changedVars = 0
let endPlaceholder = pos[2] - 1 + pos[1]
" Subtract changeLen from each tab stop that was after any of
" the current tab stop's placeholders.
for [lnum, col] in s:origPos
for [lnum, col] in s:oldVars
if lnum > pos[0] | break | endif
if pos[0] == lnum
if pos[1] > col || (pos[2] == -1 && pos[1] == col)
@@ -211,8 +241,8 @@ fun s:UpdatePlaceholderTabStops()
if pos[2] == -1 | continue | endif
" Do the same to any placeholders in the other tab stops.
for nPos in pos[3]
let changed = nPos[0] == curLine && nPos[1] > s:origSnipPos
for [lnum, col] in s:origPos
let changed = nPos[0] == curLine && nPos[1] > s:oldEndCol
for [lnum, col] in s:oldVars
if lnum > nPos[0] | break | endif
if nPos[0] == lnum && nPos[1] > col
let changed += 1
@@ -222,23 +252,23 @@ fun s:UpdatePlaceholderTabStops()
endfor
endfor
endif
unl s:endSnip s:origPos s:origSnipPos
unl s:endCol s:oldVars s:oldEndCol
endf
fun s:UpdateTabStops()
let changeLine = s:endSnipLine - g:snipPos[s:curPos][0]
let changeCol = s:endSnip - g:snipPos[s:curPos][1]
let changeLine = s:endLine - g:snipPos[s:curPos][0]
let changeCol = s:endCol - g:snipPos[s:curPos][1]
if exists('s:origWordLen')
let changeCol -= s:origWordLen
unl s:origWordLen
endif
let lnum = g:snipPos[s:curPos][0]
let col = g:snipPos[s:curPos][1]
let col = g:snipPos[s:curPos][1]
" Update the line number of all proceeding tab stops if <cr> has
" been inserted.
if changeLine != 0
let changeLine -= 1
for pos in g:snipPos[s:curPos + 1:]
for pos in g:snipPos
if pos[0] >= lnum
if pos[0] == lnum | let pos[1] += changeCol | endif
let pos[0] += changeLine
@@ -254,7 +284,7 @@ fun s:UpdateTabStops()
elseif changeCol != 0
" Update the column of all proceeding tab stops if text has
" been inserted/deleted in the current line.
for pos in g:snipPos[s:curPos + 1:]
for pos in g:snipPos
if pos[1] >= col && pos[0] == lnum
let pos[1] += changeCol
endif
@@ -271,13 +301,13 @@ endf
fun s:SelectWord()
let s:origWordLen = g:snipPos[s:curPos][2]
let s:oldWord = strpart(getline('.'), g:snipPos[s:curPos][1] - 1,
\ s:origWordLen)
let s:oldWord = strpart(getline('.'), g:snipPos[s:curPos][1] - 1,
\ s:origWordLen)
let s:prevLen[1] -= s:origWordLen
if !empty(g:snipPos[s:curPos][3])
let s:update = 1
let s:endSnip = -1
let s:startSnip = g:snipPos[s:curPos][1] - 1
let s:update = 1
let s:endCol = -1
let s:startCol = g:snipPos[s:curPos][1] - 1
endif
if !s:origWordLen | return '' | endif
let l = col('.') != 1 ? 'l' : ''
@@ -300,49 +330,53 @@ fun s:UpdateChangedSnip(entering)
if exists('g:snipPos') && bufnr(0) != s:lastBuf
call s:RemoveSnippet()
elseif exists('s:update') " If modifying a placeholder
if !exists('s:origPos') && s:curPos + 1 < s:snipLen
if !exists('s:oldVars') && s:curPos + 1 < s:snipLen
" Save the old snippet & word length before it's updated
" s:startSnip must be saved too, in case text is added
" s:startCol must be saved too, in case text is added
" before the snippet (e.g. in "foo$1${2}bar${1:foo}").
let s:origSnipPos = s:startSnip
let s:origPos = deepcopy(g:snipPos[s:curPos][3])
let s:oldEndCol = s:startCol
let s:oldVars = deepcopy(g:snipPos[s:curPos][3])
endif
let col = col('.') - 1
if s:endSnip != -1
if s:endCol != -1
let changeLen = col('$') - s:prevLen[1]
let s:endSnip += changeLen
let s:endCol += changeLen
else " When being updated the first time, after leaving select mode
if a:entering | return | endif
let s:endSnip = col - 1
let s:endCol = col - 1
endif
" If the cursor moves outside the snippet, quit it
if line('.') != g:snipPos[s:curPos][0] || col < s:startSnip ||
\ col - 1 > s:endSnip
unl! s:startSnip s:origWordLen s:origPos s:update
if line('.') != g:snipPos[s:curPos][0] || col < s:startCol ||
\ col - 1 > s:endCol
unl! s:startCol s:origWordLen s:oldVars s:update
return s:RemoveSnippet()
endif
call s:UpdateVars()
let s:prevLen[1] = col('$')
elseif exists('g:snipPos')
let col = col('.')
let lnum = line('.')
if !a:entering && g:snipPos[s:curPos][2] != -1
let g:snipPos[s:curPos][2] = -2
endif
let col = col('.')
let lnum = line('.')
let changeLine = line('$') - s:prevLen[0]
if lnum == s:endSnipLine
let s:endSnip += col('$') - s:prevLen[1]
if lnum == s:endLine
let s:endCol += col('$') - s:prevLen[1]
let s:prevLen = [line('$'), col('$')]
endif
if changeLine != 0
let s:endSnipLine += changeLine
let s:endSnip = col
let s:endLine += changeLine
let s:endCol = col
endif
" Delete snippet if cursor moves out of it in insert mode
if (lnum == s:endSnipLine && (col > s:endSnip || col < g:snipPos[s:curPos][1]))
\ || lnum > s:endSnipLine || lnum < g:snipPos[s:curPos][0]
if (lnum == s:endLine && (col > s:endCol || col < g:snipPos[s:curPos][1]))
\ || lnum > s:endLine || lnum < g:snipPos[s:curPos][0]
call s:RemoveSnippet()
endif
endif
@@ -351,25 +385,25 @@ endf
" This updates the variables in a snippet when a placeholder has been edited.
" (e.g., each "$1" in "${1:foo} $1bar $1bar")
fun s:UpdateVars()
let newWordLen = s:endSnip - s:startSnip + 1
let newWord = strpart(getline('.'), s:startSnip, newWordLen)
let newWordLen = s:endCol - s:startCol + 1
let newWord = strpart(getline('.'), s:startCol, newWordLen)
if newWord == s:oldWord || empty(g:snipPos[s:curPos][3])
return
endif
let changeLen = g:snipPos[s:curPos][2] - newWordLen
let curLine = line('.')
let startCol = col('.')
let oldStartSnip = s:startSnip
let changeLen = g:snipPos[s:curPos][2] - newWordLen
let curLine = line('.')
let startCol = col('.')
let oldStartSnip = s:startCol
let updateTabStops = changeLen != 0
let i = 0
let i = 0
for [lnum, col] in g:snipPos[s:curPos][3]
if updateTabStops
let start = s:startSnip
let start = s:startCol
if lnum == curLine && col <= start
let s:startSnip -= changeLen
let s:endSnip -= changeLen
let s:startCol -= changeLen
let s:endCol -= changeLen
endif
for nPos in g:snipPos[s:curPos][3][(i):]
" This list is in ascending order, so quit if we've gone too far.
@@ -389,8 +423,8 @@ fun s:UpdateVars()
call setline(lnum, substitute(getline(lnum), '\%'.col.'c\V'.
\ escape(s:oldWord, '\'), escape(newWord, '\&'), ''))
endfor
if oldStartSnip != s:startSnip
call cursor(0, startCol + s:startSnip - oldStartSnip)
if oldStartSnip != s:startCol
call cursor(0, startCol + s:startCol - oldStartSnip)
endif
let s:oldWord = newWord

View File

@@ -1,7 +1,7 @@
*snipMate.txt* Plugin for using TextMate-style snippets in Vim.
snipMate *snippet* *snippets* *snipMate*
Last Change: May 8, 2009
Last Change: July 12, 2009
|snipMate-description| Description
|snipMate-syntax| Snippet syntax
@@ -33,7 +33,7 @@ you type "for<tab>" in insert mode, it will expand a typical for loop in C: >
To go to the next item in the loop, simply <tab> over to it; if there is
repeated code, such as the "i" variable in this example, you can simply
start typing once it's highlighted and all the matches specified in the
snippet will be updated.
snippet will be updated. To go in reverse, use <shift-tab>.
==============================================================================
SYNTAX *snippet-syntax*
@@ -62,7 +62,7 @@ only be used outside of a snippet declaration. E.g.: >
snippet trigger
expanded text
snippet another_trigger
# this doesn't work!
# this isn't a comment!
expanded text
<
This should hopefully be obvious with the included syntax highlighting.
@@ -221,14 +221,14 @@ spaces. If 'softtabstop' is not set, 'shiftwidth' is used instead.
snipMate does not come with a setting to customize the trigger key, but you
can remap it easily in the two lines it's defined in the 'after' directory
under 'plugin/snipMate.vim'. For instance, to change the trigger key
to shift-tab, just change this: >
to CTRL-J, just change this: >
ino <tab> <c-r>=TriggerSnippet()<cr>
snor <tab> <esc>i<right><c-r>=TriggerSnippet()<cr>
to this: >
ino <s-tab> <c-r>=TriggerSnippet()<cr>
snor <s-tab> <esc>i<right><c-r>=TriggerSnippet()<cr>
ino <c-j> <c-r>=TriggerSnippet()<cr>
snor <c-j> <esc>i<right><c-r>=TriggerSnippet()<cr>
==============================================================================
FEATURES *snipMate-features*
@@ -243,15 +243,15 @@ snipMate.vim has the following features among others:
- Snippets can have multiple matches.
- Snippets can be out of order. For instance, in a do...while loop, the
condition can be added before the code.
- (New) File-based snippets are supported.
- (New) Triggers after non-word delimiters are expanded, e.g. "foo"
- [New] File-based snippets are supported.
- [New] Triggers after non-word delimiters are expanded, e.g. "foo"
in "bar.foo".
- [New] <shift-tab> can now be used to jump tab stops in reverse order.
==============================================================================
DISADVANTAGES *snipMate-disadvantages*
snipMate.vim currently has the following disadvantages to TextMate's snippets:
- There is no way to go back a tab stop, like shift-tab in TextMate.
- There is no $0; the order of tab stops must be explicitly stated.
- Placeholders within placeholders are not possible. E.g.: >
@@ -276,4 +276,6 @@ To contact the author (Michael Sanders), please email:
I greatly appreciate any suggestions or improvements offered for the script.
==============================================================================
vim:tw=78:ts=8:ft=help:norl:

View File

@@ -1,6 +1,6 @@
" File: snipMate.vim
" Author: Michael Sanders
" Version: 0.82
" Version: 0.83
" Description: snipMate.vim implements some of TextMate's snippets features in
" Vim. A snippet is a piece of often-typed text that you can
" insert into your document using a trigger word followed by a "<tab>".
@@ -138,7 +138,7 @@ fun! TriggerSnippet()
call feedkeys("\<tab>") | return ''
endif
if exists('g:snipPos') | return snipMate#jumpTabStop() | endif
if exists('g:snipPos') | return snipMate#jumpTabStop(0) | endif
let word = matchstr(getline('.'), '\S\+\%'.col('.').'c')
for scope in [bufnr('%')] + split(&ft, '\.') + ['_']
@@ -147,7 +147,7 @@ fun! TriggerSnippet()
" the snippet.
if snippet != ''
let col = col('.') - len(trigger)
sil exe 's/\V'.escape(trigger, '/').'\%#//'
sil exe 's/\V'.escape(trigger, '/.').'\%#//'
return snipMate#expandSnip(snippet, col)
endif
endfor
@@ -159,6 +159,23 @@ fun! TriggerSnippet()
return "\<tab>"
endf
fun! BackwardsSnippet()
if exists('g:snipPos') | return snipMate#jumpTabStop(1) | endif
if exists('g:SuperTabMappingForward')
if g:SuperTabMappingBackward == "<s-tab>"
let SuperTabKey = "\<c-p>"
elseif g:SuperTabMappingForward == "<s-tab>"
let SuperTabKey = "\<c-n>"
endif
endif
if exists('SuperTabKey')
call feedkeys(SuperTabKey)
return ''
endif
return "\<s-tab>"
endf
" Check if word under cursor is snippet trigger; if it isn't, try checking if
" the text after non-word characters is (e.g. check for "foo" in "bar.foo")
fun s:GetSnippet(word, scope)
@@ -174,6 +191,9 @@ fun s:GetSnippet(word, scope)
let word = substitute(word, '.\{-}\W', '', '')
endif
endw
if word == '' && a:word != '.' && stridx(a:word, '.') != -1
let [word, snippet] = s:GetSnippet('.', a:scope)
endif
return [word, snippet]
endf