1773a97c2SBram Moolenaar" Vim indent file
2773a97c2SBram Moolenaar" Language: TypeScript
3773a97c2SBram Moolenaar" Maintainer: See https://github.com/HerringtonDarkholme/yats.vim
496f45c0bSBram Moolenaar" Last Change: 2019 Oct 18
5773a97c2SBram Moolenaar" Acknowledgement: Based off of vim-ruby maintained by Nikolai Weibull http://vim-ruby.rubyforge.org
6773a97c2SBram Moolenaar
7773a97c2SBram Moolenaar" 0. Initialization {{{1
8773a97c2SBram Moolenaar" =================
9773a97c2SBram Moolenaar
10773a97c2SBram Moolenaar" Only load this indent file when no other was loaded.
11773a97c2SBram Moolenaarif exists("b:did_indent")
12773a97c2SBram Moolenaar  finish
13773a97c2SBram Moolenaarendif
14773a97c2SBram Moolenaarlet b:did_indent = 1
15773a97c2SBram Moolenaar
16773a97c2SBram Moolenaarsetlocal nosmartindent
17773a97c2SBram Moolenaar
18773a97c2SBram Moolenaar" Now, set up our indentation expression and keys that trigger it.
19773a97c2SBram Moolenaarsetlocal indentexpr=GetTypescriptIndent()
20773a97c2SBram Moolenaarsetlocal formatexpr=Fixedgq(v:lnum,v:count)
21773a97c2SBram Moolenaarsetlocal indentkeys=0{,0},0),0],0\,,!^F,o,O,e
22773a97c2SBram Moolenaar
23773a97c2SBram Moolenaar" Only define the function once.
24773a97c2SBram Moolenaarif exists("*GetTypescriptIndent")
25773a97c2SBram Moolenaar  finish
26773a97c2SBram Moolenaarendif
27773a97c2SBram Moolenaar
28773a97c2SBram Moolenaarlet s:cpo_save = &cpo
29773a97c2SBram Moolenaarset cpo&vim
30773a97c2SBram Moolenaar
31773a97c2SBram Moolenaar" 1. Variables {{{1
32773a97c2SBram Moolenaar" ============
33773a97c2SBram Moolenaar
34773a97c2SBram Moolenaarlet s:js_keywords = '^\s*\(break\|case\|catch\|continue\|debugger\|default\|delete\|do\|else\|finally\|for\|function\|if\|in\|instanceof\|new\|return\|switch\|this\|throw\|try\|typeof\|var\|void\|while\|with\)'
35773a97c2SBram Moolenaar
36773a97c2SBram Moolenaar" Regex of syntax group names that are or delimit string or are comments.
37773a97c2SBram Moolenaarlet s:syng_strcom = 'string\|regex\|comment\c'
38773a97c2SBram Moolenaar
39773a97c2SBram Moolenaar" Regex of syntax group names that are strings.
40773a97c2SBram Moolenaarlet s:syng_string = 'regex\c'
41773a97c2SBram Moolenaar
42773a97c2SBram Moolenaar" Regex of syntax group names that are strings or documentation.
43773a97c2SBram Moolenaarlet s:syng_multiline = 'comment\c'
44773a97c2SBram Moolenaar
45773a97c2SBram Moolenaar" Regex of syntax group names that are line comment.
46773a97c2SBram Moolenaarlet s:syng_linecom = 'linecomment\c'
47773a97c2SBram Moolenaar
48773a97c2SBram Moolenaar" Expression used to check whether we should skip a match with searchpair().
49773a97c2SBram Moolenaarlet s:skip_expr = "synIDattr(synID(line('.'),col('.'),1),'name') =~ '".s:syng_strcom."'"
50773a97c2SBram Moolenaar
51773a97c2SBram Moolenaarlet s:line_term = '\s*\%(\%(\/\/\).*\)\=$'
52773a97c2SBram Moolenaar
53773a97c2SBram Moolenaar" Regex that defines continuation lines, not including (, {, or [.
54773a97c2SBram Moolenaarlet s:continuation_regex = '\%([\\*+/.:]\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\|[^=]=[^=].*,\)' . s:line_term
55773a97c2SBram Moolenaar
56773a97c2SBram Moolenaar" Regex that defines continuation lines.
57773a97c2SBram Moolenaar" TODO: this needs to deal with if ...: and so on
58773a97c2SBram Moolenaarlet s:msl_regex = s:continuation_regex
59773a97c2SBram Moolenaar
60773a97c2SBram Moolenaarlet s:one_line_scope_regex = '\<\%(if\|else\|for\|while\)\>[^{;]*' . s:line_term
61773a97c2SBram Moolenaar
62773a97c2SBram Moolenaar" Regex that defines blocks.
63773a97c2SBram Moolenaarlet s:block_regex = '\%([{[]\)\s*\%(|\%([*@]\=\h\w*,\=\s*\)\%(,\s*[*@]\=\h\w*\)*|\)\=' . s:line_term
64773a97c2SBram Moolenaar
65773a97c2SBram Moolenaarlet s:var_stmt = '^\s*var'
66773a97c2SBram Moolenaar
67773a97c2SBram Moolenaarlet s:comma_first = '^\s*,'
68773a97c2SBram Moolenaarlet s:comma_last = ',\s*$'
69773a97c2SBram Moolenaar
70773a97c2SBram Moolenaarlet s:ternary = '^\s\+[?|:]'
71773a97c2SBram Moolenaarlet s:ternary_q = '^\s\+?'
72773a97c2SBram Moolenaar
73773a97c2SBram Moolenaar" 2. Auxiliary Functions {{{1
74773a97c2SBram Moolenaar" ======================
75773a97c2SBram Moolenaar
76773a97c2SBram Moolenaar" Check if the character at lnum:col is inside a string, comment, or is ascii.
77773a97c2SBram Moolenaarfunction s:IsInStringOrComment(lnum, col)
78773a97c2SBram Moolenaar  return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_strcom
79773a97c2SBram Moolenaarendfunction
80773a97c2SBram Moolenaar
81773a97c2SBram Moolenaar" Check if the character at lnum:col is inside a string.
82773a97c2SBram Moolenaarfunction s:IsInString(lnum, col)
83773a97c2SBram Moolenaar  return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_string
84773a97c2SBram Moolenaarendfunction
85773a97c2SBram Moolenaar
86773a97c2SBram Moolenaar" Check if the character at lnum:col is inside a multi-line comment.
87773a97c2SBram Moolenaarfunction s:IsInMultilineComment(lnum, col)
88773a97c2SBram Moolenaar  return !s:IsLineComment(a:lnum, a:col) && synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_multiline
89773a97c2SBram Moolenaarendfunction
90773a97c2SBram Moolenaar
91773a97c2SBram Moolenaar" Check if the character at lnum:col is a line comment.
92773a97c2SBram Moolenaarfunction s:IsLineComment(lnum, col)
93773a97c2SBram Moolenaar  return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_linecom
94773a97c2SBram Moolenaarendfunction
95773a97c2SBram Moolenaar
96773a97c2SBram Moolenaar" Find line above 'lnum' that isn't empty, in a comment, or in a string.
97773a97c2SBram Moolenaarfunction s:PrevNonBlankNonString(lnum)
98773a97c2SBram Moolenaar  let in_block = 0
99773a97c2SBram Moolenaar  let lnum = prevnonblank(a:lnum)
100773a97c2SBram Moolenaar  while lnum > 0
101773a97c2SBram Moolenaar    " Go in and out of blocks comments as necessary.
102773a97c2SBram Moolenaar    " If the line isn't empty (with opt. comment) or in a string, end search.
103773a97c2SBram Moolenaar    let line = getline(lnum)
104773a97c2SBram Moolenaar    if line =~ '/\*'
105773a97c2SBram Moolenaar      if in_block
106773a97c2SBram Moolenaar        let in_block = 0
107773a97c2SBram Moolenaar      else
108773a97c2SBram Moolenaar        break
109773a97c2SBram Moolenaar      endif
110773a97c2SBram Moolenaar    elseif !in_block && line =~ '\*/'
111773a97c2SBram Moolenaar      let in_block = 1
112773a97c2SBram Moolenaar    elseif !in_block && line !~ '^\s*\%(//\).*$' && !(s:IsInStringOrComment(lnum, 1) && s:IsInStringOrComment(lnum, strlen(line)))
113773a97c2SBram Moolenaar      break
114773a97c2SBram Moolenaar    endif
115773a97c2SBram Moolenaar    let lnum = prevnonblank(lnum - 1)
116773a97c2SBram Moolenaar  endwhile
117773a97c2SBram Moolenaar  return lnum
118773a97c2SBram Moolenaarendfunction
119773a97c2SBram Moolenaar
120773a97c2SBram Moolenaar" Find line above 'lnum' that started the continuation 'lnum' may be part of.
121773a97c2SBram Moolenaarfunction s:GetMSL(lnum, in_one_line_scope)
122773a97c2SBram Moolenaar  " Start on the line we're at and use its indent.
123773a97c2SBram Moolenaar  let msl = a:lnum
124773a97c2SBram Moolenaar  let lnum = s:PrevNonBlankNonString(a:lnum - 1)
125773a97c2SBram Moolenaar  while lnum > 0
126773a97c2SBram Moolenaar    " If we have a continuation line, or we're in a string, use line as MSL.
127773a97c2SBram Moolenaar    " Otherwise, terminate search as we have found our MSL already.
128773a97c2SBram Moolenaar    let line = getline(lnum)
129773a97c2SBram Moolenaar    let col = match(line, s:msl_regex) + 1
130773a97c2SBram Moolenaar    if (col > 0 && !s:IsInStringOrComment(lnum, col)) || s:IsInString(lnum, strlen(line))
131773a97c2SBram Moolenaar      let msl = lnum
132773a97c2SBram Moolenaar    else
133773a97c2SBram Moolenaar      " Don't use lines that are part of a one line scope as msl unless the
134773a97c2SBram Moolenaar      " flag in_one_line_scope is set to 1
135773a97c2SBram Moolenaar      "
136773a97c2SBram Moolenaar      if a:in_one_line_scope
137773a97c2SBram Moolenaar        break
138773a97c2SBram Moolenaar      end
139773a97c2SBram Moolenaar      let msl_one_line = s:Match(lnum, s:one_line_scope_regex)
140773a97c2SBram Moolenaar      if msl_one_line == 0
141773a97c2SBram Moolenaar        break
142773a97c2SBram Moolenaar      endif
143773a97c2SBram Moolenaar    endif
144773a97c2SBram Moolenaar    let lnum = s:PrevNonBlankNonString(lnum - 1)
145773a97c2SBram Moolenaar  endwhile
146773a97c2SBram Moolenaar  return msl
147773a97c2SBram Moolenaarendfunction
148773a97c2SBram Moolenaar
149773a97c2SBram Moolenaarfunction s:RemoveTrailingComments(content)
150773a97c2SBram Moolenaar  let single = '\/\/\(.*\)\s*$'
151773a97c2SBram Moolenaar  let multi = '\/\*\(.*\)\*\/\s*$'
152773a97c2SBram Moolenaar  return substitute(substitute(a:content, single, '', ''), multi, '', '')
153773a97c2SBram Moolenaarendfunction
154773a97c2SBram Moolenaar
155773a97c2SBram Moolenaar" Find if the string is inside var statement (but not the first string)
156773a97c2SBram Moolenaarfunction s:InMultiVarStatement(lnum)
157773a97c2SBram Moolenaar  let lnum = s:PrevNonBlankNonString(a:lnum - 1)
158773a97c2SBram Moolenaar
159773a97c2SBram Moolenaar"  let type = synIDattr(synID(lnum, indent(lnum) + 1, 0), 'name')
160773a97c2SBram Moolenaar
161773a97c2SBram Moolenaar  " loop through previous expressions to find a var statement
162773a97c2SBram Moolenaar  while lnum > 0
163773a97c2SBram Moolenaar    let line = getline(lnum)
164773a97c2SBram Moolenaar
165773a97c2SBram Moolenaar    " if the line is a js keyword
166773a97c2SBram Moolenaar    if (line =~ s:js_keywords)
167773a97c2SBram Moolenaar      " check if the line is a var stmt
168773a97c2SBram Moolenaar      " if the line has a comma first or comma last then we can assume that we
169773a97c2SBram Moolenaar      " are in a multiple var statement
170773a97c2SBram Moolenaar      if (line =~ s:var_stmt)
171773a97c2SBram Moolenaar        return lnum
172773a97c2SBram Moolenaar      endif
173773a97c2SBram Moolenaar
174773a97c2SBram Moolenaar      " other js keywords, not a var
175773a97c2SBram Moolenaar      return 0
176773a97c2SBram Moolenaar    endif
177773a97c2SBram Moolenaar
178773a97c2SBram Moolenaar    let lnum = s:PrevNonBlankNonString(lnum - 1)
179773a97c2SBram Moolenaar  endwhile
180773a97c2SBram Moolenaar
181773a97c2SBram Moolenaar  " beginning of program, not a var
182773a97c2SBram Moolenaar  return 0
183773a97c2SBram Moolenaarendfunction
184773a97c2SBram Moolenaar
185773a97c2SBram Moolenaar" Find line above with beginning of the var statement or returns 0 if it's not
186773a97c2SBram Moolenaar" this statement
187773a97c2SBram Moolenaarfunction s:GetVarIndent(lnum)
188773a97c2SBram Moolenaar  let lvar = s:InMultiVarStatement(a:lnum)
189773a97c2SBram Moolenaar  let prev_lnum = s:PrevNonBlankNonString(a:lnum - 1)
190773a97c2SBram Moolenaar
191773a97c2SBram Moolenaar  if lvar
192773a97c2SBram Moolenaar    let line = s:RemoveTrailingComments(getline(prev_lnum))
193773a97c2SBram Moolenaar
194773a97c2SBram Moolenaar    " if the previous line doesn't end in a comma, return to regular indent
195773a97c2SBram Moolenaar    if (line !~ s:comma_last)
196773a97c2SBram Moolenaar      return indent(prev_lnum) - shiftwidth()
197773a97c2SBram Moolenaar    else
198773a97c2SBram Moolenaar      return indent(lvar) + shiftwidth()
199773a97c2SBram Moolenaar    endif
200773a97c2SBram Moolenaar  endif
201773a97c2SBram Moolenaar
202773a97c2SBram Moolenaar  return -1
203773a97c2SBram Moolenaarendfunction
204773a97c2SBram Moolenaar
205773a97c2SBram Moolenaar
206773a97c2SBram Moolenaar" Check if line 'lnum' has more opening brackets than closing ones.
207773a97c2SBram Moolenaarfunction s:LineHasOpeningBrackets(lnum)
208773a97c2SBram Moolenaar  let open_0 = 0
209773a97c2SBram Moolenaar  let open_2 = 0
210773a97c2SBram Moolenaar  let open_4 = 0
211773a97c2SBram Moolenaar  let line = getline(a:lnum)
212773a97c2SBram Moolenaar  let pos = match(line, '[][(){}]', 0)
213773a97c2SBram Moolenaar  while pos != -1
214773a97c2SBram Moolenaar    if !s:IsInStringOrComment(a:lnum, pos + 1)
215773a97c2SBram Moolenaar      let idx = stridx('(){}[]', line[pos])
216773a97c2SBram Moolenaar      if idx % 2 == 0
217773a97c2SBram Moolenaar        let open_{idx} = open_{idx} + 1
218773a97c2SBram Moolenaar      else
219773a97c2SBram Moolenaar        let open_{idx - 1} = open_{idx - 1} - 1
220773a97c2SBram Moolenaar      endif
221773a97c2SBram Moolenaar    endif
222773a97c2SBram Moolenaar    let pos = match(line, '[][(){}]', pos + 1)
223773a97c2SBram Moolenaar  endwhile
224773a97c2SBram Moolenaar  return (open_0 > 0) . (open_2 > 0) . (open_4 > 0)
225773a97c2SBram Moolenaarendfunction
226773a97c2SBram Moolenaar
227773a97c2SBram Moolenaarfunction s:Match(lnum, regex)
228773a97c2SBram Moolenaar  let col = match(getline(a:lnum), a:regex) + 1
229773a97c2SBram Moolenaar  return col > 0 && !s:IsInStringOrComment(a:lnum, col) ? col : 0
230773a97c2SBram Moolenaarendfunction
231773a97c2SBram Moolenaar
232773a97c2SBram Moolenaarfunction s:IndentWithContinuation(lnum, ind, width)
233773a97c2SBram Moolenaar  " Set up variables to use and search for MSL to the previous line.
234773a97c2SBram Moolenaar  let p_lnum = a:lnum
235773a97c2SBram Moolenaar  let lnum = s:GetMSL(a:lnum, 1)
236773a97c2SBram Moolenaar  let line = getline(lnum)
237773a97c2SBram Moolenaar
238773a97c2SBram Moolenaar  " If the previous line wasn't a MSL and is continuation return its indent.
239773a97c2SBram Moolenaar  " TODO: the || s:IsInString() thing worries me a bit.
240773a97c2SBram Moolenaar  if p_lnum != lnum
241773a97c2SBram Moolenaar    if s:Match(p_lnum,s:continuation_regex)||s:IsInString(p_lnum,strlen(line))
242773a97c2SBram Moolenaar      return a:ind
243773a97c2SBram Moolenaar    endif
244773a97c2SBram Moolenaar  endif
245773a97c2SBram Moolenaar
246773a97c2SBram Moolenaar  " Set up more variables now that we know we aren't continuation bound.
247773a97c2SBram Moolenaar  let msl_ind = indent(lnum)
248773a97c2SBram Moolenaar
249773a97c2SBram Moolenaar  " If the previous line ended with [*+/.-=], start a continuation that
250773a97c2SBram Moolenaar  " indents an extra level.
251773a97c2SBram Moolenaar  if s:Match(lnum, s:continuation_regex)
252773a97c2SBram Moolenaar    if lnum == p_lnum
253773a97c2SBram Moolenaar      return msl_ind + a:width
254773a97c2SBram Moolenaar    else
255773a97c2SBram Moolenaar      return msl_ind
256773a97c2SBram Moolenaar    endif
257773a97c2SBram Moolenaar  endif
258773a97c2SBram Moolenaar
259773a97c2SBram Moolenaar  return a:ind
260773a97c2SBram Moolenaarendfunction
261773a97c2SBram Moolenaar
262773a97c2SBram Moolenaarfunction s:InOneLineScope(lnum)
263773a97c2SBram Moolenaar  let msl = s:GetMSL(a:lnum, 1)
264773a97c2SBram Moolenaar  if msl > 0 && s:Match(msl, s:one_line_scope_regex)
265773a97c2SBram Moolenaar    return msl
266773a97c2SBram Moolenaar  endif
267773a97c2SBram Moolenaar  return 0
268773a97c2SBram Moolenaarendfunction
269773a97c2SBram Moolenaar
270773a97c2SBram Moolenaarfunction s:ExitingOneLineScope(lnum)
271773a97c2SBram Moolenaar  let msl = s:GetMSL(a:lnum, 1)
272773a97c2SBram Moolenaar  if msl > 0
273773a97c2SBram Moolenaar    " if the current line is in a one line scope ..
274773a97c2SBram Moolenaar    if s:Match(msl, s:one_line_scope_regex)
275773a97c2SBram Moolenaar      return 0
276773a97c2SBram Moolenaar    else
277773a97c2SBram Moolenaar      let prev_msl = s:GetMSL(msl - 1, 1)
278773a97c2SBram Moolenaar      if s:Match(prev_msl, s:one_line_scope_regex)
279773a97c2SBram Moolenaar        return prev_msl
280773a97c2SBram Moolenaar      endif
281773a97c2SBram Moolenaar    endif
282773a97c2SBram Moolenaar  endif
283773a97c2SBram Moolenaar  return 0
284773a97c2SBram Moolenaarendfunction
285773a97c2SBram Moolenaar
286773a97c2SBram Moolenaar" 3. GetTypescriptIndent Function {{{1
287773a97c2SBram Moolenaar" =========================
288773a97c2SBram Moolenaar
289773a97c2SBram Moolenaarfunction GetTypescriptIndent()
290773a97c2SBram Moolenaar  " 3.1. Setup {{{2
291773a97c2SBram Moolenaar  " ----------
292773a97c2SBram Moolenaar
293773a97c2SBram Moolenaar  " Set up variables for restoring position in file.  Could use v:lnum here.
294773a97c2SBram Moolenaar  let vcol = col('.')
295773a97c2SBram Moolenaar
296773a97c2SBram Moolenaar  " 3.2. Work on the current line {{{2
297773a97c2SBram Moolenaar  " -----------------------------
298773a97c2SBram Moolenaar
299773a97c2SBram Moolenaar  let ind = -1
300773a97c2SBram Moolenaar  " Get the current line.
301773a97c2SBram Moolenaar  let line = getline(v:lnum)
302773a97c2SBram Moolenaar  " previous nonblank line number
303773a97c2SBram Moolenaar  let prevline = prevnonblank(v:lnum - 1)
304773a97c2SBram Moolenaar
305773a97c2SBram Moolenaar  " If we got a closing bracket on an empty line, find its match and indent
306773a97c2SBram Moolenaar  " according to it.  For parentheses we indent to its column - 1, for the
307773a97c2SBram Moolenaar  " others we indent to the containing line's MSL's level.  Return -1 if fail.
308773a97c2SBram Moolenaar  let col = matchend(line, '^\s*[],})]')
309773a97c2SBram Moolenaar  if col > 0 && !s:IsInStringOrComment(v:lnum, col)
310773a97c2SBram Moolenaar    call cursor(v:lnum, col)
311773a97c2SBram Moolenaar
312773a97c2SBram Moolenaar    let lvar = s:InMultiVarStatement(v:lnum)
313773a97c2SBram Moolenaar    if lvar
314773a97c2SBram Moolenaar      let prevline_contents = s:RemoveTrailingComments(getline(prevline))
315773a97c2SBram Moolenaar
316773a97c2SBram Moolenaar      " check for comma first
317773a97c2SBram Moolenaar      if (line[col - 1] =~ ',')
318773a97c2SBram Moolenaar        " if the previous line ends in comma or semicolon don't indent
319773a97c2SBram Moolenaar        if (prevline_contents =~ '[;,]\s*$')
320773a97c2SBram Moolenaar          return indent(s:GetMSL(line('.'), 0))
321773a97c2SBram Moolenaar        " get previous line indent, if it's comma first return prevline indent
322773a97c2SBram Moolenaar        elseif (prevline_contents =~ s:comma_first)
323773a97c2SBram Moolenaar          return indent(prevline)
324773a97c2SBram Moolenaar        " otherwise we indent 1 level
325773a97c2SBram Moolenaar        else
326773a97c2SBram Moolenaar          return indent(lvar) + shiftwidth()
327773a97c2SBram Moolenaar        endif
328773a97c2SBram Moolenaar      endif
329773a97c2SBram Moolenaar    endif
330773a97c2SBram Moolenaar
331773a97c2SBram Moolenaar
332773a97c2SBram Moolenaar    let bs = strpart('(){}[]', stridx(')}]', line[col - 1]) * 2, 2)
333773a97c2SBram Moolenaar    if searchpair(escape(bs[0], '\['), '', bs[1], 'bW', s:skip_expr) > 0
334773a97c2SBram Moolenaar      if line[col-1]==')' && col('.') != col('$') - 1
335773a97c2SBram Moolenaar        let ind = virtcol('.')-1
336773a97c2SBram Moolenaar      else
337773a97c2SBram Moolenaar        let ind = indent(s:GetMSL(line('.'), 0))
338773a97c2SBram Moolenaar      endif
339773a97c2SBram Moolenaar    endif
340773a97c2SBram Moolenaar    return ind
341773a97c2SBram Moolenaar  endif
342773a97c2SBram Moolenaar
343773a97c2SBram Moolenaar  " If the line is comma first, dedent 1 level
344773a97c2SBram Moolenaar  if (getline(prevline) =~ s:comma_first)
345773a97c2SBram Moolenaar    return indent(prevline) - shiftwidth()
346773a97c2SBram Moolenaar  endif
347773a97c2SBram Moolenaar
348773a97c2SBram Moolenaar  if (line =~ s:ternary)
349773a97c2SBram Moolenaar    if (getline(prevline) =~ s:ternary_q)
350773a97c2SBram Moolenaar      return indent(prevline)
351773a97c2SBram Moolenaar    else
352773a97c2SBram Moolenaar      return indent(prevline) + shiftwidth()
353773a97c2SBram Moolenaar    endif
354773a97c2SBram Moolenaar  endif
355773a97c2SBram Moolenaar
356773a97c2SBram Moolenaar  " If we are in a multi-line comment, cindent does the right thing.
357773a97c2SBram Moolenaar  if s:IsInMultilineComment(v:lnum, 1) && !s:IsLineComment(v:lnum, 1)
358773a97c2SBram Moolenaar    return cindent(v:lnum)
359773a97c2SBram Moolenaar  endif
360773a97c2SBram Moolenaar
361773a97c2SBram Moolenaar  " Check for multiple var assignments
362773a97c2SBram Moolenaar"  let var_indent = s:GetVarIndent(v:lnum)
363773a97c2SBram Moolenaar"  if var_indent >= 0
364773a97c2SBram Moolenaar"    return var_indent
365773a97c2SBram Moolenaar"  endif
366773a97c2SBram Moolenaar
367773a97c2SBram Moolenaar  " 3.3. Work on the previous line. {{{2
368773a97c2SBram Moolenaar  " -------------------------------
369773a97c2SBram Moolenaar
370773a97c2SBram Moolenaar  " If the line is empty and the previous nonblank line was a multi-line
371773a97c2SBram Moolenaar  " comment, use that comment's indent. Deduct one char to account for the
372773a97c2SBram Moolenaar  " space in ' */'.
373773a97c2SBram Moolenaar  if line =~ '^\s*$' && s:IsInMultilineComment(prevline, 1)
374773a97c2SBram Moolenaar    return indent(prevline) - 1
375773a97c2SBram Moolenaar  endif
376773a97c2SBram Moolenaar
377773a97c2SBram Moolenaar  " Find a non-blank, non-multi-line string line above the current line.
378773a97c2SBram Moolenaar  let lnum = s:PrevNonBlankNonString(v:lnum - 1)
379773a97c2SBram Moolenaar
380773a97c2SBram Moolenaar  " If the line is empty and inside a string, use the previous line.
381773a97c2SBram Moolenaar  if line =~ '^\s*$' && lnum != prevline
382773a97c2SBram Moolenaar    return indent(prevnonblank(v:lnum))
383773a97c2SBram Moolenaar  endif
384773a97c2SBram Moolenaar
385773a97c2SBram Moolenaar  " At the start of the file use zero indent.
386773a97c2SBram Moolenaar  if lnum == 0
387773a97c2SBram Moolenaar    return 0
388773a97c2SBram Moolenaar  endif
389773a97c2SBram Moolenaar
390773a97c2SBram Moolenaar  " Set up variables for current line.
391773a97c2SBram Moolenaar  let line = getline(lnum)
392773a97c2SBram Moolenaar  let ind = indent(lnum)
393773a97c2SBram Moolenaar
394773a97c2SBram Moolenaar  " If the previous line ended with a block opening, add a level of indent.
395773a97c2SBram Moolenaar  if s:Match(lnum, s:block_regex)
396773a97c2SBram Moolenaar    return indent(s:GetMSL(lnum, 0)) + shiftwidth()
397773a97c2SBram Moolenaar  endif
398773a97c2SBram Moolenaar
399773a97c2SBram Moolenaar  " If the previous line contained an opening bracket, and we are still in it,
400773a97c2SBram Moolenaar  " add indent depending on the bracket type.
401773a97c2SBram Moolenaar  if line =~ '[[({]'
402773a97c2SBram Moolenaar    let counts = s:LineHasOpeningBrackets(lnum)
403773a97c2SBram Moolenaar    if counts[0] == '1' && searchpair('(', '', ')', 'bW', s:skip_expr) > 0
404773a97c2SBram Moolenaar      if col('.') + 1 == col('$')
405773a97c2SBram Moolenaar        return ind + shiftwidth()
406773a97c2SBram Moolenaar      else
407773a97c2SBram Moolenaar        return virtcol('.')
408773a97c2SBram Moolenaar      endif
409773a97c2SBram Moolenaar    elseif counts[1] == '1' || counts[2] == '1'
410773a97c2SBram Moolenaar      return ind + shiftwidth()
411773a97c2SBram Moolenaar    else
412773a97c2SBram Moolenaar      call cursor(v:lnum, vcol)
413773a97c2SBram Moolenaar    end
414773a97c2SBram Moolenaar  endif
415773a97c2SBram Moolenaar
416773a97c2SBram Moolenaar  " 3.4. Work on the MSL line. {{{2
417773a97c2SBram Moolenaar  " --------------------------
418773a97c2SBram Moolenaar
419773a97c2SBram Moolenaar  let ind_con = ind
420773a97c2SBram Moolenaar  let ind = s:IndentWithContinuation(lnum, ind_con, shiftwidth())
421773a97c2SBram Moolenaar
422773a97c2SBram Moolenaar  " }}}2
423773a97c2SBram Moolenaar  "
424773a97c2SBram Moolenaar  "
425773a97c2SBram Moolenaar  let ols = s:InOneLineScope(lnum)
426773a97c2SBram Moolenaar  if ols > 0
427773a97c2SBram Moolenaar    let ind = ind + shiftwidth()
428773a97c2SBram Moolenaar  else
429773a97c2SBram Moolenaar    let ols = s:ExitingOneLineScope(lnum)
430773a97c2SBram Moolenaar    while ols > 0 && ind > 0
431773a97c2SBram Moolenaar      let ind = ind - shiftwidth()
432773a97c2SBram Moolenaar      let ols = s:InOneLineScope(ols - 1)
433773a97c2SBram Moolenaar    endwhile
434773a97c2SBram Moolenaar  endif
435773a97c2SBram Moolenaar
436773a97c2SBram Moolenaar  return ind
437773a97c2SBram Moolenaarendfunction
438773a97c2SBram Moolenaar
439773a97c2SBram Moolenaar" }}}1
440773a97c2SBram Moolenaar
441773a97c2SBram Moolenaarlet &cpo = s:cpo_save
442773a97c2SBram Moolenaarunlet s:cpo_save
443773a97c2SBram Moolenaar
444773a97c2SBram Moolenaarfunction! Fixedgq(lnum, count)
44596f45c0bSBram Moolenaar    let l:tw = &tw ? &tw : 80
446773a97c2SBram Moolenaar
447773a97c2SBram Moolenaar    let l:count = a:count
448773a97c2SBram Moolenaar    let l:first_char = indent(a:lnum) + 1
449773a97c2SBram Moolenaar
450773a97c2SBram Moolenaar    if mode() == 'i' " gq was not pressed, but tw was set
451773a97c2SBram Moolenaar        return 1
452773a97c2SBram Moolenaar    endif
453773a97c2SBram Moolenaar
454773a97c2SBram Moolenaar    " This gq is only meant to do code with strings, not comments
455773a97c2SBram Moolenaar    if s:IsLineComment(a:lnum, l:first_char) || s:IsInMultilineComment(a:lnum, l:first_char)
456773a97c2SBram Moolenaar        return 1
457773a97c2SBram Moolenaar    endif
458773a97c2SBram Moolenaar
459773a97c2SBram Moolenaar    if len(getline(a:lnum)) < l:tw && l:count == 1 " No need for gq
460773a97c2SBram Moolenaar        return 1
461773a97c2SBram Moolenaar    endif
462773a97c2SBram Moolenaar
463*6c391a74SBram Moolenaar    " Put all the lines on one line and do normal splitting after that
464773a97c2SBram Moolenaar    if l:count > 1
465773a97c2SBram Moolenaar        while l:count > 1
466773a97c2SBram Moolenaar            let l:count -= 1
467773a97c2SBram Moolenaar            normal J
468773a97c2SBram Moolenaar        endwhile
469773a97c2SBram Moolenaar    endif
470773a97c2SBram Moolenaar
471773a97c2SBram Moolenaar    let l:winview = winsaveview()
472773a97c2SBram Moolenaar
473773a97c2SBram Moolenaar    call cursor(a:lnum, l:tw + 1)
474773a97c2SBram Moolenaar    let orig_breakpoint = searchpairpos(' ', '', '\.', 'bcW', '', a:lnum)
475773a97c2SBram Moolenaar    call cursor(a:lnum, l:tw + 1)
476773a97c2SBram Moolenaar    let breakpoint = searchpairpos(' ', '', '\.', 'bcW', s:skip_expr, a:lnum)
477773a97c2SBram Moolenaar
478773a97c2SBram Moolenaar    " No need for special treatment, normal gq handles edgecases better
479773a97c2SBram Moolenaar    if breakpoint[1] == orig_breakpoint[1]
480773a97c2SBram Moolenaar        call winrestview(l:winview)
481773a97c2SBram Moolenaar        return 1
482773a97c2SBram Moolenaar    endif
483773a97c2SBram Moolenaar
484773a97c2SBram Moolenaar    " Try breaking after string
485773a97c2SBram Moolenaar    if breakpoint[1] <= indent(a:lnum)
486773a97c2SBram Moolenaar        call cursor(a:lnum, l:tw + 1)
487773a97c2SBram Moolenaar        let breakpoint = searchpairpos('\.', '', ' ', 'cW', s:skip_expr, a:lnum)
488773a97c2SBram Moolenaar    endif
489773a97c2SBram Moolenaar
490773a97c2SBram Moolenaar
491773a97c2SBram Moolenaar    if breakpoint[1] != 0
492773a97c2SBram Moolenaar        call feedkeys("r\<CR>")
493773a97c2SBram Moolenaar    else
494773a97c2SBram Moolenaar        let l:count = l:count - 1
495773a97c2SBram Moolenaar    endif
496773a97c2SBram Moolenaar
497773a97c2SBram Moolenaar    " run gq on new lines
498773a97c2SBram Moolenaar    if l:count == 1
499773a97c2SBram Moolenaar        call feedkeys("gqq")
500773a97c2SBram Moolenaar    endif
501773a97c2SBram Moolenaar
502773a97c2SBram Moolenaar    return 0
503773a97c2SBram Moolenaarendfunction
504