\n\t${1}\n"
exe "Snipp head \n\t\n\t"
diff --git a/after/ftplugin/java_snips.vim b/after/ftplugin/java_snips.vim
index ab03aeb..adda47a 100644
--- a/after/ftplugin/java_snips.vim
+++ b/after/ftplugin/java_snips.vim
@@ -1,7 +1,7 @@
-if !exists('loaded_snips') || exists('b:did_java_snips')
+if !exists('loaded_snips') || exists('s:did_java_snips')
fini
en
-let b:did_java_snips = 1
+let s:did_java_snips = 1
exe "Snipp main public static void main (String [] args)\n{\n\t${1:/* code */}\n}"
exe 'Snipp pu public'
diff --git a/after/ftplugin/javascript_snips.vim b/after/ftplugin/javascript_snips.vim
index b07a59a..d273791 100644
--- a/after/ftplugin/javascript_snips.vim
+++ b/after/ftplugin/javascript_snips.vim
@@ -1,7 +1,7 @@
-if !exists('loaded_snips') || exists('b:did_js_snips')
+if !exists('loaded_snips') || exists('s:did_js_snips')
fini
en
-let b:did_js_snips = 1
+let s:did_js_snips = 1
" Prototype
exe "Snipp proto ${1:class_name}.prototype.${2:method_name} =\nfunction(${3:first_argument}) {\n\t${4:// body...}\n};"
diff --git a/after/ftplugin/objc_snips.vim b/after/ftplugin/objc_snips.vim
index 756b52a..6a04b71 100644
--- a/after/ftplugin/objc_snips.vim
+++ b/after/ftplugin/objc_snips.vim
@@ -1,7 +1,7 @@
-if !exists('loaded_snips') || exists('b:did_js_snips')
+if !exists('loaded_snips') || exists('s:did_objc_snips')
fini
en
-let b:did_js_snips = 1
+let s:did_objc_snips = 1
" #import <...>
exe 'Snipp imp #import <${1:Cocoa/Cocoa.h}>${2}'
@@ -15,26 +15,40 @@ exe 'Snipp log NSLog(@"${1}"${2});${3}'
exe "Snipp objc @interface ${1:`Filename('', 'object')`} : ${2:NSObject}\n{\n}\n@end\n\n@implementation $1\n- (id) init\n{\n\tif (self = [super init])"
\."\n\t{${3}\n\t}\n\treturn self\n}\n@end"
" Class Interface
-exe "Snipp cli @interface ${1:ClassName} : ${2:NSObject}\n{${3}\n}\n${4}\n@end"
+exe "Snipp clh @interface ${1:ClassName} : ${2:NSObject}\n{${3}\n}\n${4}\n@end"
+exe 'Snipp ibo IBOutlet ${1:NSSomeClass} *${2:$1};'
" Category
exe "Snipp cat @interface ${1:NSObject} (${2:Category})\n@end\n\n@implementation $1 ($2)\n${3}\n@end"
" Category Interface
-exe "Snipp cati @interface ${1:NSObject} (${2:Category})\n${3}\n@end"
+exe "Snipp cath @interface ${1:NSObject} (${2:Category})\n${3}\n@end"
" NSArray
exe 'Snipp array NSMutableArray *${1:array} = [NSMutable array];${2}'
" NSDictionary
exe 'Snipp dict NSMutableDictionary *${1:dict} = [NSMutableDictionary dictionary];${2}'
" NSBezierPath
exe 'Snipp bez NSBezierPath *${1:path} = [NSBezierPath bezierPath];${2}'
+" Method
+exe "Snipp m - (${1:id})${2:method}\n{\n\t${3:return self;}\n}"
+" Method declaration
+exe "Snipp md - (${1:id})${2:method};${3}"
" Class Method
-exe "Snipp M + (${1:id}) ${2:method}\n{${3}\n\treturn nil;\n}"
+exe "Snipp M + (${1:id})${2:method}\n{${3}\n\treturn nil;\n}"
" Sub-method (Call super)
-exe "Snipp sm - (${1:id}) ${2:method}:(${3:id})${4:anArgument}\n{\n\t$1 res = [super $2:$4];${5}\n\treturn res;\n}"
+exe "Snipp sm - (${1:id})${2:method}\n{\n\t[super $2];${3}\n\treturn self;\n}"
" Method: Initialize
exe "Snipp I + (void) initialize\n{\n\t[[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWIthObjectsAndKeys:\n\t\t${1}@\"value\", @\"key\",\n\t\tnil]];\n}"
" Accessor Methods For:
" Object
exe "Snipp objacc - (${1:id})${2:thing}\n{\n\treturn $2;\n}\n\n- (void) set$2:($1)\n{\n\t$1 old$2 = $2;\n\t$2 = [aValue retain];\n\t[old$2 release];\n}"
exe "Snipp forarray unsigned int\t${1:object}Count = [${2:array} count];\n\nfor (unsigned int index = 0; index < $1Count; index++)\n{\n\t${3:id}\t$1 = [$2 $1AtIndex:index];\n\t${4}\n}"
+" IBOutlet
+" @property (Objective-C 2.0)
+exe "Snipp prop @property (${1:retain}) ${2:NSSomeClass} *${3:$2};${4}"
+" @synthesize (Objective-C 2.0)
+exe "Snipp syn @synthesize ${1:NSSomeClass};${2}"
" [[ alloc] init]
exe 'Snipp alloc [[${1:foo} alloc] init]${2};${3}'
+" retain
+exe 'Snipp ret [${1:foo} retain];${2}'
+" release
+exe 'Snipp rel [${1:foo} release];${2}'
diff --git a/after/ftplugin/perl_snips.vim b/after/ftplugin/perl_snips.vim
index b2f89d5..1a0e230 100644
--- a/after/ftplugin/perl_snips.vim
+++ b/after/ftplugin/perl_snips.vim
@@ -1,7 +1,7 @@
-if !exists('loaded_snips') || exists('b:did_perl_snips')
+if !exists('loaded_snips') || exists('s:did_perl_snips')
fini
en
-let b:did_perl_snips = 1
+let s:did_perl_snips = 1
" Hash Pointer
exe 'Snipp . =>'
diff --git a/after/ftplugin/php_snips.vim b/after/ftplugin/php_snips.vim
index 0299163..7a87720 100644
--- a/after/ftplugin/php_snips.vim
+++ b/after/ftplugin/php_snips.vim
@@ -1,7 +1,7 @@
-if !exists('loaded_snips') || exists('b:did_php_snips')
+if !exists('loaded_snips') || exists('s:did_php_snips')
fini
en
-let b:did_php_snips = 1
+let s:did_php_snips = 1
exe "Snipp php "
exe 'Snipp ec echo "${1:string}"${2};'
diff --git a/after/ftplugin/python_snips.vim b/after/ftplugin/python_snips.vim
index 0c13a2b..e663039 100644
--- a/after/ftplugin/python_snips.vim
+++ b/after/ftplugin/python_snips.vim
@@ -1,7 +1,7 @@
-if !exists('loaded_snips') || exists('b:did_python_snips')
+if !exists('loaded_snips') || exists('s:did_python_snips')
fini
en
-let b:did_python_snips = 1
+let s:did_python_snips = 1
" New Class
exe "Snipp cl class ${1:ClassName}(${2:object}):\n\t\"\"\"${3:docstring for $1}\"\"\"\n\tdef __init__(self, ${4:arg}):\n\t\t${5:super($1, self).__init__()}\n\t\tself.$4 = $4\n\t\t${6}"
@@ -10,7 +10,7 @@ exe "Snipp def def ${1:fname}(${2:`indent('.') ? 'self' : ''`}):\n\t\"\"\"${3:do
" New Method
exe "Snipp defs def ${1:mname}(self, ${2:arg})):\n\t${3:pass}"
" New Property
-exe "Snipp property def def ${1:foo}():\n\tdoc = \"${2:The $1 property.}\"\n\tdef fget(self):\n\t\t\t${3:return self._$1}\n\tdef fset(self, value):\n\t\t"
+exe "Snipp property def ${1:foo}():\n\tdoc = \"${2:The $1 property.}\"\n\tdef fget(self):\n\t\t\t${3:return self._$1}\n\tdef fset(self, value):\n\t\t"
\."${4:self._$1 = value}\n\tdef fdel(self):\n\t\t\t${5:del self._$1}\n\treturn locals()\n$1 = property(**$1())${6}"
" Self
exe 'Snipp . self.'
@@ -19,9 +19,9 @@ exe "Snipp try try:\n\t${1:pass}\nexcept ${2:Exception}, ${3:e}:\n\t${4:raise $3
" Try/Except/Else
exe "Snipp trye try:\n\t${1:pass}\nexcept ${2:Exception}, ${3:e}:\n\t${4:raise $3}\nelse:\n\t${5:pass}"
" Try/Except/Finally
-exe "Snipp tryf try:\n\t${1:pass}\nexcept ${2:Exception}, ${3:e}:\n\t${4:raise $3}\nfinally:\n${5:pass}"
+exe "Snipp tryf try:\n\t${1:pass}\nexcept ${2:Exception}, ${3:e}:\n\t${4:raise $3}\nfinally:\n\t${5:pass}"
" Try/Except/Else/Finally
-exe "Snipp tryef try:\n\t${1:pass}\nexcept ${2:Exception}, ${3:e}:\n\t${4:raise $3}\nelse:\n\t${5:pass}\nfinally:\n${6:pass}"
+exe "Snipp tryef try:\n\t${1:pass}\nexcept ${2:Exception}, ${3:e}:\n\t${4:raise $3}\nelse:\n\t${5:pass}\nfinally:\n\t${6:pass}"
" if __name__ == '__main__':
exe "Snipp ifmain if __name__ == '__main__':\n\t${1:main()}"
" __magic__
diff --git a/after/ftplugin/ruby_snips.vim b/after/ftplugin/ruby_snips.vim
index d5439ef..de16c6e 100644
--- a/after/ftplugin/ruby_snips.vim
+++ b/after/ftplugin/ruby_snips.vim
@@ -1,7 +1,7 @@
-if !exists('loaded_snips') || exists('b:did_ruby_snips')
+if !exists('loaded_snips') || exists('s:did_ruby_snips')
fini
en
-let b:did_ruby_snips = 1
+let s:did_ruby_snips = 1
" New Block
exe "Snipp =b =begin rdoc\n\t${1}\n=end"
diff --git a/after/ftplugin/sh_snips.vim b/after/ftplugin/sh_snips.vim
index 69e5e37..a7794da 100644
--- a/after/ftplugin/sh_snips.vim
+++ b/after/ftplugin/sh_snips.vim
@@ -1,7 +1,7 @@
-if !exists('loaded_snips') || exists('b:did_sh_snips')
+if !exists('loaded_snips') || exists('s:did_sh_snips')
fini
en
-let b:did_sh_snips = 1
+let s:did_sh_snips = 1
exe "Snipp if if [[ ${1:condition} ]]; then\n\t${2:#statements}\nfi"
exe "Snipp elif elif [[ ${1:condition} ]]; then\n\t${2:#statements}"
diff --git a/after/ftplugin/tex_snips.vim b/after/ftplugin/tex_snips.vim
index 7b96ef6..b912802 100644
--- a/after/ftplugin/tex_snips.vim
+++ b/after/ftplugin/tex_snips.vim
@@ -1,7 +1,7 @@
-if !exists('loaded_snips') || exists('b:did_tex_snips')
+if !exists('loaded_snips') || exists('s:did_tex_snips')
fini
en
-let b:did_tex_snips = 1
+let s:did_tex_snips = 1
" \begin{}...\end{}
exe "Snipp begin \\begin{${1:env}}\n\t${2}\n\\end{$1}"
@@ -41,9 +41,9 @@ exe "Snipp subs \\subsubsection{${1:subsubsection name}} % (fold)\n\\label{ssub:
exe "Snipp par \\paragraph{${1:paragraph name}} % (fold)\n\\label{par:${2:$1}}\n${3}\n% paragraph $2 (end)"
" Sub Paragraph
exe "Snipp subp \\subparagraph{${1:subparagraph name}} % (fold)\n\\label{subp:${2:$1}}\n${3}\n% subparagraph $2 (end)"
-exe 'Snipp itd \\item[${1:description}] ${2:item}'
-exe 'Snipp figure ${1:Figure}~\\ref{${2:fig:}}${3}'
-exe 'Snipp table ${1:Table}~\\ref{${2:tab:}}${3}'
-exe 'Snipp listing ${1:Listing}~\\ref{${2:list}}${3}'
-exe 'Snipp section ${1:Section}~\\ref{${2:sec:}}${3}'
-exe 'Snipp page ${1:page}~\\pageref{${2}}${3}'
+exe 'Snipp itd \item[${1:description}] ${2:item}'
+exe 'Snipp figure ${1:Figure}~\ref{${2:fig:}}${3}'
+exe 'Snipp table ${1:Table}~\ref{${2:tab:}}${3}'
+exe 'Snipp listing ${1:Listing}~\ref{${2:list}}${3}'
+exe 'Snipp section ${1:Section}~\ref{${2:sec:}}${3}'
+exe 'Snipp page ${1:page}~\pageref{${2}}${3}'
diff --git a/after/plugin/snipMate.vim b/after/plugin/snipMate.vim
index dcb00dd..96027bc 100644
--- a/after/plugin/snipMate.vim
+++ b/after/plugin/snipMate.vim
@@ -5,9 +5,19 @@ if exists('s:did_snips_mappings') || &cp || version < 700
en
let s:did_snips_mappings = 1
-ino =ExpandSnippet()
-snor i=ExpandSnippet()
+ino =TriggerSnippet()
+snor i=TriggerSnippet()
snor b
snor ' b'
snor a
snor bi
+
+" By default load snippets in ~/.vim/snippets/
+if isdirectory($HOME.'/.vim/snippets')
+ if isdirectory($HOME.'/.vim/snippets/_')
+ cal ExtractSnips($HOME.'/.vim/snippets/_', '_')
+ en
+ au FileType * if !exists('s:did_'.&ft) &&
+ \ isdirectory($HOME.'/.vim/snippets/'.&ft)
+ \| cal ExtractSnips($HOME.'/.vim/snippets/'.&ft, &ft) | en
+en
diff --git a/doc/snipMate.txt b/doc/snipMate.txt
index 802fa51..a3e63ff 100644
--- a/doc/snipMate.txt
+++ b/doc/snipMate.txt
@@ -1,7 +1,7 @@
*snipMate.txt* Plugin for using TextMate-style snippets in Vim.
Snippets *snippet* *snippets* *snipMate*
-Last Change: February 13, 2009
+Last Change: February 21, 2009
|snipMate-description| Description
|snipMate-usage| Usage
@@ -39,7 +39,34 @@ be updated.
==============================================================================
USAGE *snipMate-usage*
-Snippets should be installed in the 'after' directory, usually located in
+There are two ways to make snippets: file-based and command-based. File-based
+snippets are simply plain text files named after the trigger of the snippet
+and placed in the directory of the filetype (/.snippet);
+command-based snippets are snippets defined using the |Snipp| and |GlobalSnip|
+commands. File-based snippets have the advantage of being easier to read, but
+do not support some special characters in snippet triggers, while
+command-based snippets are obviously convenient for short snippets but can
+quickly get unreadable.
+
+ *file-snippets* *'snippets'*
+File-based snippets by default are looked for in the 'snippets' directory
+inside your home '.vim' directory, typically located in
+'~/.vim/snippets/'. To change that location or add another one,
+edit '~/.vim/after/plugin/snipMate.vim' and use the |ExtractSnips()|function.
+
+ExtractSnips({directory}, {filetype}) *ExtractSnips()*
+
+ExtractSnips() extracts *.snippet files from the specified directory and
+defines them as snippets for the given filetype; to define a global snippet,
+use '_' for the {filetype} argument.
+
+ *ResetSnips()*
+The ResetSnips() function removes all snippets from memory. This is useful to
+put at the top of a snippet setup file if you would like to :source it
+multiple times.
+
+ *command-snippets*
+Command-based should be installed in the 'after' directory, usually located in
'~/.vim/after'. Filetype specific snippets should be installed in
'after/ftplugin', such as 'after/ftplugin/_snips.vim'.
See |ftplugins|. Global snippets should be installed in 'after/plugin'.
@@ -87,6 +114,11 @@ to add: >
to the top of your snippets files.
+ *snipMate-expandtab* *snipMate-indenting*
+If you would like your snippets to use spaces instead of tabs, just enable
+'expandtab' and set 'softtabstop' to your preferred amount of spaces. If
+'softtabstop' is not set, 'shiftwidth' is used instead.
+
==============================================================================
SYNTAX *snipMate-syntax* *snipMate-${#}*
@@ -184,14 +216,13 @@ to this: >
FEATURES *snipMate-features*
snipMate.vim has the following features among others:
- - The syntax of snippets is very similar to TextMate's allowing
+ - The syntax of snippets is very similar to TextMate's, allowing
easy conversion.
- The position of the snippet is kept transparently (i.e. it does not use
- regexes from text inserted in the buffer to track your position), which
- allows you to escape out of an incomplete snippet, something particularly
- useful in Vim.
- - Variables in snippets are updated as-you-type
- - Snippets can have multiple matches
+ markers/placeholders written to the buffer), which allows you to escape
+ out of an incomplete snippet, something particularly useful in Vim.
+ - Variables in snippets are updated as-you-type.
+ - 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.
diff --git a/plugin/snipMate.vim b/plugin/snipMate.vim
index ee9574b..b3116ce 100644
--- a/plugin/snipMate.vim
+++ b/plugin/snipMate.vim
@@ -1,150 +1,251 @@
" File: snipMate.vim
" Author: Michael Sanders
-" Version: 0.61803399
+" Version: 0.7
" 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 "".
"
-" For more help see snipMate.txt; you can do this by doing:
+" For more help see snipMate.txt; you can do this by using:
" :helptags ~/.vim/doc
" :h snipMate.txt
-" Last Modified: February 16, 2009.
+" Last Modified: February 25, 2009.
-if exists('g:loaded_snips') || &cp || version < 700
- fini
-en
-let g:loaded_snips = 1
+if exists('loaded_snips') || &cp || version < 700
+ finish
+endif
+let loaded_snips = 1
+if !exists('snips_author') | let snips_author = 'Me' | endif
-" snippets for making snippets :)
-au FileType vim let b:Snippet_snip = 'exe "Snipp ${1:trigger}"${2}'
- \| let b:Snippet_snipp = "exe 'Snipp ${1:trigger}'${2}"
- \| let b:Snippet_gsnip = 'exe "GlobalSnip ${1:trigger}"${2}'
- \| let b:Snippet_gsnipp = "exe 'GlobalSnip ${1:trigger}'${2}"
+com! -nargs=+ -bang Snipp call s:MakeSnippet(, &ft, 0)
+com! -nargs=+ -bang GlobalSnip call s:MakeSnippet(, '_', 0)
-com! -nargs=+ -bang Snipp cal s:MakeSnippet(, 'b', 0)
-com! -nargs=+ -bang GlobalSnip cal s:MakeSnippet(, 'g', 0)
-
-if !exists('g:snips_author') | let g:snips_author = 'Me' | en
+let s:snippets = {} | let s:multi_snips = {}
fun! Filename(...)
let filename = expand('%:t:r')
- if filename == '' | retu a:0 == 2 ? a:2 : '' | en
- retu !a:0 || a:1 == '' ? filename : substitute(a:1, '$1', filename, 'g')
+ if filename == '' | return a:0 == 2 ? a:2 : '' | endif
+ return !a:0 || a:1 == '' ? filename : substitute(a:1, '$1', filename, 'g')
endf
" escapes special characters in snippet triggers
fun s:Hash(text)
- retu substitute(a:text, '\W', '\="_".char2nr(submatch(0))."_"', 'g')
+ return substitute(a:text, '\W', '\="_".char2nr(submatch(0))."_"', 'g')
endf
-fun s:MakeSnippet(text, scope, bang)
+fun s:MakeSnippet(text, ft, multisnip)
let space = stridx(a:text, ' ')
let trigger = s:Hash(strpart(a:text, 0, space))
- if a:bang
+ if a:multisnip
let space += 2
- let quote = stridx(a:text, '"', space)
- let name = strpart(a:text, space, quote-space)
- let space = stridx(a:text, ' ', quote)
- let trigger = a:scope.':Snippets_'.trigger
- el
- let trigger = a:scope.':Snippet_'.trigger
- en
+ let quote = stridx(a:text, '"', space)
+ let name = strpart(a:text, space, quote-space)
+ let space = stridx(a:text, ' ', quote)
+ let var = 's:multi_snips'
+ else " evaluating a regular snippet
+ let var = 's:snippets'
+ endif
+ if !has_key({var}, a:ft) | let {var}[a:ft] = {} | endif
let end = strpart(a:text, space+1)
- if !exists(trigger) || a:bang
- if end != '' && space != -1 && (!a:bang || name != '')
- if a:bang
- if !exists(trigger) | let {trigger} = [] | en
- let {trigger} += [[name, end]]
- el
- let {trigger} = end
- en
- el
- echoh ErrorMsg
- echom 'Error in snipMate.vim: Snippet '.a:text.' is undefined.'
- echoh None
- en
- el
- echoh WarningMsg
+
+ if end == '' || space == '' || (a:multisnip && name == '')
+ echom 'Error in snipMate.vim: Snippet '.a:text.' is undefined.'
+ elseif !has_key({var}[a:ft], trigger)
+ let {var}[a:ft][trigger] = a:multisnip ? [[name, end]] : end
+ elseif a:multisnip | let {var}[a:ft][trigger] += [[name, end]]
+ else
echom 'Warning in snipMate.vim: Snippet '.strpart(a:text, 0, stridx(a:text, ' '))
\ .' is already defined. See :h multi_snip for help on snippets'
\ .' with multiple matches.'
- echoh None
- en
+ endif
endf
-" This updates the snippet as you type when text needs to be inserted
-" into multiple places (e.g. in "${1:default text}foo$1bar$1",
-" "default text" would be highlighted, and if the user types something,
-" UpdateChangedSnip() would be called so that the text after "foo" & "bar"
-" are updated accordingly)
-"
-" It also automatically quits the snippet if the cursor is moved out of it
-" while in insert mode.
-au CursorMovedI * cal s:UpdateChangedSnip(0)
-au InsertEnter * cal s:UpdateChangedSnip(1)
-fun s:UpdateChangedSnip(entering)
- if exists('s:update')
- if !exists('s:origPos') && 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
- " before the snippet (e.g. in "foo$1${2}bar${1:foo}")
- let s:origSnipPos = s:startSnip
- let s:origPos = deepcopy(s:snipPos[s:curPos][3])
- en
- let col = col('.')-1
+fun! ExtractSnips(dir, ft)
+ let s:slash = has('win16') || has('win32') || has('win64') ? '\\' : '/'
+ for path in split(globpath(a:dir, '*'), '\n')
+ if isdirectory(path)
+ for snipFile in split(globpath(path, '*.snippet'), '\n')
+ call s:ProcessFile(snipFile, a:ft, strpart(path, strridx(path, s:slash)+1))
+ endfor
+ continue
+ endif
+ call s:ProcessFile(path, a:ft)
+ endfor
+ unl s:slash
+ let s:did_{a:ft} = 1
+endf
- if s:endSnip != -1
- let changeLen = col('$') - s:prevLen[1]
- let s:endSnip += changeLen
- el " when being updated the first time, after leaving select mode
- if a:entering | retu | en
- let s:endSnip = col-1
- en
+" Processes a snippet file; optionally add the name of the parent directory
+" for a snippet with multiple matches.
+fun s:ProcessFile(file, ft, ...)
+ let keyword = matchstr(a:file, '.*'.s:slash.'\zs.*\ze\.snippet')
+ if keyword == '' | return | endif
+ try
+ let text = join(readfile(a:file), '\n')
+ catch /E484/
+ echom "Error in snipMate.vim: couldn't read file: ".a:file
+ endtry
+ return a:0 ? s:MakeSnippet(a:1.' "'.keyword.'" '.text, a:ft, 1)
+ \ : s:MakeSnippet(keyword.' '.text, a:ft, 0)
+endf
- " if the cursor moves outside the snippet, quit it
- if line('.') != s:snipPos[s:curPos][0] || col < s:startSnip ||
- \ col-1 > s:endSnip
- unl! s:startSnip s:origWordLen s:origPos s:update
- cal s:RemoveSnippet()
- retu
- en
-
- cal s:UpdateSnip()
- let s:prevLen[1] = col('$')
- elsei exists('s:snipPos')
- let col = col('.')
- let lnum = line('.')
- let changeLine = line('$') - s:prevLen[0]
-
- if lnum == s:endSnipLine
- let s:endSnip += col('$') - s:prevLen[1]
- let s:prevLen = [line('$'), col('$')]
- en
- if changeLine != 0 "&& !a:entering
- let s:endSnipLine += changeLine
- let s:endSnip = col
- en
-
- " delete snippet if cursor moves out of it in insert mode
- if (lnum == s:endSnipLine && (col > s:endSnip || col < s:snipPos[s:curPos][1]))
- \ || lnum > s:endSnipLine || lnum < s:snipPos[s:curPos][0]
- cal s:RemoveSnippet()
- en
- en
+fun! ResetSnippets()
+ let s:snippets = {} | let s:multi_snips = {}
endf
fun s:RemoveSnippet()
unl s:snipPos s:curPos s:snipLen s:endSnip s:endSnipLine s:prevLen
endf
-fun s:ChooseSnippet(snippet)
- let snippet = [] | let i = 1
- for snip in {a:snippet}
- let snippet += [i.'. '.snip[0]] | let i += 1
- endfo
- if i == 2 | retu {a:snippet}[0][1] | en
+fun s:ChooseSnippet(ft, trigger)
+ let snippet = []
+ let i = 1
+ for snip in s:multi_snips[a:ft][a:trigger]
+ let snippet += [i.'. '.snip[0]]
+ let i += 1
+ endfor
+ if i == 2 | return s:multi_snips[a:ft][a:trigger][0][1] | endif
let num = inputlist(snippet)-1
- retu num < i-1 ? {a:snippet}[num][1] : ''
+ return num < i-1 ? s:multi_snips[a:ft][a:trigger][num][1] : ''
+endf
+
+fun! TriggerSnippet()
+ if pumvisible() " update snippet if completion is used, or deal with supertab
+ if exists('s:sid') | return "\" | endif
+ call feedkeys("\a", 'n') | call s:UpdateChangedSnip(0)
+ endif
+
+ if !exists('s:snipPos') " don't expand snippets within snippets
+ if !exists('s:sid') && exists('g:SuperTabMappingForward')
+ \ && g:SuperTabMappingForward == ""
+ call s:GetSuperTabSID()
+ endif
+ let word = s:GetSnippet()
+
+ if exists('s:snippet')
+ if s:snippet == ''
+ return unl s:snippet " if user cancelled multi snippet, quit
+ endif
+ let col = col('.')-len(word)
+ " if word is a trigger for a snippet, delete the trigger & expand
+ " the snippet
+ exe 'sil s/'.word.'\%#//'
+ return s:ExpandSnippet(col)
+ endif
+ return exists('s:sid') ? {s:sid}_SuperTab('n') : "\"
+ endif
+ return s:JumpTabStop()
+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()
+ let origWord = matchstr(getline('.'), '\S\+\%'.col('.').'c')
+ wh !exists('s:snippet')
+ let word = s:Hash(origWord)
+ if exists('s:snippets["'.&ft.'"]["'.word.'"]')
+ let s:snippet = s:snippets[&ft][word]
+ elseif exists('s:snippets["_"]["'.word.'"]')
+ let s:snippet = s:snippets['_'][word]
+ elseif exists('s:multi_snips["'.&ft.'"]["'.word.'"]')
+ let s:snippet = s:ChooseSnippet(&ft, word)
+ elseif exists('s:multi_snips["_"]["'.word.'"]')
+ let s:snippet = s:ChooseSnippet('_', word)
+ en
+ if match(origWord, '\W') == -1 | break | en
+ let origWord = substitute(origWord, '.\{-}\W', '', '')
+ endw
+ return origWord
+endf
+
+fun s:GetSuperTabSID()
+ let old = @a
+ redir @a | exe 'sil fun /SuperTab$' | redir END
+ let s:sid = matchstr(@a, '\d\+\ze_SuperTab(command)')
+ let @a = old
+endf
+
+fun s:ExpandSnippet(col)
+ let lnum = line('.') | let col = a:col
+ let afterCursor = strpart(getline('.'), col-1)
+ if afterCursor != "\t" && afterCursor != ' '
+ sil exe 's/\%'.col.'c.*//'
+ else | let afterCursor = '' | endif
+
+ call s:ProcessSnippet()
+ if s:snippet == ''
+ return unl s:snippet " avoid an error if the snippet is now empty
+ endif
+
+ let snip = split(substitute(s:snippet, '$\d\|${\d.\{-}}', '', 'g'), "\n", 1)
+ if afterCursor != '' | let snip[-1] .= afterCursor | endif
+ let line = getline(lnum)
+ call setline(lnum, line.snip[0])
+
+ " for some reason the cursor needs to move one right after this
+ if line != '' && col == 1 && afterCursor == '' && &ve !~ 'all\|onemore'
+ let col += 1
+ endif
+ " autoindent snippet according to previous indentation
+ let indent = matchend(line, '^.\{-}\ze\(\S\|$\)')+1
+ if !indent
+ call append(lnum, snip[1:])
+ else
+ call append(lnum, map(snip[1:], "'".strpart(line, 0, indent-1)."'.v:val"))
+ endif
+
+ let snipLen = s:BuildTabStops(lnum, col-indent, indent)
+ unl s:snippet
+
+ if snipLen
+ let s:curPos = 0
+ let s:snipLen = snipLen
+ let s:endSnip = s:snipPos[0][1]
+ let s:endSnipLine = s:snipPos[s:curPos][0]
+
+ call cursor(s:snipPos[0][0], s:snipPos[0][1])
+ let s:prevLen = [line('$'), col('$')]
+ if s:snipPos[0][2] != -1 | return s:SelectWord() | endif
+ else
+ " place cursor at end of snippet if no tab stop is given
+ unl s:snipPos | let newlines = len(snip)-1
+ call cursor(lnum + newlines, tab + len(snip[-1]) - len(afterCursor)
+ \ + (newlines ? 0: col))
+ endif
+ return ''
+endf
+
+fun s:ProcessSnippet()
+ " evaluate eval (`...`) expressions
+ " Using a loop here instead of a regex fixes a bug with nested "\="
+ if stridx(s:snippet, '`') != -1
+ wh match(s:snippet, '`.\{-}`') != -1
+ let s:snippet = substitute(s:snippet, '`.\{-}`',
+ \ substitute(eval(matchstr(s:snippet, '`\zs.\{-}\ze`')),
+ \ "\n\\%$", '', ''), '')
+ endw
+ let s:snippet = substitute(s:snippet, "\r", "\n", 'g')
+ endif
+
+ " place all text after a colon in a tab stop after the tab stop
+ " (e.g. "${#:foo}" becomes "${:foo}foo")
+ " this helps tell the position of the tab stops later.
+ let s:snippet = substitute(s:snippet, '${\d:\(.\{-}\)}', '&\1', 'g')
+
+ " update the s:snippet so that all the $# become
+ " the text after the colon in their associated ${#}
+ " (e.g. "${1:foo}" turns all "$1"'s into "foo")
+ let i = 1
+ wh stridx(s:snippet, '${'.i) != -1
+ let s = matchstr(s:snippet, '${'.i.':\zs.\{-}\ze}')
+ if s != ''
+ let s:snippet = substitute(s:snippet, '$'.i, '&'.s, 'g')
+ endif
+ let i += 1
+ endw
+ if &et " expand tabs to spaces if 'expandtab' is set
+ let s:snippet = substitute(s:snippet, '\t',
+ \ repeat(' ', &sts ? &sts : &sw), 'g')
+ endif
endf
fun s:Count(haystack, needle)
@@ -154,268 +255,184 @@ fun s:Count(haystack, needle)
let index = stridx(a:haystack, a:needle, index+1)
let counter += 1
endw
- retu counter
+ return counter
endf
-fun! ExpandSnippet()
- if !exists('s:snipPos') " don't expand snippets within snippets
- " get word under cursor
- let word = matchstr(getline('.'), '\(^\|\s\)\zs\S\+\%'.col('.').'c\ze\($\|\s\)')
- let len = len(word) | let word = s:Hash(word)
-
- if exists('b:Snippet_'.word)
- let snippet = b:Snippet_{word}
- elsei exists('g:Snippet_'.word)
- let snippet = g:Snippet_{word}
- elsei exists('b:Snippets_'.word)
- let snippet = s:ChooseSnippet('b:Snippets_'.word)
- elsei exists('g:Snippet_'.word)
- let snippet = s:ChooseSnippet('g:Snippets_'.word)
- en
-
- if exists('snippet')
- if snippet == '' | retu '' | en " if user cancelled multi snippet, quit
- let b:word = word
- " if word is a trigger for a snippet, delete the trigger & expand
- " the snippet (BdE doesn't work for just a single character)
- if len == 1 | norm! h"_x
- el | norm! B"_dE
- en
- let lnum = line('.')
- let col = col('.')
-
- let afterCursor = strpart(getline('.'), col-1)
- if afterCursor != "\t" && afterCursor != ' ' | sil s/\%#.*//
- el | let afterCursor = '' | en
-
- " evaluate eval expressions
- if stridx(snippet, '`') != -1
- let snippet = substitute(substitute(snippet, '`\(.\{-}\)`',
- \ '\=substitute(eval(submatch(1)), "\n\\%$", "", "")', 'g'),
- \ "\r", "\n", 'g')
- if snippet == '' | retu '' | en " avoid an error if the snippet is now empty
- en
-
- " place all text after a colon in a tab stop after the tab stop
- " (e.g. "${#:foo}" becomes "${:foo}foo")
- " this helps tell the position of the tab stops later.
- let snippet = substitute(snippet, '${\d:\(.\{-}\)}', '&\1', 'g')
-
- " update the snippet so that all the $# become
- " the text after the colon in their associated ${#}
- " (e.g. "${1:foo}" turns all "$1"'s into "foo")
- let i = 1
- wh stridx(snippet, '${'.i) != -1
- let s = matchstr(snippet, '${'.i.':\zs.\{-}\ze}')
- if s != ''
- let snippet = substitute(snippet, '$'.i, '&'.s, 'g')
- en
- let i += 1
- endw
- " expand tabs to spaces if 'expandtab' is set
- if &et | let snippet = substitute(snippet, '\t', repeat(' ', &sts), 'g') | en
-
- let snip = split(substitute(snippet, '$\d\|${\d.\{-}}', '', 'g'), "\n", 1)
- if afterCursor != '' | let snip[-1] .= afterCursor | en
- let line = getline(lnum)
- cal setline(lnum, line.snip[0])
-
- " for some reason the cursor needs to move one right after this
- if line != '' && afterCursor == '' && &ve != 'all' && &ve != 'onemore'
- let col += 1
- en
- " autoindent snippet according to previous indentation
- let tab = matchstr(line, '^.\{-}\ze\(\S\|$\)')
- cal append(lnum, tab != '' ? map(snip[1:], "'".tab."'.v:val") : snip[1:])
- let tab = len(tab)+1 | let col -= tab
-
- " Sorry, this next section is a bit convoluted...
- " This loop builds a list of a list of each tab stop in the snippet
- " containing:
- " 1.) The number of the current line plus the number of "\n"s (line
- " breaks) before the tab stop
- " 2.) The current column plus the position of the next "${#}" on
- " the line by getting the length of the string between the last "\n"
- " and the "${#}" tab stop,
- " 3.) The length of the text after the colon for the current tab stop
- " (e.g. "${#:foo}" would return 3). If there is no text, -1 is returned.
- " 4.) If the "${#:}" construct is given, the fourth part of the list
- " is another list containing all the matches of "$#", to be replaced
- " with the variable. This list is composed the same way as the parent:
- " the first part is the number of "\n"s before the tab stop, and
- " second is the position (column) of the "$#" tab stop on the line.
- " If there are none of these tab stop, an empty list ([]) is returned
- let s:snipPos = [] | let i = 1
- " temporarily delete placeholders
- let cut_snip = substitute(snippet, '$\d', '', 'g')
- wh stridx(snippet, '${'.i) != -1
- let s:snipPos += [[lnum+s:Count(matchstr(cut_snip, '^.*\ze${'.i), "\n"),
- \ tab+len(matchstr(substitute(cut_snip, '${'.i.'\@!\d.\{-}}', '', 'g'),
+" Sorry, this next section is a bit convoluted...
+" This function builds a list of a list of each tab stop in the snippet
+" containing:
+" 1.) The number of the current line plus the number of "\n"s (line
+" breaks) before the tab stop
+" 2.) The current column plus the position of the next "${#}" on
+" the line by getting the length of the string between the last "\n"
+" and the "${#}" tab stop,
+" 3.) The length of the text after the colon for the current tab stop
+" (e.g. "${#:foo}" would returnrn 3). If there is no text, -1 is returnrned.
+" 4.) If the "${#:}" construct is given, the fourth part of the list
+" is another list containing all the matches of "$#", to be replaced
+" with the variable. This list is composed the same way as the parent:
+" the first part is the number of "\n"s before the tab stop, and
+" second is the position (column) of the "$#" tab stop on the line.
+" If there are none of these tab stop, an empty list ([]) is returnrned
+fun s:BuildTabStops(lnum, col, indent)
+ let s:snipPos = []
+ let i = 1
+ " temporarily delete placeholders
+ let cut_snip = substitute(s:snippet, '$\d', '', 'g')
+ wh stridx(s:snippet, '${'.i) != -1
+ let s:snipPos += [[a:lnum+s:Count(matchstr(cut_snip, '^.*\ze${'.i), "\n"),
+ \ a:indent+len(matchstr(substitute(cut_snip, '${'.i.'\@!\d.\{-}}', '', 'g'),
\ "^.*\\(\n\\|^\\)\\zs.*\\ze${".i.'.\{-}}')), -1]]
- if s:snipPos[i-1][0] == lnum | let s:snipPos[i-1][1] += col | en
+ if s:snipPos[i-1][0] == a:lnum
+ let s:snipPos[i-1][1] += a:col
+ endif
- " get all $# matches in another list, if ${#:name} is given
- if stridx(cut_snip, '${'.i.':') != -1
- let j = i-1
- let s:snipPos[j][2] = len(matchstr(cut_snip, '${'.i.':\zs.\{-}\ze}'))
- let s:snipPos[j] += [[]]
- " temporarily delete all other tab stops/placeholders
- let tempstr = substitute(snippet, '$'.i.'\@!\d\|${\d.\{-}}', '', 'g')
- wh stridx(tempstr, '$'.i) != -1
- let beforeMark = matchstr(tempstr, '^.\{-}\ze$'.i)
- let linecount = lnum+s:Count(beforeMark, "\n")
- let s:snipPos[j][3] += [[linecount,
- \ tab+(linecount > lnum ?
- \ len(matchstr(beforeMark, "^.*\n\\zs.*"))
- \ : col+len(beforeMark))]]
- let tempstr = substitute(tempstr, '$'.i, '', '')
- endw
- en
- let i += 1
+ " get all $# matches in another list, if ${#:name} is given
+ if stridx(cut_snip, '${'.i.':') != -1
+ let j = i-1
+ let s:snipPos[j][2] = len(matchstr(cut_snip, '${'.i.':\zs.\{-}\ze}'))
+ let s:snipPos[j] += [[]]
+ " temporarily delete all other tab stops/placeholders
+ let tempstr = substitute(s:snippet, '$'.i.'\@!\d\|${\d.\{-}}', '', 'g')
+ wh stridx(tempstr, '$'.i) != -1
+ let beforeMark = matchstr(tempstr, '^.\{-}\ze$'.i)
+ let linecount = a:lnum+s:Count(beforeMark, "\n")
+ let s:snipPos[j][3] += [[linecount,
+ \ a:indent+(linecount > a:lnum ?
+ \ len(matchstr(beforeMark, "^.*\n\\zs.*"))
+ \ : a:col+len(beforeMark))]]
+ let tempstr = substitute(tempstr, '$'.i, '', '')
endw
+ endif
+ let i += 1
+ endw
+ return i-1
+endf
- if i > 1 " if snippet is not empty
- let s:curPos = 0
- let s:snipLen = i-1
- let s:endSnip = s:snipPos[0][1]
- let s:endSnipLine = s:snipPos[s:curPos][0]
-
- cal cursor(s:snipPos[0][0], s:snipPos[0][1])
- let s:prevLen = [line('$'), col('$')]
- if s:snipPos[0][2] != -1 | retu s:SelectWord() | en
- el
- unl s:snipPos
- " place cursor at end of snippet if no tab stop is given
- let len = len(snip)-1
- cal cursor(lnum+len, tab+len(snip[-1])+(len ? 0 : col))
- en
- retu ''
- en
- if !exists('s:sid') && exists('g:SuperTabMappingForward')
- \ && g:SuperTabMappingForward == ""
- cal s:GetSuperTabSID()
- en
- retu exists('s:sid') ? {s:sid}_SuperTab('n') : "\"
- en
-
+fun s:JumpTabStop()
if exists('s:update')
- " update tab stops in snippet if text has been added via "$#",
- " e.g. in "${1:foo}bar$1${2}"
- if exists('s:origPos')
- let changeLen = s:origWordLen - s:snipPos[s:curPos][2]
-
- " This could probably be more efficent...
- if changeLen != 0
- let lnum = line('.')
- let len = len(s:origPos)
- for pos in s:snipPos[(s:curPos+1):]
- let i = 0 | let j = 0 | let k = 0
- let endSnip = pos[2]+pos[1]-1
- wh i < len && s:origPos[i][0] <= pos[0]
- if pos[0] == s:origPos[i][0]
- if pos[1] > s:origPos[i][1]
- \ || (pos[2] == -1 && pos[1] == s:origPos[i][1])
- let j += 1
- elsei s:origPos[i][1] < endSnip " parse variables within placeholders
- let k += 1
- en
- en
- let i += 1
- endw
- if pos[0] == lnum && pos[1] > s:origSnipPos | let j += 1 | en
- let pos[1] -= changeLen*j | let pos[2] -= changeLen*k
-
- if pos[2] != -1
- for nPos in pos[3]
- let i = 0 | let j = 0
- wh i < len && s:origPos[i][0] <= nPos[0]
- if nPos[0] == s:origPos[i][0] && nPos[1] > s:origPos[i][1]
- let j += 1
- en
- let i += 1
- endw
- if nPos[0] == lnum && nPos[1] > s:origSnipPos | let j += 1 | en
- if nPos[0] > s:origPos[0][0] | brea | en
- let nPos[1] -= changeLen*j
- endfo
- en
- endfo
- en
- unl s:endSnip s:origPos s:origSnipPos
- en
- let changeLine = 0
- let changeCol = 0
- unl s:startSnip s:origWordLen s:update
- el
+ call s:UpdatePlaceholderTabStops()
+ let changeLine = 0 | let changeCol = 0
+ else
let changeLine = s:endSnipLine - s:snipPos[s:curPos][0]
let changeCol = s:endSnip - s:snipPos[s:curPos][1]
if exists('s:origWordLen')
let changeCol -= s:origWordLen | unl s:origWordLen
- en
- en
+ endif
+ endif
let s:curPos += 1
if s:curPos == s:snipLen
let sMode = s:endSnip == s:snipPos[s:curPos-1][1]+s:snipPos[s:curPos-1][2]
- cal s:RemoveSnippet()
- retu sMode ? "\" : ExpandSnippet()
- en
- if changeLine != 0 || changeCol != 0
- " there's probably a more efficient way to do this as well...
- let lnum = s:snipPos[s:curPos-1][0]
- let col = s:snipPos[s:curPos-1][1]
- " update the line number of all proceeding tab stops if has
- " been inserted
- if changeLine != 0
- for pos in s:snipPos[(s:curPos):]
- if pos[0] >= lnum
- if pos[0] == lnum | let pos[1] += changeCol | en
- let pos[0] += changeLine
- en
- if pos[2] != -1
- for nPos in pos[3]
- if nPos[0] >= lnum
- if nPos[0] == lnum | let nPos[1] += changeCol | en
- let nPos[0] += changeLine
- en
- endfo
- en
- endfo
- el
- " update the column of all proceeding tab stops if text has
- " been inserted/deleted in the current line
- for pos in s:snipPos[(s:curPos):]
- if pos[1] >= col && pos[0] == lnum
- let pos[1] += changeCol
- en
- if pos[2] != -1
- for nPos in pos[3]
- if nPos[0] > lnum | brea | en
- if nPos[0] == lnum && nPos[1] >= col
- let nPos[1] += changeCol
- en
- endfo
- en
- endfo
- en
- en
- cal cursor(s:snipPos[s:curPos][0], s:snipPos[s:curPos][1])
+ call s:RemoveSnippet()
+ return sMode ? "\" : TriggerSnippet()
+ endif
+ call s:UpdateTabStops(changeLine, changeCol)
+
+ call cursor(s:snipPos[s:curPos][0], s:snipPos[s:curPos][1])
let s:endSnipLine = s:snipPos[s:curPos][0]
let s:endSnip = s:snipPos[s:curPos][1]
let s:prevLen = [line('$'), col('$')]
- if s:snipPos[s:curPos][2] != -1 | retu s:SelectWord() | en
- retu ''
+ return s:snipPos[s:curPos][2] == -1 ? '' : s:SelectWord()
endf
-fun s:GetSuperTabSID()
- let a_save = @a
- redir @a
- exe 'sil fu /SuperTab$'
- redir END
- let s:sid = matchstr(@a, '\d\+\ze_SuperTab(command)')
- let @a = a_save
+fun s:UpdatePlaceholderTabStops()
+ " update tab stops in snippet if text has been added via "$#",
+ " e.g. in "${1:foo}bar$1${2}"
+ if exists('s:origPos')
+ let changeLen = s:origWordLen - s:snipPos[s:curPos][2]
+
+ " This could probably be more efficent...
+ if changeLen != 0
+ let lnum = line('.')
+ let len = len(s:origPos)
+ for pos in s:snipPos[(s:curPos+1):]
+ let i = 0 | let j = 0 | let k = 0
+ let endSnip = pos[2]+pos[1]-1
+ wh i < len && s:origPos[i][0] <= pos[0]
+ if pos[0] == s:origPos[i][0]
+ if pos[1] > s:origPos[i][1]
+ \ || (pos[2] == -1 && pos[1] == s:origPos[i][1])
+ let j += 1
+ elseif s:origPos[i][1] < endSnip " parse variables within placeholders
+ let k += 1
+ endif
+ endif
+ let i += 1
+ endw
+ if pos[0] == lnum && pos[1] > s:origSnipPos
+ let j += 1
+ endif
+ let pos[1] -= changeLen*j
+ let pos[2] -= changeLen*k
+
+ if pos[2] != -1
+ for nPos in pos[3]
+ let i = 0 | let j = 0
+ wh i < len && s:origPos[i][0] <= nPos[0]
+ if nPos[0] == s:origPos[i][0] && nPos[1] > s:origPos[i][1]
+ let j += 1
+ endif
+ let i += 1
+ endw
+ if nPos[0] == lnum && nPos[1] > s:origSnipPos
+ let j += 1
+ endif
+ if nPos[0] > s:origPos[0][0] | break | endif
+ let nPos[1] -= changeLen*j
+ endfor
+ endif
+ endfor
+ endif
+ unl s:endSnip s:origPos s:origSnipPos
+ endif
+ unl s:startSnip s:origWordLen s:update
+endf
+
+fun s:UpdateTabStops(changeLine, changeCol)
+ " there's probably a more efficient way to do this as well...
+ let lnum = s:snipPos[s:curPos-1][0]
+ let col = s:snipPos[s:curPos-1][1]
+ " update the line number of all proceeding tab stops if has
+ " been inserted
+ if a:changeLine != 0
+ for pos in s:snipPos[(s:curPos):]
+ if pos[0] >= lnum
+ if pos[0] == lnum
+ let pos[1] += a:changeCol
+ endif
+ let pos[0] += a:changeLine
+ endif
+ if pos[2] != -1
+ for nPos in pos[3]
+ if nPos[0] >= lnum
+ if nPos[0] == lnum
+ let nPos[1] += a:changeCol
+ endif
+ let nPos[0] += a:changeLine
+ endif
+ endfor
+ endif
+ endfor
+ elseif a:changeCol != 0
+ " update the column of all proceeding tab stops if text has
+ " been inserted/deleted in the current line
+ for pos in s:snipPos[(s:curPos):]
+ if pos[1] >= col && pos[0] == lnum
+ let pos[1] += a:changeCol
+ endif
+ if pos[2] != -1
+ for nPos in pos[3]
+ if nPos[0] > lnum | break | endif
+ if nPos[0] == lnum && nPos[1] >= col
+ let nPos[1] += a:changeCol
+ endif
+ endfor
+ endif
+ endfor
+ en
endf
fun s:SelectWord()
@@ -428,10 +445,71 @@ fun s:SelectWord()
let s:endSnip = -1
let s:startSnip = s:snipPos[s:curPos][1]-1
en
- if !s:origWordLen | retu '' | en
+ if !s:origWordLen | return '' | en
let l = col('.') != 1 ? 'l' : ''
- if &sel == 'exclusive' | retu "\".l.'v'.s:origWordLen."l\" | en
- retu "\".l.(s:origWordLen == 1 ? 'gh' : 'v'.(s:origWordLen-1)."l\")
+ if &sel == 'exclusive' | return "\".l.'v'.s:origWordLen."l\" | en
+ return s:origWordLen == 1 ? "\".l.'gh'
+ \ : "\".l.'v'.(s:origWordLen-1)."l\"
+endf
+
+" This updates the snippet as you type when text needs to be inserted
+" into multiple places (e.g. in "${1:default text}foo$1bar$1",
+" "default text" would be highlighted, and if the user types something,
+" UpdateChangedSnip() would be called so that the text after "foo" & "bar"
+" are updated accordingly)
+"
+" It also automatically quits the snippet if the cursor is moved out of it
+" while in insert mode.
+au CursorMovedI * call s:UpdateChangedSnip(0)
+au InsertEnter * call s:UpdateChangedSnip(1)
+fun s:UpdateChangedSnip(entering)
+ if exists('s:update')
+ if !exists('s:origPos') && 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
+ " before the snippet (e.g. in "foo$1${2}bar${1:foo}")
+ let s:origSnipPos = s:startSnip
+ let s:origPos = deepcopy(s:snipPos[s:curPos][3])
+ endif
+ let col = col('.')-1
+
+ if s:endSnip != -1
+ let changeLen = col('$') - s:prevLen[1]
+ let s:endSnip += changeLen
+ else " when being updated the first time, after leaving select mode
+ if a:entering | return | endif
+ let s:endSnip = col-1
+ endif
+
+ " if the cursor moves outside the snippet, quit it
+ if line('.') != s:snipPos[s:curPos][0] || col < s:startSnip ||
+ \ col-1 > s:endSnip
+ unl! s:startSnip s:origWordLen s:origPos s:update
+ return s:RemoveSnippet()
+ endif
+
+ call s:UpdateSnip()
+ let s:prevLen[1] = col('$')
+ elseif exists('s:snipPos')
+ let col = col('.')
+ let lnum = line('.')
+ let changeLine = line('$') - s:prevLen[0]
+
+ if lnum == s:endSnipLine
+ let s:endSnip += col('$') - s:prevLen[1]
+ let s:prevLen = [line('$'), col('$')]
+ endif
+ if changeLine != 0
+ let s:endSnipLine += changeLine
+ let s:endSnip = col
+ endif
+
+ " delete snippet if cursor moves out of it in insert mode
+ if (lnum == s:endSnipLine && (col > s:endSnip || col < s:snipPos[s:curPos][1]))
+ \ || lnum > s:endSnipLine || lnum < s:snipPos[s:curPos][0]
+ call s:RemoveSnippet()
+ endif
+ endif
endf
fun s:UpdateSnip()
@@ -449,33 +527,31 @@ fun s:UpdateSnip()
for pos in s:snipPos[s:curPos][3]
if updateSnip
- let start = s:startSnip
- if pos[0] == curLine && pos[1] <= start
- let s:startSnip -= changeLen
- let s:endSnip -= changeLen
- en
- for nPos in s:snipPos[s:curPos][3][(i):]
- if nPos[0] == pos[0]
- if nPos[1] > pos[1] || (nPos == [curLine, pos[1]] &&
- \ nPos[1] > start)
- let nPos[1] -= changeLen
- en
- elsei nPos[0] > pos[0]
- brea
+ let start = s:startSnip
+ if pos[0] == curLine && pos[1] <= start
+ let s:startSnip -= changeLen
+ let s:endSnip -= changeLen
+ en
+ for nPos in s:snipPos[s:curPos][3][(i):]
+ if nPos[0] == pos[0]
+ if nPos[1] > pos[1] || (nPos == [curLine, pos[1]] &&
+ \ nPos[1] > start)
+ let nPos[1] -= changeLen
en
- endfo
- let i += 1
+ elseif nPos[0] > pos[0] | break | en
+ endfor
+ let i += 1
en
- cal setline(pos[0], substitute(getline(pos[0]), '\%'.pos[1].'c'.
+ call setline(pos[0], substitute(getline(pos[0]), '\%'.pos[1].'c'.
\ s:oldWord, newWord, ''))
- endfo
+ endfor
if oldStartSnip != s:startSnip
- cal cursor('.', startCol + s:startSnip - oldStartSnip)
+ call cursor('.', startCol + s:startSnip - oldStartSnip)
en
let s:oldWord = newWord
let s:snipPos[s:curPos][2] = newWordLen
en
endf
-" vim:sw=4:ts=4:ft=vim
+" vim:noet:sw=4:ts=4:ft=vim
diff --git a/snip/after/ftplugin/c_snips.vim b/snip/after/ftplugin/c_snips.vim
deleted file mode 100644
index d6bf59e..0000000
--- a/snip/after/ftplugin/c_snips.vim
+++ /dev/null
@@ -1,56 +0,0 @@
-if !exists('loaded_snips') || exists('s:did_'.&ft.'_snips')
- fini
-en
-let s:did_{&ft}_snips = 1
-
-" main()
-exe "Snipp main int main (int argc, char const* argv[])\n{\n\t${1}\n\treturn 0;\n}"
-" #include <...>
-exe 'Snipp inc #include <${1:stdio}.h>${2}'
-" #include "..."
-exe 'Snipp Inc #include "${1:`Filename("$1.h")`}"${2}'
-" #ifndef ... #define ... #endif
-exe "Snipp def #ifndef $1\n#define ${1:SYMBOL} ${2:value}\n#endif${3}"
-" Header Include-Guard
-" (the randomizer code is taken directly from TextMate; I don't know how to do
-" it in vim script, it could probably be cleaner)
-exe "Snipp once #ifndef ${1:`toupper(Filename('', 'UNTITLED').'_'.system(\"/usr/bin/ruby -e 'print (rand * 2821109907455).round.to_s(36)'\"))`}\n"
- \ ."#define $1\n\n${2}\n\n#endif /* end of include guard: $1 */"
-" Read File Into Vector
-exe "Snipp readfile std::vector v;\nif (FILE *${2:fp} = fopen(${1:\"filename\"}, \"r\")) {\n\tchar buf[1024];\n\twhile (size_t len = "
- \ ."fread(buf, 1, sizeof(buf), $2))\n\t\tv.insert(v.end(), buf, buf + len);\n\tfclose($2);\n}${3}"
-" If Condition
-exe "Snipp if if (${1:/* condition */}) {\n\t${2:/* code */}\n}"
-exe "Snipp el else {\n\t${1}\n}"
-" Tertiary conditional
-exe 'Snipp t ${1:/* condition */} ? ${2:a} : ${3:b}'
-" Do While Loop
-exe "Snipp do do {\n\t${2:/* code */}\n} while (${1:/* condition */});"
-" While Loop
-exe "Snipp wh while (${1:/* condition */}) {\n\t${2:/* code */}\n}"
-" For Loop
-exe "Snipp for for (${2:i} = 0; $2 < ${1:count}; $2${3:++}) {\n\t${4:/* code */}\n}"
-" Custom For Loop
-exe "Snipp forr for (${1:i} = 0; ${2:$1 < 5}; $1${3:++}) {\n\t${4:/* code */}\n}"
-" Function
-exe "Snipp fun ${1:void} ${2:function_name} (${3})\n{\n\t${4:/* code */}\n}"
-" Typedef
-exe 'Snipp td typedef ${1:int} ${2:MyCustomType};'
-" Struct
-exe "Snipp st struct ${1:`Filename('$1_t', 'name')`} {\n\t${2:/* data */}\n}${3: /* optional variable list */};${4}"
-" Typedef struct
-exe "Snipp tds typedef struct ${2:$1 }{\n\t${3:/* data */}\n} ${1:`Filename('$1_t', 'name')`};"
-" Class
-exe "Snipp cl class ${1:`Filename('$1_t', 'name')`} {\npublic:\n\t$1 (${2:arguments});\n\tvirtual ~$1 ();\n\nprivate:\n\t${3:/* data */}\n};"
-" Namespace
-exe "Snipp ns namespace ${1:`Filename('', 'my')`} {\n\t${2}\n} /* $1 */"
-" std::map
-exe "Snipp map std::map<${1:key}, ${2:value}> map${3};"
-" std::vector
-exe "Snipp vector std::vector<${1:char}> v${2};"
-" printf
-" unfortunately version this isn't as nice as TextMates's, given the lack of a
-" dynamic `...`
-exe 'Snipp pr printf("${1:%s}\n"${2});${3}'
-" fprintf (again, this isn't as nice as TextMate's version, but it works)
-exe 'Snipp fpr fprintf(${1:stderr}, "${2:%s}\n"${3});${4}'
diff --git a/snip/after/ftplugin/html_snips.vim b/snip/after/ftplugin/html_snips.vim
deleted file mode 100644
index e04a3f5..0000000
--- a/snip/after/ftplugin/html_snips.vim
+++ /dev/null
@@ -1,94 +0,0 @@
-if !exists('g:loaded_snips') || exists('s:did_'.&ft.'_snips')
- fini
-en
-let s:did_{&ft}_snips = 1
-
-" automatically add a closing '/' to the end of xhtml tags
-let c = &ft == 'xhtml' ? ' /' : ''
-
-" Some useful Unicode entities
-" Non-Breaking Space
-exe 'Snipp nbs '
-" ←
-exe 'Snipp left ←'
-" →
-exe 'Snipp right →'
-" ↑
-exe 'Snipp up ↑'
-" ↓
-exe 'Snipp down ↓'
-" ↩
-exe 'Snipp return ↩'
-" ⇤
-exe 'Snipp backtab ⇤'
-" ⇥
-exe 'Snipp tab ⇥'
-" ⇧
-exe 'Snipp shift ⇧'
-" ⌃
-exe 'Snipp control ⌃'
-" ⌅
-exe 'Snipp enter ⌅'
-" ⌘
-exe 'Snipp command ⌘'
-" ⌥
-exe 'Snipp option ⌥'
-" ⌦
-exe 'Snipp delete ⌦'
-" ⌫
-exe 'Snipp backspace ⌫'
-" ⎋
-exe 'Snipp escape ⎋'
-" Generic Doctype
-exe "Snipp! doctype \"HTML 4.01 Strict\" "
-exe "Snipp! doctype \"HTML 4.01 Transitional\" "
-exe "Snipp! doctype \"HTML 5\" "
-exe "Snipp! doctype \"XHTML 1.0 Frameset\" "
-exe "Snipp! doctype \"XHTML 1.0 Strict\" "
-exe "Snipp! doctype \"XHTML 1.0 Transitional\" "
-exe "Snipp! doctype \"XHTML 1.1\" "
-" HTML Doctype 4.01 Strict
-exe "Snipp docts "
-" HTML Doctype 4.01 Transitional
-exe "Snipp doct "
-" HTML Doctype 5
-exe 'Snipp doct5 '
-" XHTML Doctype 1.0 Frameset
-exe "Snipp docxf "
-" XHTML Doctype 1.0 Strict
-exe "Snipp docxs "
-" XHTML Doctype 1.0 Transitional
-exe "Snipp docxt "
-" XHTML Doctype 1.1
-exe "Snipp docx "
-exe "Snipp html \n${1}\n"
-exe "Snipp xhtml \n${1}\n"
-exe "Snipp body \n\t${1}\n"
-exe "Snipp head \n\t\n\t"
-\. "${1:`substitute(Filename('', 'Page Title'), '^.', '\\u&', '')`}\n\t${2}\n"
-exe 'Snipp title ${1:`substitute(Filename("", "Page Title"), "^.", "\\u&", "")`}${2}'
-exe "Snipp script ${2}"
-exe "Snipp scriptsrc ${2}"
-exe "Snipp style ${3}"
-exe 'Snipp base '
-exe 'Snipp r '
-exe "Snipp div
\n\t${2}\n
"
-" Embed QT Movie
-exe "Snipp movie ${6}"
-exe "Snipp fieldset "
-exe "Snipp form "
-exe 'Snipp h1
tag, and then allow
-the user to to the middle of it:
- >
- exe "Snipp div
\n\t${2}\n
"
-<
- *snipMate-placeholders* *snipMate-${#:}* *snipMate-$#*
-Placeholders ~
-
-Placeholder text can be supplied using "${#:text}", where # is the number of
-the tab stop. This text then can be copied throughout the snippet using "$#",
-given # is the same number as used before. So, to make a C for loop: >
-
- exe "Snipp for for (${2:i}; $2 < ${1:count}; $1++) {\n\t${4}\n}"
-
-This will cause "count" to first be selected and change if the user starts
-typing. When is pressed, the "i" in ${2}'s position will be selected;
-all $2 variables will default to "i" and automatically be updated if the user
-starts typing.
-NOTE: "$#" syntax is used only for variables, not for tab stops as in TextMate.
-
-Variables within variables are also possible. For instance: >
-
- exe 'Snipp opt '
-
-Will, as usual, cause "option" to first be selected and update all the $1
-variables if the user starts typing. Since one of these variables is inside of
-${2}, this text will then be used as a placeholder for the next tab stop,
-allowing the user to change it if he wishes.
-
-To copy a value throughout a snippet without supplying default text, simply
-use the "${#:}" construct without the text; e.g.: >
-
- exe 'Snipp foo${1:}bar$1'
-< *snipMate-commands*
-Interpolated Vim Script ~
-
-Snippets can also contain Vim script commands that are executed (via |eval()|)
-when the snippet is inserted. Commands are given inside backticks (`...`); for
-TextMates's functionality, use the |system()| function. E.g.: >
-
- exe 'Snipp date `system("date +%Y-%m-%d")`'
-
-will insert the current date, assuming you are on a Unix system. Note you can
-also (and should) use |strftime()| for this example.
-
-Filename([{expr}, {defaultText}]) *snipMate-filename* *Filename()*
-
-Since the current filename is used often in snippets, a default function
-has been defined for it in snipMate.vim, appropriately called Filename().
-
-With no arguments, the default filename without an extension is returned;
-the first argument specifies what to place before or after the filename,
-and the second argument supplies the default text to be used if the file
-has not been named. "$1" in the first argument is replaced with the filename;
-if you only want the filename to be returned, the first argument can be left
-blank. Examples: >
-
- exe 'Snipp filename `Filename()`'
- exe 'Snipp filename_with_default `Filename("", "name")`'
- exe 'Snipp filename_foo `Filename("$1_foo")`'
-
-
-The first example returns the filename if it the file has been named, and an
-empty string if it hasn't. The second returns the filename if it's been named,
-and "name" if it hasn't. The third returns the filename followed by "_foo" if
-it has been named, and an empty string if it hasn't.
-
- *snipMate-settings* *g:snips_author*
-The g:snips_author string (similar to $TM_FULLNAME in TextMate) should be set
-to your name; it can then be used in snippets to automatically add it. E.g.: >
-
- let g:snips_author = 'Hubert Farnsworth'
- exe 'Snipp name `g:snips_author`'
-<
- *snipMate-remap*
-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
-'~/.vim/after/plugin/snipMate.vim'. For instance, to change the trigger key
-to shift-tab, just change this: >
- ino =ExpandSnippet()
- snor i=ExpandSnippet()
-
-to this: >
- ino =ExpandSnippet()
- snor i=ExpandSnippet()
-
-==============================================================================
-FEATURES *snipMate-features*
-
-snipMate.vim has the following features among others:
- - The syntax of snippets is very similar to TextMate's, allowing
- easy conversion.
- - The position of the snippet is kept transparently (i.e. it does not use
- markers/placeholders written to the buffer), which allows you to escape
- out of an incomplete snippet, something particularly useful in Vim.
- - Variables in snippets are updated as-you-type.
- - 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.
-
-==============================================================================
-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.: >
-
- '
${3}
'
-<
- In TextMate this would first highlight ' id="some_id"', and if
- you hit delete it would automatically skip ${2} and go to ${3}
- on the next , but if you didn't delete it it would highlight
- "some_id" first. You cannot do this in snipMate.vim.
- - Regex cannot be performed on variables, such as "${1/.*/\U&}"
- - Placeholders cannot span multiple lines.
- - Activating snippets in different scopes of the same file is
- not possible.
-
-Perhaps some of these features will be added in a later release.
-
-==============================================================================
-CONTACT *snipMate-contact* *snipMate-author*
-
-To contact the author (Michael Sanders), please email:
- msanders42+snipmate gmail com
-
-I greatly appreciate any suggestions or improvements offered for the script.
-
-vim:tw=78:ts=8:ft=help:norl:
diff --git a/snip/plugin/snipMate.vim b/snip/plugin/snipMate.vim
deleted file mode 100644
index b3116ce..0000000
--- a/snip/plugin/snipMate.vim
+++ /dev/null
@@ -1,557 +0,0 @@
-" File: snipMate.vim
-" Author: Michael Sanders
-" Version: 0.7
-" 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 "".
-"
-" For more help see snipMate.txt; you can do this by using:
-" :helptags ~/.vim/doc
-" :h snipMate.txt
-" Last Modified: February 25, 2009.
-
-if exists('loaded_snips') || &cp || version < 700
- finish
-endif
-let loaded_snips = 1
-if !exists('snips_author') | let snips_author = 'Me' | endif
-
-com! -nargs=+ -bang Snipp call s:MakeSnippet(, &ft, 0)
-com! -nargs=+ -bang GlobalSnip call s:MakeSnippet(, '_', 0)
-
-let s:snippets = {} | let s:multi_snips = {}
-
-fun! Filename(...)
- let filename = expand('%:t:r')
- if filename == '' | return a:0 == 2 ? a:2 : '' | endif
- return !a:0 || a:1 == '' ? filename : substitute(a:1, '$1', filename, 'g')
-endf
-
-" escapes special characters in snippet triggers
-fun s:Hash(text)
- return substitute(a:text, '\W', '\="_".char2nr(submatch(0))."_"', 'g')
-endf
-
-fun s:MakeSnippet(text, ft, multisnip)
- let space = stridx(a:text, ' ')
- let trigger = s:Hash(strpart(a:text, 0, space))
- if a:multisnip
- let space += 2
- let quote = stridx(a:text, '"', space)
- let name = strpart(a:text, space, quote-space)
- let space = stridx(a:text, ' ', quote)
- let var = 's:multi_snips'
- else " evaluating a regular snippet
- let var = 's:snippets'
- endif
- if !has_key({var}, a:ft) | let {var}[a:ft] = {} | endif
- let end = strpart(a:text, space+1)
-
- if end == '' || space == '' || (a:multisnip && name == '')
- echom 'Error in snipMate.vim: Snippet '.a:text.' is undefined.'
- elseif !has_key({var}[a:ft], trigger)
- let {var}[a:ft][trigger] = a:multisnip ? [[name, end]] : end
- elseif a:multisnip | let {var}[a:ft][trigger] += [[name, end]]
- else
- echom 'Warning in snipMate.vim: Snippet '.strpart(a:text, 0, stridx(a:text, ' '))
- \ .' is already defined. See :h multi_snip for help on snippets'
- \ .' with multiple matches.'
- endif
-endf
-
-fun! ExtractSnips(dir, ft)
- let s:slash = has('win16') || has('win32') || has('win64') ? '\\' : '/'
- for path in split(globpath(a:dir, '*'), '\n')
- if isdirectory(path)
- for snipFile in split(globpath(path, '*.snippet'), '\n')
- call s:ProcessFile(snipFile, a:ft, strpart(path, strridx(path, s:slash)+1))
- endfor
- continue
- endif
- call s:ProcessFile(path, a:ft)
- endfor
- unl s:slash
- let s:did_{a:ft} = 1
-endf
-
-" Processes a snippet file; optionally add the name of the parent directory
-" for a snippet with multiple matches.
-fun s:ProcessFile(file, ft, ...)
- let keyword = matchstr(a:file, '.*'.s:slash.'\zs.*\ze\.snippet')
- if keyword == '' | return | endif
- try
- let text = join(readfile(a:file), '\n')
- catch /E484/
- echom "Error in snipMate.vim: couldn't read file: ".a:file
- endtry
- return a:0 ? s:MakeSnippet(a:1.' "'.keyword.'" '.text, a:ft, 1)
- \ : s:MakeSnippet(keyword.' '.text, a:ft, 0)
-endf
-
-fun! ResetSnippets()
- let s:snippets = {} | let s:multi_snips = {}
-endf
-
-fun s:RemoveSnippet()
- unl s:snipPos s:curPos s:snipLen s:endSnip s:endSnipLine s:prevLen
-endf
-
-fun s:ChooseSnippet(ft, trigger)
- let snippet = []
- let i = 1
- for snip in s:multi_snips[a:ft][a:trigger]
- let snippet += [i.'. '.snip[0]]
- let i += 1
- endfor
- if i == 2 | return s:multi_snips[a:ft][a:trigger][0][1] | endif
- let num = inputlist(snippet)-1
- return num < i-1 ? s:multi_snips[a:ft][a:trigger][num][1] : ''
-endf
-
-fun! TriggerSnippet()
- if pumvisible() " update snippet if completion is used, or deal with supertab
- if exists('s:sid') | return "\" | endif
- call feedkeys("\a", 'n') | call s:UpdateChangedSnip(0)
- endif
-
- if !exists('s:snipPos') " don't expand snippets within snippets
- if !exists('s:sid') && exists('g:SuperTabMappingForward')
- \ && g:SuperTabMappingForward == ""
- call s:GetSuperTabSID()
- endif
- let word = s:GetSnippet()
-
- if exists('s:snippet')
- if s:snippet == ''
- return unl s:snippet " if user cancelled multi snippet, quit
- endif
- let col = col('.')-len(word)
- " if word is a trigger for a snippet, delete the trigger & expand
- " the snippet
- exe 'sil s/'.word.'\%#//'
- return s:ExpandSnippet(col)
- endif
- return exists('s:sid') ? {s:sid}_SuperTab('n') : "\"
- endif
- return s:JumpTabStop()
-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()
- let origWord = matchstr(getline('.'), '\S\+\%'.col('.').'c')
- wh !exists('s:snippet')
- let word = s:Hash(origWord)
- if exists('s:snippets["'.&ft.'"]["'.word.'"]')
- let s:snippet = s:snippets[&ft][word]
- elseif exists('s:snippets["_"]["'.word.'"]')
- let s:snippet = s:snippets['_'][word]
- elseif exists('s:multi_snips["'.&ft.'"]["'.word.'"]')
- let s:snippet = s:ChooseSnippet(&ft, word)
- elseif exists('s:multi_snips["_"]["'.word.'"]')
- let s:snippet = s:ChooseSnippet('_', word)
- en
- if match(origWord, '\W') == -1 | break | en
- let origWord = substitute(origWord, '.\{-}\W', '', '')
- endw
- return origWord
-endf
-
-fun s:GetSuperTabSID()
- let old = @a
- redir @a | exe 'sil fun /SuperTab$' | redir END
- let s:sid = matchstr(@a, '\d\+\ze_SuperTab(command)')
- let @a = old
-endf
-
-fun s:ExpandSnippet(col)
- let lnum = line('.') | let col = a:col
- let afterCursor = strpart(getline('.'), col-1)
- if afterCursor != "\t" && afterCursor != ' '
- sil exe 's/\%'.col.'c.*//'
- else | let afterCursor = '' | endif
-
- call s:ProcessSnippet()
- if s:snippet == ''
- return unl s:snippet " avoid an error if the snippet is now empty
- endif
-
- let snip = split(substitute(s:snippet, '$\d\|${\d.\{-}}', '', 'g'), "\n", 1)
- if afterCursor != '' | let snip[-1] .= afterCursor | endif
- let line = getline(lnum)
- call setline(lnum, line.snip[0])
-
- " for some reason the cursor needs to move one right after this
- if line != '' && col == 1 && afterCursor == '' && &ve !~ 'all\|onemore'
- let col += 1
- endif
- " autoindent snippet according to previous indentation
- let indent = matchend(line, '^.\{-}\ze\(\S\|$\)')+1
- if !indent
- call append(lnum, snip[1:])
- else
- call append(lnum, map(snip[1:], "'".strpart(line, 0, indent-1)."'.v:val"))
- endif
-
- let snipLen = s:BuildTabStops(lnum, col-indent, indent)
- unl s:snippet
-
- if snipLen
- let s:curPos = 0
- let s:snipLen = snipLen
- let s:endSnip = s:snipPos[0][1]
- let s:endSnipLine = s:snipPos[s:curPos][0]
-
- call cursor(s:snipPos[0][0], s:snipPos[0][1])
- let s:prevLen = [line('$'), col('$')]
- if s:snipPos[0][2] != -1 | return s:SelectWord() | endif
- else
- " place cursor at end of snippet if no tab stop is given
- unl s:snipPos | let newlines = len(snip)-1
- call cursor(lnum + newlines, tab + len(snip[-1]) - len(afterCursor)
- \ + (newlines ? 0: col))
- endif
- return ''
-endf
-
-fun s:ProcessSnippet()
- " evaluate eval (`...`) expressions
- " Using a loop here instead of a regex fixes a bug with nested "\="
- if stridx(s:snippet, '`') != -1
- wh match(s:snippet, '`.\{-}`') != -1
- let s:snippet = substitute(s:snippet, '`.\{-}`',
- \ substitute(eval(matchstr(s:snippet, '`\zs.\{-}\ze`')),
- \ "\n\\%$", '', ''), '')
- endw
- let s:snippet = substitute(s:snippet, "\r", "\n", 'g')
- endif
-
- " place all text after a colon in a tab stop after the tab stop
- " (e.g. "${#:foo}" becomes "${:foo}foo")
- " this helps tell the position of the tab stops later.
- let s:snippet = substitute(s:snippet, '${\d:\(.\{-}\)}', '&\1', 'g')
-
- " update the s:snippet so that all the $# become
- " the text after the colon in their associated ${#}
- " (e.g. "${1:foo}" turns all "$1"'s into "foo")
- let i = 1
- wh stridx(s:snippet, '${'.i) != -1
- let s = matchstr(s:snippet, '${'.i.':\zs.\{-}\ze}')
- if s != ''
- let s:snippet = substitute(s:snippet, '$'.i, '&'.s, 'g')
- endif
- let i += 1
- endw
- if &et " expand tabs to spaces if 'expandtab' is set
- let s:snippet = substitute(s:snippet, '\t',
- \ repeat(' ', &sts ? &sts : &sw), 'g')
- endif
-endf
-
-fun s:Count(haystack, needle)
- let counter = 0
- let index = stridx(a:haystack, a:needle)
- wh index != -1
- let index = stridx(a:haystack, a:needle, index+1)
- let counter += 1
- endw
- return counter
-endf
-
-" Sorry, this next section is a bit convoluted...
-" This function builds a list of a list of each tab stop in the snippet
-" containing:
-" 1.) The number of the current line plus the number of "\n"s (line
-" breaks) before the tab stop
-" 2.) The current column plus the position of the next "${#}" on
-" the line by getting the length of the string between the last "\n"
-" and the "${#}" tab stop,
-" 3.) The length of the text after the colon for the current tab stop
-" (e.g. "${#:foo}" would returnrn 3). If there is no text, -1 is returnrned.
-" 4.) If the "${#:}" construct is given, the fourth part of the list
-" is another list containing all the matches of "$#", to be replaced
-" with the variable. This list is composed the same way as the parent:
-" the first part is the number of "\n"s before the tab stop, and
-" second is the position (column) of the "$#" tab stop on the line.
-" If there are none of these tab stop, an empty list ([]) is returnrned
-fun s:BuildTabStops(lnum, col, indent)
- let s:snipPos = []
- let i = 1
- " temporarily delete placeholders
- let cut_snip = substitute(s:snippet, '$\d', '', 'g')
- wh stridx(s:snippet, '${'.i) != -1
- let s:snipPos += [[a:lnum+s:Count(matchstr(cut_snip, '^.*\ze${'.i), "\n"),
- \ a:indent+len(matchstr(substitute(cut_snip, '${'.i.'\@!\d.\{-}}', '', 'g'),
- \ "^.*\\(\n\\|^\\)\\zs.*\\ze${".i.'.\{-}}')), -1]]
- if s:snipPos[i-1][0] == a:lnum
- let s:snipPos[i-1][1] += a:col
- endif
-
- " get all $# matches in another list, if ${#:name} is given
- if stridx(cut_snip, '${'.i.':') != -1
- let j = i-1
- let s:snipPos[j][2] = len(matchstr(cut_snip, '${'.i.':\zs.\{-}\ze}'))
- let s:snipPos[j] += [[]]
- " temporarily delete all other tab stops/placeholders
- let tempstr = substitute(s:snippet, '$'.i.'\@!\d\|${\d.\{-}}', '', 'g')
- wh stridx(tempstr, '$'.i) != -1
- let beforeMark = matchstr(tempstr, '^.\{-}\ze$'.i)
- let linecount = a:lnum+s:Count(beforeMark, "\n")
- let s:snipPos[j][3] += [[linecount,
- \ a:indent+(linecount > a:lnum ?
- \ len(matchstr(beforeMark, "^.*\n\\zs.*"))
- \ : a:col+len(beforeMark))]]
- let tempstr = substitute(tempstr, '$'.i, '', '')
- endw
- endif
- let i += 1
- endw
- return i-1
-endf
-
-fun s:JumpTabStop()
- if exists('s:update')
- call s:UpdatePlaceholderTabStops()
- let changeLine = 0 | let changeCol = 0
- else
- let changeLine = s:endSnipLine - s:snipPos[s:curPos][0]
- let changeCol = s:endSnip - s:snipPos[s:curPos][1]
- if exists('s:origWordLen')
- let changeCol -= s:origWordLen | unl s:origWordLen
- endif
- endif
-
- let s:curPos += 1
- if s:curPos == s:snipLen
- let sMode = s:endSnip == s:snipPos[s:curPos-1][1]+s:snipPos[s:curPos-1][2]
- call s:RemoveSnippet()
- return sMode ? "\" : TriggerSnippet()
- endif
- call s:UpdateTabStops(changeLine, changeCol)
-
- call cursor(s:snipPos[s:curPos][0], s:snipPos[s:curPos][1])
-
- let s:endSnipLine = s:snipPos[s:curPos][0]
- let s:endSnip = s:snipPos[s:curPos][1]
- let s:prevLen = [line('$'), col('$')]
-
- return s:snipPos[s:curPos][2] == -1 ? '' : s:SelectWord()
-endf
-
-fun s:UpdatePlaceholderTabStops()
- " update tab stops in snippet if text has been added via "$#",
- " e.g. in "${1:foo}bar$1${2}"
- if exists('s:origPos')
- let changeLen = s:origWordLen - s:snipPos[s:curPos][2]
-
- " This could probably be more efficent...
- if changeLen != 0
- let lnum = line('.')
- let len = len(s:origPos)
- for pos in s:snipPos[(s:curPos+1):]
- let i = 0 | let j = 0 | let k = 0
- let endSnip = pos[2]+pos[1]-1
- wh i < len && s:origPos[i][0] <= pos[0]
- if pos[0] == s:origPos[i][0]
- if pos[1] > s:origPos[i][1]
- \ || (pos[2] == -1 && pos[1] == s:origPos[i][1])
- let j += 1
- elseif s:origPos[i][1] < endSnip " parse variables within placeholders
- let k += 1
- endif
- endif
- let i += 1
- endw
- if pos[0] == lnum && pos[1] > s:origSnipPos
- let j += 1
- endif
- let pos[1] -= changeLen*j
- let pos[2] -= changeLen*k
-
- if pos[2] != -1
- for nPos in pos[3]
- let i = 0 | let j = 0
- wh i < len && s:origPos[i][0] <= nPos[0]
- if nPos[0] == s:origPos[i][0] && nPos[1] > s:origPos[i][1]
- let j += 1
- endif
- let i += 1
- endw
- if nPos[0] == lnum && nPos[1] > s:origSnipPos
- let j += 1
- endif
- if nPos[0] > s:origPos[0][0] | break | endif
- let nPos[1] -= changeLen*j
- endfor
- endif
- endfor
- endif
- unl s:endSnip s:origPos s:origSnipPos
- endif
- unl s:startSnip s:origWordLen s:update
-endf
-
-fun s:UpdateTabStops(changeLine, changeCol)
- " there's probably a more efficient way to do this as well...
- let lnum = s:snipPos[s:curPos-1][0]
- let col = s:snipPos[s:curPos-1][1]
- " update the line number of all proceeding tab stops if has
- " been inserted
- if a:changeLine != 0
- for pos in s:snipPos[(s:curPos):]
- if pos[0] >= lnum
- if pos[0] == lnum
- let pos[1] += a:changeCol
- endif
- let pos[0] += a:changeLine
- endif
- if pos[2] != -1
- for nPos in pos[3]
- if nPos[0] >= lnum
- if nPos[0] == lnum
- let nPos[1] += a:changeCol
- endif
- let nPos[0] += a:changeLine
- endif
- endfor
- endif
- endfor
- elseif a:changeCol != 0
- " update the column of all proceeding tab stops if text has
- " been inserted/deleted in the current line
- for pos in s:snipPos[(s:curPos):]
- if pos[1] >= col && pos[0] == lnum
- let pos[1] += a:changeCol
- endif
- if pos[2] != -1
- for nPos in pos[3]
- if nPos[0] > lnum | break | endif
- if nPos[0] == lnum && nPos[1] >= col
- let nPos[1] += a:changeCol
- endif
- endfor
- endif
- endfor
- en
-endf
-
-fun s:SelectWord()
- let s:origWordLen = s:snipPos[s:curPos][2]
- let s:oldWord = strpart(getline('.'), s:snipPos[s:curPos][1]-1,
- \ s:origWordLen)
- let s:prevLen[1] -= s:origWordLen
- if !empty(s:snipPos[s:curPos][3])
- let s:update = 1
- let s:endSnip = -1
- let s:startSnip = s:snipPos[s:curPos][1]-1
- en
- if !s:origWordLen | return '' | en
- let l = col('.') != 1 ? 'l' : ''
- if &sel == 'exclusive' | return "\".l.'v'.s:origWordLen."l\" | en
- return s:origWordLen == 1 ? "\".l.'gh'
- \ : "\".l.'v'.(s:origWordLen-1)."l\"
-endf
-
-" This updates the snippet as you type when text needs to be inserted
-" into multiple places (e.g. in "${1:default text}foo$1bar$1",
-" "default text" would be highlighted, and if the user types something,
-" UpdateChangedSnip() would be called so that the text after "foo" & "bar"
-" are updated accordingly)
-"
-" It also automatically quits the snippet if the cursor is moved out of it
-" while in insert mode.
-au CursorMovedI * call s:UpdateChangedSnip(0)
-au InsertEnter * call s:UpdateChangedSnip(1)
-fun s:UpdateChangedSnip(entering)
- if exists('s:update')
- if !exists('s:origPos') && 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
- " before the snippet (e.g. in "foo$1${2}bar${1:foo}")
- let s:origSnipPos = s:startSnip
- let s:origPos = deepcopy(s:snipPos[s:curPos][3])
- endif
- let col = col('.')-1
-
- if s:endSnip != -1
- let changeLen = col('$') - s:prevLen[1]
- let s:endSnip += changeLen
- else " when being updated the first time, after leaving select mode
- if a:entering | return | endif
- let s:endSnip = col-1
- endif
-
- " if the cursor moves outside the snippet, quit it
- if line('.') != s:snipPos[s:curPos][0] || col < s:startSnip ||
- \ col-1 > s:endSnip
- unl! s:startSnip s:origWordLen s:origPos s:update
- return s:RemoveSnippet()
- endif
-
- call s:UpdateSnip()
- let s:prevLen[1] = col('$')
- elseif exists('s:snipPos')
- let col = col('.')
- let lnum = line('.')
- let changeLine = line('$') - s:prevLen[0]
-
- if lnum == s:endSnipLine
- let s:endSnip += col('$') - s:prevLen[1]
- let s:prevLen = [line('$'), col('$')]
- endif
- if changeLine != 0
- let s:endSnipLine += changeLine
- let s:endSnip = col
- endif
-
- " delete snippet if cursor moves out of it in insert mode
- if (lnum == s:endSnipLine && (col > s:endSnip || col < s:snipPos[s:curPos][1]))
- \ || lnum > s:endSnipLine || lnum < s:snipPos[s:curPos][0]
- call s:RemoveSnippet()
- endif
- endif
-endf
-
-fun s:UpdateSnip()
- " using strpart() here avoids a bug if s:endSnip is negative that would
- " happen with the getline('.')[(s:startSnip):(s:endSnip)] syntax
- let newWordLen = s:endSnip - s:startSnip + 1
- let newWord = strpart(getline('.'), s:startSnip, newWordLen)
- if newWord != s:oldWord
- let changeLen = s:snipPos[s:curPos][2] - newWordLen
- let curLine = line('.')
- let startCol = col('.')
- let oldStartSnip = s:startSnip
- let updateSnip = changeLen != 0
- let i = 0
-
- for pos in s:snipPos[s:curPos][3]
- if updateSnip
- let start = s:startSnip
- if pos[0] == curLine && pos[1] <= start
- let s:startSnip -= changeLen
- let s:endSnip -= changeLen
- en
- for nPos in s:snipPos[s:curPos][3][(i):]
- if nPos[0] == pos[0]
- if nPos[1] > pos[1] || (nPos == [curLine, pos[1]] &&
- \ nPos[1] > start)
- let nPos[1] -= changeLen
- en
- elseif nPos[0] > pos[0] | break | en
- endfor
- let i += 1
- en
-
- call setline(pos[0], substitute(getline(pos[0]), '\%'.pos[1].'c'.
- \ s:oldWord, newWord, ''))
- endfor
- if oldStartSnip != s:startSnip
- call cursor('.', startCol + s:startSnip - oldStartSnip)
- en
-
- let s:oldWord = newWord
- let s:snipPos[s:curPos][2] = newWordLen
- en
-endf
-" vim:noet:sw=4:ts=4:ft=vim