From d9f239c6eaebb220d7c48a293c0f9fcbf72cf5a8 Mon Sep 17 00:00:00 2001 From: Marc Weber Date: Thu, 21 Jan 2010 14:33:01 +0100 Subject: [PATCH] big change: Add a function collecting snippets on demand. .snippets files are parsed only once and cached for performance reasons. However if the timestamp changes they are reread automatically. I tried to preserve behaviour. However I may have failed. The main reason for this is that it is possible now to define new script sources easily. Eg there could be a plugin which reads Eclipse plugins, translating them to snipMate snippets on the fly etc. This patch adds a dependency on my vim-addon-mw-utils package. Therefor installation is more difficult unless you use vim-addon-manager Send bug reports about this change to marco-oweber@gmx.de --- after/plugin/snipMate.vim | 3 - autoload/snipMate.vim | 114 ++++++++++++++++++++++++++ plugin/snipMate.vim | 164 +++++++------------------------------- snipMate-addon-info.txt | 10 +++ 4 files changed, 154 insertions(+), 137 deletions(-) create mode 100644 snipMate-addon-info.txt diff --git a/after/plugin/snipMate.vim b/after/plugin/snipMate.vim index 03e79ae2..8a3c7c7c 100644 --- a/after/plugin/snipMate.vim +++ b/after/plugin/snipMate.vim @@ -29,7 +29,4 @@ if empty(snippets_dir) finish endif -call GetSnippets(snippets_dir, '_') " Get global snippets - -au FileType * if &ft != 'help' | call GetSnippets(snippets_dir, &ft) | endif " vim:noet:sw=4:ts=4:ft=vim diff --git a/autoload/snipMate.vim b/autoload/snipMate.vim index 0ad5a58d..f2bb5fa9 100644 --- a/autoload/snipMate.vim +++ b/autoload/snipMate.vim @@ -1,3 +1,14 @@ +" config which can be overridden (shared lines) +if !exists('g:snipMate') + let g:snipMate = {} +endif +let s:snipMate = g:snipMate + +" if filetype is objc, cpp, or cs also append snippets from scope 'c' +" you can add multiple by separating scopes by ',', see s:ScopeAliases +" TODO add documentation to doc/* +let s:snipMate['scope_aliases'] = get(s:snipMate,'scope_aliases', {'objc' :'c', 'cpp': 'c', 'cs':'c'} ) + fun! Filename(...) let filename = expand('%:t:r') if filename == '' | return a:0 == 2 ? a:2 : '' | endif @@ -432,4 +443,107 @@ fun s:UpdateVars() let s:oldWord = newWord let g:snipPos[s:curPos][2] = newWordLen endf + +" should be moved to utils or such? +fun! snipMate#SetByPath(dict, path, value) + let d = a:dict + for p in a:path[:-2] + if !has_key(d,p) | let d[p] = {} | endif + let d = d[p] + endfor + let d[a:path[-1]] = a:value +endf + +" reads a .snippets file +" returns list of +" ['triggername', 'name', 'contents'] +fun! snipMate#ReadSnippetsFile(file) + let result = [] + if !filereadable(a:file) | return result | endif + let text = readfile(a:file) + + let text = readfile(a:file) + let inSnip = 0 + for line in text + ["\n"] + if inSnip && (line[0] == "\t" || line == '') + let content .= strpart(line, 1)."\n" + continue + elseif inSnip + call add(result, [trigger, name == '' ? 'default' : name, content[:-2]]) + let inSnip = 0 + endif + + if line[:6] == 'snippet' + let inSnip = 1 + let trigger = strpart(line, 8) + let name = '' + let space = stridx(trigger, ' ') + 1 + if space " Process multi snip + let name = strpart(trigger, space) + let trigger = strpart(trigger, 0, space - 1) + endif + let content = '' + endif + endfor + return result +endf + + +let s:read_snippets_cached = {'func' : function('snipMate#ReadSnippetsFile'), 'version': 3, 'use_file_cache':1} + +fun! s:ScopeAliases(list) + let result = [] + let scope_aliases = get(s:snipMate,'scope_aliases', {}) + for i in a:list + if has_key(scope_aliases, i) + call add(result, split(scope_aliases[i],',')) + endif + endfor + return result +endf + +" return a dict of snippets found in runtimepath matching trigger +" scopes: list of scopes. usually this is the filetype. eg ['c','cpp'] +" trigger may contain glob patterns. Thus use '*' to get all triggers +fun! snipMate#GetSnippets(scopes, trigger) + let result = {} + let triggerR = substitute(a:trigger,'*','.*','g') + let scopes = a:scopes + s:ScopeAliases(a:scopes) + for scope in scopes + + for r in split(&runtimepath,',') + + " .snippets files (many snippets per file). cache result for + " performance reason + " assume everything is a file.. + for snippetsF in split(glob(r.'/snippets/'.scope.'.snippets'),"\n") + for [trigger, name, contents] in cached_file_contents#CachedFileContents(snippetsF, s:read_snippets_cached, 0) + if trigger !~ triggerR | continue | endif + call snipMate#SetByPath(result, [trigger, name], contents) + endfor + endfor + + " == one file per snippet: == + + " without name snippets//.snippet + for f in split(glob(r.'/snippets/'.scope.'/'.a:trigger.'.snippet'),"\n") + let trigger = fnamemodify(f,':t:r') + " lazily read files as needed + call snipMate#SetByPath(result, [trigger, 'default'], funcref#Function('return readfile('.string(f).')')) + endfor + " add /snippets/trigger/*.snippet files (TODO) + + " with name (multi-snip) snippets///.snippet + for f in split(glob(r.'/snippets/'.scope.'/'.a:trigger.'/*.snippet'),"\n") + let name = fnamemodify(f,':t:r') + let trigger = fnamemodify(f,':h:t') + " lazily read files as needed + call snipMate#SetByPath(result, [trigger, name], funcref#Function('return readfile('.string(f).')')) + endfor + endfor + endfor + return result +endf + + " vim:noet:sw=4:ts=4:ft=vim diff --git a/plugin/snipMate.vim b/plugin/snipMate.vim index 9df0843e..af8cbe34 100644 --- a/plugin/snipMate.vim +++ b/plugin/snipMate.vim @@ -18,47 +18,18 @@ if !exists('snips_author') | let snips_author = 'Me' | endif au BufRead,BufNewFile *.snippets\= set ft=snippet au FileType snippet setl noet fdm=indent -" bind local dict to global dict (debugging purposes) -" you should use MakeSnip to add custom snippets -if !exists('g:multi_snips') - let g:multi_snips = {} +" config which can be overridden (shared lines) +if !exists('g:snipMate') + let g:snipMate = {} endif -let s:multi_snips = g:multi_snips +let s:snipMate = g:snipMate + +let s:snipMate['get_snippets'] = get(s:snipMate, 'get_snippets', funcref#Function("snipMate#GetSnippets")) if !exists('snippets_dir') let snippets_dir = substitute(globpath(&rtp, 'snippets/'), "\n", ',', 'g') endif -fun! MakeSnip(scope, trigger, content, ...) - let description = a:0 > 0 ? a:1 : "default" - if description == "" - let description = "default" - endif - let s:multi_snips[a:scope] = get(s:multi_snips, a:scope, {}) - let scopeDict = s:multi_snips[a:scope] - let scopeDict[a:trigger] = get(scopeDict, a:trigger, {}) - let triggerDict = scopeDict[a:trigger] - if has_key(triggerDict, description) - echom 'Warning in snipMate.vim: Snippet '.a:trigger.' is already defined.' - \ .' See :h multi_snip for help on snippets with multiple matches.' - endif - " override existing snippet. User may have reloaded something - let triggerDict[description] = a:content -endf - -fun! ExtractSnips(dir, ft) - for path in split(globpath(a:dir, '*'), "\n") - if isdirectory(path) - let pathname = fnamemodify(path, ':t') - for snipFile in split(globpath(path, '*.snippet'), "\n") - call s:ProcessFile(snipFile, a:ft, pathname) - endfor - elseif fnamemodify(path, ':e') == 'snippet' - call s:ProcessFile(path, a:ft) - endif - endfor -endf - " Processes a single-snippet file; optionally add the name of the parent " directory for a snippet with multiple matches. fun s:ProcessFile(file, ft, ...) @@ -73,88 +44,6 @@ fun s:ProcessFile(file, ft, ...) \ : MakeSnip(a:ft, keyword, text) endf -fun! ExtractSnipsFile(file, ft) - if !filereadable(a:file) | return | endif - let text = readfile(a:file) - let inSnip = 0 - for line in text + ["\n"] - if inSnip && (line[0] == "\t" || line == '') - let content .= strpart(line, 1)."\n" - continue - elseif inSnip - call MakeSnip(a:ft, trigger, content[:-2], name) - let inSnip = 0 - endif - - if line[:6] == 'snippet' - let inSnip = 1 - let trigger = strpart(line, 8) - let name = '' - let space = stridx(trigger, ' ') + 1 - if space " Process multi snip - let name = strpart(trigger, space) - let trigger = strpart(trigger, 0, space - 1) - endif - let content = '' - endif - endfor -endf - -" Reset snippets for filetype. -fun! ResetSnippets(ft) - let ft = a:ft == '' ? '_' : a:ft - for dict in [s:multi_snips, g:did_ft] - if has_key(dict, ft) - unlet dict[ft] - endif - endfor -endf - -" Reset snippets for all filetypes. -fun! ResetAllSnippets() - let s:multi_snips = {} | let g:did_ft = {} -endf - -" Reload snippets for filetype. -fun! ReloadSnippets(ft) - let ft = a:ft == '' ? '_' : a:ft - call ResetSnippets(ft) - call GetSnippets(g:snippets_dir, ft) -endf - -" Reload snippets for all filetypes. -fun! ReloadAllSnippets() - for ft in keys(g:did_ft) - call ReloadSnippets(ft) - endfor -endf - -let g:did_ft = {} -fun! GetSnippets(dir, filetypes) - for ft in split(a:filetypes, '\.') - if has_key(g:did_ft, ft) | continue | endif - call s:DefineSnips(a:dir, ft, ft) - if ft == 'objc' || ft == 'cpp' || ft == 'cs' - call s:DefineSnips(a:dir, 'c', ft) - elseif ft == 'xhtml' - call s:DefineSnips(a:dir, 'html', 'xhtml') - endif - let g:did_ft[ft] = 1 - endfor -endf - -" Define "aliasft" snippets for the filetype "realft". -fun s:DefineSnips(dir, aliasft, realft) - for path in split(globpath(a:dir, a:aliasft.'/')."\n". - \ globpath(a:dir, a:aliasft.'-*/'), "\n") - call ExtractSnips(path, a:realft) - endfor - for path in split(globpath(a:dir, a:aliasft.'.snippets')."\n". - \ globpath(a:dir, a:aliasft.'-*.snippets'), "\n") - call ExtractSnipsFile(path, a:realft) - endfor -endf - fun! TriggerSnippet() if exists('g:SuperTabMappingForward') if g:SuperTabMappingForward == "" @@ -217,8 +106,10 @@ endf fun s:GetSnippet(word, scope) let word = a:word | let snippet = '' while snippet == '' - if exists('s:multi_snips["'.a:scope.'"]["'.escape(word, '\"').'"]') - let snippet = s:ChooseSnippet(a:scope, word) + let snippetD = get(snipMate#GetSnippets([a:scope], word),word, {}) + if !empty(snippetD) + let s = s:ChooseSnippet(snippetD) + let snippet = s if snippet == '' | break | endif else if match(word, '\W') == -1 | break | endif @@ -231,10 +122,11 @@ fun s:GetSnippet(word, scope) return [word, snippet] endf -fun s:ChooseSnippet(scope, trigger) +" snippets: dict containing snippets by name +" usually this is just {'default' : snippet_contents } +fun s:ChooseSnippet(snippets) let snippet = [] - let triggerDict = get(s:multi_snips[a:scope], a:trigger, {}) - let keys = keys(triggerDict) + let keys = keys(a:snippets) let i = 1 for snip in keys let snippet += [i.'. '.snip] @@ -245,7 +137,12 @@ fun s:ChooseSnippet(scope, trigger) else let idx = inputlist(snippet) - 1 endif - return idx == -1 ? '' : triggerDict[keys[idx]] + " if a:snippets[..] is a String Call returns it + " If it's a function or a function string the result is returned + if idx == -1 + return '' + endif + return funcref#Call(a:snippets[keys(a:snippets)[idx]]) endf fun! ShowAvailableSnips() @@ -258,17 +155,16 @@ fun! ShowAvailableSnips() endif let matchlen = 0 let matches = [] - for scope in [bufnr('%')] + split(&ft, '\.') + ['_'] - for trigger in keys(get(s:multi_snips, scope, {})) - for word in words - if word == '' - let matches += [trigger] " Show all matches if word is empty - elseif trigger =~ '^'.word - let matches += [trigger] - let len = len(word) - if len > matchlen | let matchlen = len | endif - endif - endfor + let snips = snipMate#GetSnippets([bufnr('%')] + split(&ft, '\.') + ['_'], word.'*') + for trigger in keys(snips) + for word in words + if word == '' + let matches += [trigger] " Show all matches if word is empty + elseif trigger =~ '^'.word + let matches += [trigger] + let len = len(word) + if len > matchlen | let matchlen = len | endif + endif endfor endfor diff --git a/snipMate-addon-info.txt b/snipMate-addon-info.txt new file mode 100644 index 00000000..a440df95 --- /dev/null +++ b/snipMate-addon-info.txt @@ -0,0 +1,10 @@ +{ + "name" : "snipMate", + "author" : "Michael Sanders -> original project http://github.com/msanders/snipmate.vim", + "maintainer" : "Marc Weber (I mantain this fork only)", + "repository" : {"type": "git", "url": "git://github.com/MarcWeber/snipMate.vim.git"}, + "dependencies" : { + "vim-addon-mw-utils": {} + }, + "description" : "TextMate-style snippets for Vim - this fork loads snippets on demand lazily thus no need to reload any snippets ever" +}