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