├── 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 | --------------------------------------------------------------------------------