blob: efc98a2851b8c7d95bc8272af842c9997d3ff068 [file] [log] [blame]
Bram Moolenaar6aa57292021-08-14 21:25:52 +02001" Vim indent file
2" Language: Julia
3" Maintainer: Carlo Baldassi <carlobaldassi@gmail.com>
4" Homepage: https://github.com/JuliaEditorSupport/julia-vim
Bram Moolenaard592deb2022-06-17 15:42:40 +01005" Last Change: 2022 Jun 14
dkearns0382f052023-08-29 05:32:59 +10006" 2023 Aug 28 by Vim Project (undo_indent)
7" Notes: originally based on Bram Moolenaar's indent file for vim
Bram Moolenaar6aa57292021-08-14 21:25:52 +02008
Bram Moolenaard592deb2022-06-17 15:42:40 +01009" Only load this indent file when no other was loaded.
10if exists("b:did_indent")
11 finish
12endif
13let b:did_indent = 1
14
Bram Moolenaar6aa57292021-08-14 21:25:52 +020015setlocal autoindent
16
17setlocal indentexpr=GetJuliaIndent()
18setlocal indentkeys+==end,=else,=catch,=finally,),],}
19setlocal indentkeys-=0#
20setlocal indentkeys-=:
21setlocal indentkeys-=0{
22setlocal indentkeys-=0}
23setlocal nosmartindent
24
dkearns0382f052023-08-29 05:32:59 +100025let b:undo_indent = "setl ai< inde< indk< si<"
26
Bram Moolenaar6aa57292021-08-14 21:25:52 +020027" Only define the function once.
28if exists("*GetJuliaIndent")
29 finish
30endif
31
32let s:skipPatternsBasic = '\<julia\%(Comment\%([LM]\|Delim\)\)\>'
33let s:skipPatterns = '\<julia\%(Comprehension\%(For\|If\)\|RangeKeyword\|Comment\%([LM]\|Delim\)\|\%([bs]\|Shell\|Printf\|Doc\)\?String\|StringPrefixed\|DocStringM\(Raw\)\?\|RegEx\|SymbolS\?\|Macro\|Dotted\)\>'
34
35function JuliaMatch(lnum, str, regex, st, ...)
36 let s = a:st
37 let e = a:0 > 0 ? a:1 : -1
38 let basic_skip = a:0 > 1 ? a:2 : 'all'
39 let skip = basic_skip ==# 'basic' ? s:skipPatternsBasic : s:skipPatterns
40 while 1
41 let f = match(a:str, '\C' . a:regex, s)
42 if e >= 0 && f >= e
43 return -1
44 endif
45 if f >= 0
46 let attr = synIDattr(synID(a:lnum,f+1,1),"name")
47 let attrT = synIDattr(synID(a:lnum,f+1,0),"name")
48 if attr =~# skip || attrT =~# skip
49 let s = f+1
50 continue
51 endif
52 endif
53 break
54 endwhile
55 return f
56endfunction
57
58function GetJuliaNestingStruct(lnum, ...)
59 " Auxiliary function to inspect the block structure of a line
60 let line = getline(a:lnum)
61 let s = a:0 > 0 ? a:1 : 0
62 let e = a:0 > 1 ? a:2 : -1
63 let blocks_stack = []
64 let num_closed_blocks = 0
65 while 1
66 let fb = JuliaMatch(a:lnum, line, '\<\%(if\|else\%(if\)\?\|while\|for\|try\|catch\|finally\|\%(staged\)\?function\|macro\|begin\|mutable\s\+struct\|\%(mutable\s\+\)\@<!struct\|\%(abstract\|primitive\)\s\+type\|let\|\%(bare\)\?module\|quote\|do\)\>', s, e)
67 let fe = JuliaMatch(a:lnum, line, '\<end\>', s, e)
68
69 if fb < 0 && fe < 0
70 " No blocks found
71 break
72 end
73
74 if fb >= 0 && (fb < fe || fe < 0)
75 " The first occurrence is an opening block keyword
76 " Note: some keywords (elseif,else,catch,finally) are both
77 " closing blocks and opening new ones
78
79 let i = JuliaMatch(a:lnum, line, '\<if\>', s)
80 if i >= 0 && i == fb
81 let s = i+1
82 call add(blocks_stack, 'if')
83 continue
84 endif
85 let i = JuliaMatch(a:lnum, line, '\<elseif\>', s)
86 if i >= 0 && i == fb
87 let s = i+1
88 if len(blocks_stack) > 0 && blocks_stack[-1] == 'if'
89 let blocks_stack[-1] = 'elseif'
90 elseif (len(blocks_stack) > 0 && blocks_stack[-1] != 'elseif') || len(blocks_stack) == 0
91 call add(blocks_stack, 'elseif')
92 let num_closed_blocks += 1
93 endif
94 continue
95 endif
96 let i = JuliaMatch(a:lnum, line, '\<else\>', s)
97 if i >= 0 && i == fb
98 let s = i+1
99 if len(blocks_stack) > 0 && blocks_stack[-1] =~# '\<\%(else\)\=if\>'
100 let blocks_stack[-1] = 'else'
101 else
102 call add(blocks_stack, 'else')
103 let num_closed_blocks += 1
104 endif
105 continue
106 endif
107
108 let i = JuliaMatch(a:lnum, line, '\<try\>', s)
109 if i >= 0 && i == fb
110 let s = i+1
111 call add(blocks_stack, 'try')
112 continue
113 endif
114 let i = JuliaMatch(a:lnum, line, '\<catch\>', s)
115 if i >= 0 && i == fb
116 let s = i+1
117 if len(blocks_stack) > 0 && blocks_stack[-1] == 'try'
118 let blocks_stack[-1] = 'catch'
119 else
120 call add(blocks_stack, 'catch')
121 let num_closed_blocks += 1
122 endif
123 continue
124 endif
125 let i = JuliaMatch(a:lnum, line, '\<finally\>', s)
126 if i >= 0 && i == fb
127 let s = i+1
128 if len(blocks_stack) > 0 && (blocks_stack[-1] == 'try' || blocks_stack[-1] == 'catch')
129 let blocks_stack[-1] = 'finally'
130 else
131 call add(blocks_stack, 'finally')
132 let num_closed_blocks += 1
133 endif
134 continue
135 endif
136
137 let i = JuliaMatch(a:lnum, line, '\<\%(bare\)\?module\>', s)
138 if i >= 0 && i == fb
139 let s = i+1
140 if i == 0
141 call add(blocks_stack, 'col1module')
142 else
143 call add(blocks_stack, 'other')
144 endif
145 continue
146 endif
147
148 let i = JuliaMatch(a:lnum, line, '\<\%(while\|for\|function\|macro\|begin\|\%(mutable\s\+\)\?struct\|\%(abstract\|primitive\)\s\+type\|let\|quote\|do\)\>', s)
149 if i >= 0 && i == fb
150 if match(line, '\C\<\%(mutable\|abstract\|primitive\)', i) != -1
151 let s = i+11
152 else
153 let s = i+1
154 endif
155 call add(blocks_stack, 'other')
156 continue
157 endif
158
159 " Note: it should be impossible to get here
160 break
161
162 else
163 " The first occurrence is an 'end'
164
165 let s = fe+1
166 if len(blocks_stack) == 0
167 let num_closed_blocks += 1
168 else
169 call remove(blocks_stack, -1)
170 endif
171 continue
172
173 endif
174
175 " Note: it should be impossible to get here
176 break
177 endwhile
178 let num_open_blocks = len(blocks_stack) - count(blocks_stack, 'col1module')
179 return [num_open_blocks, num_closed_blocks]
180endfunction
181
182function GetJuliaNestingBrackets(lnum, c)
183 " Auxiliary function to inspect the brackets structure of a line
184 let line = getline(a:lnum)[0 : (a:c - 1)]
185 let s = 0
186 let brackets_stack = []
187 let last_closed_bracket = -1
188 while 1
189 let fb = JuliaMatch(a:lnum, line, '[([{]', s)
190 let fe = JuliaMatch(a:lnum, line, '[])}]', s)
191
192 if fb < 0 && fe < 0
193 " No brackets found
194 break
195 end
196
197 if fb >= 0 && (fb < fe || fe < 0)
198 " The first occurrence is an opening bracket
199
200 let i = JuliaMatch(a:lnum, line, '(', s)
201 if i >= 0 && i == fb
202 let s = i+1
203 call add(brackets_stack, ['par',i])
204 continue
205 endif
206
207 let i = JuliaMatch(a:lnum, line, '\[', s)
208 if i >= 0 && i == fb
209 let s = i+1
210 call add(brackets_stack, ['sqbra',i])
211 continue
212 endif
213
214 let i = JuliaMatch(a:lnum, line, '{', s)
215 if i >= 0 && i == fb
216 let s = i+1
217 call add(brackets_stack, ['curbra',i])
218 continue
219 endif
220
221 " Note: it should be impossible to get here
222 break
223
224 else
225 " The first occurrence is a closing bracket
226
227 let i = JuliaMatch(a:lnum, line, ')', s)
228 if i >= 0 && i == fe
229 let s = i+1
230 if len(brackets_stack) > 0 && brackets_stack[-1][0] == 'par'
231 call remove(brackets_stack, -1)
232 else
233 let last_closed_bracket = i + 1
234 endif
235 continue
236 endif
237
238 let i = JuliaMatch(a:lnum, line, ']', s)
239 if i >= 0 && i == fe
240 let s = i+1
241 if len(brackets_stack) > 0 && brackets_stack[-1][0] == 'sqbra'
242 call remove(brackets_stack, -1)
243 else
244 let last_closed_bracket = i + 1
245 endif
246 continue
247 endif
248
249 let i = JuliaMatch(a:lnum, line, '}', s)
250 if i >= 0 && i == fe
251 let s = i+1
252 if len(brackets_stack) > 0 && brackets_stack[-1][0] == 'curbra'
253 call remove(brackets_stack, -1)
254 else
255 let last_closed_bracket = i + 1
256 endif
257 continue
258 endif
259
260 " Note: it should be impossible to get here
261 break
262
263 endif
264
265 " Note: it should be impossible to get here
266 break
267 endwhile
268 let first_open_bracket = -1
269 let last_open_bracket = -1
270 let infuncargs = 0
271 if len(brackets_stack) > 0
272 let first_open_bracket = brackets_stack[0][1]
273 let last_open_bracket = brackets_stack[-1][1]
274 if brackets_stack[-1][0] == 'par' && IsFunctionArgPar(a:lnum, last_open_bracket+1)
275 let infuncargs = 1
276 endif
277 endif
278 return [first_open_bracket, last_open_bracket, last_closed_bracket, infuncargs]
279endfunction
280
281let s:bracketBlocks = '\<julia\%(\%(\%(Printf\)\?Par\|SqBra\%(Idx\)\?\|CurBra\)Block\|ParBlockInRange\|StringVars\%(Par\|SqBra\|CurBra\)\|Dollar\%(Par\|SqBra\)\|QuotedParBlockS\?\)\>'
282
283function IsInBrackets(lnum, c)
284 let stack = map(synstack(a:lnum, a:c), 'synIDattr(v:val, "name")')
285 call filter(stack, 'v:val =~# s:bracketBlocks')
286 return len(stack) > 0
287endfunction
288
289function IsInDocString(lnum)
290 let stack = map(synstack(a:lnum, 1), 'synIDattr(v:val, "name")')
291 call filter(stack, 'v:val =~# "\\<juliaDocString\\(Delim\\|M\\\(Raw\\)\\?\\)\\?\\>"')
292 return len(stack) > 0
293endfunction
294
295function IsInContinuationImportLine(lnum)
296 let stack = map(synstack(a:lnum, 1), 'synIDattr(v:val, "name")')
297 call filter(stack, 'v:val =~# "\\<juliaImportLine\\>"')
298 if len(stack) == 0
299 return 0
300 endif
301 return JuliaMatch(a:lnum, getline(a:lnum), '\<\%(import\|using\|export\)\>', indent(a:lnum)) == -1
302endfunction
303
304function IsFunctionArgPar(lnum, c)
305 if a:c == 0
306 return 0
307 endif
308 let stack = map(synstack(a:lnum, a:c-1), 'synIDattr(v:val, "name")')
309 return len(stack) >= 2 && stack[-2] ==# 'juliaFunctionDef'
310endfunction
311
312function JumpToMatch(lnum, last_closed_bracket)
Viktor Szépedbf749b2023-10-16 09:53:37 +0200313 " we use the % command to skip back (tries to use matchit if possible,
Bram Moolenaar6aa57292021-08-14 21:25:52 +0200314 " otherwise resorts to vim's default, which is buggy but better than
315 " nothing)
316 call cursor(a:lnum, a:last_closed_bracket)
317 let percmap = maparg("%", "n")
318 if exists("g:loaded_matchit") && percmap =~# 'Match\%(it\|_wrapper\)'
319 normal %
320 else
321 normal! %
322 end
323endfunction
324
325" Auxiliary function to find a line which does not start in the middle of a
326" multiline bracketed expression, to be used as reference for block
327" indentation.
328function LastBlockIndent(lnum)
329 let lnum = a:lnum
330 let ind = 0
331 while lnum > 0
332 let ind = indent(lnum)
333 if ind == 0
334 return [lnum, 0]
335 endif
336 if !IsInBrackets(lnum, 1)
337 break
338 endif
339 let lnum = prevnonblank(lnum - 1)
340 endwhile
341 return [max([lnum,1]), ind]
342endfunction
343
344function GetJuliaIndent()
345 " Do not alter doctrings indentation
346 if IsInDocString(v:lnum)
347 return -1
348 endif
349
350 " Find a non-blank line above the current line.
351 let lnum = prevnonblank(v:lnum - 1)
352
353 " At the start of the file use zero indent.
354 if lnum == 0
355 return 0
356 endif
357
358 let ind = -1
359 let st = -1
360 let lim = -1
361
362 " Multiline bracketed expressions take precedence
363 let align_brackets = get(g:, "julia_indent_align_brackets", 1)
364 let align_funcargs = get(g:, "julia_indent_align_funcargs", 0)
365 let c = len(getline(lnum)) + 1
366 while IsInBrackets(lnum, c)
367 let [first_open_bracket, last_open_bracket, last_closed_bracket, infuncargs] = GetJuliaNestingBrackets(lnum, c)
368
369 " First scenario: the previous line has a hanging open bracket:
370 " set the indentation to match the opening bracket (plus an extra space)
371 " unless we're in a function arguments list or alignment is disabled, in
372 " which case we just add an extra indent
373 if last_open_bracket != -1
374 if (!infuncargs && align_brackets) || (infuncargs && align_funcargs)
375 let st = last_open_bracket
376 let ind = virtcol([lnum, st + 1])
377 else
378 let ind = indent(lnum) + shiftwidth()
379 endif
380
381 " Second scenario: some multiline bracketed expression was closed in the
382 " previous line. But since we know we are still in a bracketed expression,
383 " we need to find the line where the bracket was opened
384 elseif last_closed_bracket != -1
385 call JumpToMatch(lnum, last_closed_bracket)
386 if line(".") == lnum
387 " something wrong here, give up
388 let ind = indent(lnum)
389 else
390 let lnum = line(".")
391 let c = col(".") - 1
392 if c == 0
393 " uhm, give up
394 let ind = 0
395 else
396 " we skipped a bracket set, keep searching for an opening bracket
397 let lim = c
398 continue
399 endif
400 endif
401
402 " Third scenario: nothing special: keep the indentation
403 else
404 let ind = indent(lnum)
405 endif
406
407 " Does the current line start with a closing bracket? Then depending on
408 " the situation we align it with the opening one, or we let the rest of
409 " the code figure it out (the case in which we're closing a function
410 " argument list is special-cased)
411 if JuliaMatch(v:lnum, getline(v:lnum), '[])}]', indent(v:lnum)) == indent(v:lnum) && ind > 0
412 if !align_brackets && !align_funcargs
413 call JumpToMatch(v:lnum, indent(v:lnum))
414 return indent(line("."))
415 elseif (align_brackets && getline(v:lnum)[indent(v:lnum)] != ')') || align_funcargs
416 return ind - 1
417 else " must be a ')' and align_brackets==1 and align_funcargs==0
418 call JumpToMatch(v:lnum, indent(v:lnum))
419 if IsFunctionArgPar(line("."), col("."))
420 let ind = -1
421 else
422 return ind - 1
423 endif
424 endif
425 endif
426
427 break
428 endwhile
429
430 if ind == -1
431 " We are not in a multiline bracketed expression. Thus we look for a
432 " previous line to use as a reference
433 let [lnum,ind] = LastBlockIndent(lnum)
434 let c = len(getline(lnum)) + 1
435 if IsInBrackets(lnum, c)
436 let [first_open_bracket, last_open_bracket, last_closed_bracket, infuncargs] = GetJuliaNestingBrackets(lnum, c)
437 let lim = first_open_bracket
438 endif
439 end
440
441 " Analyse the reference line
442 let [num_open_blocks, num_closed_blocks] = GetJuliaNestingStruct(lnum, st, lim)
443 " Increase indentation for each newly opened block in the reference line
444 let ind += shiftwidth() * num_open_blocks
445
446 " Analyse the current line
447 let [num_open_blocks, num_closed_blocks] = GetJuliaNestingStruct(v:lnum)
448 " Decrease indentation for each closed block in the current line
449 let ind -= shiftwidth() * num_closed_blocks
450
451 " Additional special case: multiline import/using/export statements
452
453 let prevline = getline(lnum)
454 " Are we in a multiline import/using/export statement, right below the
455 " opening line?
456 if IsInContinuationImportLine(v:lnum) && !IsInContinuationImportLine(lnum)
457 if get(g:, 'julia_indent_align_import', 1)
458 " if the opening line has a colon followed by non-comments, use it as
459 " reference point
460 let cind = JuliaMatch(lnum, prevline, ':', indent(lnum), lim)
461 if cind >= 0
462 let nonwhiteind = JuliaMatch(lnum, prevline, '\S', cind+1, -1, 'basic')
463 if nonwhiteind >= 0
464 " return match(prevline, '\S', cind+1) " a bit overkill...
465 return cind + 2
466 endif
467 else
468 " if the opening line is not a naked import/using/export statement, use
469 " it as reference
470 let iind = JuliaMatch(lnum, prevline, '\<import\|using\|export\>', indent(lnum), lim)
471 if iind >= 0
472 " assuming whitespace after using... so no `using(XYZ)` please!
473 let nonwhiteind = JuliaMatch(lnum, prevline, '\S', iind+6, -1, 'basic')
474 if nonwhiteind >= 0
475 return match(prevline, '\S', iind+6)
476 endif
477 endif
478 endif
479 endif
480 let ind += shiftwidth()
481
482 " Or did we just close a multiline import/using/export statement?
483 elseif !IsInContinuationImportLine(v:lnum) && IsInContinuationImportLine(lnum)
484 " find the starting line of the statement
485 let ilnum = 0
486 for iln in range(lnum-1, 1, -1)
487 if !IsInContinuationImportLine(iln)
488 let ilnum = iln
489 break
490 endif
491 endfor
492 if ilnum == 0
493 " something went horribly wrong, give up
494 let ind = indent(lnum)
495 endif
496 let ind = indent(ilnum)
497 endif
498
499 return ind
500endfunction