├── README.md
├── ftdetect
└── erlang.vim
├── ftplugin
└── erlang.vim
├── indent
└── erlang.vim
├── syntax
└── erlang.vim
└── test
├── helper.vim
├── test_ftplugin_set_options.vader
├── test_include_search.vader
├── test_indent.erl
├── test_indent_manual.erl
├── test_indent_manual.vim
├── test_indent_tabs.erl
└── test_syntax.erl
/README.md:
--------------------------------------------------------------------------------
1 | # Erlang runtime files for Vim
2 |
3 | This repository contains the indentation, syntax and ftplugin scripts which are
4 | shipped with Vim for the Erlang programming language. Here you can download the
5 | newest version and contribute.
6 |
7 | ## Table of Contents
8 |
9 | * [Installation](#installation)
10 | * [Tips](#tips)
11 | * [Indentation from the command line](#indentation-from-the-command-line)
12 | * [Finding files](#finding-files)
13 | * [Development](#development)
14 | * [File layout](#file-layout)
15 | * [Erlang-related files in Vim](#erlang-related-files-in-vim)
16 | * [Developing and testing the indentation script](#developing-and-testing-the-indentation-script)
17 | * [Running Vader tests](#running-vader-tests)
18 | * [Contributing](#contributing)
19 |
20 | ## Installation
21 |
22 |
23 | Vim's built-in package manager
24 |
25 | This is the recommended installation method if you use at least Vim 8 and you
26 | don't use another package manager.
27 |
28 | Information about Vim's built-in package manager: [`:help packages`].
29 |
30 | Installation steps:
31 |
32 | 1. Clone this repository (you can replace `foo` with the directory name of your
33 | choice):
34 |
35 | ```sh
36 | $ git clone https://github.com/vim-erlang/vim-erlang-runtime.git \
37 | ~/.vim/pack/foo/start/vim-erlang-runtime
38 | ```
39 |
40 | 2. Restart Vim.
41 |
42 |
43 |
44 | Pathogen
45 |
46 | Information about Pathogen: [Pathogen repository].
47 |
48 | Installation steps:
49 |
50 | 1. Clone this repository:
51 |
52 | ```
53 | $ git clone https://github.com/vim-erlang/vim-erlang-runtime.git \
54 | ~/.vim/bundle/vim-erlang-runtime
55 | ```
56 |
57 | 2. Restart Vim.
58 |
59 |
60 |
61 | Vundle
62 |
63 | Information about Vundle: [Vundle repository].
64 |
65 | Installation steps:
66 |
67 | 1. Add `vim-erlang-runtime` to your plugin list in `.vimrc` by inserting
68 | the line that starts with `Plugin`:
69 |
70 | ```
71 | call vundle#begin()
72 | [...]
73 | Plugin 'vim-erlang/vim-erlang-runtime'
74 | [...]
75 | call vundle#end()
76 | ```
77 |
78 | 2. Restart Vim.
79 |
80 | 3. Run `:PluginInstall`.
81 |
82 |
83 |
84 | Vim-Plug
85 |
86 | Information about Vim-Plug: [vim-plug repository].
87 |
88 | Installation steps:
89 |
90 | 1. Add `vim-erlang-runtime` to your plugin list in `.vimrc` by inserting the
91 | line that starts with `Plug`:
92 |
93 | ```
94 | call plug#begin()
95 | [...]
96 | Plug 'vim-erlang/vim-erlang-runtime'
97 | [...]
98 | call plug#end()
99 | ```
100 |
101 | 2. Restart Vim.
102 |
103 | 3. Run `:PlugInstall`.
104 |
105 |
106 | ## Tips
107 |
108 | ### Indentation from the command line
109 |
110 | The following snippet re-indents all `src/*.?rl` files using the indentation
111 | shipped with Vim:
112 |
113 | ```bash
114 | vim -ENn -u NONE \
115 | -c 'filetype plugin indent on' \
116 | -c 'set expandtab shiftwidth=4' \
117 | -c 'args src/*.?rl' \
118 | -c 'argdo silent execute "normal gg=G" | update' \
119 | -c q
120 | ```
121 |
122 | Notes:
123 |
124 | * This can be for example added to a Makefile as a "re-indent rule".
125 |
126 | * You can use the `expandtab`, `shiftwidth` and `tabstop` options to customize
127 | how to use space and tab characters. The command above uses only spaces, and
128 | one level of indentation is 4 spaces.
129 |
130 | * If you would like to use a different version of the indentation script from
131 | that one shipped in Vim, then also add the following as the first command
132 | parameter (replace the `/path/to` part):
133 |
134 | ```bash
135 | -c ':set runtimepath^=/path/to/vim-erlang-runtime/'
136 | ```
137 |
138 | ### Finding files
139 |
140 | This plugin augments vim's `path` setting to include common Erlang source and
141 | header file paths. These paths are relative to the current directory so ensure
142 | that your vim working directory is at your project's root (see `:help
143 | current-directory`).
144 |
145 | To disable this feature, add the following setting:
146 |
147 | ```vim
148 | set g:erlang_extend_path=0
149 | ```
150 |
151 | ## Development
152 |
153 | ### File layout
154 |
155 | This repository contains the following files and directories:
156 |
157 |
158 |
159 | * [`ftdetect/erlang.vim`]: Script for detecting Erlang files based on file
160 | extension. See [`:help ftdetect`].
161 |
162 | File type detection based on content (e.g., when the first line
163 | is `#!/usr/bin/escript`) is not in this file. See
164 | [`:help new-filetype-scripts`].
165 |
166 | * [`ftplugin/erlang.vim`] File type plugin for Erlang files. See
167 | [`:help ftplugin`].
168 |
169 | This file is also distributed with Vim as
170 | [`runtime/ftplugin/erlang.vim`][vim-src/runtime/ftplugin/erlang.vim].
171 |
172 | * [`indent/erlang.vim`]: Indentation plugin for Erlang files. See
173 | [`:help indent-expression`].
174 |
175 | This file is also distributed with Vim as
176 | [`runtime/indent/erlang.vim`][vim-src/runtime/indent/erlang.vim].
177 |
178 | * [`syntax/erlang.vim`]: Syntax highlight plugin for Erlang files. See
179 | [`:help syntax`].
180 |
181 | This file is also distributed with Vim as
182 | [`runtime/syntax/erlang.vim`][vim-src/runtime/syntax/erlang.vim].
183 |
184 | * [`test`]: Manual and automatic test that help the development and testing of
185 | vim-erlang-runtime.
186 |
187 | ### Erlang-related files in Vim
188 |
189 | The Vim repository contains the following Erlang-related files:
190 |
191 |
192 |
193 | * [`runtime/compiler/erlang.vim`][vim-src/runtime/compiler/erlang.vim]:
194 | Allows simple Erlang files to be compiled after calling `:compiler erlang`.
195 | `vim-erlang-compiler` has a similar but broader scope.
196 |
197 | * [`runtime/doc/syntax.txt`][vim-src/runtime/doc/syntax.txt]:
198 | Contains documentation about configuring `runtime/syntax/erlang.vim`.
199 |
200 | * [`runtime/filetype.vim`][vim-src/runtime/filetype.vim]:
201 | Sets the file type to `erlang` if the file name matches either `*.erl`,
202 | `*.hrl` or `*.yaws`. The list of patterns is a subset of the patterns in
203 | `ftdetect/erlang.vim` in this repository.
204 |
205 | * [`runtime/ftplugin/erlang.vim`][vim-src/runtime/ftplugin/erlang.vim]:
206 | Same as [`ftplugin/erlang.vim`] in this repository.
207 |
208 | * [`runtime/indent/erlang.vim`][vim-src/runtime/indent/erlang.vim]:
209 | Same as [`indent/erlang.vim`] in this repository.
210 |
211 | * [`runtime/makemenu.vim`][vim-src/runtime/makemenu.vim]:
212 | Allows Erlang to be selected in the syntax menu. See also
213 | `runtime/synmenu.vim`.
214 |
215 | * [`runtime/scripts.vim`][vim-src/runtime/scripts.vim]:
216 | Sets the file type to `erlang` if the first line of the file matches
217 | `^#! [...]escript`. (It is not trivial what is accepted in the `[...]`
218 | part.)
219 |
220 | * [`runtime/synmenu.vim`][vim-src/runtime/synmenu.vim]:
221 | Allows Erlang to be selected in the syntax menu. See also
222 | `runtime/makemenu.vim`.
223 |
224 | * [`runtime/syntax/erlang.vim`][vim-src/runtime/syntax/erlang.vim]:
225 | Same as [`syntax/erlang.vim`] in this repository.
226 |
227 | * [`src/testdir/test_filetype.vim`][vim-src/src/testdir/test_filetype.vim]:
228 | An automatic test for setting file types.
229 |
230 | ### Developing and testing the indentation script
231 |
232 | The indentation script can be tested in the following way:
233 |
234 | 1. Copy `syntax/erlang.vim` into `~/syntax`.
235 |
236 | 2. Change to the `test` directory and open `test/test_indent.erl`.
237 |
238 | Note: `test_indent.erl` always shows how the Erlang code is indented by the
239 | script – not how it should be.
240 |
241 | 3. Source `helper.vim` (`:source helper.vim`)
242 |
243 | 4. Press F1 to load the new indentation (`indent/erlang.vim`).
244 |
245 | 5. Press F3 to re-indent the current line. Press shift-F3 to print a log.
246 |
247 | 6. Press F4 to re-indent the current buffer.
248 |
249 | 7. Press F5 to show the tokens of the current line.
250 |
251 | Note: When the indentation scripts detects a syntax error in test mode (i.e.
252 | when it was loaded with `F1` from `helper.vim`), it indents the line to column
253 | 40 instead of leaving it as it is. This behavior is useful for testing.
254 |
255 | ### Running Vader tests
256 |
257 | The tests for the `include` and `define` options in `test_include_search.vader`
258 | are run using the [Vader] Vim plugin.
259 |
260 | A common pattern to use for test cases is to do the following:
261 |
262 | ```vim
263 | Given:
264 | text to test
265 |
266 | Do:
267 | daw
268 |
269 | Expect:
270 | to text
271 | ```
272 |
273 | The text that should be tested is placed in the `Given` block. A normal command
274 | is placed in the `Do` block and the expected output in the `Expect` block. The
275 | cursor is by default on the first column in the first line, and doing `daw`
276 | should therefore delete around the first word.
277 |
278 | The simplest way to run a Vader test file is to open the test file in Vim and
279 | run `:Vader`. To run it from the command line, do `vim '+Vader!*' && echo
280 | Success || echo Failure`. If the environment variable `VADER_OUTPUT_FILE` is
281 | set, the results are written to this file.
282 |
283 | To test the code with only the wanted plugins loaded and without a vimrc, you
284 | can go to the `test` directory and execute the following command:
285 |
286 | ```bash
287 | vim -N -u NONE \
288 | -c 'set runtimepath=..,$VIMRUNTIME,~/.vim/plugged/vader.vim' \
289 | -c 'runtime plugin/vader.vim' \
290 | -c 'filetype plugin indent on' \
291 | -c 'Vader!*' \
292 | && echo Success || echo Failure
293 | ```
294 |
295 | The command does the following:
296 |
297 | 1. Starts Vim with `nocompatible` set and without sourcing any vimrc.
298 |
299 | 2. Puts the directory above the current one, i.e. the root directory of this
300 | repository, first in the `runtimepath`, such that the ftplugin, indent etc.
301 | from this repository are sourced first. Then the regular runtime path is
302 | added and finally the path to where Vader is installed is added (this will
303 | be different depending on which plugin manager you use, the path below is
304 | where vim-plug puts it).
305 |
306 | 3. Sources the Vader plugin file so that the `Vader` command can be used.
307 |
308 | 4. Enables using filetype specific settings and indentation.
309 |
310 | 5. Runs all Vader test files found in the current directory and then exits Vim.
311 |
312 | 6. Echoes `Success` if all test cases pass, else `Failure`.
313 |
314 | For more details, see the [Vader] repository.
315 |
316 | ## Contributing
317 |
318 | * Please read the [Contributing][vim-erlang-contributing] section of the
319 | [`vim-erlang`] README.
320 |
321 |
322 |
323 | [`:help ftdetect`]: https://vimhelp.org/filetype.txt.html#ftdetect
324 | [`:help ftplugin`]: https://vimhelp.org/usr_41.txt.html#ftplugin
325 | [`:help indent-expression`]: https://vimhelp.org/indent.txt.html#indent-expression
326 | [`:help new-filetype-scripts`]: https://vimhelp.org/filetype.txt.html#new-filetype-scripts
327 | [`:help packages`]: https://vimhelp.org/repeat.txt.html#packages
328 | [`:help syntax`]: https://vimhelp.org/syntax.txt.html#syntax
329 | [`ftdetect/erlang.vim`]: ftdetect/erlang.vim
330 | [`ftplugin/erlang.vim`]: ftplugin/erlang.vim
331 | [`indent/erlang.vim`]: indent/erlang.vim
332 | [`syntax/erlang.vim`]: syntax/erlang.vim
333 | [`test`]: test
334 | [`vim-erlang`]: https://github.com/vim-erlang/vim-erlang
335 | [Pathogen repository]: https://github.com/tpope/vim-pathogen
336 | [Vader]: https://github.com/junegunn/vader.vim
337 | [vim-erlang-contributing]: https://github.com/vim-erlang/vim-erlang#contributing
338 | [vim-plug repository]: https://github.com/junegunn/vim-plug
339 | [vim-src/runtime/compiler/erlang.vim]: https://github.com/vim/vim/blob/master/runtime/compiler/erlang.vim
340 | [vim-src/runtime/doc/syntax.txt]: https://github.com/vim/vim/blob/master/runtime/doc/syntax.txt
341 | [vim-src/runtime/filetype.vim]: https://github.com/vim/vim/blob/master/runtime/filetype.vim
342 | [vim-src/runtime/ftplugin/erlang.vim]: https://github.com/vim/vim/blob/master/runtime/ftplugin/erlang.vim
343 | [vim-src/runtime/indent/erlang.vim]: https://github.com/vim/vim/blob/master/runtime/indent/erlang.vim
344 | [vim-src/runtime/makemenu.vim]: https://github.com/vim/vim/blob/master/runtime/makemenu.vim
345 | [vim-src/runtime/scripts.vim]: https://github.com/vim/vim/blob/master/runtime/scripts.vim
346 | [vim-src/runtime/synmenu.vim]: https://github.com/vim/vim/blob/master/runtime/synmenu.vim
347 | [vim-src/runtime/syntax/erlang.vim]: https://github.com/vim/vim/blob/master/runtime/syntax/erlang.vim
348 | [vim-src/src/testdir/test_filetype.vim]: https://github.com/vim/vim/blob/master/src/testdir/test_filetype.vim
349 | [Vundle repository]: https://github.com/VundleVim/Vundle.vim
350 |
--------------------------------------------------------------------------------
/ftdetect/erlang.vim:
--------------------------------------------------------------------------------
1 | au BufNewFile,BufRead *.erl,*.hrl,rebar.config,*.app,*.app.src,*.yaws,*.xrl,*.escript set ft=erlang
2 |
--------------------------------------------------------------------------------
/ftplugin/erlang.vim:
--------------------------------------------------------------------------------
1 | " Vim ftplugin file
2 | " Language: Erlang (http://www.erlang.org)
3 | " Maintainer: Csaba Hoch
4 | " Author: Oscar Hellström
5 | " Contributors: Ricardo Catalinas Jiménez
6 | " Eduardo Lopez (http://github.com/tapichu)
7 | " Arvid Bjurklint (http://github.com/slarwise)
8 | " Paweł Zacharek (http://github.com/subc2)
9 | " Last Update: 2022-Sep-28
10 | " License: Vim license
11 | " URL: https://github.com/vim-erlang/vim-erlang-runtime
12 |
13 | if exists('b:did_ftplugin')
14 | finish
15 | endif
16 | let b:did_ftplugin = 1
17 |
18 | let s:cpo_save = &cpo
19 | set cpo&vim
20 |
21 | let &l:keywordprg = get(g:, 'erlang_keywordprg', 'erl -man')
22 |
23 | if get(g:, 'erlang_folding', 0)
24 | setlocal foldmethod=expr
25 | setlocal foldexpr=GetErlangFold(v:lnum)
26 | setlocal foldtext=ErlangFoldText()
27 | endif
28 |
29 | setlocal comments=:%%%,:%%,:%
30 | setlocal commentstring=%%s
31 |
32 | setlocal formatoptions+=ro
33 |
34 | if get(g:, 'erlang_extend_path', 1)
35 | " typical erlang.mk paths
36 | let &l:path = join([
37 | \ 'deps/*/include',
38 | \ 'deps/*/src',
39 | \ 'deps/*/test',
40 | \ 'deps/*/apps/*/include',
41 | \ 'deps/*/apps/*/src',
42 | \ &g:path], ',')
43 | " typical rebar3 paths
44 | let &l:path = join([
45 | \ 'apps/*/include',
46 | \ 'apps/*/src',
47 | \ '_build/default/lib/*/src',
48 | \ '_build/default/*/include',
49 | \ &l:path], ',')
50 | " typical erlang paths
51 | let &l:path = join(['include', 'src', 'test', &l:path], ',')
52 |
53 | set wildignore+=*/.erlang.mk/*,*.beam
54 | endif
55 |
56 | setlocal suffixesadd=.erl,.hrl
57 |
58 | let &l:include = '^\s*-\%(include\|include_lib\)\s*("\zs\f*\ze")'
59 | let &l:define = '^\s*-\%(define\|record\|type\|opaque\)'
60 |
61 | let s:erlang_fun_begin = '^\l[A-Za-z0-9_@]*(.*$'
62 | let s:erlang_fun_end = '^[^%]*\.\s*\(%.*\)\?$'
63 |
64 | if !exists('*GetErlangFold')
65 | function GetErlangFold(lnum)
66 | let lnum = a:lnum
67 | let line = getline(lnum)
68 |
69 | if line =~ s:erlang_fun_end
70 | return '<1'
71 | endif
72 |
73 | if line =~ s:erlang_fun_begin && foldlevel(lnum - 1) == 1
74 | return '1'
75 | endif
76 |
77 | if line =~ s:erlang_fun_begin
78 | return '>1'
79 | endif
80 |
81 | return '='
82 | endfunction
83 | endif
84 |
85 | if !exists('*ErlangFoldText')
86 | function ErlangFoldText()
87 | let line = getline(v:foldstart)
88 | let foldlen = v:foldend - v:foldstart + 1
89 | let lines = ' ' . foldlen . ' lines: ' . substitute(line, "[\ \t]*", '', '')
90 | if foldlen < 10
91 | let lines = ' ' . lines
92 | endif
93 | let retval = '+' . v:folddashes . lines
94 |
95 | return retval
96 | endfunction
97 | endif
98 |
99 | let b:undo_ftplugin = "setlocal keywordprg< foldmethod< foldexpr< foldtext<"
100 | \ . " comments< commentstring< formatoptions< suffixesadd< include<"
101 | \ . " define<"
102 |
103 | " The following lines enable the macros/matchit.vim plugin for
104 | " extended matching with the % key.
105 | if exists("loaded_matchit")
106 | let s:sw = &sw
107 | if exists('*shiftwidth')
108 | let s:sw = shiftwidth()
109 | endif
110 |
111 | let b:match_words =
112 | \ '\<\%(begin\|case\|fun\|if\|maybe\|receive\|try\)\>' .
113 | \ ':\<\%(after\|catch\|else\|of\)\>' .
114 | \ ':\,' .
115 | \ '^\l[A-Za-z0-9_@]*' .
116 | \ ':^\%(\%(\t\| \{' . s:sw . '}\)\%([^\t\ %][^%]*\)\?\)\?;\s*\%(%.*\)\?$\|\.[\t\ %]\|\.$'
117 | let b:match_skip = 's:comment\|string\|erlangmodifier\|erlangquotedatom'
118 | endif
119 |
120 | let &cpo = s:cpo_save
121 | unlet s:cpo_save
122 |
123 | " vim: sw=2 et
124 |
--------------------------------------------------------------------------------
/indent/erlang.vim:
--------------------------------------------------------------------------------
1 | " Vim indent file
2 | " Language: Erlang (http://www.erlang.org)
3 | " Author: Csaba Hoch
4 | " Contributors: Edwin Fine
5 | " Pawel 'kTT' Salata
6 | " Ricardo Catalinas Jiménez
7 | " Last Update: 2022-Sep-06
8 | " License: Vim license
9 | " URL: https://github.com/vim-erlang/vim-erlang-runtime
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 | " ==============
23 |
24 | " Only load this indent file when no other was loaded
25 | " Vim 7 or later is needed
26 | if exists("b:did_indent") || version < 700
27 | finish
28 | else
29 | let b:did_indent = 1
30 | endif
31 |
32 | setlocal indentexpr=ErlangIndent()
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<"
36 |
37 | " Only define the functions once
38 | if exists("*ErlangIndent")
39 | finish
40 | endif
41 |
42 | let s:cpo_save = &cpo
43 | set cpo&vim
44 |
45 | " Logging library {{{1
46 | " ===============
47 |
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
56 | endfunction
57 |
58 | " Line tokenizer library {{{1
59 | " ======================
60 |
61 | " Indtokens are "indentation tokens". See their exact format in the
62 | " documentation of the s:GetTokensFromLine function.
63 |
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)
79 |
80 | " We copy the relevant segment of the line, otherwise if the line were
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]
84 |
85 | let i = 0
86 | let last_index = a:last_index - a:first_index
87 | let vcol = a:vcol
88 |
89 | while 0 <= i && i <= last_index
90 |
91 | if line[i] ==# "\t"
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)
105 | else
106 | let next_i = matchend(line, '[^\t]*', i + 1)
107 | let vcol += next_i - i
108 | call s:Log('new vcol after other: '. vcol)
109 | endif
110 | let i = next_i
111 | endwhile
112 |
113 | return vcol
114 | endfunction
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]
125 | " token = string (examples: 'begin', '', '}')
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)
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)
139 | if i ==# -1
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, ['', vcol, i])
145 | endif
146 | elseif a:atom_continuation
147 | let i = matchend(a:line, "^\\%([^'\\\\]\\|\\\\.\\)*'", 0)
148 | if i ==# -1
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, ['', vcol, i])
154 | endif
155 | endif
156 |
157 | while 0 <= i && i < linelen
158 |
159 | let next_vcol = ''
160 |
161 | " Spaces
162 | if a:line[i] ==# ' '
163 | let next_i = matchend(a:line, ' *', i + 1)
164 |
165 | " Tabs
166 | elseif a:line[i] ==# "\t"
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
173 | elseif a:line[i] ==# '%'
174 | let next_i = linelen
175 |
176 | " String token: "..."
177 | elseif a:line[i] ==# '"'
178 | let next_i = matchend(a:line, '\%([^"\\]\|\\.\)*"', i + 1)
179 | if next_i ==# -1
180 | call add(indtokens, ['', vcol, i])
181 | else
182 | let next_vcol = s:CalcVCol(a:line, i, next_i - 1, vcol, a:tabstop)
183 | call add(indtokens, ['', vcol, i])
184 | endif
185 |
186 | " Quoted atom token: '...'
187 | elseif a:line[i] ==# "'"
188 | let next_i = matchend(a:line, "\\%([^'\\\\]\\|\\\\.\\)*'", i + 1)
189 | if next_i ==# -1
190 | call add(indtokens, ['', vcol, i])
191 | else
192 | let next_vcol = s:CalcVCol(a:line, i, next_i - 1, vcol, a:tabstop)
193 | call add(indtokens, ['', 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: $ (as in: $a)
204 | elseif a:line[i] ==# '$'
205 | call add(indtokens, ['$.', vcol, i])
206 | let next_i = i + 2
207 |
208 | " Dot token: .
209 | elseif a:line[i] ==# '.'
210 |
211 | let next_i = i + 1
212 |
213 | if i + 1 ==# linelen || a:line[i + 1] =~# '[[:blank:]%]'
214 | " End of clause token: . (as in: f() -> ok.)
215 | call add(indtokens, ['', 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
225 | elseif a:line[i] ==# '='
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 &&
240 | \ index(['->', '<<', '>>', '||', '==', '/=', '=<', '>=', '?=', '++',
241 | \ '--', '::'],
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 |
253 | if next_vcol ==# ''
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)
271 | if a:indtokens[i][2] ==# a:col
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)
350 | if a:direction ==# 'up'
351 | let lnum = prevnonblank(a:lnum)
352 | else " a:direction ==# 'down'
353 | let lnum = nextnonblank(a:lnum)
354 | endif
355 |
356 | " We hit the beginning or end of the file
357 | if lnum ==# 0
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:
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
399 | function! s:FindIndToken(lnum, dir)
400 | let lnum = a:lnum
401 | while 1
402 | let lnum += (a:dir ==# 'up' ? -1 : 1)
403 | let [lnum, indtokens] = s:TokenizeLine(lnum, a:dir)
404 | if lnum ==# 0
405 | " We hit the beginning or end of the file
406 | return [[], 0, 0]
407 | elseif !empty(indtokens)
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]
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
430 | return [s:all_tokens[a:lnum][a:i - 1], a:lnum, a:i - 1]
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
450 | return [s:all_tokens[a:lnum][a:i + 1], a:lnum, a:i + 1]
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')
531 | let syn_name = synIDattr(synID(a:lnum, 1, 0), 'name')
532 | return syn_name =~# '^erlangQuotedAtom' ||
533 | \ syn_name =~# '^erlangQuotedRecord'
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)
550 | let [prev_indtoken, _, _] = s:PrevIndToken(a:lnum, a:i)
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 |
559 | if prev_token =~# '^[A-Z_@0-9]'
560 | let is_standalone = 0
561 | elseif prev_token =~# '[a-z]'
562 | if index(['after', 'and', 'andalso', 'band', 'begin', 'bnot', 'bor', 'bsl',
563 | \ 'bsr', 'bxor', 'case', 'catch', 'div', 'maybe', 'not', 'or',
564 | \ 'orelse', 'rem', 'try', 'xor'], prev_token) != -1
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
573 | " - may not precede 'catch': else fun if of receive when
574 | " - unused: cond let query
575 | let is_standalone = 0
576 | endif
577 | elseif index([')', ']', '}', '', '', '',
578 | \ '', '$.'], prev_token) != -1
579 | let is_standalone = 0
580 | else
581 | " This 'else' branch includes the following tokens:
582 | " -> == /= =< < >= > ?= =:= =/= + - * / ++ -- :: < > ; ( [ { ? = ! . |
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
595 | " if already empty.
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)
609 | if a:stored_vcol ==# -1
610 | call s:Log(' "' . a:token . '" directly precedes LTI -> return')
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 |
646 | if a:stack[0] ==# a:end_token
647 | call s:Log(' "' . a:token . '" pops "' . a:end_token . '"')
648 | call s:Pop(a:stack)
649 | if !empty(a:stack) && a:stack[0] ==# 'align_to_begin_element'
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
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
678 | " Returns:
679 | " result: [should_return, indent]
680 | " should_return: bool -- if true, the caller should return `indent` to Vim
681 | " indent -- integer
682 | function! s:BeginningOfClauseFound(stack, token, stored_vcol, lnum, i)
683 | if !empty(a:stack) && a:stack[0] ==# 'when'
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')
688 | return [1, a:stored_vcol + shiftwidth() + 2]
689 | else
690 | return [1, s:UnexpectedToken(a:token, a:stack)]
691 | endif
692 | elseif !empty(a:stack) && a:stack[0] ==# '->'
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')
697 | return [1, a:stored_vcol + shiftwidth()]
698 | elseif a:stack[0] ==# ';'
699 | call s:Pop(a:stack)
700 |
701 | if !empty(a:stack)
702 | return [1, s:UnexpectedToken(a:token, a:stack)]
703 | endif
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 |
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,
764 | \ '\C\<\%(case\|try\|begin\|receive\|if\|maybe\)\>\|' .
765 | \ '\\%(\s\|\n\|%.*$\|[A-Z_@][a-zA-Z_@]*\)*(',
766 | \ '',
767 | \ '\')
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
805 | if lnum ==# 0
806 | let [ret, res] = s:BeginningOfClauseFound(stack, 'beginning_of_file',
807 | \stored_vcol, 0, 0)
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 |
825 | if token ==# ''
826 | let [ret, res] = s:BeginningOfClauseFound(stack, token, stored_vcol,
827 | \lnum, i)
828 | if ret | return res | endif
829 |
830 | if stored_vcol ==# -1
831 | call s:Log(' End of clause directly precedes LTI -> return')
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']
839 | if token =~# '[a-zA-Z_@#]' ||
840 | \ token ==# '' || token ==# '' ||
841 | \ token ==# '' || token ==# ''
842 | call s:Log(' previous token found: curr_vcol + plus = ' .
843 | \curr_vcol . " + " . plus)
844 | return curr_vcol + plus
845 | endif
846 |
847 | elseif token ==# 'begin'
848 | let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
849 | \stored_vcol, 'end', shiftwidth())
850 | if ret | return res | endif
851 |
852 | " case EXPR of BRANCHES end
853 | " if BRANCHES end
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
862 | " maybe EXPR end
863 | " maybe EXPR else BRANCHES end
864 |
865 | " This branch is not Emacs-compatible
866 | elseif (index(['of', 'receive', 'after', 'if', 'else'], token) != -1 ||
867 | \ (token ==# 'catch' && !s:IsCatchStandalone(lnum, i))) &&
868 | \ !last_token_of_line &&
869 | \ (empty(stack) || stack ==# ['when'] || stack ==# ['->'] ||
870 | \ stack ==# ['->', ';'])
871 |
872 | " If we are after of/receive/etc, but these are not the last
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 ' .
898 | \'"of/receive/after/if/else/catch" -> return')
899 | return stored_vcol
900 | elseif stack == ['->']
901 | call s:Log(' LTI is in a branch after ' .
902 | \'"of/receive/after/if/else/catch" -> return')
903 | return stored_vcol + shiftwidth()
904 | elseif stack == ['when']
905 | call s:Log(' LTI is in a guard after ' .
906 | \'"of/receive/after/if/else/catch" -> return')
907 | return stored_vcol + shiftwidth()
908 | else
909 | return s:UnexpectedToken(token, stack)
910 | endif
911 |
912 | elseif index(['case', 'if', 'try', 'receive', 'maybe'], token) != -1
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
920 | elseif (token ==# 'case' && stack[0] ==# 'of') ||
921 | \ (token ==# 'if') ||
922 | \ (token ==# 'maybe' && stack[0] ==# 'else') ||
923 | \ (token ==# 'try' && (stack[0] ==# 'of' ||
924 | \ stack[0] ==# 'catch' ||
925 | \ stack[0] ==# 'after')) ||
926 | \ (token ==# 'receive')
927 |
928 | " From the indentation point of view, the keyword
929 | " (of/catch/after/else/end) before the LTI is what counts, so
930 | " when we reached these tokens, and the stack already had
931 | " a catch/after/else/end, we didn't modify it.
932 | "
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
935 | " stack.
936 | if token ==# 'case' || token ==# 'try' ||
937 | \ (token ==# 'receive' && stack[0] ==# 'after') ||
938 | \ (token ==# 'maybe' && stack[0] ==# 'else')
939 | call s:Pop(stack)
940 | endif
941 |
942 | if empty(stack)
943 | call s:Log(' LTI is in a condition; matching ' .
944 | \'"case/if/try/receive/maybe" found')
945 | let stored_vcol = curr_vcol + shiftwidth()
946 | elseif stack[0] ==# 'align_to_begin_element'
947 | call s:Pop(stack)
948 | let stored_vcol = curr_vcol
949 | elseif len(stack) > 1 && stack[0] ==# '->' && stack[1] ==# ';'
950 | call s:Log(' LTI is in a condition; matching ' .
951 | \'"case/if/try/receive/maybe" found')
952 | call s:Pop(stack)
953 | call s:Pop(stack)
954 | let stored_vcol = curr_vcol + shiftwidth()
955 | elseif stack[0] ==# '->'
956 | call s:Log(' LTI is in a branch; matching ' .
957 | \'"case/if/try/receive/maybe" found')
958 | call s:Pop(stack)
959 | let stored_vcol = curr_vcol + 2 * shiftwidth()
960 | elseif stack[0] ==# 'when'
961 | call s:Log(' LTI is in a guard; matching ' .
962 | \'"case/if/try/receive/maybe" found')
963 | call s:Pop(stack)
964 | let stored_vcol = curr_vcol + 2 * shiftwidth() + 2
965 | endif
966 |
967 | endif
968 |
969 | let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
970 | \stored_vcol, 'end', shiftwidth())
971 | if ret | return res | endif
972 |
973 | elseif token ==# 'fun'
974 | let [next_indtoken, next_lnum, next_i] = s:NextIndToken(lnum, i)
975 | call s:Log(' Next indtoken = ' . string(next_indtoken))
976 |
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 |
986 | if !empty(next_indtoken) && next_indtoken[0] ==# '('
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')
996 | let stored_vcol = curr_vcol + shiftwidth()
997 | elseif len(stack) > 1 && stack[0] ==# '->' && stack[1] ==# ';'
998 | call s:Log(' LTI is in a condition; matching "fun" found')
999 | call s:Pop(stack)
1000 | call s:Pop(stack)
1001 | elseif stack[0] ==# '->'
1002 | call s:Log(' LTI is in a branch; matching "fun" found')
1003 | call s:Pop(stack)
1004 | let stored_vcol = curr_vcol + 2 * shiftwidth()
1005 | elseif stack[0] ==# 'when'
1006 | call s:Log(' LTI is in a guard; matching "fun" found')
1007 | call s:Pop(stack)
1008 | let stored_vcol = curr_vcol + 2 * shiftwidth() + 2
1009 | endif
1010 |
1011 | let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
1012 | \stored_vcol, 'end', shiftwidth())
1013 | if ret | return res | endif
1014 | else
1015 | " Pass: we have a function reference (e.g. "fun f/0")
1016 | endif
1017 |
1018 | elseif token ==# '['
1019 | " Emacs compatibility
1020 | let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
1021 | \stored_vcol, ']', 1)
1022 | if ret | return res | endif
1023 |
1024 | elseif token ==# '<<'
1025 | " Emacs compatibility
1026 | let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
1027 | \stored_vcol, '>>', 2)
1028 | if ret | return res | endif
1029 |
1030 | elseif token ==# '(' || token ==# '{'
1031 |
1032 | let end_token = (token ==# '(' ? ')' :
1033 | \token ==# '{' ? '}' : 'error')
1034 |
1035 | if empty(stack)
1036 | " We found the opening paren whose block contains the LTI.
1037 | let mode = 'inside'
1038 | elseif stack[0] ==# end_token
1039 | call s:Log(' "' . token . '" pops "' . end_token . '"')
1040 | call s:Pop(stack)
1041 |
1042 | if !empty(stack) && stack[0] ==# 'align_to_begin_element'
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 |
1055 | if mode ==# 'inside' || mode ==# 'align_to_begin_element'
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']
1079 | let plus = (mode ==# 'inside' ? 2 : 1)
1080 | call s:Log(' "' . token .
1081 | \'" token found at end of line -> find previous token')
1082 | elseif mode ==# 'align_to_begin_element'
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
1106 | elseif stored_vcol ==# -1
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 |
1152 | elseif index(['end', ')', ']', '}', '>>'], token) != -1
1153 |
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 '>>'
1163 | elseif token ==# '>>'
1164 | call s:Push(stack, token)
1165 |
1166 | elseif token ==# 'end'
1167 | let [lnum_new, col_new] = s:SearchEndPair(lnum, curr_col)
1168 |
1169 | if lnum_new ==# 0
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]))
1184 | endif
1185 |
1186 | else " token is one of the following: ')', ']', '}'
1187 |
1188 | call s:Push(stack, token)
1189 |
1190 | " We have to escape '[', because this string will be interpreted as a
1191 | " regexp
1192 | let open_paren = (token ==# ')' ? '(' :
1193 | \token ==# ']' ? '\[' :
1194 | \ '{')
1195 |
1196 | let [lnum_new, col_new] = s:SearchPair(lnum, curr_col,
1197 | \open_paren, '', token)
1198 |
1199 | if lnum_new ==# 0
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
1208 |
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
1217 | endif
1218 | endif
1219 |
1220 | elseif token ==# ';'
1221 |
1222 | if empty(stack)
1223 | call s:Push(stack, ';')
1224 | elseif index([';', '->', 'when', 'end', 'after', 'catch', 'else'],
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
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
1238 | " the stack.
1239 | else
1240 | return s:UnexpectedToken(token, stack)
1241 | endif
1242 |
1243 | elseif token ==# '->'
1244 |
1245 | if empty(stack) && !last_token_of_line
1246 | call s:Log(' LTI is in expression after arrow -> return')
1247 | return stored_vcol
1248 | elseif empty(stack) || stack[0] ==# ';' || stack[0] ==# 'end'
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, '->')
1253 | elseif index(['->', 'when', 'end', 'after', 'catch', 'else'],
1254 | \stack[0]) != -1
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
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
1266 | " the stack.
1267 | else
1268 | return s:UnexpectedToken(token, stack)
1269 | endif
1270 |
1271 | elseif token ==# 'when'
1272 |
1273 | " Pop all ';' from the top of the stack
1274 | while !empty(stack) && stack[0] ==# ';'
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,
1287 | \stored_vcol, shiftwidth())
1288 | if ret | return res | endif
1289 | else
1290 | " Example:
1291 | " when
1292 | " LTI
1293 | call s:Push(stack, token)
1294 | endif
1295 | elseif index(['->', 'when', 'end', 'after', 'catch', 'else'],
1296 | \stack[0]) != -1
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
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
1308 | " the stack.
1309 | else
1310 | return s:UnexpectedToken(token, stack)
1311 | endif
1312 |
1313 | elseif token ==# 'of' || token ==# 'after' || token ==# 'else' ||
1314 | \ (token ==# 'catch' && !s:IsCatchStandalone(lnum, i))
1315 |
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 compatibility {{{
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 | " }}}
1361 | let [ret, res] = s:BeginElementFoundIfEmpty(stack, token, curr_vcol,
1362 | \stored_vcol, shiftwidth())
1363 | if ret | return res | endif
1364 | endif
1365 |
1366 | if empty(stack) || stack[0] ==# '->' || stack[0] ==# 'when'
1367 | call s:Push(stack, token)
1368 | elseif stack[0] ==# 'catch' || stack[0] ==# 'after' ||
1369 | \stack[0] ==# 'else' || stack[0] ==# 'end'
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 |
1380 | elseif token ==# '||' && empty(stack) && !last_token_of_line
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 |
1390 | if empty(stack) || stack[0] ==# '->' || stack[0] ==# 'when'
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)
1395 | elseif stack[0] ==# ';'
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] != '' &&
1411 | \ indtokens[0][0] != '')
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 |
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 |
1458 | let ml = matchlist(currline,
1459 | \'^\(\s*\)\(\%(end\|of\|catch\|after\|else\)\>\|[)\]}]\|>>\)')
1460 |
1461 | " If the line has a special beginning, but not a standalone catch
1462 | if !empty(ml) && !(ml[2] ==# 'catch' && s:IsCatchStandalone(v:lnum, 0))
1463 |
1464 | let curr_col = len(ml[1])
1465 |
1466 | " If we can be sure that there is synchronization in the Erlang
1467 | " syntax, we use searchpair to make the script quicker.
1468 | if ml[2] ==# 'end' && exists('b:erlang_syntax_synced')
1469 |
1470 | let [lnum, col] = s:SearchEndPair(v:lnum, curr_col)
1471 |
1472 | if lnum ==# 0
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 |
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 |
1530 | " Cleanup {{{1
1531 | " =======
1532 |
1533 | let &cpo = s:cpo_save
1534 | unlet s:cpo_save
1535 |
1536 | " vim: sw=2 et fdm=marker
1537 |
--------------------------------------------------------------------------------
/syntax/erlang.vim:
--------------------------------------------------------------------------------
1 | " Vim syntax file
2 | " Language: Erlang (http://www.erlang.org)
3 | " Maintainer: Csaba Hoch
4 | " Contributor: Adam Rutkowski
5 | " Last Update: 2022-Sep-06
6 | " License: Vim license
7 | " URL: https://github.com/vim-erlang/vim-erlang-runtime
8 |
9 | " Acknowledgements: This script was originally created by Kresimir Marzic [1].
10 | " The script was then revamped by Csaba Hoch [2]. During the revamp, the new
11 | " highlighting style and some code was taken from the Erlang syntax script
12 | " that is part of vimerl [3], created by Oscar Hellström [4] and improved by
13 | " Ricardo Catalinas Jiménez [5].
14 |
15 | " [1]: Kreąimir Marľić (Kresimir Marzic)
16 | " [2]: Csaba Hoch
17 | " [3]: https://github.com/jimenezrick/vimerl
18 | " [4]: Oscar Hellström (http://oscar.hellstrom.st)
19 | " [5]: Ricardo Catalinas Jiménez
20 |
21 | " Customization:
22 | "
23 | " To use the old highlighting style, add this to your .vimrc:
24 | "
25 | " let g:erlang_old_style_highlight = 1
26 | "
27 | " To highlight further module attributes, add them to
28 | " ~/.vim/after/syntax/erlang.vim:
29 | "
30 | " syn keyword erlangAttribute myattr1 myattr2 contained
31 |
32 | " quit when a syntax file was already loaded
33 | if exists("b:current_syntax")
34 | finish
35 | endif
36 |
37 | let s:cpo_save = &cpo
38 | set cpo&vim
39 |
40 | " "g:erlang_old_style_highlight": Whether to use old style highlighting.
41 | "
42 | " * "g:erlang_old_style_highlight == 0" (default): Use new style
43 | " highlighting.
44 | "
45 | " * "g:erlang_old_style_highlight == 1": Use old style highlighting.
46 | let s:old_style = (exists("g:erlang_old_style_highlight") &&
47 | \g:erlang_old_style_highlight == 1)
48 |
49 | " "g:erlang_use_markdown_for_docs": Whether to use Markdown highlighting in
50 | " docstrings.
51 | "
52 | " * "g:erlang_use_markdown_for_docs == 1": Enable Markdown highlighting in
53 | " docstrings.
54 | "
55 | " * "g:erlang_use_markdown_for_docs == 0" (default): Disable Markdown
56 | " highlighting in docstrings.
57 | if exists("g:erlang_use_markdown_for_docs")
58 | let s:use_markdown = g:erlang_use_markdown_for_docs
59 | else
60 | let s:use_markdown = 0
61 | endif
62 |
63 | " "g:erlang_docstring_default_highlight": How to highlight the text inside
64 | " docstrings (except the text which is highlighted by Markdown).
65 | "
66 | " If "g:erlang_use_markdown_for_docs == 1":
67 | "
68 | " * "g:erlang_docstring_default_highlight == 'Comment'" (default): the plugin
69 | " highlights the plain text inside Markdown as Markdown normally does,
70 | " with comment highlighting to regular text in the docstring.
71 | "
72 | " * If you set g:erlang_docstring_default_highlight to the name of highlight
73 | " group, for example "String", the plugin highlights the plain text inside
74 | " Markdown with the specified highlight group. See ":highlight" for the
75 | " available groups. You may also set it to an empty string to disable any
76 | " specific highlighting.
77 | "
78 | " If "g:erlang_use_markdown_for_docs == 0":
79 | "
80 | " * "g:erlang_docstring_default_highlight == 'Comment'" (default): the plugin
81 | " does not highlight the contents of the docstring as markdown, but
82 | " continues to display them in the style of comments.
83 | "
84 | " * If you set g:erlang_docstring_default_highlight to the name of highlight
85 | " group, for example "String", the plugin highlights the plain text inside
86 | " Markdown with the specified highlight group. See ":highlight" for the
87 | " available groups. You may also set it to an empty string to disable any
88 | " specific highlighting.
89 | "
90 | " Configuration examples:
91 | "
92 | " " Highlight docstrings as Markdown.
93 | " let g:erlang_use_markdown_for_docs = 1
94 | " let g:erlang_docstring_default_highlight = 'Comment'
95 | "
96 | " " 1. Highlight Markdown elements in docstrings as Markdown.
97 | " " 2. Highlight the plain text in docstrings as String.
98 | " let g:erlang_use_markdown_for_docs = 1
99 | " let g:erlang_docstring_default_highlight = 'String'
100 | "
101 | " " Highlight docstrings as strings.
102 | " let g:erlang_use_markdown_for_docs = 0
103 | " let g:erlang_docstring_default_highlight = 'String'
104 | "
105 | " " Highlight docstrings as comments (default).
106 | " let g:erlang_use_markdown_for_docs = 0
107 | " let g:erlang_docstring_default_highlight = 'Comment'
108 | if exists("g:erlang_docstring_default_highlight")
109 | let s:docstring_default_highlight = g:erlang_docstring_default_highlight
110 | else
111 | let s:docstring_default_highlight = 'Comment'
112 | endif
113 |
114 | " Case sensitive
115 | syn case match
116 |
117 | setlocal iskeyword+=$,@-@
118 |
119 | " Comments
120 | syn match erlangComment '%.*$' contains=erlangCommentAnnotation,erlangTodo
121 | syn match erlangCommentAnnotation ' \@<=@\%(clear\|docfile\|end\|headerfile\|todo\|TODO\|type\|author\|copyright\|doc\|reference\|see\|since\|title\|version\|deprecated\|hidden\|param\|private\|equiv\|spec\|throws\)' contained
122 | syn match erlangCommentAnnotation /`[^']*'/ contained
123 | syn keyword erlangTodo TODO FIXME XXX contained
124 |
125 | " Numbers (minimum base is 2, maximum is 36.)
126 | syn match erlangNumberInteger '\<\d\+\>'
127 | syn match erlangNumberInteger '\<\%([2-9]\|[12]\d\|3[0-6]\)\+#[[:alnum:]]\+\>'
128 | syn match erlangNumberFloat '\<\d\+\.\d\+\%([eE][+-]\=\d\+\)\=\>'
129 |
130 | " Strings, atoms, characters
131 | syn region erlangString start=/"/ end=/"/ contains=erlangStringModifier
132 | syn region erlangStringTripleQuoted matchgroup=String start=/"""/ end=/\%(^\s*\)\@<="""/ keepend
133 |
134 | " Documentation
135 | syn region erlangDocString start=/^-\%(module\)\=doc\s*\~\="""/ end=/\%(^\s*\)\@<="""\.$/ contains=@erlangDocStringCluster keepend
136 | syn region erlangDocString start=/^-\%(module\)\=doc\s*<<"""/ end=/\%(^\s*\)\@<=""">>\.$/ contains=@erlangDocStringCluster keepend
137 | syn region erlangDocString start=/^-\%(module\)\=doc\s*\~\="/ end=/"\.$/ contains=@erlangDocStringCluster keepend
138 | syn region erlangDocString start=/^-\%(module\)\=doc\s*<<"/ end=/">>\.$/ contains=@erlangDocStringCluster keepend
139 | syn cluster erlangDocStringCluster contains=erlangInnerDocAttribute,erlangDocStringDelimiter
140 | syn region erlangDocStringDelimiter matchgroup=erlangString start=/"/ end=/"/ contains=@erlangDocStringContained contained
141 | syn region erlangDocStringDelimiter matchgroup=erlangString start=/"""/ end=/"""/ contains=@erlangDocStringContained contained
142 |
143 | if s:use_markdown
144 | syn cluster erlangDocStringContained contains=@markdown
145 | endif
146 |
147 | syn region erlangQuotedAtom start=/'/ end=/'/ contains=erlangQuotedAtomModifier
148 | syn match erlangStringModifier '\\\%(\o\{1,3}\|x\x\x\|x{\x\+}\|\^.\|.\)\|\~\%([ni~]\|\%(-\=\d\+\|\*\)\=\.\=\%(\*\|\d\+\)\=\%(\..\)\=[tl]*[cfegswpWPBX#bx+]\)' contained
149 | syn match erlangQuotedAtomModifier '\\\%(\o\{1,3}\|x\x\x\|x{\x\+}\|\^.\|.\)' contained
150 | syn match erlangModifier '\$\%([^\\]\|\\\%(\o\{1,3}\|x\x\x\|x{\x\+}\|\^.\|.\)\)'
151 |
152 | " Operators, separators
153 | syn match erlangOperator '==\|=:=\|/=\|=/=\|<\|=<\|>\|>=\|=>\|:=\|?=\|++\|--\|=\|!\|<-\|+\|-\|\*\|\/'
154 | syn match erlangEqualsBinary '=<<\%(<\)\@!'
155 | syn keyword erlangOperator div rem or xor bor bxor bsl bsr and band not bnot andalso orelse
156 | syn match erlangBracket '{\|}\|\[\|]\||\|||'
157 | syn match erlangPipe '|'
158 | syn match erlangRightArrow '->'
159 |
160 | " Atoms, function calls (order is important)
161 | syn match erlangAtom '\<\l[[:alnum:]_@]*' contains=erlangBoolean
162 | syn keyword erlangBoolean true false contained
163 | syn match erlangLocalFuncCall '\<\a[[:alnum:]_@]*\>\%(\%(\s\|\n\|%.*\n\)*(\)\@=' contains=erlangBIF
164 | syn match erlangLocalFuncRef '\<\a[[:alnum:]_@]*\>\%(\%(\s\|\n\|%.*\n\)*/\)\@='
165 | syn match erlangGlobalFuncCall '\<\%(\a[[:alnum:]_@]*\%(\s\|\n\|%.*\n\)*\.\%(\s\|\n\|%.*\n\)*\)*\a[[:alnum:]_@]*\%(\s\|\n\|%.*\n\)*:\%(\s\|\n\|%.*\n\)*\a[[:alnum:]_@]*\>\%(\%(\s\|\n\|%.*\n\)*(\)\@=' contains=erlangComment,erlangVariable
166 | syn match erlangGlobalFuncRef '\<\%(\a[[:alnum:]_@]*\%(\s\|\n\|%.*\n\)*\.\%(\s\|\n\|%.*\n\)*\)*\a[[:alnum:]_@]*\%(\s\|\n\|%.*\n\)*:\%(\s\|\n\|%.*\n\)*\a[[:alnum:]_@]*\>\%(\%(\s\|\n\|%.*\n\)*/\)\@=' contains=erlangComment,erlangVariable
167 |
168 | " Variables, macros, records, maps
169 | syn match erlangVariable '\<[A-Z][[:alnum:]_@]*'
170 | syn match erlangAnonymousVariable '\<_[[:alnum:]_@]*'
171 | syn match erlangMacro '??\=[[:alnum:]_@]\+'
172 | syn match erlangMacro '\%(-define(\)\@<=[[:alnum:]_@]\+'
173 | syn region erlangQuotedMacro start=/??\=\s*'/ end=/'/ contains=erlangQuotedAtomModifier
174 | syn match erlangMap '#'
175 | syn match erlangRecord '#\s*\l[[:alnum:]_@]*'
176 | syn region erlangQuotedRecord start=/#\s*'/ end=/'/ contains=erlangQuotedAtomModifier
177 |
178 | " Shebang (this line has to be after the ErlangMap)
179 | syn match erlangShebang '^#!.*'
180 |
181 | " Bitstrings
182 | syn match erlangBitType '\%(\/\%(\s\|\n\|%.*\n\)*\)\@<=\%(integer\|float\|binary\|bytes\|bitstring\|bits\|binary\|utf8\|utf16\|utf32\|signed\|unsigned\|big\|little\|native\|unit\)\%(\%(\s\|\n\|%.*\n\)*-\%(\s\|\n\|%.*\n\)*\%(integer\|float\|binary\|bytes\|bitstring\|bits\|binary\|utf8\|utf16\|utf32\|signed\|unsigned\|big\|little\|native\|unit\)\)*' contains=erlangComment
183 |
184 | " Constants and Directives
185 | syn match erlangUnknownAttribute '^\s*-\%(\s\|\n\|%.*\n\)*\l[[:alnum:]_@]*' contains=erlangComment
186 | syn match erlangAttribute '^\s*-\%(\s\|\n\|%.*\n\)*\%(behaviou\=r\|compile\|dialyzer\|export\|export_type\|file\|import\|module\|author\|copyright\|vsn\|on_load\|optional_callbacks\|feature\|mode\)\>' contains=erlangComment
187 | syn match erlangDocAttribute '^\s*-\%(\s\|\n\|%.*\n\)*\%(moduledoc\|doc\)\>' contains=erlangComment,erlangDocString
188 | syn match erlangInnerDocAttribute '^\s*-\%(\s\|\n\|%.*\n\)*\%(moduledoc\|doc\)\>' contained
189 | syn match erlangInclude '^\s*-\%(\s\|\n\|%.*\n\)*\%(include\|include_lib\)\>' contains=erlangComment
190 | syn match erlangRecordDef '^\s*-\%(\s\|\n\|%.*\n\)*record\>' contains=erlangComment
191 | syn match erlangDefine '^\s*-\%(\s\|\n\|%.*\n\)*\%(define\|undef\)\>' contains=erlangComment
192 | syn match erlangPreCondit '^\s*-\%(\s\|\n\|%.*\n\)*\%(ifdef\|ifndef\|else\|endif\)\>' contains=erlangComment
193 | syn match erlangType '^\s*-\%(\s\|\n\|%.*\n\)*\%(spec\|type\|opaque\|nominal\|callback\)\>' contains=erlangComment
194 |
195 | " Keywords
196 | syn keyword erlangKeyword after begin case catch cond end fun if let of else
197 | syn keyword erlangKeyword receive when try maybe
198 |
199 | " Build-in-functions (BIFs)
200 | syn keyword erlangBIF abs alive apply atom_to_binary atom_to_list contained
201 | syn keyword erlangBIF binary_part binary_to_atom contained
202 | syn keyword erlangBIF binary_to_existing_atom binary_to_float contained
203 | syn keyword erlangBIF binary_to_integer bitstring_to_list contained
204 | syn keyword erlangBIF binary_to_list binary_to_term bit_size contained
205 | syn keyword erlangBIF byte_size check_old_code check_process_code contained
206 | syn keyword erlangBIF concat_binary date delete_module demonitor contained
207 | syn keyword erlangBIF disconnect_node element erase error exit contained
208 | syn keyword erlangBIF float float_to_binary float_to_list contained
209 | syn keyword erlangBIF garbage_collect get get_keys group_leader contained
210 | syn keyword erlangBIF halt hd integer_to_binary integer_to_list contained
211 | syn keyword erlangBIF iolist_to_binary iolist_size is_alive contained
212 | syn keyword erlangBIF is_atom is_binary is_bitstring is_boolean contained
213 | syn keyword erlangBIF is_float is_function is_integer is_list is_map is_map_key contained
214 | syn keyword erlangBIF is_number is_pid is_port is_process_alive contained
215 | syn keyword erlangBIF is_record is_reference is_tuple length link contained
216 | syn keyword erlangBIF list_to_atom list_to_binary contained
217 | syn keyword erlangBIF list_to_bitstring list_to_existing_atom contained
218 | syn keyword erlangBIF list_to_float list_to_integer list_to_pid contained
219 | syn keyword erlangBIF list_to_tuple load_module make_ref map_size max contained
220 | syn keyword erlangBIF min module_loaded monitor monitor_node node contained
221 | syn keyword erlangBIF nodes now open_port pid_to_list port_close contained
222 | syn keyword erlangBIF port_command port_connect pre_loaded contained
223 | syn keyword erlangBIF process_flag process_flag process_info contained
224 | syn keyword erlangBIF process purge_module put register registered contained
225 | syn keyword erlangBIF round self setelement size spawn spawn_link contained
226 | syn keyword erlangBIF spawn_monitor spawn_opt split_binary contained
227 | syn keyword erlangBIF statistics term_to_binary throw time tl contained
228 | syn keyword erlangBIF trunc tuple_size tuple_to_list unlink contained
229 | syn keyword erlangBIF unregister whereis contained
230 |
231 | " Sync at the beginning of functions: if this is not used, multiline string
232 | " are not always recognized, and the indentation script cannot use the
233 | " "searchpair" (because it would not always skip strings and comments when
234 | " looking for keywords and opening parens/brackets).
235 | syn sync match erlangSync grouphere NONE "^[a-z]\s*("
236 | let b:erlang_syntax_synced = 1
237 |
238 | " Define the default highlighting. See ":help group-name" for the groups and
239 | " their colors.
240 |
241 | if s:use_markdown
242 | " Add markdown syntax elements for docstrings (actually, for all
243 | " triple-quoted strings).
244 | unlet! b:current_syntax
245 |
246 | syn include @markdown syntax/markdown.vim
247 | let b:current_syntax = "erlang"
248 |
249 | " markdown-erlang.vim includes html.vim, which includes css.vim, which adds
250 | " the dash character (-) to the list of syntax keywords, which causes
251 | " `-VarName` not to be highlighted as a variable in the Erlang code.
252 | "
253 | " Here we override that.
254 | syntax iskeyword @,48-57,192-255,$,_
255 | endif
256 |
257 | " Comments
258 | hi def link erlangComment Comment
259 | hi def link erlangCommentAnnotation Special
260 | hi def link erlangTodo Todo
261 | hi def link erlangShebang Comment
262 |
263 | " Numbers
264 | hi def link erlangNumberInteger Number
265 | hi def link erlangNumberFloat Float
266 |
267 | " Strings, atoms, characters
268 | hi def link erlangString String
269 | hi def link erlangStringTripleQuoted String
270 |
271 | " Triple quoted strings
272 | if s:docstring_default_highlight != ''
273 | execute 'hi def link erlangDocStringDelimiter '. s:docstring_default_highlight
274 | endif
275 |
276 | if s:old_style
277 | hi def link erlangQuotedAtom Type
278 | else
279 | hi def link erlangQuotedAtom String
280 | endif
281 |
282 | hi def link erlangStringModifier Special
283 | hi def link erlangQuotedAtomModifier Special
284 | hi def link erlangModifier Special
285 |
286 | " Operators, separators
287 | hi def link erlangOperator Operator
288 | hi def link erlangEqualsBinary ErrorMsg
289 | hi def link erlangRightArrow Operator
290 | if s:old_style
291 | hi def link erlangBracket Normal
292 | hi def link erlangPipe Normal
293 | else
294 | hi def link erlangBracket Delimiter
295 | hi def link erlangPipe Delimiter
296 | endif
297 |
298 | " Atoms, functions, variables, macros
299 | if s:old_style
300 | hi def link erlangAtom Normal
301 | hi def link erlangLocalFuncCall Normal
302 | hi def link erlangLocalFuncRef Normal
303 | hi def link erlangGlobalFuncCall Function
304 | hi def link erlangGlobalFuncRef Function
305 | hi def link erlangVariable Normal
306 | hi def link erlangAnonymousVariable erlangVariable
307 | hi def link erlangMacro Normal
308 | hi def link erlangQuotedMacro Normal
309 | hi def link erlangRecord Normal
310 | hi def link erlangQuotedRecord Normal
311 | hi def link erlangMap Normal
312 | else
313 | hi def link erlangAtom String
314 | hi def link erlangLocalFuncCall Normal
315 | hi def link erlangLocalFuncRef Normal
316 | hi def link erlangGlobalFuncCall Normal
317 | hi def link erlangGlobalFuncRef Normal
318 | hi def link erlangVariable Identifier
319 | hi def link erlangAnonymousVariable erlangVariable
320 | hi def link erlangMacro Macro
321 | hi def link erlangQuotedMacro Macro
322 | hi def link erlangRecord Structure
323 | hi def link erlangQuotedRecord Structure
324 | hi def link erlangMap Structure
325 | endif
326 |
327 | " Bitstrings
328 | if !s:old_style
329 | hi def link erlangBitType Type
330 | endif
331 |
332 | " Constants and Directives
333 | if s:old_style
334 | hi def link erlangAttribute Type
335 | hi def link erlangMacroDef Type
336 | hi def link erlangUnknownAttribute Normal
337 | hi def link erlangInclude Type
338 | hi def link erlangRecordDef Type
339 | hi def link erlangDefine Type
340 | hi def link erlangPreCondit Type
341 | hi def link erlangType Type
342 | else
343 | hi def link erlangAttribute Keyword
344 | hi def link erlangDocAttribute Keyword
345 | hi def link erlangInnerDocAttribute Keyword
346 | hi def link erlangMacroDef Macro
347 | hi def link erlangUnknownAttribute Normal
348 | hi def link erlangInclude Include
349 | hi def link erlangRecordDef Keyword
350 | hi def link erlangDefine Define
351 | hi def link erlangPreCondit PreCondit
352 | hi def link erlangType Type
353 | endif
354 |
355 | " Keywords
356 | hi def link erlangKeyword Keyword
357 |
358 | " Build-in-functions (BIFs)
359 | hi def link erlangBIF Function
360 |
361 | if s:old_style
362 | hi def link erlangBoolean Statement
363 | hi def link erlangExtra Statement
364 | hi def link erlangSignal Statement
365 | else
366 | hi def link erlangBoolean Boolean
367 | hi def link erlangExtra Statement
368 | hi def link erlangSignal Statement
369 | endif
370 |
371 |
372 | let b:current_syntax = "erlang"
373 |
374 | let &cpo = s:cpo_save
375 | unlet s:cpo_save
376 |
377 | " vim: sw=2 et
378 |
--------------------------------------------------------------------------------
/test/helper.vim:
--------------------------------------------------------------------------------
1 | if &ft != 'erlang'
2 | throw 'This helper script should be called only on an Erlang file!'
3 | endif
4 |
5 | " ----------- "
6 | " Indentation "
7 | " ----------- "
8 |
9 | setlocal debug=msg,throw
10 |
11 | " Automatic indentkeys are not always helpful for developing the indentation
12 | " script
13 | setlocal indentkeys-==after,=end,=catch,=),=],=}
14 |
15 | " Reread indentation file
16 | noremap :call RereadIndent()
17 |
18 | function! RereadIndent()
19 | if exists("*ErlangIndent")
20 | delfunction ErlangIndent
21 | endif
22 | unlet b:did_indent
23 | so ../indent/erlang.vim
24 | let g:erlang_unexpected_token_indent = 40
25 | let g:erlang_indent_searchpair_timeout = 20000
26 | endfunction
27 |
28 | " Indent the current line
29 | noremap :call IndentCurrentLinePerf()
30 | noremap :call IndentCurrentLineLog()
31 |
32 | function! IndentCurrentLineLog()
33 | let g:hcs1 = exists("*ErlangIndentLog")
34 | call DefineErlangLog()
35 | let g:hcs2 = exists("*ErlangIndentLog")
36 | call ClearDebugLog()
37 | normal ==
38 | call PrintDebugLog()
39 | endfunction
40 |
41 | function! IndentCurrentLinePerf()
42 | call DeleteErlangLog()
43 | let start = reltime()
44 | normal ==
45 | echo "Execution time: " . reltimestr(reltime(start))
46 | endfunction
47 |
48 |
49 | " Indent the whole buffer
50 | noremap :call IndentCurrentBufferPerf(1)
51 | noremap :call IndentCurrentBufferPerf(0)
52 |
53 | function! IndentCurrentBufferPerf(use_cache)
54 | call DeleteErlangLog()
55 | let start = reltime()
56 | normal mkHmlggvG=`lzt`k
57 | echo "Execution time: " . reltimestr(reltime(start))
58 | endfunction
59 |
60 | " Show tokens in current line
61 | noremap :call ErlangShowTokensInCurrentLine()
62 |
63 | " --------- "
64 | " Debugging "
65 | " --------- "
66 |
67 | let g:debug_log = ''
68 |
69 | function! ClearDebugLog()
70 | let g:debug_log = ''
71 | endfunction
72 |
73 | function! PrintDebugLog()
74 | echo g:debug_log
75 | endfunction
76 |
77 | function! DefineErlangLog()
78 | function! ErlangIndentLog(line)
79 | let g:debug_log .= a:line . "\n"
80 | endfunction
81 | endfunction
82 |
83 | function! DeleteErlangLog()
84 | if exists("*ErlangIndentLog")
85 | delfunction ErlangIndentLog
86 | endif
87 | endfunction
88 |
--------------------------------------------------------------------------------
/test/test_ftplugin_set_options.vader:
--------------------------------------------------------------------------------
1 | " Tests for checking that options and variables are set correctly in
2 | " ftplugin/erlang.vim.
3 |
4 | Before (Map options/variables to their expected values and clean up global variables):
5 | let b:erlang_expected_values = {
6 | \ '&l:keywordprg': 'erl -man',
7 | \ '&l:foldmethod': &foldmethod,
8 | \ '&l:foldexpr': &foldexpr,
9 | \ '&l:foldtext': &foldtext,
10 | \ '&l:comments': ':%%%,:%%,:%',
11 | \ '&l:commentstring': '%%s',
12 | \ '&l:formatoptions': &formatoptions . 'ro',
13 | \ '&l:suffixesadd': '.erl,.hrl',
14 | \ '&l:include': '^\s*-\%(include\|include_lib\)\s*("\zs\f*\ze")',
15 | \ '&l:define': '^\s*-\%(define\|record\|type\|opaque\)',
16 | \ 'b:did_ftplugin': 1,
17 | \ '&cpoptions': &cpoptions,
18 | \ 'b:undo_ftplugin': 'setlocal keywordprg< foldmethod< foldexpr< foldtext<'
19 | \ . ' comments< commentstring< formatoptions< suffixesadd< include<'
20 | \ . ' define<'
21 | \ }
22 |
23 | unlet! g:erlang_folding
24 | unlet! g:erlang_keywordprg
25 |
26 | After (Check if options/variables have been set correctly and clean up global variables):
27 | for [option, expected] in items(b:erlang_expected_values)
28 | AssertEqual expected, eval(option)
29 | endfor
30 |
31 | unlet! g:erlang_folding
32 | unlet! g:erlang_keywordprg
33 |
34 | Execute(Don't set any g:erlang_* variables):
35 | source ../ftplugin/erlang.vim
36 |
37 | Execute(Set g:erlang_keywordprg):
38 | let g:erlang_keywordprg = 'mykeywordprg'
39 | let b:erlang_expected_values['&l:keywordprg'] = 'mykeywordprg'
40 |
41 | source ../ftplugin/erlang.vim
42 |
43 | Execute(Set g:erlang_folding):
44 | let g:erlang_folding = 1
45 | let b:erlang_expected_values['&l:foldmethod'] = 'expr'
46 | let b:erlang_expected_values['&l:foldexpr'] = 'GetErlangFold(v:lnum)'
47 | let b:erlang_expected_values['&l:foldtext'] = 'ErlangFoldText()'
48 |
49 | source ../ftplugin/erlang.vim
50 |
51 | Execute(Set g:erlang_keywordprg and g:erlang_folding):
52 | let g:erlang_keywordprg = 'mykeywordprg'
53 | let b:erlang_expected_values['&l:foldmethod'] = 'expr'
54 | let b:erlang_expected_values['&l:foldexpr'] = 'GetErlangFold(v:lnum)'
55 | let b:erlang_expected_values['&l:foldtext'] = 'ErlangFoldText()'
56 |
57 | let g:erlang_folding = 1
58 | let b:erlang_expected_values['&l:keywordprg'] = 'mykeywordprg'
59 |
60 | source ../ftplugin/erlang.vim
61 |
--------------------------------------------------------------------------------
/test/test_include_search.vader:
--------------------------------------------------------------------------------
1 | # Tests for include and define, which are set in ftplugin/erlang.vim
2 | # Include {{{1
3 |
4 | Given erlang(include):
5 | -include("header.hrl").
6 |
7 | Execute:
8 | let actual = split(execute('checkpath'), "---\n")[-1]
9 | let expected = '"header.hrl"'
10 | AssertEqual expected, actual
11 |
12 | Given erlang(include: space before -):
13 | -include("header.hrl").
14 |
15 | Execute:
16 | let actual = split(execute('checkpath'), "---\n")[-1]
17 | let expected = '"header.hrl"'
18 | AssertEqual expected, actual
19 |
20 | Given erlang(include: space before opening parenthesis):
21 | -include ("header.hrl").
22 |
23 | Execute:
24 | let actual = split(execute('checkpath'), "---\n")[-1]
25 | let expected = '"header.hrl"'
26 | AssertEqual expected, actual
27 |
28 | # Include_lib {{{1
29 |
30 | Given erlang(include_lib):
31 | -include_lib("header.hrl").
32 |
33 | Execute:
34 | let actual = split(execute('checkpath'), "---\n")[-1]
35 | let expected = '"header.hrl"'
36 | AssertEqual expected, actual
37 |
38 | Given erlang(include_lib: space before -):
39 | -include_lib("header.hrl").
40 |
41 | Execute:
42 | let actual = split(execute('checkpath'), "---\n")[-1]
43 | let expected = '"header.hrl"'
44 | AssertEqual expected, actual
45 |
46 | Given erlang(include_lib: space before opening parenthesis):
47 | -include_lib ("header.hrl").
48 |
49 | Execute:
50 | let actual = split(execute('checkpath'), "---\n")[-1]
51 | let expected = '"header.hrl"'
52 | AssertEqual expected, actual
53 |
54 | # Macro definition {{{1
55 | Given erlang(macro):
56 | -define(PI, 3.14).
57 | PI
58 |
59 | Do:
60 | G[\x
61 |
62 | Expect erlang:
63 | -define(I, 3.14).
64 | PI
65 |
66 | Given erlang((macro: space before -):
67 | -define(PI, 3.14).
68 | PI
69 |
70 | Do:
71 | G[\x
72 |
73 | Expect erlang:
74 | -define(I, 3.14).
75 | PI
76 |
77 | Given erlang(macro: space before opening parenthesis):
78 | -define (PI, 3.14).
79 | PI
80 |
81 | Do:
82 | G[\x
83 |
84 | Expect erlang:
85 | -define (I, 3.14).
86 | PI
87 |
88 | Given erlang(macro: definition not found):
89 | -define(PIE, 3.14).
90 | PI
91 |
92 | Execute:
93 | $
94 | AssertThrows normal [D
95 |
96 | # Record definition {{{1
97 | Given erlang(record):
98 | -record(person, {name, phone, address}).
99 | person
100 |
101 | Do:
102 | G[\x
103 |
104 | Expect erlang:
105 | -record(erson, {name, phone, address}).
106 | person
107 |
108 | Given erlang(record: space before -):
109 | -record(person, {name, phone, address}).
110 | person
111 |
112 | Do:
113 | G[\x
114 |
115 | Expect erlang:
116 | -record(erson, {name, phone, address}).
117 | person
118 |
119 | Given erlang(record: space before opening parenthesis):
120 | -record (person, {name, phone, address}).
121 | person
122 |
123 | Do:
124 | G[\x
125 |
126 | Expect erlang:
127 | -record (erson, {name, phone, address}).
128 | person
129 |
130 | Given erlang(record: definition not found):
131 | -record(persona, {name, phone, address}).
132 | person
133 |
134 | Execute:
135 | $
136 | AssertThrows normal [D
137 |
138 | # Type definition {{{1
139 | Given erlang(type):
140 | -type my_integer() :: integer().
141 | my_integer()
142 |
143 | Do:
144 | G[\x
145 |
146 | Expect erlang:
147 | -type y_integer() :: integer().
148 | my_integer()
149 |
150 | Given erlang(type: space before -):
151 | -type my_integer() :: integer().
152 | my_integer()
153 |
154 | Do:
155 | G[\x
156 |
157 | Expect erlang:
158 | -type y_integer() :: integer().
159 | my_integer()
160 |
161 | Given erlang(type: definition not found):
162 | -type my_integera() :: integer().
163 | my_integer
164 |
165 | Execute:
166 | $
167 | AssertThrows normal [D
168 |
169 | # Type( definition {{{1
170 | Given erlang(type():
171 | -type(my_integer() :: integer()).
172 | my_integer()
173 |
174 | Do:
175 | G[\x
176 |
177 | Expect erlang:
178 | -type(y_integer() :: integer()).
179 | my_integer()
180 |
181 | Given erlang(type(: space before -):
182 | -type(my_integer() :: integer()).
183 | my_integer()
184 |
185 | Do:
186 | G[\x
187 |
188 | Expect erlang:
189 | -type(y_integer() :: integer()).
190 | my_integer()
191 |
192 | Given erlang(type(: space before opening parenthesis):
193 | -type (my_integer() :: integer()).
194 | my_integer()
195 |
196 | Do:
197 | G[\x
198 |
199 | Expect erlang:
200 | -type (y_integer() :: integer()).
201 | my_integer()
202 |
203 | Given erlang(type(: definition not found):
204 | -type(my_integera() :: integer()).
205 | my_integer
206 |
207 | Execute:
208 | $
209 | AssertThrows normal [D
210 |
211 | # Opaque definition {{{1
212 | Given erlang(opaque):
213 | -opaque my_integer() :: integer().
214 | my_integer()
215 |
216 | Do:
217 | G[\x
218 |
219 | Expect erlang:
220 | -opaque y_integer() :: integer().
221 | my_integer()
222 |
223 | Given erlang(opaque: (space before -):
224 | -opaque my_integer() :: integer().
225 | my_integer()
226 |
227 | Do:
228 | G[\x
229 |
230 | Expect erlang:
231 | -opaque y_integer() :: integer().
232 | my_integer()
233 |
234 | Given erlang(opaque):
235 | -opaque my_integera() :: integer().
236 | my_integer()
237 |
238 | Execute:
239 | $
240 | AssertThrows normal [D
241 |
242 | # Opaque( definition {{{1
243 | Given erlang(opaque():
244 | -opaque(my_integer() :: integer()).
245 | my_integer()
246 |
247 | Do:
248 | G[\x
249 |
250 | Expect erlang:
251 | -opaque(y_integer() :: integer()).
252 | my_integer()
253 |
254 | Given erlang(opaque(: space before -):
255 | -opaque(my_integer() :: integer()).
256 | my_integer()
257 |
258 | Do:
259 | G[\x
260 |
261 | Expect erlang:
262 | -opaque(y_integer() :: integer()).
263 | my_integer()
264 |
265 | Given erlang(opaque(: space before opening parenthesis):
266 | -opaque (my_integer() :: integer()).
267 | my_integer()
268 |
269 | Do:
270 | G[\x
271 |
272 | Expect erlang:
273 | -opaque (y_integer() :: integer()).
274 | my_integer()
275 |
276 | Given erlang(opaque(: definition not found):
277 | -opaque(my_integera() :: integer()).
278 | my_integer()
279 |
280 | Execute:
281 | $
282 | AssertThrows normal [D
283 |
284 | # vim:foldmethod=marker
285 |
--------------------------------------------------------------------------------
/test/test_indent.erl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vim-erlang/vim-erlang-runtime/eff3cbd3306892b23ad2e55f5252061c1aad0871/test/test_indent.erl
--------------------------------------------------------------------------------
/test/test_indent_manual.erl:
--------------------------------------------------------------------------------
1 | f() ->
2 | % Comment indented weirdly for some reason.
3 | % Reindent this line - it should be below the previous one.
4 |
--------------------------------------------------------------------------------
/test/test_indent_manual.vim:
--------------------------------------------------------------------------------
1 | " The function in this file shall be called when test_indent_manual.erl is
2 | " open.
3 |
4 | function! TestIndentManual()
5 | normal 3gg
6 | exec "normal! I "
7 | normal ==
8 | endfunction
9 |
10 | noremap :call TestIndentManual()
11 |
--------------------------------------------------------------------------------
/test/test_indent_tabs.erl:
--------------------------------------------------------------------------------
1 | % vi: noexpandtab tabstop=5 shiftwidth=3
2 | %
3 | % This is a test file for indent/erlang.vim that is used to test that the
4 | % indentation works correctly when tabs and spaces are mixed.
5 | %
6 | % Tabstop and shiftwidth are set to peculiar values to make the test more
7 | % interesing.
8 |
9 | blocks() ->
10 | case
11 | X
12 | of
13 | A ->
14 | begin
15 | Expr1,
16 | Expr2
17 | end;
18 | B when
19 | Guard1;
20 | Guard2A,
21 | Guarg2B ->
22 | Expr
23 | end,
24 | ok.
25 |
26 | strings() ->
27 | begin
28 | "a b c", begin
29 | Expression
30 | end,
31 | "a
32 | b", begin
33 | Expression
34 | end,
35 | end,
36 | ok.
37 |
38 | atoms() ->
39 | begin
40 | 'a b c', begin
41 | Expression
42 | end,
43 | 'a
44 | b', begin
45 | Expression
46 | end,
47 | end,
48 | ok.
49 |
--------------------------------------------------------------------------------
/test/test_syntax.erl:
--------------------------------------------------------------------------------
1 | %%% Vim syntax highlight example file
2 | %%%
3 | %%% Language: Erlang (http://www.erlang.org)
4 | %%% Author: Csaba Hoch
5 | %%% License: Vim license (http://vimdoc.sourceforge.net/htmldoc/uganda.html)
6 | %%% URL: https://github.com/hcs42/vim-erlang
7 |
8 | %%% The organization of this file follows the Erlang Reference Manual:
9 | %%%
10 | %%% http://erlang.org/doc/reference_manual/users_guide.html
11 |
12 | %%% ===========================================================================
13 | %%% 1 Introduction
14 | %%% ===========================================================================
15 |
16 | %%% ===========================================================================
17 | %%% 1.5 Reserved Words
18 | %%% ===========================================================================
19 |
20 | reserved_words() ->
21 | after and andalso band begin bnot bor bsl bsr bxor case catch cond div end
22 | fun if let not of or orelse receive rem try when xor.
23 |
24 | %%% ===========================================================================
25 | %%% 2 Character Set and Source File Encoding
26 | %%% ===========================================================================
27 |
28 | %%% ===========================================================================
29 | %%% 2.1 Character Set
30 | %%% ===========================================================================
31 |
32 | string_characters() ->
33 | " ¿ ÀÖ × ØÞ ßö ÷ øÿ".
34 |
35 | variable_characters() ->
36 | ÀÖØÞßöøÿ.
37 |
38 | %%% ===========================================================================
39 | %%% 3 Data Types
40 | %%% ===========================================================================
41 |
42 | %%% ===========================================================================
43 | %%% 3.2 Number
44 | %%% ===========================================================================
45 |
46 | number_examples() ->
47 | 42,
48 | $A,
49 | $\n,
50 | 2#101,
51 | 2#102, % bad
52 | 8#17,
53 | 8#19, % bad
54 | 16#ff,
55 | 16#fg, % bad
56 | 17#fg,
57 | 2.3,
58 | 2.3e3,
59 | 2e3, % bad
60 | 2.3e-3,
61 | 2.3e1.2. % bad
62 |
63 | number_with_dollar() ->
64 | $\b,
65 | $\d,
66 | $\e,
67 | $\f,
68 | $\n,
69 | $\r,
70 | $\s,
71 | $\t,
72 | $\v,
73 | $\a,
74 | $\c,
75 | $\1,
76 | $\11,
77 | $\111,
78 | $\1111, % The last digit is not part of the modifier
79 | $\88, % "8" is not an octal digit
80 | $\x1, % Incorrect
81 | $\x11,
82 | $\xaF,
83 | $\x111, % The last digit is not part of the modifier
84 | $\x{1},
85 | $\x{abcDEF},
86 | $\^a, $\^z,
87 | $\^A, $\^Z,
88 | $\',
89 | $\",
90 | $\\.
91 |
92 | %%% ===========================================================================
93 | %%% 3.3 Atom
94 | %%% ===========================================================================
95 |
96 | atom_examples() ->
97 | '',
98 | hello,
99 | phone_number,
100 | hello@you,
101 | 'Monday',
102 | 'phone number',
103 | case@case.
104 |
105 | %%% ===========================================================================
106 | %%% 3.9 Tuple
107 | %%% 3.11 List
108 | %%% ===========================================================================
109 |
110 | tuple_list() ->
111 | {}, {A}, {A, B}
112 | [], [A], [A, B], [A|B].
113 |
114 | %%% ===========================================================================
115 | %%% 3.12 String
116 | %%% ===========================================================================
117 |
118 | multiline_string() ->
119 | "",
120 | "plain",
121 | "plain string",
122 | "multi
123 | line",
124 | "
125 | ".
126 |
127 | %%% ===========================================================================
128 | %%% 3.14 Boolean
129 | %%% ===========================================================================
130 |
131 | bools() ->
132 | true, false.
133 |
134 | true() ->
135 | true.
136 |
137 | false() ->
138 | false.
139 |
140 | %%% ===========================================================================
141 | %%% 3.15 Escape Sequences
142 | %%% ===========================================================================
143 |
144 | escape_sequences() ->
145 | "\b",
146 | "\d",
147 | "\e",
148 | "\f",
149 | "\n",
150 | "\r",
151 | "\s",
152 | "\t",
153 | "\v",
154 | "\a", % no such modifier
155 | "\c", % no such modifier
156 | "\1",
157 | "\11",
158 | "\111",
159 | "\1111", % The last digit is not part of the modifier
160 | "\88", % "8" is not an octal digit
161 | "\x1", % Incorrect
162 | "\x11",
163 | "\xaF",
164 | "\x111", % The last digit is not part of the modifier
165 | "\x{1}",
166 | "\x{abcDEF}",
167 | "\^a \^z ",
168 | "\^A \^Z",
169 | "\'",
170 | "\"",
171 | "\\".
172 |
173 | escape_sequences() ->
174 | '\b',
175 | '\d',
176 | '\e',
177 | '\f',
178 | '\n',
179 | '\r',
180 | '\s',
181 | '\t',
182 | '\v',
183 | '\1',
184 | '\11',
185 | '\111',
186 | '\x11',
187 | '\x{1}',
188 | '\x{abcDEF}',
189 | '\^a \^z ',
190 | '\^A \^Z',
191 | '\'',
192 | '\"',
193 | '\\'.
194 |
195 | %%% ===========================================================================
196 | %%% 5 Modules
197 | %%% ===========================================================================
198 |
199 | %%% ===========================================================================
200 | %%% 5.1 Module Syntax
201 | %%% ===========================================================================
202 |
203 | -module(Module).
204 | -export(Functions).
205 | -import(Module,Functions).
206 | -compile(Options).
207 | -vsn(Vsn).
208 | -on_load(Function).
209 | -behaviour(Behaviour).
210 | -behavior(Behaviour).
211 | -file(File, Line).
212 | -feature(maybe_expr, enable).
213 |
214 | % Specified in https://www.erlang.org/doc/man/escript.
215 | -mode(compile).
216 |
217 | % Unknown attribute (not highlighted).
218 | -other(File, Line).
219 |
220 | macros() ->
221 | ?FILE, ?LINE.
222 |
223 | -export_type([my_struct_type/0, orddict/2]).
224 |
225 | -export(Functions). -export(Functions).
226 |
227 | -
228 | export(Functions).
229 |
230 | - % comment
231 | export(Functions).
232 |
233 | %%% ===========================================================================
234 | %%% 5.3 Comments
235 | %%% ===========================================================================
236 |
237 | % Comment
238 | %% Comment
239 | %%% Comment
240 | %%%% Comment
241 |
242 |
243 | %%% ===========================================================================
244 | %%% 6 Documentation
245 | %%% ===========================================================================
246 |
247 | -module(syntax_test).
248 | % Support lack of () around it, see https://github.com/erlang/otp/issues/9502
249 | -module syntax_test.
250 | % Triple-quoted documentation strings.
251 | -moduledoc """
252 | A module for basic arithmetic.
253 | """.
254 |
255 | -moduledoc "A module for basic arithmetic".
256 |
257 | % Binary strings (1/2)
258 | -moduledoc ~"""
259 | A module for basic arithmetic.
260 | **Hello**!
261 | """.
262 | -moduledoc ~"A module for basic arithmetic".
263 |
264 | % Binary strings (2/2)
265 | -moduledoc <<"""
266 | A module for basic arithmetic.
267 | """>>.
268 | -moduledoc <<"A module for basic arithmetic">>.
269 |
270 | -export([add/2]).
271 |
272 | -doc "Adds two numbers.".
273 | -doc ~"Adds two numbers.".
274 | -doc <<"Adds two numbers.">>.
275 | add(One, Two) ->
276 | """
277 | This inline triple-quoted string serves to check highlighting.
278 | """,
279 | One + Two.
280 |
281 | %%% ===========================================================================
282 | %%% 6.1 Documentation metadata
283 | %%% ===========================================================================
284 |
285 | -moduledoc #{since => "1.0"}.
286 | -moduledoc #{since => ~"1.0"}.
287 | -moduledoc #{since => <<"1.0">>}.
288 |
289 | %%% ===========================================================================
290 | %%% 6.2 External documentation files
291 | %%% ===========================================================================
292 |
293 | -doc({file, "add.md"}).
294 | add(One, Two) -> One + Two.
295 |
296 | -moduledoc {file, "add.md"}.
297 |
298 | %%% ===========================================================================
299 | %%% 6.4 Documenting functions, user-defined types, and callbacks
300 | %%% ===========================================================================
301 |
302 | -doc(#{since => "1.0"}).
303 | -doc(#{since => ~"1.0"}).
304 | -doc(#{since => <<"1.0">>}).
305 | -doc(#{equiv => do_addition/2}).
306 | -doc(#{equiv => do_addition(One, Two)}).
307 | -doc(#{equiv => do_addition(One, Two)}).
308 | -doc(#{equiv => do_addition(One, Two)}).
309 | add(One, Two) -> One + Two.
310 |
311 | -doc "Documents a type.".
312 | -type editor :: vim | ed.
313 |
314 | %%% ===========================================================================
315 | %%% 6.5 Links in Markdown
316 | %%% ===========================================================================
317 |
318 | -doc "See [subtract](`sub/2`) for more details".
319 | sub(A, B) -> A - B.
320 |
321 | -doc "See [`sub/2`] for more details".
322 | sub(A, B) -> A - B.
323 |
324 | -doc """
325 | See [subtract] for more details
326 |
327 | **This is extra markdown syntax** *with the goal to test* the _highlighting_
328 | of nested markdown.
329 |
330 | [subtract]: `sub/2`
331 | """.
332 | sub(A, B) -> A - B.
333 |
334 | -doc """
335 | See [subtract][1] for more details
336 |
337 | [1]: `sub/2`
338 | """.
339 | sub(A, B) -> A - B.
340 |
341 | %%% ===========================================================================
342 | %%% 6.6 What is visible versus hidden?
343 | %%% ===========================================================================
344 |
345 | -moduledoc false.
346 |
347 | -doc false.
348 |
349 | %%% ===========================================================================
350 | %%% 7 Functions
351 | %%% ===========================================================================
352 |
353 | %%% ===========================================================================
354 | %%% 7.1 Function Declaration Syntax
355 | %%% ===========================================================================
356 |
357 | f({A}, [H|T]) when H > 0, T == 0;
358 | H < 0 ->
359 | ok;
360 | f(_X, _) ->
361 | ok.
362 |
363 | %%% ===========================================================================
364 | %%% 8 Types and Function Specifications
365 | %%% ===========================================================================
366 |
367 | -spec func(Opt, M) -> #{ 'status' => S, 'c' => integer() } when
368 | Opt :: 'inc' | 'dec',
369 | M :: #{ 'status' => S, 'c' => integer() },
370 | S :: 'update' | 'keep'.
371 |
372 | func(inc, #{ status := update, c := C} = M) -> M#{ c := C + 1};
373 | func(dec, #{ status := update, c := C} = M) -> M#{ c := C - 1};
374 | func(_, #{ status := keep } = M) -> M.
375 |
376 | -type map1() :: #{ binary() => integer() }.
377 | -type map2() :: #{ <<"list1">> | <<"list2">> => [numbers()] }.
378 |
379 | -nominal meter() :: integer().
380 | -opaque wheels() :: term().
381 |
382 | %%% ===========================================================================
383 | %%% 8.3 Type declarations of user-defined types
384 | %%% ===========================================================================
385 |
386 | -spec my_type() = term()
387 | | binary()
388 | | bitstring()
389 | | boolean()
390 | | byte()
391 | | char()
392 | | number()
393 | | list()
394 | | maybe_improper_list()
395 | | maybe_improper_list(T)
396 | | string()
397 | | nonempty_string()
398 | | iolist()
399 | | module()
400 | | mfa()
401 | | node()
402 | | timeout()
403 | | no_return()
404 | | non_neg_integer()
405 | | pos_integer()
406 | | neg_integer().
407 |
408 | %%% ===========================================================================
409 | %%% 10 Expressions
410 | %%% ===========================================================================
411 |
412 | %%% ===========================================================================
413 | %%% 10.3 Variables
414 | %%% ===========================================================================
415 |
416 | variables() ->
417 | X,
418 | Name1,
419 | PhoneNumber,
420 | Phone_number,
421 | _,
422 | _Height,
423 | Var@case. % just a variable
424 |
425 | %%% ===========================================================================
426 | %%% 10.6 Function calls
427 | %%% ===========================================================================
428 |
429 | func_calls() ->
430 |
431 | func(),
432 | func (),
433 |
434 | func
435 | (),
436 |
437 | func % comment
438 | (),
439 |
440 | a:b, % bad
441 |
442 | mod:func(),
443 | my_mod:my_func(),
444 | mod_:func_(),
445 |
446 | 1mod:func(), % bad
447 | @mod:func(), % bad
448 | m@d:f@nc(), % good
449 | mod : func(), % good
450 |
451 | mod
452 | :
453 | func(),
454 |
455 | mod % comment
456 | : % comment
457 | func().
458 |
459 | function_call_examples() ->
460 | ''(),
461 | hello(),
462 | phone_number(),
463 | hello@you(),
464 | 'Monday'(),
465 | 'phone number'(),
466 | case@case().
467 |
468 | func_calls() ->
469 |
470 | Func(),
471 | Func (),
472 |
473 | Func
474 | (),
475 |
476 | Func % comment
477 | (),
478 |
479 | a:b, % bad
480 |
481 | mod:Func(),
482 | my_mod:my_func(),
483 | mod_:func_(),
484 |
485 | 1mod:Func(), % bad
486 | @mod:Func(), % bad
487 | m@d:f@nc(), % good
488 | mod : Func(), % good
489 |
490 | mod
491 | :
492 | Func(),
493 |
494 | mod % comment
495 | : % comment
496 | Func().
497 |
498 | %%% ===========================================================================
499 | %%% 10.7 If
500 | %%% ===========================================================================
501 |
502 | if_example() ->
503 | if
504 | A -> A;
505 | true -> ok
506 | end.
507 |
508 | %%% ===========================================================================
509 | %%% 10.8 Case
510 | %%% ===========================================================================
511 |
512 | case_example() ->
513 | case X of
514 | A when A > 0 ->
515 | ok;
516 | A when A < 0 ->
517 | ok
518 | end.
519 |
520 | %%% ===========================================================================
521 | %%% 10.9 Maybe
522 | %%% ===========================================================================
523 |
524 | maybe_example_1() ->
525 | maybe
526 | {ok, A} ?= a(),
527 | true = A >= 0,
528 | {ok, B} ?= b(),
529 | A + B
530 | end.
531 |
532 | maybe_example_2() ->
533 | maybe
534 | {ok, A} ?= a(),
535 | true = A >= 0,
536 | {ok, B} ?= b(),
537 | A + B
538 | else
539 | error -> error;
540 | wrong -> error
541 | end.
542 |
543 | %%% ===========================================================================
544 | %%% 10.10 Send
545 | %%% ===========================================================================
546 |
547 | send_example() ->
548 | a ! b.
549 |
550 | %%% ===========================================================================
551 | %%% 10.11 Receive
552 | %%% ===========================================================================
553 |
554 | receive_example() ->
555 | receive
556 | A -> A;
557 | B -> B
558 | after
559 | T -> T
560 | end.
561 |
562 | %%% ===========================================================================
563 | %%% 10.12 Term Comparisons
564 | %%% ===========================================================================
565 |
566 | term_comparisons() ->
567 | A == A,
568 | A /= A,
569 | A =< A,
570 | A < A,
571 | A >= A,
572 | A > A,
573 | A =:= A,
574 | A =/= A,
575 | A=<<<"binary">>, % Same as: A =< <<"binary">>
576 | A==<<"binary">> % Same as: A == <<"binary">>
577 | A=<<"binary">>, % Same as: A =< <"binary">> (INVALID)
578 | ok.
579 |
580 | %%% ===========================================================================
581 | %%% 10.13 Arithmetic Expressions
582 | %%% ===========================================================================
583 |
584 | unary_operators() ->
585 | + A,
586 | - A,
587 | bnot A.
588 |
589 | binary_operators() ->
590 | A + A,
591 | A - A,
592 | A * A,
593 | A / A,
594 | A div A,
595 | A rem A,
596 | A band A,
597 | A bor A,
598 | A bxor A,
599 | A bsl A,
600 | A bsr A.
601 |
602 | %%% ===========================================================================
603 | %%% 10.14 Boolean Expressions
604 | %%% ===========================================================================
605 |
606 | unary_boolean() ->
607 | not A,
608 | ok.
609 |
610 | binary_boolean() ->
611 | A and A,
612 | A or A,
613 | A xor A,
614 | ok.
615 |
616 | %%% ===========================================================================
617 | %%% 10.15 Short-Circuit Expressions
618 | %%% ===========================================================================
619 |
620 | short_circuit() ->
621 | A andalso A,
622 | A orelse A.
623 |
624 | %%% ===========================================================================
625 | %%% 10.16 List Operations
626 | %%% ===========================================================================
627 |
628 | list_operations() ->
629 | A ++ A,
630 | A -- A.
631 |
632 | %%% ===========================================================================
633 | %%% 10.17 Map Expressions
634 | %%% ===========================================================================
635 |
636 | map_expressions() ->
637 | M0 = #{}, % empty map
638 | M1 = #{ a => <<"hello">> }, % single association with literals
639 | M2 = #{ 1 => 2, b => b }, % multiple associations with literals
640 | M3 = #{ A => B }, % single association with variables
641 | M4 = #{ {A, B} => f() }, % compound key associated to an evaluated expression
642 |
643 | M0 = #{},
644 | M1 = M0#{ a => 0 },
645 | M2 = M1#{ a => 1, b => 2 },
646 | M3 = M2#{ "function" => fun() -> f() end },
647 | M4 = M3#{ a := 2, b := 3 }, % 'a' and 'b' was added in `M1` and `M2`.
648 |
649 | M1 = #{ a => 1, c => 3 },
650 | 3 = M1#{ c },
651 |
652 | M = #{ a => {1,2}},
653 | #{ a := {1,B}} = M,
654 |
655 | M1 = #{ E0 => E1 || K := V <- M0 }. % map comprehension
656 |
657 | %%% ===========================================================================
658 | %%% 10.18 Bit Syntax Expressions
659 | %%% ===========================================================================
660 |
661 | bit_syntax() ->
662 | <<>>,
663 |
664 | <<
665 | >>,
666 |
667 | <>,
668 | <>,
669 | << A : 1 >>,
670 |
671 | <>,
672 | <>,
673 | << A : 1 / bits >>,
674 |
675 | <>,
676 | <>,
677 | << A : 1 / integer >>.
678 |
679 | bit_syntax_types() ->
680 | <>,
689 |
690 | <>,
691 | <>,
692 | <>,
693 | <>,
694 |
695 | <>,
696 | <>,
697 | <>,
698 |
699 | <>,
700 |
701 | <>,
713 |
714 | <>, % comment
726 |
727 | <<$a/utf8,$b/utf8,$c/utf8>>.
728 |
729 | just_atoms() ->
730 | integer,
731 | float,
732 | binary,
733 | bytes,
734 | bitstring,
735 | bits,
736 | utf8,
737 | utf16,
738 | utf32,
739 | signed,
740 | unsigned,
741 | big,
742 | little,
743 | native,
744 | unit,
745 | utf8.
746 |
747 | %%% ===========================================================================
748 | %%% 10.19 Fun Expressions
749 | %%% ===========================================================================
750 |
751 | f() ->
752 | fun func/0,
753 | fun mod:func/0,
754 | fun (A) when A > 0 -> A;
755 | (B) when B > 0 -> B
756 | end.
757 |
758 | f() ->
759 | fun Func/0,
760 | fun mod:Func/0,
761 | fun (A) when A > 0 -> A;
762 | (B) when B > 0 -> B
763 | end.
764 |
765 | %%% ===========================================================================
766 | %%% 10.20 Catch and Throw
767 | %%% ===========================================================================
768 |
769 | catch_example() ->
770 | catch 1 + 2.
771 |
772 | throw_example() ->
773 | throw(hello).
774 |
775 | %%% ===========================================================================
776 | %%% 10.21 Try
777 | %%% ===========================================================================
778 |
779 | try_example() ->
780 | try
781 | f()
782 | of
783 | A -> B
784 | catch
785 | throw:E -> E;
786 | exit:E -> E;
787 | error:E -> E
788 | after
789 | Timeout -> timeout
790 | end.
791 |
792 | try_example() ->
793 | try
794 | f()
795 | catch
796 | _:_ -> error;
797 | end.
798 |
799 | %%% ===========================================================================
800 | %%% 10.26 Guard Sequences
801 | %%% ===========================================================================
802 |
803 | test_type_bifs() when is_atom(A);
804 | is_binary(A);
805 | is_bitstring(A);
806 | is_boolean(A);
807 | is_float(A);
808 | is_function(A);
809 | is_function(A, B);
810 | is_integer(A);
811 | is_list(A);
812 | is_number(A);
813 | is_pid(A);
814 | is_port(A);
815 | is_record(A, B);
816 | is_record(A, B, C);
817 | is_reference(A);
818 | is_tuple(A) ->
819 | ok.
820 |
821 | old_guards() when abs(Number);
822 | bit_size(Bitstring);
823 | byte_size(Bitstring);
824 | element(N, Tuple);
825 | float(Term);
826 | hd(List);
827 | length(List);
828 | node();
829 | node(Pid|Ref|Port);
830 | round(Number);
831 | self();
832 | size(Tuple|Bitstring);
833 | tl(List);
834 | trunc(Number);
835 | tuple_size(Tuple) ->
836 | ok.
837 |
838 | not_guards() when myfunc(Number) ->
839 | ok.
840 |
841 | %%% ===========================================================================
842 | %%% 11 The Preprocessor
843 | %%% ===========================================================================
844 |
845 | %%% ===========================================================================
846 | %%% 11.1 File Inclusion
847 | %%% ===========================================================================
848 |
849 | -include("my_records.hrl").
850 | -include("incdir/my_records.hrl").
851 | -include("/home/user/proj/my_records.hrl").
852 | -include("$PROJ_ROOT/my_records.hrl").
853 | -include_lib("kernel/include/file.hrl").
854 |
855 | %%% ===========================================================================
856 | %%% 11.2 Defining and Using Macros
857 | %%% ===========================================================================
858 |
859 | -define(TIMEOUT, 200).
860 | -define(timeout, 200).
861 | -define(_Timeout, 200).
862 | -define(_timeout, 200).
863 | -define(_, 200).
864 |
865 | call(Request) ->
866 | server:call(refserver, Request, ?TIMEOUT).
867 |
868 | -define(MACRO1(X, Y), {a, X, b, Y}).
869 |
870 | bar(X) ->
871 | ?MACRO1,
872 | ??MACRO1,
873 | ?MACRO1(a, b),
874 | ?MACRO1(X, 123).
875 | ??MACRO1(X, 123).
876 |
877 | %%% ===========================================================================
878 | %%% 11.3 Predefined Macros
879 | %%% ===========================================================================
880 |
881 | predefined_macros() ->
882 | ?MODULE,
883 | ?MODULE_STRING,
884 | ?FILE,
885 | ?LINE,
886 | ?MACHINE.
887 |
888 | %%% ===========================================================================
889 | %%% 11.5 Flow Control in Macros
890 | %%% ===========================================================================
891 |
892 | -undef(Macro).
893 | -ifdef(Macro).
894 | -ifndef(Macro).
895 | -else.
896 | -endif.
897 |
898 | %%% ===========================================================================
899 | %%% 11.7 Stringifying Macro Arguments
900 | %%% ===========================================================================
901 |
902 | -define(TESTCALL(Call), io:format("Call ~s: ~w~n", [??Call, Call])).
903 |
904 | f() ->
905 | ?TESTCALL(myfunction(1,2)),
906 | ?TESTCALL(you:function(2,1)).
907 |
908 | %%% ===========================================================================
909 | %%% 12 Records
910 | %%% ===========================================================================
911 |
912 | %%% ===========================================================================
913 | %%% 12.1 Defining Records
914 | %%% ===========================================================================
915 |
916 | -record(person, {name,
917 | phone=0,
918 | address}).
919 |
920 | access_fields(Name, Tab) ->
921 | ets:match_object(Tab, #person{name=Name, _='_'}),
922 | lists:keysearch(Name, #person.name, List).
923 |
924 | update_fields() ->
925 | Person#person{field1=Expr1, field1=ExprK}.
926 |
927 | %%% ===========================================================================
928 | %%% 15 Processes
929 | %%% ===========================================================================
930 |
931 | bifs() ->
932 | register(Name, Pid),
933 | registered(),
934 | whereis(Name),
935 | spawn(),
936 | spawn_link(),
937 | spawn_opt(),
938 | link(),
939 | unlink(),
940 | process_flag(trap_exit, true),
941 | {'DOWN', Ref, process, Pid2, Reason}.
942 |
943 | process_dictionary_bifs() ->
944 | put(Key, Value),
945 | get(Key),
946 | get(),
947 | get_keys(Value),
948 | erase(Key),
949 | erase().
950 |
951 | %%% ===========================================================================
952 | %%% 16 Distributed Erlang
953 | %%% ===========================================================================
954 |
955 | %%% ===========================================================================
956 | %%% 16.9 Distribution BIFs
957 | %%% ===========================================================================
958 |
959 | distribution_bifs() ->
960 | disconnect_node(Node),
961 | erlang:get_cookie(),
962 | erlang:set_cookie(Node, Cookie),
963 | is_alive(),
964 | monitor_node(Node, true),
965 | node(),
966 | node(Arg),
967 | nodes(),
968 | nodes(Arg).
969 |
970 | just_atoms() ->
971 | disconnect_node,
972 | erlang:get_cookie,
973 | erlang:set_cookie,
974 | is_alive,
975 | monitor_node,
976 | node,
977 | nodes.
978 |
979 | %%% ===========================================================================
980 | %%% Escript
981 | %%% ===========================================================================
982 |
983 | #!/usr/bin/env escript
984 | #!/usr/bin/escript
985 |
986 | %%% ===========================================================================
987 | %%% io:format
988 | %%% ===========================================================================
989 |
990 | %%% http://erlang.org/doc/man/io.html#format-1
991 | io_format_control_sequences() ->
992 | '~a', % no highlight
993 | "~cxx~10.5cxx~-10.5cxx~.5cxx~10.cxx~tcxx~1tc", % highlight
994 | "~-.5cxx~t1c", % no highlight
995 | "~fxx~22fxx~-22.11fxx~.11fxx~.*fxx~.*.1f", % highlight
996 | "~2n ~1~ ~1i", % no highlight
997 | "|~10s|~n",
998 |
999 | io:fwrite("|~10.5c|~-10.5c|~5c|~n", [$a, $b, $c]),
1000 | io:fwrite("~tc~n",[1024]),
1001 | io:fwrite("~c~n",[1024]),
1002 | io:fwrite("|~10w|~n", [{hey, hey, hey}]),
1003 | io:fwrite("|~10s|~n", [io_lib:write({hey, hey, hey})]),
1004 | io:fwrite("|~-10.8s|~n", [io_lib:write({hey, hey, hey})]),
1005 | io:fwrite("~ts~n",[[1024]]),
1006 | io:fwrite("~s~n",[[1024]]),
1007 | io:fwrite("~w~n", [T]),
1008 | io:fwrite("~62p~n", [T]),
1009 | io:fwrite("Here T = ~62p~n", [T]),
1010 | io:fwrite("~15p~n", [S]),
1011 | io:fwrite("~15lp~n", [S],
1012 | io:fwrite("~p~n",[[1024]]),
1013 | io:fwrite("~tp~n",[[1024]]),
1014 | io:fwrite("~tp~n", [<<128,128>>]),
1015 | io:fwrite("~tp~n", [<<208,128>>]),
1016 | io:fwrite("~W~n", [T,9]),
1017 | io:fwrite("~62P~n", [T,9]),
1018 | io:fwrite("~.16B~n", [31]),
1019 | io:fwrite("~.2B~n", [-19]),
1020 | io:fwrite("~.36B~n", [5*36+35],
1021 | io:fwrite("~X~n", [31,"10#"]),
1022 | io:fwrite("~.16X~n", [-31,"0x"]),
1023 | io:fwrite("~.10#~n", [31]),
1024 | io:fwrite("~.16#~n", [-31]).
1025 |
--------------------------------------------------------------------------------