blob: 2ae3658a4b0f0d8d7f627eb101dc6c2888c6cee6 [file] [log] [blame]
Bram Moolenaar071d4272004-06-13 20:20:40 +00001" Vim indent file
2" Language: Vim script
3" Maintainer: Bram Moolenaar <Bram@vim.org>
Bram Moolenaara57b5532022-06-24 11:48:03 +01004" Last Change: 2022 Jun 24
Bram Moolenaar071d4272004-06-13 20:20:40 +00005
6" Only load this indent file when no other was loaded.
7if exists("b:did_indent")
8 finish
9endif
10let b:did_indent = 1
11
12setlocal indentexpr=GetVimIndent()
Bram Moolenaarc51cf032022-02-26 12:25:45 +000013setlocal indentkeys+==endif,=enddef,=endfu,=endfor,=endwh,=endtry,=},=else,=cat,=finall,=END,0\\,0=\"\\\
Bram Moolenaar2547aa92020-07-26 17:00:44 +020014setlocal indentkeys-=0#
Bram Moolenaar47003982021-12-05 21:54:04 +000015setlocal indentkeys-=:
Bram Moolenaar071d4272004-06-13 20:20:40 +000016
Bram Moolenaarc8734422012-06-01 22:38:45 +020017let b:undo_indent = "setl indentkeys< indentexpr<"
18
Bram Moolenaar071d4272004-06-13 20:20:40 +000019" Only define the function once.
20if exists("*GetVimIndent")
21 finish
22endif
Bram Moolenaar8e52a592012-05-18 21:49:28 +020023let s:keepcpo= &cpo
24set cpo&vim
Bram Moolenaar071d4272004-06-13 20:20:40 +000025
26function GetVimIndent()
Bram Moolenaar9b451252012-08-15 17:43:31 +020027 let ignorecase_save = &ignorecase
28 try
29 let &ignorecase = 0
30 return GetVimIndentIntern()
31 finally
32 let &ignorecase = ignorecase_save
33 endtry
34endfunc
35
Bram Moolenaar0daafaa2022-09-04 17:45:43 +010036" Legacy script line continuation and Vim9 script operators that must mean an
37" expression that continues from the previous line.
38let s:lineContPat = '^\s*\(\\\|"\\ \|->\)'
Bram Moolenaar67f8ab82018-09-11 22:37:29 +020039
Bram Moolenaar9b451252012-08-15 17:43:31 +020040function GetVimIndentIntern()
Bram Moolenaara57b5532022-06-24 11:48:03 +010041 " If the current line has line continuation and the previous one too, use
42 " the same indent. This does not skip empty lines.
43 let cur_text = getline(v:lnum)
44 let cur_has_linecont = cur_text =~ s:lineContPat
45 if cur_has_linecont && v:lnum > 1 && getline(v:lnum - 1) =~ s:lineContPat
46 return indent(v:lnum - 1)
47 endif
48
Bram Moolenaar071d4272004-06-13 20:20:40 +000049 " Find a non-blank line above the current line.
50 let lnum = prevnonblank(v:lnum - 1)
51
Bram Moolenaare0e39172021-01-25 21:14:57 +010052 " The previous line, ignoring line continuation
53 let prev_text_end = lnum > 0 ? getline(lnum) : ''
54
Bram Moolenaar67f8ab82018-09-11 22:37:29 +020055 " If the current line doesn't start with '\' or '"\ ' and below a line that
56 " starts with '\' or '"\ ', use the indent of the line above it.
Bram Moolenaara57b5532022-06-24 11:48:03 +010057 if !cur_has_linecont
Bram Moolenaar67f8ab82018-09-11 22:37:29 +020058 while lnum > 0 && getline(lnum) =~ s:lineContPat
Bram Moolenaar071d4272004-06-13 20:20:40 +000059 let lnum = lnum - 1
60 endwhile
61 endif
62
63 " At the start of the file use zero indent.
64 if lnum == 0
65 return 0
66 endif
Bram Moolenaare0e39172021-01-25 21:14:57 +010067
68 " the start of the previous line, skipping over line continuation
Bram Moolenaar91e15e12014-09-19 22:38:48 +020069 let prev_text = getline(lnum)
Bram Moolenaar82be4842021-01-11 19:40:15 +010070 let found_cont = 0
Bram Moolenaar071d4272004-06-13 20:20:40 +000071
72 " Add a 'shiftwidth' after :if, :while, :try, :catch, :finally, :function
Bram Moolenaar67f8ab82018-09-11 22:37:29 +020073 " and :else. Add it three times for a line that starts with '\' or '"\ '
74 " after a line that doesn't (or g:vim_indent_cont if it exists).
Bram Moolenaar071d4272004-06-13 20:20:40 +000075 let ind = indent(lnum)
Bram Moolenaar1ff14ba2019-11-02 14:09:23 +010076
77 " In heredoc indenting works completely differently.
78 if has('syntax_items')
79 let syn_here = synIDattr(synID(v:lnum, 1, 1), "name")
80 if syn_here =~ 'vimLetHereDocStop'
81 " End of heredoc: use indent of matching start line
82 let lnum = v:lnum - 1
83 while lnum > 0
Bram Moolenaar11e3c5b2021-04-21 18:09:37 +020084 let attr = synIDattr(synID(lnum, 1, 1), "name")
85 if attr != '' && attr !~ 'vimLetHereDoc'
Bram Moolenaar1ff14ba2019-11-02 14:09:23 +010086 return indent(lnum)
87 endif
88 let lnum -= 1
89 endwhile
90 return 0
91 endif
92 if syn_here =~ 'vimLetHereDoc'
93 if synIDattr(synID(lnum, 1, 1), "name") !~ 'vimLetHereDoc'
94 " First line in heredoc: increase indent
95 return ind + shiftwidth()
96 endif
97 " Heredoc continues: no change in indent
98 return ind
99 endif
100 endif
101
Bram Moolenaar67f8ab82018-09-11 22:37:29 +0200102 if cur_text =~ s:lineContPat && v:lnum > 1 && prev_text !~ s:lineContPat
Bram Moolenaar82be4842021-01-11 19:40:15 +0100103 let found_cont = 1
Bram Moolenaard4755bb2004-09-02 19:12:26 +0000104 if exists("g:vim_indent_cont")
105 let ind = ind + g:vim_indent_cont
106 else
Bram Moolenaar705ada12016-01-24 17:56:50 +0100107 let ind = ind + shiftwidth() * 3
Bram Moolenaard4755bb2004-09-02 19:12:26 +0000108 endif
Bram Moolenaare18dbe82016-07-02 21:42:23 +0200109 elseif prev_text =~ '^\s*aug\%[roup]\s\+' && prev_text !~ '^\s*aug\%[roup]\s\+[eE][nN][dD]\>'
Bram Moolenaar705ada12016-01-24 17:56:50 +0100110 let ind = ind + shiftwidth()
Bram Moolenaarcab49df2011-03-22 17:40:10 +0100111 else
Bram Moolenaar91e15e12014-09-19 22:38:48 +0200112 " A line starting with :au does not increment/decrement indent.
Bram Moolenaar942db232021-02-13 18:14:48 +0100113 " A { may start a block or a dict. Assume that when a } follows it's a
114 " terminated dict.
Bram Moolenaarc51cf032022-02-26 12:25:45 +0000115 " ":function" starts a block but "function(" doesn't.
Bram Moolenaar942db232021-02-13 18:14:48 +0100116 if prev_text !~ '^\s*au\%[tocmd]' && prev_text !~ '^\s*{.*}'
Bram Moolenaar1588bc82022-03-08 21:35:07 +0000117 let i = match(prev_text, '\(^\||\)\s*\(export\s\+\)\?\({\|\(if\|wh\%[ile]\|for\|try\|cat\%[ch]\|fina\|finall\%[y]\|def\|el\%[seif]\)\>\|fu\%[nction][! ]\)')
Bram Moolenaar91e15e12014-09-19 22:38:48 +0200118 if i >= 0
Bram Moolenaar705ada12016-01-24 17:56:50 +0100119 let ind += shiftwidth()
Bram Moolenaar91e15e12014-09-19 22:38:48 +0200120 if strpart(prev_text, i, 1) == '|' && has('syntax_items')
Bram Moolenaar113cb512021-11-07 20:27:04 +0000121 \ && synIDattr(synID(lnum, i, 1), "name") =~ '\(Comment\|String\|PatSep\)$'
Bram Moolenaar705ada12016-01-24 17:56:50 +0100122 let ind -= shiftwidth()
Bram Moolenaar91e15e12014-09-19 22:38:48 +0200123 endif
Bram Moolenaarcab49df2011-03-22 17:40:10 +0100124 endif
125 endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000126 endif
127
128 " If the previous line contains an "end" after a pipe, but not in an ":au"
Bram Moolenaar520470a2005-06-16 21:59:56 +0000129 " command. And not when there is a backslash before the pipe.
Bram Moolenaardc27ac12005-07-06 22:35:45 +0000130 " And when syntax HL is enabled avoid a match inside a string.
Bram Moolenaar91e15e12014-09-19 22:38:48 +0200131 let i = match(prev_text, '[^\\]|\s*\(ene\@!\)')
132 if i > 0 && prev_text !~ '^\s*au\%[tocmd]'
Bram Moolenaardc27ac12005-07-06 22:35:45 +0000133 if !has('syntax_items') || synIDattr(synID(lnum, i + 2, 1), "name") !~ '\(Comment\|String\)$'
Bram Moolenaar705ada12016-01-24 17:56:50 +0100134 let ind = ind - shiftwidth()
Bram Moolenaardc27ac12005-07-06 22:35:45 +0000135 endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000136 endif
137
Bram Moolenaar0daafaa2022-09-04 17:45:43 +0100138 " For a line starting with "}" find the matching "{". Align with that line,
139 " it is either the matching block start or dictionary start.
Bram Moolenaar82be4842021-01-11 19:40:15 +0100140 " Use the mapped "%" from matchit to find the match, otherwise we may match
141 " a { inside a comment or string.
142 if cur_text =~ '^\s*}'
143 if maparg('%') != ''
144 exe v:lnum
145 silent! normal %
Bram Moolenaar0daafaa2022-09-04 17:45:43 +0100146 if line('.') < v:lnum
Bram Moolenaar82be4842021-01-11 19:40:15 +0100147 let ind = indent('.')
148 endif
149 else
150 " todo: use searchpair() to find a match
151 endif
152 endif
153
Bram Moolenaar0daafaa2022-09-04 17:45:43 +0100154 " Look back for a line to align with
155 while lnum > 1
156 " Below a line starting with "}" find the matching "{".
157 if prev_text =~ '^\s*}'
158 if maparg('%') != ''
159 exe lnum
160 silent! normal %
161 if line('.') < lnum
162 let lnum = line('.')
163 let ind = indent(lnum)
164 let prev_text = getline(lnum)
165 else
166 break
167 endif
168 else
169 " todo: use searchpair() to find a match
170 break
Bram Moolenaar82be4842021-01-11 19:40:15 +0100171 endif
Bram Moolenaar0daafaa2022-09-04 17:45:43 +0100172 elseif prev_text =~ s:lineContPat
173 " looks like a continuation like, go back one line
174 let lnum = lnum - 1
175 let ind = indent(lnum)
176 let prev_text = getline(lnum)
Bram Moolenaar82be4842021-01-11 19:40:15 +0100177 else
Bram Moolenaar0daafaa2022-09-04 17:45:43 +0100178 break
Bram Moolenaar82be4842021-01-11 19:40:15 +0100179 endif
Bram Moolenaar0daafaa2022-09-04 17:45:43 +0100180 endwhile
Bram Moolenaar82be4842021-01-11 19:40:15 +0100181
182 " Below a line starting with "]" we must be below the end of a list.
Bram Moolenaar942db232021-02-13 18:14:48 +0100183 " Include a "}" and "},} in case a dictionary ends too.
184 if prev_text_end =~ '^\s*\(},\=\s*\)\=]'
Bram Moolenaar82be4842021-01-11 19:40:15 +0100185 let ind = ind - shiftwidth()
186 endif
187
Bram Moolenaar942db232021-02-13 18:14:48 +0100188 let ends_in_comment = has('syntax_items')
Bram Moolenaar9faec4e2021-02-27 16:38:07 +0100189 \ && synIDattr(synID(lnum, len(getline(lnum)), 1), "name") =~ '\(Comment\|String\)$'
Bram Moolenaar942db232021-02-13 18:14:48 +0100190
Bram Moolenaar9faec4e2021-02-27 16:38:07 +0100191 " A line ending in "{" or "[" is most likely the start of a dict/list literal,
Bram Moolenaar942db232021-02-13 18:14:48 +0100192 " indent the next line more. Not for a continuation line or {{{.
193 if !ends_in_comment && prev_text_end =~ '\s[{[]\s*$' && !found_cont
Bram Moolenaar82be4842021-01-11 19:40:15 +0100194 let ind = ind + shiftwidth()
195 endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000196
Bram Moolenaarc51cf032022-02-26 12:25:45 +0000197 " Subtract a 'shiftwidth' on a :endif, :endwhile, :endfor, :catch, :finally,
198 " :endtry, :endfun, :enddef, :else and :augroup END.
199 " Although ":en" would be enough only match short command names as in
200 " 'indentkeys'.
201 if cur_text =~ '^\s*\(endif\|endwh\|endfor\|endtry\|endfu\|enddef\|cat\|finall\|else\|aug\%[roup]\s\+[eE][nN][dD]\)'
Bram Moolenaar705ada12016-01-24 17:56:50 +0100202 let ind = ind - shiftwidth()
Bram Moolenaarc51cf032022-02-26 12:25:45 +0000203 if ind < 0
204 let ind = 0
205 endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000206 endif
207
208 return ind
209endfunction
210
Bram Moolenaar8e52a592012-05-18 21:49:28 +0200211let &cpo = s:keepcpo
212unlet s:keepcpo
213
Bram Moolenaar071d4272004-06-13 20:20:40 +0000214" vim:sw=2