blob: bbbbbf02ebdc6ee9bd38e07196a8c1e3191b8b9c [file] [log] [blame]
Bram Moolenaar036986f2017-03-16 17:41:02 +01001" Vim indent file
2" Language: SAS
3" Maintainer: Zhen-Huan Hu <wildkeny@gmail.com>
Bram Moolenaar6dc819b2018-07-03 16:42:19 +02004" Version: 3.0.3
Bram Moolenaarcbaff5e2022-04-08 17:45:08 +01005" Last Change: 2022 Apr 06
Bram Moolenaar036986f2017-03-16 17:41:02 +01006
7if exists("b:did_indent")
8 finish
9endif
10let b:did_indent = 1
11
12setlocal indentexpr=GetSASIndent()
13setlocal indentkeys+=;,=~data,=~proc,=~macro
14
Bram Moolenaarcbaff5e2022-04-08 17:45:08 +010015let b:undo_indent = "setl inde< indk<"
16
Bram Moolenaar036986f2017-03-16 17:41:02 +010017if exists("*GetSASIndent")
18 finish
19endif
20
21let s:cpo_save = &cpo
22set cpo&vim
23
24" Regex that captures the start of a data/proc section
25let s:section_str = '\v%(^|;)\s*%(data|proc)>'
26" Regex that captures the end of a run-processing section
27let s:section_run = '\v%(^|;)\s*run\s*;'
28" Regex that captures the end of a data/proc section
29let s:section_end = '\v%(^|;)\s*%(quit|enddata)\s*;'
30
31" Regex that captures the start of a control block (anything inside a section)
Bram Moolenaar6dc819b2018-07-03 16:42:19 +020032let 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*;'
Bram Moolenaar036986f2017-03-16 17:41:02 +010033" Regex that captures the end of a control block (anything inside a section)
Bram Moolenaar6dc819b2018-07-03 16:42:19 +020034let s:block_end = '\v<%(end|endcomp|endlayout|endgraph)\s*;'
Bram Moolenaar036986f2017-03-16 17:41:02 +010035
36" Regex that captures the start of a macro
37let s:macro_str = '\v%(^|;)\s*\%macro>'
38" Regex that captures the end of a macro
39let s:macro_end = '\v%(^|;)\s*\%mend\s*;'
40
41" Regex that defines the end of the program
42let s:program_end = '\v%(^|;)\s*endsas\s*;'
43
44" List of procs supporting run-processing
45let 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
53function! s:PrevMatch(lnum, regex)
54 let prev_lnum = prevnonblank(a:lnum - 1)
55 while prev_lnum > 0
56 let prev_line = getline(prev_lnum)
Bram Moolenaar6dc819b2018-07-03 16:42:19 +020057 if prev_line =~? a:regex
Bram Moolenaar036986f2017-03-16 17:41:02 +010058 break
59 else
60 let prev_lnum = prevnonblank(prev_lnum - 1)
61 endif
62 endwhile
63 return prev_lnum
64endfunction
65
66" Main function
67function! 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)
Bram Moolenaar6dc819b2018-07-03 16:42:19 +020076 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
Bram Moolenaar036986f2017-03-16 17:41:02 +010081 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 &&
Bram Moolenaar6dc819b2018-07-03 16:42:19 +020088 \ getline(prev_section_str_lnum) =~? '\v%(^|;)\s*proc\s+%(' .
Bram Moolenaar036986f2017-03-16 17:41:02 +010089 \ join(s:run_processing_procs, '|') . ')>'
Bram Moolenaar6dc819b2018-07-03 16:42:19 +020090 let ind = indent(prev_lnum) + shiftwidth()
Bram Moolenaar036986f2017-03-16 17:41:02 +010091 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)
Bram Moolenaar6dc819b2018-07-03 16:42:19 +0200100 if curr_line =~? s:program_end
Bram Moolenaar036986f2017-03-16 17:41:02 +0100101 " End of the program
102 " Same indentation as the first non-blank line
103 return indent(nextnonblank(1))
Bram Moolenaar6dc819b2018-07-03 16:42:19 +0200104 elseif curr_line =~? s:macro_end
Bram Moolenaar036986f2017-03-16 17:41:02 +0100105 " 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))
Bram Moolenaar6dc819b2018-07-03 16:42:19 +0200108 elseif curr_line =~? s:block_end && curr_line !~? s:block_str
Bram Moolenaar036986f2017-03-16 17:41:02 +0100109 " 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
Bram Moolenaar6dc819b2018-07-03 16:42:19 +0200113 let ind = ind - shiftwidth()
114 elseif curr_line =~? s:section_str || curr_line =~? s:section_run || curr_line =~? s:section_end
Bram Moolenaar036986f2017-03-16 17:41:02 +0100115 " 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
Bram Moolenaar6dc819b2018-07-03 16:42:19 +0200119 if getline(prev_section_str_lnum) =~? '\v%(^|;)\s*proc\s+%(' .
Bram Moolenaar036986f2017-03-16 17:41:02 +0100120 \ 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
Bram Moolenaar6dc819b2018-07-03 16:42:19 +0200133 let ind = ind - shiftwidth()
Bram Moolenaar036986f2017-03-16 17:41:02 +0100134 endif
135 endif
136 return ind
137endfunction
138
139let &cpo = s:cpo_save
140unlet s:cpo_save