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