summaryrefslogtreecommitdiff
path: root/runtime/indent/vim.vim
blob: d2f5f317af7d25e332f4bfef319ecbcbb8ae2dda (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
" Vim indent file
" Language:	Vim script
" Maintainer:	Bram Moolenaar <Bram@vim.org>
" Last Change:	2021 Feb 13

" Only load this indent file when no other was loaded.
if exists("b:did_indent")
  finish
endif
let b:did_indent = 1

setlocal indentexpr=GetVimIndent()
setlocal indentkeys+==end,=},=else,=cat,=finall,=END,0\\,0=\"\\\ 
setlocal indentkeys-=0#

let b:undo_indent = "setl indentkeys< indentexpr<"

" Only define the function once.
if exists("*GetVimIndent")
  finish
endif
let s:keepcpo= &cpo
set cpo&vim

function GetVimIndent()
  let ignorecase_save = &ignorecase
  try
    let &ignorecase = 0
    return GetVimIndentIntern()
  finally
    let &ignorecase = ignorecase_save
  endtry
endfunc

let s:lineContPat = '^\s*\(\\\|"\\ \)'

function GetVimIndentIntern()
  " Find a non-blank line above the current line.
  let lnum = prevnonblank(v:lnum - 1)

  " The previous line, ignoring line continuation
  let prev_text_end = lnum > 0 ? getline(lnum) : ''

  " If the current line doesn't start with '\' or '"\ ' and below a line that
  " starts with '\' or '"\ ', use the indent of the line above it.
  let cur_text = getline(v:lnum)
  if cur_text !~ s:lineContPat
    while lnum > 0 && getline(lnum) =~ s:lineContPat
      let lnum = lnum - 1
    endwhile
  endif

  " At the start of the file use zero indent.
  if lnum == 0
    return 0
  endif

  " the start of the previous line, skipping over line continuation
  let prev_text = getline(lnum)
  let found_cont = 0

  " Add a 'shiftwidth' after :if, :while, :try, :catch, :finally, :function
  " and :else.  Add it three times for a line that starts with '\' or '"\ '
  " after a line that doesn't (or g:vim_indent_cont if it exists).
  let ind = indent(lnum)

  " In heredoc indenting works completely differently.
  if has('syntax_items') 
    let syn_here = synIDattr(synID(v:lnum, 1, 1), "name")
    if syn_here =~ 'vimLetHereDocStop'
      " End of heredoc: use indent of matching start line
      let lnum = v:lnum - 1
      while lnum > 0
	if synIDattr(synID(lnum, 1, 1), "name") !~ 'vimLetHereDoc'
	  return indent(lnum)
	endif
	let lnum -= 1
      endwhile
      return 0
    endif
    if syn_here =~ 'vimLetHereDoc'
      if synIDattr(synID(lnum, 1, 1), "name") !~ 'vimLetHereDoc'
	" First line in heredoc: increase indent
	return ind + shiftwidth()
      endif
      " Heredoc continues: no change in indent
      return ind
    endif
  endif

  if cur_text =~ s:lineContPat && v:lnum > 1 && prev_text !~ s:lineContPat
    let found_cont = 1
    if exists("g:vim_indent_cont")
      let ind = ind + g:vim_indent_cont
    else
      let ind = ind + shiftwidth() * 3
    endif
  elseif prev_text =~ '^\s*aug\%[roup]\s\+' && prev_text !~ '^\s*aug\%[roup]\s\+[eE][nN][dD]\>'
    let ind = ind + shiftwidth()
  else
    " A line starting with :au does not increment/decrement indent.
    " A { may start a block or a dict.  Assume that when a } follows it's a
    " terminated dict.
    if prev_text !~ '^\s*au\%[tocmd]' && prev_text !~ '^\s*{.*}'
      let i = match(prev_text, '\(^\||\)\s*\(export\s\+\)\?\({\|\(if\|wh\%[ile]\|for\|try\|cat\%[ch]\|fina\|finall\%[y]\|fu\%[nction]\|def\|el\%[seif]\)\>\)')
      if i >= 0
	let ind += shiftwidth()
	if strpart(prev_text, i, 1) == '|' && has('syntax_items')
	      \ && synIDattr(synID(lnum, i, 1), "name") =~ '\(Comment\|String\)$'
	  let ind -= shiftwidth()
	endif
      endif
    endif
  endif

  " If the previous line contains an "end" after a pipe, but not in an ":au"
  " command.  And not when there is a backslash before the pipe.
  " And when syntax HL is enabled avoid a match inside a string.
  let i = match(prev_text, '[^\\]|\s*\(ene\@!\)')
  if i > 0 && prev_text !~ '^\s*au\%[tocmd]'
    if !has('syntax_items') || synIDattr(synID(lnum, i + 2, 1), "name") !~ '\(Comment\|String\)$'
      let ind = ind - shiftwidth()
    endif
  endif

  " For a line starting with "}" find the matching "{".  If it is at the start
  " of the line align with it, probably end of a block.
  " Use the mapped "%" from matchit to find the match, otherwise we may match
  " a { inside a comment or string.
  if cur_text =~ '^\s*}'
    if maparg('%') != ''
      exe v:lnum
      silent! normal %
      if line('.') < v:lnum && getline('.') =~ '^\s*{'
	let ind = indent('.')
      endif
    else
      " todo: use searchpair() to find a match
    endif
  endif

  " Below a line starting with "}" find the matching "{".  If it is at the
  " end of the line we must be below the end of a dictionary.
  if prev_text =~ '^\s*}'
    if maparg('%') != ''
      exe lnum
      silent! normal %
      if line('.') == lnum || getline('.') !~ '^\s*{'
	let ind = ind - shiftwidth()
      endif
    else
      " todo: use searchpair() to find a match
    endif
  endif

  " Below a line starting with "]" we must be below the end of a list.
  " Include a "}" and "},} in case a dictionary ends too.
  if prev_text_end =~ '^\s*\(},\=\s*\)\=]'
    let ind = ind - shiftwidth()
  endif

  let ends_in_comment = has('syntax_items')
	      \ && synIDattr(synID(lnum, col('$'), 1), "name") =~ '\(Comment\|String\)$'

  " A line ending in "{"/"[} is most likely the start of a dict/list literal,
  " indent the next line more.  Not for a continuation line or {{{.
  if !ends_in_comment && prev_text_end =~ '\s[{[]\s*$' && !found_cont
    let ind = ind + shiftwidth()
  endif

  " Subtract a 'shiftwidth' on a :endif, :endwhile, :catch, :finally, :endtry,
  " :endfun, :enddef, :else and :augroup END.
  if cur_text =~ '^\s*\(ene\@!\|cat\|finall\|el\|aug\%[roup]\s\+[eE][nN][dD]\)'
    let ind = ind - shiftwidth()
  endif

  return ind
endfunction

let &cpo = s:keepcpo
unlet s:keepcpo

" vim:sw=2