Bram Moolenaar | 3577c6f | 2008-06-24 21:16:56 +0000 | [diff] [blame] | 1 | " Vim indent file |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 2 | " Language: Erlang (http://www.erlang.org) |
Bram Moolenaar | 6be7f87 | 2012-01-20 21:08:56 +0100 | [diff] [blame] | 3 | " Author: Csaba Hoch <csaba.hoch@gmail.com> |
| 4 | " Contributors: Edwin Fine <efine145_nospam01 at usa dot net> |
| 5 | " Pawel 'kTT' Salata <rockplayer.pl@gmail.com> |
| 6 | " Ricardo Catalinas Jiménez <jimenezrick@gmail.com> |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 7 | " Last Update: 2022-Sep-06 |
Bram Moolenaar | 6be7f87 | 2012-01-20 21:08:56 +0100 | [diff] [blame] | 8 | " License: Vim license |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 9 | " URL: https://github.com/vim-erlang/vim-erlang-runtime |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 10 | |
| 11 | " Note About Usage: |
| 12 | " This indentation script works best with the Erlang syntax file created by |
| 13 | " Kreąimir Marľić (Kresimir Marzic) and maintained by Csaba Hoch. |
| 14 | |
| 15 | " Notes About Implementation: |
| 16 | " |
| 17 | " - LTI = Line to indent. |
| 18 | " - The index of the first line is 1, but the index of the first column is 0. |
| 19 | |
| 20 | |
| 21 | " Initialization {{{1 |
| 22 | " ============== |
Bram Moolenaar | 3577c6f | 2008-06-24 21:16:56 +0000 | [diff] [blame] | 23 | |
Bram Moolenaar | 6be7f87 | 2012-01-20 21:08:56 +0100 | [diff] [blame] | 24 | " Only load this indent file when no other was loaded |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 25 | " Vim 7 or later is needed |
| 26 | if exists("b:did_indent") || version < 700 |
| 27 | finish |
Bram Moolenaar | 6be7f87 | 2012-01-20 21:08:56 +0100 | [diff] [blame] | 28 | else |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 29 | let b:did_indent = 1 |
Bram Moolenaar | 3577c6f | 2008-06-24 21:16:56 +0000 | [diff] [blame] | 30 | endif |
Bram Moolenaar | 3577c6f | 2008-06-24 21:16:56 +0000 | [diff] [blame] | 31 | |
| 32 | setlocal indentexpr=ErlangIndent() |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 33 | setlocal indentkeys+=0=end,0=of,0=catch,0=after,0=else,0=when,0=),0=],0=},0=>> |
| 34 | |
| 35 | let b:undo_indent = "setl inde< indk<" |
Bram Moolenaar | 3577c6f | 2008-06-24 21:16:56 +0000 | [diff] [blame] | 36 | |
Bram Moolenaar | 6be7f87 | 2012-01-20 21:08:56 +0100 | [diff] [blame] | 37 | " Only define the functions once |
Bram Moolenaar | 3577c6f | 2008-06-24 21:16:56 +0000 | [diff] [blame] | 38 | if exists("*ErlangIndent") |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 39 | finish |
Bram Moolenaar | 3577c6f | 2008-06-24 21:16:56 +0000 | [diff] [blame] | 40 | endif |
| 41 | |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 42 | let s:cpo_save = &cpo |
| 43 | set cpo&vim |
Bram Moolenaar | 3577c6f | 2008-06-24 21:16:56 +0000 | [diff] [blame] | 44 | |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 45 | " Logging library {{{1 |
| 46 | " =============== |
Bram Moolenaar | 3577c6f | 2008-06-24 21:16:56 +0000 | [diff] [blame] | 47 | |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 48 | " Purpose: |
| 49 | " Logs the given string using the ErlangIndentLog function if it exists. |
| 50 | " Parameters: |
| 51 | " s: string |
| 52 | function! s:Log(s) |
| 53 | if exists("*ErlangIndentLog") |
| 54 | call ErlangIndentLog(a:s) |
| 55 | endif |
Bram Moolenaar | 3577c6f | 2008-06-24 21:16:56 +0000 | [diff] [blame] | 56 | endfunction |
| 57 | |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 58 | " Line tokenizer library {{{1 |
| 59 | " ====================== |
Bram Moolenaar | 3577c6f | 2008-06-24 21:16:56 +0000 | [diff] [blame] | 60 | |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 61 | " Indtokens are "indentation tokens". See their exact format in the |
Bram Moolenaar | 6c391a7 | 2021-09-09 21:55:11 +0200 | [diff] [blame] | 62 | " documentation of the s:GetTokensFromLine function. |
Bram Moolenaar | 3577c6f | 2008-06-24 21:16:56 +0000 | [diff] [blame] | 63 | |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 64 | " Purpose: |
| 65 | " Calculate the new virtual column after the given segment of a line. |
| 66 | " Parameters: |
| 67 | " line: string |
| 68 | " first_index: integer -- the index of the first character of the segment |
| 69 | " last_index: integer -- the index of the last character of the segment |
| 70 | " vcol: integer -- the virtual column of the first character of the token |
| 71 | " tabstop: integer -- the value of the 'tabstop' option to be used |
| 72 | " Returns: |
| 73 | " vcol: integer |
| 74 | " Example: |
| 75 | " " index: 0 12 34567 |
| 76 | " " vcol: 0 45 89 |
| 77 | " s:CalcVCol("\t'\tx', b", 1, 4, 4) -> 10 |
| 78 | function! s:CalcVCol(line, first_index, last_index, vcol, tabstop) |
Bram Moolenaar | 3577c6f | 2008-06-24 21:16:56 +0000 | [diff] [blame] | 79 | |
Bram Moolenaar | 6c391a7 | 2021-09-09 21:55:11 +0200 | [diff] [blame] | 80 | " We copy the relevant segment of the line, otherwise if the line were |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 81 | " e.g. `"\t", term` then the else branch below would consume the `", term` |
| 82 | " part at once. |
| 83 | let line = a:line[a:first_index : a:last_index] |
Bram Moolenaar | 3577c6f | 2008-06-24 21:16:56 +0000 | [diff] [blame] | 84 | |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 85 | let i = 0 |
| 86 | let last_index = a:last_index - a:first_index |
| 87 | let vcol = a:vcol |
Bram Moolenaar | 3577c6f | 2008-06-24 21:16:56 +0000 | [diff] [blame] | 88 | |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 89 | while 0 <= i && i <= last_index |
| 90 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 91 | if line[i] ==# "\t" |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 92 | " Example (when tabstop == 4): |
| 93 | " |
| 94 | " vcol + tab -> next_vcol |
| 95 | " 0 + tab -> 4 |
| 96 | " 1 + tab -> 4 |
| 97 | " 2 + tab -> 4 |
| 98 | " 3 + tab -> 4 |
| 99 | " 4 + tab -> 8 |
| 100 | " |
| 101 | " next_i - i == the number of tabs |
| 102 | let next_i = matchend(line, '\t*', i + 1) |
| 103 | let vcol = (vcol / a:tabstop + (next_i - i)) * a:tabstop |
| 104 | call s:Log('new vcol after tab: '. vcol) |
Bram Moolenaar | 6be7f87 | 2012-01-20 21:08:56 +0100 | [diff] [blame] | 105 | else |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 106 | let next_i = matchend(line, '[^\t]*', i + 1) |
| 107 | let vcol += next_i - i |
| 108 | call s:Log('new vcol after other: '. vcol) |
Bram Moolenaar | 6be7f87 | 2012-01-20 21:08:56 +0100 | [diff] [blame] | 109 | endif |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 110 | let i = next_i |
| 111 | endwhile |
Bram Moolenaar | 3577c6f | 2008-06-24 21:16:56 +0000 | [diff] [blame] | 112 | |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 113 | return vcol |
Bram Moolenaar | 3577c6f | 2008-06-24 21:16:56 +0000 | [diff] [blame] | 114 | endfunction |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 115 | |
| 116 | " Purpose: |
| 117 | " Go through the whole line and return the tokens in the line. |
| 118 | " Parameters: |
| 119 | " line: string -- the line to be examined |
| 120 | " string_continuation: bool |
| 121 | " atom_continuation: bool |
| 122 | " Returns: |
| 123 | " indtokens = [indtoken] |
| 124 | " indtoken = [token, vcol, col] |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 125 | " token = string (examples: 'begin', '<quoted_atom>', '}') |
| 126 | " vcol = integer (the virtual column of the first character of the token; |
| 127 | " counting starts from 0) |
| 128 | " col = integer (counting starts from 0) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 129 | function! s:GetTokensFromLine(line, string_continuation, atom_continuation, |
| 130 | \tabstop) |
| 131 | |
| 132 | let linelen = strlen(a:line) " The length of the line |
| 133 | let i = 0 " The index of the current character in the line |
| 134 | let vcol = 0 " The virtual column of the current character |
| 135 | let indtokens = [] |
| 136 | |
| 137 | if a:string_continuation |
| 138 | let i = matchend(a:line, '^\%([^"\\]\|\\.\)*"', 0) |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 139 | if i ==# -1 |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 140 | call s:Log(' Whole line is string continuation -> ignore') |
| 141 | return [] |
| 142 | else |
| 143 | let vcol = s:CalcVCol(a:line, 0, i - 1, 0, a:tabstop) |
| 144 | call add(indtokens, ['<string_end>', vcol, i]) |
| 145 | endif |
| 146 | elseif a:atom_continuation |
| 147 | let i = matchend(a:line, "^\\%([^'\\\\]\\|\\\\.\\)*'", 0) |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 148 | if i ==# -1 |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 149 | call s:Log(' Whole line is quoted atom continuation -> ignore') |
| 150 | return [] |
| 151 | else |
| 152 | let vcol = s:CalcVCol(a:line, 0, i - 1, 0, a:tabstop) |
| 153 | call add(indtokens, ['<quoted_atom_end>', vcol, i]) |
| 154 | endif |
| 155 | endif |
| 156 | |
| 157 | while 0 <= i && i < linelen |
| 158 | |
| 159 | let next_vcol = '' |
| 160 | |
| 161 | " Spaces |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 162 | if a:line[i] ==# ' ' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 163 | let next_i = matchend(a:line, ' *', i + 1) |
| 164 | |
| 165 | " Tabs |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 166 | elseif a:line[i] ==# "\t" |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 167 | let next_i = matchend(a:line, '\t*', i + 1) |
| 168 | |
| 169 | " See example in s:CalcVCol |
| 170 | let next_vcol = (vcol / a:tabstop + (next_i - i)) * a:tabstop |
| 171 | |
| 172 | " Comment |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 173 | elseif a:line[i] ==# '%' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 174 | let next_i = linelen |
| 175 | |
| 176 | " String token: "..." |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 177 | elseif a:line[i] ==# '"' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 178 | let next_i = matchend(a:line, '\%([^"\\]\|\\.\)*"', i + 1) |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 179 | if next_i ==# -1 |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 180 | call add(indtokens, ['<string_start>', vcol, i]) |
| 181 | else |
| 182 | let next_vcol = s:CalcVCol(a:line, i, next_i - 1, vcol, a:tabstop) |
| 183 | call add(indtokens, ['<string>', vcol, i]) |
| 184 | endif |
| 185 | |
| 186 | " Quoted atom token: '...' |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 187 | elseif a:line[i] ==# "'" |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 188 | let next_i = matchend(a:line, "\\%([^'\\\\]\\|\\\\.\\)*'", i + 1) |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 189 | if next_i ==# -1 |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 190 | call add(indtokens, ['<quoted_atom_start>', vcol, i]) |
| 191 | else |
| 192 | let next_vcol = s:CalcVCol(a:line, i, next_i - 1, vcol, a:tabstop) |
| 193 | call add(indtokens, ['<quoted_atom>', vcol, i]) |
| 194 | endif |
| 195 | |
| 196 | " Keyword or atom or variable token or number |
| 197 | elseif a:line[i] =~# '[a-zA-Z_@0-9]' |
| 198 | let next_i = matchend(a:line, |
| 199 | \'[[:alnum:]_@:]*\%(\s*#\s*[[:alnum:]_@:]*\)\=', |
| 200 | \i + 1) |
| 201 | call add(indtokens, [a:line[(i):(next_i - 1)], vcol, i]) |
| 202 | |
| 203 | " Character token: $<char> (as in: $a) |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 204 | elseif a:line[i] ==# '$' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 205 | call add(indtokens, ['$.', vcol, i]) |
| 206 | let next_i = i + 2 |
| 207 | |
| 208 | " Dot token: . |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 209 | elseif a:line[i] ==# '.' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 210 | |
| 211 | let next_i = i + 1 |
| 212 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 213 | if i + 1 ==# linelen || a:line[i + 1] =~# '[[:blank:]%]' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 214 | " End of clause token: . (as in: f() -> ok.) |
| 215 | call add(indtokens, ['<end_of_clause>', vcol, i]) |
| 216 | |
| 217 | else |
| 218 | " Possibilities: |
| 219 | " - Dot token in float: . (as in: 3.14) |
| 220 | " - Dot token in record: . (as in: #myrec.myfield) |
| 221 | call add(indtokens, ['.', vcol, i]) |
| 222 | endif |
| 223 | |
| 224 | " Equal sign |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 225 | elseif a:line[i] ==# '=' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 226 | " This is handled separately so that "=<<" will be parsed as |
| 227 | " ['=', '<<'] instead of ['=<', '<']. Although Erlang parses it |
| 228 | " currently in the latter way, that may be fixed some day. |
| 229 | call add(indtokens, [a:line[i], vcol, i]) |
| 230 | let next_i = i + 1 |
| 231 | |
| 232 | " Three-character tokens |
| 233 | elseif i + 1 < linelen && |
| 234 | \ index(['=:=', '=/='], a:line[i : i + 1]) != -1 |
| 235 | call add(indtokens, [a:line[i : i + 1], vcol, i]) |
| 236 | let next_i = i + 2 |
| 237 | |
| 238 | " Two-character tokens |
| 239 | elseif i + 1 < linelen && |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 240 | \ index(['->', '<<', '>>', '||', '==', '/=', '=<', '>=', '?=', '++', |
| 241 | \ '--', '::'], |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 242 | \ a:line[i : i + 1]) != -1 |
| 243 | call add(indtokens, [a:line[i : i + 1], vcol, i]) |
| 244 | let next_i = i + 2 |
| 245 | |
| 246 | " Other character: , ; < > ( ) [ ] { } # + - * / : ? = ! | |
| 247 | else |
| 248 | call add(indtokens, [a:line[i], vcol, i]) |
| 249 | let next_i = i + 1 |
| 250 | |
| 251 | endif |
| 252 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 253 | if next_vcol ==# '' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 254 | let vcol += next_i - i |
| 255 | else |
| 256 | let vcol = next_vcol |
| 257 | endif |
| 258 | |
| 259 | let i = next_i |
| 260 | |
| 261 | endwhile |
| 262 | |
| 263 | return indtokens |
| 264 | |
| 265 | endfunction |
| 266 | |
| 267 | " TODO: doc, handle "not found" case |
| 268 | function! s:GetIndtokenAtCol(indtokens, col) |
| 269 | let i = 0 |
| 270 | while i < len(a:indtokens) |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 271 | if a:indtokens[i][2] ==# a:col |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 272 | return [1, i] |
| 273 | elseif a:indtokens[i][2] > a:col |
| 274 | return [0, s:IndentError('No token at col ' . a:col . ', ' . |
| 275 | \'indtokens = ' . string(a:indtokens), |
| 276 | \'', '')] |
| 277 | endif |
| 278 | let i += 1 |
| 279 | endwhile |
| 280 | return [0, s:IndentError('No token at col ' . a:col . ', ' . |
| 281 | \'indtokens = ' . string(a:indtokens), |
| 282 | \'', '')] |
| 283 | endfunction |
| 284 | |
| 285 | " Stack library {{{1 |
| 286 | " ============= |
| 287 | |
| 288 | " Purpose: |
| 289 | " Push a token onto the parser's stack. |
| 290 | " Parameters: |
| 291 | " stack: [token] |
| 292 | " token: string |
| 293 | function! s:Push(stack, token) |
| 294 | call s:Log(' Stack Push: "' . a:token . '" into ' . string(a:stack)) |
| 295 | call insert(a:stack, a:token) |
| 296 | endfunction |
| 297 | |
| 298 | " Purpose: |
| 299 | " Pop a token from the parser's stack. |
| 300 | " Parameters: |
| 301 | " stack: [token] |
| 302 | " token: string |
| 303 | " Returns: |
| 304 | " token: string -- the removed element |
| 305 | function! s:Pop(stack) |
| 306 | let head = remove(a:stack, 0) |
| 307 | call s:Log(' Stack Pop: "' . head . '" from ' . string(a:stack)) |
| 308 | return head |
| 309 | endfunction |
| 310 | |
| 311 | " Library for accessing and storing tokenized lines {{{1 |
| 312 | " ================================================= |
| 313 | |
| 314 | " The Erlang token cache: an `lnum -> indtokens` dictionary that stores the |
| 315 | " tokenized lines. |
| 316 | let s:all_tokens = {} |
| 317 | let s:file_name = '' |
| 318 | let s:last_changedtick = -1 |
| 319 | |
| 320 | " Purpose: |
| 321 | " Clear the Erlang token cache if we have a different file or the file has |
| 322 | " been changed since the last indentation. |
| 323 | function! s:ClearTokenCacheIfNeeded() |
| 324 | let file_name = expand('%:p') |
| 325 | if file_name != s:file_name || |
| 326 | \ b:changedtick != s:last_changedtick |
| 327 | let s:file_name = file_name |
| 328 | let s:last_changedtick = b:changedtick |
| 329 | let s:all_tokens = {} |
| 330 | endif |
| 331 | endfunction |
| 332 | |
| 333 | " Purpose: |
| 334 | " Return the tokens of line `lnum`, if that line is not empty. If it is |
| 335 | " empty, find the first non-empty line in the given `direction` and return |
| 336 | " the tokens of that line. |
| 337 | " Parameters: |
| 338 | " lnum: integer |
| 339 | " direction: 'up' | 'down' |
| 340 | " Returns: |
| 341 | " result: [] -- the result is an empty list if we hit the beginning or end |
| 342 | " of the file |
| 343 | " | [lnum, indtokens] |
| 344 | " lnum: integer -- the index of the non-empty line that was found and |
| 345 | " tokenized |
| 346 | " indtokens: [indtoken] -- the tokens of line `lnum` |
| 347 | function! s:TokenizeLine(lnum, direction) |
| 348 | |
| 349 | call s:Log('Tokenizing starts from line ' . a:lnum) |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 350 | if a:direction ==# 'up' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 351 | let lnum = prevnonblank(a:lnum) |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 352 | else " a:direction ==# 'down' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 353 | let lnum = nextnonblank(a:lnum) |
| 354 | endif |
| 355 | |
| 356 | " We hit the beginning or end of the file |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 357 | if lnum ==# 0 |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 358 | let indtokens = [] |
| 359 | call s:Log(' We hit the beginning or end of the file.') |
| 360 | |
| 361 | " The line has already been parsed |
| 362 | elseif has_key(s:all_tokens, lnum) |
| 363 | let indtokens = s:all_tokens[lnum] |
| 364 | call s:Log('Cached line ' . lnum . ': ' . getline(lnum)) |
| 365 | call s:Log(" Tokens in the line:\n - " . join(indtokens, "\n - ")) |
| 366 | |
| 367 | " The line should be parsed now |
| 368 | else |
| 369 | |
| 370 | " Parse the line |
| 371 | let line = getline(lnum) |
| 372 | let string_continuation = s:IsLineStringContinuation(lnum) |
| 373 | let atom_continuation = s:IsLineAtomContinuation(lnum) |
| 374 | let indtokens = s:GetTokensFromLine(line, string_continuation, |
| 375 | \atom_continuation, &tabstop) |
| 376 | let s:all_tokens[lnum] = indtokens |
| 377 | call s:Log('Tokenizing line ' . lnum . ': ' . line) |
| 378 | call s:Log(" Tokens in the line:\n - " . join(indtokens, "\n - ")) |
| 379 | |
| 380 | endif |
| 381 | |
| 382 | return [lnum, indtokens] |
| 383 | endfunction |
| 384 | |
| 385 | " Purpose: |
| 386 | " As a helper function for PrevIndToken and NextIndToken, the FindIndToken |
| 387 | " function finds the first line with at least one token in the given |
| 388 | " direction. |
| 389 | " Parameters: |
| 390 | " lnum: integer |
| 391 | " direction: 'up' | 'down' |
| 392 | " Returns: |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 393 | " result: [[], 0, 0] |
| 394 | " -- the result is an empty list if we hit the beginning or end of |
| 395 | " the file |
| 396 | " | [indtoken, lnum, i] |
| 397 | " -- the content, lnum and token index of the next (or previous) |
| 398 | " indtoken |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 399 | function! s:FindIndToken(lnum, dir) |
| 400 | let lnum = a:lnum |
| 401 | while 1 |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 402 | let lnum += (a:dir ==# 'up' ? -1 : 1) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 403 | let [lnum, indtokens] = s:TokenizeLine(lnum, a:dir) |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 404 | if lnum ==# 0 |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 405 | " We hit the beginning or end of the file |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 406 | return [[], 0, 0] |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 407 | elseif !empty(indtokens) |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 408 | " We found a non-empty line. If we were moving up, we return the last |
| 409 | " token of this line. Otherwise we return the first token if this line. |
| 410 | let i = (a:dir ==# 'up' ? len(indtokens) - 1 : 0) |
| 411 | return [indtokens[i], lnum, i] |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 412 | endif |
| 413 | endwhile |
| 414 | endfunction |
| 415 | |
| 416 | " Purpose: |
| 417 | " Find the token that directly precedes the given token. |
| 418 | " Parameters: |
| 419 | " lnum: integer -- the line of the given token |
| 420 | " i: the index of the given token within line `lnum` |
| 421 | " Returns: |
| 422 | " result = [] -- the result is an empty list if the given token is the first |
| 423 | " token of the file |
| 424 | " | indtoken |
| 425 | function! s:PrevIndToken(lnum, i) |
| 426 | call s:Log(' PrevIndToken called: lnum=' . a:lnum . ', i =' . a:i) |
| 427 | |
| 428 | " If the current line has a previous token, return that |
| 429 | if a:i > 0 |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 430 | return [s:all_tokens[a:lnum][a:i - 1], a:lnum, a:i - 1] |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 431 | else |
| 432 | return s:FindIndToken(a:lnum, 'up') |
| 433 | endif |
| 434 | endfunction |
| 435 | |
| 436 | " Purpose: |
| 437 | " Find the token that directly succeeds the given token. |
| 438 | " Parameters: |
| 439 | " lnum: integer -- the line of the given token |
| 440 | " i: the index of the given token within line `lnum` |
| 441 | " Returns: |
| 442 | " result = [] -- the result is an empty list if the given token is the last |
| 443 | " token of the file |
| 444 | " | indtoken |
| 445 | function! s:NextIndToken(lnum, i) |
| 446 | call s:Log(' NextIndToken called: lnum=' . a:lnum . ', i =' . a:i) |
| 447 | |
| 448 | " If the current line has a next token, return that |
| 449 | if len(s:all_tokens[a:lnum]) > a:i + 1 |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 450 | return [s:all_tokens[a:lnum][a:i + 1], a:lnum, a:i + 1] |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 451 | else |
| 452 | return s:FindIndToken(a:lnum, 'down') |
| 453 | endif |
| 454 | endfunction |
| 455 | |
| 456 | " ErlangCalcIndent helper functions {{{1 |
| 457 | " ================================= |
| 458 | |
| 459 | " Purpose: |
| 460 | " This function is called when the parser encounters a syntax error. |
| 461 | " |
| 462 | " If we encounter a syntax error, we return |
| 463 | " g:erlang_unexpected_token_indent, which is -1 by default. This means that |
| 464 | " the indentation of the LTI will not be changed. |
| 465 | " Parameter: |
| 466 | " msg: string |
| 467 | " token: string |
| 468 | " stack: [token] |
| 469 | " Returns: |
| 470 | " indent: integer |
| 471 | function! s:IndentError(msg, token, stack) |
| 472 | call s:Log('Indent error: ' . a:msg . ' -> return') |
| 473 | call s:Log(' Token = ' . a:token . ', ' . |
| 474 | \' stack = ' . string(a:stack)) |
| 475 | return g:erlang_unexpected_token_indent |
| 476 | endfunction |
| 477 | |
| 478 | " Purpose: |
| 479 | " This function is called when the parser encounters an unexpected token, |
| 480 | " and the parser will return the number given back by UnexpectedToken. |
| 481 | " |
| 482 | " If we encounter an unexpected token, we return |
| 483 | " g:erlang_unexpected_token_indent, which is -1 by default. This means that |
| 484 | " the indentation of the LTI will not be changed. |
| 485 | " Parameter: |
| 486 | " token: string |
| 487 | " stack: [token] |
| 488 | " Returns: |
| 489 | " indent: integer |
| 490 | function! s:UnexpectedToken(token, stack) |
| 491 | call s:Log(' Unexpected token ' . a:token . ', stack = ' . |
| 492 | \string(a:stack) . ' -> return') |
| 493 | return g:erlang_unexpected_token_indent |
| 494 | endfunction |
| 495 | |
| 496 | if !exists('g:erlang_unexpected_token_indent') |
| 497 | let g:erlang_unexpected_token_indent = -1 |
| 498 | endif |
| 499 | |
| 500 | " Purpose: |
| 501 | " Return whether the given line starts with a string continuation. |
| 502 | " Parameter: |
| 503 | " lnum: integer |
| 504 | " Returns: |
| 505 | " result: bool |
| 506 | " Example: |
| 507 | " f() -> % IsLineStringContinuation = false |
| 508 | " "This is a % IsLineStringContinuation = false |
| 509 | " multiline % IsLineStringContinuation = true |
| 510 | " string". % IsLineStringContinuation = true |
| 511 | function! s:IsLineStringContinuation(lnum) |
| 512 | if has('syntax_items') |
| 513 | return synIDattr(synID(a:lnum, 1, 0), 'name') =~# '^erlangString' |
| 514 | else |
| 515 | return 0 |
| 516 | endif |
| 517 | endfunction |
| 518 | |
| 519 | " Purpose: |
| 520 | " Return whether the given line starts with an atom continuation. |
| 521 | " Parameter: |
| 522 | " lnum: integer |
| 523 | " Returns: |
| 524 | " result: bool |
| 525 | " Example: |
| 526 | " 'function with % IsLineAtomContinuation = true, but should be false |
| 527 | " weird name'() -> % IsLineAtomContinuation = true |
| 528 | " ok. % IsLineAtomContinuation = false |
| 529 | function! s:IsLineAtomContinuation(lnum) |
| 530 | if has('syntax_items') |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 531 | let syn_name = synIDattr(synID(a:lnum, 1, 0), 'name') |
| 532 | return syn_name =~# '^erlangQuotedAtom' || |
| 533 | \ syn_name =~# '^erlangQuotedRecord' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 534 | else |
| 535 | return 0 |
| 536 | endif |
| 537 | endfunction |
| 538 | |
| 539 | " Purpose: |
| 540 | " Return whether the 'catch' token (which should be the `i`th token in line |
| 541 | " `lnum`) is standalone or part of a try-catch block, based on the preceding |
| 542 | " token. |
| 543 | " Parameters: |
| 544 | " lnum: integer |
| 545 | " i: integer |
| 546 | " Return: |
| 547 | " is_standalone: bool |
| 548 | function! s:IsCatchStandalone(lnum, i) |
| 549 | call s:Log(' IsCatchStandalone called: lnum=' . a:lnum . ', i=' . a:i) |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 550 | let [prev_indtoken, _, _] = s:PrevIndToken(a:lnum, a:i) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 551 | |
| 552 | " If we hit the beginning of the file, it is not a catch in a try block |
| 553 | if prev_indtoken == [] |
| 554 | return 1 |
| 555 | endif |
| 556 | |
| 557 | let prev_token = prev_indtoken[0] |
| 558 | |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 559 | if prev_token =~# '^[A-Z_@0-9]' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 560 | let is_standalone = 0 |
| 561 | elseif prev_token =~# '[a-z]' |
| 562 | if index(['after', 'and', 'andalso', 'band', 'begin', 'bnot', 'bor', 'bsl', |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 563 | \ 'bsr', 'bxor', 'case', 'catch', 'div', 'maybe', 'not', 'or', |
| 564 | \ 'orelse', 'rem', 'try', 'xor'], prev_token) != -1 |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 565 | " If catch is after these keywords, it is standalone |
| 566 | let is_standalone = 1 |
| 567 | else |
| 568 | " If catch is after another keyword (e.g. 'end') or an atom, it is |
| 569 | " part of try-catch. |
| 570 | " |
| 571 | " Keywords: |
| 572 | " - may precede 'catch': end |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 573 | " - may not precede 'catch': else fun if of receive when |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 574 | " - unused: cond let query |
| 575 | let is_standalone = 0 |
| 576 | endif |
| 577 | elseif index([')', ']', '}', '<string>', '<string_end>', '<quoted_atom>', |
| 578 | \ '<quoted_atom_end>', '$.'], prev_token) != -1 |
| 579 | let is_standalone = 0 |
| 580 | else |
| 581 | " This 'else' branch includes the following tokens: |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 582 | " -> == /= =< < >= > ?= =:= =/= + - * / ++ -- :: < > ; ( [ { ? = ! . | |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 583 | let is_standalone = 1 |
| 584 | endif |
| 585 | |
| 586 | call s:Log(' "catch" preceded by "' . prev_token . '" -> catch ' . |
| 587 | \(is_standalone ? 'is standalone' : 'belongs to try-catch')) |
| 588 | return is_standalone |
| 589 | |
| 590 | endfunction |
| 591 | |
| 592 | " Purpose: |
| 593 | " This function is called when a begin-type element ('begin', 'case', |
| 594 | " '[', '<<', etc.) is found. It asks the caller to return if the stack |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 595 | " if already empty. |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 596 | " Parameters: |
| 597 | " stack: [token] |
| 598 | " token: string |
| 599 | " curr_vcol: integer |
| 600 | " stored_vcol: integer |
| 601 | " sw: integer -- number of spaces to be used after the begin element as |
| 602 | " indentation |
| 603 | " Returns: |
| 604 | " result: [should_return, indent] |
| 605 | " should_return: bool -- if true, the caller should return `indent` to Vim |
| 606 | " indent -- integer |
| 607 | function! s:BeginElementFoundIfEmpty(stack, token, curr_vcol, stored_vcol, sw) |
| 608 | if empty(a:stack) |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 609 | if a:stored_vcol ==# -1 |
Bram Moolenaar | 6c391a7 | 2021-09-09 21:55:11 +0200 | [diff] [blame] | 610 | call s:Log(' "' . a:token . '" directly precedes LTI -> return') |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 611 | return [1, a:curr_vcol + a:sw] |
| 612 | else |
| 613 | call s:Log(' "' . a:token . |
| 614 | \'" token (whose expression includes LTI) found -> return') |
| 615 | return [1, a:stored_vcol] |
| 616 | endif |
| 617 | else |
| 618 | return [0, 0] |
| 619 | endif |
| 620 | endfunction |
| 621 | |
| 622 | " Purpose: |
| 623 | " This function is called when a begin-type element ('begin', 'case', '[', |
| 624 | " '<<', etc.) is found, and in some cases when 'after' and 'when' is found. |
| 625 | " It asks the caller to return if the stack is already empty. |
| 626 | " Parameters: |
| 627 | " stack: [token] |
| 628 | " token: string |
| 629 | " curr_vcol: integer |
| 630 | " stored_vcol: integer |
| 631 | " end_token: end token that belongs to the begin element found (e.g. if the |
| 632 | " begin element is 'begin', the end token is 'end') |
| 633 | " sw: integer -- number of spaces to be used after the begin element as |
| 634 | " indentation |
| 635 | " Returns: |
| 636 | " result: [should_return, indent] |
| 637 | " should_return: bool -- if true, the caller should return `indent` to Vim |
| 638 | " indent -- integer |
| 639 | function! s:BeginElementFound(stack, token, curr_vcol, stored_vcol, end_token, sw) |
| 640 | |
| 641 | " Return 'return' if the stack is empty |
| 642 | let [ret, res] = s:BeginElementFoundIfEmpty(a:stack, a:token, a:curr_vcol, |
| 643 | \a:stored_vcol, a:sw) |
| 644 | if ret | return [ret, res] | endif |
| 645 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 646 | if a:stack[0] ==# a:end_token |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 647 | call s:Log(' "' . a:token . '" pops "' . a:end_token . '"') |
| 648 | call s:Pop(a:stack) |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 649 | if !empty(a:stack) && a:stack[0] ==# 'align_to_begin_element' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 650 | call s:Pop(a:stack) |
| 651 | if empty(a:stack) |
| 652 | return [1, a:curr_vcol] |
| 653 | else |
| 654 | return [1, s:UnexpectedToken(a:token, a:stack)] |
| 655 | endif |
| 656 | else |
| 657 | return [0, 0] |
| 658 | endif |
| 659 | else |
| 660 | return [1, s:UnexpectedToken(a:token, a:stack)] |
| 661 | endif |
| 662 | endfunction |
| 663 | |
| 664 | " Purpose: |
| 665 | " This function is called when we hit the beginning of a file or an |
| 666 | " end-of-clause token -- i.e. when we found the beginning of the current |
| 667 | " clause. |
| 668 | " |
| 669 | " If the stack contains an '->' or 'when', this means that we can return |
| 670 | " now, since we were looking for the beginning of the clause. |
| 671 | " Parameters: |
| 672 | " stack: [token] |
| 673 | " token: string |
| 674 | " stored_vcol: integer |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 675 | " lnum: the line number of the "end of clause" mark (or 0 if we hit the |
| 676 | " beginning of the file) |
| 677 | " i: the index of the "end of clause" token within its own line |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 678 | " Returns: |
| 679 | " result: [should_return, indent] |
| 680 | " should_return: bool -- if true, the caller should return `indent` to Vim |
| 681 | " indent -- integer |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 682 | function! s:BeginningOfClauseFound(stack, token, stored_vcol, lnum, i) |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 683 | if !empty(a:stack) && a:stack[0] ==# 'when' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 684 | call s:Log(' BeginningOfClauseFound: "when" found in stack') |
| 685 | call s:Pop(a:stack) |
| 686 | if empty(a:stack) |
| 687 | call s:Log(' Stack is ["when"], so LTI is in a guard -> return') |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 688 | return [1, a:stored_vcol + shiftwidth() + 2] |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 689 | else |
| 690 | return [1, s:UnexpectedToken(a:token, a:stack)] |
| 691 | endif |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 692 | elseif !empty(a:stack) && a:stack[0] ==# '->' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 693 | call s:Log(' BeginningOfClauseFound: "->" found in stack') |
| 694 | call s:Pop(a:stack) |
| 695 | if empty(a:stack) |
| 696 | call s:Log(' Stack is ["->"], so LTI is in function body -> return') |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 697 | return [1, a:stored_vcol + shiftwidth()] |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 698 | elseif a:stack[0] ==# ';' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 699 | call s:Pop(a:stack) |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 700 | |
| 701 | if !empty(a:stack) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 702 | return [1, s:UnexpectedToken(a:token, a:stack)] |
| 703 | endif |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 704 | |
| 705 | if a:lnum ==# 0 |
| 706 | " Set lnum and i to be NextIndToken-friendly |
| 707 | let lnum = 1 |
| 708 | let i = -1 |
| 709 | else |
| 710 | let lnum = a:lnum |
| 711 | let i = a:i |
| 712 | endif |
| 713 | |
| 714 | " Are we after a "-spec func() ...;" clause? |
| 715 | let [next1_indtoken, next1_lnum, next1_i] = s:NextIndToken(lnum, i) |
| 716 | if !empty(next1_indtoken) && next1_indtoken[0] =~# '-' |
| 717 | let [next2_indtoken, next2_lnum, next2_i] = |
| 718 | \s:NextIndToken(next1_lnum, next1_i) |
| 719 | if !empty(next2_indtoken) && next2_indtoken[0] =~# 'spec' |
| 720 | let [next3_indtoken, next3_lnum, next3_i] = |
| 721 | \s:NextIndToken(next2_lnum, next2_i) |
| 722 | if !empty(next3_indtoken) |
| 723 | let [next4_indtoken, next4_lnum, next4_i] = |
| 724 | \s:NextIndToken(next3_lnum, next3_i) |
| 725 | if !empty(next4_indtoken) |
| 726 | " Yes, we are. |
| 727 | call s:Log(' Stack is ["->", ";"], so LTI is in a "-spec" ' . |
| 728 | \'attribute -> return') |
| 729 | return [1, next4_indtoken[1]] |
| 730 | endif |
| 731 | endif |
| 732 | endif |
| 733 | endif |
| 734 | |
| 735 | call s:Log(' Stack is ["->", ";"], so LTI is in a function head ' . |
| 736 | \'-> return') |
| 737 | return [1, a:stored_vcol] |
| 738 | |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 739 | else |
| 740 | return [1, s:UnexpectedToken(a:token, a:stack)] |
| 741 | endif |
| 742 | else |
| 743 | return [0, 0] |
| 744 | endif |
| 745 | endfunction |
| 746 | |
| 747 | let g:erlang_indent_searchpair_timeout = 2000 |
| 748 | |
| 749 | " TODO |
| 750 | function! s:SearchPair(lnum, curr_col, start, middle, end) |
| 751 | call cursor(a:lnum, a:curr_col + 1) |
| 752 | let [lnum_new, col1_new] = |
| 753 | \searchpairpos(a:start, a:middle, a:end, 'bW', |
| 754 | \'synIDattr(synID(line("."), col("."), 0), "name") ' . |
| 755 | \'=~? "string\\|quotedatom\\|todo\\|comment\\|' . |
| 756 | \'erlangmodifier"', |
| 757 | \0, g:erlang_indent_searchpair_timeout) |
| 758 | return [lnum_new, col1_new - 1] |
| 759 | endfunction |
| 760 | |
| 761 | function! s:SearchEndPair(lnum, curr_col) |
| 762 | return s:SearchPair( |
| 763 | \ a:lnum, a:curr_col, |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 764 | \ '\C\<\%(case\|try\|begin\|receive\|if\|maybe\)\>\|' . |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 765 | \ '\<fun\>\%(\s\|\n\|%.*$\|[A-Z_@][a-zA-Z_@]*\)*(', |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 766 | \ '', |
| 767 | \ '\<end\>') |
| 768 | endfunction |
| 769 | |
| 770 | " ErlangCalcIndent {{{1 |
| 771 | " ================ |
| 772 | |
| 773 | " Purpose: |
| 774 | " Calculate the indentation of the given line. |
| 775 | " Parameters: |
| 776 | " lnum: integer -- index of the line for which the indentation should be |
| 777 | " calculated |
| 778 | " stack: [token] -- initial stack |
| 779 | " Return: |
| 780 | " indent: integer -- if -1, that means "don't change the indentation"; |
| 781 | " otherwise it means "indent the line with `indent` |
| 782 | " number of spaces or equivalent tabs" |
| 783 | function! s:ErlangCalcIndent(lnum, stack) |
| 784 | let res = s:ErlangCalcIndent2(a:lnum, a:stack) |
| 785 | call s:Log("ErlangCalcIndent returned: " . res) |
| 786 | return res |
| 787 | endfunction |
| 788 | |
| 789 | function! s:ErlangCalcIndent2(lnum, stack) |
| 790 | |
| 791 | let lnum = a:lnum |
| 792 | let stored_vcol = -1 " Virtual column of the first character of the token that |
| 793 | " we currently think we might align to. |
| 794 | let mode = 'normal' |
| 795 | let stack = a:stack |
| 796 | let semicolon_abscol = '' |
| 797 | |
| 798 | " Walk through the lines of the buffer backwards (starting from the |
| 799 | " previous line) until we can decide how to indent the current line. |
| 800 | while 1 |
| 801 | |
| 802 | let [lnum, indtokens] = s:TokenizeLine(lnum, 'up') |
| 803 | |
| 804 | " Hit the start of the file |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 805 | if lnum ==# 0 |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 806 | let [ret, res] = s:BeginningOfClauseFound(stack, 'beginning_of_file', |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 807 | \stored_vcol, 0, 0) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 808 | if ret | return res | endif |
| 809 | |
| 810 | return 0 |
| 811 | endif |
| 812 | |
| 813 | let i = len(indtokens) - 1 |
| 814 | let last_token_of_line = 1 |
| 815 | |
| 816 | while i >= 0 |
| 817 | |
| 818 | let [token, curr_vcol, curr_col] = indtokens[i] |
| 819 | call s:Log(' Analyzing the following token: ' . string(indtokens[i])) |
| 820 | |
| 821 | if len(stack) > 256 " TODO: magic number |
| 822 | return s:IndentError('Stack too long', token, stack) |
| 823 | endif |
| 824 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 825 | if token ==# '<end_of_clause>' |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 826 | let [ret, res] = s:BeginningOfClauseFound(stack, token, stored_vcol, |
| 827 | \lnum, i) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 828 | if ret | return res | endif |
| 829 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 830 | if stored_vcol ==# -1 |
Bram Moolenaar | 6c391a7 | 2021-09-09 21:55:11 +0200 | [diff] [blame] | 831 | call s:Log(' End of clause directly precedes LTI -> return') |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 832 | return 0 |
| 833 | else |
| 834 | call s:Log(' End of clause (but not end of line) -> return') |
| 835 | return stored_vcol |
| 836 | endif |
| 837 | |
| 838 | elseif stack == ['prev_term_plus'] |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 839 | if token =~# '[a-zA-Z_@#]' || |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 840 | \ token ==# '<string>' || token ==# '<string_start>' || |
| 841 | \ token ==# '<quoted_atom>' || token ==# '<quoted_atom_start>' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 842 | call s:Log(' previous token found: curr_vcol + plus = ' . |
| 843 | \curr_vcol . " + " . plus) |
| 844 | return curr_vcol + plus |
| 845 | endif |
| 846 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 847 | elseif token ==# 'begin' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 848 | let [ret, res] = s:BeginElementFound(stack, token, curr_vcol, |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 849 | \stored_vcol, 'end', shiftwidth()) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 850 | if ret | return res | endif |
| 851 | |
| 852 | " case EXPR of BRANCHES end |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 853 | " if BRANCHES end |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 854 | " try EXPR catch BRANCHES end |
| 855 | " try EXPR after BODY end |
| 856 | " try EXPR catch BRANCHES after BODY end |
| 857 | " try EXPR of BRANCHES catch BRANCHES end |
| 858 | " try EXPR of BRANCHES after BODY end |
| 859 | " try EXPR of BRANCHES catch BRANCHES after BODY end |
| 860 | " receive BRANCHES end |
| 861 | " receive BRANCHES after BRANCHES end |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 862 | " maybe EXPR end |
| 863 | " maybe EXPR else BRANCHES end |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 864 | |
| 865 | " This branch is not Emacs-compatible |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 866 | elseif (index(['of', 'receive', 'after', 'if', 'else'], token) != -1 || |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 867 | \ (token ==# 'catch' && !s:IsCatchStandalone(lnum, i))) && |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 868 | \ !last_token_of_line && |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 869 | \ (empty(stack) || stack ==# ['when'] || stack ==# ['->'] || |
| 870 | \ stack ==# ['->', ';']) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 871 | |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 872 | " If we are after of/receive/etc, but these are not the last |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 873 | " tokens of the line, we want to indent like this: |
| 874 | " |
| 875 | " % stack == [] |
| 876 | " receive stored_vcol, |
| 877 | " LTI |
| 878 | " |
| 879 | " % stack == ['->', ';'] |
| 880 | " receive stored_vcol -> |
| 881 | " B; |
| 882 | " LTI |
| 883 | " |
| 884 | " % stack == ['->'] |
| 885 | " receive stored_vcol -> |
| 886 | " LTI |
| 887 | " |
| 888 | " % stack == ['when'] |
| 889 | " receive stored_vcol when |
| 890 | " LTI |
| 891 | |
| 892 | " stack = [] => LTI is a condition |
| 893 | " stack = ['->'] => LTI is a branch |
| 894 | " stack = ['->', ';'] => LTI is a condition |
| 895 | " stack = ['when'] => LTI is a guard |
| 896 | if empty(stack) || stack == ['->', ';'] |
| 897 | call s:Log(' LTI is in a condition after ' . |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 898 | \'"of/receive/after/if/else/catch" -> return') |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 899 | return stored_vcol |
| 900 | elseif stack == ['->'] |
| 901 | call s:Log(' LTI is in a branch after ' . |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 902 | \'"of/receive/after/if/else/catch" -> return') |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 903 | return stored_vcol + shiftwidth() |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 904 | elseif stack == ['when'] |
| 905 | call s:Log(' LTI is in a guard after ' . |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 906 | \'"of/receive/after/if/else/catch" -> return') |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 907 | return stored_vcol + shiftwidth() |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 908 | else |
| 909 | return s:UnexpectedToken(token, stack) |
| 910 | endif |
| 911 | |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 912 | elseif index(['case', 'if', 'try', 'receive', 'maybe'], token) != -1 |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 913 | |
| 914 | " stack = [] => LTI is a condition |
| 915 | " stack = ['->'] => LTI is a branch |
| 916 | " stack = ['->', ';'] => LTI is a condition |
| 917 | " stack = ['when'] => LTI is in a guard |
| 918 | if empty(stack) |
| 919 | " pass |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 920 | elseif (token ==# 'case' && stack[0] ==# 'of') || |
| 921 | \ (token ==# 'if') || |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 922 | \ (token ==# 'maybe' && stack[0] ==# 'else') || |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 923 | \ (token ==# 'try' && (stack[0] ==# 'of' || |
| 924 | \ stack[0] ==# 'catch' || |
| 925 | \ stack[0] ==# 'after')) || |
| 926 | \ (token ==# 'receive') |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 927 | |
| 928 | " From the indentation point of view, the keyword |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 929 | " (of/catch/after/else/end) before the LTI is what counts, so |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 930 | " when we reached these tokens, and the stack already had |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 931 | " a catch/after/else/end, we didn't modify it. |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 932 | " |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 933 | " This way when we reach case/try/receive/maybe (i.e. now), |
| 934 | " there is at most one of/catch/after/else/end token in the |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 935 | " stack. |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 936 | if token ==# 'case' || token ==# 'try' || |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 937 | \ (token ==# 'receive' && stack[0] ==# 'after') || |
| 938 | \ (token ==# 'maybe' && stack[0] ==# 'else') |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 939 | call s:Pop(stack) |
| 940 | endif |
| 941 | |
| 942 | if empty(stack) |
| 943 | call s:Log(' LTI is in a condition; matching ' . |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 944 | \'"case/if/try/receive/maybe" found') |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 945 | let stored_vcol = curr_vcol + shiftwidth() |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 946 | elseif stack[0] ==# 'align_to_begin_element' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 947 | call s:Pop(stack) |
| 948 | let stored_vcol = curr_vcol |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 949 | elseif len(stack) > 1 && stack[0] ==# '->' && stack[1] ==# ';' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 950 | call s:Log(' LTI is in a condition; matching ' . |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 951 | \'"case/if/try/receive/maybe" found') |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 952 | call s:Pop(stack) |
| 953 | call s:Pop(stack) |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 954 | let stored_vcol = curr_vcol + shiftwidth() |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 955 | elseif stack[0] ==# '->' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 956 | call s:Log(' LTI is in a branch; matching ' . |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 957 | \'"case/if/try/receive/maybe" found') |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 958 | call s:Pop(stack) |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 959 | let stored_vcol = curr_vcol + 2 * shiftwidth() |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 960 | elseif stack[0] ==# 'when' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 961 | call s:Log(' LTI is in a guard; matching ' . |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 962 | \'"case/if/try/receive/maybe" found') |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 963 | call s:Pop(stack) |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 964 | let stored_vcol = curr_vcol + 2 * shiftwidth() + 2 |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 965 | endif |
| 966 | |
| 967 | endif |
| 968 | |
| 969 | let [ret, res] = s:BeginElementFound(stack, token, curr_vcol, |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 970 | \stored_vcol, 'end', shiftwidth()) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 971 | if ret | return res | endif |
| 972 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 973 | elseif token ==# 'fun' |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 974 | let [next_indtoken, next_lnum, next_i] = s:NextIndToken(lnum, i) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 975 | call s:Log(' Next indtoken = ' . string(next_indtoken)) |
| 976 | |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 977 | if !empty(next_indtoken) && next_indtoken[0] =~# '^[A-Z_@]' |
| 978 | " The "fun" is followed by a variable, so we might have a named fun: |
| 979 | " "fun Fun() -> ok end". Thus we take the next token to decide |
| 980 | " whether this is a function definition ("fun()") or just a function |
| 981 | " reference ("fun Mod:Fun"). |
| 982 | let [next_indtoken, _, _] = s:NextIndToken(next_lnum, next_i) |
| 983 | call s:Log(' Next indtoken = ' . string(next_indtoken)) |
| 984 | endif |
| 985 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 986 | if !empty(next_indtoken) && next_indtoken[0] ==# '(' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 987 | " We have an anonymous function definition |
| 988 | " (e.g. "fun () -> ok end") |
| 989 | |
| 990 | " stack = [] => LTI is a condition |
| 991 | " stack = ['->'] => LTI is a branch |
| 992 | " stack = ['->', ';'] => LTI is a condition |
| 993 | " stack = ['when'] => LTI is in a guard |
| 994 | if empty(stack) |
| 995 | call s:Log(' LTI is in a condition; matching "fun" found') |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 996 | let stored_vcol = curr_vcol + shiftwidth() |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 997 | elseif len(stack) > 1 && stack[0] ==# '->' && stack[1] ==# ';' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 998 | call s:Log(' LTI is in a condition; matching "fun" found') |
| 999 | call s:Pop(stack) |
| 1000 | call s:Pop(stack) |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1001 | elseif stack[0] ==# '->' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1002 | call s:Log(' LTI is in a branch; matching "fun" found') |
| 1003 | call s:Pop(stack) |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 1004 | let stored_vcol = curr_vcol + 2 * shiftwidth() |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1005 | elseif stack[0] ==# 'when' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1006 | call s:Log(' LTI is in a guard; matching "fun" found') |
| 1007 | call s:Pop(stack) |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 1008 | let stored_vcol = curr_vcol + 2 * shiftwidth() + 2 |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1009 | endif |
| 1010 | |
| 1011 | let [ret, res] = s:BeginElementFound(stack, token, curr_vcol, |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 1012 | \stored_vcol, 'end', shiftwidth()) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1013 | if ret | return res | endif |
| 1014 | else |
| 1015 | " Pass: we have a function reference (e.g. "fun f/0") |
| 1016 | endif |
| 1017 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1018 | elseif token ==# '[' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1019 | " Emacs compatibility |
| 1020 | let [ret, res] = s:BeginElementFound(stack, token, curr_vcol, |
| 1021 | \stored_vcol, ']', 1) |
| 1022 | if ret | return res | endif |
| 1023 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1024 | elseif token ==# '<<' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1025 | " Emacs compatibility |
| 1026 | let [ret, res] = s:BeginElementFound(stack, token, curr_vcol, |
| 1027 | \stored_vcol, '>>', 2) |
| 1028 | if ret | return res | endif |
| 1029 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1030 | elseif token ==# '(' || token ==# '{' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1031 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1032 | let end_token = (token ==# '(' ? ')' : |
| 1033 | \token ==# '{' ? '}' : 'error') |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1034 | |
| 1035 | if empty(stack) |
| 1036 | " We found the opening paren whose block contains the LTI. |
| 1037 | let mode = 'inside' |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1038 | elseif stack[0] ==# end_token |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1039 | call s:Log(' "' . token . '" pops "' . end_token . '"') |
| 1040 | call s:Pop(stack) |
| 1041 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1042 | if !empty(stack) && stack[0] ==# 'align_to_begin_element' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1043 | " We found the opening paren whose closing paren |
| 1044 | " starts LTI |
| 1045 | let mode = 'align_to_begin_element' |
| 1046 | else |
| 1047 | " We found the opening pair for a closing paren that |
| 1048 | " was already in the stack. |
| 1049 | let mode = 'outside' |
| 1050 | endif |
| 1051 | else |
| 1052 | return s:UnexpectedToken(token, stack) |
| 1053 | endif |
| 1054 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1055 | if mode ==# 'inside' || mode ==# 'align_to_begin_element' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1056 | |
| 1057 | if last_token_of_line && i != 0 |
| 1058 | " Examples: {{{ |
| 1059 | " |
| 1060 | " mode == 'inside': |
| 1061 | " |
| 1062 | " my_func( |
| 1063 | " LTI |
| 1064 | " |
| 1065 | " [Variable, { |
| 1066 | " LTI |
| 1067 | " |
| 1068 | " mode == 'align_to_begin_element': |
| 1069 | " |
| 1070 | " my_func( |
| 1071 | " Params |
| 1072 | " ) % LTI |
| 1073 | " |
| 1074 | " [Variable, { |
| 1075 | " Terms |
| 1076 | " } % LTI |
| 1077 | " }}} |
| 1078 | let stack = ['prev_term_plus'] |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1079 | let plus = (mode ==# 'inside' ? 2 : 1) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1080 | call s:Log(' "' . token . |
| 1081 | \'" token found at end of line -> find previous token') |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1082 | elseif mode ==# 'align_to_begin_element' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1083 | " Examples: {{{ |
| 1084 | " |
| 1085 | " mode == 'align_to_begin_element' && !last_token_of_line |
| 1086 | " |
| 1087 | " my_func(stored_vcol |
| 1088 | " ) % LTI |
| 1089 | " |
| 1090 | " [Variable, {stored_vcol |
| 1091 | " } % LTI |
| 1092 | " |
| 1093 | " mode == 'align_to_begin_element' && i == 0 |
| 1094 | " |
| 1095 | " ( |
| 1096 | " stored_vcol |
| 1097 | " ) % LTI |
| 1098 | " |
| 1099 | " { |
| 1100 | " stored_vcol |
| 1101 | " } % LTI |
| 1102 | " }}} |
| 1103 | call s:Log(' "' . token . '" token (whose closing token ' . |
| 1104 | \'starts LTI) found -> return') |
| 1105 | return curr_vcol |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1106 | elseif stored_vcol ==# -1 |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1107 | " Examples: {{{ |
| 1108 | " |
| 1109 | " mode == 'inside' && stored_vcol == -1 && !last_token_of_line |
| 1110 | " |
| 1111 | " my_func( |
| 1112 | " LTI |
| 1113 | " [Variable, { |
| 1114 | " LTI |
| 1115 | " |
| 1116 | " mode == 'inside' && stored_vcol == -1 && i == 0 |
| 1117 | " |
| 1118 | " ( |
| 1119 | " LTI |
| 1120 | " |
| 1121 | " { |
| 1122 | " LTI |
| 1123 | " }}} |
| 1124 | call s:Log(' "' . token . |
| 1125 | \'" token (which directly precedes LTI) found -> return') |
| 1126 | return curr_vcol + 1 |
| 1127 | else |
| 1128 | " Examples: {{{ |
| 1129 | " |
| 1130 | " mode == 'inside' && stored_vcol != -1 && !last_token_of_line |
| 1131 | " |
| 1132 | " my_func(stored_vcol, |
| 1133 | " LTI |
| 1134 | " |
| 1135 | " [Variable, {stored_vcol, |
| 1136 | " LTI |
| 1137 | " |
| 1138 | " mode == 'inside' && stored_vcol != -1 && i == 0 |
| 1139 | " |
| 1140 | " (stored_vcol, |
| 1141 | " LTI |
| 1142 | " |
| 1143 | " {stored_vcol, |
| 1144 | " LTI |
| 1145 | " }}} |
| 1146 | call s:Log(' "' . token . |
| 1147 | \'" token (whose block contains LTI) found -> return') |
| 1148 | return stored_vcol |
| 1149 | endif |
| 1150 | endif |
| 1151 | |
Bram Moolenaar | 203d04d | 2013-06-06 21:36:40 +0200 | [diff] [blame] | 1152 | elseif index(['end', ')', ']', '}', '>>'], token) != -1 |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1153 | |
Bram Moolenaar | 203d04d | 2013-06-06 21:36:40 +0200 | [diff] [blame] | 1154 | " If we can be sure that there is synchronization in the Erlang |
| 1155 | " syntax, we use searchpair to make the script quicker. Otherwise we |
| 1156 | " just push the token onto the stack and keep parsing. |
| 1157 | |
| 1158 | " No synchronization -> no searchpair optimization |
| 1159 | if !exists('b:erlang_syntax_synced') |
| 1160 | call s:Push(stack, token) |
| 1161 | |
| 1162 | " We don't have searchpair optimization for '>>' |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1163 | elseif token ==# '>>' |
Bram Moolenaar | 203d04d | 2013-06-06 21:36:40 +0200 | [diff] [blame] | 1164 | call s:Push(stack, token) |
| 1165 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1166 | elseif token ==# 'end' |
Bram Moolenaar | 203d04d | 2013-06-06 21:36:40 +0200 | [diff] [blame] | 1167 | let [lnum_new, col_new] = s:SearchEndPair(lnum, curr_col) |
| 1168 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1169 | if lnum_new ==# 0 |
Bram Moolenaar | 203d04d | 2013-06-06 21:36:40 +0200 | [diff] [blame] | 1170 | return s:IndentError('Matching token for "end" not found', |
| 1171 | \token, stack) |
| 1172 | else |
| 1173 | if lnum_new != lnum |
| 1174 | call s:Log(' Tokenize for "end" <<<<') |
| 1175 | let [lnum, indtokens] = s:TokenizeLine(lnum_new, 'up') |
| 1176 | call s:Log(' >>>> Tokenize for "end"') |
| 1177 | endif |
| 1178 | |
| 1179 | let [success, i] = s:GetIndtokenAtCol(indtokens, col_new) |
| 1180 | if !success | return i | endif |
| 1181 | let [token, curr_vcol, curr_col] = indtokens[i] |
| 1182 | call s:Log(' Match for "end" in line ' . lnum_new . ': ' . |
| 1183 | \string(indtokens[i])) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1184 | endif |
| 1185 | |
Bram Moolenaar | 203d04d | 2013-06-06 21:36:40 +0200 | [diff] [blame] | 1186 | else " token is one of the following: ')', ']', '}' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1187 | |
Bram Moolenaar | 203d04d | 2013-06-06 21:36:40 +0200 | [diff] [blame] | 1188 | call s:Push(stack, token) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1189 | |
Bram Moolenaar | 203d04d | 2013-06-06 21:36:40 +0200 | [diff] [blame] | 1190 | " We have to escape '[', because this string will be interpreted as a |
| 1191 | " regexp |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1192 | let open_paren = (token ==# ')' ? '(' : |
| 1193 | \token ==# ']' ? '\[' : |
Bram Moolenaar | 203d04d | 2013-06-06 21:36:40 +0200 | [diff] [blame] | 1194 | \ '{') |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1195 | |
Bram Moolenaar | 203d04d | 2013-06-06 21:36:40 +0200 | [diff] [blame] | 1196 | let [lnum_new, col_new] = s:SearchPair(lnum, curr_col, |
| 1197 | \open_paren, '', token) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1198 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1199 | if lnum_new ==# 0 |
Bram Moolenaar | 203d04d | 2013-06-06 21:36:40 +0200 | [diff] [blame] | 1200 | return s:IndentError('Matching token not found', |
| 1201 | \token, stack) |
| 1202 | else |
| 1203 | if lnum_new != lnum |
| 1204 | call s:Log(' Tokenize the opening paren <<<<') |
| 1205 | let [lnum, indtokens] = s:TokenizeLine(lnum_new, 'up') |
| 1206 | call s:Log(' >>>>') |
| 1207 | endif |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1208 | |
Bram Moolenaar | 203d04d | 2013-06-06 21:36:40 +0200 | [diff] [blame] | 1209 | let [success, i] = s:GetIndtokenAtCol(indtokens, col_new) |
| 1210 | if !success | return i | endif |
| 1211 | let [token, curr_vcol, curr_col] = indtokens[i] |
| 1212 | call s:Log(' Match in line ' . lnum_new . ': ' . |
| 1213 | \string(indtokens[i])) |
| 1214 | |
| 1215 | " Go back to the beginning of the loop and handle the opening paren |
| 1216 | continue |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1217 | endif |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1218 | endif |
| 1219 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1220 | elseif token ==# ';' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1221 | |
| 1222 | if empty(stack) |
| 1223 | call s:Push(stack, ';') |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 1224 | elseif index([';', '->', 'when', 'end', 'after', 'catch', 'else'], |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1225 | \stack[0]) != -1 |
| 1226 | " Pass: |
| 1227 | " |
| 1228 | " - If the stack top is another ';', then one ';' is |
| 1229 | " enough. |
| 1230 | " - If the stack top is an '->' or a 'when', then we |
| 1231 | " should keep that, because they signify the type of the |
| 1232 | " LTI (branch, condition or guard). |
| 1233 | " - From the indentation point of view, the keyword |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 1234 | " (of/catch/after/else/end) before the LTI is what counts, so |
| 1235 | " if the stack already has a catch/after/else/end, we don't |
| 1236 | " modify it. This way when we reach case/try/receive/maybe, |
| 1237 | " there will be at most one of/catch/after/else/end token in |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1238 | " the stack. |
| 1239 | else |
| 1240 | return s:UnexpectedToken(token, stack) |
| 1241 | endif |
| 1242 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1243 | elseif token ==# '->' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1244 | |
| 1245 | if empty(stack) && !last_token_of_line |
| 1246 | call s:Log(' LTI is in expression after arrow -> return') |
| 1247 | return stored_vcol |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1248 | elseif empty(stack) || stack[0] ==# ';' || stack[0] ==# 'end' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1249 | " stack = [';'] -> LTI is either a branch or in a guard |
| 1250 | " stack = ['->'] -> LTI is a condition |
| 1251 | " stack = ['->', ';'] -> LTI is a branch |
| 1252 | call s:Push(stack, '->') |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 1253 | elseif index(['->', 'when', 'end', 'after', 'catch', 'else'], |
| 1254 | \stack[0]) != -1 |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1255 | " Pass: |
| 1256 | " |
| 1257 | " - If the stack top is another '->', then one '->' is |
| 1258 | " enough. |
| 1259 | " - If the stack top is a 'when', then we should keep |
| 1260 | " that, because this signifies that LTI is a in a guard. |
| 1261 | " - From the indentation point of view, the keyword |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 1262 | " (of/catch/after/else/end) before the LTI is what counts, so |
| 1263 | " if the stack already has a catch/after/else/end, we don't |
| 1264 | " modify it. This way when we reach case/try/receive/maybe, |
| 1265 | " there will be at most one of/catch/after/else/end token in |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1266 | " the stack. |
| 1267 | else |
| 1268 | return s:UnexpectedToken(token, stack) |
| 1269 | endif |
| 1270 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1271 | elseif token ==# 'when' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1272 | |
| 1273 | " Pop all ';' from the top of the stack |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1274 | while !empty(stack) && stack[0] ==# ';' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1275 | call s:Pop(stack) |
| 1276 | endwhile |
| 1277 | |
| 1278 | if empty(stack) |
| 1279 | if semicolon_abscol != '' |
| 1280 | let stored_vcol = semicolon_abscol |
| 1281 | endif |
| 1282 | if !last_token_of_line |
| 1283 | " Example: |
| 1284 | " when A, |
| 1285 | " LTI |
| 1286 | let [ret, res] = s:BeginElementFoundIfEmpty(stack, token, curr_vcol, |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 1287 | \stored_vcol, shiftwidth()) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1288 | if ret | return res | endif |
| 1289 | else |
| 1290 | " Example: |
| 1291 | " when |
| 1292 | " LTI |
| 1293 | call s:Push(stack, token) |
| 1294 | endif |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 1295 | elseif index(['->', 'when', 'end', 'after', 'catch', 'else'], |
| 1296 | \stack[0]) != -1 |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1297 | " Pass: |
| 1298 | " - If the stack top is another 'when', then one 'when' is |
| 1299 | " enough. |
| 1300 | " - If the stack top is an '->' or a 'when', then we |
| 1301 | " should keep that, because they signify the type of the |
| 1302 | " LTI (branch, condition or guard). |
| 1303 | " - From the indentation point of view, the keyword |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 1304 | " (of/catch/after/else/end) before the LTI is what counts, so |
| 1305 | " if the stack already has a catch/after/else/end, we don't |
| 1306 | " modify it. This way when we reach case/try/receive/maybe, |
| 1307 | " there will be at most one of/catch/after/else/end token in |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1308 | " the stack. |
| 1309 | else |
| 1310 | return s:UnexpectedToken(token, stack) |
| 1311 | endif |
| 1312 | |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 1313 | elseif token ==# 'of' || token ==# 'after' || token ==# 'else' || |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1314 | \ (token ==# 'catch' && !s:IsCatchStandalone(lnum, i)) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1315 | |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 1316 | if token ==# 'after' || token ==# 'else' |
| 1317 | " If LTI is between an after/else and the corresponding 'end', then |
| 1318 | " let's return because calculating the indentation based on |
| 1319 | " after/else is enough. |
| 1320 | " |
| 1321 | " Example: |
| 1322 | " receive A after |
| 1323 | " LTI |
| 1324 | " maybe A else |
| 1325 | " LTI |
| 1326 | " |
| 1327 | " Note about Emacs compabitility {{{ |
| 1328 | " |
| 1329 | " It would be fine to indent the examples above the following way: |
| 1330 | " |
| 1331 | " receive A after |
| 1332 | " LTI |
| 1333 | " maybe A else |
| 1334 | " LTI |
| 1335 | " |
| 1336 | " We intend it the way above because that is how Emacs does it. |
| 1337 | " Also, this is a bit faster. |
| 1338 | " |
| 1339 | " We are still not 100% Emacs compatible because of placing the |
| 1340 | " 'end' after the indented blocks. |
| 1341 | " |
| 1342 | " Emacs example: |
| 1343 | " |
| 1344 | " receive A after |
| 1345 | " LTI |
| 1346 | " end, |
| 1347 | " maybe A else |
| 1348 | " LTI |
| 1349 | " end % Yes, it's here (in OTP 25.0, might change |
| 1350 | " % later) |
| 1351 | " |
| 1352 | " vim-erlang example: |
| 1353 | " |
| 1354 | " receive A after |
| 1355 | " LTI |
| 1356 | " end, |
| 1357 | " maybe A else |
| 1358 | " LTI |
| 1359 | " end |
| 1360 | " }}} |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1361 | let [ret, res] = s:BeginElementFoundIfEmpty(stack, token, curr_vcol, |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 1362 | \stored_vcol, shiftwidth()) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1363 | if ret | return res | endif |
| 1364 | endif |
| 1365 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1366 | if empty(stack) || stack[0] ==# '->' || stack[0] ==# 'when' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1367 | call s:Push(stack, token) |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 1368 | elseif stack[0] ==# 'catch' || stack[0] ==# 'after' || |
| 1369 | \stack[0] ==# 'else' || stack[0] ==# 'end' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1370 | " Pass: From the indentation point of view, the keyword |
| 1371 | " (of/catch/after/end) before the LTI is what counts, so |
| 1372 | " if the stack already has a catch/after/end, we don't |
| 1373 | " modify it. This way when we reach case/try/receive, |
| 1374 | " there will be at most one of/catch/after/end token in |
| 1375 | " the stack. |
| 1376 | else |
| 1377 | return s:UnexpectedToken(token, stack) |
| 1378 | endif |
| 1379 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1380 | elseif token ==# '||' && empty(stack) && !last_token_of_line |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1381 | |
| 1382 | call s:Log(' LTI is in expression after "||" -> return') |
| 1383 | return stored_vcol |
| 1384 | |
| 1385 | else |
| 1386 | call s:Log(' Misc token, stack unchanged = ' . string(stack)) |
| 1387 | |
| 1388 | endif |
| 1389 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1390 | if empty(stack) || stack[0] ==# '->' || stack[0] ==# 'when' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1391 | let stored_vcol = curr_vcol |
| 1392 | let semicolon_abscol = '' |
| 1393 | call s:Log(' Misc token when the stack is empty or has "->" ' . |
| 1394 | \'-> setting stored_vcol to ' . stored_vcol) |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1395 | elseif stack[0] ==# ';' |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1396 | let semicolon_abscol = curr_vcol |
| 1397 | call s:Log(' Setting semicolon-stored_vcol to ' . stored_vcol) |
| 1398 | endif |
| 1399 | |
| 1400 | let i -= 1 |
| 1401 | call s:Log(' Token processed. stored_vcol=' . stored_vcol) |
| 1402 | |
| 1403 | let last_token_of_line = 0 |
| 1404 | |
| 1405 | endwhile " iteration on tokens in a line |
| 1406 | |
| 1407 | call s:Log(' Line analyzed. stored_vcol=' . stored_vcol) |
| 1408 | |
| 1409 | if empty(stack) && stored_vcol != -1 && |
| 1410 | \ (!empty(indtokens) && indtokens[0][0] != '<string_end>' && |
| 1411 | \ indtokens[0][0] != '<quoted_atom_end>') |
| 1412 | call s:Log(' Empty stack at the beginning of the line -> return') |
| 1413 | return stored_vcol |
| 1414 | endif |
| 1415 | |
| 1416 | let lnum -= 1 |
| 1417 | |
| 1418 | endwhile " iteration on lines |
| 1419 | |
| 1420 | endfunction |
| 1421 | |
| 1422 | " ErlangIndent function {{{1 |
| 1423 | " ===================== |
| 1424 | |
| 1425 | function! ErlangIndent() |
| 1426 | |
| 1427 | call s:ClearTokenCacheIfNeeded() |
| 1428 | |
| 1429 | let currline = getline(v:lnum) |
| 1430 | call s:Log('Indenting line ' . v:lnum . ': ' . currline) |
| 1431 | |
| 1432 | if s:IsLineStringContinuation(v:lnum) || s:IsLineAtomContinuation(v:lnum) |
| 1433 | call s:Log('String or atom continuation found -> ' . |
| 1434 | \'leaving indentation unchanged') |
| 1435 | return -1 |
| 1436 | endif |
| 1437 | |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 1438 | " If the line starts with the comment, and so is the previous non-blank line |
| 1439 | if currline =~# '^\s*%' |
| 1440 | let lnum = prevnonblank(v:lnum - 1) |
| 1441 | if lnum ==# 0 |
| 1442 | call s:Log('First non-empty line of the file -> return 0.') |
| 1443 | return 0 |
| 1444 | else |
| 1445 | let ml = matchlist(getline(lnum), '^\(\s*\)%') |
| 1446 | " If the previous line also starts with a comment, then return the same |
| 1447 | " indentation that line has. Otherwise exit from this special "if" and |
| 1448 | " don't care that the current line is a comment. |
| 1449 | if !empty(ml) |
| 1450 | let new_col = s:CalcVCol(ml[1], 0, len(ml[1]) - 1, 0, &tabstop) |
| 1451 | call s:Log('Comment line after another comment line -> ' . |
| 1452 | \'use same indent: ' . new_col) |
| 1453 | return new_col |
| 1454 | endif |
| 1455 | endif |
| 1456 | endif |
| 1457 | |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1458 | let ml = matchlist(currline, |
Bram Moolenaar | f269eab | 2022-10-03 18:04:35 +0100 | [diff] [blame] | 1459 | \'^\(\s*\)\(\%(end\|of\|catch\|after\|else\)\>\|[)\]}]\|>>\)') |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1460 | |
| 1461 | " If the line has a special beginning, but not a standalone catch |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1462 | if !empty(ml) && !(ml[2] ==# 'catch' && s:IsCatchStandalone(v:lnum, 0)) |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1463 | |
| 1464 | let curr_col = len(ml[1]) |
| 1465 | |
Bram Moolenaar | 203d04d | 2013-06-06 21:36:40 +0200 | [diff] [blame] | 1466 | " If we can be sure that there is synchronization in the Erlang |
| 1467 | " syntax, we use searchpair to make the script quicker. |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1468 | if ml[2] ==# 'end' && exists('b:erlang_syntax_synced') |
Bram Moolenaar | 203d04d | 2013-06-06 21:36:40 +0200 | [diff] [blame] | 1469 | |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1470 | let [lnum, col] = s:SearchEndPair(v:lnum, curr_col) |
| 1471 | |
Bram Moolenaar | 9d98fe9 | 2013-08-03 18:35:36 +0200 | [diff] [blame] | 1472 | if lnum ==# 0 |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1473 | return s:IndentError('Matching token for "end" not found', |
| 1474 | \'end', []) |
| 1475 | else |
| 1476 | call s:Log(' Tokenize for "end" <<<<') |
| 1477 | let [lnum, indtokens] = s:TokenizeLine(lnum, 'up') |
| 1478 | call s:Log(' >>>> Tokenize for "end"') |
| 1479 | |
| 1480 | let [success, i] = s:GetIndtokenAtCol(indtokens, col) |
| 1481 | if !success | return i | endif |
| 1482 | let [token, curr_vcol, curr_col] = indtokens[i] |
| 1483 | call s:Log(' Match for "end" in line ' . lnum . ': ' . |
| 1484 | \string(indtokens[i])) |
| 1485 | return curr_vcol |
| 1486 | endif |
| 1487 | |
| 1488 | else |
| 1489 | |
| 1490 | call s:Log(" Line type = 'end'") |
| 1491 | let new_col = s:ErlangCalcIndent(v:lnum - 1, |
| 1492 | \[ml[2], 'align_to_begin_element']) |
| 1493 | endif |
| 1494 | else |
| 1495 | call s:Log(" Line type = 'normal'") |
| 1496 | |
| 1497 | let new_col = s:ErlangCalcIndent(v:lnum - 1, []) |
| 1498 | if currline =~# '^\s*when\>' |
| 1499 | let new_col += 2 |
| 1500 | endif |
| 1501 | endif |
| 1502 | |
| 1503 | if new_col < -1 |
| 1504 | call s:Log('WARNING: returning new_col == ' . new_col) |
| 1505 | return g:erlang_unexpected_token_indent |
| 1506 | endif |
| 1507 | |
| 1508 | return new_col |
| 1509 | |
| 1510 | endfunction |
| 1511 | |
Bram Moolenaar | 1d59aa1 | 2020-09-19 18:50:13 +0200 | [diff] [blame] | 1512 | " ErlangShowTokensInLine functions {{{1 |
| 1513 | " ================================ |
| 1514 | |
| 1515 | " These functions are useful during development. |
| 1516 | |
| 1517 | function! ErlangShowTokensInLine(line) |
| 1518 | echo "Line: " . a:line |
| 1519 | let indtokens = s:GetTokensFromLine(a:line, 0, 0, &tabstop) |
| 1520 | echo "Tokens:" |
| 1521 | for it in indtokens |
| 1522 | echo it |
| 1523 | endfor |
| 1524 | endfunction |
| 1525 | |
| 1526 | function! ErlangShowTokensInCurrentLine() |
| 1527 | return ErlangShowTokensInLine(getline('.')) |
| 1528 | endfunction |
| 1529 | |
Bram Moolenaar | ad3b366 | 2013-05-17 18:14:19 +0200 | [diff] [blame] | 1530 | " Cleanup {{{1 |
| 1531 | " ======= |
| 1532 | |
| 1533 | let &cpo = s:cpo_save |
| 1534 | unlet s:cpo_save |
| 1535 | |
| 1536 | " vim: sw=2 et fdm=marker |