neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

sas.vim (5309B)


      1 " Vim indent file
      2 " Language:     SAS
      3 " Maintainer:   Zhen-Huan Hu <wildkeny@gmail.com>
      4 " Version:      3.0.3
      5 " Last Change:  2022 Apr 06
      6 
      7 if exists("b:did_indent")
      8  finish
      9 endif
     10 let b:did_indent = 1
     11 
     12 setlocal indentexpr=GetSASIndent()
     13 setlocal indentkeys+=;,=~data,=~proc,=~macro
     14 
     15 let b:undo_indent = "setl inde< indk<"
     16 
     17 if exists("*GetSASIndent")
     18  finish
     19 endif
     20 
     21 let s:cpo_save = &cpo
     22 set cpo&vim
     23 
     24 " Regex that captures the start of a data/proc section
     25 let s:section_str = '\v%(^|;)\s*%(data|proc)>'
     26 " Regex that captures the end of a run-processing section
     27 let s:section_run = '\v%(^|;)\s*run\s*;'
     28 " Regex that captures the end of a data/proc section
     29 let s:section_end = '\v%(^|;)\s*%(quit|enddata)\s*;'
     30 
     31 " Regex that captures the start of a control block (anything inside a section)
     32 let s:block_str = '\v<%(do>%([^;]+<%(to|over|while)>[^;]+)=|%(compute|define\s+%(column|footer|header|style|table|tagset|crosstabs|statgraph)|edit|layout|method|select)>[^;]+|begingraph)\s*;'
     33 " Regex that captures the end of a control block (anything inside a section)
     34 let s:block_end = '\v<%(end|endcomp|endlayout|endgraph)\s*;'
     35 
     36 " Regex that captures the start of a macro
     37 let s:macro_str = '\v%(^|;)\s*\%macro>'
     38 " Regex that captures the end of a macro
     39 let s:macro_end = '\v%(^|;)\s*\%mend\s*;'
     40 
     41 " Regex that defines the end of the program
     42 let s:program_end = '\v%(^|;)\s*endsas\s*;'
     43 
     44 " List of procs supporting run-processing
     45 let s:run_processing_procs = [
     46      \ 'catalog', 'chart', 'datasets', 'document', 'ds2', 'plot', 'sql',
     47      \ 'gareabar', 'gbarline', 'gchart', 'gkpi', 'gmap', 'gplot', 'gradar', 'greplay', 'gslide', 'gtile',
     48      \ 'anova', 'arima', 'catmod', 'factex', 'glm', 'model', 'optex', 'plan', 'reg',
     49      \ 'iml',
     50      \ ]
     51 
     52 " Find the line number of previous keyword defined by the regex
     53 function! s:PrevMatch(lnum, regex)
     54  let prev_lnum = prevnonblank(a:lnum - 1)
     55  while prev_lnum > 0
     56    let prev_line = getline(prev_lnum)
     57    if prev_line =~? a:regex
     58      break
     59    else
     60      let prev_lnum = prevnonblank(prev_lnum - 1)
     61    endif
     62  endwhile
     63  return prev_lnum
     64 endfunction
     65 
     66 " Main function
     67 function! GetSASIndent()
     68  let prev_lnum = prevnonblank(v:lnum - 1)
     69  if prev_lnum ==# 0
     70    " Leave the indentation of the first line unchanged
     71    return indent(1)
     72  else
     73    let prev_line = getline(prev_lnum)
     74    " Previous non-blank line contains the start of a macro/section/block
     75    " while not the end of a macro/section/block (at the same line)
     76    if (prev_line =~? s:section_str && prev_line !~? s:section_run && prev_line !~? s:section_end) ||
     77          \ (prev_line =~? s:block_str && prev_line !~? s:block_end) ||
     78          \ (prev_line =~? s:macro_str && prev_line !~? s:macro_end)
     79      let ind = indent(prev_lnum) + shiftwidth()
     80    elseif prev_line =~? s:section_run && prev_line !~? s:section_end
     81      let prev_section_str_lnum = s:PrevMatch(v:lnum, s:section_str)
     82      let prev_section_end_lnum = max([
     83            \ s:PrevMatch(v:lnum, s:section_end),
     84            \ s:PrevMatch(v:lnum, s:macro_end  ),
     85            \ s:PrevMatch(v:lnum, s:program_end)])
     86      " Check if the section supports run-processing
     87      if prev_section_end_lnum < prev_section_str_lnum &&
     88            \ getline(prev_section_str_lnum) =~? '\v%(^|;)\s*proc\s+%(' .
     89            \ join(s:run_processing_procs, '|') . ')>'
     90        let ind = indent(prev_lnum) + shiftwidth()
     91      else
     92        let ind = indent(prev_lnum)
     93      endif
     94    else
     95      let ind = indent(prev_lnum)
     96    endif
     97  endif
     98  " Re-adjustments based on the inputs of the current line
     99  let curr_line = getline(v:lnum)
    100  if curr_line =~? s:program_end
    101    " End of the program
    102    " Same indentation as the first non-blank line
    103    return indent(nextnonblank(1))
    104  elseif curr_line =~? s:macro_end
    105    " Current line is the end of a macro
    106    " Match the indentation of the start of the macro
    107    return indent(s:PrevMatch(v:lnum, s:macro_str))
    108  elseif curr_line =~? s:block_end && curr_line !~? s:block_str
    109    " Re-adjust if current line is the end of a block
    110    " while not the beginning of a block (at the same line)
    111    " Returning the indent of previous block start directly
    112    " would not work due to nesting
    113    let ind = ind - shiftwidth()
    114  elseif curr_line =~? s:section_str || curr_line =~? s:section_run || curr_line =~? s:section_end
    115    " Re-adjust if current line is the start/end of a section
    116    " since the end of a section could be inexplicit
    117    let prev_section_str_lnum = s:PrevMatch(v:lnum, s:section_str)
    118    " Check if the previous section supports run-processing
    119    if getline(prev_section_str_lnum) =~? '\v%(^|;)\s*proc\s+%(' .
    120          \ join(s:run_processing_procs, '|') . ')>'
    121      let prev_section_end_lnum = max([
    122            \ s:PrevMatch(v:lnum, s:section_end),
    123            \ s:PrevMatch(v:lnum, s:macro_end  ),
    124            \ s:PrevMatch(v:lnum, s:program_end)])
    125    else
    126      let prev_section_end_lnum = max([
    127            \ s:PrevMatch(v:lnum, s:section_end),
    128            \ s:PrevMatch(v:lnum, s:section_run),
    129            \ s:PrevMatch(v:lnum, s:macro_end  ),
    130            \ s:PrevMatch(v:lnum, s:program_end)])
    131    endif
    132    if prev_section_end_lnum < prev_section_str_lnum
    133      let ind = ind - shiftwidth()
    134    endif
    135  endif
    136  return ind
    137 endfunction
    138 
    139 let &cpo = s:cpo_save
    140 unlet s:cpo_save