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