sas.vim 5.2 KB

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