├── .gitignore ├── LICENSE ├── README.md ├── autoload ├── erlang_complete.erl └── erlang_complete.vim ├── doc └── vim-erlang-omnicomplete.txt ├── ftplugin └── erlang.vim └── plugin └── erlang_omnicomplete.vim /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/tags 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | vim-erlang-compiler is released under Vim license (see :help license). 2 | This is the list of people who have contributed to this plugin: 3 | 4 | Copyright (C) 2007 Oscar Hellström 5 | Copyright (C) 2010 Pawel 'kTT' Salata (http://github.com/kTT) 6 | Copyright (C) 2010-2012 Ricardo Catalinas Jiménez 7 | Copyright (C) 2011 Eduardo Lopez (http://github.com/tapichu) 8 | Copyright (C) 2012 Ignas Vyšniauskas (https://github.com/yfyf) 9 | Copyright (C) 2014 Adam Rutkowski 10 | Copyright (C) 2014 Csaba Hoch 11 | 12 | vim-erlang-compiler's original source code comes from vimerl 13 | (https://github.com/jimenezrick/vimerl). 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-erlang-omnicomplete 2 | 3 | `vim-erlang-omnicomplete` is an Erlang **autocompletion plugin** for Vim. 4 | 5 | ## Table of Contents 6 | 7 | * [Installation](#installation) 8 | * [Quick start](#quick-start) 9 | * [Documentation](#documentation) 10 | * [Development](#development) 11 | * [File layout](#file-layout) 12 | * [Contributing](#contributing) 13 | 14 | ## Installation 15 | 16 |
17 | Vim's built-in package manager 18 | 19 | This is the recommended installation method if you use at least Vim 8 and you 20 | don't use another package manager. 21 | 22 | Information about Vim's built-in package manager: [`:help packages`]. 23 | 24 | Installation steps: 25 | 26 | 1. Clone this repository (you can replace `foo` with the directory name of your 27 | choice): 28 | 29 | ```sh 30 | $ git clone https://github.com/vim-erlang/vim-erlang-omnicomplete.git \ 31 | ~/.vim/pack/foo/start/vim-erlang-omnicomplete 32 | ``` 33 | 34 | 2. Restart Vim. 35 | 36 | 3. Generate help page (replace `foo` with the same directory name as above): 37 | 38 | ``` 39 | :helptags ~/.vim/pack/foo/start/vim-erlang-omnicomplete/doc 40 | ``` 41 | 42 |
43 | 44 |
45 | Pathogen 46 | 47 | Information about Pathogen: [Pathogen repository]. 48 | 49 | Installation steps: 50 | 51 | 1. Clone this repository: 52 | 53 | ``` 54 | $ git clone https://github.com/vim-erlang/vim-erlang-omnicomplete.git \ 55 | ~/.vim/bundle/vim-erlang-omnicomplete 56 | ``` 57 | 58 | 2. Restart Vim. 59 | 60 | 3. Generate help page: 61 | 62 | ``` 63 | :Helptags 64 | ``` 65 |
66 | 67 |
68 | Vundle 69 | 70 | Information about Vundle: [Vundle repository]. 71 | 72 | Installation steps: 73 | 74 | 1. Add `vim-erlang-omnicomplete` to your plugin list in `.vimrc` by inserting 75 | the line that starts with `Plugin`: 76 | 77 | ``` 78 | call vundle#begin() 79 | [...] 80 | Plugin 'vim-erlang/vim-erlang-omnicomplete' 81 | [...] 82 | call vundle#end() 83 | ``` 84 | 85 | 2. Restart Vim. 86 | 87 | 3. Run `:PluginInstall`. 88 |
89 | 90 |
91 | Vim-Plug 92 | 93 | Information about Vim-Plug: [vim-plug repository]. 94 | 95 | Installation steps: 96 | 97 | 1. Add `vim-erlang-omnicomplete` to your plugin list in `.vimrc` by inserting the 98 | line that starts with `Plug`: 99 | 100 | ``` 101 | call plug#begin() 102 | [...] 103 | Plug 'vim-erlang/vim-erlang-omnicomplete' 104 | [...] 105 | call plug#end() 106 | ``` 107 | 108 | 2. Restart Vim. 109 | 110 | 3. Run `:PlugInstall`. 111 | 112 |
113 | 114 | ## Quick start 115 | 116 | 1. Open an Erlang source file. 117 | 118 | 2. Start typing something (e.g. `li` or `io:fo`). 119 | 120 | 3. Without leaving insert mode, hit CTRL-X and then CTRL-O. 121 | 122 | 4. After at most a few seconds, you should see a list of completions. 123 | 124 | ## Documentation 125 | 126 | * Vim's omni completion (i.e., autocomplete) functionality: 127 | [`:help compl-omni`]. 128 | 129 | * Vim's `completeopt` option to configure omni completion 130 | [`:help completeopt`]. 131 | 132 | * `vim-erlang-omnicomplete` plugin: [`:help vim-erlang-omnicomplete`]. 133 | 134 | ## Development 135 | 136 | ### File layout 137 | 138 | This repository contains the following files and directories: 139 | 140 | 141 | 142 | * [`autoload/erlang_complete.erl`]: Erlang script which can analyse the code 143 | base and calculate the list of modules and the list of functions in 144 | a module. 145 | 146 | * [`autoload/erlang_complete.vim`]: This script contains most of 147 | `vim-erlang-omnicomplete`'s functionality on the Vim side. 148 | 149 | This functionality is here (instead of [`plugin/erlang_omnicomplete.vim`]) 150 | so that Vim's autoload functionality ([`:help autoload`]) can make sure that 151 | this script is executed only when needed the first time. 152 | 153 | * [`doc/vim-erlang-omnicomplete.txt`]: This file contains the user 154 | documentation of `vim-erlang-omnicomplete`. 155 | 156 | * [`ftplugin/erlang.vim`]: This script sets up `vim-erlang-omnicomplete` when 157 | an Erlang source file is opened. 158 | 159 | * [`plugin/erlang_omnicomplete.vim`]: This script sets up 160 | `vim-erlang-omnicomplete` when Vim is started. It is kept small so that the 161 | effect on Vim's startup time is minimal. 162 | 163 | ## Contributing 164 | 165 | * Please read the [Contributing][vim-erlang-contributing] section of the 166 | [`vim-erlang`] README. 167 | 168 | * If you modify [`autoload/erlang_complete.erl`], please: 169 | 170 | - update the tests in the [`vim-erlang`] repository 171 | 172 | - also modify `vim-erlang-compiler` (if you modify the 173 | ["load build information" code block][common-code-block]) 174 | 175 | 176 | 177 | [`:help autoload`]: https://vimhelp.org/eval.txt.html#autoload 178 | [`:help compl-omni`]: https://vimhelp.org/insert.txt.html#compl-omni 179 | [`:help completeopt`]: https://vimhelp.org/options.txt.html#%27completeopt%27 180 | [`:help packages`]: https://vimhelp.org/repeat.txt.html#packages 181 | [`:help vim-erlang-omnicomplete`]: doc/vim-erlang-omnicomplete.txt 182 | [`autoload/erlang_complete.erl`]: autoload/erlang_complete.erl 183 | [`autoload/erlang_complete.vim`]: autoload/erlang_complete.vim 184 | [`doc/vim-erlang-omnicomplete.txt`]: doc/vim-erlang-omnicomplete.txt 185 | [`ftplugin/erlang.vim`]: ftplugin/erlang.vim 186 | [`plugin/erlang_omnicomplete.vim`]: plugin/erlang_omnicomplete.vim 187 | [`vim-erlang`]: https://github.com/vim-erlang/vim-erlang 188 | [common-code-block]: https://github.com/vim-erlang/vim-erlang-omnicomplete/blob/448a9feae5a284bf36748e611111d183cfa52ab5/autoload/erlang_complete.erl#L160-L700 189 | [Pathogen repository]: https://github.com/tpope/vim-pathogen 190 | [vim-erlang-contributing]: https://github.com/vim-erlang/vim-erlang#contributing 191 | [vim-plug repository]: https://github.com/junegunn/vim-plug 192 | [Vundle repository]: https://github.com/VundleVim/Vundle.vim 193 | -------------------------------------------------------------------------------- /autoload/erlang_complete.erl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | 3 | %%% This script prints all modules or prints all functions of a module. 4 | %%% 5 | %%% See more information in the {@link print_help/0} function. 6 | 7 | % 'compile' mode gives better error messages if the script throws an error. 8 | -mode(compile). 9 | 10 | -ifdef(OTP_RELEASE). 11 | -if(?OTP_RELEASE >= 24). 12 | -define(USE_FIND_SOURCE, 1). 13 | -endif. % -if 14 | -endif. % -ifdef 15 | 16 | -ifndef(USE_FIND_SOURCE). 17 | % The filename:find_src/1 function is deprecated. Its replacement function is 18 | % filelib:find_source, which has a different interface and was introduced only 19 | % in Erlang 20. As long as filename:find_src/1 is present in the newest Erlang 20 | % version, we can keep using filename:find_src/1. 21 | -compile({nowarn_deprecated_function, [{filename, find_src, 1}]}). 22 | -endif. 23 | 24 | -include_lib("xmerl/include/xmerl.hrl"). 25 | 26 | %%%============================================================================= 27 | %%% Types 28 | %%%============================================================================= 29 | 30 | -type build_system() :: rebar | rebar3 | makefile. 31 | 32 | -type function_spec() :: 33 | {FunName :: atom(), 34 | ArgNames :: [string()], 35 | ReturnType :: string()} | 36 | {FunName :: atom(), 37 | Arity :: non_neg_integer()}. 38 | %% The specification of a function. 39 | %% 40 | %% If we are lucky, we know the names of arguments and the return type. If we 41 | %% are less lucky, then we know only the arity. 42 | 43 | -type simplified_xml_element() :: 44 | {XmlTag :: atom(), 45 | Attributes :: [{Name :: atom(), Value :: string()}], 46 | Content :: [(ChildXmlElement :: simplified_xml_element()) | 47 | (Text :: string())]}. 48 | %% A `simplified_xml_element()' term represents an XML element. 49 | %% 50 | %% `xmerl_lib:simplify_element/1' can be used to convert an #xmlElement{} record 51 | %% into a `simplified_xml_element()' term. 52 | %% 53 | %% An example `simplified_xml_element()' term: 54 | %% 55 | %% ``` 56 | %% [{function, 57 | %% [{name,"my_fun"},{arity,"1"},{exported,"yes"},{label,"my_fun-1"}], 58 | %% [{args,[], 59 | %% [{arg,[] 60 | %% [{argName,[], 61 | %% ["X1"]}]}]}, 62 | %% {typespec,[], 63 | %% [{erlangName,[{name,"my_fun"}],[]}, 64 | %% {type,[], 65 | %% [{'fun',[], 66 | %% [{argtypes,[], 67 | %% [{type, [{name,"X1"}], 68 | %% [{abstype,[], 69 | %% [{erlangName,[{name,"integer"}],[]}]}]}]}, 70 | %% {type,[], 71 | %% [{abstype,[], 72 | %% [{erlangName,[{name,"float"}],[]}]}]}]}]}]}, 73 | %% ''' 74 | %% 75 | %% This term represents the following XML structure (with pseudo-XML notation): 76 | %% 77 | %% ``` 78 | %% 79 | %% 80 | %% 81 | %% 82 | %% X1 83 | %% 84 | %% 85 | %% 86 | %% 87 | %% 88 | %% 89 | %% 90 | %% 91 | %% 92 | %% 93 | %% 94 | %% ''' 95 | 96 | %%%============================================================================= 97 | %%% Main function 98 | %%%============================================================================= 99 | 100 | %%------------------------------------------------------------------------------ 101 | %% @doc This function is the entry point of the script. 102 | %% 103 | %% Print all modules or print all functions of a module. 104 | %% @end 105 | %%------------------------------------------------------------------------------ 106 | -spec main(Args) -> no_return() when 107 | Args :: [string()]. 108 | main([]) -> 109 | io:format("Usage: see --help.~n"), 110 | halt(2); 111 | main(Args) -> 112 | PositionalParams = parse_args(Args), 113 | case PositionalParams of 114 | ["list-modules"] -> 115 | run(list_modules); 116 | ["list-functions", ModuleString] -> 117 | run({list_functions, list_to_atom(ModuleString)}); 118 | _ -> 119 | log_error("Erroneous parameters: ~p~n", [PositionalParams]), 120 | halt(2) 121 | end. 122 | 123 | %%%============================================================================= 124 | %%% Parse command line arguments 125 | %%%============================================================================= 126 | 127 | %%------------------------------------------------------------------------------ 128 | %% @doc Parse the argument list. 129 | %% 130 | %% Put the options into the process dictionary and return the positional 131 | %% parameters. 132 | %% @end 133 | %%------------------------------------------------------------------------------ 134 | -spec parse_args(Args) -> PositionalParams when 135 | Args :: [string()], 136 | PositionalParams :: [string()]. 137 | parse_args(Args) -> 138 | lists:reverse(parse_args(Args, [])). 139 | 140 | %%------------------------------------------------------------------------------ 141 | %% @doc Parse the argument list. 142 | %% 143 | %% Put the options into the process dictionary and return the positional 144 | %% parameters in reverse order. 145 | %% @end 146 | %%------------------------------------------------------------------------------ 147 | -spec parse_args(Args, Acc) -> PositionalParams when 148 | Args :: [string()], 149 | Acc :: [PositionalParam :: string()], 150 | PositionalParams :: [string()]. 151 | parse_args([], Acc) -> 152 | Acc; 153 | parse_args([Help|_], _Acc) when Help == "-h"; 154 | Help == "--help" -> 155 | print_help(), 156 | halt(0); 157 | parse_args([Verbose|OtherArgs], Acc) when Verbose == "-v"; 158 | Verbose == "--verbose" -> 159 | put(verbose, true), 160 | log("Verbose mode on.~n"), 161 | parse_args(OtherArgs, Acc); 162 | parse_args(["--basedir", BaseDir|OtherArgs], Acc) -> 163 | put(basedir, BaseDir), 164 | parse_args(OtherArgs, Acc); 165 | parse_args(["--basedir"], _Acc) -> 166 | log_error("Argument needed after '--basedir'.~n", []), 167 | halt(2); 168 | parse_args(["--"|PosPars], Acc) -> 169 | PosPars ++ Acc; 170 | parse_args([[$-|_] = Arg|_], _Acc) -> 171 | log_error("Unknown option: ~s~n", [Arg]), 172 | halt(2); 173 | parse_args([PosPar|OtherArgs], Acc) -> 174 | parse_args(OtherArgs, [PosPar|Acc]). 175 | 176 | %%------------------------------------------------------------------------------ 177 | %% @doc Print the script's help text. 178 | %% @end 179 | %%------------------------------------------------------------------------------ 180 | -spec print_help() -> ok. 181 | print_help() -> 182 | Text = 183 | "Usage: erlang_complete.erl [options] [--] list-modules 184 | erlang_complete.erl [options] [--] list-functions MODULE 185 | 186 | Description: 187 | erlang_complete lists all modules or all functions of a module. 188 | 189 | Options: 190 | -- Process all remaining parameters as filenames. 191 | -h, --help Print help. 192 | -v, --verbose Verbose output. 193 | --basedir DIR 194 | When searching for rebar.config files, the search will start 195 | from this directory. 196 | 197 | Examples: 198 | 199 | $ erlang_complete.erl list-modules | head -n 5 200 | ELDAPv3 201 | OTP-PUB-KEY 202 | PKCS-FRAME 203 | alarm_handler 204 | application 205 | 206 | $ erlang_complete.erl list-functions my_module 207 | module_info/0 208 | module_info/1 209 | my_fun_1(Float) -> integer() 210 | my_fun_2(Integer) -> float() 211 | ", 212 | io:format(Text). 213 | 214 | %%%============================================================================= 215 | %%% Preparation 216 | %%%============================================================================= 217 | 218 | %%------------------------------------------------------------------------------ 219 | %% @doc Complete the given query. 220 | %% 221 | %% This function sets the code paths too. 222 | %% @end 223 | %%------------------------------------------------------------------------------ 224 | -spec run(Query) -> ok when 225 | Query :: list_modules | 226 | {list_functions, module()}. 227 | run(Target) -> 228 | 229 | AbsDir = 230 | case get(basedir) of 231 | undefined -> 232 | {ok, Cwd} = file:get_cwd(), 233 | Cwd; 234 | BaseDir -> 235 | filename:absname(BaseDir) 236 | end, 237 | put(compiled_file_path, AbsDir), 238 | 239 | {_AppRoot, _ProjectRoot, BuildSystemOpts} = load_build_info(AbsDir), 240 | 241 | case BuildSystemOpts of 242 | {opts, _Opts} -> 243 | try 244 | run2(Target) 245 | catch 246 | throw:error -> 247 | % The error messages were already printed. 248 | ok 249 | end; 250 | error -> 251 | error 252 | end. 253 | 254 | %%%============================================================================= 255 | %%% Load build information. 256 | %%% 257 | %%% This code block is also present in erlang_check.erl in the 258 | %%% vim-erlang-compiler project. If you modify this code block, please also 259 | %%% modify erlang_check.erl. 260 | %%%============================================================================= 261 | 262 | %%------------------------------------------------------------------------------ 263 | %% @doc Load information about the build system. 264 | %% 265 | %% The `Path' parameter is the absolute path to the parent directory of the 266 | %% relevant Erlang source file. 267 | %% 268 | %% The code paths are set by the function. The include paths are returned in 269 | %% `BuildSystemOpts'. 270 | %% @end 271 | %%------------------------------------------------------------------------------ 272 | -spec load_build_info(Path) -> Result when 273 | Path :: string(), 274 | Result :: {AppRoot, ProjectRoot, BuildSystemOpts}, 275 | AppRoot :: string(), 276 | ProjectRoot :: string(), 277 | BuildSystemOpts :: {opts, [{atom(), term()}]} | 278 | error. 279 | load_build_info(Path) -> 280 | 281 | % AppRoot: the directory of the Erlang app. 282 | AppRoot = 283 | case find_app_root(Path) of 284 | no_root -> 285 | log("Could not find project root.~n"), 286 | Path; 287 | Root -> 288 | log("Found project root: ~p~n", [Root]), 289 | Root 290 | end, 291 | 292 | {BuildSystem, BuildFiles} = guess_build_system(AppRoot), 293 | 294 | % ProjectRoot: the directory of the Erlang release (if it exists; otherwise 295 | % same as AppRoot). 296 | ProjectRoot = get_project_root(BuildSystem, BuildFiles, AppRoot), 297 | BuildSystemOpts = load_build_files(BuildSystem, ProjectRoot, BuildFiles), 298 | 299 | {AppRoot, ProjectRoot, BuildSystemOpts}. 300 | 301 | %%------------------------------------------------------------------------------ 302 | %% @doc Traverse the directory structure upwards until is_app_root matches. 303 | %% @end 304 | %%------------------------------------------------------------------------------ 305 | -spec find_app_root(Path) -> Root when 306 | Path :: string(), 307 | Root :: string() | 'no_root'. 308 | find_app_root("/") -> 309 | case is_app_root("/") of 310 | true -> "/"; 311 | false -> no_root 312 | end; 313 | find_app_root(Path) -> 314 | case is_app_root(Path) of 315 | true -> Path; 316 | false -> find_app_root(filename:dirname(Path)) 317 | end. 318 | 319 | %%------------------------------------------------------------------------------ 320 | %% @doc Check directory if it is the root of an OTP application. 321 | %% @end 322 | %%------------------------------------------------------------------------------ 323 | -spec is_app_root(Path) -> boolean() when 324 | Path :: string(). 325 | is_app_root(Path) -> 326 | filelib:wildcard("ebin/*.app", Path) /= [] orelse 327 | filelib:wildcard("src/*.app.src", Path) /= []. 328 | 329 | %%------------------------------------------------------------------------------ 330 | %% @doc Check for some known files and try to guess what build system is being 331 | %% used. 332 | %% @end 333 | %%------------------------------------------------------------------------------ 334 | -spec guess_build_system(Path) -> Result when 335 | Path :: string(), 336 | Result :: {build_system(), 337 | BuildFiles :: [string()]}. 338 | guess_build_system(Path) -> 339 | % The order is important, at least Makefile needs to come last since a lot 340 | % of projects include a Makefile along any other build system. 341 | BuildSystems = [ 342 | {rebar3, [ 343 | "rebar.lock" 344 | ] 345 | }, 346 | {rebar, [ 347 | "rebar.config", 348 | "rebar.config.script" 349 | ] 350 | }, 351 | {makefile, [ 352 | "Makefile" 353 | ] 354 | } 355 | ], 356 | guess_build_system(Path, BuildSystems). 357 | 358 | %%------------------------------------------------------------------------------ 359 | %% @doc Check which build system's files are contained by the project. 360 | %% @end 361 | %%------------------------------------------------------------------------------ 362 | -spec guess_build_system(Path, BuildSystems) -> Result when 363 | BuildSystems :: [{build_system(), 364 | BaseNames :: [string()]}], 365 | Path :: string(), 366 | Result :: {build_system(), 367 | BuildFiles :: [string()]}. 368 | guess_build_system(_Path, []) -> 369 | log("Unknown build system.~n"), 370 | {unknown_build_system, []}; 371 | guess_build_system(Path, [{BuildSystem, BaseNames}|Rest]) -> 372 | log("Try build system: ~p~n", [BuildSystem]), 373 | case find_files(Path, BaseNames) of 374 | [] -> 375 | guess_build_system(Path, Rest); 376 | BuildFiles -> 377 | {BuildSystem, BuildFiles} 378 | end. 379 | 380 | %%------------------------------------------------------------------------------ 381 | %% @doc Get the root directory of the project. 382 | %% @end 383 | %%------------------------------------------------------------------------------ 384 | -spec get_project_root(BuildSystem, BuildFiles, AppRoot) -> ProjectRoot when 385 | BuildSystem :: build_system(), 386 | BuildFiles :: [string()], 387 | AppRoot :: string(), 388 | ProjectRoot :: string(). 389 | get_project_root(rebar3, BuildFiles, _) -> 390 | RebarLocks = [F || F <- BuildFiles, 391 | filename:basename(F) == "rebar.lock"], 392 | RebarLocksWithPriority = [{F, rebar3_lock_priority(F)} || F <- RebarLocks], 393 | {RebarLock, _Priority} = hd(lists:keysort(2, RebarLocksWithPriority)), 394 | filename:dirname(RebarLock); 395 | get_project_root(_BuildSystem, _Files, AppRoot) -> 396 | AppRoot. 397 | 398 | %%------------------------------------------------------------------------------ 399 | %% @doc Get the priority of a rebar3 lock file. 400 | %% 401 | %% Standalone rebar3 lock files found along the parent paths could end up making 402 | %% their directories be prioritised in our attempt to search for the true root 403 | %% of the project. 404 | %% 405 | %% This will in turn result in 'rebar.config not found in [...]' kind of errors 406 | %% being printed out when checking for syntax errors. 407 | %% 408 | %% This function attempts to minimise the risk of that happening by prioritising 409 | %% the found locks according to simple heuristics for how likely are those lock 410 | %% files to be the genuine article. 411 | %% @end 412 | %%------------------------------------------------------------------------------ 413 | -spec rebar3_lock_priority(Filename) -> Result when 414 | Filename :: string(), 415 | Result :: [non_neg_integer()]. 416 | rebar3_lock_priority(Filename) -> 417 | Dir = filename:dirname(Filename), 418 | AbsDir = filename:absname(Dir), 419 | {ok, Siblings} = file:list_dir(AbsDir), 420 | {SiblingDirs, SiblingFiles} = 421 | lists:partition(fun filelib:is_dir/1, Siblings), 422 | AbsDirComponents = filename:split(AbsDir), 423 | 424 | MightBeRebarProject = lists:member("rebar.config", SiblingFiles), 425 | MightBeSingleApp = lists:member("src", SiblingDirs), 426 | MightBeUmbrellaApp = lists:member("apps", SiblingDirs), 427 | Depth = length(AbsDirComponents), 428 | 429 | if MightBeRebarProject -> 430 | % Lock files standing beside a rebar.config file 431 | % get a higher priority than to those that don't. 432 | % Between them, those higher in file system hierarchy will 433 | % themselves get prioritised. 434 | [1, Depth]; 435 | MightBeSingleApp xor MightBeUmbrellaApp -> 436 | % Lock files standing beside either a src or apps directory 437 | % get a higher priority than those that don't. 438 | % Between them, those higher in file system hierarchy will 439 | % themselves get prioritised. 440 | [2, Depth]; 441 | true -> 442 | % No good criteria remain. Prioritise by placement in 443 | % file system hierarchy. 444 | [3, Depth] 445 | end. 446 | 447 | %%------------------------------------------------------------------------------ 448 | %% @doc Load the settings from a given set of build system files. 449 | %% @end 450 | %%------------------------------------------------------------------------------ 451 | -spec load_build_files(BuildSystem, ProjectRoot, ConfigFiles) -> Result when 452 | BuildSystem :: build_system(), 453 | ProjectRoot :: string(), 454 | ConfigFiles :: [string()], 455 | Result :: {opts, [{atom(), term()}]} | 456 | error. 457 | load_build_files(rebar, _ProjectRoot, ConfigFiles) -> 458 | load_rebar_files(ConfigFiles, no_config); 459 | load_build_files(rebar3, ProjectRoot, _ConfigFiles) -> 460 | % _ConfigFiles is a list containing only rebar.lock. 461 | ConfigNames = ["rebar.config", "rebar.config.script"], 462 | case find_files(ProjectRoot, ConfigNames) of 463 | [] -> 464 | log_error("rebar.config not found in ~p~n", [ProjectRoot]), 465 | error; 466 | [RebarConfigFile|_] -> 467 | load_rebar3_files(RebarConfigFile) 468 | end; 469 | load_build_files(makefile, _ProjectRoot, ConfigFiles) -> 470 | load_makefiles(ConfigFiles); 471 | load_build_files(unknown_build_system, ProjectRoot, _) -> 472 | {opts, [ 473 | {i, absname(ProjectRoot, "include")}, 474 | {i, absname(ProjectRoot, "../include")}, 475 | {i, ProjectRoot} 476 | ]}. 477 | 478 | %%------------------------------------------------------------------------------ 479 | %% @doc Load the content of each rebar file. 480 | %% 481 | %% Note worthy: The config returned by this function only represents the first 482 | %% rebar file (the one closest to the file to compile). The subsequent rebar 483 | %% files will be processed for code path only. 484 | %% @end 485 | %%------------------------------------------------------------------------------ 486 | -spec load_rebar_files(ConfigFiles, Config) -> Result when 487 | ConfigFiles :: [string()], 488 | Config :: no_config | [{atom(), term()}], 489 | Result :: {opts, [{atom(), term()}]} | 490 | error. 491 | load_rebar_files([], no_config) -> 492 | error; 493 | load_rebar_files([], Config) -> 494 | {opts, Config}; 495 | load_rebar_files([ConfigFile|Rest], Config) -> 496 | ConfigPath = filename:dirname(ConfigFile), 497 | ConfigResult = case filename:extension(ConfigFile) of 498 | ".script" -> file:script(ConfigFile); 499 | ".config" -> file:consult(ConfigFile) 500 | end, 501 | case ConfigResult of 502 | {ok, ConfigTerms} -> 503 | log("rebar.config read: ~s~n", [ConfigFile]), 504 | NewConfig = process_rebar_config(ConfigPath, ConfigTerms, Config), 505 | case load_rebar_files(Rest, NewConfig) of 506 | {opts, SubConfig} -> {opts, SubConfig}; 507 | error -> {opts, NewConfig} 508 | end; 509 | {error, Reason} -> 510 | log_error("rebar.config consult failed:~n"), 511 | file_error(ConfigFile, {file_error, Reason}), 512 | error 513 | end. 514 | 515 | %%------------------------------------------------------------------------------ 516 | %% @doc Apply a rebar.config file. 517 | %% 518 | %% This function adds the directories in the rebar.config file to the code path 519 | %% and returns and compilation options to be used when compiling the file. 520 | %% @end 521 | %%------------------------------------------------------------------------------ 522 | -spec process_rebar_config(Path, Terms, Config) -> Result when 523 | Path :: string(), 524 | Terms :: [{atom(), term()}], 525 | Config :: no_config | [{atom(), term()}], 526 | Result :: [{atom(), term()}]. 527 | process_rebar_config(Path, Terms, Config) -> 528 | 529 | % App layout: 530 | % 531 | % * rebar.config 532 | % * src/ 533 | % * ebin/ => ebin -> code_path 534 | % * include/ => ".." -> include. This is needed because files in src may 535 | % use `-include_lib("appname/include/f.hrl")` 536 | 537 | % Project layout: 538 | % 539 | % * rebar.config 540 | % * src/ 541 | % * $(deps_dir)/ 542 | % * $(app_name)/ 543 | % * ebin/ => deps -> code_path 544 | % * apps/ 545 | % * $(sub_dir)/ 546 | % * ebin/ => sub_dirs -> code_path 547 | % * include/ => apps -> include 548 | 549 | DepsDir = proplists:get_value(deps_dir, Terms, "deps"), 550 | LibDirs = proplists:get_value(lib_dirs, Terms, []), 551 | SubDirs = proplists:get_value(sub_dirs, Terms, []), 552 | ErlOpts = proplists:get_value(erl_opts, Terms, []), 553 | 554 | % ebin -> code_path (when the rebar.config file is in the app directory 555 | code:add_pathsa([absname(Path, "ebin")]), 556 | 557 | % deps -> code_path 558 | code:add_pathsa(filelib:wildcard(absname(Path, DepsDir) ++ "/*/ebin")), 559 | 560 | % libs -> code_path 561 | code:add_pathsa(filelib:wildcard(absname(Path, LibDirs) ++ "/*/ebin")), 562 | 563 | % sub_dirs -> code_path 564 | [ code:add_pathsa(filelib:wildcard(absname(Path, SubDir) ++ "/ebin")) 565 | || SubDir <- SubDirs ], 566 | 567 | case Config of 568 | no_config -> 569 | Includes = 570 | [ {i, absname(Path, Dir)} 571 | || Dir <- ["apps", "include"] ] ++ 572 | [ {i, absname(Path, filename:append(SubDir, "include"))} 573 | || SubDir <- SubDirs ], 574 | 575 | Opts = ErlOpts ++ Includes, 576 | remove_warnings_as_errors(Opts); 577 | _ -> 578 | Config 579 | end. 580 | 581 | %%------------------------------------------------------------------------------ 582 | %% @doc Load the content of each rebar3 file. 583 | %% 584 | %% Note worthy: The config returned by this function only represent the first 585 | %% rebar file (the one closest to the file to compile). 586 | %% @end 587 | %%------------------------------------------------------------------------------ 588 | -spec load_rebar3_files(ConfigFile) -> Result when 589 | ConfigFile :: string(), 590 | Result :: {opts, [{atom(), term()}]} | 591 | error. 592 | load_rebar3_files(ConfigFile) -> 593 | ConfigPath = filename:dirname(ConfigFile), 594 | ConfigResult = case filename:extension(ConfigFile) of 595 | ".script" -> file:script(ConfigFile); 596 | ".config" -> file:consult(ConfigFile) 597 | end, 598 | case ConfigResult of 599 | {ok, ConfigTerms} -> 600 | log("rebar.config read: ~s~n", [ConfigFile]), 601 | try process_rebar3_config(ConfigPath, ConfigTerms) of 602 | error -> 603 | error; 604 | Config -> 605 | {opts, Config} 606 | catch 607 | throw:error -> 608 | error 609 | end; 610 | {error, Reason} -> 611 | log_error("rebar.config consult failed:~n"), 612 | file_error(ConfigFile, {file_error, Reason}), 613 | error 614 | end. 615 | 616 | %%------------------------------------------------------------------------------ 617 | %% @doc Apply a rebar.config file. 618 | %% 619 | %% This function adds the directories returned by rebar3 to the code path and 620 | %% returns and compilation options to be used when compiling the file. 621 | %% @end 622 | %%------------------------------------------------------------------------------ 623 | -spec process_rebar3_config(ConfigPath, Terms) -> Result when 624 | ConfigPath :: string(), 625 | Terms :: [{atom(), term()}], 626 | Result :: [{atom(), term()}] | error. 627 | process_rebar3_config(ConfigPath, Terms) -> 628 | case find_rebar3(ConfigPath) of 629 | not_found -> 630 | % Compilation would likely fail without settings the paths, so let's 631 | % give an explicit error instead of proceeding anyway. 632 | log_error("rebar3 executable not found.~n"), 633 | error; 634 | {ok, Rebar3Rel} -> 635 | log("rebar3 executable found: ~s~n", [Rebar3Rel]), 636 | Rebar3 = filename:absname(Rebar3Rel), 637 | log("Absolute path to rebar3 executable: ~s~n", [Rebar3]), 638 | % load the profile used by rebar3 to print the dependency path list 639 | Profile = rebar3_get_profile(Terms), 640 | % "rebar3 path" prints all paths that belong to the project; we add 641 | % these to the Erlang paths. 642 | % 643 | % QUIET=1 ensures that it won't print other messages, see 644 | % https://github.com/erlang/rebar3/issues/1143. 645 | {ok, Cwd} = file:get_cwd(), 646 | file:set_cwd(ConfigPath), 647 | os:putenv("QUIET", "1"), 648 | MainCmd = io_lib:format("~p as ~p path", [Rebar3, Profile]), 649 | log("Call: ~s~n", [MainCmd]), 650 | {ExitCode, Output} = command(MainCmd, []), 651 | log("Result: ~p~n", [{ExitCode, Output}]), 652 | file:set_cwd(Cwd), 653 | 654 | Paths = 655 | case ExitCode of 656 | 0 -> 657 | Output; 658 | _ -> 659 | file_error( 660 | get(compiled_file_path), 661 | {format, 662 | "'~s' failed with exit code ~p: ~s~n", 663 | [MainCmd, ExitCode, Output]}), 664 | throw(error) 665 | end, 666 | 667 | CleanedPaths = [absname(ConfigPath, SubDir) 668 | || SubDir <- string:tokens(Paths, " ")], 669 | code:add_pathsa(CleanedPaths), 670 | 671 | % Add _checkouts dependencies to code_path. 672 | % 673 | % These dependencies are compiled into the following directories: 674 | % 675 | % - `_checkouts//ebin' until rebar 3.13 676 | % - `_build//checkouts//ebin/' from rebar 3.14 677 | % 678 | % Documentation for _checkouts dependencies: 679 | % https://www.rebar3.org/docs/dependencies#section-checkout-dependencies 680 | code:add_pathsa( 681 | filelib:wildcard(absname(ConfigPath, "_checkouts") ++ 682 | "/*/ebin")), 683 | code:add_pathsa( 684 | filelib:wildcard(absname(ConfigPath, "_build") ++ 685 | "/default/checkouts/*/ebin")), 686 | 687 | lists:foreach( 688 | fun({ProfileName, Deps}) -> 689 | Apps = string:join([atom_to_list(D) || D <- Deps], ","), 690 | file:set_cwd(ConfigPath), 691 | Cmd2 = io_lib:format("QUIET=1 ~p as ~p path --app=~s", 692 | [Rebar3, ProfileName, Apps]), 693 | log("Call: ~s~n", [Cmd2]), 694 | ProfilePaths = os:cmd(Cmd2), 695 | log("Result: ~s~n", [Paths]), 696 | file:set_cwd(Cwd), 697 | Cleaned = [absname(ConfigPath, SubDir) 698 | || SubDir <- string:tokens(ProfilePaths, " ")], 699 | code:add_pathsa(Cleaned); 700 | (_) -> ok 701 | end, rebar3_get_extra_profiles(Terms)), 702 | 703 | ErlOpts = proplists:get_value(erl_opts, Terms, []), 704 | remove_warnings_as_errors(ErlOpts) 705 | end. 706 | 707 | %%------------------------------------------------------------------------------ 708 | %% @doc Find the rebar3 executable. 709 | %% 710 | %% First we try to find rebar3 in the project directory. Second we try to find 711 | %% it in the PATH. 712 | %% @end 713 | %%------------------------------------------------------------------------------ 714 | -spec find_rebar3(ConfigPath) -> Result when 715 | ConfigPath :: [string()], 716 | Result :: {ok, string()} | 717 | not_found. 718 | find_rebar3(ConfigPath) -> 719 | case find_files(ConfigPath, ["rebar3"]) of 720 | [Rebar3|_] -> 721 | {ok, Rebar3}; 722 | [] -> 723 | case os:find_executable("rebar3") of 724 | false -> 725 | not_found; 726 | Rebar3 -> 727 | {ok, Rebar3} 728 | end 729 | end. 730 | 731 | %%------------------------------------------------------------------------------ 732 | %% @doc Read the profile name defined in rebar.config for Rebar3 733 | %% 734 | %% Look inside rebar.config to find a special configuration called 735 | %% `vim_erlang_compiler`. 736 | %% 737 | %% E.g. to use the "test" profile: 738 | %% 739 | %% ``` 740 | %% {vim_erlang_compiler, [ 741 | %% {profile, "test"} 742 | %% ]}. 743 | %% ''' 744 | %%------------------------------------------------------------------------------ 745 | -spec rebar3_get_profile(Terms) -> Result when 746 | Terms :: [{atom(), term()}], 747 | Result :: string(). 748 | rebar3_get_profile(Terms) -> 749 | case proplists:get_value(vim_erlang_compiler, Terms) of 750 | undefined -> "default"; 751 | Options -> proplists:get_value(profile, Options, "default") 752 | end. 753 | 754 | %%------------------------------------------------------------------------------ 755 | %% @doc Read all extra profile names declared within the rebar.config 756 | %% 757 | %%------------------------------------------------------------------------------ 758 | -spec rebar3_get_extra_profiles(Terms) -> Result when 759 | Terms :: [{atom(), term()}], 760 | Result :: [{ProfileName :: string(), 761 | [Dependency :: term()]}]. 762 | rebar3_get_extra_profiles(Terms) -> 763 | case proplists:get_value(profiles, Terms, []) of 764 | [] -> 765 | []; 766 | Profiles -> 767 | lists:flatmap( 768 | fun({ProfileName, Profile}) -> 769 | case proplists:get_value(deps, Profile, []) of 770 | [] -> 771 | []; 772 | Deps -> 773 | [{ProfileName, [Dep || {Dep, _} <- Deps]}] 774 | end; 775 | (_) -> 776 | [] 777 | end, Profiles) 778 | end. 779 | 780 | %%------------------------------------------------------------------------------ 781 | %% @doc Remove the "warnings_as_errors" option from the given Erlang options. 782 | %% 783 | %% If "warnings_as_errors" is left in, rebar sometimes prints the following 784 | %% line: 785 | %% 786 | %% compile: warnings being treated as errors 787 | %% 788 | %% The problem is that Vim interprets this as a line about an actual warning 789 | %% about a file called "compile", so it will jump to the "compile" file. 790 | %% 791 | %% And anyway, it is fine to show warnings as warnings not not errors: the 792 | %% developer knows whether their project handles warnings as errors and can 793 | %% interpret them accordingly. 794 | %% @end 795 | %%------------------------------------------------------------------------------ 796 | -spec remove_warnings_as_errors(ErlOpts) -> ErlOpts when 797 | ErlOpts :: [{atom(), string()}]. 798 | remove_warnings_as_errors(ErlOpts) -> 799 | proplists:delete(warnings_as_errors, ErlOpts). 800 | 801 | %%------------------------------------------------------------------------------ 802 | %% @doc Set code paths and options for a simple Makefile 803 | %% @end 804 | %%------------------------------------------------------------------------------ 805 | -spec load_makefiles(BuildFiles) -> Result when 806 | BuildFiles :: [string()], 807 | Result :: {opts, [{atom(), term()}]} | 808 | error. 809 | load_makefiles([Makefile|_Rest]) -> 810 | Path = filename:dirname(Makefile), 811 | code:add_pathsa([absname(Path, "ebin")]), 812 | code:add_pathsa(filelib:wildcard(absname(Path, "deps") ++ "/*/ebin")), 813 | code:add_pathsa(filelib:wildcard(absname(Path, "lib") ++ "/*/ebin")), 814 | {opts, [{i, absname(Path, "include")}, 815 | {i, absname(Path, "deps")}, 816 | {i, absname(Path, "lib")}]}. 817 | 818 | %%%============================================================================= 819 | %%% Execution 820 | %%%============================================================================= 821 | 822 | %%------------------------------------------------------------------------------ 823 | %% @doc Print the completions for a target. 824 | %% 825 | %% If there was no error, then the function first prints the line 826 | %% "execution_successful", and it prints the completion results afterwards. If 827 | %% there was an error, then the errors are printed. 828 | %% 829 | %% The reason for this design is that edoc:get_doc/1 prints its errors to the 830 | %% standard output before returning. This means that we have no easy way of 831 | %% printing anything before Edoc to indicate to the caller of 832 | %% erlang_complete.erl that an error message will follow. (This caller is 833 | %% usually erlang_complete.vim.) This design is also useful in case of other 834 | %% errors, e.g. if the erlang_complete.erl script itself cannot be executed, and 835 | %% the error is printed by the Erlang environment to the standard 836 | %% output or standard error. 837 | %% 838 | %% This function assumes that the code paths are all set. 839 | %% @end 840 | %%------------------------------------------------------------------------------ 841 | -spec run2(Target) -> ok when 842 | Target :: list_modules | 843 | {list_functions, module()}. 844 | 845 | run2(list_modules) -> 846 | Modules = [filename:basename(File, ".beam") 847 | || Dir <- code:get_path(), 848 | File <- filelib:wildcard(filename:join(Dir, "*.beam"))], 849 | [io:format("~s\n", [Mod]) || Mod <- lists:sort(Modules)], 850 | ok; 851 | 852 | run2({list_functions, Mod}) -> 853 | 854 | Edoc = 855 | try 856 | module_edoc(Mod) 857 | catch 858 | throw:not_found -> 859 | []; 860 | error:{badmatch, _} -> 861 | [] 862 | end, 863 | 864 | Info = 865 | try 866 | module_info2(Mod) 867 | catch 868 | error:undef -> 869 | [] 870 | end, 871 | 872 | case {Edoc, Info} of 873 | {[], []} -> 874 | io:format("Module not found.\n"); 875 | _ -> 876 | FunSpecs = merge_functions(Edoc, Info), 877 | io:format("execution_successful\n"), 878 | [print_function(Fun) || Fun <- FunSpecs ], 879 | ok 880 | end. 881 | 882 | %%------------------------------------------------------------------------------ 883 | %% @doc Return the specification of all exported functions. 884 | %% 885 | %% The returned list is sorted by function name. 886 | %% @end 887 | %%------------------------------------------------------------------------------ 888 | -spec module_edoc(Mod) -> Result when 889 | Mod :: module(), 890 | Result :: [function_spec()]. 891 | module_edoc(Mod) -> 892 | File = find_source(Mod), 893 | 894 | {_, Doc} = 895 | try 896 | % We ask Edoc to: 897 | % 898 | % 1. Parse the module's source code. 899 | % 2. Extract information about functions, types and docstrings. 900 | % 3. Put this information into an Erlang term that represents an XML 901 | % (and can be queried with functions in the xmerl application). 902 | FileRel = relatizive_path_maybe(File), 903 | edoc:get_doc(FileRel) 904 | catch 905 | _:Error -> 906 | % If edoc:get_doc/1 was unsuccessful, it already printed its 907 | % errors and throw an exception. Sometimes it might be still 908 | % useful to print the exception, so let's print it. 909 | io:format("Edoc error: ~p~n", [Error]), 910 | throw(error) 911 | end, 912 | 913 | Funs = xmerl_xpath:string("/module/functions/function", Doc), 914 | FunSpecs = lists:map(fun analyze_function/1, Funs), 915 | lists:keysort(1, FunSpecs). 916 | 917 | -ifdef(USE_FIND_SOURCE). 918 | 919 | find_source(Mod) -> 920 | BeamFile = atom_to_list(Mod) ++ ".beam", 921 | case code:where_is_file(BeamFile) of 922 | non_existing -> 923 | throw(not_found); 924 | BeamPath -> 925 | case filelib:find_source(BeamPath) of 926 | {ok, SrcPath} -> 927 | SrcPath; 928 | {error, not_found} -> 929 | throw(not_found) 930 | end 931 | end. 932 | 933 | -else. % ifndef(USE_FIND_SOURCE) 934 | 935 | find_source(Mod) -> 936 | case filename:find_src(Mod) of 937 | {error, _} -> 938 | BeamFile = atom_to_list(Mod) ++ ".beam", 939 | case code:where_is_file(BeamFile) of 940 | non_existing -> 941 | throw(not_found); 942 | BeamPath -> 943 | SrcPath = beam_to_src_path(BeamPath), 944 | case filelib:is_regular(SrcPath) of 945 | true -> 946 | SrcPath; 947 | false -> 948 | throw(not_found) 949 | end 950 | end; 951 | {File0, _} -> 952 | File0 ++ ".erl" 953 | end. 954 | 955 | %%------------------------------------------------------------------------------ 956 | %% @doc Convert the path of a BEAM file to the path of the corresponding Erlang 957 | %% source file. 958 | %% 959 | %% This is only a heuristic that we try if `filename:find_src/1' fails. 960 | %% @end 961 | %%------------------------------------------------------------------------------ 962 | -spec beam_to_src_path(BeamPath) -> Result when 963 | BeamPath :: file:filename(), 964 | Result :: file:filename_all(). 965 | beam_to_src_path(BeamPath) -> 966 | % Example: 967 | % - PathParts = ["myproject", "apps", "ebin", "mymodule.beam"] 968 | % - Dirs = ["myproject", "apps", "ebin"] 969 | % - BeamFile = "mymodule.beam" 970 | % - Dirs2 = ["myproject", "apps"] 971 | % - DirLast = "ebin" 972 | PathParts = filename:split(BeamPath), 973 | {Dirs, [BeamFile]} = lists:split(length(PathParts) - 1, PathParts), 974 | {Dirs2, [DirsLast]} = lists:split(length(Dirs) - 1, Dirs), 975 | Dirs3 = 976 | case filename:pathtype(BeamPath) of 977 | absolute -> 978 | case DirsLast of 979 | "ebin" -> 980 | Dirs2 ++ ["src"]; 981 | _ -> 982 | Dirs 983 | end; 984 | relative -> 985 | Dirs 986 | end, 987 | filename:join(Dirs3 ++ [beam_to_src_file(BeamFile)]). 988 | 989 | %%------------------------------------------------------------------------------ 990 | %% @doc Convert ".beam" into ".erl". 991 | %% @end 992 | %%------------------------------------------------------------------------------ 993 | -spec beam_to_src_file(BeamFile) -> Result when 994 | BeamFile :: file:filename(), 995 | Result :: file:filename(). 996 | beam_to_src_file(BeamFile) -> 997 | [ModName, "beam"] = string:tokens(BeamFile, "."), 998 | ModName ++ ".erl". 999 | 1000 | -endif. % USE_FIND_SOURCE 1001 | 1002 | %%------------------------------------------------------------------------------ 1003 | %% @doc Return the specification of a function. 1004 | %% 1005 | %% Example (with pseudo-XML notation): 1006 | %% 1007 | %% ``` 1008 | %% % Original Erlang function 1009 | %% -spec my_fun(integer()) -> float(); 1010 | %% (string()) -> binary(). 1011 | %% my_fun(_) -> 1012 | %% ok. 1013 | %% 1014 | %% FunXmlElement = 1015 | %% 1016 | %% 1017 | %% 1018 | %% 1019 | %% 1020 | %% 1021 | %% 1022 | %% X1 1023 | %% 1024 | %% 1025 | %% 1026 | %% 1027 | %% 1028 | %% 1029 | %% 1030 | %% 1031 | %% 1032 | %% 1033 | %% 1034 | %% 1035 | %% 1038 | %% 1039 | %% 1040 | %% 1041 | %% X1 1042 | %% 1043 | %% 1044 | %% 1045 | %% 1046 | %% 1047 | %% 1048 | %% 1049 | %% 1050 | %% 1051 | %% 1052 | %% 1053 | %% 1054 | %% Result = {my_fun, ["X1"], "float()"} 1055 | %% ''' 1056 | %% @end 1057 | %%------------------------------------------------------------------------------ 1058 | -spec analyze_function(FunXmlElement) -> Result when 1059 | FunXmlElement :: #xmlElement{}, 1060 | Result :: function_spec(). 1061 | analyze_function(Fun) -> 1062 | Name = list_to_atom(get_attribute(Fun, "name")), 1063 | TypeSpecClauses = xmerl_xpath:string("typespec", Fun), 1064 | case TypeSpecClauses of 1065 | [] -> 1066 | % This function has no type spec. 1067 | Arity = get_attribute(Fun, "arity"), 1068 | {Name, list_to_integer(Arity)}; 1069 | [TypeSpecClause|_OtherTypeSpecClauses] -> 1070 | % A function might have more than one type spec clauses (but only if 1071 | % Erlang OTP version is at least 23). We currently show only the 1072 | % first one and ignore the rest. 1073 | Args0 = xmerl_xpath:string("type/fun/argtypes/type", 1074 | TypeSpecClause), 1075 | ArgNames = lists:map(fun(Arg) -> get_attribute(Arg, "name") end, 1076 | Args0), 1077 | Return = analyze_return(TypeSpecClause), 1078 | {Name, ArgNames, Return} 1079 | end. 1080 | 1081 | %%------------------------------------------------------------------------------ 1082 | %% @doc Return the value of an attribute in an XML element. 1083 | %% 1084 | %% Example (with pseudo-XML notation): 1085 | %% 1086 | %% ``` 1087 | %% Elem = 1091 | %% [...] 1092 | %% AttrName = "arity" 1093 | %% Result = "0" 1094 | %% ``` 1095 | %% 1096 | %% @end 1097 | %%------------------------------------------------------------------------------ 1098 | -spec get_attribute(Elem, AttrName) -> Result when 1099 | Elem :: #xmlElement{}, 1100 | AttrName :: string(), 1101 | Result :: iolist() | atom() | integer(). 1102 | get_attribute(Elem, AttrName) -> 1103 | [Attr] = xmerl_xpath:string("@" ++ AttrName, Elem), 1104 | Attr#xmlAttribute.value. 1105 | 1106 | %%------------------------------------------------------------------------------ 1107 | %% @doc Return the return type of a function as a string. 1108 | %% 1109 | %% Example (in pseudo-XML notation): 1110 | %% 1111 | %% ``` 1112 | %% TypeSpecClause = 1113 | %% 1114 | %% 1115 | %% 1116 | %% 1117 | %% 1118 | %% 1119 | %% 1120 | %% 1121 | %% 1122 | %% 1123 | %% 1124 | %% Result = "float()" 1125 | %% 1126 | %% If the type spec clause does not contain the return value, "?" is returned. 1127 | %% ''' 1128 | %% @end 1129 | %%------------------------------------------------------------------------------ 1130 | -spec analyze_return(TypeSpecClause) -> Result when 1131 | TypeSpecClause :: #xmlElement{}, 1132 | Result :: string(). 1133 | analyze_return(TypeSpecClause) -> 1134 | case xmerl_xpath:string("type/fun/type/*", TypeSpecClause) of 1135 | [ReturnType] -> 1136 | simplify_return(xmerl_lib:simplify_element(ReturnType)); 1137 | _ -> 1138 | "?" 1139 | end. 1140 | 1141 | %%------------------------------------------------------------------------------ 1142 | %% @doc Return a function's return type as a string. 1143 | %% 1144 | %% Example: 1145 | %% 1146 | %% ``` 1147 | %% SimplifiedTypeSpecClause = {abstype,[],[{erlangName,[{name,"float"}],[]}]} 1148 | %% Result = "float()" 1149 | %% ''' 1150 | %% @end 1151 | %%------------------------------------------------------------------------------ 1152 | -spec simplify_return(SimplifiedTypeSpecClause) -> Result when 1153 | SimplifiedTypeSpecClause :: simplified_xml_element(), 1154 | Result :: string(). 1155 | simplify_return({typevar, [{name, Name}], _}) -> 1156 | Name; 1157 | simplify_return({type, _, [Type]}) -> 1158 | simplify_return(Type); 1159 | simplify_return({abstype, _, [Type|_]}) -> 1160 | {erlangName, Attrs, _} = Type, 1161 | Name = proplists:get_value(name, Attrs), 1162 | Name ++ "()"; 1163 | simplify_return({record, _, [Type|_]}) -> 1164 | simplify_return(Type) ++ "()"; 1165 | simplify_return({nonempty_list, _, [Type]}) -> 1166 | "[" ++ simplify_return(Type) ++ "]"; 1167 | simplify_return({tuple, _, Types}) -> 1168 | Elems = lists:map(fun(Type) -> simplify_return(Type) end, Types), 1169 | "{" ++ string:join(Elems, ", ") ++ "}"; 1170 | simplify_return({list, _, Types}) -> 1171 | Elems = lists:map(fun(Type) -> simplify_return(Type) end, Types), 1172 | "[" ++ string:join(Elems, ", ") ++ "]"; 1173 | simplify_return({paren, _, Types}) -> 1174 | Elems = lists:map(fun(Type) -> simplify_return(Type) end, Types), 1175 | "(" ++ string:join(Elems, ", ") ++ ")"; 1176 | simplify_return({union, _, Types}) -> 1177 | Elems = lists:map(fun(Type) -> simplify_return(Type) end, Types), 1178 | string:join(Elems, " | "); 1179 | simplify_return({integer, [{value, Val}], _}) -> 1180 | Val; 1181 | simplify_return({atom, [{value, Val}], _}) -> 1182 | Val; 1183 | simplify_return({nil, _, _}) -> 1184 | "[]"; 1185 | simplify_return({map_field, _, [Key, Value]}) -> 1186 | simplify_return(Key) ++ " => " ++ simplify_return(Value); 1187 | simplify_return({map, _, PairList}) -> 1188 | Pairs = string:join([ simplify_return(Pair) || Pair <- PairList ], ", "), 1189 | "#{" ++ Pairs ++ "}". 1190 | 1191 | %%------------------------------------------------------------------------------ 1192 | %% @doc Get the specifications of all exported functions. 1193 | %% 1194 | %% The result will contain only arities. For example: 1195 | %% 1196 | %% ``` 1197 | %% > lists:keysort(1, lists:module_info(exports)). 1198 | %% [{all,2}, 1199 | %% {any,2}, 1200 | %% {append,2}, 1201 | %% {append,1}, 1202 | %% {concat,1}, 1203 | %% ''' 1204 | %% 1205 | %% The returned list is sorted by function name. 1206 | %% @end 1207 | %%------------------------------------------------------------------------------ 1208 | -spec module_info2(Mod) -> Result when 1209 | Mod :: module(), 1210 | Result :: [function_spec()]. 1211 | module_info2(Mod) -> 1212 | lists:keysort(1, Mod:module_info(exports)). 1213 | 1214 | %%------------------------------------------------------------------------------ 1215 | %% @doc Merge functions specifications read with edoc and with `module_info'. 1216 | %% 1217 | %% Notes: 1218 | %% 1219 | %% 1. This function assumes that the input lists are sorted. 1220 | %% 1221 | %% 2. If a function is present in both lists, edoc wins. This is because the 1222 | %% `module_info' function specification contains only the arity, so the edoc 1223 | %% function specification is either the same or better (if it contains the 1224 | %% argument list and return type). 1225 | %% @end 1226 | %%------------------------------------------------------------------------------ 1227 | -spec merge_functions(Edoc, Info) -> Result when 1228 | Edoc :: [function_spec()], 1229 | Info :: [function_spec()], 1230 | Result :: [function_spec()]. 1231 | merge_functions(Edoc, Info) -> 1232 | merge_functions(Edoc, Info, []). 1233 | 1234 | merge_functions([], [], Funs) -> 1235 | lists:reverse(Funs); 1236 | merge_functions([], Info, Funs) -> 1237 | lists:reverse(Funs, Info); 1238 | merge_functions(Edoc, [], Funs) -> 1239 | lists:reverse(Funs, Edoc); 1240 | merge_functions(Edoc, Info, Funs) -> 1241 | [H1 | T1] = Edoc, 1242 | [H2 = {K2, _} | T2] = Info, 1243 | K1 = 1244 | case H1 of 1245 | {Name, _, _} -> Name; 1246 | {Name, _} -> Name 1247 | end, 1248 | if 1249 | K1 == K2 -> 1250 | merge_functions(T1, T2, [H1 | Funs]); 1251 | K1 < K2 -> 1252 | merge_functions(T1, Info, [H1 | Funs]); 1253 | K1 > K2 -> 1254 | merge_functions(Edoc, T2, [H2 | Funs]) 1255 | end. 1256 | 1257 | %%------------------------------------------------------------------------------ 1258 | %% @doc Print a function speficiation in a human-friendly way. 1259 | %% 1260 | %% When this script is called by the plugin, the printed lines are sent back to 1261 | %% Vim, who shows them to the user in the completion menu. 1262 | %% @end 1263 | %%------------------------------------------------------------------------------ 1264 | -spec print_function(Fun) -> ok when 1265 | Fun :: function_spec(). 1266 | print_function({FunName, Arity}) -> 1267 | io:format("~s/~B~n", [FunName, Arity]); 1268 | print_function({FunName, Args, ReturnType}) -> 1269 | io:format("~s(~s) -> ~s~n", [FunName, string:join(Args, ", "), ReturnType]). 1270 | 1271 | %%%============================================================================= 1272 | %%% Utility functions (in alphabetical order) 1273 | %%%============================================================================= 1274 | 1275 | %%------------------------------------------------------------------------------ 1276 | %% @doc Return the absolute name of the file which is in the given directory. 1277 | %% 1278 | %% Example: 1279 | %% 1280 | %% - cwd = "/home/my" 1281 | %% - Dir = "projects/erlang" 1282 | %% - Filename = "rebar.config" 1283 | %% - Result: "/home/my/projects/erlang/rebar.config" 1284 | %% @end 1285 | %%------------------------------------------------------------------------------ 1286 | -spec absname(Dir, Filename) -> Result when 1287 | Dir :: string(), 1288 | Filename :: string(), 1289 | Result :: string(). 1290 | absname(Dir, Filename) -> 1291 | filename:absname(filename:join(Dir, Filename)). 1292 | 1293 | %%------------------------------------------------------------------------------ 1294 | %% @doc Execute the given OS command. 1295 | %% 1296 | %% The command's output is printed, and its exit code is returned. 1297 | %% 1298 | %% Original code from 1299 | %% http://erlang.org/pipermail/erlang-questions/2007-February/025210.html 1300 | %% @end 1301 | %%------------------------------------------------------------------------------ 1302 | -spec command(Cmd, Options) -> Result when 1303 | Cmd :: string(), 1304 | Options :: [{print_output, boolean()}], 1305 | ExitCode :: integer(), 1306 | Output :: string(), 1307 | Result :: {ExitCode, Output}. 1308 | command(Cmd, Options) -> 1309 | PortOptions = [stream, exit_status, use_stdio, stderr_to_stdout, in, eof], 1310 | Port = open_port({spawn, Cmd}, PortOptions), 1311 | command_loop(Port, Options, []). 1312 | 1313 | %%------------------------------------------------------------------------------ 1314 | %% @doc Read the output of an OS command started via a port. 1315 | %% @end 1316 | %%------------------------------------------------------------------------------ 1317 | -spec command_loop(Port, Options, OutputAcc) -> Result when 1318 | Port :: port(), 1319 | Options :: [{print_output, boolean()}], 1320 | OutputAcc :: [string()], 1321 | ExitCode :: integer(), 1322 | Output :: string(), 1323 | Result :: {ExitCode, Output}. 1324 | command_loop(Port, Options, OutputAcc) -> 1325 | receive 1326 | {Port, {data, Data}} -> 1327 | case proplists:get_value(print_output, Options, false) of 1328 | true -> 1329 | io:format(user, "~s", [Data]); 1330 | false -> 1331 | ok 1332 | end, 1333 | command_loop(Port, Options, [Data|OutputAcc]); 1334 | {Port, eof} -> 1335 | port_close(Port), 1336 | receive 1337 | {Port, {exit_status, ExitCode}} -> 1338 | Output = lists:append(lists:reverse(OutputAcc)), 1339 | {ExitCode, Output} 1340 | end 1341 | end. 1342 | 1343 | %%------------------------------------------------------------------------------ 1344 | %% @doc Print the given error reason in a Vim-friendly and human-friendly way. 1345 | %% @end 1346 | %%------------------------------------------------------------------------------ 1347 | -spec file_error(File, Error) -> ok when 1348 | File :: string(), 1349 | Error :: {format, Format, Data} | 1350 | {file_error, FileError}, 1351 | Format :: io:format(), 1352 | Data :: [term()], 1353 | FileError :: file:posix() | badarg | terminated | system_limit | 1354 | {Line :: integer(), Mod :: module(), Term :: term()}. 1355 | file_error(File, Error) -> 1356 | 1357 | LineNumber = 1358 | case Error of 1359 | {file_error, {LineNumber0, _, _}} -> 1360 | LineNumber0; 1361 | _ -> 1362 | % The error doesn't belong to a specific line, but Vim shows it 1363 | % only if it has a line number, so let's assign line 1 to it. 1364 | 1 1365 | end, 1366 | 1367 | FileRel = relatizive_path_maybe(File), 1368 | io:format(standard_io, "~s:~p: ", [FileRel, LineNumber]), 1369 | 1370 | case Error of 1371 | {file_error, FileError} -> 1372 | io:format(standard_io, "~s~n", [file:format_error(FileError)]); 1373 | {format, Format, Data} -> 1374 | io:format(standard_io, Format, Data) 1375 | end. 1376 | 1377 | 1378 | %%------------------------------------------------------------------------------ 1379 | %% @doc Find the first file matching one of the filenames in the given path. 1380 | %% @end 1381 | %%------------------------------------------------------------------------------ 1382 | -spec find_file(Path, Files) -> Result when 1383 | Path :: string(), 1384 | Files :: [string()], 1385 | Result :: [string()]. 1386 | find_file(_Path, []) -> 1387 | []; 1388 | find_file(Path, [File|Rest]) -> 1389 | AbsFile = absname(Path, File), 1390 | case filelib:is_regular(AbsFile) of 1391 | true -> 1392 | log("Found build file: [~p] ~p~n", [Path, AbsFile]), 1393 | % Return file and continue searching in parent directory. 1394 | [AbsFile]; 1395 | false -> 1396 | find_file(Path, Rest) 1397 | end. 1398 | 1399 | %%------------------------------------------------------------------------------ 1400 | %% @doc Recursively search upward through the path tree and returns the absolute 1401 | %% path to all files matching the given filenames. 1402 | %% @end 1403 | %%------------------------------------------------------------------------------ 1404 | -spec find_files(Path, Files) -> Result when 1405 | Path :: string(), 1406 | Files :: [string()], 1407 | Result :: [string()]. 1408 | find_files("/", Files) -> 1409 | find_file("/", Files); 1410 | find_files([_|":/"] = Path, Files) -> 1411 | % E.g. "C:/". This happens on Windows. 1412 | find_file(Path, Files); 1413 | find_files(Path, Files) -> 1414 | ParentPath = filename:dirname(Path), 1415 | find_file(Path, Files) ++ 1416 | find_files(ParentPath, Files). 1417 | 1418 | %%------------------------------------------------------------------------------ 1419 | %% @doc Log the given entry if we are in verbose mode. 1420 | %% @end 1421 | %%------------------------------------------------------------------------------ 1422 | -spec log(Format) -> ok when 1423 | Format :: io:format(). 1424 | log(Format) -> 1425 | log(Format, []). 1426 | 1427 | %%------------------------------------------------------------------------------ 1428 | %% @doc Log the given entry if we are in verbose mode. 1429 | %% @end 1430 | %%------------------------------------------------------------------------------ 1431 | -spec log(Format, Data) -> ok when 1432 | Format :: io:format(), 1433 | Data :: [term()]. 1434 | log(Format, Data) -> 1435 | case get(verbose) of 1436 | true -> 1437 | io:format(Format, Data); 1438 | _ -> 1439 | ok 1440 | end. 1441 | 1442 | %%------------------------------------------------------------------------------ 1443 | %% @doc Log the given error. 1444 | %% @end 1445 | %%------------------------------------------------------------------------------ 1446 | -spec log_error(Format) -> ok when 1447 | Format :: io:format(). 1448 | log_error(Format) -> 1449 | io:format(standard_error, Format, []). 1450 | 1451 | %%------------------------------------------------------------------------------ 1452 | %% @doc Log the given error. 1453 | %% @end 1454 | %%------------------------------------------------------------------------------ 1455 | -spec log_error(Format, Data) -> ok when 1456 | Format :: io:format(), 1457 | Data :: [term()]. 1458 | log_error(Format, Data) -> 1459 | io:format(standard_error, Format, Data). 1460 | 1461 | %%------------------------------------------------------------------------------ 1462 | %% @doc Try to convert a path to a relative path. 1463 | %% @end 1464 | %%------------------------------------------------------------------------------ 1465 | -spec relatizive_path_maybe(Path) -> Path when 1466 | Path :: file:filename(). 1467 | relatizive_path_maybe(Path) -> 1468 | {ok, Cwd} = file:get_cwd(), 1469 | 1470 | case lists:prefix(Cwd, Path) of 1471 | true -> 1472 | % Example: 1473 | % Cwd = "/home/my" 1474 | % Path = "/home/my/dir/my_file.erl" 1475 | % ^ length(Cwd) 1476 | % ^ Start 1477 | % <-------------> Len 1478 | % FileRel = "dir/my_file.erl" 1479 | Start = length(Cwd) + 2, 1480 | Len = length(Path) - length(Cwd) - 1, 1481 | lists:sublist(Path, Start, Len); 1482 | false -> 1483 | % The path is not below the current directory, so let's keep it 1484 | % absolute. 1485 | Path 1486 | end. 1487 | -------------------------------------------------------------------------------- /autoload/erlang_complete.vim: -------------------------------------------------------------------------------- 1 | " Vim omni completion file 2 | " Language: Erlang 3 | " Author: Oscar Hellström 4 | " Contributors: kTT (http://github.com/kTT) 5 | " Ricardo Catalinas Jiménez 6 | " Eduardo Lopez (http://github.com/tapichu) 7 | " License: Vim license 8 | 9 | " Completion program path 10 | let s:erlang_complete_file = expand(':p:h') . '/erlang_complete.erl' 11 | 12 | " Returns whether "substring" is a prefix of "string". 13 | function s:StartsWith(string, substring) 14 | let string_start = strpart(a:string, 0, len(a:substring)) 15 | return string_start ==# a:substring 16 | endfunction 17 | 18 | " If we are running in Cygwin, the path needs to be converted. 19 | " See: https://github.com/vim-erlang/vim-erlang-omnicomplete/issues/21 20 | if has('win32') == 0 && s:StartsWith(system('uname'), 'CYGWIN') 21 | " Cygwin system. Now check if erlang is Windows or cygwin (currently only 22 | " Windows is possible) 23 | let cygwin_base_path = system('cygpath -w /') 24 | if !s:StartsWith(s:erlang_complete_file, cygwin_base_path) 25 | " Windows, as expected 26 | let s:erlang_complete_file = system('cygpath -w ' . s:erlang_complete_file) 27 | endif 28 | endif 29 | 30 | if !exists('g:erlang_completion_cache') 31 | let g:erlang_completion_cache = 1 32 | endif 33 | 34 | if !exists('g:erlang_completion_preview_help') 35 | let g:erlang_completion_preview_help = 1 36 | end 37 | 38 | if !exists('g:erlang_completion_zero_arity_paren') 39 | let g:erlang_completion_zero_arity_paren = '()' 40 | end 41 | 42 | if !exists('g:erlang_completion_nonzero_arity_paren') 43 | let g:erlang_completion_nonzero_arity_paren = '(' 44 | end 45 | 46 | if !exists('g:erlang_completion_extend_arity') 47 | let g:erlang_completion_extend_arity = 1 48 | end 49 | 50 | " Modules cache used to speed up the completion. 51 | " 52 | " This dictionary contains completion items that represent functions exported 53 | " from this module. Each value in this dictionary is a list of completion 54 | " items. A completion item is itself a dictionary (see ":help complete-items" 55 | " for the format of the completion item dictionaries). 56 | " 57 | " Type: module -> [complete_item] 58 | " 59 | " Example: {'mymod': [{'word': 'myfun(', # text inserted during the completion 60 | " 'abbr': 'myfun/1', # text displayed in the popup menu 61 | " 'kind': 'f', # this is a function 62 | " 'dup': 1}, # 'word' values might be duplicates 63 | " {'word': 'myfun(', 64 | " 'abbr': 'myfun/2', 65 | " 'kind': 'f', 66 | " 'dup': 1} , 67 | " {'word': 'myfun_with_type_spec(', 68 | " 'abbr': 'myfun_with_type_spec(A, B)', 69 | " 'kind': 'f', 70 | " 'dup': 1}], 71 | " 'myothermod': [{'word': 'myotherfun(', 72 | " 'abbr': 'myotherfun/2', 73 | " 'kind': 'f', 74 | " 'dup': 1}]} 75 | let s:modules_cache = {} 76 | 77 | " Patterns for completions 78 | let s:erlang_local_func_beg = '\(\<[0-9A-Za-z_-]*\|\s*\)$' 79 | let s:erlang_external_func_beg = '\<[0-9A-Za-z_-]\+:[0-9A-Za-z_-]*$' 80 | let s:erlang_blank_line = '^\s*\(%.*\)\?$' 81 | 82 | " This list comes from http://www.erlang.org/doc/man/erlang.html (minor 83 | " modifications have been performed). 84 | let s:auto_imported_bifs = [ 85 | \ 'abs(Number) -> number()', 86 | \ 'apply(Fun, Args) -> term()', 87 | \ 'apply(Module, Function, Args) -> term()', 88 | \ 'atom_to_binary(Atom, Encoding) -> binary()', 89 | \ 'atom_to_list(Atom) -> string()', 90 | \ 'binary_part(Subject, PosLen) -> binary()', 91 | \ 'binary_part(Subject, Start, Length) -> binary()', 92 | \ 'binary_to_atom(Binary, Encoding) -> atom()', 93 | \ 'binary_to_existing_atom(Binary, Encoding) -> atom()', 94 | \ 'binary_to_float(Binary) -> float()', 95 | \ 'binary_to_integer(Binary) -> integer()', 96 | \ 'binary_to_integer(Binary, Base) -> integer()', 97 | \ 'binary_to_list(Binary) -> [byte()]', 98 | \ 'binary_to_list(Binary, Start, Stop) -> [byte()]', 99 | \ 'bitstring_to_list(Bitstring) -> [byte() | bitstring()]', 100 | \ 'binary_to_term(Binary) -> term()', 101 | \ 'binary_to_term(Binary, Opts) -> term()', 102 | \ 'bit_size(Bitstring) -> integer() >= 0', 103 | \ 'byte_size(Bitstring) -> integer() >= 0', 104 | \ 'check_old_code(Module) -> boolean()', 105 | \ 'check_process_code(Pid, Module) -> boolean()', 106 | \ 'date() -> Date', 107 | \ 'delete_module(Module) -> true | undefined', 108 | \ 'demonitor(MonitorRef) -> true', 109 | \ 'demonitor(MonitorRef, OptionList) -> boolean()', 110 | \ 'disconnect_node(Node) -> boolean() | ignored', 111 | \ 'element(N, Tuple) -> term()', 112 | \ 'erase() -> [{Key, Val}]', 113 | \ 'erase(Key) -> Val | undefined', 114 | \ 'error(Reason) -> no_return()', 115 | \ 'error(Reason, Args) -> no_return()', 116 | \ 'exit(Reason) -> no_return()', 117 | \ 'exit(Pid, Reason) -> true', 118 | \ 'float(Number) -> float()', 119 | \ 'float_to_binary(Float) -> binary()', 120 | \ 'float_to_binary(Float, Options) -> binary()', 121 | \ 'float_to_list(Float) -> string()', 122 | \ 'float_to_list(Float, Options) -> string()', 123 | \ 'garbage_collect() -> true', 124 | \ 'garbage_collect(Pid) -> boolean()', 125 | \ 'get() -> [{Key, Val}]', 126 | \ 'get(Key) -> Val | undefined', 127 | \ 'get_keys(Val) -> [Key]', 128 | \ 'group_leader() -> pid()', 129 | \ 'group_leader(GroupLeader, Pid) -> true', 130 | \ 'halt() -> no_return()', 131 | \ 'halt(Status) -> no_return()', 132 | \ 'halt(Status, Options) -> no_return()', 133 | \ 'hd(List) -> term()', 134 | \ 'integer_to_binary(Integer) -> binary()', 135 | \ 'integer_to_binary(Integer, Base) -> binary()', 136 | \ 'integer_to_list(Integer) -> string()', 137 | \ 'integer_to_list(Integer, Base) -> string()', 138 | \ 'iolist_to_binary(IoListOrBinary) -> binary()', 139 | \ 'iolist_size(Item) -> integer() >= 0', 140 | \ 'is_alive() -> boolean()', 141 | \ 'is_atom(Term) -> boolean()', 142 | \ 'is_binary(Term) -> boolean()', 143 | \ 'is_bitstring(Term) -> boolean()', 144 | \ 'is_boolean(Term) -> boolean()', 145 | \ 'is_float(Term) -> boolean()', 146 | \ 'is_function(Term) -> boolean()', 147 | \ 'is_function(Term, Arity) -> boolean()', 148 | \ 'is_integer(Term) -> boolean()', 149 | \ 'is_list(Term) -> boolean()', 150 | \ 'is_number(Term) -> boolean()', 151 | \ 'is_pid(Term) -> boolean()', 152 | \ 'is_port(Term) -> boolean()', 153 | \ 'is_process_alive(Pid) -> boolean()', 154 | \ 'is_record(Term, RecordTag) -> boolean()', 155 | \ 'is_record(Term, RecordTag, Size) -> boolean()', 156 | \ 'is_reference(Term) -> boolean()', 157 | \ 'is_tuple(Term) -> boolean()', 158 | \ 'length(List) -> integer() >= 0', 159 | \ 'link(PidOrPort) -> true', 160 | \ 'list_to_atom(String) -> atom()', 161 | \ 'list_to_binary(IoList) -> binary()', 162 | \ 'list_to_bitstring(BitstringList) -> bitstring()', 163 | \ 'list_to_existing_atom(String) -> atom()', 164 | \ 'list_to_float(String) -> float()', 165 | \ 'list_to_integer(String) -> integer()', 166 | \ 'list_to_integer(String, Base) -> integer()', 167 | \ 'list_to_pid(String) -> pid()', 168 | \ 'list_to_tuple(List) -> tuple()', 169 | \ 'load_module(Module, Binary) -> {module, Module} | {error, Reason}', 170 | \ 'make_ref() -> reference()', 171 | \ 'max(Term1, Term2) -> Maximum', 172 | \ 'min(Term1, Term2) -> Minimum', 173 | \ 'module_loaded(Module) -> boolean()', 174 | \ 'monitor(Type, Item) -> MonitorRef', 175 | \ 'monitor_node(Node, Flag) -> true', 176 | \ 'node() -> Node', 177 | \ 'node(Arg) -> Node', 178 | \ 'nodes() -> Nodes', 179 | \ 'nodes(Arg) -> Nodes', 180 | \ 'now() -> Timestamp', 181 | \ 'open_port(PortName, PortSettings) -> port()', 182 | \ 'pid_to_list(Pid) -> string()', 183 | \ 'port_close(Port) -> true', 184 | \ 'port_command(Port, Data) -> true', 185 | \ 'port_command(Port, Data, OptionList) -> boolean()', 186 | \ 'port_connect(Port, Pid) -> true', 187 | \ 'port_control(Port, Operation, Data) -> iodata() | binary()', 188 | \ 'pre_loaded() -> [module()]', 189 | \ 'process_flag(Flag :: trap_exit, Boolean) -> OldBoolean', 190 | \ 'process_flag(Pid, Flag, Value) -> OldValue', 191 | \ 'process_info(Pid) -> Info', 192 | \ 'process_info(Pid, Item) -> InfoTuple | [] | undefined', 193 | \ 'process_info(Pid, ItemList) -> InfoTupleList | [] | undefined', 194 | \ 'processes() -> [pid()]', 195 | \ 'purge_module(Module) -> true', 196 | \ 'put(Key, Val) -> term()', 197 | \ 'register(RegName, PidOrPort) -> true', 198 | \ 'registered() -> [RegName]', 199 | \ 'round(Number) -> integer()', 200 | \ 'self() -> pid()', 201 | \ 'setelement(Index, Tuple1, Value) -> Tuple2', 202 | \ 'size(Item) -> integer() >= 0', 203 | \ 'spawn(Fun) -> pid()', 204 | \ 'spawn(Node, Fun) -> pid()', 205 | \ 'spawn(Module, Function, Args) -> pid()', 206 | \ 'spawn(Node, Module, Function, Args) -> pid()', 207 | \ 'spawn_link(Fun) -> pid()', 208 | \ 'spawn_link(Node, Fun) -> pid()', 209 | \ 'spawn_link(Module, Function, Args) -> pid()', 210 | \ 'spawn_link(Node, Module, Function, Args) -> pid()', 211 | \ 'spawn_monitor(Fun) -> {pid(), reference()}', 212 | \ 'spawn_monitor(Module, Function, Args) -> {pid(), reference()}', 213 | \ 'spawn_opt(Fun, Options) -> pid() | {pid(), reference()}', 214 | \ 'spawn_opt(Node, Fun, Options) -> pid() | {pid(), reference()}', 215 | \ 'spawn_opt(Module, Function, Args, Options) -> pid() | {pid(), reference()}', 216 | \ 'spawn_opt(Node, Module, Function, Args, Options) -> pid() | {pid(), reference()}', 217 | \ 'split_binary(Bin, Pos) -> {binary(), binary()}', 218 | \ 'statistics(Item :: context_switches) -> {ContextSwitches, 0}', 219 | \ 'term_to_binary(Term) -> ext_binary()', 220 | \ 'term_to_binary(Term, Options) -> ext_binary()', 221 | \ 'throw(Any) -> no_return()', 222 | \ 'time() -> Time', 223 | \ 'tl(List) -> term()', 224 | \ 'trunc(Number) -> integer()', 225 | \ 'tuple_size(Tuple) -> integer() >= 0', 226 | \ 'tuple_to_list(Tuple) -> [term()]', 227 | \ 'unlink(Id) -> true', 228 | \ 'unregister(RegName) -> true', 229 | \ 'whereis(RegName) -> pid() | port() | undefined'] 230 | 231 | " Return the informational line displayed at the end of the preview window. 232 | function s:GetPreviewLine() 233 | if g:erlang_completion_preview_help == 1 234 | return "\n\nClose preview window: CTRL-W z in normal mode." . 235 | \ "\nDisable preview window: :set cot-=preview." . 236 | \ "\nDon't show this message: :let g:erlang_completion_preview_help = 0." 237 | else 238 | return "" 239 | end 240 | endfunction 241 | 242 | " Find the next non-blank line 243 | function s:ErlangFindNextNonBlank(lnum) 244 | let lnum = nextnonblank(a:lnum + 1) 245 | let line = getline(lnum) 246 | 247 | while line =~ s:erlang_blank_line && 0 != lnum 248 | let lnum = nextnonblank(lnum + 1) 249 | let line = getline(lnum) 250 | endwhile 251 | 252 | return lnum 253 | endfunction 254 | 255 | " Return an argument list that contains a given number of arguments. 256 | " 257 | " Example: 258 | " 259 | " - Call: s:CreateArgumentList(3) 260 | " - Return value: "(T1, T2, T3)" 261 | function s:CreateArgumentList(arity) 262 | let argument_list = [] 263 | let i = 1 264 | while i <= a:arity 265 | call add(argument_list, 'T' .. i) 266 | let i += 1 267 | endwhile 268 | return '(' .. join(argument_list, ', ') .. ')' 269 | endfunction 270 | 271 | " Return a completion item. 272 | " 273 | " Parameters: 274 | " 275 | " - type: Type of the "spec" parameter. Either 'module', 'function_name' or 276 | " 'function_spec'. 277 | " - spec: A module name, a function name or a function specification. 278 | " 279 | " See the documentation of the completion items in ":help complete-items". 280 | function s:CreateComplItem(type, spec) 281 | 282 | if a:type == 'module' 283 | " module example = "my_mod" 284 | let target_type = 'module' 285 | let module = a:spec 286 | let compl_word = module . ':' 287 | let compl_abbr = module 288 | let compl_info = module . s:GetPreviewLine() 289 | let compl_kind = 'm' 290 | elseif a:type == 'function_name' 291 | " function_spec example: "my_fun" 292 | " function_name example: "my_fun" 293 | " function_args example: "" 294 | let target_type = 'function' 295 | let function_spec = a:spec 296 | let function_name = a:spec 297 | let function_args = '' 298 | elseif a:type == 'function_spec' 299 | " function_spec examples: 300 | " - "my_fun/2" 301 | " - "my_fun(A, B) -> integer()" 302 | " function_name example: "my_fun" 303 | " function_args example: 304 | " - "/2" 305 | " - "(A, B) -> integer()" 306 | let target_type = 'function' 307 | let function_spec = a:spec 308 | let function_name = matchstr(function_spec, '\w*') 309 | let function_args = function_spec[len(function_name):] 310 | endif 311 | 312 | if target_type == 'function' 313 | 314 | " Calculate which parenthesis to insert after the function name 315 | " (depending on whether the function has a zero arity). 316 | if function_args == '/0' || function_args =~# '^()' 317 | let paren = g:erlang_completion_zero_arity_paren 318 | else 319 | let paren = g:erlang_completion_nonzero_arity_paren 320 | endif 321 | 322 | " Extend the function's arity to an argument list (if necessary) 323 | if g:erlang_completion_extend_arity && function_args[0] == '/' 324 | let arity = str2nr(function_args[1:]) 325 | let function_args = s:CreateArgumentList(arity) .. ' -> any()' 326 | let function_spec = function_name .. function_args 327 | endif 328 | 329 | let compl_word = function_name . paren 330 | let compl_abbr = function_spec 331 | let compl_info = function_spec . s:GetPreviewLine() 332 | let compl_kind = 'f' 333 | endif 334 | 335 | return {'word': compl_word, 336 | \'abbr': compl_abbr, 337 | \'info': compl_info, 338 | \'kind': compl_kind, 339 | \'dup': 1} 340 | endfunction 341 | 342 | " Add error as completion items. 343 | " 344 | " Parameters: 345 | " 346 | " - base: The word to be completed. 347 | " - error_output: The output of the erlang_complete.erl script, which 348 | " describes the error. 349 | " 350 | " This functions adds a short help text ("Completion error...") to the 351 | " completion popup, and it adds the actual error to the preview window. 352 | " 353 | " See the documentation of the completion items in ":help complete-items". 354 | function s:AddComplErrorItems(compl_words, base, error_output) 355 | 356 | " Vim inserts compl_word instead of the word already typed by the user. We 357 | " don't want to modify the text already typed by the user (a:base), so we 358 | " set compl_word to a:base, making Vim replace the already typed word with 359 | " itself. 360 | " 361 | " There is one exception. If the already typed word is an empty string, we 362 | " need to replace it with something, because if compl_word is an empty 363 | " string, then Vim will ignore that completion item, and it will say "Omni 364 | " completion Pattern not found), and it will not show either the 365 | " completion popup or the preview window. To make Vim show the error 366 | " popup, we set compl_word to ' ' in this case. 367 | let compl_word = (a:base == '') ? ' ' : a:base 368 | 369 | " Let's show the error output in the preview window (if it is enabled). 370 | let compl_info = a:error_output . s:GetPreviewLine() 371 | 372 | let help_lines = 373 | \ ["Completion error.", 374 | \ " ", 375 | \ "The preview window contains the error output.", 376 | \ "Enable the preview window with: ':set cot+=preview'.", 377 | \ " ", 378 | \ "See ':help vim-erlang-omnicomplete-errors' for more information."] 379 | 380 | " In order to show the help lines in the completion popup, we add each 381 | " help line in a separate completion item. 382 | for help_line in help_lines 383 | let compl_item = {'word': compl_word, 384 | \'abbr': help_line, 385 | \'info': compl_info, 386 | \'dup': 1} 387 | call add(a:compl_words, compl_item) 388 | endfor 389 | 390 | endfunction 391 | 392 | " Find external function names 393 | " 394 | " Parameters: 395 | " 396 | " - module: the module being edited. 397 | " - base: the word to be completed. 398 | function s:ErlangFindExternalFunc(module, base) 399 | 400 | " If the module is cached, load its functions 401 | if has_key(s:modules_cache, a:module) 402 | let compl_words = [] 403 | let module_cache = get(s:modules_cache, a:module) 404 | for compl_item in module_cache 405 | " If a:base is a prefix of compl_item, add compl_item to the list 406 | " of possible completions 407 | if match(compl_item.word, a:base) == 0 408 | call add(compl_words, compl_item) 409 | endif 410 | endfor 411 | 412 | return compl_words 413 | endif 414 | 415 | let compl_words = [] 416 | let output = system('escript ' . fnameescape(s:erlang_complete_file) . 417 | \' list-functions ' . fnameescape(a:module) . 418 | \' --basedir ' . fnameescape(expand('%:p:h'))) 419 | let output_lines = split(output, '\n') 420 | 421 | " There are two possibilities: 422 | " 423 | " 1. If the completion items were successfully calculated, the script 424 | "   output has the following format: 425 | " 426 | " 427 | " ... 428 | " 429 | " execution_successful 430 | " 431 | " ... 432 | " 433 | " 434 | " If the user wants to complete a module, we do not prevent them from 435 | " doing that. So we simply ignore the warnings. 436 | " 437 | " 2. If there was an error, the script output has the following format: 438 | " 439 | "   440 | " ... 441 | "   442 | " 443 | " In this case we present the errors to the user in the preview 444 | " window. 445 | " 446 | " The marker_index variable is the index of the line that contains 447 | " the 'execution_successful' marker (counting the first line as 0). If 448 | " there is no such marker, marker_index is -1. 449 | let marker_index = index(output_lines, 'execution_successful') 450 | 451 | if marker_index != -1 452 | 453 | " We found possible completions, so we add them as completions items. 454 | 455 | " We iterate on functions in the given module that start with `base` and 456 | " add them to the completion list. 457 | let function_specs = output_lines[(marker_index + 1):] 458 | for function_spec in function_specs 459 | " - When the function doesn't have a type spec, its function_spec 460 | " will be e.g. "f/2". 461 | " - When the function has a type spec, its function_spec will be e.g. 462 | " "f(A, B)". 463 | if match(function_spec, a:base) == 0 464 | let compl_item = s:CreateComplItem('function_spec', function_spec) 465 | call add(compl_words, compl_item) 466 | 467 | " Populate the cache only when iterating over all the 468 | " module functions (i.e. no prefix for the completion) 469 | if g:erlang_completion_cache && a:base == '' 470 | if !has_key(s:modules_cache, a:module) 471 | let s:modules_cache[a:module] = [compl_item] 472 | else 473 | let module_cache = get(s:modules_cache, a:module) 474 | let s:modules_cache[a:module] = 475 | \ add(module_cache, compl_item) 476 | endif 477 | endif 478 | 479 | " The user entered some text, so stop the completion 480 | if complete_check() 481 | " The module couldn't be entirely cached 482 | if has_key(s:modules_cache, a:module) 483 | call remove(s:modules_cache, a:module) 484 | endif 485 | break 486 | endif 487 | endif 488 | endfor 489 | 490 | else 491 | 492 | " There was an error during completion. 493 | call s:AddComplErrorItems(compl_words, a:base, output) 494 | 495 | endif 496 | 497 | return compl_words 498 | endfunction 499 | 500 | " Find local function names, BIFs and modules. 501 | " 502 | " This function is called when a word (without a ":") needs to be completed, 503 | " such as base = "lis". This could be a local function call (e.g. 504 | " "list_things"), a BIF ("list_to_binary") or a module ("lists"). 505 | " 506 | " Parameter: 507 | " 508 | " - base: the word to be completed. 509 | function s:ErlangFindLocalFunc(base) 510 | " Begin at line 1 511 | let lnum = s:ErlangFindNextNonBlank(1) 512 | 513 | if "" == a:base 514 | let base = '^\w' " Used to match against word symbol 515 | else 516 | let base = '^' . a:base 517 | endif 518 | 519 | " Find local functions that start with `base`. 520 | let compl_words = [] 521 | while 0 != lnum && !complete_check() 522 | let line = getline(lnum) 523 | let function_name = matchstr(line, base . '[0-9A-Za-z_-]\+(\@=') 524 | if function_name != "" 525 | " We found such a local function. 526 | let compl_item = s:CreateComplItem('function_name', function_name) 527 | call add(compl_words, compl_item) 528 | endif 529 | let lnum = s:ErlangFindNextNonBlank(lnum) 530 | endwhile 531 | 532 | if "" == a:base 533 | let base = '' 534 | else 535 | let base = '^' . a:base 536 | endif 537 | 538 | " Find BIFs that start with `base`. 539 | for bif_spec in s:auto_imported_bifs 540 | if bif_spec =~# base 541 | let compl_item = s:CreateComplItem('function_spec', bif_spec) 542 | call add(compl_words, compl_item) 543 | endif 544 | endfor 545 | 546 | " Find modules that start with `base`. 547 | let modules = system('escript ' . fnameescape(s:erlang_complete_file) . 548 | \' list-modules ' . 549 | \' --basedir ' . fnameescape(expand('%:p:h'))) 550 | for module in split(modules, '\n') 551 | if module =~# base 552 | let compl_item = s:CreateComplItem('module', module) 553 | call add(compl_words, compl_item) 554 | endif 555 | endfor 556 | 557 | return compl_words 558 | endfunction 559 | 560 | " Main function for completion. 561 | " 562 | " - If findstart = 1, then the function must return the column where the base 563 | " (the word to be completed) starts. 564 | " - If findstart = 0, then a:base is the word to be completed, and the 565 | " function must return a list with the possible completions. 566 | " 567 | " See ":help complete-functions" for the exact specification of this function. 568 | function erlang_complete#Complete(findstart, base) 569 | let lnum = line('.') 570 | let column = col('.') 571 | let line = strpart(getline('.'), 0, column - 1) 572 | 573 | " 1) If the char to the left of us is not the part of a function call, the 574 | " user probably wants to type a local function, a module or a BIF 575 | if line[column - 2] !~ '[0-9A-Za-z:_-]' 576 | if a:findstart 577 | return column 578 | else 579 | return s:ErlangFindLocalFunc(a:base) 580 | endif 581 | endif 582 | 583 | " 2) Function in external module 584 | if line =~ s:erlang_external_func_beg 585 | let delimiter = match(line, ':[0-9A-Za-z_-]*$') + 1 586 | if a:findstart 587 | return delimiter 588 | else 589 | let module = matchstr(line[:-2], '\<\k*\>$') 590 | return s:ErlangFindExternalFunc(module, a:base) 591 | endif 592 | endif 593 | 594 | " 3) Local function 595 | if line =~ s:erlang_local_func_beg 596 | let funcstart = match(line, ':\@ 27 | 28 | +----------+ 29 | | myfun f | 30 | +----------+ 31 | 32 | 2. Auto-imported BIFs, e.g. "list_to" is completed as: > 33 | 34 | +-----------------------------------------+ 35 | | list_to_atom(String) -> atom() f | 36 | | list_to_binary(IoList) -> binary() f | 37 | | ... | 38 | +-----------------------------------------+ 39 | 40 | 3. Modules, e.g. "gen_" is completed as: > 41 | 42 | +--------------+ 43 | | gen_event m | 44 | | gen_fsm m | 45 | | ... m | 46 | +--------------+ 47 | 48 | 4. Exported functions from other modules, e.g. "lists:mem" is completed as: > 49 | 50 | +-----------------------------------------+ 51 | | lists:member(Elem, List) -> boolean() f | 52 | +-----------------------------------------+ 53 | 54 | Type specs and EDoc information are also used when showing completion for 55 | functions in other modules. 56 | 57 | If you use rebar, the plugin will find the rebar.config file corresponding to 58 | the module being edited, and uses the directories in rebar.config to find the 59 | possible module and function names. The plugin does not support rebar.script 60 | files though. 61 | 62 | The plugin has a basic support for rebar3. It is aware of rebar3 when a 63 | rebar.lock is found inside the project. In the moment of syntax check, the 64 | plugin discovers all the dependencies invoking the "rebar3 as default path" 65 | command in background. The rebar executable is looked up inside the root 66 | directory of the project and, if it's not found, through the current execution 67 | path. 68 | 69 | ============================================================================== 70 | COMMANDS *vim-erlang-omnicomplete-commands* 71 | 72 | *:ErlangCompleteClearAllCache* 73 | :ErlangCompleteClearAllCache 74 | Clear the completion cache. This can be used if a module has changed 75 | but the plugin have not noticed that and still completes the previous 76 | function names. 77 | 78 | ============================================================================== 79 | CONFIGURATION *vim-erlang-omnicomplete-config* 80 | 81 | The vim-erlang-omnicomplete plugin's behaviour can be configured by setting 82 | the global variables below from the |vimrc| file and restarting Vim. 83 | 84 | CONFIGURATION OPTIONS *vim-erlang-omnicomplete-options* 85 | 86 | g:erlang_completion_cache *g:erlang_completion_cache* 87 | Determines whether the functions exported from available modules 88 | should be cached. If `1`, caching is enabled; if `0`, caching is 89 | disabled. The default value is `1`. 90 | 91 | g:erlang_completion_preview_help *g:erlang_completion_preview_help* 92 | Determines whether a help message about the preview window should 93 | appear in the preview window after the completion information. If `1`, 94 | the help message appears; if `0`, it does not appear. The default 95 | value is `1`. 96 | 97 | *g:erlang_completion_zero_arity_paren* 98 | g:erlang_completion_zero_arity_paren 99 | Determines which additional string to insert after completing a 100 | zero-arity function. The default value is "()". 101 | 102 | An example scenario: 103 | 104 | 1. Type "se" in an Erlang buffer. 105 | 106 | 2. Hit |i_CTRL-X_CTRL-O|. 107 | 108 | 3. The first element in the completion list is: > 109 | 110 | self() -> pid() 111 | 112 | < 4. Vim inserts the function name "self". 113 | 114 | 5. Vim also inserts the value of 115 | |g:erlang_completion_zero_arity_paren|. With the default value, 116 | this means that the inserted text will be: > 117 | 118 | self() 119 | < 120 | *g:erlang_completion_nonzero_arity_paren* 121 | g:erlang_completion_nonzero_arity_paren 122 | Determines which additional string to insert after completing a 123 | function with non-zero arity (or unknown arity). The default value is 124 | "(". 125 | 126 | An example scenario: 127 | 128 | 1. Type "list_to_a" in an Erlang buffer. 129 | 130 | 2. Hit |i_CTRL-X_CTRL-O|. 131 | 132 | 3. The first element in the completion list is: > 133 | 134 | list_to_atom(String) -> atom() 135 | 136 | < 4. Vim inserts the function name list_to_atom. 137 | 138 | 5. Vim also inserts the value of 139 | |g:erlang_completion_nonzero_arity_paren|. With the default value, 140 | this means that the inserted text will be: > 141 | 142 | list_to_atom( 143 | < 144 | g:erlang_completion_extend_arity g:erlang_completion_extend_arity 145 | Determines how to display functions where only the arity is known. 146 | 147 | a. If set to `0`, such functions will be displayed with their arity, 148 | for example: > 149 | 150 | module_info/1 151 | 152 | < b. If set to `1`, a type spec will be generated for such functions, 153 | for example: > 154 | 155 | module_info(T1) -> any() 156 | 157 | < The default value is `1`. 158 | 159 | ============================================================================== 160 | CONFIGURATION REBAR3 *vim-erlang-omnicomplete-config-rebar3* 161 | 162 | If you want to make the syntax check of the current project under a different 163 | profile (see rebar3 profiles), you can easily instruct rebar3 by adding a 164 | special configuration item in your rebar.config file. 165 | 166 | By default, if nothing is specified, the plugin will assume you chose the 167 | `default` profile. 168 | 169 | E.g. to use the "test" profile: > 170 | 171 | {vim_erlang_compiler, [ 172 | {profile, "test"} 173 | ]}. 174 | < 175 | ============================================================================== 176 | ERRORS *vim-erlang-omnicomplete-errors* 177 | 178 | The plugin can encounter various errors when trying to execute a completion. 179 | For example: 180 | 181 | - The plugin cannot find the module whose functions should be completed. 182 | - The plugin finds the module's source code but cannot parse it. 183 | - There is an uncaught exception is the plugin's Erlang code 184 | (erlang_complete.erl). 185 | 186 | In case of an error: 187 | 188 | - The plugin displays "Completion error" and a short help message in the 189 | completion popup (unless 'completeopt' contains neither "menu" nor 190 | "menuone"). 191 | - The plugin displays the error itself in the preview window (if 'completeopt' 192 | contains "preview"), or as a popup (if 'completeopt' contains "popup"). 193 | 194 | Since the default 'completeopt' value is "menu,preview", both of the above are 195 | shown by default. 196 | 197 | ============================================================================== 198 | CREDITS *vim-erlang-omnicomplete-credits* 199 | 200 | Developed by the vim-erlang community. Distributed under Vim's |license|. 201 | 202 | vim-erlang-omnicomplete's original source code comes from vimerl 203 | (https://github.com/jimenezrick/vimerl). 204 | 205 | Author: Oscar Hellström 206 | Contributors: Pawel 'kTT' Salata (http://github.com/kTT) 207 | Ricardo Catalinas Jiménez 208 | Eduardo Lopez (http://github.com/tapichu) 209 | Ignas Vyšniauskas (https://github.com/yfyf) 210 | Adam Rutkowski 211 | Csaba Hoch 212 | Zengda 213 | License: Vim License (see |license|) 214 | 215 | ============================================================================== 216 | CONTRIBUTING *vim-erlang-omnicomplete-contributing* 217 | 218 | Bug reports, suggestions and improvements encouraged at the project's GitHub: 219 | 220 | https://github.com/vim-erlang/vim-erlang-omnicomplete 221 | 222 | ============================================================================== 223 | -------------------------------------------------------------------------------- /ftplugin/erlang.vim: -------------------------------------------------------------------------------- 1 | if exists('b:erlang_omnicomplete_loaded') 2 | finish 3 | else 4 | let b:erlang_omnicomplete_loaded = 1 5 | endif 6 | 7 | setlocal omnifunc=erlang_complete#Complete 8 | -------------------------------------------------------------------------------- /plugin/erlang_omnicomplete.vim: -------------------------------------------------------------------------------- 1 | if exists('g:loaded_erlang_omnicomplete') 2 | finish 3 | endif 4 | 5 | let g:loaded_erlang_omnicomplete = 1 6 | 7 | command ErlangCompleteClearAllCache call erlang_complete#ClearAllCache() 8 | --------------------------------------------------------------------------------