cobol.vim 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. " Vim indent file
  2. " Language: cobol
  3. " Maintainer: Ankit Jain <ajatkj@yahoo.co.in>
  4. " (formerly Tim Pope <vimNOSPAM@tpope.info>)
  5. " $Id: cobol.vim,v 1.1 2007/05/05 18:08:19 vimboss Exp $
  6. " Last Update: By Ankit Jain on 22.03.2019
  7. " Ankit Jain 22.03.2019 Changes & fixes:
  8. " Allow chars in 1st 6 columns
  9. " #C22032019
  10. " Ankit Jain 24.09.2021 add b:undo_indent (request by tpope)
  11. if exists("b:did_indent")
  12. finish
  13. endif
  14. let b:did_indent = 1
  15. setlocal expandtab
  16. setlocal indentexpr=GetCobolIndent(v:lnum)
  17. setlocal indentkeys&
  18. setlocal indentkeys+=0<*>,0/,0$,0=01,=~division,=~section,0=~end,0=~then,0=~else,0=~when,*<Return>,.
  19. let b:undo_indent = "setlocal expandtab< indentexpr< indentkeys<"
  20. " Only define the function once.
  21. if exists("*GetCobolIndent")
  22. finish
  23. endif
  24. let s:skip = 'getline(".") =~ "^.\\{6\\}[*/$-]\\|\"[^\"]*\""'
  25. function! s:prevgood(lnum)
  26. " Find a non-blank line above the current line.
  27. " Skip over comments.
  28. let lnum = a:lnum
  29. while lnum > 0
  30. let lnum = prevnonblank(lnum - 1)
  31. let line = getline(lnum)
  32. if line !~? '^\s*[*/$-]' && line !~? '^.\{6\}[*/$CD-]'
  33. break
  34. endif
  35. endwhile
  36. return lnum
  37. endfunction
  38. function! s:stripped(lnum)
  39. return substitute(strpart(getline(a:lnum),0,72),'^\s*','','')
  40. endfunction
  41. function! s:optionalblock(lnum,ind,blocks,clauses)
  42. let ind = a:ind
  43. let clauses = '\c\<\%(\<NOT\s\+\)\@<!\%(NOT\s\+\)\=\%('.a:clauses.'\)'
  44. let begin = '\c-\@<!\<\%('.a:blocks.'\)\>'
  45. let beginfull = begin.'\ze.*\%(\n\%(\s*\%([*/$-].*\)\=\n\)*\)\=\s*\%('.clauses.'\)'
  46. let end = '\c\<end-\%('.a:blocks.'\)\>\|\%(\.\%( \|$\)\)\@='
  47. let cline = s:stripped(a:lnum)
  48. let line = s:stripped(s:prevgood(a:lnum))
  49. if cline =~? clauses "&& line !~? '^search\>'
  50. call cursor(a:lnum,1)
  51. let lastclause = searchpair(beginfull,clauses,end,'bWr',s:skip)
  52. if getline(lastclause) =~? clauses && s:stripped(lastclause) !~? '^'.begin
  53. let ind = indent(lastclause)
  54. elseif lastclause > 0
  55. let ind = indent(lastclause) + shiftwidth()
  56. "let ind = ind + shiftwidth()
  57. endif
  58. elseif line =~? clauses && cline !~? end
  59. let ind = ind + shiftwidth()
  60. endif
  61. return ind
  62. endfunction
  63. function! GetCobolIndent(lnum) abort
  64. let minshft = 6
  65. let ashft = minshft + 1
  66. let bshft = ashft + 4
  67. " (Obsolete) numbered lines
  68. " #C22032019: Columns 1-6 could have alphabets as well as numbers
  69. "if getline(a:lnum) =~? '^\s*\d\{6\}\%($\|[ */$CD-]\)'
  70. if getline(a:lnum) =~? '^\s*[a-zA-Z0-9]\{6\}\%($\|[ */$CD-]\)'
  71. return 0
  72. endif
  73. let cline = s:stripped(a:lnum)
  74. " Comments, etc. must start in the 7th column
  75. if cline =~? '^[*/$-]'
  76. return minshft
  77. elseif cline =~# '^[CD]' && indent(a:lnum) == minshft
  78. return minshft
  79. endif
  80. " Divisions, sections, and file descriptions start in area A
  81. if cline =~? '\<\(DIVISION\|SECTION\)\%($\|\.\)' || cline =~? '^[FS]D\>'
  82. return ashft
  83. endif
  84. " Fields
  85. if cline =~? '^0*\(1\|77\)\>'
  86. return ashft
  87. endif
  88. if cline =~? '^\d\+\>'
  89. let cnum = matchstr(cline,'^\d\+\>')
  90. let default = 0
  91. let step = -1
  92. while step < 2
  93. let lnum = a:lnum
  94. while lnum > 0 && lnum < line('$') && lnum > a:lnum - 500 && lnum < a:lnum + 500
  95. let lnum = step > 0 ? nextnonblank(lnum + step) : prevnonblank(lnum + step)
  96. let line = getline(lnum)
  97. let lindent = indent(lnum)
  98. if line =~? '^\s*\d\+\>'
  99. let num = matchstr(line,'^\s*\zs\d\+\>')
  100. if 0+cnum == num
  101. return lindent
  102. elseif 0+cnum > num && default < lindent + shiftwidth()
  103. let default = lindent + shiftwidth()
  104. endif
  105. elseif lindent < bshft && lindent >= ashft
  106. break
  107. endif
  108. endwhile
  109. let step = step + 2
  110. endwhile
  111. return default ? default : bshft
  112. endif
  113. let lnum = s:prevgood(a:lnum)
  114. " Hit the start of the file, use "zero" indent.
  115. if lnum == 0
  116. return ashft
  117. endif
  118. " Initial spaces are ignored
  119. let line = s:stripped(lnum)
  120. let ind = indent(lnum)
  121. " Paragraphs. There may be some false positives.
  122. if cline =~? '^\(\a[A-Z0-9-]*[A-Z0-9]\|\d[A-Z0-9-]*\a\)\.' "\s*$'
  123. if cline !~? '^EXIT\s*\.' && line =~? '\.\s*$'
  124. return ashft
  125. endif
  126. endif
  127. " Paragraphs in the identification division.
  128. "if cline =~? '^\(PROGRAM-ID\|AUTHOR\|INSTALLATION\|' .
  129. "\ 'DATE-WRITTEN\|DATE-COMPILED\|SECURITY\)\>'
  130. "return ashft
  131. "endif
  132. if line =~? '\.$'
  133. " XXX
  134. return bshft
  135. endif
  136. if line =~? '^PERFORM\>'
  137. let perfline = substitute(line, '\c^PERFORM\s*', "", "")
  138. if perfline =~? '^\%(\k\+\s\+TIMES\)\=\s*$'
  139. let ind = ind + shiftwidth()
  140. elseif perfline =~? '^\%(WITH\s\+TEST\|VARYING\|UNTIL\)\>.*[^.]$'
  141. let ind = ind + shiftwidth()
  142. endif
  143. endif
  144. if line =~? '^\%(IF\|THEN\|ELSE\|READ\|EVALUATE\|SEARCH\|SELECT\)\>'
  145. let ind = ind + shiftwidth()
  146. endif
  147. let ind = s:optionalblock(a:lnum,ind,'ADD\|COMPUTE\|DIVIDE\|MULTIPLY\|SUBTRACT','ON\s\+SIZE\s\+ERROR')
  148. let ind = s:optionalblock(a:lnum,ind,'STRING\|UNSTRING\|ACCEPT\|DISPLAY\|CALL','ON\s\+OVERFLOW\|ON\s\+EXCEPTION')
  149. if cline !~? '^AT\s\+END\>' || line !~? '^SEARCH\>'
  150. let ind = s:optionalblock(a:lnum,ind,'DELETE\|REWRITE\|START\|WRITE\|READ','INVALID\s\+KEY\|AT\s\+END\|NO\s\+DATA\|AT\s\+END-OF-PAGE')
  151. endif
  152. if cline =~? '^WHEN\>'
  153. call cursor(a:lnum,1)
  154. " We also search for READ so that contained AT ENDs are skipped
  155. let lastclause = searchpair('\c-\@<!\<\%(SEARCH\|EVALUATE\|READ\)\>','\c\<\%(WHEN\|AT\s\+END\)\>','\c\<END-\%(SEARCH\|EVALUATE\|READ\)\>','bW',s:skip)
  156. let g:foo = s:stripped(lastclause)
  157. if s:stripped(lastclause) =~? '\c\<\%(WHEN\|AT\s\+END\)\>'
  158. "&& s:stripped(lastclause) !~? '^\%(SEARCH\|EVALUATE\|READ\)\>'
  159. let ind = indent(lastclause)
  160. elseif lastclause > 0
  161. let ind = indent(lastclause) + shiftwidth()
  162. endif
  163. elseif line =~? '^WHEN\>'
  164. let ind = ind + shiftwidth()
  165. endif
  166. "I'm not sure why I had this
  167. "if line =~? '^ELSE\>-\@!' && line !~? '\.$'
  168. "let ind = indent(s:prevgood(lnum))
  169. "endif
  170. if cline =~? '^\(END\)\>-\@!'
  171. " On lines with just END, 'guess' a simple shift left
  172. let ind = ind - shiftwidth()
  173. elseif cline =~? '^\(END-IF\|THEN\|ELSE\)\>-\@!'
  174. call cursor(a:lnum,indent(a:lnum))
  175. let match = searchpair('\c-\@<!\<IF\>','\c-\@<!\%(THEN\|ELSE\)\>','\c-\@<!\<END-IF\>\zs','bnW',s:skip)
  176. if match > 0
  177. let ind = indent(match)
  178. endif
  179. elseif cline =~? '^END-[A-Z]'
  180. let beginword = matchstr(cline,'\c\<END-\zs[A-Z0-9-]\+')
  181. let endword = 'END-'.beginword
  182. let first = 0
  183. let suffix = '.*\%(\n\%(\%(\s*\|.\{6\}\)[*/].*\n\)*\)\=\s*'
  184. if beginword =~? '^\%(ADD\|COMPUTE\|DIVIDE\|MULTIPLY\|SUBTRACT\)$'
  185. let beginword = beginword . suffix . '\<\%(NOT\s\+\)\=ON\s\+SIZE\s\+ERROR'
  186. let g:beginword = beginword
  187. let first = 1
  188. elseif beginword =~? '^\%(STRING\|UNSTRING\)$'
  189. let beginword = beginword . suffix . '\<\%(NOT\s\+\)\=ON\s\+OVERFLOW'
  190. let first = 1
  191. elseif beginword =~? '^\%(ACCEPT\|DISPLAY\)$'
  192. let beginword = beginword . suffix . '\<\%(NOT\s\+\)\=ON\s\+EXCEPTION'
  193. let first = 1
  194. elseif beginword ==? 'CALL'
  195. let beginword = beginword . suffix . '\<\%(NOT\s\+\)\=ON\s\+\%(EXCEPTION\|OVERFLOW\)'
  196. let first = 1
  197. elseif beginword =~? '^\%(DELETE\|REWRITE\|START\|READ\|WRITE\)$'
  198. let first = 1
  199. let beginword = beginword . suffix . '\<\%(NOT\s\+\)\=\(INVALID\s\+KEY'
  200. if beginword =~? '^READ'
  201. let first = 0
  202. let beginword = beginword . '\|AT\s\+END\|NO\s\+DATA'
  203. elseif beginword =~? '^WRITE'
  204. let beginword = beginword . '\|AT\s\+END-OF-PAGE'
  205. endif
  206. let beginword = beginword . '\)'
  207. endif
  208. call cursor(a:lnum,indent(a:lnum))
  209. let match = searchpair('\c-\@<!\<'.beginword.'\>','','\c\<'.endword.'\>\zs','bnW'.(first? 'r' : ''),s:skip)
  210. if match > 0
  211. let ind = indent(match)
  212. elseif cline =~? '^\(END-\(READ\|EVALUATE\|SEARCH\|PERFORM\)\)\>'
  213. let ind = ind - shiftwidth()
  214. endif
  215. endif
  216. return ind < bshft ? bshft : ind
  217. endfunction