1" Vim indent file 2" Language: Shell Script 3" Maintainer: Christian Brabandt <cb@256bit.org> 4" Original Author: Nikolai Weibull <now@bitwi.se> 5" Previous Maintainer: Peter Aronoff <telemachus@arpinum.org> 6" Latest Revision: 2019-10-24 7" License: Vim (see :h license) 8" Repository: https://github.com/chrisbra/vim-sh-indent 9" Changelog: 10" 20190726 - Correctly skip if keywords in syntax comments 11" (issue #17) 12" 20190603 - Do not indent in zsh filetypes with an `if` in comments 13" 20190428 - De-indent fi correctly when typing with 14" https://github.com/chrisbra/vim-sh-indent/issues/15 15" 20190325 - Indent fi; correctly 16" https://github.com/chrisbra/vim-sh-indent/issues/14 17" 20190319 - Indent arrays (only zsh and bash) 18" https://github.com/chrisbra/vim-sh-indent/issues/13 19" 20190316 - Make use of searchpairpos for nested if sections 20" fixes https://github.com/chrisbra/vim-sh-indent/issues/11 21" 20190201 - Better check for closing if sections 22" 20180724 - make check for zsh syntax more rigid (needs word-boundaries) 23" 20180326 - better support for line continuation 24" 20180325 - better detection of function definitions 25" 20180127 - better support for zsh complex commands 26" 20170808: - better indent of line continuation 27" 20170502: - get rid of buffer-shiftwidth function 28" 20160912: - preserve indentation of here-doc blocks 29" 20160627: - detect heredocs correctly 30" 20160213: - detect function definition correctly 31" 20160202: - use shiftwidth() function 32" 20151215: - set b:undo_indent variable 33" 20150728: - add foreach detection for zsh 34 35if exists("b:did_indent") 36 finish 37endif 38let b:did_indent = 1 39 40setlocal indentexpr=GetShIndent() 41setlocal indentkeys+=0=then,0=do,0=else,0=elif,0=fi,0=esac,0=done,0=end,),0=;;,0=;& 42setlocal indentkeys+=0=fin,0=fil,0=fip,0=fir,0=fix 43setlocal indentkeys-=:,0# 44setlocal nosmartindent 45 46let b:undo_indent = 'setlocal indentexpr< indentkeys< smartindent<' 47 48if exists("*GetShIndent") 49 finish 50endif 51 52let s:cpo_save = &cpo 53set cpo&vim 54 55let s:sh_indent_defaults = { 56 \ 'default': function('shiftwidth'), 57 \ 'continuation-line': function('shiftwidth'), 58 \ 'case-labels': function('shiftwidth'), 59 \ 'case-statements': function('shiftwidth'), 60 \ 'case-breaks': 0 } 61 62function! s:indent_value(option) 63 let Value = exists('b:sh_indent_options') 64 \ && has_key(b:sh_indent_options, a:option) ? 65 \ b:sh_indent_options[a:option] : 66 \ s:sh_indent_defaults[a:option] 67 if type(Value) == type(function('type')) 68 return Value() 69 endif 70 return Value 71endfunction 72 73function! GetShIndent() 74 let curline = getline(v:lnum) 75 let lnum = prevnonblank(v:lnum - 1) 76 if lnum == 0 77 return 0 78 endif 79 let line = getline(lnum) 80 81 let pnum = prevnonblank(lnum - 1) 82 let pline = getline(pnum) 83 let ind = indent(lnum) 84 85 " Check contents of previous lines 86 " should not apply to e.g. commented lines 87 if line =~ '^\s*\%(if\|then\|do\|else\|elif\|case\|while\|until\|for\|select\|foreach\)\>' || 88 \ (&ft is# 'zsh' && line =~ '^\s*\<\%(if\|then\|do\|else\|elif\|case\|while\|until\|for\|select\|foreach\)\>') 89 if !s:is_end_expression(line) 90 let ind += s:indent_value('default') 91 endif 92 elseif s:is_case_label(line, pnum) 93 if !s:is_case_ended(line) 94 let ind += s:indent_value('case-statements') 95 endif 96 " function definition 97 elseif s:is_function_definition(line) 98 if line !~ '}\s*\%(#.*\)\=$' 99 let ind += s:indent_value('default') 100 endif 101 " array (only works for zsh or bash) 102 elseif s:is_array(line) && line !~ ')\s*$' && (&ft is# 'zsh' || s:is_bash()) 103 let ind += s:indent_value('continuation-line') 104 " end of array 105 elseif curline =~ '^\s*)$' 106 let ind -= s:indent_value('continuation-line') 107 elseif s:is_continuation_line(line) 108 if pnum == 0 || !s:is_continuation_line(pline) 109 let ind += s:indent_value('continuation-line') 110 endif 111 elseif s:end_block(line) && !s:start_block(line) 112 let ind -= s:indent_value('default') 113 elseif pnum != 0 && 114 \ s:is_continuation_line(pline) && 115 \ !s:end_block(curline) && 116 \ !s:is_end_expression(curline) 117 " only add indent, if line and pline is in the same block 118 let i = v:lnum 119 let ind2 = indent(s:find_continued_lnum(pnum)) 120 while !s:is_empty(getline(i)) && i > pnum 121 let i -= 1 122 endw 123 if i == pnum 124 let ind += ind2 125 else 126 let ind = ind2 127 endif 128 endif 129 130 let pine = line 131 " Check content of current line 132 let line = curline 133 " Current line is a endif line, so get indent from start of "if condition" line 134 " TODO: should we do the same for other "end" lines? 135 if curline =~ '^\s*\%(fi\);\?\s*\%(#.*\)\=$' 136 let ind = indent(v:lnum) 137 let previous_line = searchpair('\<if\>', '', '\<fi\>\zs', 'bnW', 'synIDattr(synID(line("."),col("."), 1),"name") =~? "comment\\|quote"') 138 if previous_line > 0 139 let ind = indent(previous_line) 140 endif 141 elseif line =~ '^\s*\%(then\|do\|else\|elif\|done\|end\)\>' || s:end_block(line) 142 let ind -= s:indent_value('default') 143 elseif line =~ '^\s*esac\>' && s:is_case_empty(getline(v:lnum - 1)) 144 let ind -= s:indent_value('default') 145 elseif line =~ '^\s*esac\>' 146 let ind -= (s:is_case_label(pine, lnum) && s:is_case_ended(pine) ? 147 \ 0 : s:indent_value('case-statements')) + 148 \ s:indent_value('case-labels') 149 if s:is_case_break(pine) 150 let ind += s:indent_value('case-breaks') 151 endif 152 elseif s:is_case_label(line, lnum) 153 if s:is_case(pine) 154 let ind = indent(lnum) + s:indent_value('case-labels') 155 else 156 let ind -= (s:is_case_label(pine, lnum) && s:is_case_ended(pine) ? 157 \ 0 : s:indent_value('case-statements')) - 158 \ s:indent_value('case-breaks') 159 endif 160 elseif s:is_case_break(line) 161 let ind -= s:indent_value('case-breaks') 162 elseif s:is_here_doc(line) 163 let ind = 0 164 " statements, executed within a here document. Keep the current indent 165 elseif match(map(synstack(v:lnum, 1), 'synIDattr(v:val, "name")'), '\c\mheredoc') > -1 166 return indent(v:lnum) 167 elseif s:is_comment(line) && s:is_empty(getline(v:lnum-1)) 168 return indent(v:lnum) 169 endif 170 171 return ind > 0 ? ind : 0 172endfunction 173 174function! s:is_continuation_line(line) 175 " Comment, cannot be a line continuation 176 if a:line =~ '^\s*#' 177 return 0 178 else 179 " start-of-line 180 " \\ or && or || or | 181 " followed optionally by { or # 182 return a:line =~ '\%(\%(^\|[^\\]\)\\\|&&\|||\||\)' . 183 \ '\s*\({\s*\)\=\(#.*\)\=$' 184 endif 185endfunction 186 187function! s:find_continued_lnum(lnum) 188 let i = a:lnum 189 while i > 1 && s:is_continuation_line(getline(i - 1)) 190 let i -= 1 191 endwhile 192 return i 193endfunction 194 195function! s:is_function_definition(line) 196 return a:line =~ '^\s*\<\k\+\>\s*()\s*{' || 197 \ a:line =~ '^\s*{' || 198 \ a:line =~ '^\s*function\s*\k\+\s*\%(()\)\?\s*{' 199endfunction 200 201function! s:is_array(line) 202 return a:line =~ '^\s*\<\k\+\>=(' 203endfunction 204 205function! s:is_case_label(line, pnum) 206 if a:line !~ '^\s*(\=.*)' 207 return 0 208 endif 209 210 if a:pnum > 0 211 let pine = getline(a:pnum) 212 if !(s:is_case(pine) || s:is_case_ended(pine)) 213 return 0 214 endif 215 endif 216 217 let suffix = substitute(a:line, '^\s*(\=', "", "") 218 let nesting = 0 219 let i = 0 220 let n = strlen(suffix) 221 while i < n 222 let c = suffix[i] 223 let i += 1 224 if c == '\\' 225 let i += 1 226 elseif c == '(' 227 let nesting += 1 228 elseif c == ')' 229 if nesting == 0 230 return 1 231 endif 232 let nesting -= 1 233 endif 234 endwhile 235 return 0 236endfunction 237 238function! s:is_case(line) 239 return a:line =~ '^\s*case\>' 240endfunction 241 242function! s:is_case_break(line) 243 return a:line =~ '^\s*;[;&]' 244endfunction 245 246function! s:is_here_doc(line) 247 if a:line =~ '^\w\+$' 248 let here_pat = '<<-\?'. s:escape(a:line). '\$' 249 return search(here_pat, 'bnW') > 0 250 endif 251 return 0 252endfunction 253 254function! s:is_case_ended(line) 255 return s:is_case_break(a:line) || a:line =~ ';[;&]\s*\%(#.*\)\=$' 256endfunction 257 258function! s:is_case_empty(line) 259 if a:line =~ '^\s*$' || a:line =~ '^\s*#' 260 return s:is_case_empty(getline(v:lnum - 1)) 261 else 262 return a:line =~ '^\s*case\>' 263 endif 264endfunction 265 266function! s:escape(pattern) 267 return '\V'. escape(a:pattern, '\\') 268endfunction 269 270function! s:is_empty(line) 271 return a:line =~ '^\s*$' 272endfunction 273 274function! s:end_block(line) 275 return a:line =~ '^\s*}' 276endfunction 277 278function! s:start_block(line) 279 return a:line =~ '{\s*\(#.*\)\?$' 280endfunction 281 282function! s:find_start_block(lnum) 283 let i = a:lnum 284 while i > 1 && !s:start_block(getline(i)) 285 let i -= 1 286 endwhile 287 return i 288endfunction 289 290function! s:is_comment(line) 291 return a:line =~ '^\s*#' 292endfunction 293 294function! s:is_end_expression(line) 295 return a:line =~ '\<\%(fi\|esac\|done\|end\)\>\s*\%(#.*\)\=$' 296endfunction 297 298function! s:is_bash() 299 return get(g:, 'is_bash', 0) || get(b:, 'is_bash', 0) 300endfunction 301 302let &cpo = s:cpo_save 303unlet s:cpo_save 304