aboutsummaryrefslogtreecommitdiff
path: root/vim/bundle/vim-snipmate/autoload/snipMate.vim
blob: 74366fa8074350bbe7d4bdaace0a4225017281ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
" config which can be overridden (shared lines)
if !exists('g:snipMate')
  let g:snipMate = {}
endif

try
	call tlib#input#List('mi', '', [])
catch /.*/
	echoe "you're missing tlib. See install instructions at ".expand('<sfile>:h:h').'/README.md'
endtry

fun! Filename(...) abort
	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

let s:cache = {}

function! snipMate#expandSnip(snip, version, col) abort
	let lnum = line('.')
	let col = a:col
	let line = getline(lnum)
	let indent = match(line, '\S\|$') + 1
	let b:snip_state = snipmate#jumping#state()

	if a:version == 1
		let [snippet, b:snip_state.stops] = snipmate#parse#snippet(a:snip)
		" Build stop/mirror info
		let b:snip_state.stop_count = s:build_stops(snippet, b:snip_state.stops, lnum, col, indent)
		let snipLines = map(copy(snippet),
					\ 'snipMate#sniplist_str(v:val, b:snip_state.stops)')
	else
		let snippet = snipmate#legacy#process_snippet(a:snip)
		let [b:snip_state.stops, b:snip_state.stop_count] = snipmate#legacy#build_stops(snippet, lnum, col - indent, indent)
		let snipLines = split(substitute(snippet, printf('%s\d\+\|%s{\d\+.\{-}}',
					\ g:snipmate#legacy#sigil, g:snipmate#legacy#sigil), '', 'g'), "\n", 1)
	endif

	" Abort if the snippet is empty
	if empty(snippet)
		return ''
	endif

	" Expand snippet onto current position
	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
	else
		let afterCursor = ''
		" For some reason the cursor needs to move one right after this
		if line != '' && col == 1 && &ve != 'all' && &ve != 'onemore'
			let col += 1
		endif
	endif

	" Insert snippet with proper indentation
	call setline(lnum, line . snipLines[0])
	call append(lnum, map(snipLines[1:], "empty(v:val) ? v:val : '" . strpart(line, 0, indent - 1) . "' . v:val"))

	" Open any folds snippet expands into
	if &foldenable
		silent! exec lnum . ',' . (lnum + len(snipLines) - 1) . 'foldopen'
	endif

	aug snipmate_changes
		au CursorMoved,CursorMovedI <buffer> if exists('b:snip_state') |
					\     call b:snip_state.update_changes() |
					\ else |
					\     silent! au! snipmate_changes * <buffer> |
					\ endif
	aug END

	let b:snip_state.stop_no = 0
	return b:snip_state.set_stop(0)
endfunction

function! snipMate#placeholder_str(num, stops) abort
	return snipMate#sniplist_str(a:stops[a:num].placeholder, a:stops)
endfunction

function! snipMate#sniplist_str(snippet, stops) abort
	let str = ''
	let pos = 0
	let add_to = 1
	let seen_stops = []

	while pos < len(a:snippet)
		let item = a:snippet[pos]

		if type(item) == type('')
			let str .= item
		elseif type(item) == type([])
			let str .= snipMate#placeholder_str(item[0], a:stops)
		endif

		let pos += 1
		unlet item " avoid E706
	endwhile

	return str
endfunction

function! s:build_stops(snippet, stops, lnum, col, indent) abort
	let stops = a:stops
	let lnum  = a:lnum
	let col   = a:col

	for line in a:snippet
		let col = s:build_loc_info(line, stops, lnum, col, [])
		if line isnot a:snippet[-1]
			let lnum += 1
			let col = a:indent
		endif
	endfor

	" add zero tabstop if it doesn't exist and then link it to the highest stop
	" number
	let stops[0] = get(stops, 0,
				\ { 'placeholder' : [], 'line' : lnum, 'col' : col })
	let stop_count = max(keys(stops)) + 2
	let stops[stop_count - 1] = stops[0]

	return stop_count
endfunction

function! s:build_loc_info(snippet, stops, lnum, col, seen_items) abort
	let stops   = a:stops
	let lnum    = a:lnum
	let col     = a:col
	let pos     = 0
	let in_text = 0
	let seen_items = a:seen_items

	for item in a:snippet
		if type(item) == type('')
			let col += len(item)
		elseif type(item) == type([])
			let id = item[0]
			let stub = item[-1]
			let stub.line = lnum
			let stub.col = col
			call s:add_update_objects(stub, seen_items)

			if len(item) > 2 && type(item[1]) != type({})
				let col = s:build_loc_info(item[1:-2], stops, lnum, col, seen_items)
			else
				let col += len(snipMate#placeholder_str(id, stops))
			endif

			let in_text = 0
		endif
		unlet item " avoid E706
	endfor

	return col
endfunction

function! s:add_update_objects(object, targets) abort
	let targets = a:targets

	for item in targets
		let item.update_objects = get(item, 'update_objects', [])
		call add(item.update_objects, a:object)
	endfor

	call add(targets, a:object)
endfunction

" reads a .snippets file
" returns list of
" ['triggername', 'name', 'contents']
" if triggername is not set 'default' is assumed
" TODO: better error checking
fun! snipMate#ReadSnippetsFile(file) abort
	let result = []
	let new_scopes = []
	if !filereadable(a:file) | return [result, new_scopes] | endif
	let inSnip = 0
	let line_no = 0
	let snipversion = get(g:snipMate, 'snippet_version', 0)
	for line in readfile(a:file) + ["\n"]
		let line_no += 1

		if inSnip && (line[0] == "\t" || line == '')
			let content .= strpart(line, 1)."\n"
			continue
		elseif inSnip
			call add(result, [trigger, name,
						\     content[:-2], bang, snipversion])
			let inSnip = 0
		endif

		if line[:6] == 'snippet'
			let inSnip = 1
			let bang = (line[7] == '!')
			if bang
				let bang += line[8] == '!'
			endif
			let trigger = strpart(line, 8 + bang)
			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 = ''
			if trigger =~ '^\s*$' " discard snippets with empty triggers
				echom 'Invalid snippet in' a:file 'near line' line_no
				let inSnip = 0
			endif
		elseif line[:6] == 'extends'
			call extend(new_scopes, map(split(strpart(line, 8)),
						\ "substitute(v:val, ',*$', '', '')"))
		elseif line[:6] == 'version'
			let snipversion = +strpart(line, 8)
		endif
	endfor
	return [result, new_scopes]
endf

function! s:GetScopes() abort
	let ret = exists('b:snipMate.scope_aliases') ? copy(b:snipMate.scope_aliases) : {}
	let global = get(g:snipMate, 'scope_aliases', {})
	for alias in keys(global)
		if has_key(ret, alias)
			let ret[alias] = join(split(ret[alias], ',')
						\ + split(global[alias], ','), ',')
		else
			let ret[alias] = global[alias]
		endif
	endfor
	return ret
endfunction

" adds scope aliases to list.
" returns new list
" the aliases of aliases are added recursively
fun! s:AddScopeAliases(list) abort
  let did = {}
  let scope_aliases = s:GetScopes()
  let new = a:list
  let new2 =  []
  while !empty(new)
	for i in new
	  if !has_key(did, i)
		let did[i] = 1
		call extend(new2, split(get(scope_aliases,i,''),','))
	  endif
	endfor
	let new = new2
	let new2 = []
  endwhile
  return keys(did)
endf

augroup SnipMateSource
	au SourceCmd *.snippet,*.snippets call s:source_snippet()
augroup END

function! s:info_from_filename(file) abort
	let parts = split(fnamemodify(a:file, ':r'), '/')
	let snipidx = len(parts) - index(reverse(copy(parts)), 'snippets') - 1
	let rtp_prefix = join(parts[(snipidx -
				\ (parts[snipidx - 1] == 'after' ? 3 : 2)):snipidx - 1], '/')
	let trigger = get(parts, snipidx + 2, '')
	let desc = get(parts, snipidx + 3, get(g:snipMate, 'override', 0) ?
				\ '' : fnamemodify(a:file, ':t'))
	return [rtp_prefix, trigger, desc]
endfunction

function! s:source_snippet() abort
	let file = expand('<afile>:p')
	let [rtp_prefix, trigger, desc] = s:info_from_filename(file)
	let new_snips = []
	if fnamemodify(file, ':e') == 'snippet'
		call add(new_snips, [trigger, desc, join(readfile(file), "\n"), 0,
					\ get(g:snipMate, 'snippet_version', 0)])
	else
		let [snippets, extends] = s:CachedSnips(file)
		let new_snips = deepcopy(snippets)
		call extend(s:lookup_state.extends, extends)
	endif
	for snip in new_snips
		if get(g:snipMate, 'override', 0)
			let snip[1] = join([s:lookup_state.scope, snip[1]])
		else
			let snip[1] = join([s:lookup_state.scope, rtp_prefix,
						\ empty(snip[1]) ? desc : snip[1]])
		endif
	endfor
	call extend(s:lookup_state.snips, new_snips)
endfunction

function! s:CachedSnips(file) abort
	let mtime = getftime(a:file)
	if has_key(s:cache, a:file) && s:cache[a:file].mtime >= mtime
		return s:cache[a:file].contents
	endif
	let s:cache[a:file] = {}
	let s:cache[a:file].mtime = mtime
	let s:cache[a:file].contents = snipMate#ReadSnippetsFile(a:file)
	return s:cache[a:file].contents
endfunction

function! s:snippet_filenames(scope, trigger) abort
	let mid = ['', '_*', '/*']
	let mid += map(copy(mid), "'/' . a:trigger . '*' . v:val")
	call map(mid, "'snippets/' . a:scope . v:val . '.snippet'")
	return map(mid[:2], 'v:val . "s"') + mid[3:]
endfunction

function! snipMate#SetByPath(dict, trigger, path, snippet, bang, snipversion) abort
	let d = a:dict
	if a:bang == 2
		unlet! d[a:trigger]
		return
	elseif !has_key(d, a:trigger) || a:bang == 1
		let d[a:trigger] = {}
	endif
	let d[a:trigger][a:path] = [a:snippet, a:snipversion]
endfunction

if v:version < 704 || has('win32')
	function! s:Glob(path, expr)
		let res = []
		for p in split(a:path, ',')
			let h = split(fnamemodify(a:expr, ':h'), '/')[0]
			if isdirectory(p . '/' . h)
				call extend(res, split(glob(p . '/' . a:expr), "\n"))
			endif
		endfor
		return filter(res, 'filereadable(v:val)')
	endfunction
else
	function! s:Glob(path, expr)
		return split(globpath(a:path, a:expr), "\n")
	endfunction
endif

" default triggers based on paths
function! snipMate#DefaultPool(scopes, trigger, result) abort
	let scopes = s:AddScopeAliases(a:scopes)
	let scopes_done = []
	let s:lookup_state = {}
	let s:lookup_state.snips = []

	while !empty(scopes)
		let scope = remove(scopes, 0)
		let s:lookup_state.scope = scope
		let s:lookup_state.extends = []

		for expr in s:snippet_filenames(scope, escape(a:trigger, "*[]?{}`'$|#%"))
			for path in g:snipMate.snippet_dirs
				for file in s:Glob(path, expr)
					source `=file`
				endfor
			endfor
		endfor

		call add(scopes_done, scope)
		call extend(scopes, s:lookup_state.extends)
		call filter(scopes, 'index(scopes_done, v:val) == -1')
	endwhile

	for [trigger, desc, contents, bang, snipversion] in s:lookup_state.snips
		if trigger =~ '\V\^' . escape(a:trigger, '\')
			call snipMate#SetByPath(a:result, trigger, desc, contents, bang, snipversion)
		endif
	endfor
endfunction

" 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) abort
	let result = {}

	for F in values(g:snipMateSources)
	  call funcref#Call(F, [a:scopes, a:trigger, result])
	endfor
	return result
endf

function! snipMate#OpenSnippetFiles() abort
	let files = []
	let scopes_done = []
	let exists = []
	let notexists = []
	for scope in s:AddScopeAliases(snipMate#ScopesByFile())
		let files += s:snippet_filenames(scope, '')
	endfor
	call filter(files, "v:val !~# '\\*'")
	for path in g:snipMate.snippet_dirs
		let fullpaths = map(copy(files), 'printf("%s/%s", path, v:val)')
		let exists += filter(copy(fullpaths), 'filereadable(v:val)')
		let notexists += map(filter(copy(fullpaths),
					\ 'v:val =~# "\.snippets" && !filereadable(v:val)'),
					\       '"does not exist: " . v:val')
	endfor
	let all = exists + notexists
	let select = tlib#input#List('mi', 'select files to be opened in splits', all)
	for idx in select
		exec 'sp' all[idx - 1]
	endfor
endfunction

fun! snipMate#ScopesByFile() abort
	" duplicates are removed in AddScopeAliases
	return filter(funcref#Call(g:snipMate.get_scopes), "v:val != ''")
endf

" used by both: completion and insert snippet
fun! snipMate#GetSnippetsForWordBelowCursor(word, exact) abort
	" Split non-word characters into their own piece
	" so 'foo.bar..baz' becomes ['foo', '.', 'bar', '.', '.', 'baz']
	" First split just after a \W and then split each resultant string just
	" before a \W
	let parts = filter(tlib#list#Flatten(
				\ map(split(a:word, '\W\zs'), 'split(v:val, "\\ze\\W")')),
				\ '!empty(v:val)')
	" Only look at the last few possibilities. Too many can be slow.
	if len(parts) > 5
		let parts = parts[-5:]
	endif
	let lookups = [a:word]
	let lookup = ''
	for w in reverse(parts)
		let lookup = w . lookup
		if index(lookups, lookup) == -1
			call add(lookups, lookup)
		endif
	endfor

	" Remove empty lookup entries, but only if there are other nonempty lookups
	if len(lookups) > 1
		call filter(lookups, 'v:val != ""')
	endif

	let matching_snippets = []
	let snippet = ''
	" prefer longest word
	for word in lookups
		let g:snipMate.word = word
		for [k,snippetD] in items(funcref#Call(g:snipMate['get_snippets'], [snipMate#ScopesByFile(), word]))
			" hack: require exact match
			if a:exact && k !=# word
				continue
			endif
			call add(matching_snippets, [k, snippetD])
			if a:exact
				break
			endif
		endfor
	endfor
	return matching_snippets
endf

" snippets: dict containing snippets by name
" usually this is just {'default' : snippet_contents }
fun! s:ChooseSnippet(snippets) abort
	let snippet = []
	let keys = keys(a:snippets)
	let i = 1
	for snip in keys
		let snippet += [i.'. '.snip]
		let i += 1
	endfor
	if len(snippet) == 1
		" there's only a single snippet, choose it
		let idx = 0
	else
		let idx = tlib#input#List('si','select snippet by name',snippet) -1
		if idx == -1
			return ''
		endif
	endif
	" if a:snippets[..] is a String Call returns it
	" If it's a function or a function string the result is returned
	return funcref#Call(a:snippets[keys(a:snippets)[idx]])
endf

fun! snipMate#WordBelowCursor() abort
	return matchstr(getline('.'), '\S\+\%' . col('.') . 'c')
endf

fun! snipMate#GetSnippetsForWordBelowCursorForComplete(word) abort
	let snippets = map(snipMate#GetSnippetsForWordBelowCursor(a:word, 0), 'v:val[0]')
	return filter(snippets, 'v:val =~# "\\V\\^' . escape(a:word, '"\') . '"')
endf

fun! snipMate#CanBeTriggered() abort
	let word    = snipMate#WordBelowCursor()
	let matches = snipMate#GetSnippetsForWordBelowCursorForComplete(word)
	return len(matches) > 0
endf

fun! snipMate#ShowAvailableSnips() abort
	let col     = col('.')
	let word    = snipMate#WordBelowCursor()
	let matches = snipMate#GetSnippetsForWordBelowCursorForComplete(word)

	" Pretty hacky, but really can't have the tab swallowed!
	if len(matches) == 0
		call feedkeys(g:snipMate['no_match_completion_feedkeys_chars'], 'n')
		return ""
	endif

	call complete(col - len(word), sort(matches))
	return ''
endf

" Pass an argument to force snippet expansion instead of triggering or jumping
function! snipMate#TriggerSnippet(...) abort
	if exists('g:SuperTabMappingForward')
		if g:SuperTabMappingForward == "<tab>"
			let SuperTabPlug = maparg('<Plug>SuperTabForward', 'i')
			if SuperTabPlug == ""
				let SuperTabKey = "\<c-n>"
			else
				exec "let SuperTabKey = \"" . escape(SuperTabPlug, '<') . "\""
			endif
		elseif g:SuperTabMappingBackward == "<tab>"
			let SuperTabPlug = maparg('<Plug>SuperTabBackward', 'i')
			if SuperTabPlug == ""
				let SuperTabKey = "\<c-p>"
			else
				exec "let SuperTabKey = \"" . escape(SuperTabPlug, '<') . "\""
			endif
		endif
	endif

	if pumvisible() " Update snippet if completion is used, or deal with supertab
		if exists('SuperTabKey')
			call feedkeys(SuperTabKey) | return ''
		endif
		call feedkeys("\<esc>a", 'n') " Close completion menu
		call feedkeys("\<tab>") | return ''
	endif

	if exists('b:snip_state') && a:0 == 0 " Jump only if no arguments
		let jump = b:snip_state.jump_stop(0)
		if type(jump) == 1 " returned a string
			return jump
		endif
	endif

	let word = matchstr(getline('.'), '\S\+\%'.col('.').'c')
	let list = snipMate#GetSnippetsForWordBelowCursor(word, 1)
	if empty(list)
		let snippet = ''
	else
		let [trigger, snippetD] = list[0]
		let snippet = s:ChooseSnippet(snippetD)
		" Before expanding snippet, create new undo point |i_CTRL-G|
		let &undolevels = &undolevels
		let col = col('.') - len(trigger)
		sil exe 's/\V'.escape(trigger, '/\.').'\%#//'
		return snipMate#expandSnip(snippet[0], snippet[1], col)
	endif

	" should allow other plugins to register hooks instead (duplicate code)
	if exists('SuperTabKey')
		call feedkeys(SuperTabKey)
		return ''
	endif
	return word == ''
	  \ ? "\<tab>"
	  \ : "\<c-r>=snipMate#ShowAvailableSnips()\<cr>"
endfunction

fun! snipMate#BackwardsSnippet() abort
	if exists('b:snip_state') | return b:snip_state.jump_stop(1) | endif

	if exists('g:SuperTabMappingForward')
		if g:SuperTabMappingForward == "<s-tab>"
			let SuperTabPlug = maparg('<Plug>SuperTabForward', 'i')
			if SuperTabPlug == ""
				let SuperTabKey = "\<c-n>"
			else
				exec "let SuperTabKey = \"" . escape(SuperTabPlug, '<') . "\""
			endif
		elseif g:SuperTabMappingBackward == "<s-tab>"
			let SuperTabPlug = maparg('<Plug>SuperTabBackward', 'i')
			if SuperTabPlug == ""
				let SuperTabKey = "\<c-p>"
			else
				exec "let SuperTabKey = \"" . escape(SuperTabPlug, '<') . "\""
			endif
		endif
	endif
	" should allow other plugins to register hooks instead (duplicate code)
	if exists('SuperTabKey')
		call feedkeys(SuperTabKey)
		return ''
	endif
	return "\<s-tab>"
endf

" vim:noet:sw=4:ts=4:ft=vim