1" Vim filetype plugin
2" Language:	Cucumber
3" Maintainer:	Tim Pope <[email protected]>
4
5" Only do this when not done yet for this buffer
6if (exists("b:did_ftplugin"))
7  finish
8endif
9let b:did_ftplugin = 1
10
11setlocal formatoptions-=t formatoptions+=croql
12setlocal comments=:# commentstring=#\ %s
13setlocal omnifunc=CucumberComplete
14
15let b:undo_ftplugin = "setl fo< com< cms< ofu<"
16
17let b:cucumber_root = expand('%:p:h:s?.*[\/]\%(features\|stories\)\zs[\/].*??')
18
19if !exists("g:no_plugin_maps") && !exists("g:no_cucumber_maps")
20  nmap <silent><buffer> <C-]>       :<C-U>exe <SID>jump('edit',v:count)<CR>
21  nmap <silent><buffer> <C-W>]      :<C-U>exe <SID>jump('split',v:count)<CR>
22  nmap <silent><buffer> <C-W><C-]>  :<C-U>exe <SID>jump('split',v:count)<CR>
23  nmap <silent><buffer> <C-W>}      :<C-U>exe <SID>jump('pedit',v:count)<CR>
24  let b:undo_ftplugin .= "| sil! iunmap! <C-]>| sil! iunmap! <C-W>]| sil! iunmap! <C-W><C-]>| sil! iunmap! <C-W>}"
25endif
26
27function! s:jump(command,count)
28  let steps = s:steps('.')
29  if len(steps) == 0 || len(steps) < a:count
30    return 'echoerr "No matching step found"'
31  elseif len(steps) > 1 && !a:count
32    return 'echoerr "Multiple matching steps found"'
33  else
34    let c = a:count ? a:count-1 : 0
35    return a:command.' +'.steps[c][1].' '.escape(steps[c][0],' %#')
36  endif
37endfunction
38
39function! s:allsteps()
40  let step_pattern = '\C^\s*\K\k*\>\s*\zs\S.\{-\}\ze\s*\%(do\|{\)\s*\%(|[^|]*|\s*\)\=\%($\|#\)'
41  let steps = []
42  for file in split(glob(b:cucumber_root.'/**/*.rb'),"\n")
43    let lines = readfile(file)
44    let num = 0
45    for line in lines
46      let num += 1
47      if line =~ step_pattern
48        let type = matchstr(line,'\w\+')
49        let steps += [[file,num,type,matchstr(line,step_pattern)]]
50      endif
51    endfor
52  endfor
53  return steps
54endfunction
55
56function! s:steps(lnum)
57  let c = indent(a:lnum) + 1
58  while synIDattr(synID(a:lnum,c,1),'name') !~# '^$\|Region$'
59    let c = c + 1
60  endwhile
61  let step = matchstr(getline(a:lnum)[c-1 : -1],'^\s*\zs.\{-\}\ze\s*$')
62  return filter(s:allsteps(),'s:stepmatch(v:val[3],step)')
63endfunction
64
65function! s:stepmatch(receiver,target)
66  if a:receiver =~ '^[''"].*[''"]$'
67    let pattern = '^'.escape(substitute(a:receiver[1:-2],'$\w\+','(.*)','g'),'/').'$'
68  elseif a:receiver =~ '^/.*/$'
69    let pattern = a:receiver[1:-2]
70  elseif a:receiver =~ '^%r..*.$'
71    let pattern = escape(a:receiver[3:-2],'/')
72  else
73    return 0
74  endif
75  try
76    let vimpattern = substitute(substitute(pattern,'\\\@<!(?:','%(','g'),'\\\@<!\*?','{-}','g')
77    if a:target =~# '\v'.vimpattern
78      return 1
79    endif
80  catch
81  endtry
82  if has("ruby")
83    ruby VIM.command("return #{if (begin; Kernel.eval('/'+VIM.evaluate('pattern')+'/'); rescue SyntaxError; end) === VIM.evaluate('a:target') then 1 else 0 end}")
84  else
85    return 0
86  endif
87endfunction
88
89function! s:bsub(target,pattern,replacement)
90  return  substitute(a:target,'\C\\\@<!'.a:pattern,a:replacement,'g')
91endfunction
92
93function! CucumberComplete(findstart,base) abort
94  let indent = indent('.')
95  let group = synIDattr(synID(line('.'),indent+1,1),'name')
96  let type = matchstr(group,'\Ccucumber\zs\%(Given\|When\|Then\)')
97  let e = matchend(getline('.'),'^\s*\S\+\s')
98  if type == '' || col('.') < col('$') || e < 0
99    return -1
100  endif
101  if a:findstart
102    return e
103  endif
104  let steps = []
105  for step in s:allsteps()
106    if step[2] ==# type
107      if step[3] =~ '^[''"]'
108        let steps += [step[3][1:-2]]
109      elseif step[3] =~ '^/\^.*\$/$'
110        let pattern = step[3][2:-3]
111        let pattern = substitute(pattern,'\C^(?:|I )','I ','')
112        let pattern = s:bsub(pattern,'\\[Sw]','w')
113        let pattern = s:bsub(pattern,'\\d','1')
114        let pattern = s:bsub(pattern,'\\[sWD]',' ')
115        let pattern = s:bsub(pattern,'\[\^\\\="\]','_')
116        let pattern = s:bsub(pattern,'[[:alnum:]. _-][?*]?\=','')
117        let pattern = s:bsub(pattern,'\[\([^^]\).\{-\}\]','\1')
118        let pattern = s:bsub(pattern,'+?\=','')
119        let pattern = s:bsub(pattern,'(\([[:alnum:]. -]\{-\}\))','\1')
120        let pattern = s:bsub(pattern,'\\\([[:punct:]]\)','\1')
121        if pattern !~ '[\\()*?]'
122          let steps += [pattern]
123        endif
124      endif
125    endif
126  endfor
127  call filter(steps,'strpart(v:val,0,strlen(a:base)) ==# a:base')
128  return sort(steps)
129endfunction
130
131" vim:set sts=2 sw=2:
132