" Vim indent file
" Language:	Cucumber
" Maintainer:	Tim Pope <vimNOSPAM@tpope.org>
" Last Change:	2023 Dec 28

if exists("b:did_indent")
  finish
endif
let b:did_indent = 1

setlocal autoindent
setlocal indentexpr=GetCucumberIndent()
setlocal indentkeys=o,O,*<Return>,<:>,0<Bar>,0#,=,!^F

let b:undo_indent = 'setl ai< inde< indk<'

" Only define the function once.
if exists("*GetCucumberIndent")
  finish
endif

let s:headings = {
      \ 'Feature': 'feature',
      \ 'Rule': 'rule',
      \ 'Background': 'bg_or_scenario',
      \ 'Scenario': 'bg_or_scenario',
      \ 'ScenarioOutline': 'bg_or_scenario',
      \ 'Examples': 'examples',
      \ 'Scenarios': 'examples'}

function! s:Line(lnum) abort
  if getline(a:lnum) =~# ':'
    let group = matchstr(synIDattr(synID(a:lnum,1+indent(a:lnum), 1), 'name'), '^cucumber\zs.*')
    if !has_key(s:headings, group)
      let group = substitute(matchstr(getline(a:lnum), '^\s*\zs\%([^:]\+\)\ze:\S\@!'), '\s\+', '', 'g')
    endif
  else
    let group = ''
  endif
  let char = matchstr(getline(a:lnum), '^\s*\zs[[:punct:]]')
  return {
        \ 'lnum': a:lnum,
        \ 'indent': indent(a:lnum),
        \ 'heading': get(s:headings, group, ''),
        \ 'tag': char ==# '@',
        \ 'table': char ==# '|',
        \ 'comment': char ==# '#',
        \ }
endfunction

function! GetCucumberIndent(...) abort
  let lnum = a:0 ? a:1 : v:lnum
  let sw = shiftwidth()
  let prev = s:Line(prevnonblank(lnum-1))
  let curr = s:Line(lnum)
  let next = s:Line(nextnonblank(lnum+1))
  if curr.heading ==# 'feature'
    " feature heading
    return 0
  elseif curr.heading ==# 'examples'
    " examples heading
    return 2 * sw
  elseif curr.heading ==# 'bg_or_scenario'
    " background, scenario or outline heading
    return sw
  elseif prev.heading ==# 'feature'
    " line after feature heading
    return sw
  elseif prev.heading ==# 'examples'
    " line after examples heading
    return 3 * sw
  elseif prev.heading ==# 'bg_or_scenario'
    " line after background, scenario or outline heading
    return 2 * sw
  elseif (curr.tag || curr.comment) && (next.heading ==# 'feature' || prev.indent <= 0)
    " tag or comment before a feature heading
    return 0
  elseif curr.tag
    " other tags
    return sw
  elseif (curr.table || curr.comment) && prev.table
    " mid-table
    " preserve indent
    return prev.indent
  elseif curr.table && !prev.table
    " first line of a table, relative indent
    return prev.indent + sw
  elseif !curr.table && prev.table
    " line after a table, relative unindent
    return prev.indent - sw
  elseif curr.comment && getline(v:lnum-1) =~# '^\s*$' && next.heading ==# 'bg_or_scenario'
    " comments on scenarios
    return sw
  endif
  return prev.indent < 0 ? 0 : prev.indent
endfunction

" vim:set sts=2 sw=2:
