summaryrefslogtreecommitdiffstats
path: root/indent/lua.vim
blob: e86c1b8d9adea8acda625537fa1789fb054eb6ce (plain) (blame)
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
let files = filter(globpath(&rtp, 'indent/lua.vim', 1, 1), { _, v -> v !~ "vim-polyglot" && v !~ $VIMRUNTIME && v !~ "after" })
if len(files) > 0
  exec 'source ' . files[0]
  finish
endif
if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'lua') == -1

" Vim indent file
" Language: Lua
" URL: https://github.com/tbastos/vim-lua

" Initialization ------------------------------------------{{{1

if exists("b:did_indent")
  finish
endif
let b:did_indent = 1

setlocal autoindent
setlocal nosmartindent

setlocal indentexpr=GetLuaIndent()
setlocal indentkeys+=0=end,0=until,0=elseif,0=else

" Only define the function once.
if exists("*GetLuaIndent")
  finish
endif

" Variables -----------------------------------------------{{{1

let s:open_patt = '\C\%(\<\%(function\|if\|repeat\|do\)\>\|(\|{\)'
let s:middle_patt = '\C\<\%(else\|elseif\)\>'
let s:close_patt = '\C\%(\<\%(end\|until\)\>\|)\|}\)'

let s:anon_func_start = '\S\+\s*[({].*\<function\s*(.*)\s*$'
let s:anon_func_end = '\<end\%(\s*[)}]\)\+'

let s:chained_func_call = "^\\v\\s*[:.]\\w+[({\"']"

" Expression used to check whether we should skip a match with searchpair().
let s:skip_expr = "synIDattr(synID(line('.'),col('.'),1),'name') =~# 'luaComment\\|luaString'"

" Auxiliary Functions -------------------------------------{{{1

function s:IsInCommentOrString(lnum, col)
  return synIDattr(synID(a:lnum, a:col, 1), 'name') =~# 'luaCommentLong\|luaStringLong'
        \ && !(getline(a:lnum) =~# '^\s*\%(--\)\?\[=*\[') " opening tag is not considered 'in'
endfunction

" Find line above 'lnum' that isn't blank, in a comment or string.
function s:PrevLineOfCode(lnum)
  let lnum = prevnonblank(a:lnum)
  while s:IsInCommentOrString(lnum, 1)
    let lnum = prevnonblank(lnum - 1)
  endwhile
  return lnum
endfunction

" Gets line contents, excluding trailing comments.
function s:GetContents(lnum)
  return substitute(getline(a:lnum), '\v\m--.*$', '', '')
endfunction

" GetLuaIndent Function -----------------------------------{{{1

function GetLuaIndent()
  " if the line is in a long comment or string, don't change the indent
  if s:IsInCommentOrString(v:lnum, 1)
    return -1
  endif

  let prev_line = s:PrevLineOfCode(v:lnum - 1)
  if prev_line == 0
    " this is the first non-empty line
    return 0
  endif

  let contents_cur = s:GetContents(v:lnum)
  let contents_prev = s:GetContents(prev_line)

  let original_cursor_pos = getpos(".")

  " count how many blocks the previous line opens
  call cursor(v:lnum, 1)
  let num_prev_opens = searchpair(s:open_patt, s:middle_patt, s:close_patt,
        \ 'mrb', s:skip_expr, prev_line)

  " count how many blocks the current line closes
  call cursor(prev_line, col([prev_line,'$']))
  let num_cur_closes = searchpair(s:open_patt, s:middle_patt, s:close_patt,
        \ 'mr', s:skip_expr, v:lnum)

  let i = num_prev_opens - num_cur_closes

  " if the previous line closed a paren, outdent (except with anon funcs)
  call cursor(prev_line - 1, col([prev_line - 1, '$']))
  let num_prev_closed_parens = searchpair('(', '', ')', 'mr', s:skip_expr, prev_line)
  if num_prev_closed_parens > 0 && contents_prev !~# s:anon_func_end
    let i -= 1
  endif

  " if this line closed a paren, indent (except with anon funcs)
  call cursor(prev_line, col([prev_line, '$']))
  let num_cur_closed_parens = searchpair('(', '', ')', 'mr', s:skip_expr, v:lnum)
  if num_cur_closed_parens > 0 && contents_cur !~# s:anon_func_end
    let i += 1
  endif

  " if the current line chains a function call to previous unchained line
  if contents_prev !~# s:chained_func_call && contents_cur =~# s:chained_func_call
    let i += 1
  endif

  " if the current line chains a function call to previous unchained line
  if contents_prev =~# s:chained_func_call && contents_cur !~# s:chained_func_call
    let i -= 1
  endif

  " special case: call(with, {anon = function() -- should indent only once
  if i > 1 && contents_prev =~# s:anon_func_start
    let i = 1
  endif

  " special case: end}) -- end of call w/ anon func should outdent only once
  if i < -1 && contents_cur =~# s:anon_func_end
    let i = -1
  endif

  " restore cursor
  call setpos(".", original_cursor_pos)

  return indent(prev_line) + (shiftwidth() * i)

endfunction

endif