Fix missing slashes in short dir.
[profile.git] / .vim / autoload / speeddating.vim
1 " Location:     autoload/speeddating.vim
2
3 " Initialization {{{1
4
5 if !exists("g:loaded_speeddating") || &cp || v:version < 700
6   finish
7 endif
8
9 let s:cpo_save = &cpo
10 set cpo&vim
11
12 let s:install_dir = expand("<sfile>:p:h:h")
13
14 " }}}1
15 " Utility Functions {{{1
16
17 function! s:function(name)
18   return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '.*\zs<SNR>\d\+_'),''))
19 endfunction
20
21 " In Vim, -4 % 3 == -1.  Let's return 2 instead.
22 function! s:mod(a,b)
23   if (a:a < 0 && a:b > 0 || a:a > 0 && a:b < 0) && a:a % a:b != 0
24     return (a:a % a:b) + a:b
25   else
26     return a:a % a:b
27   endif
28 endfunction
29
30 " In Vim, -4 / 3 == -1.  Let's return -2 instead.
31 function! s:div(a,b)
32   if a:a < 0 && a:b > 0
33     return (a:a-a:b+1)/a:b
34   elseif a:a > 0 && a:b < 0
35     return (a:a-a:b-1)/a:b
36   else
37     return a:a / a:b
38   endif
39 endfunction
40
41 function! s:match(...)
42   let b = call("match",a:000)
43   let e = call("matchend",a:000)
44   let s = call("matchlist",a:000)
45   if s == []
46     let s = ["","","","","","","","","",""]
47   endif
48   return [b,e] + s
49 endfunction
50
51 function! s:findatoffset(string,pattern,offset)
52   let line = a:string
53   let curpos = 0
54   let offset = a:offset
55   while strpart(line,offset,1) == " "
56     let offset += 1
57   endwhile
58   let [start,end,string;caps] = s:match(line,a:pattern,curpos,0)
59   while start >= 0
60     if offset >= start && offset < end
61       break
62     endif
63     let curpos = start + 1
64     let [start,end,string;caps] = s:match(line,a:pattern,curpos,0)
65   endwhile
66   return [start,end,string] + caps
67 endfunction
68
69 function! s:findinline(pattern)
70   return s:findatoffset(getline('.'),a:pattern,col('.')-1)
71 endfunction
72
73 function! s:replaceinline(start,end,new)
74   let line = getline('.')
75   let before_text = strpart(line,0,a:start)
76   let after_text = strpart(line,a:end)
77   " If this generates a warning it will be attached to an ugly backtrace.
78   " No warning at all is preferable to that.
79   silent call setline('.',before_text.a:new.after_text)
80   call setpos("'[",[0,line('.'),strlen(before_text)+1,0])
81   call setpos("']",[0,line('.'),a:start+strlen(a:new),0])
82 endfunction
83
84 " }}}1
85 " Normal Mode {{{1
86
87 function! speeddating#increment(increment)
88   for handler in s:time_handlers + g:speeddating_handlers
89     let pattern = type(handler.regexp) == type(function('tr')) ? handler.regexp() : handler.regexp
90     let [start,end,string;caps] = s:findinline('\C'.pattern)
91     if string != ""
92       let [repl,offset] = handler.increment(string,col('.')-1-start,a:increment)
93       if offset < 0
94         let offset += strlen(repl) + 1
95       endif
96       if repl != ""
97         call s:replaceinline(start,end,repl)
98         call setpos('.',[0,line('.'),start+offset,0])
99         silent! call repeat#set("\<Plug>SpeedDating" . (a:increment < 0 ? "Down" : "Up"),a:increment < 0 ? -a:increment : a:increment)
100         return
101       endif
102     endif
103   endfor
104   if a:increment > 0
105     exe "norm! ". a:increment."\<C-A>"
106   else
107     exe "norm! ".-a:increment."\<C-X>"
108   endif
109   silent! call repeat#set("\<Plug>SpeedDating" . (a:increment < 0 ? "Down" : "Up"),a:increment < 0 ? -a:increment : a:increment)
110 endfunction
111
112 " }}}1
113 " Visual Mode {{{1
114
115 function! s:setvirtcol(line,col)
116   call setpos('.',[0,a:line,a:col,0])
117   while virtcol('.') < a:col
118     call setpos('.',[0,a:line,col('.')+1,0])
119   endwhile
120   while virtcol('.') > a:col
121     call setpos('.',[0,a:line,col('.')-1,0])
122   endwhile
123   return col('.') + getpos('.')[3]
124 endfunction
125
126 function! s:chars(string)
127   return strlen(substitute(a:string,'.','.','g'))
128 endfunction
129
130 function! s:incrementstring(string,offset,count)
131   let repl = ""
132   let offset = -1
133   for handler in s:time_handlers + g:speeddating_handlers + s:visual_handlers
134     let pattern = type(handler.regexp) == type(function('tr')) ? handler.regexp() : handler.regexp
135     let [start,end,string;caps] = s:findatoffset(a:string,'\C'.pattern,a:offset)
136     if string != ""
137       let [repl,offset] = handler.increment(string,a:offset,a:count)
138       if repl != ""
139         break
140       endif
141     endif
142   endfor
143   if offset < 0
144     let offset += strlen(repl) + 1
145   endif
146
147   if repl != ""
148     let before_text = strpart(a:string,0,start)
149     let change = s:chars(repl) - s:chars(string)
150     if change < 0 && before_text !~ '\w$'
151       let offset -= change
152       let repl = repeat(' ',-change) . repl
153     elseif change > 0 && before_text =~ ' $'
154       let before_text = substitute(before_text,' \{1,'.change.'\}$','','')
155       let before_text = substitute(before_text,'\w$','& ','')
156       let start = strlen(before_text)
157     endif
158     let offset += start
159     let repl = before_text.repl.strpart(a:string,end)
160   endif
161   return [repl,offset,start,end]
162 endfunction
163
164 function! speeddating#incrementvisual(count)
165   let ve = &ve
166   set virtualedit=all
167   exe "norm! gv\<Esc>"
168   if &selection ==# 'exclusive' && getpos('.') == getpos("'>")
169     normal! h
170   endif
171   let vcol = virtcol('.')
172   let lnum = line("'<")
173   let lastrepl = ""
174   call s:setvirtcol(lnum,vcol)
175   call setpos("'[",[0,line("'<"),1,0])
176   while lnum <= line("'>")
177     call s:setvirtcol(lnum,vcol)
178     let [repl,offset,start,end] = s:incrementstring(getline('.'),col('.')-1,a:count)
179     if repl == "" && lastrepl != ""
180       call setpos(".",[0,lnum-1,laststart,0])
181       let start = s:setvirtcol(lnum,virtcol('.'))
182       call setpos(".",[0,lnum-1,lastend,0])
183       let end = s:setvirtcol(lnum,virtcol('.'))
184       call s:setvirtcol(lnum,vcol)
185       if strpart(getline('.'),start,end-start) =~ '^\s*$'
186         let before_padded = start == end ? '' : printf("%-".start."s",strpart(getline('.'),0,start))
187         let tweaked_line  = before_padded.strpart(lastrepl,laststart,lastend-laststart).strpart(getline('.'),end)
188         let [repl,offset,start,end] = s:incrementstring(tweaked_line,col('.')-1,a:count*(lnum-lastlnum))
189       endif
190     elseif repl != ""
191       let [lastrepl,laststart,lastend,lastlnum] = [repl,start,end,lnum]
192     endif
193     if repl != ""
194       silent call setline('.',repl)
195     endif
196     let lnum += 1
197   endwhile
198   let &ve = ve
199   call setpos("']",[0,line('.'),col('$'),0])
200 endfunction
201
202 " }}}1
203 " Visual Mode Handlers {{{1
204
205 let s:visual_handlers = []
206
207 function! s:numberincrement(string,offset,increment)
208   let n = (a:string + a:increment)
209   if a:string =~# '^0x.*[A-F]'
210     return [printf("0x%X",n),-1]
211   elseif a:string =~# '^0x'
212     return [printf("0x%x",n),-1]
213   elseif a:string =~# '^00*[^0]' && &nrformats =~# 'octal'
214     return [printf("0%o",n),-1]
215   elseif a:string =~# '^00*[^0]'
216     return [printf("%0".strlen(a:string)."d",n),-1]
217   else
218     return [printf("%d",n),-1]
219   endif
220 endfunction
221
222 let s:visual_handlers += [{'regexp': '-\=\<\%(0x\x\+\|\d\+\)\>', 'increment': s:function("s:numberincrement")}]
223
224 function! s:letterincrement(string,offset,increment)
225   return [nr2char((char2nr(toupper(a:string)) - char2nr('A') + a:increment) % 26 + (a:string =~# '[A-Z]' ? char2nr('A') : char2nr('a'))),-1]
226 endfunction
227
228 let s:visual_handlers += [{'regexp': '\<[A-Za-z]\>', 'increment': s:function("s:letterincrement")}]
229
230 " }}}1
231 " Ordinals {{{1
232
233 function! s:ordinalize(number)
234   let n = a:number
235   let a = n < 0 ? -n : +n
236   if a % 100 == 11 || a % 100 == 12 || a % 100 == 13
237     return n."th"
238   elseif a % 10 == 1
239     return n."st"
240   elseif a % 10 == 2
241     return n."nd"
242   elseif a % 10 == 3
243     return n."rd"
244   else
245     return n."th"
246   endif
247 endfunction
248
249 function! s:ordinalincrement(string,offset,increment)
250   return [s:ordinalize(a:string+a:increment),-1]
251 endfunction
252
253 let g:speeddating_handlers += [{'regexp': '-\=\<\d\+\%(st\|nd\|rd\|th\)\>', 'increment': s:function("s:ordinalincrement")}]
254
255 " }}}1
256 " Roman Numerals {{{1
257
258 " Based on similar functions from VisIncr.vim
259
260 let s:a2r = [[1000, 'm'], [900, 'cm'], [500, 'd'], [400, 'cd'], [100, 'c'],
261       \             [90 , 'xc'], [50 , 'l'], [40 , 'xl'], [10 , 'x'],
262       \             [9  , 'ix'], [5  , 'v'], [4  , 'iv'], [1  , 'i']]
263
264 function! s:roman2arabic(roman)
265   let roman  = tolower(a:roman)
266   let sign   = 1
267   let arabic = 0
268   while roman != ''
269     if roman =~ '^[-n]'
270       let sign = -sign
271     endif
272     for [numbers,letters] in s:a2r
273       if roman =~ '^'.letters
274         let arabic += sign * numbers
275         let roman = strpart(roman,strlen(letters)-1)
276         break
277       endif
278     endfor
279     let roman = strpart(roman,1)
280   endwhile
281
282   return arabic
283 endfunction
284
285 function! s:arabic2roman(arabic)
286   if a:arabic <= 0
287     let arabic = -a:arabic
288     let roman = "n"
289   else
290     let arabic = a:arabic
291     let roman = ""
292   endif
293   for [numbers, letters] in s:a2r
294     let roman .= repeat(letters,arabic/numbers)
295     let arabic = arabic % numbers
296   endfor
297   return roman
298 endfunction
299
300 " }}}1
301 " Time Helpers {{{1
302
303 function! s:ary2pat(array)
304   return '\%('.join(a:array,'\|').'\)'
305   return '\%('.join(map(copy(a:array),'substitute(v:val,"[[:alpha:]]","[\\u&\\l&]","g")'),'\|').'\)'
306 endfunction
307
308 function! s:initializetime(time)
309   call extend(a:time,{'y': '','b':1,'d':0,'h':0,'m':0,'s':0,'o':0,'k':0},"keep")
310   if get(a:time,'b','') !~ '^\d*$'
311     let full = index(s:months_full ,a:time.b,0,1) + 1
312     let engl = index(s:months_engl ,a:time.b,0,1) + 1
313     let abbr = index(s:months_abbr ,a:time.b,0,1) + 1
314     if full
315       let a:time.b = full
316     elseif engl
317       let a:time.b = engl
318     elseif abbr
319       let a:time.b = abbr
320     else
321       let a:time.b = 1
322     endif
323   endif
324   if has_key(a:time,'p')
325     let a:time.h = a:time.h % 12
326     if a:time.p ==? "PM"
327       let a:time.h += 12
328     endif
329     call remove(a:time,"p")
330   endif
331   if a:time.y !~ '^\d*$'
332     let a:time.y = s:roman2arabic(a:time.y)
333   elseif a:time.y =~ '^-\=0..'
334     let a:time.y = substitute(a:time.y,'0\+','','')
335   elseif a:time.y < 38 && a:time.y >= 0 && ''.a:time.y != ''
336     let a:time.y += 2000
337   elseif a:time.y < 100 && a:time.y >= 38
338     let a:time.y += 1900
339   endif
340   if has_key(a:time,'w')
341     let full = index(s:days_full,a:time.w,0,1)
342     let engl = index(s:days_engl,a:time.w,0,1)
343     let abbr = index(s:days_abbr,a:time.w,0,1)
344     let a:time.w = full > 0 ? full : (engl > 0 ? engl : (abbr > 0 ? abbr : a:time.w))
345     if a:time.d == 0
346       let a:time.d = s:mod(a:time.w - s:jd(a:time.y,a:time.b,1),7)
347     elseif a:time.y == '' && a:time.b * a:time.d > 0
348       let a:time.y = strftime("%Y")-2
349       while s:mod(s:jd(a:time.y,a:time.b,a:time.d)+1,7) != a:time.w
350         let a:time.y += 1
351       endwhile
352     endif
353     call remove(a:time,'w')
354   endif
355   if a:time.d == 0
356     let a:time.d = 1
357   endif
358   if ''.a:time.y == ''
359     let a:time.y = 2000
360   endif
361   if a:time.o =~ '^[+-]\d\d:\=\d\d$'
362     let a:time.o = (a:time.o[0]=="-" ? -1 : 1)*(a:time.o[1:2]*60+matchstr(a:time.o,'\d\d$'))
363   elseif get(a:time,'z','') == g:speeddating_zone
364     let a:time.o = s:offset
365   elseif get(a:time,'z','') == g:speeddating_zone_dst
366     let a:time.o = s:offset_dst
367   endif
368   return a:time
369 endfunction
370
371 " Julian day (always Gregorian calendar)
372 function! s:jd(year,mon,day)
373   let y = a:year + 4800 - (a:mon <= 2)
374   let m = a:mon + (a:mon <= 2 ? 9 : -3)
375   let jul = a:day + (153*m+2)/5 + s:div(1461*y,4) - 32083
376   return jul - s:div(y,100) + s:div(y,400) + 38
377 endfunction
378
379 function! s:gregorian(jd)
380   let l = a:jd + 68569
381   let n = s:div(4 * l, 146097)
382   let l = l - s:div(146097 * n + 3, 4)
383   let i = ( 4000 * ( l + 1 ) ) / 1461001
384   let l = l - ( 1461 * i ) / 4 + 31
385   let j = ( 80 * l ) / 2447
386   let d = l - ( 2447 * j ) / 80
387   let l = j / 11
388   let m = j + 2 - ( 12 * l )
389   let y = 100 * ( n - 49 ) + i + l
390   return {'y':y,'b':m,'d':d}
391 endfunction
392
393 function! s:normalizetime(time)
394   let a:time.y += s:div(a:time.b-1,12)
395   let a:time.b = s:mod(a:time.b-1,12)+1
396   let seconds = a:time.h * 3600 + a:time.m * 60 + a:time.s + s:div(a:time.k,1000)
397   let a:time.k = s:mod(a:time.k,1000)
398   let a:time.s = s:mod(seconds,60)
399   let a:time.m = s:mod(s:div(seconds,60),60)
400   let a:time.h = s:mod(s:div(seconds,3600),24)
401   if seconds != 0 || a:time.b != 1 || a:time.d != 1
402     let day = s:gregorian(s:jd(a:time.y,a:time.b,a:time.d)+s:div(seconds,86400))
403     return extend(a:time,day)
404   else
405     return a:time
406   endif
407 endfunction
408
409 function! s:applymodifer(number,modifier,width)
410   if a:modifier == '-'
411     return substitute(a:number,'^0*','','')
412   elseif a:modifier == '_'
413     return printf('%'.a:width.'d',a:number)
414   elseif a:modifier == '^'
415     return toupper(a:number)
416   else
417     return printf('%0'.a:width.'s',a:number)
418   endif
419 endfunction
420
421 function! s:modyear(y)
422   return printf('%02d',s:mod(a:y,100))
423 endfunction
424
425 function! s:strftime(pattern,time)
426   if type(a:time) == type({})
427     let time = s:normalizetime(copy(a:time))
428   else
429     let time = s:normalizetime(s:initializetime({'y':1970,'s':a:time}))
430   endif
431   let time.w = s:mod(s:jd(time.y,time.b,time.d)+1,7)
432   let time.p = time.h
433   let expanded = ""
434   let remaining = a:pattern
435   while remaining != ""
436     if remaining =~ '^%'
437       let modifier = matchstr(remaining,'%\zs[-_0^]\=\ze.')
438       let specifier = matchstr(remaining,'%[-_0^]\=\zs.')
439       let remaining = matchstr(remaining,'%[-_0^]\=.\zs.*')
440       if specifier == '%'
441         let expanded .= '%'
442       elseif has_key(s:strftime_items,specifier)
443         let item = s:strftime_items[specifier]
444         let number = time[item[1]]
445         if type(item[4]) == type([])
446           let expanded .= s:applymodifer(item[4][number % len(item[4])],modifier,1)
447         elseif type(item[4]) == type(function('tr'))
448           let expanded .= s:applymodifer(call(item[4],[number]),modifier,1)
449         else
450           let expanded .= s:applymodifer(number,modifier,item[4])
451         endif
452       else
453         let expanded .= '%'.modifier.specifier
454       endif
455     else
456       let expanded .= matchstr(remaining,'[^%]*')
457       let remaining = matchstr(remaining,'[^%]*\zs.*')
458     endif
459   endwhile
460   return expanded
461 endfunction
462
463 function! s:localtime(...)
464   let ts = a:0 ? a:1 : has('unix') ? reltimestr(reltime()) : localtime().'.0'
465   let us = matchstr(ts,'\.\zs.\{0,6\}')
466   let us .= repeat(0,6-strlen(us))
467   let us = +matchstr(us,'[1-9].*')
468   let time = {
469         \ 'y': +strftime('%Y',ts),
470         \ 'b': +strftime('%m',ts),
471         \ 'd': +strftime('%d',ts),
472         \ 'h': +strftime('%H',ts),
473         \ 'm': +strftime('%M',ts),
474         \ 's': +strftime('%S',ts),
475         \ 'k': us / 1000}
476   let jd = s:jd(time.y,time.b,time.d) - s:jd(1970,1,1)
477   let real_ts = jd * 86400 + time.h * 3600 + time.m * 60 + time.s
478   let time.o = (real_ts - ts) / 60
479   return time
480 endfunction
481
482 function! s:formattz(offset)
483   if a:offset < 0
484     let offset = -a:offset
485     let sign = "-"
486   else
487     let offset = a:offset
488     let sign = "+"
489   endif
490   return printf("%s%02d%02d",sign,offset/60,offset%60)
491 endfunction
492
493 " }}}1
494 " Time Data {{{1
495
496 let s:offset     = s:localtime((  0+30*365)*86400).o
497 if !exists("g:speeddating_zone")
498   let g:speeddating_zone = strftime("%Z",30*365*86400)
499   if g:speeddating_zone == ""
500     let g:speeddating_zone = get({-8:'PST',-7:'MST',-6:'CST',-5:'EST',0:'WET',1:'CET',2:'EET'},s:offset/60,"XST")
501   endif
502 endif
503
504 let s:offset_dst = s:localtime((180+30*365)*86400).o
505 if !exists("g:speeddating_zone_dst")
506   let g:speeddating_zone_dst = strftime("%Z",(180+30*365)*86400)
507   if g:speeddating_zone_dst == ""
508     if s:offset == s:offset_dst
509       let g:speeddating_zone_dst = g:speeddating_zone
510     else
511       let g:speeddating_zone_dst = get({-7:'PDT',-6:'MDT',-5:'CDT',-4:'EDT',1:'WEST',2:'CEST',3:'EEST'},s:offset_dst/60,"XDT")
512     endif
513   endif
514 endif
515
516 let s:days_engl   =["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]
517 let s:days_abbr   =map(range(86400*3+43200-s:offset*60,86400*12,86400),'strftime("%a",v:val)')[0:6]
518 let s:days_full   =map(range(86400*3+43200-s:offset*60,86400*12,86400),'strftime("%A",v:val)')[0:6]
519
520 let s:months_engl =["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]
521 let s:months_abbr =map(range(86400*2,86400*365,86400*31),'strftime("%b",v:val)')
522 let s:months_full =map(range(86400*2,86400*365,86400*31),'strftime("%B",v:val)')
523
524 let s:strftime_items = {
525       \ "a": ['d','w',s:ary2pat(s:days_abbr),   'weekday (abbreviation)',s:days_abbr],
526       \ "A": ['d','w',s:ary2pat(s:days_full),   'weekday (full name)',s:days_full],
527       \ "i": ['d','w',s:ary2pat(s:days_engl),   'weekday (English abbr)',s:days_engl],
528       \ "b": ['b','b',s:ary2pat(s:months_abbr), 'month (abbreviation)',[""]+s:months_abbr],
529       \ "B": ['b','b',s:ary2pat(s:months_full), 'month (full name)',[""]+s:months_full],
530       \ "h": ['b','b',s:ary2pat(s:months_engl), 'month (English abbr)',[""]+s:months_engl],
531       \ "d": ['d','d','[ 0-3]\=\d', 'day   (01-31)',2],
532       \ "H": ['h','h','[ 0-2]\=\d', 'hour  (00-23)',2],
533       \ "I": ['h','h','[ 0-2]\=\d', 'hour  (01-12)',['12', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11']],
534       \ "m": ['b','b','[ 0-1]\=\d', 'month (01-12)',2],
535       \ "M": ['m','m','[ 0-5]\=\d', 'minutes',2],
536       \ "o": ['d','d','[ 0-3]\=\d\%(st\|nd\|rd\|th\)','day  (1st-31st)',s:function("s:ordinalize")],
537       \ "P": ['h','p','[ap]m', 'am/pm',repeat(['am'],12) + repeat(['pm'],12)],
538       \ "S": ['s','s','[ 0-5]\=\d', 'seconds',2],
539       \ "v": ['y','y','[ivxlcdmn]\+','year (roman numerals)',s:function("s:arabic2roman")],
540       \ "y": ['y','y','\d\d','year  (00-99)',s:function("s:modyear")],
541       \ "Y": ['y','y','-\=\d\d\d\=\d\=','year',4],
542       \ "k": ['k','k','\d\d\d','milliseconds',3],
543       \ "z": ['o','o','[+-]\d\d\d\d','timezone offset',s:function("s:formattz")],
544       \ "Z": [' ','z','[A-Z]\{3,5}','timezone (incomplete)',3]}
545
546 " }}}1
547 " Time Handler {{{1
548
549 function! speeddating#timestamp(utc,count)
550   for handler in s:time_handlers
551     let [start,end,string;caps] = s:findinline('\C'.join(handler.groups,''))
552     if string != ""
553       let format = substitute(handler.strftime,'\\\([1-9]\)','\=caps[submatch(1)-1]','g')
554       if a:utc || a:count
555         let offset = (a:utc ? 1 : -1) * a:count * 15
556         let time = s:initializetime({'y':1970,'s':localtime()+offset*60,'o':offset})
557       else
558         let time = s:localtime()
559       endif
560       if a:utc && !a:count
561         let time.z = 'UTC'
562       elseif time.o == s:offset
563         let time.z = g:speeddating_zone
564       elseif time.o == s:offset_dst
565         let time.z = g:speeddating_zone_dst
566       elseif time.o == 0
567         let time.z = 'UTC'
568       else
569         let time.z = 'XXT'
570       endif
571       let newstring = s:strftime(format,time)
572       call s:replaceinline(start,end,newstring)
573       call setpos('.',[0,line('.'),start+strlen(newstring),0])
574       silent! call repeat#set("\<Plug>SpeedDatingNow".(a:utc ? "UTC" : "Local"),a:count)
575       return ""
576     endif
577   endfor
578   let [start,end,string;caps] = s:findinline('-\=\<\d\+\>')
579   if string != ""
580     let newstring = localtime() + (a:utc ? 1 : -1) * a:count * 60*15
581     call s:replaceinline(start,end,newstring)
582     call setpos('.',[0,line('.'),start+strlen(newstring),0])
583     silent! call repeat#set("\<Plug>SpeedDatingNow".(a:utc ? "UTC" : "Local"),a:count)
584   endif
585 endfunction
586
587 function! s:dateincrement(string,offset,increment) dict
588   let [start,end,string;caps] = s:match(a:string,'\C'.join(self.groups,''))
589   let string = a:string
590   let offset = a:offset
591   let cursor_capture = 1
592   let idx = 0
593   while idx < len(self.groups)
594     let partial_matchend = matchend(string,join(self.groups[0:idx],''))
595     if partial_matchend > offset
596       break
597     endif
598     let idx += 1
599   endwhile
600   while get(self.targets,idx,"") == " "
601     let idx += 1
602   endwhile
603   while get(self.targets,idx," ") == " "
604     let idx -= 1
605   endwhile
606   let partial_pattern = join(self.groups[0:idx],'')
607   let char = self.targets[idx]
608   let i = 0
609   let time = {}
610   for cap in caps
611     if get(self.reader,i," ") !~ '^\s\=$'
612       let time[self.reader[i]] = substitute(cap,'^\s*','','')
613     endif
614     let i += 1
615   endfor
616   call s:initializetime(time)
617   let inner_offset = 0
618   if char == 'o'
619     let inner_offset = partial_matchend - offset - 1
620     let factor = 15
621     if inner_offset <= 0
622       let inner_offset = 0
623       let factor = 1
624     elseif inner_offset > 1
625       let factor = 60
626       let inner_offset = 2
627     endif
628     let time.o += factor * a:increment
629     let time.m += factor * a:increment
630   elseif char == 'b'
631     let time.b += a:increment
632     let goal = time.y*12 + time.b
633     call s:normalizetime(time)
634     while time.y*12 + time.b > goal
635       let time.d -= 1
636       call s:normalizetime(time)
637     endwhile
638   else
639     let time[char] += a:increment
640   endif
641   let format = substitute(self.strftime,'\\\([1-9]\)','\=caps[submatch(1)-1]','g')
642   let time_string = s:strftime(format,time)
643   return [time_string, matchend(time_string,partial_pattern)-inner_offset]
644 endfunction
645
646 function! s:timeregexp() dict
647   return join(self.groups,'')
648 endfunction
649
650 function! s:createtimehandler(format)
651   let pattern = '^\%(%?\=\[.\{-\}\]\|%[-_0^]\=.\|[^%]*\)'
652   let regexp = ['\%(\<\|-\@=\)']
653   let reader = []
654   let targets = [' ']
655   let template = ""
656   let default = ""
657   let remaining = substitute(a:format,'\C%\@<!%p','%^P','g')
658   let group = 0
659   let usergroups = []
660   let userdefaults = []
661   while remaining != ""
662     let fragment  = matchstr(remaining,pattern)
663     let remaining = matchstr(remaining,pattern.'\zs.*')
664     if fragment =~ '^%\*\W'
665       let suffix = '*'
666       let fragment = '%' . strpart(fragment,2)
667     elseif fragment =~ '^%?\W'
668       let suffix = '\='
669       let fragment = '%' . strpart(fragment,2)
670     else
671       let suffix = ''
672     endif
673     let targets += [' ']
674     if fragment =~ '^%' && has_key(s:strftime_items,matchstr(fragment,'.$'))
675       let item = s:strftime_items[matchstr(fragment,'.$')]
676       let modifier = matchstr(fragment,'^%\zs.\ze.$')
677       let targets[-1] = item[0]
678       let reader += [item[1]]
679       if modifier == '^'
680         let pat = substitute(item[2],'\C\\\@<![[:lower:]]','\u&','g')
681       elseif modifier == '0'
682         let pat = substitute(item[2],' \|-\@<!\\=','','g')
683       else
684         let pat = item[2]
685       endif
686       let regexp += ['\('.pat.'\)']
687       let group += 1
688       let template .= fragment
689       let default .= fragment
690     elseif fragment =~ '^%\[.*\]$'
691       let reader += [' ']
692       let regexp += ['\('.matchstr(fragment,'\[.*').suffix.'\)']
693       let group += 1
694       let usergroups += [group]
695       let template .= "\\".group
696       if suffix == ""
697         let default .= strpart(fragment,2,1)
698         let userdefaults += [strpart(fragment,2,1)]
699       else
700         let userdefaults += [""]
701       endif
702     elseif fragment =~ '^%\d'
703       let regexp += ["\\".usergroups[strpart(fragment,1)-1]]
704       let template .= regexp[-1]
705       let default .= userdefaults[strpart(fragment,1)-1]
706     elseif fragment == '%*'
707       if len(regexp) == 1
708         let regexp = []
709         let targets = []
710       else
711         let regexp += ['\(.*\)']
712       endif
713     else
714       let regexp += [escape(fragment,'.*^$[\]~')]
715       let template .= fragment
716       let default .= fragment
717     endif
718   endwhile
719   if regexp[-1] == '\(.*\)'
720     call remove(regexp,-1)
721     call remove(targets,-1)
722   else
723     let regexp += ['\>']
724   endif
725   return {'source': a:format, 'strftime': template, 'groups': regexp, 'regexp': s:function('s:timeregexp'), 'reader': reader, 'targets': targets, 'default': default, 'increment': s:function('s:dateincrement')}
726 endfunction
727
728 function! s:comparecase(i1, i2)
729   if a:i1 ==? a:i2
730     return a:i1 ==# a:i2 ? 0 : a:i1 ># a:i2 ? 1 : -1
731   else
732     return tolower(a:i1) > tolower(a:i2) ? 1 : -1
733   endif
734 endfunction
735
736 function! speeddating#loadformats()
737   if exists("g:speeddating_loaded_formats")
738     return
739   endif
740
741   for fmt in g:speeddating_formats
742     call speeddating#adddate( fmt[0], fmt[1], fmt[2] )
743   endfor
744   let g:speeddating_loaded_formats = 1
745 endfunction
746
747 function! speeddating#adddate(master,count,bang)
748   if a:master == ""
749     let time = s:initializetime({'y':1970,'s':localtime(),'z': 'UTC'})
750     if a:bang && a:count
751       silent! call remove(s:time_handlers,a:count - 1)
752     elseif a:bang
753       echo "SpeedDatingFormat             List defined formats"
754       echo "SpeedDatingFormat!            This help"
755       echo "SpeedDatingFormat %Y-%m-%d    Add a format"
756       echo "1SpeedDatingFormat %Y-%m-%d   Add a format before first format"
757       echo "SpeedDatingFormat! %Y-%m-%d   Remove a format"
758       echo "1SpeedDatingFormat!           Remove first format"
759       echo " "
760       echo "Expansions:"
761       for key in sort(keys(s:strftime_items),s:function("s:comparecase"))
762         echo printf("%2s     %-25s %s",'%'.key,s:strftime_items[key][3],s:strftime('%'.key,time))
763       endfor
764       echo '%0x    %x with mandatory leading zeros'
765       echo '%_x    %x with spaces rather than leading zeros'
766       echo '%-x    %x with no leading spaces or zeros'
767       echo '%^x    %x in uppercase'
768       echo '%*     at beginning/end, surpress \</\> respectively'
769       echo '%[..]  any one character         \([..]\)'
770       echo '%?[..] up to one character       \([..]\=\)'
771       echo '%1     character from first collection match \1'
772       echo " "
773       echo "Examples:"
774       echo 'SpeedDatingFormat %m%[/-]%d%1%Y    " American 12/25/2007'
775       echo 'SpeedDatingFormat %d%[/-]%m%1%Y    " European 25/12/2007'
776       echo " "
777       echo "Define formats in ".s:install_dir."/after/plugin/speeddating.vim"
778     elseif a:count
779       echo get(s:time_handlers,a:count-1,{'source':''}).source
780     else
781       let i = 0
782       for handler in s:time_handlers
783         let i += 1
784         echo printf("%3d %-32s %-32s",i,handler.source,s:strftime(handler.default,time))
785       endfor
786     endif
787   elseif a:bang
788     call filter(s:time_handlers,'v:val.source != a:master')
789   else
790     let handler = s:createtimehandler(a:master)
791     if a:count
792       call insert(s:time_handlers,handler,a:count - 1)
793     else
794       let s:time_handlers += [handler]
795     endif
796   endif
797 endfunction
798
799 if !exists('s:time_handlers')
800   let s:time_handlers = []
801   call speeddating#loadformats()
802 endif
803
804 " }}}1
805
806 let &cpo = s:cpo_save
807
808 " vim:set et sw=2 sts=2: