xref: /vim-8.2.3635/runtime/indent/sas.vim (revision 6dc819b1)
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