From 94f1c84aad8963537a661ed6e02399558fbf33df Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Sat, 11 Dec 2021 17:38:07 +0100
Subject: [PATCH 001/239] [#1152] Emit error in case the module name does not
coincide with the filename
---
.../src/diagnostics_module_name_check.erl | 1 +
apps/els_lsp/src/els_compiler_diagnostics.erl | 36 ++++++++++++++++++-
apps/els_lsp/test/els_diagnostics_SUITE.erl | 15 ++++++++
3 files changed, 51 insertions(+), 1 deletion(-)
create mode 100644 apps/els_lsp/priv/code_navigation/src/diagnostics_module_name_check.erl
diff --git a/apps/els_lsp/priv/code_navigation/src/diagnostics_module_name_check.erl b/apps/els_lsp/priv/code_navigation/src/diagnostics_module_name_check.erl
new file mode 100644
index 000000000..09898048f
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/diagnostics_module_name_check.erl
@@ -0,0 +1 @@
+-module(module_name_check).
diff --git a/apps/els_lsp/src/els_compiler_diagnostics.erl b/apps/els_lsp/src/els_compiler_diagnostics.erl
index 3e4916188..1038c0846 100644
--- a/apps/els_lsp/src/els_compiler_diagnostics.erl
+++ b/apps/els_lsp/src/els_compiler_diagnostics.erl
@@ -723,7 +723,41 @@ compile_file(Path, Dependencies) ->
[code:load_binary(Dependency, Filename, Binary)
|| {{Dependency, Binary, Filename}, _} <- Olds],
Diagnostics = lists:flatten([ Diags || {_, Diags} <- Olds ]),
- {Res, Diagnostics}.
+ {Res, Diagnostics ++ module_name_check(Path)}.
+
+%% The module_name error is only emitted by the Erlang compiler during
+%% the "save binary" phase. This phase does not occur when code
+%% generation is disabled (e.g. by using the basic_validation or
+%% strong_validation arguments when invoking the compile:file/2
+%% function), which is exactly the case for the Erlang LS compiler
+%% diagnostics. Therefore, let's replicate the check explicitly.
+%% See issue #1152.
+-spec module_name_check(string()) -> [els_diagnostics:diagnostic()].
+module_name_check(Path) ->
+ Basename = filename:basename(Path, ".erl"),
+ Uri = els_uri:uri(els_utils:to_binary(Path)),
+ case els_dt_document:lookup(Uri) of
+ {ok, [Document]} ->
+ case els_dt_document:pois(Document, [module]) of
+ [#{id := Module, range := Range}] ->
+ case atom_to_list(Module) =:= Basename of
+ true ->
+ [];
+ false ->
+ Message =
+ io_lib:format("Module name '~s' does not match file name '~ts'",
+ [Module, Basename]),
+ Diagnostic = els_diagnostics:make_diagnostic(
+ els_protocol:range(Range),
+ els_utils:to_binary(Message),
+ ?DIAGNOSTIC_ERROR,
+ <<"Compiler (via Erlang LS)">>),
+ [Diagnostic]
+ end
+ end;
+ _ ->
+ []
+ end.
%% @doc Load a dependency, return the old version of the code (if any),
%% so it can be restored.
diff --git a/apps/els_lsp/test/els_diagnostics_SUITE.erl b/apps/els_lsp/test/els_diagnostics_SUITE.erl
index 928c82205..65732538e 100644
--- a/apps/els_lsp/test/els_diagnostics_SUITE.erl
+++ b/apps/els_lsp/test/els_diagnostics_SUITE.erl
@@ -41,6 +41,7 @@
, unused_macros/1
, unused_record_fields/1
, gradualizer/1
+ , module_name_check/1
]).
%%==============================================================================
@@ -654,6 +655,20 @@ gradualizer(_Config) ->
Hints = [],
els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+-spec module_name_check(config()) -> ok.
+module_name_check(_Config) ->
+ Path = src_path("diagnostics_module_name_check.erl"),
+ Source = <<"Compiler (via Erlang LS)">>,
+ Errors = [ #{ message =>
+ <<"Module name 'module_name_check' does not match "
+ "file name 'diagnostics_module_name_check'">>
+ , range => {{0, 8}, {0, 25}}
+ }
+ ],
+ Warnings = [],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+
%%==============================================================================
%% Internal Functions
%%==============================================================================
From 08114387b54d0f12f227c20effaa15e9d5bbb921 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A9ter=20G=C3=B6m=C3=B6ri?=
Date: Fri, 26 Nov 2021 14:51:37 +0100
Subject: [PATCH 002/239] Parse incomplete text
In case of unparsable forms, try removing some trailing tokens, this way the
first half of the form could be parsed and POIs extracted.
related to #1037
---
apps/els_lsp/src/els_parser.erl | 110 ++++++++++++++++++++-----
apps/els_lsp/test/els_parser_SUITE.erl | 43 +++++++++-
rebar.config | 3 +-
rebar.lock | 7 +-
4 files changed, 135 insertions(+), 28 deletions(-)
diff --git a/apps/els_lsp/src/els_parser.erl b/apps/els_lsp/src/els_parser.erl
index 18d851228..479e89a80 100644
--- a/apps/els_lsp/src/els_parser.erl
+++ b/apps/els_lsp/src/els_parser.erl
@@ -1,14 +1,17 @@
%%==============================================================================
-%% The erlang_ls parser. It uses the epp_dodger OTP library.
+%% The erlang_ls parser. It uses the parser of erlfmt library.
%%==============================================================================
-module(els_parser).
%%==============================================================================
%% Exports
%%==============================================================================
--export([ parse/1
- , parse_file/1
+-export([ parse/1]).
+
+%% For manual use only, to test the parser
+-export([ parse_file/1
, parse_text/1
+ , parse_incomplete_text/2
]).
%%==============================================================================
@@ -49,6 +52,18 @@ forms_to_ast({ok, Forms, _ErrorInfo}) ->
forms_to_ast({error, _ErrorInfo} = Error) ->
Error.
+-spec parse_incomplete_text(string(), {erl_anno:line(), erl_anno:column()})
+ -> {ok, tree()} | error.
+parse_incomplete_text(Text, {_Line, _Col} = StartLoc) ->
+ Tokens = scan_text(Text, StartLoc),
+ case parse_incomplete_tokens(Tokens) of
+ {ok, Form} ->
+ Tree = els_erlfmt_ast:erlfmt_to_st(Form),
+ {ok, Tree};
+ error ->
+ error
+ end.
+
%%==============================================================================
%% Internal Functions
%%==============================================================================
@@ -66,14 +81,76 @@ parse_forms(Forms) ->
-spec parse_form(erlfmt_parse:abstract_node()) -> deep_list(poi()).
parse_form({raw_string, Anno, Text}) ->
- Start = erlfmt_scan:get_anno(location, Anno),
- {ok, RangeTokens, _EndLocation} = erl_scan:string(Text, Start, [text]),
- find_attribute_tokens(RangeTokens);
+ StartLoc = erlfmt_scan:get_anno(location, Anno),
+ RangeTokens = scan_text(Text, StartLoc),
+ case parse_incomplete_tokens(RangeTokens) of
+ {ok, Form} ->
+ parse_form(Form);
+ error ->
+ find_attribute_tokens(RangeTokens)
+ end;
parse_form(Form) ->
Tree = els_erlfmt_ast:erlfmt_to_st(Form),
POIs = points_of_interest(Tree),
POIs.
+-spec scan_text(string(), {erl_anno:line(), erl_anno:column()})
+ -> [erlfmt_scan:token()].
+scan_text(Text, StartLoc) ->
+ PaddedText = pad_text(Text, StartLoc),
+ {ok, Tokens, _Comments, _Cont} = erlfmt_scan:string_node(PaddedText),
+ ensure_dot(Tokens).
+
+-spec parse_incomplete_tokens([erlfmt_scan:token()])
+ -> {ok, erlfmt_parse:abstract_node()} | error.
+parse_incomplete_tokens([{dot, _}]) ->
+ error;
+parse_incomplete_tokens(Tokens) ->
+ case erlfmt_parse:parse_node(Tokens) of
+ {ok, Form} ->
+ {ok, Form};
+ {error, {ErrorLoc, erlfmt_parse, _Reason}} ->
+ TrimmedTokens = tokens_until(Tokens, ErrorLoc),
+ parse_incomplete_tokens(TrimmedTokens)
+ end.
+
+%% @doc Drop tokens after given location but keep final dot, to preserve its
+%% location
+-spec tokens_until([erlfmt_scan:token()], erl_anno:location())
+ -> [erlfmt_scan:token()].
+tokens_until([_Hd, {dot, _} = Dot], _Loc) ->
+ %% We need to drop at least one token before the dot.
+ %% Otherwise if error location is at the dot, we cannot just drop the dot and
+ %% add a dot again, because it would result in an infinite loop.
+ [Dot];
+tokens_until([Hd | Tail], Loc) ->
+ case erlfmt_scan:get_anno(location, Hd) < Loc of
+ true ->
+ [Hd | tokens_until(Tail, Loc)];
+ false ->
+ tokens_until(Tail, Loc)
+ end.
+
+%% `erlfmt_scan' does not support start location other than {1,1}
+%% so we have to shift the text with newlines and spaces
+-spec pad_text(string(), {erl_anno:line(), erl_anno:column()}) -> string().
+pad_text(Text, {StartLine, StartColumn}) ->
+ lists:duplicate(StartLine - 1, $\n)
+ ++ lists:duplicate(StartColumn - 1, $\s)
+ ++ Text.
+
+-spec ensure_dot([erlfmt_scan:token()]) -> [erlfmt_scan:token()].
+ensure_dot(Tokens) ->
+ case lists:last(Tokens) of
+ {dot, _} ->
+ Tokens;
+ T ->
+ EndLocation = erlfmt_scan:get_anno(end_location, T),
+ %% Add a dot which has zero length (invisible) so it does not modify the
+ %% end location of the whole form
+ Tokens ++ [{dot, #{location => EndLocation, end_location => EndLocation}}]
+ end.
+
%% @doc Resolve POI for specific sections
%%
%% These sections are such things as `export' or `spec' attributes, for which
@@ -81,31 +158,20 @@ parse_form(Form) ->
%% completion items. Using the tokens provides accurate position for the
%% beginning and end for this sections, and can also handle the situations when
%% the code is not parsable.
--spec find_attribute_tokens([erl_scan:token()]) -> [poi()].
+-spec find_attribute_tokens([erlfmt_scan:token()]) -> [poi()].
find_attribute_tokens([ {'-', Anno}, {atom, _, Name} | [_|_] = Rest])
when Name =:= export;
Name =:= export_type ->
- From = erl_anno:location(Anno),
- To = token_end_location(lists:last(Rest)),
+ From = erlfmt_scan:get_anno(location, Anno),
+ To = erlfmt_scan:get_anno(end_location, lists:last(Rest)),
[poi({From, To}, Name, From)];
find_attribute_tokens([ {'-', Anno}, {atom, _, spec} | [_|_] = Rest]) ->
- From = erl_anno:location(Anno),
- To = token_end_location(lists:last(Rest)),
+ From = erlfmt_scan:get_anno(location, Anno),
+ To = erlfmt_scan:get_anno(end_location, lists:last(Rest)),
[poi({From, To}, spec, undefined)];
find_attribute_tokens(_) ->
[].
-%% Inspired by erlfmt_scan:dot_anno
--spec token_end_location(erl_scan:token()) -> erl_anno:location().
-token_end_location({dot, Anno}) ->
- %% Special handling for dot tokens, which by definition contain a dot char
- %% followed by a whitespace char. We don't want to count the whitespace (which
- %% is usually a newline) as part of the form.
- {Line, Col} = erl_anno:location(Anno),
- {Line, Col + 1};
-token_end_location(Token) ->
- erl_scan:end_location(Token).
-
-spec points_of_interest(tree()) -> [[poi()]].
points_of_interest(Tree) ->
FoldFun = fun(T, Acc) -> [do_points_of_interest(T) | Acc] end,
diff --git a/apps/els_lsp/test/els_parser_SUITE.erl b/apps/els_lsp/test/els_parser_SUITE.erl
index d5318b5a0..c45268565 100644
--- a/apps/els_lsp/test/els_parser_SUITE.erl
+++ b/apps/els_lsp/test/els_parser_SUITE.erl
@@ -9,6 +9,8 @@
%% Test cases
-export([ specs_location/1
, parse_invalid_code/1
+ , parse_incomplete_function/1
+ , parse_incomplete_spec/1
, underscore_macro/1
, specs_with_record/1
, types_with_record/1
@@ -58,11 +60,48 @@ specs_location(_Config) ->
?assertMatch([_], parse_find_pois(Text, spec, {foo, 1})),
ok.
-%% Issue #170
+%% Issue #170 - scanning error does not crash the parser
-spec parse_invalid_code(config()) -> ok.
parse_invalid_code(_Config) ->
Text = "foo(X) -> 16#.",
- {ok, _POIs} = els_parser:parse(Text),
+ %% Currently, if scanning fails (eg. invalid integer), no POIs are created
+ {ok, []} = els_parser:parse(Text),
+ %% In the future, it would be nice to have at least the POIs before the error
+ %% ?assertMatch([#{id := {foo, 1}}], parse_find_pois(Text, function)),
+ %% ?assertMatch([#{id := 'X'}], parse_find_pois(Text, variable)),
+
+ %% Or at least the POIs from the previous forms
+ Text2 =
+ "bar() -> ok.\n"
+ "foo() -> 'ato",
+ %% (unterminated atom)
+ {ok, []} = els_parser:parse(Text2),
+ %% ?assertMatch([#{id := {bar, 0}}], parse_find_pois(Text2, function)),
+ ok.
+
+%% Issue #1037
+-spec parse_incomplete_function(config()) -> ok.
+parse_incomplete_function(_Config) ->
+ Text = "f(VarA) -> VarB = g(), case h() of VarC -> Var",
+
+ %% VarA and VarB are found, but VarC is not
+ ?assertMatch([#{id := 'VarA'},
+ #{id := 'VarB'}], parse_find_pois(Text, variable)),
+ %% g() is found but h() is not
+ ?assertMatch([#{id := {g, 0}}], parse_find_pois(Text, application)),
+
+ ?assertMatch([#{id := {f, 1}}], parse_find_pois(Text, function)),
+ ok.
+
+-spec parse_incomplete_spec(config()) -> ok.
+parse_incomplete_spec(_Config) ->
+ Text = "-spec f() -> aa bb cc\n.",
+
+ %% spec range ends where the original dot ends, including ignored parts
+ ?assertMatch([#{id := {f, 0}, range := #{from := {1, 1}, to := {2, 2}}}],
+ parse_find_pois(Text, spec)),
+ %% only first atom is found
+ ?assertMatch([#{id := aa}], parse_find_pois(Text, atom)),
ok.
-spec underscore_macro(config()) -> ok.
diff --git a/rebar.config b/rebar.config
index aa7377986..35f768572 100644
--- a/rebar.config
+++ b/rebar.config
@@ -12,7 +12,8 @@
, {docsh, "0.7.2"}
, {elvis_core, "1.1.1"}
, {rebar3_format, "0.8.2"}
- , {erlfmt, "1.0.0"}
+ %%, {erlfmt, "1.0.0"}
+ , {erlfmt, {git, "https://github.com/gomoripeti/erlfmt.git", {tag, "erlang_ls_parser_error_loc"}}} %% Temp until erlfmt PR 325 is merged (commit d4422d1)
, {ephemeral, "2.0.4"}
, {tdiff, "0.1.2"}
, {uuid, "2.0.1", {pkg, uuid_erl}}
diff --git a/rebar.lock b/rebar.lock
index 36a73a456..00b37cf61 100644
--- a/rebar.lock
+++ b/rebar.lock
@@ -3,7 +3,10 @@
{<<"docsh">>,{pkg,<<"docsh">>,<<"0.7.2">>},0},
{<<"elvis_core">>,{pkg,<<"elvis_core">>,<<"1.1.1">>},0},
{<<"ephemeral">>,{pkg,<<"ephemeral">>,<<"2.0.4">>},0},
- {<<"erlfmt">>,{pkg,<<"erlfmt">>,<<"1.0.0">>},0},
+ {<<"erlfmt">>,
+ {git,"https://github.com/gomoripeti/erlfmt.git",
+ {ref,"d4422d1fd79a73ef534c2bcbe5b5da4da5338833"}},
+ 0},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},2},
{<<"gradualizer">>,
{git,"https://github.com/josefs/Gradualizer.git",
@@ -25,7 +28,6 @@
{<<"docsh">>, <<"F893D5317A0E14269DD7FE79CF95FB6B9BA23513DA0480EC6E77C73221CAE4F2">>},
{<<"elvis_core">>, <<"EB7864CE4BC87D13FBF1C222A82230A4C3D327C21080B73E97FC6343C3A5264D">>},
{<<"ephemeral">>, <<"B3E57886ADD5D90C82FE3880F5954978222A122CB8BAA123667401BBAAEC51D6">>},
- {<<"erlfmt">>, <<"4F3853A48F791DBB9EA5B578BAE4DB24FF7ED47BBA063698F6AF2168F55E7C6B">>},
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>},
{<<"jsx">>, <<"20A170ABD4335FC6DB24D5FAD1E5D677C55DADF83D1B20A8A33B5FE159892A39">>},
{<<"katana_code">>, <<"B2195859DF57D8BEBF619A9FD3327CD7D01563A98417156D0F4C5FAB435F2630">>},
@@ -42,7 +44,6 @@
{<<"docsh">>, <<"4E7DB461BB07540D2BC3D366B8513F0197712D0495BB85744F367D3815076134">>},
{<<"elvis_core">>, <<"391C95BAA49F2718D7FB498BCF08046DDFC202CF0AAB63B2E439271485C9DC42">>},
{<<"ephemeral">>, <<"4B293D80F75F9C4575FF4B9C8E889A56802F40B018BF57E74F19644EFEE6C850">>},
- {<<"erlfmt">>, <<"44BE0BE03CE69902DC6DCD8F65E7B3ADED6AAF5D0BE70964188CABD4AD24F04E">>},
{<<"getopt">>, <<"53E1AB83B9CEB65C9672D3E7A35B8092E9BDC9B3EE80721471A161C10C59959C">>},
{<<"jsx">>, <<"37BECA0435F5CA8A2F45F76A46211E76418FBEF80C36F0361C249FC75059DC6D">>},
{<<"katana_code">>, <<"8448AD3F56D9814F98A28BE650F7191BDD506575E345CC16D586660B10F6E992">>},
From 8b489fffb82f3901e233b06982b009299019b4e0 Mon Sep 17 00:00:00 2001
From: Amin Arria
Date: Fri, 17 Dec 2021 19:16:05 +0100
Subject: [PATCH 003/239] [#1050] Filter log notification method to avoid
recursion
---
apps/els_lsp/src/els_server.erl | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/apps/els_lsp/src/els_server.erl b/apps/els_lsp/src/els_server.erl
index eedfb736a..fba32d39e 100644
--- a/apps/els_lsp/src/els_server.erl
+++ b/apps/els_lsp/src/els_server.erl
@@ -197,6 +197,11 @@ handle_request(Response, State0) ->
State0.
-spec do_send_notification(binary(), map(), state()) -> ok.
+%% This notification is specifically filtered out to avoid recursive
+%% calling of log notifications (see issue #1050)
+do_send_notification(<<"window/logMessage">> = Method, Params, State) ->
+ Notification = els_protocol:notification(Method, Params),
+ send(Notification, State);
do_send_notification(Method, Params, State) ->
Notification = els_protocol:notification(Method, Params),
?LOG_DEBUG( "[SERVER] Sending notification [notification=~s]"
From 2f6decd8cfe342da60e102b55cde66c4af7f784a Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Fri, 24 Dec 2021 10:11:27 +0100
Subject: [PATCH 004/239] Precompute list of enabled diagnostics
---
apps/els_core/src/els_config.erl | 1 +
apps/els_lsp/src/els_diagnostics.erl | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/apps/els_core/src/els_config.erl b/apps/els_core/src/els_config.erl
index 2fd3652ce..5a6c6257d 100644
--- a/apps/els_core/src/els_config.erl
+++ b/apps/els_core/src/els_config.erl
@@ -154,6 +154,7 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
ok = set(otp_paths , otp_paths(OtpPath, false) -- ExcludePaths),
ok = set(lenses , Lenses),
ok = set(diagnostics , Diagnostics),
+ ok = set(enabled_diagnostics, els_diagnostics:enabled_diagnostics()),
%% All (including subdirs) paths used to search files with file:path_open/3
ok = set( search_paths
, lists:append([ project_paths(RootPath, AppsDirs, true)
diff --git a/apps/els_lsp/src/els_diagnostics.erl b/apps/els_lsp/src/els_diagnostics.erl
index 9c9b4b303..3e288ee15 100644
--- a/apps/els_lsp/src/els_diagnostics.erl
+++ b/apps/els_lsp/src/els_diagnostics.erl
@@ -90,7 +90,7 @@ make_diagnostic(Range, Message, Severity, Source) ->
-spec run_diagnostics(uri()) -> [pid()].
run_diagnostics(Uri) ->
- [run_diagnostic(Uri, Id) || Id <- enabled_diagnostics()].
+ [run_diagnostic(Uri, Id) || Id <- els_config:get(enabled_diagnostics)].
%%==============================================================================
%% Internal Functions
From d485f64bf88a0a70d75ec5f0666fe4697fa2c03c Mon Sep 17 00:00:00 2001
From: Luke Bakken
Date: Thu, 13 Jan 2022 14:31:20 -0800
Subject: [PATCH 005/239] Take CRLF into account
Related to https://github.com/inaka/elvis_core/issues/220
---
apps/els_core/src/els_text.erl | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/apps/els_core/src/els_text.erl b/apps/els_core/src/els_text.erl
index 61585b52a..4fb4f8a48 100644
--- a/apps/els_core/src/els_text.erl
+++ b/apps/els_core/src/els_text.erl
@@ -25,7 +25,8 @@
%% @doc Extract the N-th line from a text.
-spec line(text(), line_num()) -> text().
line(Text, LineNum) ->
- Lines = binary:split(Text, <<"\n">>, [global]),
+ % LRB TODO Lines = binary:split(Text, <<"\n">>, [global]),
+ Lines = binary:split(Text, [<<"\r\n">>, <<"\n">>], [global]),
lists:nth(LineNum + 1, Lines).
%% @doc Extract the N-th line from a text, up to the given column number.
@@ -107,7 +108,8 @@ lines_to_bin(Lines) ->
-spec bin_to_lines(text()) -> lines().
bin_to_lines(Text) ->
- [Bin || Bin <- binary:split(Text, <<"\n">>, [global])].
+ % LRB TODO [Bin || Bin <- binary:split(Text, <<"\n">>, [global])].
+ [Bin || Bin <- binary:split(Text, [<<"\r\n">>, <<"\n">>], [global])].
-spec ensure_string(binary() | string()) -> string().
ensure_string(Text) when is_binary(Text) ->
From 59bb8a5d11ed1d772488ad5e28921b79f0e214fa Mon Sep 17 00:00:00 2001
From: Kian-Meng Ang
Date: Fri, 14 Jan 2022 23:26:16 +0800
Subject: [PATCH 006/239] Fix typos
---
SPAWNFEST.md | 4 ++--
apps/els_core/include/els_core.hrl | 2 +-
apps/els_dap/src/els_dap_general_provider.erl | 2 +-
apps/els_dap/test/els_dap_general_provider_SUITE.erl | 2 +-
apps/els_lsp/src/els_code_navigation.erl | 4 ++--
apps/els_lsp/src/els_erlfmt_ast.erl | 2 +-
specs/runtime_node.md | 4 ++--
7 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/SPAWNFEST.md b/SPAWNFEST.md
index 7c61fbdf4..f843d14c9 100644
--- a/SPAWNFEST.md
+++ b/SPAWNFEST.md
@@ -20,14 +20,14 @@ The [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/
## Rationale
-One of the strengths of the Erlang programming language is the ability to seemlessly _debug_ and _trace_ Erlang code. Many tools and libraries exist, but they are sometimes under-utilized by the Community, either because their API is not intuitive (think to the [dbg](https://erlang.org/doc/man/dbg.html) Erlang module), or because they offer a limited, obsolete, UI (think the [debugger](http://erlang.org/doc/apps/debugger/debugger_chapter.html) application).
+One of the strengths of the Erlang programming language is the ability to seamlessly _debug_ and _trace_ Erlang code. Many tools and libraries exist, but they are sometimes under-utilized by the Community, either because their API is not intuitive (think to the [dbg](https://erlang.org/doc/man/dbg.html) Erlang module), or because they offer a limited, obsolete, UI (think the [debugger](http://erlang.org/doc/apps/debugger/debugger_chapter.html) application).
We want to solve this problem by leveraging some of the existing debugging and tracing facilities provided by Erlang/OTP and bringing the debugging experience directly in the text-editor, next to the code, improving the user experience when using such
tools.
[This video](https://www.youtube.com/watch?v=ydcrdwQKqI8&t=3s) shows what debugging Erlang code is like via the _debugger_ application. [This other video](https://www.youtube.com/watch?v=ydcrdwQKqI8) shows what the same experience looks like from Emacs.
-Due to the editor-agnostic nature of the _DAP_ protocol, a very similar experience is delivered to users of a different developement tool, be it _Vim_, _VS Code_ or _Sublime Text 3_.
+Due to the editor-agnostic nature of the _DAP_ protocol, a very similar experience is delivered to users of a different development tool, be it _Vim_, _VS Code_ or _Sublime Text 3_.
## Project Goal
diff --git a/apps/els_core/include/els_core.hrl b/apps/els_core/include/els_core.hrl
index 2ec182a12..2d142e2ea 100644
--- a/apps/els_core/include/els_core.hrl
+++ b/apps/els_core/include/els_core.hrl
@@ -207,7 +207,7 @@
}.
%%------------------------------------------------------------------------------
-%% Document Fiter
+%% Document Filter
%%------------------------------------------------------------------------------
-type document_filter() :: #{ language => binary()
, scheme => binary()
diff --git a/apps/els_dap/src/els_dap_general_provider.erl b/apps/els_dap/src/els_dap_general_provider.erl
index e1534e815..9a41d27c4 100644
--- a/apps/els_dap/src/els_dap_general_provider.erl
+++ b/apps/els_dap/src/els_dap_general_provider.erl
@@ -1,7 +1,7 @@
%%==============================================================================
%% @doc Erlang DAP General Provider
%%
-%% Implements the logic for hanlding all of the commands in the protocol.
+%% Implements the logic for handling all of the commands in the protocol.
%%
%% The functionality in this module will eventually be broken into several
%% different providers.
diff --git a/apps/els_dap/test/els_dap_general_provider_SUITE.erl b/apps/els_dap/test/els_dap_general_provider_SUITE.erl
index 47f77d0fc..01c0c850c 100644
--- a/apps/els_dap/test/els_dap_general_provider_SUITE.erl
+++ b/apps/els_dap/test/els_dap_general_provider_SUITE.erl
@@ -291,7 +291,7 @@ frame_variables(Config) ->
-spec navigation_and_frames(config()) -> ok.
navigation_and_frames(Config) ->
- %% test next, stepIn, continue and check aginst expeted stack frames
+ %% test next, stepIn, continue and check against expected stack frames
Provider = ?config(provider, Config),
#{<<"threads">> := [#{<<"id">> := ThreadId}]} =
els_provider:handle_request( Provider
diff --git a/apps/els_lsp/src/els_code_navigation.erl b/apps/els_lsp/src/els_code_navigation.erl
index 988b8365f..d32eb324e 100644
--- a/apps/els_lsp/src/els_code_navigation.erl
+++ b/apps/els_lsp/src/els_code_navigation.erl
@@ -36,7 +36,7 @@ goto_definition( Uri
[] -> {error, not_in_function_clause};
FunRanges ->
FunRange = lists:last(FunRanges),
- %% Find the first occurance of the variable in the function clause
+ %% Find the first occurrence of the variable in the function clause
[POI|_] = [P || P = #{range := Range, id := Id} <- VarPOIs,
els_range:compare(Range, VarRange),
els_range:compare(FunRange, Range),
@@ -61,7 +61,7 @@ goto_definition( Uri
Kind =:= implicit_fun;
Kind =:= export_entry ->
%% try to find local function first
- %% fall back to bif search if unsuccesful
+ %% fall back to bif search if unsuccessful
case find(Uri, function, {F, A}) of
{error, Error} ->
case is_imported_bif(Uri, F, A) of
diff --git a/apps/els_lsp/src/els_erlfmt_ast.erl b/apps/els_lsp/src/els_erlfmt_ast.erl
index 0d92759ed..6c95b1a8d 100644
--- a/apps/els_lsp/src/els_erlfmt_ast.erl
+++ b/apps/els_lsp/src/els_erlfmt_ast.erl
@@ -321,7 +321,7 @@ erlfmt_to_st(Node) ->
%% clauses of case/if/receive/try
erlfmt_clause_to_st(Clause);
%% Lists are represented as a `list` node instead of a chain of `cons`
- %% and `nil` nodes, similar to the `tuple` node. The last elemenent of
+ %% and `nil` nodes, similar to the `tuple` node. The last element of
%% the list can be a `cons` node representing explicit consing syntax.
{list, Pos, Elements} ->
%% a "cons" node here means 'H | T' in isolation
diff --git a/specs/runtime_node.md b/specs/runtime_node.md
index aee0b78e1..fed6516cf 100644
--- a/specs/runtime_node.md
+++ b/specs/runtime_node.md
@@ -46,7 +46,7 @@ Initially we envisage ones for
- "plain" project, i.e. just some random .erl files on the filesystem
somewhere
-Becase the protocol between the `Server` and the `Runtime Node` will be
+Because the protocol between the `Server` and the `Runtime Node` will be
standardised, and we will have out of the box implementations for popular
project build systems, it becomes easy for particular Erlang production sites to
adapt these nodes for use in their particular environments. This includes the
@@ -77,7 +77,7 @@ The `Runtime Node` should be able to respond to the following requests.
- provide xref information for a file
- run dialyzer
-### Current Unkowns / Questions
+### Current Unknowns / Questions
- Should the `Server` and `Runtime Node` share a file system? i.e. should a URI
be usable directly in any context for the current state of a file.
From 8ee4d63cdbce034f5443d5ed03f5b16e8cc38a2e Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Mon, 17 Jan 2022 17:48:38 +0100
Subject: [PATCH 007/239] [#1174] [emacs] Ensure keybinding is configured
before loading lsp-mode
---
misc/dotemacs | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/misc/dotemacs b/misc/dotemacs
index fbeec4db0..77ac8b0af 100644
--- a/misc/dotemacs
+++ b/misc/dotemacs
@@ -23,12 +23,13 @@
;; Install the official Erlang mode
(package-require 'erlang)
-;; Include the Language Server Protocol Clients
-(package-require 'lsp-mode)
-
;; Customize prefix for key-bindings
+;; This has to be done before lsp-mode itself is loaded
(setq lsp-keymap-prefix "C-l")
+;; Include the Language Server Protocol Clients
+(package-require 'lsp-mode)
+
;; Enable LSP for Erlang files
(add-hook 'erlang-mode-hook #'lsp)
From 54c6fced26b2d3e81c1b45e053b749f9bffbd727 Mon Sep 17 00:00:00 2001
From: Luke Bakken
Date: Tue, 18 Jan 2022 07:00:28 -0800
Subject: [PATCH 008/239] Update elvis_core
---
apps/els_core/src/els_text.erl | 2 --
rebar.config | 2 +-
rebar.lock | 6 +++---
3 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/apps/els_core/src/els_text.erl b/apps/els_core/src/els_text.erl
index 4fb4f8a48..7644df235 100644
--- a/apps/els_core/src/els_text.erl
+++ b/apps/els_core/src/els_text.erl
@@ -25,7 +25,6 @@
%% @doc Extract the N-th line from a text.
-spec line(text(), line_num()) -> text().
line(Text, LineNum) ->
- % LRB TODO Lines = binary:split(Text, <<"\n">>, [global]),
Lines = binary:split(Text, [<<"\r\n">>, <<"\n">>], [global]),
lists:nth(LineNum + 1, Lines).
@@ -108,7 +107,6 @@ lines_to_bin(Lines) ->
-spec bin_to_lines(text()) -> lines().
bin_to_lines(Text) ->
- % LRB TODO [Bin || Bin <- binary:split(Text, <<"\n">>, [global])].
[Bin || Bin <- binary:split(Text, [<<"\r\n">>, <<"\n">>], [global])].
-spec ensure_string(binary() | string()) -> string().
diff --git a/rebar.config b/rebar.config
index 35f768572..3aa65e0c4 100644
--- a/rebar.config
+++ b/rebar.config
@@ -10,7 +10,7 @@
, {redbug, "2.0.6"}
, {yamerl, "0.8.1"}
, {docsh, "0.7.2"}
- , {elvis_core, "1.1.1"}
+ , {elvis_core, "~> 1.3"}
, {rebar3_format, "0.8.2"}
%%, {erlfmt, "1.0.0"}
, {erlfmt, {git, "https://github.com/gomoripeti/erlfmt.git", {tag, "erlang_ls_parser_error_loc"}}} %% Temp until erlfmt PR 325 is merged (commit d4422d1)
diff --git a/rebar.lock b/rebar.lock
index 00b37cf61..7ce421b45 100644
--- a/rebar.lock
+++ b/rebar.lock
@@ -1,7 +1,7 @@
{"1.2.0",
[{<<"bucs">>,{pkg,<<"bucs">>,<<"1.0.16">>},1},
{<<"docsh">>,{pkg,<<"docsh">>,<<"0.7.2">>},0},
- {<<"elvis_core">>,{pkg,<<"elvis_core">>,<<"1.1.1">>},0},
+ {<<"elvis_core">>,{pkg,<<"elvis_core">>,<<"1.3.1">>},0},
{<<"ephemeral">>,{pkg,<<"ephemeral">>,<<"2.0.4">>},0},
{<<"erlfmt">>,
{git,"https://github.com/gomoripeti/erlfmt.git",
@@ -26,7 +26,7 @@
{pkg_hash,[
{<<"bucs">>, <<"D69A4CD6D1238CD1ADC5C95673DBDE0F8459A5DBB7D746516434D8C6D935E96F">>},
{<<"docsh">>, <<"F893D5317A0E14269DD7FE79CF95FB6B9BA23513DA0480EC6E77C73221CAE4F2">>},
- {<<"elvis_core">>, <<"EB7864CE4BC87D13FBF1C222A82230A4C3D327C21080B73E97FC6343C3A5264D">>},
+ {<<"elvis_core">>, <<"844C339300DD3E9F929A045932D25DC5C99B4603D47536E995198143169CDF26">>},
{<<"ephemeral">>, <<"B3E57886ADD5D90C82FE3880F5954978222A122CB8BAA123667401BBAAEC51D6">>},
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>},
{<<"jsx">>, <<"20A170ABD4335FC6DB24D5FAD1E5D677C55DADF83D1B20A8A33B5FE159892A39">>},
@@ -42,7 +42,7 @@
{pkg_hash_ext,[
{<<"bucs">>, <<"FF6A5C72A500AD7AEC1EE3BA164AE3C450EADEE898B0D151E1FACA18AC8D0D62">>},
{<<"docsh">>, <<"4E7DB461BB07540D2BC3D366B8513F0197712D0495BB85744F367D3815076134">>},
- {<<"elvis_core">>, <<"391C95BAA49F2718D7FB498BCF08046DDFC202CF0AAB63B2E439271485C9DC42">>},
+ {<<"elvis_core">>, <<"7A8890BF8185A3252CD4EBD826FE5F8AD6B93024EDF88576EB27AE9E5DC19D69">>},
{<<"ephemeral">>, <<"4B293D80F75F9C4575FF4B9C8E889A56802F40B018BF57E74F19644EFEE6C850">>},
{<<"getopt">>, <<"53E1AB83B9CEB65C9672D3E7A35B8092E9BDC9B3EE80721471A161C10C59959C">>},
{<<"jsx">>, <<"37BECA0435F5CA8A2F45F76A46211E76418FBEF80C36F0361C249FC75059DC6D">>},
From b168d3931500339ec8e8e537752249d2b40a3c6f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A9ter=20G=C3=B6m=C3=B6ri?=
Date: Tue, 11 Jan 2022 17:16:28 +0100
Subject: [PATCH 009/239] Fix parsing trailing comments in modules
erlfmt_parser returns a `raw_string` form for comments which are after the last
form in the file and not followed by any more form. It was not expected by
els_parser that a raw_string can contain no tokens (just comments and
whitespace). A file with no code just whitespace has the same result.
Fixes #1171
---
apps/els_lsp/src/els_parser.erl | 9 +++++++--
apps/els_lsp/test/els_parser_SUITE.erl | 23 +++++++++++++++++++++++
2 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/apps/els_lsp/src/els_parser.erl b/apps/els_lsp/src/els_parser.erl
index 479e89a80..c09ee71dc 100644
--- a/apps/els_lsp/src/els_parser.erl
+++ b/apps/els_lsp/src/els_parser.erl
@@ -99,12 +99,17 @@ parse_form(Form) ->
scan_text(Text, StartLoc) ->
PaddedText = pad_text(Text, StartLoc),
{ok, Tokens, _Comments, _Cont} = erlfmt_scan:string_node(PaddedText),
- ensure_dot(Tokens).
+ case Tokens of
+ [] -> [];
+ _ -> ensure_dot(Tokens)
+ end.
-spec parse_incomplete_tokens([erlfmt_scan:token()])
-> {ok, erlfmt_parse:abstract_node()} | error.
parse_incomplete_tokens([{dot, _}]) ->
error;
+parse_incomplete_tokens([]) ->
+ error;
parse_incomplete_tokens(Tokens) ->
case erlfmt_parse:parse_node(Tokens) of
{ok, Form} ->
@@ -139,7 +144,7 @@ pad_text(Text, {StartLine, StartColumn}) ->
++ lists:duplicate(StartColumn - 1, $\s)
++ Text.
--spec ensure_dot([erlfmt_scan:token()]) -> [erlfmt_scan:token()].
+-spec ensure_dot([erlfmt_scan:token(), ...]) -> [erlfmt_scan:token(), ...].
ensure_dot(Tokens) ->
case lists:last(Tokens) of
{dot, _} ->
diff --git a/apps/els_lsp/test/els_parser_SUITE.erl b/apps/els_lsp/test/els_parser_SUITE.erl
index c45268565..dd83609f5 100644
--- a/apps/els_lsp/test/els_parser_SUITE.erl
+++ b/apps/els_lsp/test/els_parser_SUITE.erl
@@ -11,6 +11,7 @@
, parse_invalid_code/1
, parse_incomplete_function/1
, parse_incomplete_spec/1
+ , parse_no_tokens/1
, underscore_macro/1
, specs_with_record/1
, types_with_record/1
@@ -104,6 +105,28 @@ parse_incomplete_spec(_Config) ->
?assertMatch([#{id := aa}], parse_find_pois(Text, atom)),
ok.
+%% Issue #1171
+parse_no_tokens(_Config) ->
+ %% scanning text containing only whitespaces returns an empty list of tokens,
+ %% which used to crash els_parser
+ Text1 = " \n ",
+ {ok, []} = els_parser:parse(Text1),
+ %% `els_parser:parse' actually catches the exception and only prints a warning
+ %% log. In order to make sure there is no crash, we need to call an internal
+ %% debug function that would really crash and make the test case fail
+ error = els_parser:parse_incomplete_text(Text1, {1, 1}),
+
+ %% same for text only containing comments
+ Text2 = "%% only a comment",
+ {ok, []} = els_parser:parse(Text2),
+ error = els_parser:parse_incomplete_text(Text2, {1, 1}),
+
+ %% trailing comment, also used to crash els_parser
+ Text3 =
+ "-module(m).\n"
+ "%% trailing comment",
+ {ok, [#{id := m, kind := module}]} = els_parser:parse(Text3).
+
-spec underscore_macro(config()) -> ok.
underscore_macro(_Config) ->
?assertMatch({ok, [#{id := {'_', 1}, kind := define} | _]},
From d4c21c6718a8f12c363c3a493e0f684698eb318b Mon Sep 17 00:00:00 2001
From: Hakan Nilsson
Date: Thu, 27 Jan 2022 12:55:07 +0100
Subject: [PATCH 010/239] Augment local config onto global config
---
apps/els_core/src/els_config.erl | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/apps/els_core/src/els_config.erl b/apps/els_core/src/els_config.erl
index 5a6c6257d..b8fe0f39a 100644
--- a/apps/els_core/src/els_config.erl
+++ b/apps/els_core/src/els_config.erl
@@ -89,9 +89,18 @@ initialize(RootUri, Capabilities, InitOptions) ->
-spec initialize(uri(), map(), map(), boolean()) -> ok.
initialize(RootUri, Capabilities, InitOptions, ReportMissingConfig) ->
RootPath = els_utils:to_list(els_uri:path(RootUri)),
- Config = consult_config(
- config_paths(RootPath, InitOptions), ReportMissingConfig),
- do_initialize(RootUri, Capabilities, InitOptions, Config).
+ ConfigPaths = config_paths(RootPath, InitOptions),
+ {GlobalConfigPath, GlobalConfig} = consult_config(global_config_paths(),
+ false),
+ {LocalConfigPath, LocalConfig} = consult_config(ConfigPaths,
+ ReportMissingConfig),
+ ConfigPath = case LocalConfigPath of
+ undefined -> GlobalConfigPath;
+ _ -> LocalConfigPath
+ end,
+ %% Augment Config onto GlobalConfig
+ Config = maps:merge(GlobalConfig, LocalConfig),
+ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}).
-spec do_initialize(uri(), map(), map(), {undefined|path(), map()}) -> ok.
do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
@@ -216,10 +225,14 @@ config_paths(RootPath, _Config) ->
-spec default_config_paths(path()) -> [path()].
default_config_paths(RootPath) ->
- GlobalConfigDir = filename:basedir(user_config, "erlang_ls"),
[ filename:join([RootPath, ?DEFAULT_CONFIG_FILE])
, filename:join([RootPath, ?ALTERNATIVE_CONFIG_FILE])
- , filename:join([GlobalConfigDir, ?DEFAULT_CONFIG_FILE])
+ ].
+
+-spec global_config_paths() -> [path()].
+global_config_paths() ->
+ GlobalConfigDir = filename:basedir(user_config, "erlang_ls"),
+ [ filename:join([GlobalConfigDir, ?DEFAULT_CONFIG_FILE])
, filename:join([GlobalConfigDir, ?ALTERNATIVE_CONFIG_FILE])
].
From db82b7c2f50f8950915d427ba682665b40401a46 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Sun, 30 Jan 2022 17:00:38 +0100
Subject: [PATCH 011/239] Fix issues with rename variable involving specs
(#1183)
---
.../code_navigation/src/rename_variable.erl | 22 ++++-
apps/els_lsp/src/els_rename_provider.erl | 96 ++++++++++++++++---
apps/els_lsp/test/els_rename_SUITE.erl | 50 +++++++++-
3 files changed, 151 insertions(+), 17 deletions(-)
diff --git a/apps/els_lsp/priv/code_navigation/src/rename_variable.erl b/apps/els_lsp/priv/code_navigation/src/rename_variable.erl
index ef6d1c9b7..6b743049e 100644
--- a/apps/els_lsp/priv/code_navigation/src/rename_variable.erl
+++ b/apps/els_lsp/priv/code_navigation/src/rename_variable.erl
@@ -1,5 +1,5 @@
-module(rename_variable).
-
+-callback name(Var) -> Var.
foo(Var) ->
Var < 0;
foo(Var) ->
@@ -10,3 +10,23 @@ foo(_Var) ->
bar(Var) ->
Var.
+
+-spec baz(Var) -> Var
+ when Var :: atom().
+baz(Var) ->
+ Var.
+
+-record(foo, {a :: Var,
+ b :: [Var]}).
+%% BUG: `Var' in MACRO(`Var') is not considered a variable POI
+-define(MACRO(Var), Var + Var).
+
+-type type(Var) :: Var.
+-opaque opaque(Var) :: Var.
+
+foo(Var) ->
+ Var.
+
+-if(Var == Var).
+
+-endif.
diff --git a/apps/els_lsp/src/els_rename_provider.erl b/apps/els_lsp/src/els_rename_provider.erl
index 304f7d533..9d91c6439 100644
--- a/apps/els_lsp/src/els_rename_provider.erl
+++ b/apps/els_lsp/src/els_rename_provider.erl
@@ -132,7 +132,7 @@ changes(Uri, #{kind := variable, id := VarId, range := VarRange}, NewName) ->
%% Rename variable in function clause scope
case els_utils:lookup_document(Uri) of
{ok, Document} ->
- FunRange = function_clause_range(VarRange, Document),
+ FunRange = variable_scope_range(VarRange, Document),
Changes = [#{range => editable_range(POI), newText => NewName} ||
POI <- els_dt_document:pois(Document, [variable]),
maps:get(id, POI) =:= VarId,
@@ -220,20 +220,86 @@ new_name(#{kind := record_expr}, NewName) ->
new_name(_, NewName) ->
NewName.
--spec function_clause_range(poi_range(), els_dt_document:item()) -> poi_range().
-function_clause_range(VarRange, Document) ->
- FunPOIs = els_poi:sort(els_dt_document:pois(Document, [function_clause])),
- %% Find beginning of first function clause before VarRange
- From = case [R || #{range := R} <- FunPOIs, els_range:compare(R, VarRange)] of
- [] -> {0, 0}; % Beginning of document
- FunRanges -> maps:get(from, lists:last(FunRanges))
- end,
- %% Find beginning of first function clause after VarRange
- To = case [R || #{range := R} <- FunPOIs, els_range:compare(VarRange, R)] of
- [] -> {999999999, 999999999}; % End of document
- [#{from := End}|_] -> End
- end,
- #{from => From, to => To}.
+-spec variable_scope_range(poi_range(), els_dt_document:item()) -> poi_range().
+variable_scope_range(VarRange, Document) ->
+ Attributes = [spec, callback, define, record, type_definition],
+ AttrPOIs = els_dt_document:pois(Document, Attributes),
+ case pois_match(AttrPOIs, VarRange) of
+ [#{range := Range}] ->
+ %% Inside attribute, simple.
+ Range;
+ [] ->
+ %% If variable is not inside an attribute we need to figure out where the
+ %% scope of the variable begins and ends.
+ %% The scope of variables inside functions are limited by function clauses
+ %% The scope of variables outside of function are limited by top-level
+ %% POIs (attributes and functions) before and after.
+ FunPOIs = els_poi:sort(els_dt_document:pois(Document, [function])),
+ POIs = els_poi:sort(els_dt_document:pois(Document, [ function_clause
+ | Attributes
+ ])),
+ CurrentFunRange = case pois_match(FunPOIs, VarRange) of
+ [] -> undefined;
+ [POI] -> range(POI)
+ end,
+ IsInsideFunction = CurrentFunRange /= undefined,
+ BeforeFunRanges = [range(POI) || POI <- pois_before(FunPOIs, VarRange)],
+ %% Find where scope should begin
+ From =
+ case [R || #{range := R} <- pois_before(POIs, VarRange)] of
+ [] ->
+ %% No POIs before
+ {0, 0};
+ [BeforeRange|_] when IsInsideFunction ->
+ %% Inside function, use beginning of closest function clause
+ maps:get(from, BeforeRange);
+ [BeforeRange|_] when BeforeFunRanges == [] ->
+ %% No function before, use end of closest POI
+ maps:get(to, BeforeRange);
+ [BeforeRange|_] ->
+ %% Use end of closest POI, including functions.
+ max(maps:get(to, hd(BeforeFunRanges)),
+ maps:get(to, BeforeRange))
+ end,
+ %% Find when scope should end
+ To =
+ case [R || #{range := R} <- pois_after(POIs, VarRange)] of
+ [] when IsInsideFunction ->
+ %% No POIs after, use end of function
+ maps:get(to, CurrentFunRange);
+ [] ->
+ %% No POIs after, use end of document
+ {999999999, 999999999};
+ [AfterRange|_] when IsInsideFunction ->
+ %% Inside function, use closest of end of function *OR*
+ %% beginning of the next function clause
+ min(maps:get(to, CurrentFunRange), maps:get(from, AfterRange));
+ [AfterRange|_] ->
+ %% Use beginning of next POI
+ maps:get(from, AfterRange)
+ end,
+ #{from => From, to => To}
+ end.
+
+-spec pois_before([poi()], poi_range()) -> [poi()].
+pois_before(POIs, VarRange) ->
+ %% Reverse since we are typically interested in the last POI
+ lists:reverse([POI || POI <- POIs, els_range:compare(range(POI), VarRange)]).
+
+-spec pois_after([poi()], poi_range()) -> [poi()].
+pois_after(POIs, VarRange) ->
+ [POI || POI <- POIs, els_range:compare(VarRange, range(POI))].
+
+-spec pois_match([poi()], poi_range()) -> [poi()].
+pois_match(POIs, Range) ->
+ [POI || POI <- POIs, els_range:in(Range, range(POI))].
+
+-spec range(poi()) -> poi_range().
+range(#{kind := function, data := #{wrapping_range := Range}}) ->
+ Range;
+range(#{range := Range}) ->
+ Range.
+
-spec convert_references_to_pois([els_dt_references:item()], [poi_kind()]) ->
[{uri(), poi()}].
diff --git a/apps/els_lsp/test/els_rename_SUITE.erl b/apps/els_lsp/test/els_rename_SUITE.erl
index de530aaba..0fab1c9cd 100644
--- a/apps/els_lsp/test/els_rename_SUITE.erl
+++ b/apps/els_lsp/test/els_rename_SUITE.erl
@@ -117,6 +117,7 @@ rename_variable(Config) ->
Uri = ?config(rename_variable_uri, Config),
UriAtom = binary_to_atom(Uri, utf8),
NewName = <<"NewAwesomeName">>,
+ %%
#{result := Result1} = els_client:document_rename(Uri, 3, 3, NewName),
Expected1 = #{changes => #{UriAtom => [ change(NewName, {3, 2}, {3, 5})
, change(NewName, {2, 4}, {2, 7})
@@ -135,10 +136,57 @@ rename_variable(Config) ->
Expected4 = #{changes => #{UriAtom => [ change(NewName, {11, 2}, {11, 5})
, change(NewName, {10, 4}, {10, 7})
]}},
+ %% Spec
+ #{result := Result5} = els_client:document_rename(Uri, 13, 10, NewName),
+ Expected5 = #{changes => #{UriAtom => [ change(NewName, {14, 15}, {14, 18})
+ , change(NewName, {13, 18}, {13, 21})
+ , change(NewName, {13, 10}, {13, 13})
+ ]}},
+ %% Record
+ #{result := Result6} = els_client:document_rename(Uri, 18, 19, NewName),
+ Expected6 = #{changes => #{UriAtom => [ change(NewName, {19, 20}, {19, 23})
+ , change(NewName, {18, 19}, {18, 22})
+ ]}},
+ %% Macro
+ #{result := Result7} = els_client:document_rename(Uri, 21, 20, NewName),
+ Expected7 = #{changes => #{UriAtom => [ change(NewName, {21, 26}, {21, 29})
+ , change(NewName, {21, 20}, {21, 23})
+ %% This should also update, but doesn't
+ %% due to bug where Var in MACRO(Var)
+ %% isn't considered a variable POI
+ %% change(NewName, {22, 14}, {22, 17})
+ ]}},
+ %% Type
+ #{result := Result8} = els_client:document_rename(Uri, 23, 11, NewName),
+ Expected8 = #{changes => #{UriAtom => [ change(NewName, {23, 11}, {23, 14})
+ , change(NewName, {23, 19}, {23, 22})
+ ]}},
+ %% Opaque
+ #{result := Result9} = els_client:document_rename(Uri, 24, 15, NewName),
+ Expected9 = #{changes => #{UriAtom => [ change(NewName, {24, 15}, {24, 18})
+ , change(NewName, {24, 23}, {24, 26})
+ ]}},
+ %% Callback
+ #{result := Result10} = els_client:document_rename(Uri, 1, 15, NewName),
+ Expected10 = #{changes => #{UriAtom => [ change(NewName, {1, 23}, {1, 26})
+ , change(NewName, {1, 15}, {1, 18})
+ ]}},
+ %% If
+ #{result := Result11} = els_client:document_rename(Uri, 29, 4, NewName),
+ Expected11 = #{changes => #{UriAtom => [ change(NewName, {29, 11}, {29, 14})
+ , change(NewName, {29, 4}, {29, 7})
+ ]}},
assert_changes(Expected1, Result1),
assert_changes(Expected2, Result2),
assert_changes(Expected3, Result3),
- assert_changes(Expected4, Result4).
+ assert_changes(Expected4, Result4),
+ assert_changes(Expected5, Result5),
+ assert_changes(Expected6, Result6),
+ assert_changes(Expected7, Result7),
+ assert_changes(Expected8, Result8),
+ assert_changes(Expected9, Result9),
+ assert_changes(Expected10, Result10),
+ assert_changes(Expected11, Result11).
-spec rename_macro(config()) -> ok.
rename_macro(Config) ->
From 0fa9df8bbe3396d7eae8bdce6dac2317e41b89d8 Mon Sep 17 00:00:00 2001
From: Kwik Kwik <4840430+sirikid@users.noreply.github.com>
Date: Sun, 30 Jan 2022 16:01:11 +0000
Subject: [PATCH 012/239] Completion without snippets (#1170)
---
apps/els_core/src/els_client.erl | 5 ++++-
apps/els_lsp/src/els_completion_provider.erl | 22 +++++++++++++++++---
2 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/apps/els_core/src/els_client.erl b/apps/els_core/src/els_client.erl
index 345322a14..f88506c7f 100644
--- a/apps/els_core/src/els_client.erl
+++ b/apps/els_core/src/els_client.erl
@@ -449,7 +449,10 @@ request_params({completionitem_resolve, CompletionItem}) ->
request_params({initialize, {RootUri, InitOptions}}) ->
ContentFormat = [ ?MARKDOWN , ?PLAINTEXT ],
TextDocument = #{ <<"completion">> =>
- #{ <<"contextSupport">> => 'true' }
+ #{ <<"contextSupport">> => 'true'
+ , <<"completionItem">> =>
+ #{ <<"snippetSupport">> => 'true' }
+ }
, <<"hover">> =>
#{ <<"contentFormat">> => ContentFormat }
},
diff --git a/apps/els_lsp/src/els_completion_provider.erl b/apps/els_lsp/src/els_completion_provider.erl
index 30c40ae12..a8d5c2650 100644
--- a/apps/els_lsp/src/els_completion_provider.erl
+++ b/apps/els_lsp/src/els_completion_provider.erl
@@ -734,12 +734,28 @@ snippet_macro(Name, none) ->
-spec snippet_args(binary(), [{integer(), string()}]) -> binary().
snippet_args(Name, Args0) ->
- Args = [ ["${", integer_to_list(N), ":", A, "}"]
- || {N, A} <- Args0
- ],
+ Args =
+ case snippet_support() of
+ false ->
+ [A || {_N, A} <- Args0];
+ true ->
+ [["${", integer_to_list(N), ":", A, "}"] || {N, A} <- Args0]
+ end,
Snippet = [Name, "(", string:join(Args, ", "), ")"],
els_utils:to_binary(Snippet).
+-spec snippet_support() -> boolean().
+snippet_support() ->
+ case els_config:get(capabilities) of
+ #{<<"textDocument">> :=
+ #{<<"completion">> :=
+ #{<<"completionItem">> :=
+ #{<<"snippetSupport">> := SnippetSupport}}}} ->
+ SnippetSupport;
+ _ ->
+ false
+ end.
+
-spec is_in(els_dt_document:item(), line(), column(), [poi_kind()]) ->
boolean().
is_in(Document, Line, Column, POIKinds) ->
From 2d4b4e05dc1cc417de9fe67450a928f1b4229777 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Sun, 30 Jan 2022 17:02:07 +0100
Subject: [PATCH 013/239] Ensure that default node name is valid (#1182)
This fixes an issue where initialization of els distribution would fail
due to invalid node name if the project directory contains characters
that are not valid in an erlang node name.
---
apps/els_core/src/els_config_runtime.erl | 3 ++-
apps/els_core/src/els_distribution_server.erl | 21 ++++++++++++++++++-
2 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/apps/els_core/src/els_config_runtime.erl b/apps/els_core/src/els_config_runtime.erl
index e581599d4..786bd99d0 100644
--- a/apps/els_core/src/els_config_runtime.erl
+++ b/apps/els_core/src/els_config_runtime.erl
@@ -64,7 +64,8 @@ get_cookie() ->
default_node_name() ->
RootUri = els_config:get(root_uri),
{ok, Hostname} = inet:gethostname(),
- NodeName = els_utils:to_list(filename:basename(els_uri:path(RootUri))),
+ NodeName = els_distribution_server:normalize_node_name(
+ filename:basename(els_uri:path(RootUri))),
NodeName ++ "@" ++ Hostname.
-spec default_otp_path() -> string().
diff --git a/apps/els_core/src/els_distribution_server.erl b/apps/els_core/src/els_distribution_server.erl
index 2901e2a94..1abfd4b8c 100644
--- a/apps/els_core/src/els_distribution_server.erl
+++ b/apps/els_core/src/els_distribution_server.erl
@@ -18,6 +18,7 @@
, rpc_call/4
, node_name/2
, node_name/3
+ , normalize_node_name/1
]).
%%==============================================================================
@@ -205,8 +206,10 @@ ensure_epmd() ->
0 = els_utils:cmd("epmd", ["-daemon"]),
ok.
+
-spec node_name(binary(), binary()) -> atom().
-node_name(Prefix, Name) ->
+node_name(Prefix, Name0) ->
+ Name = normalize_node_name(Name0),
Int = erlang:phash2(erlang:timestamp()),
Id = lists:flatten(io_lib:format("~s_~s_~p", [Prefix, Name, Int])),
{ok, HostName} = inet:gethostname(),
@@ -219,8 +222,24 @@ node_name(Id, HostName, longnames) ->
Domain = proplists:get_value(domain, inet:get_rc(), ""),
list_to_atom(Id ++ "@" ++ HostName ++ "." ++ Domain).
+-spec normalize_node_name(string() | binary()) -> string().
+normalize_node_name(Name) ->
+ %% Replace invalid characters with _
+ re:replace(Name, "[^0-9A-Za-z_\\-]", "_", [global, {return, list}]).
+
-spec connect_node(node(), hidden | not_hidden) -> boolean() | ignored.
connect_node(Node, not_hidden) ->
net_kernel:connect_node(Node);
connect_node(Node, hidden) ->
net_kernel:hidden_connect_node(Node).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+default_node_name_test_() ->
+ [ ?_assertEqual("foobar", normalize_node_name("foobar"))
+ , ?_assertEqual("foo_bar", normalize_node_name("foo.bar"))
+ , ?_assertEqual("_", normalize_node_name("&"))
+ ].
+
+-endif.
From 4d6b1dbd2d89a02c3570715cdc0c78378d66fc50 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Tue, 1 Feb 2022 18:21:23 +0100
Subject: [PATCH 014/239] Fix parsing of define arguments (#1186)
---
.../priv/code_navigation/src/rename_variable.erl | 2 +-
apps/els_lsp/src/els_parser.erl | 8 ++++++--
apps/els_lsp/test/els_parser_SUITE.erl | 11 +++++++++++
apps/els_lsp/test/els_rename_SUITE.erl | 5 +----
4 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/apps/els_lsp/priv/code_navigation/src/rename_variable.erl b/apps/els_lsp/priv/code_navigation/src/rename_variable.erl
index 6b743049e..f7ee13403 100644
--- a/apps/els_lsp/priv/code_navigation/src/rename_variable.erl
+++ b/apps/els_lsp/priv/code_navigation/src/rename_variable.erl
@@ -18,7 +18,7 @@ baz(Var) ->
-record(foo, {a :: Var,
b :: [Var]}).
-%% BUG: `Var' in MACRO(`Var') is not considered a variable POI
+
-define(MACRO(Var), Var + Var).
-type type(Var) :: Var.
diff --git a/apps/els_lsp/src/els_parser.erl b/apps/els_lsp/src/els_parser.erl
index c09ee71dc..c3943f8df 100644
--- a/apps/els_lsp/src/els_parser.erl
+++ b/apps/els_lsp/src/els_parser.erl
@@ -969,10 +969,14 @@ attribute_subtrees(AttrName, [Exports])
when AttrName =:= export;
AttrName =:= export_type ->
[ skip_function_entries(Exports) ];
-attribute_subtrees(define, [_Name | Definition]) ->
+attribute_subtrees(define, [Name | Definition]) ->
%% The definition can contain commas, in which case it will look like as if
%% the attribute would have more than two arguments. Eg.: `-define(M, a, b).'
- [Definition];
+ Args = case erl_syntax:type(Name) of
+ application -> erl_syntax:application_arguments(Name);
+ _ -> []
+ end,
+ [Args, Definition];
attribute_subtrees(AttrName, _)
when AttrName =:= include;
AttrName =:= include_lib ->
diff --git a/apps/els_lsp/test/els_parser_SUITE.erl b/apps/els_lsp/test/els_parser_SUITE.erl
index dd83609f5..7d0c81c70 100644
--- a/apps/els_lsp/test/els_parser_SUITE.erl
+++ b/apps/els_lsp/test/els_parser_SUITE.erl
@@ -12,6 +12,7 @@
, parse_incomplete_function/1
, parse_incomplete_spec/1
, parse_no_tokens/1
+ , define/1
, underscore_macro/1
, specs_with_record/1
, types_with_record/1
@@ -127,6 +128,16 @@ parse_no_tokens(_Config) ->
"%% trailing comment",
{ok, [#{id := m, kind := module}]} = els_parser:parse(Text3).
+-spec define(config()) -> ok.
+define(_Config) ->
+ ?assertMatch({ok, [ #{id := {'MACRO', 2}, kind := define}
+ , #{id := 'B', kind := variable}
+ , #{id := 'A', kind := variable}
+ , #{id := 'B', kind := variable}
+ , #{id := 'A', kind := variable}
+ ]},
+ els_parser:parse("-define(MACRO(A, B), A:B()).")).
+
-spec underscore_macro(config()) -> ok.
underscore_macro(_Config) ->
?assertMatch({ok, [#{id := {'_', 1}, kind := define} | _]},
diff --git a/apps/els_lsp/test/els_rename_SUITE.erl b/apps/els_lsp/test/els_rename_SUITE.erl
index 0fab1c9cd..1bc13ac35 100644
--- a/apps/els_lsp/test/els_rename_SUITE.erl
+++ b/apps/els_lsp/test/els_rename_SUITE.erl
@@ -151,10 +151,7 @@ rename_variable(Config) ->
#{result := Result7} = els_client:document_rename(Uri, 21, 20, NewName),
Expected7 = #{changes => #{UriAtom => [ change(NewName, {21, 26}, {21, 29})
, change(NewName, {21, 20}, {21, 23})
- %% This should also update, but doesn't
- %% due to bug where Var in MACRO(Var)
- %% isn't considered a variable POI
- %% change(NewName, {22, 14}, {22, 17})
+ , change(NewName, {21, 14}, {21, 17})
]}},
%% Type
#{result := Result8} = els_client:document_rename(Uri, 23, 11, NewName),
From 3e7ef672917158e9fac2c49d0f2079b501ad3d40 Mon Sep 17 00:00:00 2001
From: Alejandro Sanchez
Date: Tue, 1 Feb 2022 20:33:57 +0100
Subject: [PATCH 015/239] Allow installing into custom directory (#1188)
---
Makefile | 8 ++++++--
README.md | 4 ++++
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/Makefile b/Makefile
index 349d3ce59..6f99f95d6 100644
--- a/Makefile
+++ b/Makefile
@@ -1,14 +1,18 @@
.PHONY: all
+PREFIX = '/usr/local'
+
all:
@ echo "Building escript..."
@ rebar3 escriptize
@ rebar3 as dap escriptize
+.PHONY: install
install: all
@ echo "Installing escript..."
- @ cp _build/default/bin/erlang_ls /usr/local/bin
- @ cp _build/dap/bin/els_dap /usr/local/bin
+ @ mkdir -p '${PREFIX}/bin'
+ @ cp _build/default/bin/erlang_ls ${PREFIX}/bin
+ @ cp _build/dap/bin/els_dap ${PREFIX}/bin
.PHONY: clean
clean:
diff --git a/README.md b/README.md
index 7c314c25b..09c64eb78 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,10 @@ To install the produced `erlang_ls` escript in `/usr/local/bin`:
make install
+To install to a different directory set the `PREFIX` environment variable:
+
+ PREFIX=/path/to/directory make -e install
+
## Command-line Arguments
These are the command-line arguments that can be provided to the
From 3e9902a874a34fd5150b5798c177d4fe582ff61c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Fri, 4 Feb 2022 12:01:45 +0100
Subject: [PATCH 016/239] Support renaming function when standing on spec
(#1190)
---
apps/els_lsp/src/els_rename_provider.erl | 2 ++
apps/els_lsp/test/els_rename_SUITE.erl | 2 ++
2 files changed, 4 insertions(+)
diff --git a/apps/els_lsp/src/els_rename_provider.erl b/apps/els_lsp/src/els_rename_provider.erl
index 9d91c6439..f7586e047 100644
--- a/apps/els_lsp/src/els_rename_provider.erl
+++ b/apps/els_lsp/src/els_rename_provider.erl
@@ -49,6 +49,8 @@ workspace_edits(_Uri, [], _NewName) ->
workspace_edits(Uri, [#{kind := function_clause} = POI| _], NewName) ->
#{id := {F, A, _}} = POI,
#{changes => changes(Uri, POI#{kind => function, id => {F, A}}, NewName)};
+workspace_edits(Uri, [#{kind := spec} = POI| _], NewName) ->
+ #{changes => changes(Uri, POI#{kind => function}, NewName)};
workspace_edits(Uri, [#{kind := Kind} = POI| _], NewName)
when Kind =:= define;
Kind =:= record;
diff --git a/apps/els_lsp/test/els_rename_SUITE.erl b/apps/els_lsp/test/els_rename_SUITE.erl
index 1bc13ac35..301ae691b 100644
--- a/apps/els_lsp/test/els_rename_SUITE.erl
+++ b/apps/els_lsp/test/els_rename_SUITE.erl
@@ -237,6 +237,8 @@ rename_function(Config) ->
#{result := Result} = els_client:document_rename(Uri, 1, 9, NewName),
%% Import entry
#{result := Result} = els_client:document_rename(ImportUri, 2, 26, NewName),
+ %% Spec
+ #{result := Result} = els_client:document_rename(Uri, 3, 2, NewName),
Expected = #{changes =>
#{binary_to_atom(Uri, utf8) =>
[ change(NewName, {12, 23}, {12, 26})
From 5ee868cf6f53a22a1c83969a033a35b7f584668f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Fri, 4 Feb 2022 12:13:21 +0100
Subject: [PATCH 017/239] Improve goto definition for variables (#1187)
* Use same scoping rules as variable renaming
* No longer limited to only variables inside functions
* Add support for finding variable references
---
apps/els_lsp/src/els_code_navigation.erl | 38 ++++----
apps/els_lsp/src/els_references_provider.erl | 3 +
apps/els_lsp/src/els_rename_provider.erl | 98 +-------------------
apps/els_lsp/src/els_scope.erl | 84 +++++++++++++++++
apps/els_lsp/test/els_definition_SUITE.erl | 5 +
apps/els_lsp/test/els_rename_SUITE.erl | 2 +-
6 files changed, 113 insertions(+), 117 deletions(-)
diff --git a/apps/els_lsp/src/els_code_navigation.erl b/apps/els_lsp/src/els_code_navigation.erl
index d32eb324e..15ddb5cb5 100644
--- a/apps/els_lsp/src/els_code_navigation.erl
+++ b/apps/els_lsp/src/els_code_navigation.erl
@@ -8,7 +8,9 @@
%%==============================================================================
%% API
--export([ goto_definition/2 ]).
+-export([ goto_definition/2
+ , find_in_scope/2
+ ]).
%%==============================================================================
%% Includes
@@ -23,28 +25,13 @@
-spec goto_definition(uri(), poi()) ->
{ok, uri(), poi()} | {error, any()}.
goto_definition( Uri
- , Var = #{kind := variable, id := VarId, range := VarRange}
+ , Var = #{kind := variable}
) ->
%% This will naively try to find the definition of a variable by finding the
- %% first occurrence of the variable in the function clause.
- {ok, Document} = els_utils:lookup_document(Uri),
- FunPOIs = els_poi:sort(els_dt_document:pois(Document, [function_clause])),
- VarPOIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
- %% Find the function clause we are in
- case [Range || #{range := Range} <- FunPOIs,
- els_range:compare(Range, VarRange)] of
- [] -> {error, not_in_function_clause};
- FunRanges ->
- FunRange = lists:last(FunRanges),
- %% Find the first occurrence of the variable in the function clause
- [POI|_] = [P || P = #{range := Range, id := Id} <- VarPOIs,
- els_range:compare(Range, VarRange),
- els_range:compare(FunRange, Range),
- Id =:= VarId],
- case POI of
- Var -> {error, already_at_definition};
- POI -> {ok, Uri, POI}
- end
+ %% first occurrence of the variable in variable scope.
+ case find_in_scope(Uri, Var) of
+ [Var|_] -> {error, already_at_definition};
+ [POI|_] -> {ok, Uri, POI}
end;
goto_definition( _Uri
, #{ kind := Kind, id := {M, F, A} }
@@ -213,3 +200,12 @@ maybe_imported(Document, function, {F, A}) ->
end;
maybe_imported(_Document, _Kind, _Data) ->
{error, not_found}.
+
+-spec find_in_scope(uri(), poi()) -> [poi()].
+find_in_scope(Uri, #{kind := variable, id := VarId, range := VarRange}) ->
+ {ok, Document} = els_utils:lookup_document(Uri),
+ VarPOIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
+ ScopeRange = els_scope:variable_scope_range(VarRange, Document),
+ [POI || #{range := Range, id := Id} = POI <- VarPOIs,
+ els_range:in(Range, ScopeRange),
+ Id =:= VarId].
diff --git a/apps/els_lsp/src/els_references_provider.erl b/apps/els_lsp/src/els_references_provider.erl
index 7f81b8f6e..8faa40c3a 100644
--- a/apps/els_lsp/src/els_references_provider.erl
+++ b/apps/els_lsp/src/els_references_provider.erl
@@ -67,6 +67,9 @@ find_references(Uri, #{ kind := Kind
{M, F, A} -> {M, F, A}
end,
find_references_for_id(Kind, Key);
+find_references(Uri, #{kind := variable} = Var) ->
+ POIs = els_code_navigation:find_in_scope(Uri, Var),
+ [location(Uri, Range) || #{range := Range} = POI <- POIs, POI =/= Var];
find_references(Uri, #{ kind := Kind
, id := Id
}) when Kind =:= function_clause ->
diff --git a/apps/els_lsp/src/els_rename_provider.erl b/apps/els_lsp/src/els_rename_provider.erl
index f7586e047..7db3af35b 100644
--- a/apps/els_lsp/src/els_rename_provider.erl
+++ b/apps/els_lsp/src/els_rename_provider.erl
@@ -130,20 +130,9 @@ editable_range(#{kind := _Kind, range := Range}) ->
els_protocol:range(Range).
-spec changes(uri(), poi(), binary()) -> #{uri() => [text_edit()]} | null.
-changes(Uri, #{kind := variable, id := VarId, range := VarRange}, NewName) ->
- %% Rename variable in function clause scope
- case els_utils:lookup_document(Uri) of
- {ok, Document} ->
- FunRange = variable_scope_range(VarRange, Document),
- Changes = [#{range => editable_range(POI), newText => NewName} ||
- POI <- els_dt_document:pois(Document, [variable]),
- maps:get(id, POI) =:= VarId,
- els_range:in(maps:get(range, POI), FunRange)
- ],
- #{Uri => Changes};
- {error, _} ->
- null
- end;
+changes(Uri, #{kind := variable} = Var, NewName) ->
+ POIs = els_code_navigation:find_in_scope(Uri, Var),
+ #{Uri => [#{range => editable_range(P), newText => NewName} || P <- POIs]};
changes(Uri, #{kind := type_definition, id := {Name, A}}, NewName) ->
?LOG_INFO("Renaming type ~p/~p to ~s", [Name, A, NewName]),
{ok, Doc} = els_utils:lookup_document(Uri),
@@ -222,87 +211,6 @@ new_name(#{kind := record_expr}, NewName) ->
new_name(_, NewName) ->
NewName.
--spec variable_scope_range(poi_range(), els_dt_document:item()) -> poi_range().
-variable_scope_range(VarRange, Document) ->
- Attributes = [spec, callback, define, record, type_definition],
- AttrPOIs = els_dt_document:pois(Document, Attributes),
- case pois_match(AttrPOIs, VarRange) of
- [#{range := Range}] ->
- %% Inside attribute, simple.
- Range;
- [] ->
- %% If variable is not inside an attribute we need to figure out where the
- %% scope of the variable begins and ends.
- %% The scope of variables inside functions are limited by function clauses
- %% The scope of variables outside of function are limited by top-level
- %% POIs (attributes and functions) before and after.
- FunPOIs = els_poi:sort(els_dt_document:pois(Document, [function])),
- POIs = els_poi:sort(els_dt_document:pois(Document, [ function_clause
- | Attributes
- ])),
- CurrentFunRange = case pois_match(FunPOIs, VarRange) of
- [] -> undefined;
- [POI] -> range(POI)
- end,
- IsInsideFunction = CurrentFunRange /= undefined,
- BeforeFunRanges = [range(POI) || POI <- pois_before(FunPOIs, VarRange)],
- %% Find where scope should begin
- From =
- case [R || #{range := R} <- pois_before(POIs, VarRange)] of
- [] ->
- %% No POIs before
- {0, 0};
- [BeforeRange|_] when IsInsideFunction ->
- %% Inside function, use beginning of closest function clause
- maps:get(from, BeforeRange);
- [BeforeRange|_] when BeforeFunRanges == [] ->
- %% No function before, use end of closest POI
- maps:get(to, BeforeRange);
- [BeforeRange|_] ->
- %% Use end of closest POI, including functions.
- max(maps:get(to, hd(BeforeFunRanges)),
- maps:get(to, BeforeRange))
- end,
- %% Find when scope should end
- To =
- case [R || #{range := R} <- pois_after(POIs, VarRange)] of
- [] when IsInsideFunction ->
- %% No POIs after, use end of function
- maps:get(to, CurrentFunRange);
- [] ->
- %% No POIs after, use end of document
- {999999999, 999999999};
- [AfterRange|_] when IsInsideFunction ->
- %% Inside function, use closest of end of function *OR*
- %% beginning of the next function clause
- min(maps:get(to, CurrentFunRange), maps:get(from, AfterRange));
- [AfterRange|_] ->
- %% Use beginning of next POI
- maps:get(from, AfterRange)
- end,
- #{from => From, to => To}
- end.
-
--spec pois_before([poi()], poi_range()) -> [poi()].
-pois_before(POIs, VarRange) ->
- %% Reverse since we are typically interested in the last POI
- lists:reverse([POI || POI <- POIs, els_range:compare(range(POI), VarRange)]).
-
--spec pois_after([poi()], poi_range()) -> [poi()].
-pois_after(POIs, VarRange) ->
- [POI || POI <- POIs, els_range:compare(VarRange, range(POI))].
-
--spec pois_match([poi()], poi_range()) -> [poi()].
-pois_match(POIs, Range) ->
- [POI || POI <- POIs, els_range:in(Range, range(POI))].
-
--spec range(poi()) -> poi_range().
-range(#{kind := function, data := #{wrapping_range := Range}}) ->
- Range;
-range(#{range := Range}) ->
- Range.
-
-
-spec convert_references_to_pois([els_dt_references:item()], [poi_kind()]) ->
[{uri(), poi()}].
convert_references_to_pois(Refs, Kinds) ->
diff --git a/apps/els_lsp/src/els_scope.erl b/apps/els_lsp/src/els_scope.erl
index ab12a1d55..ab63e748b 100644
--- a/apps/els_lsp/src/els_scope.erl
+++ b/apps/els_lsp/src/els_scope.erl
@@ -3,6 +3,7 @@
-export([ local_and_included_pois/2
, local_and_includer_pois/2
+ , variable_scope_range/2
]).
-include("els_lsp.hrl").
@@ -68,3 +69,86 @@ find_includers(Uri) ->
find_includers(Kind, Id) ->
{ok, Items} = els_dt_references:find_by_id(Kind, Id),
[Uri || #{uri := Uri} <- Items].
+
+%% @doc Find the rough scope of a variable, this is based on heuristics and
+%% won't always be correct.
+%% `VarRange' is expected to be the range of the variable.
+-spec variable_scope_range(poi_range(), els_dt_document:item()) -> poi_range().
+variable_scope_range(VarRange, Document) ->
+ Attributes = [spec, callback, define, record, type_definition],
+ AttrPOIs = els_dt_document:pois(Document, Attributes),
+ case pois_match(AttrPOIs, VarRange) of
+ [#{range := Range}] ->
+ %% Inside attribute, simple.
+ Range;
+ [] ->
+ %% If variable is not inside an attribute we need to figure out where the
+ %% scope of the variable begins and ends.
+ %% The scope of variables inside functions are limited by function clauses
+ %% The scope of variables outside of function are limited by top-level
+ %% POIs (attributes and functions) before and after.
+ FunPOIs = els_poi:sort(els_dt_document:pois(Document, [function])),
+ POIs = els_poi:sort(els_dt_document:pois(Document, [ function_clause
+ | Attributes
+ ])),
+ CurrentFunRange = case pois_match(FunPOIs, VarRange) of
+ [] -> undefined;
+ [POI] -> range(POI)
+ end,
+ IsInsideFunction = CurrentFunRange /= undefined,
+ BeforeFunRanges = [range(POI) || POI <- pois_before(FunPOIs, VarRange)],
+ %% Find where scope should begin
+ From =
+ case [R || #{range := R} <- pois_before(POIs, VarRange)] of
+ [] ->
+ %% No POIs before
+ {0, 0};
+ [BeforeRange|_] when IsInsideFunction ->
+ %% Inside function, use beginning of closest function clause
+ maps:get(from, BeforeRange);
+ [BeforeRange|_] when BeforeFunRanges == [] ->
+ %% No function before, use end of closest POI
+ maps:get(to, BeforeRange);
+ [BeforeRange|_] ->
+ %% Use end of closest POI, including functions.
+ max(maps:get(to, hd(BeforeFunRanges)),
+ maps:get(to, BeforeRange))
+ end,
+ %% Find when scope should end
+ To =
+ case [R || #{range := R} <- pois_after(POIs, VarRange)] of
+ [] when IsInsideFunction ->
+ %% No POIs after, use end of function
+ maps:get(to, CurrentFunRange);
+ [] ->
+ %% No POIs after, use end of document
+ {999999999, 999999999};
+ [AfterRange|_] when IsInsideFunction ->
+ %% Inside function, use closest of end of function *OR*
+ %% beginning of the next function clause
+ min(maps:get(to, CurrentFunRange), maps:get(from, AfterRange));
+ [AfterRange|_] ->
+ %% Use beginning of next POI
+ maps:get(from, AfterRange)
+ end,
+ #{from => From, to => To}
+ end.
+
+-spec pois_before([poi()], poi_range()) -> [poi()].
+pois_before(POIs, VarRange) ->
+ %% Reverse since we are typically interested in the last POI
+ lists:reverse([POI || POI <- POIs, els_range:compare(range(POI), VarRange)]).
+
+-spec pois_after([poi()], poi_range()) -> [poi()].
+pois_after(POIs, VarRange) ->
+ [POI || POI <- POIs, els_range:compare(VarRange, range(POI))].
+
+-spec pois_match([poi()], poi_range()) -> [poi()].
+pois_match(POIs, Range) ->
+ [POI || POI <- POIs, els_range:in(Range, range(POI))].
+
+-spec range(poi()) -> poi_range().
+range(#{kind := function, data := #{wrapping_range := Range}}) ->
+ Range;
+range(#{range := Range}) ->
+ Range.
diff --git a/apps/els_lsp/test/els_definition_SUITE.erl b/apps/els_lsp/test/els_definition_SUITE.erl
index a498b8176..4b23e6034 100644
--- a/apps/els_lsp/test/els_definition_SUITE.erl
+++ b/apps/els_lsp/test/els_definition_SUITE.erl
@@ -463,10 +463,12 @@ variable(Config) ->
Def1 = els_client:definition(Uri, 105, 10),
Def2 = els_client:definition(Uri, 107, 10),
Def3 = els_client:definition(Uri, 108, 10),
+ Def4 = els_client:definition(Uri, 19, 36),
#{result := #{range := Range0, uri := DefUri0}} = Def0,
#{result := #{range := Range1, uri := DefUri0}} = Def1,
#{result := #{range := Range2, uri := DefUri0}} = Def2,
#{result := #{range := Range3, uri := DefUri0}} = Def3,
+ #{result := #{range := Range4, uri := DefUri0}} = Def4,
?assertEqual(?config(code_navigation_uri, Config), DefUri0),
?assertEqual( els_protocol:range(#{from => {103, 12}, to => {103, 15}})
@@ -477,6 +479,9 @@ variable(Config) ->
, Range2),
?assertEqual( els_protocol:range(#{from => {106, 12}, to => {106, 15}})
, Range3),
+ %% Inside macro
+ ?assertEqual( els_protocol:range(#{from => {19, 17}, to => {19, 18}})
+ , Range4),
ok.
diff --git a/apps/els_lsp/test/els_rename_SUITE.erl b/apps/els_lsp/test/els_rename_SUITE.erl
index 301ae691b..cce615297 100644
--- a/apps/els_lsp/test/els_rename_SUITE.erl
+++ b/apps/els_lsp/test/els_rename_SUITE.erl
@@ -479,7 +479,7 @@ assert_changes(#{ changes := ExpectedChanges }, #{ changes := Changes }) ->
lists:sort(maps:to_list(ExpectedChanges))),
[ begin
?assertEqual(ExpectedKey, Key),
- ?assertEqual(Expected, Change)
+ ?assertEqual(lists:sort(Expected), lists:sort(Change))
end
|| {{Key, Change}, {ExpectedKey, Expected}} <- Pairs
],
From 42050283f88b6ac9ab3305db275d5bf726eaa0dc Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Fri, 4 Feb 2022 13:58:25 +0100
Subject: [PATCH 018/239] Avoid compiler_diagnostics crash in case module name
is invalid (#1191)
Invalid syntax on the module attribute can cause a crash.
---
apps/els_lsp/src/els_compiler_diagnostics.erl | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/apps/els_lsp/src/els_compiler_diagnostics.erl b/apps/els_lsp/src/els_compiler_diagnostics.erl
index 1038c0846..ca79adfa0 100644
--- a/apps/els_lsp/src/els_compiler_diagnostics.erl
+++ b/apps/els_lsp/src/els_compiler_diagnostics.erl
@@ -753,7 +753,9 @@ module_name_check(Path) ->
?DIAGNOSTIC_ERROR,
<<"Compiler (via Erlang LS)">>),
[Diagnostic]
- end
+ end;
+ _ ->
+ []
end;
_ ->
[]
From 615438831d8db64e163263db01f62dea9118a4aa Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Sat, 12 Feb 2022 13:25:28 +0100
Subject: [PATCH 019/239] Add case snippet (#1194)
---
apps/els_lsp/priv/snippets/case | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 apps/els_lsp/priv/snippets/case
diff --git a/apps/els_lsp/priv/snippets/case b/apps/els_lsp/priv/snippets/case
new file mode 100644
index 000000000..d1ba3cca6
--- /dev/null
+++ b/apps/els_lsp/priv/snippets/case
@@ -0,0 +1,4 @@
+case ${1:Exprs} of
+ ${2:Pattern} ->
+ ${3:Body}
+end
\ No newline at end of file
From e0939ff5d5da4c7eaf30673baf86e698d34267be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Mon, 14 Feb 2022 09:13:02 +0100
Subject: [PATCH 020/239] Add support for renaming modules (#1196)
---
.../code_navigation/src/rename_module_a.erl | 16 +++++++
.../code_navigation/src/rename_module_b.erl | 14 ++++++
apps/els_lsp/src/els_parser.erl | 48 ++++++++++++-------
apps/els_lsp/src/els_references_provider.erl | 34 +++++++++----
apps/els_lsp/src/els_rename_provider.erl | 46 +++++++++++++++++-
apps/els_lsp/test/els_rename_SUITE.erl | 38 +++++++++++++++
6 files changed, 167 insertions(+), 29 deletions(-)
create mode 100644 apps/els_lsp/priv/code_navigation/src/rename_module_a.erl
create mode 100644 apps/els_lsp/priv/code_navigation/src/rename_module_b.erl
diff --git a/apps/els_lsp/priv/code_navigation/src/rename_module_a.erl b/apps/els_lsp/priv/code_navigation/src/rename_module_a.erl
new file mode 100644
index 000000000..be174d088
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/rename_module_a.erl
@@ -0,0 +1,16 @@
+-module(rename_module_a).
+
+-export([ function_a/0
+ , function_b/0
+ ]).
+-export_type([type_a/0]).
+
+-type type_a() :: any().
+
+-callback function_a() -> type_a().
+
+function_a() ->
+ a.
+
+function_b() ->
+ b.
diff --git a/apps/els_lsp/priv/code_navigation/src/rename_module_b.erl b/apps/els_lsp/priv/code_navigation/src/rename_module_b.erl
new file mode 100644
index 000000000..2da5b6186
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/rename_module_b.erl
@@ -0,0 +1,14 @@
+-module(rename_module_b).
+
+-behaviour(rename_module_a).
+-import(rename_module_a, [function_b/0]).
+
+-export([function_a/0]).
+
+-type type_a() :: rename_module_a:type_a().
+
+-spec function_a() -> type_a().
+function_a() ->
+ rename_module_a:function_a(),
+ F = fun rename_module_a:function_a/0,
+ F().
diff --git a/apps/els_lsp/src/els_parser.erl b/apps/els_lsp/src/els_parser.erl
index c3943f8df..f0f11648b 100644
--- a/apps/els_lsp/src/els_parser.erl
+++ b/apps/els_lsp/src/els_parser.erl
@@ -221,8 +221,11 @@ application(Tree) ->
ModFunTree = erl_syntax:application_operator(Tree),
Pos = erl_syntax:get_pos(ModFunTree),
FunTree = erl_syntax:module_qualifier_body(ModFunTree),
- [poi(Pos, application, MFA,
- #{name_range => els_range:range(erl_syntax:get_pos(FunTree))})]
+ ModTree = erl_syntax:module_qualifier_argument(ModFunTree),
+ Data = #{ name_range => els_range:range(erl_syntax:get_pos(FunTree))
+ , mod_range => els_range:range(erl_syntax:get_pos(ModTree))
+ },
+ [poi(Pos, application, MFA, Data)]
end.
-spec application_mfa(tree()) ->
@@ -276,7 +279,8 @@ attribute(Tree) ->
AttrName =:= behavior ->
case is_atom_node(Arg) of
{true, Behaviour} ->
- [poi(Pos, behaviour, Behaviour)];
+ Data = #{mod_range => els_range:range(erl_syntax:get_pos(Arg))},
+ [poi(Pos, behaviour, Behaviour, Data)];
false ->
[]
end;
@@ -306,9 +310,9 @@ attribute(Tree) ->
find_export_pois(Tree, AttrName, Arg);
{import, [ModTree, ImportList]} ->
case is_atom_node(ModTree) of
- {true, M} ->
+ {true, _} ->
Imports = erl_syntax:list_elements(ImportList),
- find_import_entry_pois(M, Imports);
+ find_import_entry_pois(ModTree, Imports);
_ ->
[]
end;
@@ -436,14 +440,17 @@ find_export_entry_pois(EntryPoiKind, Exports) ->
|| FATree <- Exports
]).
--spec find_import_entry_pois(atom(), [tree()]) -> [poi()].
-find_import_entry_pois(M, Imports) ->
+-spec find_import_entry_pois(tree(), [tree()]) -> [poi()].
+find_import_entry_pois(ModTree, Imports) ->
+ M = erl_syntax:atom_value(ModTree),
lists:flatten(
[ case get_name_arity(FATree) of
{F, A} ->
FTree = erl_syntax:arity_qualifier_body(FATree),
- poi(erl_syntax:get_pos(FATree), import_entry, {M, F, A},
- #{name_range => els_range:range(erl_syntax:get_pos(FTree))});
+ Data = #{ name_range => els_range:range(erl_syntax:get_pos(FTree))
+ , mod_range => els_range:range(erl_syntax:get_pos(ModTree))
+ },
+ poi(erl_syntax:get_pos(FATree), import_entry, {M, F, A}, Data);
false ->
[]
end
@@ -556,16 +563,20 @@ implicit_fun(Tree) ->
undefined -> [];
_ ->
NameTree = erl_syntax:implicit_fun_name(Tree),
- FunTree =
+ Data =
case FunSpec of
{_, _, _} ->
- erl_syntax:arity_qualifier_body(
- erl_syntax:module_qualifier_body(NameTree));
+ ModTree = erl_syntax:module_qualifier_argument(NameTree),
+ FunTree = erl_syntax:arity_qualifier_body(
+ erl_syntax:module_qualifier_body(NameTree)),
+ #{ name_range => els_range:range(erl_syntax:get_pos(FunTree))
+ , mod_range => els_range:range(erl_syntax:get_pos(ModTree))
+ };
{_, _} ->
- erl_syntax:arity_qualifier_body(NameTree)
+ FunTree = erl_syntax:arity_qualifier_body(NameTree),
+ #{name_range => els_range:range(erl_syntax:get_pos(FunTree))}
end,
- [poi(erl_syntax:get_pos(Tree), implicit_fun, FunSpec,
- #{name_range => els_range:range(erl_syntax:get_pos(FunTree))})]
+ [poi(erl_syntax:get_pos(Tree), implicit_fun, FunSpec, Data)]
end.
-spec macro(tree()) -> [poi()].
@@ -727,8 +738,11 @@ type_application(Tree) ->
ModTypeTree = erl_syntax:type_application_name(Tree),
Pos = erl_syntax:get_pos(ModTypeTree),
TypeTree = erl_syntax:module_qualifier_body(ModTypeTree),
- [poi(Pos, type_application, Id,
- #{name_range => els_range:range(erl_syntax:get_pos(TypeTree))})];
+ ModTree = erl_syntax:module_qualifier_argument(ModTypeTree),
+ Data = #{ name_range => els_range:range(erl_syntax:get_pos(TypeTree))
+ , mod_range => els_range:range(erl_syntax:get_pos(ModTree))
+ },
+ [poi(Pos, type_application, Id, Data)];
{Name, Arity} when Type =:= user_type_application ->
%% user-defined local type
Id = {Name, Arity},
diff --git a/apps/els_lsp/src/els_references_provider.erl b/apps/els_lsp/src/els_references_provider.erl
index 8faa40c3a..64ae8fee7 100644
--- a/apps/els_lsp/src/els_references_provider.erl
+++ b/apps/els_lsp/src/els_references_provider.erl
@@ -9,6 +9,7 @@
%% For use in other providers
-export([ find_references/2
, find_scoped_references_for_def/2
+ , find_references_to_module/1
]).
%%==============================================================================
@@ -105,16 +106,8 @@ find_references(Uri, Poi = #{kind := Kind})
find_scoped_references_for_def(Uri, Poi))
end;
find_references(Uri, #{kind := module}) ->
- case els_utils:lookup_document(Uri) of
- {ok, Doc} ->
- Exports = els_dt_document:pois(Doc, [export_entry]),
- ExcludeLocalRefs =
- fun(Loc) ->
- maps:get(uri, Loc) =/= Uri
- end,
- Refs = lists:flatmap(fun(E) -> find_references(Uri, E) end, Exports),
- lists:filter(ExcludeLocalRefs, Refs)
- end;
+ Refs = find_references_to_module(Uri),
+ [location(U, R) || #{uri := U, range := R} <- Refs];
find_references(_Uri, #{kind := Kind, id := Name})
when Kind =:= behaviour ->
find_references_for_id(Kind, Name);
@@ -141,6 +134,27 @@ kind_to_ref_kinds(type_definition) ->
kind_to_ref_kinds(Kind) ->
[Kind].
+-spec find_references_to_module(uri()) -> [els_dt_references:item()].
+find_references_to_module(Uri) ->
+ M = els_uri:module(Uri),
+ {ok, Doc} = els_utils:lookup_document(Uri),
+ ExportRefs =
+ lists:flatmap(
+ fun(#{id := {F, A}}) ->
+ {ok, Rs} =
+ els_dt_references:find_by_id(export_entry, {M, F, A}),
+ Rs
+ end, els_dt_document:pois(Doc, [export_entry])),
+ ExportTypeRefs =
+ lists:flatmap(
+ fun(#{id := {F, A}}) ->
+ {ok, Rs} =
+ els_dt_references:find_by_id(export_type_entry, {M, F, A}),
+ Rs
+ end, els_dt_document:pois(Doc, [export_type_entry])),
+ {ok, BehaviourRefs} = els_dt_references:find_by_id(behaviour, M),
+ ExcludeLocalRefs = fun(Loc) -> maps:get(uri, Loc) =/= Uri end,
+ lists:filter(ExcludeLocalRefs, ExportRefs ++ ExportTypeRefs ++ BehaviourRefs).
-spec find_references_for_id(poi_kind(), any()) -> [location()].
find_references_for_id(Kind, Id) ->
diff --git a/apps/els_lsp/src/els_rename_provider.erl b/apps/els_lsp/src/els_rename_provider.erl
index 7db3af35b..6cd630391 100644
--- a/apps/els_lsp/src/els_rename_provider.erl
+++ b/apps/els_lsp/src/els_rename_provider.erl
@@ -46,6 +46,34 @@ handle_request({rename, Params}, State) ->
-spec workspace_edits(uri(), [poi()], binary()) -> null | [any()].
workspace_edits(_Uri, [], _NewName) ->
null;
+workspace_edits(OldUri, [#{kind := module} = POI| _], NewName) ->
+ %% Generate new Uri
+ Path = els_uri:path(OldUri),
+ Dir = filename:dirname(Path),
+ NewPath = filename:join(Dir, <>),
+ NewUri = els_uri:uri(NewPath),
+ %% Find references that needs to be changed
+ Refs = els_references_provider:find_references_to_module(OldUri),
+ RefPOIs = convert_references_to_pois(Refs, [ application
+ , implicit_fun
+ , import_entry
+ , type_application
+ , behaviour
+ ]),
+ Changes = [#{ textDocument => #{uri => RefUri}
+ , edits => [#{ range => editable_range(RefPOI, module)
+ , newText => NewName
+ }]
+ } || {RefUri, RefPOI} <- RefPOIs],
+ #{documentChanges =>
+ [ %% Update -module attribute
+ #{textDocument => #{uri => OldUri},
+ edits => [change(POI, NewName)]
+ }
+ %% Rename file
+ , #{kind => rename, oldUri => OldUri, newUri => NewUri}
+ | Changes]
+ };
workspace_edits(Uri, [#{kind := function_clause} = POI| _], NewName) ->
#{id := {F, A, _}} = POI,
#{changes => changes(Uri, POI#{kind => function, id => {F, A}}, NewName)};
@@ -112,7 +140,18 @@ workspace_edits(_Uri, _POIs, _NewName) ->
null.
-spec editable_range(poi()) -> range().
-editable_range(#{kind := Kind, data := #{name_range := Range}})
+editable_range(POI) ->
+ editable_range(POI, function).
+
+-spec editable_range(poi(), function | module) -> range().
+editable_range(#{kind := Kind, data := #{mod_range := Range}}, module)
+ when Kind =:= application;
+ Kind =:= implicit_fun;
+ Kind =:= import_entry;
+ Kind =:= type_application;
+ Kind =:= behaviour ->
+ els_protocol:range(Range);
+editable_range(#{kind := Kind, data := #{name_range := Range}}, function)
when Kind =:= application;
Kind =:= implicit_fun;
Kind =:= callback;
@@ -126,10 +165,13 @@ editable_range(#{kind := Kind, data := #{name_range := Range}})
%% type_application POI of a built-in type don't have name_range data
%% they are handled by the next clause
els_protocol:range(Range);
-editable_range(#{kind := _Kind, range := Range}) ->
+editable_range(#{kind := _Kind, range := Range}, _) ->
els_protocol:range(Range).
+
-spec changes(uri(), poi(), binary()) -> #{uri() => [text_edit()]} | null.
+changes(Uri, #{kind := module} = Mod, NewName) ->
+ #{Uri => [#{range => editable_range(Mod), newText => NewName}]};
changes(Uri, #{kind := variable} = Var, NewName) ->
POIs = els_code_navigation:find_in_scope(Uri, Var),
#{Uri => [#{range => editable_range(P), newText => NewName} || P <- POIs]};
diff --git a/apps/els_lsp/test/els_rename_SUITE.erl b/apps/els_lsp/test/els_rename_SUITE.erl
index cce615297..012bbac61 100644
--- a/apps/els_lsp/test/els_rename_SUITE.erl
+++ b/apps/els_lsp/test/els_rename_SUITE.erl
@@ -14,6 +14,7 @@
%% Test cases
-export([ rename_behaviour_callback/1
, rename_macro/1
+ , rename_module/1
, rename_variable/1
, rename_function/1
, rename_function_quoted_atom/1
@@ -220,6 +221,43 @@ rename_macro(Config) ->
},
assert_changes(Expected, Result).
+-spec rename_module(config()) -> ok.
+rename_module(Config) ->
+ UriA = ?config(rename_module_a_uri, Config),
+ UriB = ?config(rename_module_b_uri, Config),
+ NewName = <<"new_module">>,
+ Path = filename:dirname(els_uri:path(UriA)),
+ NewUri = els_uri:uri(filename:join(Path, <>)),
+ #{result := #{documentChanges := Result}} =
+ els_client:document_rename(UriA, 0, 14, NewName),
+ Expected = [
+ %% Module attribute
+ #{ edits => [change(NewName, {0, 8}, {0, 23})]
+ , textDocument => #{uri => UriA}}
+ %% Rename file
+ , #{ kind => <<"rename">>
+ , newUri => NewUri
+ , oldUri => UriA}
+ %% Implicit function
+ , #{ edits => [change(NewName, {12, 10}, {12, 25})]
+ , textDocument => #{uri => UriB}}
+ %% Function application
+ , #{ edits => [change(NewName, {11, 2}, {11, 17})]
+ , textDocument => #{uri => UriB}}
+ %% Import
+ , #{ edits => [change(NewName, {3, 8}, {3, 23})]
+ , textDocument => #{uri => UriB}}
+ %% Type application
+ , #{ edits => [change(NewName, {7, 18}, {7, 33})]
+ , textDocument => #{uri => UriB}}
+ %% Behaviour
+ , #{ edits => [change(NewName, {2, 11}, {2, 26})]
+ , textDocument => #{uri => UriB}}
+ ],
+ ?assertEqual([], Result -- Expected),
+ ?assertEqual([], Expected -- Result),
+ ?assertEqual(lists:sort(Expected), lists:sort(Result)).
+
-spec rename_function(config()) -> ok.
rename_function(Config) ->
Uri = ?config(rename_function_uri, Config),
From dcbeecb0786ad4c378a81e5ba96f90d666f8c02e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Mon, 14 Feb 2022 09:14:01 +0100
Subject: [PATCH 021/239] Fix handle module name whitespace (#1195)
* Fix false positive module name check diagnostic
Discovered that modules with whitespace in their name could cause false
positives in the module name check diagnostic.
Root cause is in els_uri:path/1, since uri_string:normalize/1 return a percent
encoded path, we need to percent decode that path to get the real path.
* Shouldn't need to handle percent encoding for windows paths any more
This change essentially reverts #1017 and adds a unit test
* Use http_uri:decode/1 for older OTP releases
---
apps/els_core/src/els_uri.erl | 43 +++++++++++++++++--
.../src/diagnostics module name check.erl | 1 +
apps/els_lsp/test/els_diagnostics_SUITE.erl | 10 +++++
3 files changed, 50 insertions(+), 4 deletions(-)
create mode 100644 apps/els_lsp/priv/code_navigation/src/diagnostics module name check.erl
diff --git a/apps/els_core/src/els_uri.erl b/apps/els_core/src/els_uri.erl
index 8f6fbd74a..cabb1eda6 100644
--- a/apps/els_core/src/els_uri.erl
+++ b/apps/els_core/src/els_uri.erl
@@ -31,16 +31,20 @@ module(Uri) ->
-spec path(uri()) -> path().
path(Uri) ->
+ path(Uri, is_windows()).
+
+-spec path(uri(), boolean()) -> path().
+path(Uri, IsWindows) ->
#{ host := Host
- , path := Path
+ , path := Path0
, scheme := <<"file">>
} = uri_string:normalize(Uri, [return_map]),
-
- case {is_windows(), Host} of
+ Path = percent_decode(Path0),
+ case {IsWindows, Host} of
{true, <<>>} ->
% Windows drive letter, have to strip the initial slash
re:replace(
- Path, "^/([a-zA-Z])(:|%3A)(.*)", "\\1:\\3", [{return, binary}]
+ Path, "^/([a-zA-Z]:)(.*)", "\\1\\2", [{return, binary}]
);
{true, _} ->
<<"//", Host/binary, Path/binary>>;
@@ -81,3 +85,34 @@ uri_join(List) ->
is_windows() ->
{OS, _} = os:type(),
OS =:= win32.
+
+-if(?OTP_RELEASE >= 23).
+-spec percent_decode(binary()) -> binary().
+percent_decode(Str) ->
+ uri_string:percent_decode(Str).
+-else.
+-spec percent_decode(binary()) -> binary().
+percent_decode(Str) ->
+ http_uri:decode(Str).
+-endif.
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+path_uri_test_() ->
+ [ ?_assertEqual( <<"/foo/bar.erl">>
+ , path(<<"file:///foo/bar.erl">>))
+ , ?_assertEqual( <<"/foo/bar baz.erl">>
+ , path(<<"file:///foo/bar%20baz.erl">>))
+ , ?_assertEqual( <<"/foo/bar.erl">>
+ , path(uri(path(<<"file:///foo/bar.erl">>))))
+ , ?_assertEqual( <<"/foo/bar baz.erl">>
+ , path(uri(<<"/foo/bar baz.erl">>)))
+ , ?_assertEqual( <<"file:///foo/bar%20baz.erl">>
+ , uri(<<"/foo/bar baz.erl">>))
+ ].
+
+path_windows_test() ->
+ ?assertEqual(<<"C:/foo/bar.erl">>,
+ path(<<"file:///C%3A/foo/bar.erl">>, true)).
+-endif.
diff --git a/apps/els_lsp/priv/code_navigation/src/diagnostics module name check.erl b/apps/els_lsp/priv/code_navigation/src/diagnostics module name check.erl
new file mode 100644
index 000000000..872e39d66
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/diagnostics module name check.erl
@@ -0,0 +1 @@
+-module('diagnostics module name check').
diff --git a/apps/els_lsp/test/els_diagnostics_SUITE.erl b/apps/els_lsp/test/els_diagnostics_SUITE.erl
index 65732538e..41c633579 100644
--- a/apps/els_lsp/test/els_diagnostics_SUITE.erl
+++ b/apps/els_lsp/test/els_diagnostics_SUITE.erl
@@ -42,6 +42,7 @@
, unused_record_fields/1
, gradualizer/1
, module_name_check/1
+ , module_name_check_whitespace/1
]).
%%==============================================================================
@@ -669,6 +670,15 @@ module_name_check(_Config) ->
Hints = [],
els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+-spec module_name_check_whitespace(config()) -> ok.
+module_name_check_whitespace(_Config) ->
+ Path = src_path("diagnostics module name check.erl"),
+ Source = <<"Compiler (via Erlang LS)">>,
+ Errors = [],
+ Warnings = [],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+
%%==============================================================================
%% Internal Functions
%%==============================================================================
From c371027871846153076732be0d94c9335101cb40 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Mon, 14 Feb 2022 18:13:45 +0100
Subject: [PATCH 022/239] Revert "Precompute list of enabled diagnostics"
(#1197)
This reverts commit 2f6decd8cfe342da60e102b55cde66c4af7f784a.
This change introduced a regression in the debugger, preventing it from starting.
The `els_dap` escript invokes the `els_config` initialization procedure
(see https://github.com/erlang-ls/erlang_ls/commit/4b475b0864f433ccc08c80609478f5b15b9b41b7),
but the new version of the initialization depends on the `els_diagnostics` module,
which is part of the `els_lsp` application, not included in the `els_dap` escript.
For now simply reverting the change, but we should revisit the application structure.
The original idea was for an application to contain the implementation of the JSON-RPC protocol
and for the `els_dap` and `els_lsp` to utilize that as a dependency. That never really happened
and the current application split in Erlang LS in its current form feels a bit arbitrary.
One may argue that Erlang LS could get rid of the umbrella structure and be a single application.
---
apps/els_core/src/els_config.erl | 1 -
apps/els_lsp/src/els_diagnostics.erl | 2 +-
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/apps/els_core/src/els_config.erl b/apps/els_core/src/els_config.erl
index b8fe0f39a..e295c715e 100644
--- a/apps/els_core/src/els_config.erl
+++ b/apps/els_core/src/els_config.erl
@@ -163,7 +163,6 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
ok = set(otp_paths , otp_paths(OtpPath, false) -- ExcludePaths),
ok = set(lenses , Lenses),
ok = set(diagnostics , Diagnostics),
- ok = set(enabled_diagnostics, els_diagnostics:enabled_diagnostics()),
%% All (including subdirs) paths used to search files with file:path_open/3
ok = set( search_paths
, lists:append([ project_paths(RootPath, AppsDirs, true)
diff --git a/apps/els_lsp/src/els_diagnostics.erl b/apps/els_lsp/src/els_diagnostics.erl
index 3e288ee15..9c9b4b303 100644
--- a/apps/els_lsp/src/els_diagnostics.erl
+++ b/apps/els_lsp/src/els_diagnostics.erl
@@ -90,7 +90,7 @@ make_diagnostic(Range, Message, Severity, Source) ->
-spec run_diagnostics(uri()) -> [pid()].
run_diagnostics(Uri) ->
- [run_diagnostic(Uri, Id) || Id <- els_config:get(enabled_diagnostics)].
+ [run_diagnostic(Uri, Id) || Id <- enabled_diagnostics()].
%%==============================================================================
%% Internal Functions
From a24f3e94161e724b6c83430358afedb12e0250d8 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Tue, 15 Feb 2022 12:55:52 +0100
Subject: [PATCH 023/239] Fix support for renaming modules (#1199)
The textDocument parameter in a TextDocumentEdit must contain a version, even if null (see the OptionalVersionedTextDocumentIdentifier definition from the LSP specification). Without it, some editors (most notably VS Code) would reject the edits
---
apps/els_lsp/src/els_rename_provider.erl | 4 ++--
apps/els_lsp/test/els_rename_SUITE.erl | 12 ++++++------
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/apps/els_lsp/src/els_rename_provider.erl b/apps/els_lsp/src/els_rename_provider.erl
index 6cd630391..535411d54 100644
--- a/apps/els_lsp/src/els_rename_provider.erl
+++ b/apps/els_lsp/src/els_rename_provider.erl
@@ -60,14 +60,14 @@ workspace_edits(OldUri, [#{kind := module} = POI| _], NewName) ->
, type_application
, behaviour
]),
- Changes = [#{ textDocument => #{uri => RefUri}
+ Changes = [#{ textDocument => #{uri => RefUri, version => null}
, edits => [#{ range => editable_range(RefPOI, module)
, newText => NewName
}]
} || {RefUri, RefPOI} <- RefPOIs],
#{documentChanges =>
[ %% Update -module attribute
- #{textDocument => #{uri => OldUri},
+ #{textDocument => #{uri => OldUri, version => null},
edits => [change(POI, NewName)]
}
%% Rename file
diff --git a/apps/els_lsp/test/els_rename_SUITE.erl b/apps/els_lsp/test/els_rename_SUITE.erl
index 012bbac61..0d2e98f5c 100644
--- a/apps/els_lsp/test/els_rename_SUITE.erl
+++ b/apps/els_lsp/test/els_rename_SUITE.erl
@@ -233,26 +233,26 @@ rename_module(Config) ->
Expected = [
%% Module attribute
#{ edits => [change(NewName, {0, 8}, {0, 23})]
- , textDocument => #{uri => UriA}}
+ , textDocument => #{uri => UriA, version => null}}
%% Rename file
, #{ kind => <<"rename">>
, newUri => NewUri
, oldUri => UriA}
%% Implicit function
, #{ edits => [change(NewName, {12, 10}, {12, 25})]
- , textDocument => #{uri => UriB}}
+ , textDocument => #{uri => UriB, version => null}}
%% Function application
, #{ edits => [change(NewName, {11, 2}, {11, 17})]
- , textDocument => #{uri => UriB}}
+ , textDocument => #{uri => UriB, version => null}}
%% Import
, #{ edits => [change(NewName, {3, 8}, {3, 23})]
- , textDocument => #{uri => UriB}}
+ , textDocument => #{uri => UriB, version => null}}
%% Type application
, #{ edits => [change(NewName, {7, 18}, {7, 33})]
- , textDocument => #{uri => UriB}}
+ , textDocument => #{uri => UriB, version => null}}
%% Behaviour
, #{ edits => [change(NewName, {2, 11}, {2, 26})]
- , textDocument => #{uri => UriB}}
+ , textDocument => #{uri => UriB, version => null}}
],
?assertEqual([], Result -- Expected),
?assertEqual([], Expected -- Result),
From ffa289a66a4c82cc69b4b1e65e0c3fd6767abb81 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Sat, 19 Feb 2022 13:50:08 +0100
Subject: [PATCH 024/239] Implement new code actions (#1212)
* If function is unused, provide action to export it
* If variable is unbound, provide action to fix spelling
* If module name doesn't match filename, provide action to fix it
This also includes refactoring of els_code_action_provider and fixing
the unused variable code action that didn't work properly.
---
apps/els_core/include/els_core.hrl | 2 +-
apps/els_core/src/els_protocol.erl | 2 +-
apps/els_core/src/els_utils.erl | 25 +++
.../priv/code_navigation/src/code_action.erl | 15 ++
apps/els_lsp/src/els_code_action_provider.erl | 194 +++++++++++-------
.../src/els_execute_command_provider.erl | 14 +-
apps/els_lsp/test/els_code_action_SUITE.erl | 107 ++++++++--
apps/els_lsp/test/els_completion_SUITE.erl | 6 +-
8 files changed, 262 insertions(+), 103 deletions(-)
create mode 100644 apps/els_lsp/priv/code_navigation/src/code_action.erl
diff --git a/apps/els_core/include/els_core.hrl b/apps/els_core/include/els_core.hrl
index 2d142e2ea..c3b6e8ef1 100644
--- a/apps/els_core/include/els_core.hrl
+++ b/apps/els_core/include/els_core.hrl
@@ -568,7 +568,7 @@
, context := code_action_context()
}.
--type code_action() :: #{ title := string()
+-type code_action() :: #{ title := binary()
, kind => code_action_kind()
, diagnostics => [els_diagnostics:diagnostic()]
, edit => workspace_edit()
diff --git a/apps/els_core/src/els_protocol.erl b/apps/els_core/src/els_protocol.erl
index e99c1ced0..f8860eb4b 100644
--- a/apps/els_core/src/els_protocol.erl
+++ b/apps/els_core/src/els_protocol.erl
@@ -84,7 +84,7 @@ range(#{ from := {FromL, FromC}, to := {ToL, ToC} }) ->
%%==============================================================================
-spec content(binary()) -> binary().
content(Body) ->
-els_utils:to_binary([headers(Body), "\r\n", Body]).
+ els_utils:to_binary([headers(Body), "\r\n", Body]).
-spec headers(binary()) -> iolist().
headers(Body) ->
diff --git a/apps/els_core/src/els_utils.erl b/apps/els_core/src/els_utils.erl
index 8b87e4b37..d4a3e169e 100644
--- a/apps/els_core/src/els_utils.erl
+++ b/apps/els_core/src/els_utils.erl
@@ -20,6 +20,7 @@
, function_signature/1
, base64_encode_term/1
, base64_decode_term/1
+ , levenshtein_distance/2
]).
@@ -449,3 +450,27 @@ base64_encode_term(Term) ->
-spec base64_decode_term(binary()) -> any().
base64_decode_term(Base64) ->
binary_to_term(base64:decode(Base64)).
+
+-spec levenshtein_distance(binary(), binary()) -> integer().
+levenshtein_distance(S, T) ->
+ {Distance, _} = levenshtein_distance(to_list(S), to_list(T), #{}),
+ Distance.
+
+-spec levenshtein_distance(string(), string(), map()) -> {integer(), map()}.
+levenshtein_distance([] = S, T, Cache) ->
+ {length(T), maps:put({S, T}, length(T), Cache)};
+levenshtein_distance(S, [] = T, Cache) ->
+ {length(S), maps:put({S, T}, length(S), Cache)};
+levenshtein_distance([X|S], [X|T], Cache) ->
+ levenshtein_distance(S, T, Cache);
+levenshtein_distance([_SH|ST] = S, [_TH|TT] = T, Cache) ->
+ case maps:find({S, T}, Cache) of
+ {ok, Distance} ->
+ {Distance, Cache};
+ error ->
+ {L1, C1} = levenshtein_distance(S, TT, Cache),
+ {L2, C2} = levenshtein_distance(ST, T, C1),
+ {L3, C3} = levenshtein_distance(ST, TT, C2),
+ L = 1 + lists:min([L1, L2, L3]),
+ {L, maps:put({S, T}, L, C3)}
+ end.
diff --git a/apps/els_lsp/priv/code_navigation/src/code_action.erl b/apps/els_lsp/priv/code_navigation/src/code_action.erl
new file mode 100644
index 000000000..6f170b684
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/code_action.erl
@@ -0,0 +1,15 @@
+-module(code_action_oops).
+
+-export([function_a/0]).
+
+function_a() ->
+ A = 123,
+ function_b().
+
+function_b() ->
+ ok.
+
+function_c() ->
+ Foo = 1,
+ Bar = 2,
+ Foo + Barf.
diff --git a/apps/els_lsp/src/els_code_action_provider.erl b/apps/els_lsp/src/els_code_action_provider.erl
index 8acd202ed..d0e77656c 100644
--- a/apps/els_lsp/src/els_code_action_provider.erl
+++ b/apps/els_lsp/src/els_code_action_provider.erl
@@ -29,79 +29,133 @@ handle_request({document_codeaction, Params}, State) ->
%% Internal Functions
%%==============================================================================
-
%% @doc Result: `(Command | CodeAction)[] | null'
-spec code_actions(uri(), range(), code_action_context()) -> [map()].
-code_actions(Uri, _Range, Context) ->
- #{ <<"diagnostics">> := Diagnostics } = Context,
- Actions0 = [ make_code_action(Uri, D) || D <- Diagnostics],
- Actions = lists:flatten(Actions0),
- Actions.
-
-%% @doc Note: if the start and end line of the range are the same, the line
-%% is simply added.
--spec replace_lines_action(uri(), binary(), binary(), binary(), range())
- -> map().
-replace_lines_action(Uri, Title, Kind, Lines, Range) ->
- #{ <<"start">> := #{ <<"character">> := _StartCol
- , <<"line">> := StartLine }
- , <<"end">> := #{ <<"character">> := _EndCol
- , <<"line">> := EndLine }
- } = Range,
+code_actions(Uri, _Range, #{<<"diagnostics">> := Diagnostics}) ->
+ lists:flatten([make_code_action(Uri, D) || D <- Diagnostics]).
+
+-spec make_code_action(uri(), map()) -> [map()].
+make_code_action(Uri, #{<<"message">> := Message, <<"range">> := Range}) ->
+ make_code_action(
+ [ {"function (.*) is unused", fun action_export_function/3}
+ , {"variable '(.*)' is unused", fun action_ignore_variable/3}
+ , {"variable '(.*)' is unbound", fun action_suggest_variable/3}
+ , {"Module name '(.*)' does not match file name '(.*)'",
+ fun action_fix_module_name/3}
+ ], Uri, Range, Message).
+
+-spec make_code_action([{string(), Fun}], uri(), range(), binary()) -> [map()]
+ when Fun :: fun((uri(), range(), [binary()]) -> [map()]).
+make_code_action([], _Uri, _Range, _Message) ->
+ [];
+make_code_action([{RE, Fun}|Rest], Uri, Range, Message) ->
+ Actions = case re:run(Message, RE, [{capture, all_but_first, binary}]) of
+ {match, Matches} ->
+ Fun(Uri, Range, Matches);
+ nomatch ->
+ []
+ end,
+ Actions ++ make_code_action(Rest, Uri, Range, Message).
+
+-spec action_export_function(uri(), range(), [binary()]) -> [map()].
+action_export_function(Uri, _Range, [UnusedFun]) ->
+ {ok, Document} = els_utils:lookup_document(Uri),
+ case els_poi:sort(els_dt_document:pois(Document, [module, export])) of
+ [] ->
+ [];
+ POIs ->
+ #{range := #{to := {Line, _Col}}} = lists:last(POIs),
+ Pos = {Line + 1, 1},
+ [ make_edit_action( Uri
+ , <<"Export ", UnusedFun/binary>>
+ , ?CODE_ACTION_KIND_QUICKFIX
+ , <<"-export([", UnusedFun/binary, "]).\n">>
+ , els_protocol:range(#{from => Pos, to => Pos})) ]
+ end.
+
+-spec action_ignore_variable(uri(), range(), [binary()]) -> [map()].
+action_ignore_variable(Uri, Range, [UnusedVariable]) ->
+ {ok, Document} = els_utils:lookup_document(Uri),
+ POIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
+ case ensure_range(els_range:to_poi_range(Range), UnusedVariable, POIs) of
+ {ok, VarRange} ->
+ [ make_edit_action( Uri
+ , <<"Add '_' to '", UnusedVariable/binary, "'">>
+ , ?CODE_ACTION_KIND_QUICKFIX
+ , <<"_", UnusedVariable/binary>>
+ , els_protocol:range(VarRange)) ];
+ error ->
+ []
+ end.
+
+-spec action_suggest_variable(uri(), range(), [binary()]) -> [map()].
+action_suggest_variable(Uri, Range, [Var]) ->
+ %% Supply a quickfix to replace an unbound variable with the most similar
+ %% variable name in scope.
+ {ok, Document} = els_utils:lookup_document(Uri),
+ POIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
+ case ensure_range(els_range:to_poi_range(Range), Var, POIs) of
+ {ok, VarRange} ->
+ ScopeRange = els_scope:variable_scope_range(VarRange, Document),
+ VarsInScope = [atom_to_binary(Id, utf8) ||
+ #{range := R, id := Id} <- POIs,
+ els_range:in(R, ScopeRange),
+ els_range:compare(R, VarRange)],
+ case [{els_utils:levenshtein_distance(V, Var), V} ||
+ V <- VarsInScope,
+ V =/= Var,
+ binary:at(Var, 0) =:= binary:at(V, 0)]
+ of
+ [] ->
+ [];
+ VariableDistances ->
+ {_, SimilarVariable} = lists:min(VariableDistances),
+ [ make_edit_action( Uri
+ , <<"Did you mean '", SimilarVariable/binary, "'?">>
+ , ?CODE_ACTION_KIND_QUICKFIX
+ , SimilarVariable
+ , els_protocol:range(VarRange)) ]
+ end;
+ error ->
+ []
+ end.
+
+-spec action_fix_module_name(uri(), range(), [binary()]) -> [map()].
+action_fix_module_name(Uri, Range0, [ModName, FileName]) ->
+ {ok, Document} = els_utils:lookup_document(Uri),
+ POIs = els_poi:sort(els_dt_document:pois(Document, [module])),
+ case ensure_range(els_range:to_poi_range(Range0), ModName, POIs) of
+ {ok, Range} ->
+ [ make_edit_action( Uri
+ , <<"Change to -module(", FileName/binary, ").">>
+ , ?CODE_ACTION_KIND_QUICKFIX
+ , FileName
+ , els_protocol:range(Range)) ];
+ error ->
+ []
+ end.
+
+-spec ensure_range(poi_range(), binary(), [poi()]) -> {ok, poi_range()} | error.
+ensure_range(#{from := {Line, _}}, SubjectId, POIs) ->
+ SubjectAtom = binary_to_atom(SubjectId, utf8),
+ Ranges = [R || #{range := R, id := Id} <- POIs,
+ els_range:in(R, #{from => {Line, 1}, to => {Line + 1, 1}}),
+ Id =:= SubjectAtom],
+ case Ranges of
+ [] ->
+ error;
+ [Range|_] ->
+ {ok, Range}
+ end.
+
+-spec make_edit_action(uri(), binary(), binary(), binary(), range())
+ -> map().
+make_edit_action(Uri, Title, Kind, Text, Range) ->
#{ title => Title
, kind => Kind
- , command =>
- els_command:make_command( Title
- , <<"replace-lines">>
- , [#{ uri => Uri
- , lines => Lines
- , from => StartLine
- , to => EndLine }])
+ , edit => edit(Uri, Text, Range)
}.
--spec make_code_action(uri(), els_diagnostics:diagnostic()) -> [map()].
-make_code_action(Uri, #{ <<"message">> := Message
- , <<"range">> := Range } = _Diagnostic) ->
- unused_variable_action(Uri, Range, Message).
-
-%%------------------------------------------------------------------------------
-
--spec unused_variable_action(uri(), range(), binary()) -> [map()].
-unused_variable_action(Uri, Range, Message) ->
- %% Processing messages like "variable 'Foo' is unused"
- case re:run(Message, "variable '(.*)' is unused"
- , [{capture, all_but_first, binary}]) of
- {match, [UnusedVariable]} ->
- make_unused_variable_action(Uri, Range, UnusedVariable);
- _ -> []
- end.
-
--spec make_unused_variable_action(uri(), range(), binary()) -> [map()].
-make_unused_variable_action(Uri, Range, UnusedVariable) ->
- #{ <<"start">> := #{ <<"character">> := _StartCol
- , <<"line">> := StartLine }
- , <<"end">> := _End
- } = Range,
- %% processing messages like "variable 'Foo' is unused"
- {ok, #{text := Bin}} = els_utils:lookup_document(Uri),
- Line = els_utils:to_list(els_text:line(Bin, StartLine)),
-
- {ok, Tokens, _} = erl_scan:string(Line, 1, [return, text]),
- UnusedString = els_utils:to_list(UnusedVariable),
- Replace =
- fun(Tok) ->
- case Tok of
- {var, [{text, UnusedString}, _], _} -> "_" ++ UnusedString;
- {var, [{text, VarName}, _], _} -> VarName;
- {_, [{text, Text }, _], _} -> Text;
- {_, [{text, Text }, _]} -> Text
- end
- end,
- UpdatedLine = lists:flatten(lists:map(Replace, Tokens)) ++ "\n",
- [ replace_lines_action( Uri
- , <<"Add '_' to '", UnusedVariable/binary, "'">>
- , ?CODE_ACTION_KIND_QUICKFIX
- , els_utils:to_binary(UpdatedLine)
- , Range)].
-
-%%------------------------------------------------------------------------------
+-spec edit(uri(), binary(), range()) -> workspace_edit().
+edit(Uri, Text, Range) ->
+ #{changes => #{Uri => [#{newText => Text, range => Range}]}}.
diff --git a/apps/els_lsp/src/els_execute_command_provider.erl b/apps/els_lsp/src/els_execute_command_provider.erl
index 5c6499849..77f54b6d8 100644
--- a/apps/els_lsp/src/els_execute_command_provider.erl
+++ b/apps/els_lsp/src/els_execute_command_provider.erl
@@ -24,8 +24,7 @@ is_enabled() -> true.
-spec options() -> map().
options() ->
- #{ commands => [ els_command:with_prefix(<<"replace-lines">>)
- , els_command:with_prefix(<<"server-info">>)
+ #{ commands => [ els_command:with_prefix(<<"server-info">>)
, els_command:with_prefix(<<"ct-run-test">>)
, els_command:with_prefix(<<"show-behaviour-usages">>)
, els_command:with_prefix(<<"suggest-spec">>)
@@ -45,17 +44,6 @@ handle_request({workspace_executecommand, Params}, State) ->
%%==============================================================================
-spec execute_command(els_command:command_id(), [any()]) -> [map()].
-execute_command(<<"replace-lines">>
- , [#{ <<"uri">> := Uri
- , <<"lines">> := Lines
- , <<"from">> := LineFrom
- , <<"to">> := LineTo }]) ->
- Method = <<"workspace/applyEdit">>,
- Params = #{ edit =>
- els_text_edit:edit_replace_text(Uri, Lines, LineFrom, LineTo)
- },
- els_server:send_request(Method, Params),
- [];
execute_command(<<"server-info">>, _Arguments) ->
{ok, Version} = application:get_key(?APP, vsn),
BinVersion = list_to_binary(Version),
diff --git a/apps/els_lsp/test/els_code_action_SUITE.erl b/apps/els_lsp/test/els_code_action_SUITE.erl
index 7dca29dfd..a4adcf919 100644
--- a/apps/els_lsp/test/els_code_action_SUITE.erl
+++ b/apps/els_lsp/test/els_code_action_SUITE.erl
@@ -11,6 +11,9 @@
%% Test cases
-export([ add_underscore_to_unused_var/1
+ , export_unused_function/1
+ , suggest_variable/1
+ , fix_module_name/1
]).
%%==============================================================================
@@ -56,29 +59,99 @@ end_per_testcase(TestCase, Config) ->
%%==============================================================================
-spec add_underscore_to_unused_var(config()) -> ok.
add_underscore_to_unused_var(Config) ->
- Uri = ?config(code_navigation_uri, Config),
+ Uri = ?config(code_action_uri, Config),
+ Range = els_protocol:range(#{from => {6, 3}, to => {6, 4}}),
Diag = #{ message => <<"variable 'A' is unused">>
- , range => #{ 'end' => #{character => 0, line => 80}
- , start => #{character => 0, line => 79}
- }
+ , range => Range
, severity => 2
, source => <<"Compiler">>
},
- Range = els_protocol:range(#{from => {80, 1}, to => {81, 1}}),
- PrefixedCommand = els_command:with_prefix(<<"replace-lines">>),
#{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
Expected =
- [ #{ command => #{ arguments => [ #{ from => 79
- , lines => <<" _A = X,\n">>
- , to => 80
- , uri => Uri
- }
- ]
- , command => PrefixedCommand
- , title => <<"Add '_' to 'A'">>
- }
- , kind => <<"quickfix">>
- , title => <<"Add '_' to 'A'">>
+ [ #{ edit => #{changes =>
+ #{ binary_to_atom(Uri, utf8) =>
+ [ #{ range => Range
+ , newText => <<"_A">>
+ }]
+ }}
+ , kind => <<"quickfix">>
+ , title => <<"Add '_' to 'A'">>
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
+
+-spec export_unused_function(config()) -> ok.
+export_unused_function(Config) ->
+ Uri = ?config(code_action_uri, Config),
+ Range = els_protocol:range(#{from => {12, 1}, to => {12, 10}}),
+ Diag = #{ message => <<"function function_c/0 is unused">>
+ , range => Range
+ , severity => 2
+ , source => <<"Compiler">>
+ },
+ #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
+ Expected =
+ [ #{ edit => #{changes =>
+ #{ binary_to_atom(Uri, utf8) =>
+ [ #{ range =>
+ #{'end' => #{ character => 0, line => 3},
+ start => #{character => 0, line => 3}}
+ , newText => <<"-export([function_c/0]).\n">>
+ }
+ ]}
+ }
+ , kind => <<"quickfix">>
+ , title => <<"Export function_c/0">>
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
+
+-spec suggest_variable(config()) -> ok.
+suggest_variable(Config) ->
+ Uri = ?config(code_action_uri, Config),
+ Range = els_protocol:range(#{from => {15, 9}, to => {15, 13}}),
+ Diag = #{ message => <<"variable 'Barf' is unbound">>
+ , range => Range
+ , severity => 3
+ , source => <<"Compiler">>
+ },
+ #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
+ Expected =
+ [ #{ edit => #{changes =>
+ #{ binary_to_atom(Uri, utf8) =>
+ [#{ range => Range
+ , newText => <<"Bar">>
+ }]
+ }}
+ , kind => <<"quickfix">>
+ , title => <<"Did you mean 'Bar'?">>
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
+
+-spec fix_module_name(config()) -> ok.
+fix_module_name(Config) ->
+ Uri = ?config(code_action_uri, Config),
+ Range = els_protocol:range(#{from => {1, 9}, to => {1, 25}}),
+ Diag = #{ message => <<"Module name 'code_action_oops' does not "
+ "match file name 'code_action'">>
+ , range => Range
+ , severity => 3
+ , source => <<"Compiler">>
+ },
+ #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
+ Expected =
+ [ #{ edit => #{changes =>
+ #{ binary_to_atom(Uri, utf8) =>
+ [#{ range => Range
+ , newText => <<"code_action">>
+ }]
+ }}
+ , kind => <<"quickfix">>
+ , title => <<"Change to -module(code_action).">>
}
],
?assertEqual(Expected, Result),
diff --git a/apps/els_lsp/test/els_completion_SUITE.erl b/apps/els_lsp/test/els_completion_SUITE.erl
index 8cc071322..e28d98ef9 100644
--- a/apps/els_lsp/test/els_completion_SUITE.erl
+++ b/apps/els_lsp/test/els_completion_SUITE.erl
@@ -423,6 +423,10 @@ default_completions(Config) ->
, kind => ?COMPLETION_ITEM_KIND_MODULE
, label => <<"code_navigation_undefined">>
}
+ , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
+ , kind => ?COMPLETION_ITEM_KIND_MODULE
+ , label => <<"code_action">>
+ }
| Functions ],
DefaultCompletion = els_completion_provider:keywords()
@@ -1265,4 +1269,4 @@ has_eep48(Module) ->
case catch code:get_doc(Module) of
{ok, _} -> true;
_ -> false
- end.
\ No newline at end of file
+ end.
From 48489ff1b72220c2bf3618ad5a56268fe0bf1074 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Sat, 19 Feb 2022 17:32:59 +0100
Subject: [PATCH 025/239] Drop OTP 21 (#1215)
With the upcoming OTP 25, let's drop support for OTP 21.
---
.github/workflows/build.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 57386bfcd..2fef06fda 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
platform: [ubuntu-latest]
- otp-version: [21, 22, 23, 24]
+ otp-version: [22, 23, 24]
runs-on: ${{ matrix.platform }}
container:
image: erlang:${{ matrix.otp-version }}
From ca11c068e1bd3c2b016878c50685fbb6a61bc082 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Sat, 19 Feb 2022 19:05:03 +0100
Subject: [PATCH 026/239] Edoc diagnostics (#1213)
* Import edoc_report module from OTP
* Fix include, indent file
* Add support for edoc diagnostics
* Only produce edoc for OTP 24
The priv directory contains some intentional broken edocs, but early versions of edoc try to generate edoc for those, too
---
.github/workflows/build.yml | 1 +
.../code_navigation/src/edoc_diagnostics.erl | 15 +++
apps/els_lsp/src/edoc_report.erl | 122 ++++++++++++++++++
apps/els_lsp/src/els_diagnostics.erl | 1 +
apps/els_lsp/src/els_edoc_diagnostics.erl | 93 +++++++++++++
apps/els_lsp/src/els_lsp.app.src | 1 +
apps/els_lsp/test/els_diagnostics_SUITE.erl | 31 +++++
apps/els_lsp/test/els_test.erl | 4 +-
elvis.config | 1 +
9 files changed, 268 insertions(+), 1 deletion(-)
create mode 100644 apps/els_lsp/priv/code_navigation/src/edoc_diagnostics.erl
create mode 100644 apps/els_lsp/src/edoc_report.erl
create mode 100644 apps/els_lsp/src/els_edoc_diagnostics.erl
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 2fef06fda..82efff2d7 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -72,6 +72,7 @@ jobs:
run: rebar3 do cover, coveralls send
- name: Produce Documentation
run: rebar3 edoc
+ if: ${{ matrix.otp-version == '24' }}
- name: Publish Documentation
uses: actions/upload-artifact@v2
with:
diff --git a/apps/els_lsp/priv/code_navigation/src/edoc_diagnostics.erl b/apps/els_lsp/priv/code_navigation/src/edoc_diagnostics.erl
new file mode 100644
index 000000000..ee3f20fbd
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/edoc_diagnostics.erl
@@ -0,0 +1,15 @@
+-module(edoc_diagnostics).
+
+-export([main/0]).
+
+%% @edoc Main function
+main() ->
+ internal().
+
+%% @docc internal
+internal() ->
+ ok.
+
+%% @doc `
+unused() ->
+ ok.
diff --git a/apps/els_lsp/src/edoc_report.erl b/apps/els_lsp/src/edoc_report.erl
new file mode 100644
index 000000000..dc6fc486d
--- /dev/null
+++ b/apps/els_lsp/src/edoc_report.erl
@@ -0,0 +1,122 @@
+%% =============================================================================
+%% An erlang_ls fork of Erlang/OTP's edoc_report
+%% =============================================================================
+%% The main reasons for the fork:
+%% * The edoc application does not offer an API to return a
+%% list of warnings and errors
+%% =====================================================================
+%% Licensed under the Apache License, Version 2.0 (the "License"); you may
+%% not use this file except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% Alternatively, you may use this file under the terms of the GNU Lesser
+%% General Public License (the "LGPL") as published by the Free Software
+%% Foundation; either version 2.1, or (at your option) any later version.
+%% If you wish to allow use of your version of this file only under the
+%% terms of the LGPL, you should delete the provisions above and replace
+%% them with the notice and other provisions required by the LGPL; see
+%% . If you do not delete the provisions
+%% above, a recipient may use your version of this file under the terms of
+%% either the Apache License or the LGPL.
+%%
+%% @private
+%% @copyright 2001-2003 Richard Carlsson
+%% @author Richard Carlsson
+%% @see edoc
+%% @end
+%% =====================================================================
+
+%% @doc EDoc verbosity/error reporting.
+
+-module(edoc_report).
+
+-compile({no_auto_import, [error/1, error/2, error/3]}).
+-export([error/1,
+ error/2,
+ error/3,
+ report/2,
+ report/3,
+ report/4,
+ warning/1,
+ warning/2,
+ warning/3,
+ warning/4]).
+
+-type where() :: any().
+-type what() :: any().
+-type line() :: non_neg_integer().
+-type severity() :: warning | error.
+
+-include_lib("edoc/src/edoc.hrl").
+
+-define(DICT_KEY, edoc_diagnostics).
+
+-spec error(what()) -> ok.
+error(What) ->
+ error([], What).
+
+-spec error(where(), what()) -> ok.
+error(Where, What) ->
+ error(0, Where, What).
+
+-spec error(line(), where(), any()) -> ok.
+error(Line, Where, S) when is_list(S) ->
+ report(Line, Where, S, [], error);
+error(Line, Where, {S, D}) when is_list(S) ->
+ report(Line, Where, S, D, error);
+error(Line, Where, {format_error, M, D}) ->
+ report(Line, Where, M:format_error(D), [], error).
+
+-spec warning(string()) -> ok.
+warning(S) ->
+ warning(S, []).
+
+-spec warning(string(), [any()]) -> ok.
+warning(S, Vs) ->
+ warning([], S, Vs).
+
+-spec warning(where(), string(), [any()]) -> ok.
+warning(Where, S, Vs) ->
+ warning(0, Where, S, Vs).
+
+-spec warning(line(), where(), string(), [any()]) -> ok.
+warning(L, Where, S, Vs) ->
+ report(L, Where, S, Vs, warning).
+
+-spec report(string(), [any()]) -> ok.
+report(S, Vs) ->
+ report([], S, Vs).
+
+-spec report(where(), string(), [any()]) -> ok.
+report(Where, S, Vs) ->
+ report(0, Where, S, Vs).
+
+-spec report(line(), where(), string(), [any()]) -> ok.
+report(L, Where, S, Vs) ->
+ report(L, Where, S, Vs, error).
+
+-spec report(line(), where(), string(), [any()], severity()) -> ok.
+report(L, Where, S, Vs, Severity) ->
+ put(?DICT_KEY, [{L, where(Where), S, Vs, Severity}|get(?DICT_KEY)]).
+
+-spec where([any()] |
+ {string(), module | footer | header | {atom(), non_neg_integer()}})
+ -> string().
+where({File, module}) ->
+ io_lib:fwrite("~ts, in module header: ", [File]);
+where({File, footer}) ->
+ io_lib:fwrite("~ts, in module footer: ", [File]);
+where({File, header}) ->
+ io_lib:fwrite("~ts, in header file: ", [File]);
+where({File, {F, A}}) ->
+ io_lib:fwrite("~ts, function ~ts/~w: ", [File, F, A]);
+where([]) ->
+ io_lib:fwrite("~s: ", [?APPLICATION]);
+where(File) when is_list(File) ->
+ File ++ ": ".
diff --git a/apps/els_lsp/src/els_diagnostics.erl b/apps/els_lsp/src/els_diagnostics.erl
index 9c9b4b303..9786bdddb 100644
--- a/apps/els_lsp/src/els_diagnostics.erl
+++ b/apps/els_lsp/src/els_diagnostics.erl
@@ -61,6 +61,7 @@ available_diagnostics() ->
, <<"compiler">>
, <<"crossref">>
, <<"dialyzer">>
+ , <<"edoc">>
, <<"gradualizer">>
, <<"elvis">>
, <<"unused_includes">>
diff --git a/apps/els_lsp/src/els_edoc_diagnostics.erl b/apps/els_lsp/src/els_edoc_diagnostics.erl
new file mode 100644
index 000000000..1a3dc7ab9
--- /dev/null
+++ b/apps/els_lsp/src/els_edoc_diagnostics.erl
@@ -0,0 +1,93 @@
+%%==============================================================================
+%% Edoc diagnostics
+%%==============================================================================
+-module(els_edoc_diagnostics).
+
+%%==============================================================================
+%% Behaviours
+%%==============================================================================
+-behaviour(els_diagnostics).
+
+%%==============================================================================
+%% Exports
+%%==============================================================================
+-export([ is_default/0
+ , run/1
+ , source/0
+ ]).
+
+%%==============================================================================
+%% Includes
+%%==============================================================================
+-include("els_lsp.hrl").
+
+%%==============================================================================
+%% Callback Functions
+%%==============================================================================
+
+-spec is_default() -> boolean().
+is_default() ->
+ false.
+
+%% The edoc application currently does not offer an API to
+%% programmatically return a list of warnings and errors. Instead,
+%% it simply outputs the warnings and errors to the standard
+%% output.
+%% We created an issue for the OTP team to address this:
+%% https://github.com/erlang-ls/erlang_ls/issues/384
+%% Meanwhile, hackity-hack!
+%% Let's override the reporting module for edoc (edoc_report)
+%% and (ab)use the process dictionary to collect the list of
+%% warnings and errors.
+-spec run(uri()) -> [els_diagnostics:diagnostic()].
+run(Uri) ->
+ Paths = [els_utils:to_list(els_uri:path(Uri))],
+ Fun = fun(Dir) ->
+ Options = edoc_options(Dir),
+ put(edoc_diagnostics, []),
+ try
+ edoc:run(Paths, Options)
+ catch
+ _:_:_ ->
+ ok
+ end,
+ [make_diagnostic(L, Format, Args, Severity) ||
+ {L, _Where, Format, Args, Severity}
+ <- get(edoc_diagnostics), L =/= 0]
+ end,
+ tempdir:mktmp(Fun).
+
+-spec source() -> binary().
+source() ->
+ <<"Edoc">>.
+
+%%==============================================================================
+%% Internal Functions
+%%==============================================================================
+-spec edoc_options(string()) -> proplists:proplist().
+edoc_options(Dir) ->
+ Macros = [{N, V} || {'d', N, V} <- els_compiler_diagnostics:macro_options()],
+ Includes = [I || {i, I} <- els_compiler_diagnostics:include_options()],
+ [ {preprocess, true}
+ , {macros, Macros}
+ , {includes, Includes}
+ , {dir, Dir}
+ ].
+
+-spec make_diagnostic(pos_integer(), string(), [any()], warning | error) ->
+ els_diagnostics:diagnostic().
+make_diagnostic(Line, Format, Args, Severity0) ->
+ Severity = severity(Severity0),
+ Message = els_utils:to_binary(io_lib:format(Format, Args)),
+ els_diagnostics:make_diagnostic( els_protocol:range(#{ from => {Line, 1}
+ , to => {Line + 1, 1}
+ })
+ , Message
+ , Severity
+ , source()).
+
+-spec severity(warning | error) -> els_diagnostics:severity().
+severity(warning) ->
+ ?DIAGNOSTIC_WARNING;
+severity(error) ->
+ ?DIAGNOSTIC_ERROR.
diff --git a/apps/els_lsp/src/els_lsp.app.src b/apps/els_lsp/src/els_lsp.app.src
index 5e62a74dd..e64287d73 100644
--- a/apps/els_lsp/src/els_lsp.app.src
+++ b/apps/els_lsp/src/els_lsp.app.src
@@ -6,6 +6,7 @@
{applications, [
kernel,
stdlib,
+ edoc,
docsh,
elvis_core,
rebar3_format,
diff --git a/apps/els_lsp/test/els_diagnostics_SUITE.erl b/apps/els_lsp/test/els_diagnostics_SUITE.erl
index 41c633579..3ca60fcc5 100644
--- a/apps/els_lsp/test/els_diagnostics_SUITE.erl
+++ b/apps/els_lsp/test/els_diagnostics_SUITE.erl
@@ -43,6 +43,7 @@
, gradualizer/1
, module_name_check/1
, module_name_check_whitespace/1
+ , edoc_main/1
]).
%%==============================================================================
@@ -125,6 +126,11 @@ init_per_testcase(TestCase, Config) when TestCase =:= gradualizer ->
meck:expect(els_gradualizer_diagnostics, is_default, 0, true),
els_mock_diagnostics:setup(),
els_test_utils:init_per_testcase(TestCase, Config);
+init_per_testcase(TestCase, Config) when TestCase =:= edoc_main ->
+ meck:new(els_edoc_diagnostics, [passthrough, no_link]),
+ meck:expect(els_edoc_diagnostics, is_default, 0, true),
+ els_mock_diagnostics:setup(),
+ els_test_utils:init_per_testcase(TestCase, Config);
init_per_testcase(TestCase, Config) ->
els_mock_diagnostics:setup(),
els_test_utils:init_per_testcase(TestCase, Config).
@@ -166,6 +172,11 @@ end_per_testcase(TestCase, Config) when TestCase =:= gradualizer ->
els_test_utils:end_per_testcase(TestCase, Config),
els_mock_diagnostics:teardown(),
ok;
+end_per_testcase(TestCase, Config) when TestCase =:= edoc_main ->
+ meck:unload(els_edoc_diagnostics),
+ els_test_utils:end_per_testcase(TestCase, Config),
+ els_mock_diagnostics:teardown(),
+ ok;
end_per_testcase(TestCase, Config) ->
els_test_utils:end_per_testcase(TestCase, Config),
els_mock_diagnostics:teardown(),
@@ -679,6 +690,26 @@ module_name_check_whitespace(_Config) ->
Hints = [],
els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+-spec edoc_main(config()) -> ok.
+edoc_main(_Config) ->
+ Path = src_path("edoc_diagnostics.erl"),
+ Source = <<"Edoc">>,
+ Errors = [ #{ message => <<"`-quote ended unexpectedly at line 13">>
+ , range => {{12, 0}, {13, 0}}
+ }
+ ],
+ Warnings = [ #{ message =>
+ <<"tag @edoc not recognized.">>
+ , range => {{4, 0}, {5, 0}}
+ }
+ , #{ message =>
+ <<"tag @docc not recognized.">>
+ , range => {{8, 0}, {9, 0}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+
%%==============================================================================
%% Internal Functions
%%==============================================================================
diff --git a/apps/els_lsp/test/els_test.erl b/apps/els_lsp/test/els_test.erl
index 1eb703bea..4d6195dd9 100644
--- a/apps/els_lsp/test/els_test.erl
+++ b/apps/els_lsp/test/els_test.erl
@@ -79,7 +79,9 @@ assert_diagnostics(Source, Expected, Diagnostics, Severity) ->
Filtered = [D || #{severity := S} = D <- Diagnostics, S =:= Severity],
Simplified = [simplify_diagnostic(D) || D <- Filtered],
FixedExpected = [maybe_fix_range(Source, D) || D <- Expected],
- ?assertEqual(FixedExpected, Simplified, Filtered).
+ ?assertEqual(lists:sort(FixedExpected),
+ lists:sort(Simplified),
+ Filtered).
-spec simplify_diagnostic(els_diagnostics:diagnostic()) ->
simplified_diagnostic().
diff --git a/elvis.config b/elvis.config
index b7b1f3ffc..7a5060a1d 100644
--- a/elvis.config
+++ b/elvis.config
@@ -34,6 +34,7 @@
, els_tcp
, els_dap_test_utils
, els_test_utils
+ , edoc_report
]}}
, {elvis_text_style, line_length, #{limit => 80, skip_comments => false}}
, {elvis_style, operator_spaces, #{ rules => [ {right, ","}
From c28a08ff4a3382e00d6881172da2fa44878390b1 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Sun, 20 Feb 2022 17:39:31 +0100
Subject: [PATCH 027/239] Drop OTP 21 support in rebar.config and README
(#1217)
---
README.md | 2 +-
rebar.config | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 09c64eb78..c3d2c3fb6 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ An Erlang server implementing Microsoft's Language Server Protocol 3.15.
## Minimum Requirements
-* [Erlang OTP 21+](https://github.com/erlang/otp)
+* [Erlang OTP 22+](https://github.com/erlang/otp)
* [rebar3 3.9.1+](https://github.com/erlang/rebar3)
## Quickstart
diff --git a/rebar.config b/rebar.config
index 3aa65e0c4..74993eacf 100644
--- a/rebar.config
+++ b/rebar.config
@@ -30,7 +30,7 @@
]
}.
-{minimum_otp_vsn, "21.0"}.
+{minimum_otp_vsn, "22.0"}.
{escript_emu_args, "%%! -connect_all false\n" }.
{escript_incl_extra, [{"els_lsp/priv/snippets/*", "_build/default/lib/"}]}.
From 451428da10b38cd0b28dbbf56c47b1a8ecfc2c1a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Thu, 24 Feb 2022 08:07:34 +0100
Subject: [PATCH 028/239] Use Jaro distance for the unbound variable code
action (#1225)
---
apps/els_core/src/els_utils.erl | 149 ++++++++++++++++++
apps/els_lsp/src/els_code_action_provider.erl | 24 ++-
2 files changed, 158 insertions(+), 15 deletions(-)
diff --git a/apps/els_core/src/els_utils.erl b/apps/els_core/src/els_utils.erl
index d4a3e169e..ded6b42b1 100644
--- a/apps/els_core/src/els_utils.erl
+++ b/apps/els_core/src/els_utils.erl
@@ -21,6 +21,7 @@
, base64_encode_term/1
, base64_decode_term/1
, levenshtein_distance/2
+ , jaro_distance/2
]).
@@ -474,3 +475,151 @@ levenshtein_distance([_SH|ST] = S, [_TH|TT] = T, Cache) ->
L = 1 + lists:min([L1, L2, L3]),
{L, maps:put({S, T}, L, C3)}
end.
+
+%%% Jaro distance
+
+%% @doc Computes the Jaro distance (similarity) between two strings.
+%%
+%% Returns a float value between 0.0 (equates to no similarity) and 1.0 (is an
+%% exact match) representing Jaro distance between String1 and String2.
+%%
+%% The Jaro distance metric is designed and best suited for short strings such
+%% as person names. Erlang LS uses this function to provide the "did you
+%% mean?" functionality.
+%%
+%% @end
+-spec jaro_distance(S, S) -> float() when S :: string() | binary().
+jaro_distance(Str, Str) -> 1.0;
+jaro_distance(_, "") -> 0.0;
+jaro_distance("", _) -> 0.0;
+jaro_distance(Str1, Str2) when is_binary(Str1),
+ is_binary(Str2) ->
+ jaro_distance(binary_to_list(Str1),
+ binary_to_list(Str2));
+jaro_distance(Str1, Str2) when is_list(Str1),
+ is_list(Str2) ->
+ Len1 = length(Str1),
+ Len2 = length(Str2),
+ case jaro_match(Str1, Len1, Str2, Len2) of
+ {0, _Trans} ->
+ 0.0;
+ {Comm, Trans} ->
+ (Comm / Len1 + Comm / Len2 + (Comm - Trans) / Comm) / 3
+ end.
+
+-type jaro_state() :: {integer(), integer(), integer()}.
+-type jaro_range() :: {integer(), integer()}.
+
+-spec jaro_match(string(), integer(), string(), integer()) ->
+ {integer(), integer()}.
+jaro_match(Chars1, Len1, Chars2, Len2) when Len1 < Len2 ->
+ jaro_match(Chars1, Chars2, (Len2 div 2) - 1);
+jaro_match(Chars1, Len1, Chars2, _Len2) ->
+ jaro_match(Chars2, Chars1, (Len1 div 2) - 1).
+
+-spec jaro_match(string(), string(), integer()) -> {integer(), integer()}.
+jaro_match(Chars1, Chars2, Lim) ->
+ jaro_match(Chars1, Chars2, {0, Lim}, {0, 0, -1}, 0).
+
+-spec jaro_match(string(), string(), jaro_range(), jaro_state(), integer()) ->
+ {integer(), integer()}.
+jaro_match([Char|Rest], Chars0, Range, State0, Idx) ->
+ {Chars, State} = jaro_submatch(Char, Chars0, Range, State0, Idx),
+ case Range of
+ {Lim, Lim} ->
+ jaro_match(Rest, tl(Chars), Range, State, Idx + 1);
+ {Pre, Lim} ->
+ jaro_match(Rest, Chars, {Pre + 1, Lim}, State, Idx + 1)
+ end;
+jaro_match([], _, _, {Comm, Trans, _}, _) ->
+ {Comm, Trans}.
+
+-spec jaro_submatch(char(), string(), jaro_range(), jaro_state(), integer()) ->
+ {string(), jaro_state()}.
+jaro_submatch(Char, Chars0, {Pre, _} = Range, State, Idx) ->
+ case jaro_detect(Char, Chars0, Range) of
+ undefined ->
+ {Chars0, State};
+ {SubIdx, Chars} ->
+ {Chars, jaro_proceed(State, Idx - Pre + SubIdx)}
+ end.
+
+-spec jaro_detect(char(), string(), jaro_range()) ->
+ {integer(), string()} | undefined.
+jaro_detect(Char, Chars, {Pre, Lim}) ->
+ jaro_detect(Char, Chars, Pre + 1 + Lim, 0, []).
+
+-spec jaro_detect(char(), string(), integer(), integer(), list()) ->
+ {integer(), string()} | undefined.
+jaro_detect(_Char, _Chars, 0, _Idx, _Acc) ->
+ undefined;
+jaro_detect(_Char, [], _Lim, _Idx, _Acc) ->
+ undefined;
+jaro_detect(Char, [Char | Rest], _Lim, Idx, Acc) ->
+ {Idx, lists:reverse(Acc) ++ [undefined | Rest]};
+jaro_detect(Char, [Other | Rest], Lim, Idx, Acc) ->
+ jaro_detect(Char, Rest, Lim - 1, Idx + 1, [Other | Acc]).
+
+-spec jaro_proceed(jaro_state(), integer()) -> jaro_state().
+jaro_proceed({Comm, Trans, Former}, Current) when Current < Former ->
+ {Comm + 1, Trans + 1, Current};
+jaro_proceed({Comm, Trans, _Former}, Current) ->
+ {Comm + 1, Trans, Current}.
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+jaro_distance_test_() ->
+ [ ?_assertEqual( jaro_distance("same", "same")
+ , 1.0)
+ , ?_assertEqual( jaro_distance("any", "")
+ , 0.0)
+ , ?_assertEqual( jaro_distance("", "any")
+ , 0.0)
+ , ?_assertEqual( jaro_distance("martha", "marhta")
+ , 0.9444444444444445)
+ , ?_assertEqual( jaro_distance("martha", "marhha")
+ , 0.888888888888889)
+ , ?_assertEqual( jaro_distance("marhha", "martha")
+ , 0.888888888888889)
+ , ?_assertEqual( jaro_distance("dwayne", "duane")
+ , 0.8222222222222223)
+ , ?_assertEqual( jaro_distance("dixon", "dicksonx")
+ , 0.7666666666666666)
+ , ?_assertEqual( jaro_distance("xdicksonx", "dixon")
+ , 0.7851851851851852)
+ , ?_assertEqual( jaro_distance("shackleford", "shackelford")
+ , 0.9696969696969697)
+ , ?_assertEqual( jaro_distance("dunningham", "cunnigham")
+ , 0.8962962962962964)
+ , ?_assertEqual( jaro_distance("nichleson", "nichulson")
+ , 0.9259259259259259)
+ , ?_assertEqual( jaro_distance("jones", "johnson")
+ , 0.7904761904761904)
+ , ?_assertEqual( jaro_distance("massey", "massie")
+ , 0.888888888888889)
+ , ?_assertEqual( jaro_distance("abroms", "abrams")
+ , 0.888888888888889)
+ , ?_assertEqual( jaro_distance("hardin", "martinez")
+ , 0.7222222222222222)
+ , ?_assertEqual( jaro_distance("itman", "smith")
+ , 0.4666666666666666)
+ , ?_assertEqual( jaro_distance("jeraldine", "geraldine")
+ , 0.9259259259259259)
+ , ?_assertEqual( jaro_distance("michelle", "michael")
+ , 0.8690476190476191)
+ , ?_assertEqual( jaro_distance("julies", "julius")
+ , 0.888888888888889)
+ , ?_assertEqual( jaro_distance("tanya", "tonya")
+ , 0.8666666666666667)
+ , ?_assertEqual( jaro_distance("sean", "susan")
+ , 0.7833333333333333)
+ , ?_assertEqual( jaro_distance("jon", "john")
+ , 0.9166666666666666)
+ , ?_assertEqual( jaro_distance("jon", "jan")
+ , 0.7777777777777777)
+ , ?_assertEqual( jaro_distance("семена", "стремя")
+ , 0.6666666666666666)
+ ].
+
+-endif.
diff --git a/apps/els_lsp/src/els_code_action_provider.erl b/apps/els_lsp/src/els_code_action_provider.erl
index d0e77656c..b98274c31 100644
--- a/apps/els_lsp/src/els_code_action_provider.erl
+++ b/apps/els_lsp/src/els_code_action_provider.erl
@@ -101,21 +101,15 @@ action_suggest_variable(Uri, Range, [Var]) ->
#{range := R, id := Id} <- POIs,
els_range:in(R, ScopeRange),
els_range:compare(R, VarRange)],
- case [{els_utils:levenshtein_distance(V, Var), V} ||
- V <- VarsInScope,
- V =/= Var,
- binary:at(Var, 0) =:= binary:at(V, 0)]
- of
- [] ->
- [];
- VariableDistances ->
- {_, SimilarVariable} = lists:min(VariableDistances),
- [ make_edit_action( Uri
- , <<"Did you mean '", SimilarVariable/binary, "'?">>
- , ?CODE_ACTION_KIND_QUICKFIX
- , SimilarVariable
- , els_protocol:range(VarRange)) ]
- end;
+ VariableDistances =
+ [{els_utils:jaro_distance(V, Var), V} || V <- VarsInScope, V =/= Var],
+ [ make_edit_action( Uri
+ , <<"Did you mean '", V/binary, "'?">>
+ , ?CODE_ACTION_KIND_QUICKFIX
+ , V
+ , els_protocol:range(VarRange))
+ || {Distance, V} <- lists:reverse(lists:usort(VariableDistances)),
+ Distance > 0.8];
error ->
[]
end.
From 0e7814e6c2a0f82dd2d43bfe68721377d91ec58d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Thu, 24 Feb 2022 08:24:16 +0100
Subject: [PATCH 029/239] Better support for goto definition when parsing is
incomplete (#1224)
* Better support for goto definition when parsing is incomplete
* Remove els_code_actions
* Use els_parser:parse_incomplete_text to preserve ranges
* Fix invalid spec
* Break out functions to els_incomplete_parser
---
apps/els_core/src/els_text.erl | 7 +++
.../src/code_navigation_broken.erl | 19 +++++++
apps/els_lsp/src/els_code_navigation.erl | 3 +-
apps/els_lsp/src/els_definition_provider.erl | 53 ++++++++++++++++++-
apps/els_lsp/src/els_incomplete_parser.erl | 30 +++++++++++
apps/els_lsp/src/els_parser.erl | 6 ++-
apps/els_lsp/test/els_completion_SUITE.erl | 4 ++
apps/els_lsp/test/els_definition_SUITE.erl | 21 ++++++++
.../test/els_workspace_symbol_SUITE.erl | 11 ++++
9 files changed, 150 insertions(+), 4 deletions(-)
create mode 100644 apps/els_lsp/priv/code_navigation/src/code_navigation_broken.erl
create mode 100644 apps/els_lsp/src/els_incomplete_parser.erl
diff --git a/apps/els_core/src/els_text.erl b/apps/els_core/src/els_text.erl
index 7644df235..37fe2cd93 100644
--- a/apps/els_core/src/els_text.erl
+++ b/apps/els_core/src/els_text.erl
@@ -7,6 +7,7 @@
, line/2
, line/3
, range/3
+ , split_at_line/2
, tokens/1
, apply_edits/2
]).
@@ -43,6 +44,12 @@ range(Text, StartLoc, EndLoc) ->
EndPos = pos(LineStarts, EndLoc),
binary:part(Text, StartPos, EndPos - StartPos).
+-spec split_at_line(text(), line_num()) -> {text(), text()}.
+split_at_line(Text, Line) ->
+ StartPos = pos(line_starts(Text), {Line + 1, 1}),
+ <> = Text,
+ {Left, Right}.
+
%% @doc Return tokens from text.
-spec tokens(text()) -> [any()].
tokens(Text) ->
diff --git a/apps/els_lsp/priv/code_navigation/src/code_navigation_broken.erl b/apps/els_lsp/priv/code_navigation/src/code_navigation_broken.erl
new file mode 100644
index 000000000..7b7237ff0
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/code_navigation_broken.erl
@@ -0,0 +1,19 @@
+-module(code_navigation_broken).
+
+function_a() ->
+ ok.
+
+function_b() ->
+ function_a() % missing comma, breaks parsing of this function!
+ function_a(),
+ case function_a() of
+ ok ->
+ function_a(),
+ case function_a() of
+ ok ->
+ ok
+ end
+ end,
+ function_a(
+ ),
+ function_a().
diff --git a/apps/els_lsp/src/els_code_navigation.erl b/apps/els_lsp/src/els_code_navigation.erl
index 15ddb5cb5..b95c53cce 100644
--- a/apps/els_lsp/src/els_code_navigation.erl
+++ b/apps/els_lsp/src/els_code_navigation.erl
@@ -31,7 +31,8 @@ goto_definition( Uri
%% first occurrence of the variable in variable scope.
case find_in_scope(Uri, Var) of
[Var|_] -> {error, already_at_definition};
- [POI|_] -> {ok, Uri, POI}
+ [POI|_] -> {ok, Uri, POI};
+ [] -> {error, nothing_in_scope} % Probably due to parse error
end;
goto_definition( _Uri
, #{ kind := Kind, id := {M, F, A} }
diff --git a/apps/els_lsp/src/els_definition_provider.erl b/apps/els_lsp/src/els_definition_provider.erl
index 937cf3758..0c92ac3b4 100644
--- a/apps/els_lsp/src/els_definition_provider.erl
+++ b/apps/els_lsp/src/els_definition_provider.erl
@@ -30,7 +30,14 @@ handle_request({definition, Params}, State) ->
POIs = els_dt_document:get_element_at_pos(Document, Line + 1, Character + 1),
case goto_definition(Uri, POIs) of
null ->
- els_references_provider:handle_request({references, Params}, State);
+ #{text := Text} = Document,
+ IncompletePOIs = match_incomplete(Text, {Line, Character}),
+ case goto_definition(Uri, IncompletePOIs) of
+ null ->
+ els_references_provider:handle_request({references, Params}, State);
+ GoTo ->
+ {GoTo, State}
+ end;
GoTo ->
{GoTo, State}
end.
@@ -45,3 +52,47 @@ goto_definition(Uri, [POI|Rest]) ->
_ ->
goto_definition(Uri, Rest)
end.
+
+-spec match_incomplete(binary(), pos()) -> [poi()].
+match_incomplete(Text, Pos) ->
+ %% Try parsing subsets of text to find a matching POI at Pos
+ match_after(Text, Pos) ++ match_line(Text, Pos).
+
+-spec match_after(binary(), pos()) -> [poi()].
+match_after(Text, {Line, Character}) ->
+ %% Try to parse current line and the lines after it
+ POIs = els_incomplete_parser:parse_after(Text, Line),
+ MatchingPOIs = match_pois(POIs, {1, Character + 1}),
+ fix_line_offsets(MatchingPOIs, Line).
+
+-spec match_line(binary(), pos()) -> [poi()].
+match_line(Text, {Line, Character}) ->
+ %% Try to parse only current line
+ POIs = els_incomplete_parser:parse_line(Text, Line),
+ MatchingPOIs = match_pois(POIs, {1, Character + 1}),
+ fix_line_offsets(MatchingPOIs, Line).
+
+-spec match_pois([poi()], pos()) -> [poi()].
+match_pois(POIs, Pos) ->
+ els_poi:sort(els_poi:match_pos(POIs, Pos)).
+
+-spec fix_line_offsets([poi()], integer()) -> [poi()].
+fix_line_offsets(POIs, Offset) ->
+ [fix_line_offset(POI, Offset) || POI <- POIs].
+
+-spec fix_line_offset(poi(), integer()) -> poi().
+fix_line_offset(#{range := #{from := {FromL, FromC},
+ to := {ToL, ToC}}} = POI, Offset) ->
+ %% TODO: Fix other ranges too
+ POI#{range => #{from => {FromL + Offset, FromC},
+ to => {ToL + Offset, ToC}
+ }}.
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+fix_line_offset_test() ->
+ In = #{range => #{from => {1, 16}, to => {1, 32}}},
+ ?assertMatch( #{range := #{from := {66, 16}, to := {66, 32}}}
+ , fix_line_offset(In, 65)).
+
+-endif.
diff --git a/apps/els_lsp/src/els_incomplete_parser.erl b/apps/els_lsp/src/els_incomplete_parser.erl
new file mode 100644
index 000000000..27ef12805
--- /dev/null
+++ b/apps/els_lsp/src/els_incomplete_parser.erl
@@ -0,0 +1,30 @@
+-module(els_incomplete_parser).
+-export([parse_after/2]).
+-export([parse_line/2]).
+
+-include("els_lsp.hrl").
+-include_lib("kernel/include/logger.hrl").
+
+-spec parse_after(binary(), integer()) -> [poi()].
+parse_after(Text, Line) ->
+ {_, AfterText} = els_text:split_at_line(Text, Line),
+ {ok, POIs} = els_parser:parse(AfterText),
+ POIs.
+
+-spec parse_line(binary(), integer()) -> [poi()].
+parse_line(Text, Line) ->
+ LineText0 = string:trim(els_text:line(Text, Line), trailing, ",;"),
+ case els_parser:parse(LineText0) of
+ {ok, []} ->
+ LineStr = els_utils:to_list(LineText0),
+ case lists:reverse(LineStr) of
+ "fo " ++ _ -> %% Kludge to parse "case foo() of"
+ LineText1 = < _ end">>,
+ {ok, POIs} = els_parser:parse(LineText1),
+ POIs;
+ _ ->
+ []
+ end;
+ {ok, POIs} ->
+ POIs
+ end.
diff --git a/apps/els_lsp/src/els_parser.erl b/apps/els_lsp/src/els_parser.erl
index f0f11648b..6d5dbb35c 100644
--- a/apps/els_lsp/src/els_parser.erl
+++ b/apps/els_lsp/src/els_parser.erl
@@ -6,12 +6,14 @@
%%==============================================================================
%% Exports
%%==============================================================================
--export([ parse/1]).
+-export([ parse/1
+ , parse_incomplete_text/2
+ , points_of_interest/1
+ ]).
%% For manual use only, to test the parser
-export([ parse_file/1
, parse_text/1
- , parse_incomplete_text/2
]).
%%==============================================================================
diff --git a/apps/els_lsp/test/els_completion_SUITE.erl b/apps/els_lsp/test/els_completion_SUITE.erl
index e28d98ef9..6754b4ecf 100644
--- a/apps/els_lsp/test/els_completion_SUITE.erl
+++ b/apps/els_lsp/test/els_completion_SUITE.erl
@@ -423,6 +423,10 @@ default_completions(Config) ->
, kind => ?COMPLETION_ITEM_KIND_MODULE
, label => <<"code_navigation_undefined">>
}
+ , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
+ , kind => ?COMPLETION_ITEM_KIND_MODULE
+ , label => <<"code_navigation_broken">>
+ }
, #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
, kind => ?COMPLETION_ITEM_KIND_MODULE
, label => <<"code_action">>
diff --git a/apps/els_lsp/test/els_definition_SUITE.erl b/apps/els_lsp/test/els_definition_SUITE.erl
index 4b23e6034..dc2ed222b 100644
--- a/apps/els_lsp/test/els_definition_SUITE.erl
+++ b/apps/els_lsp/test/els_definition_SUITE.erl
@@ -49,6 +49,7 @@
, variable/1
, opaque_application_remote/1
, opaque_application_user/1
+ , parse_incomplete/1
]).
%%==============================================================================
@@ -505,3 +506,23 @@ opaque_application_user(Config) ->
?assertEqual( els_protocol:range(#{from => {20, 1}, to => {20, 34}})
, Range),
ok.
+
+-spec parse_incomplete(config()) -> ok.
+parse_incomplete(Config) ->
+ Uri = ?config(code_navigation_broken_uri, Config),
+ Range = els_protocol:range(#{from => {3, 1}, to => {3, 11}}),
+ ?assertMatch( #{result := #{range := Range, uri := Uri}}
+ , els_client:definition(Uri, 7, 3)),
+ ?assertMatch( #{result := #{range := Range, uri := Uri}}
+ , els_client:definition(Uri, 8, 3)),
+ ?assertMatch( #{result := #{range := Range, uri := Uri}}
+ , els_client:definition(Uri, 9, 8)),
+ ?assertMatch( #{result := #{range := Range, uri := Uri}}
+ , els_client:definition(Uri, 11, 7)),
+ ?assertMatch( #{result := #{range := Range, uri := Uri}}
+ , els_client:definition(Uri, 12, 12)),
+ ?assertMatch( #{result := #{range := Range, uri := Uri}}
+ , els_client:definition(Uri, 17, 3)),
+ ?assertMatch( #{result := #{range := Range, uri := Uri}}
+ , els_client:definition(Uri, 19, 3)),
+ ok.
diff --git a/apps/els_lsp/test/els_workspace_symbol_SUITE.erl b/apps/els_lsp/test/els_workspace_symbol_SUITE.erl
index 95dc60103..e443446cc 100644
--- a/apps/els_lsp/test/els_workspace_symbol_SUITE.erl
+++ b/apps/els_lsp/test/els_workspace_symbol_SUITE.erl
@@ -66,6 +66,7 @@ query_multiple(Config) ->
ExtraUri = ?config(code_navigation_extra_uri, Config),
TypesUri = ?config(code_navigation_types_uri, Config),
UndefUri = ?config(code_navigation_undefined_uri, Config),
+ BrokenUri = ?config(code_navigation_broken_uri, Config),
#{result := Result} = els_client:workspace_symbol(Query),
Expected = [ #{ kind => ?SYMBOLKIND_MODULE
, location =>
@@ -107,6 +108,16 @@ query_multiple(Config) ->
}
, name => <<"code_navigation_undefined">>
}
+ , #{ kind => ?SYMBOLKIND_MODULE
+ , location =>
+ #{ range =>
+ #{ 'end' => #{character => 0, line => 0}
+ , start => #{character => 0, line => 0}
+ }
+ , uri => BrokenUri
+ }
+ , name => <<"code_navigation_broken">>
+ }
],
?assertEqual(lists:sort(Expected), lists:sort(Result)),
ok.
From c5444980f32f0cffe3496a1f09512525bd1355ed Mon Sep 17 00:00:00 2001
From: zhangzhenfang
Date: Thu, 24 Feb 2022 21:45:38 +0800
Subject: [PATCH 030/239] Supply quickfix to remove unused macro (#1226)
---
.../priv/code_navigation/src/code_action.erl | 4 ++
apps/els_lsp/src/els_code_action_provider.erl | 18 +++++++
apps/els_lsp/src/els_range.erl | 5 ++
apps/els_lsp/test/els_code_action_SUITE.erl | 50 ++++++++++++++++---
4 files changed, 71 insertions(+), 6 deletions(-)
diff --git a/apps/els_lsp/priv/code_navigation/src/code_action.erl b/apps/els_lsp/priv/code_navigation/src/code_action.erl
index 6f170b684..04e9c74a2 100644
--- a/apps/els_lsp/priv/code_navigation/src/code_action.erl
+++ b/apps/els_lsp/priv/code_navigation/src/code_action.erl
@@ -1,3 +1,5 @@
+%% Important: this file feeds the tests in the els_code_action_SUITE.erl.
+%% Please only add new cases from bottom, otherwise it might break those tests.
-module(code_action_oops).
-export([function_a/0]).
@@ -13,3 +15,5 @@ function_c() ->
Foo = 1,
Bar = 2,
Foo + Barf.
+
+-define(TIMEOUT, 200).
diff --git a/apps/els_lsp/src/els_code_action_provider.erl b/apps/els_lsp/src/els_code_action_provider.erl
index b98274c31..f37c85d3c 100644
--- a/apps/els_lsp/src/els_code_action_provider.erl
+++ b/apps/els_lsp/src/els_code_action_provider.erl
@@ -42,6 +42,7 @@ make_code_action(Uri, #{<<"message">> := Message, <<"range">> := Range}) ->
, {"variable '(.*)' is unbound", fun action_suggest_variable/3}
, {"Module name '(.*)' does not match file name '(.*)'",
fun action_fix_module_name/3}
+ , {"Unused macro: (.*)", fun action_remove_macro/3}
], Uri, Range, Message).
-spec make_code_action([{string(), Fun}], uri(), range(), binary()) -> [map()]
@@ -129,6 +130,23 @@ action_fix_module_name(Uri, Range0, [ModName, FileName]) ->
[]
end.
+- spec action_remove_macro(uri(), range(), [binary()]) -> [map()].
+action_remove_macro(Uri, Range, [Macro]) ->
+ %% Supply a quickfix to remove the unused Macro
+ {ok, Document} = els_utils:lookup_document(Uri),
+ POIs = els_poi:sort(els_dt_document:pois(Document, [define])),
+ case ensure_range(els_range:to_poi_range(Range), Macro, POIs) of
+ {ok, MacroRange} ->
+ LineRange = els_range:line(MacroRange),
+ [ make_edit_action( Uri
+ , <<"Remove unused macro ", Macro/binary, ".">>
+ , ?CODE_ACTION_KIND_QUICKFIX
+ , <<"">>
+ , els_protocol:range(LineRange)) ];
+ error ->
+ []
+ end.
+
-spec ensure_range(poi_range(), binary(), [poi()]) -> {ok, poi_range()} | error.
ensure_range(#{from := {Line, _}}, SubjectId, POIs) ->
SubjectAtom = binary_to_atom(SubjectId, utf8),
diff --git a/apps/els_lsp/src/els_range.erl b/apps/els_lsp/src/els_range.erl
index 89cac8fcd..019086477 100644
--- a/apps/els_lsp/src/els_range.erl
+++ b/apps/els_lsp/src/els_range.erl
@@ -6,6 +6,7 @@
, in/2
, range/4
, range/1
+ , line/1
, to_poi_range/1
]).
@@ -46,6 +47,10 @@ range(Anno) ->
To = proplists:get_value(end_location, erl_anno:to_term(Anno)),
#{ from => From, to => To }.
+-spec line(poi_range()) -> poi_range().
+line(#{ from := {FromL, _}, to := {ToL, _} }) ->
+ #{ from => {FromL, 1}, to => {ToL+1, 1} }.
+
%% @doc Converts a LSP range into a POI range
-spec to_poi_range(range()) -> poi_range().
to_poi_range(#{'start' := Start, 'end' := End}) ->
diff --git a/apps/els_lsp/test/els_code_action_SUITE.erl b/apps/els_lsp/test/els_code_action_SUITE.erl
index a4adcf919..5b301d479 100644
--- a/apps/els_lsp/test/els_code_action_SUITE.erl
+++ b/apps/els_lsp/test/els_code_action_SUITE.erl
@@ -14,6 +14,7 @@
, export_unused_function/1
, suggest_variable/1
, fix_module_name/1
+ , remove_unused_macro/1
]).
%%==============================================================================
@@ -53,6 +54,10 @@ init_per_testcase(TestCase, Config) ->
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
els_test_utils:end_per_testcase(TestCase, Config).
+%%==============================================================================
+%% Const
+%%==============================================================================
+-define(COMMENTS_LINES, 2).
%%==============================================================================
%% Testcases
@@ -60,7 +65,8 @@ end_per_testcase(TestCase, Config) ->
-spec add_underscore_to_unused_var(config()) -> ok.
add_underscore_to_unused_var(Config) ->
Uri = ?config(code_action_uri, Config),
- Range = els_protocol:range(#{from => {6, 3}, to => {6, 4}}),
+ Range = els_protocol:range(#{from => {?COMMENTS_LINES + 6, 3}
+ , to => {?COMMENTS_LINES + 6, 4}}),
Diag = #{ message => <<"variable 'A' is unused">>
, range => Range
, severity => 2
@@ -84,7 +90,8 @@ add_underscore_to_unused_var(Config) ->
-spec export_unused_function(config()) -> ok.
export_unused_function(Config) ->
Uri = ?config(code_action_uri, Config),
- Range = els_protocol:range(#{from => {12, 1}, to => {12, 10}}),
+ Range = els_protocol:range(#{from => {?COMMENTS_LINES + 12, 1}
+ , to => {?COMMENTS_LINES + 12, 10}}),
Diag = #{ message => <<"function function_c/0 is unused">>
, range => Range
, severity => 2
@@ -95,8 +102,10 @@ export_unused_function(Config) ->
[ #{ edit => #{changes =>
#{ binary_to_atom(Uri, utf8) =>
[ #{ range =>
- #{'end' => #{ character => 0, line => 3},
- start => #{character => 0, line => 3}}
+ #{'end' => #{ character => 0
+ , line => ?COMMENTS_LINES + 3},
+ start => #{character => 0
+ , line => ?COMMENTS_LINES + 3}}
, newText => <<"-export([function_c/0]).\n">>
}
]}
@@ -111,7 +120,8 @@ export_unused_function(Config) ->
-spec suggest_variable(config()) -> ok.
suggest_variable(Config) ->
Uri = ?config(code_action_uri, Config),
- Range = els_protocol:range(#{from => {15, 9}, to => {15, 13}}),
+ Range = els_protocol:range(#{from => {?COMMENTS_LINES + 15, 9}
+ , to => {?COMMENTS_LINES + 15, 13}}),
Diag = #{ message => <<"variable 'Barf' is unbound">>
, range => Range
, severity => 3
@@ -135,7 +145,8 @@ suggest_variable(Config) ->
-spec fix_module_name(config()) -> ok.
fix_module_name(Config) ->
Uri = ?config(code_action_uri, Config),
- Range = els_protocol:range(#{from => {1, 9}, to => {1, 25}}),
+ Range = els_protocol:range(#{from => {?COMMENTS_LINES + 1, 9}
+ , to => {?COMMENTS_LINES + 1, 25}}),
Diag = #{ message => <<"Module name 'code_action_oops' does not "
"match file name 'code_action'">>
, range => Range
@@ -156,3 +167,30 @@ fix_module_name(Config) ->
],
?assertEqual(Expected, Result),
ok.
+
+-spec remove_unused_macro(config()) -> ok.
+remove_unused_macro(Config) ->
+ Uri = ?config(code_action_uri, Config),
+ Range = els_protocol:range(#{from => {?COMMENTS_LINES + 17, 9}
+ , to => {?COMMENTS_LINES + 17, 15}}),
+ LineRange = els_range:line(#{from => {?COMMENTS_LINES + 17, 9}
+ , to => {?COMMENTS_LINES + 17, 15}}),
+ Diag = #{ message => <<"Unused macro: TIMEOUT">>
+ , range => Range
+ , severity => 2
+ , source => <<"UnusedMacros">>
+ },
+ #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
+ Expected =
+ [ #{ edit => #{changes =>
+ #{ binary_to_atom(Uri, utf8) =>
+ [#{ range => els_protocol:range(LineRange)
+ , newText => <<"">>
+ }]
+ }}
+ , kind => <<"quickfix">>
+ , title => <<"Remove unused macro TIMEOUT.">>
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
From e45d7aea8dd3df69dd69f3341e75390253da5bde Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Thu, 24 Feb 2022 15:31:40 +0100
Subject: [PATCH 031/239] Safer incremental sync (#1222)
* didOpen: Handle indexing through els_index_buffer to avoid race
* didSave: Read and index file through els_index_buffer, this should introduce a
synchronization point if the index data has become out of sync.
---
apps/els_lsp/src/els_index_buffer.erl | 30 +++++++++++++++++++
apps/els_lsp/src/els_text_synchronization.erl | 13 ++++----
2 files changed, 37 insertions(+), 6 deletions(-)
diff --git a/apps/els_lsp/src/els_index_buffer.erl b/apps/els_lsp/src/els_index_buffer.erl
index d97adf8e1..7687cb12d 100644
--- a/apps/els_lsp/src/els_index_buffer.erl
+++ b/apps/els_lsp/src/els_index_buffer.erl
@@ -11,6 +11,7 @@
, stop/0
, apply_edits_async/2
, flush/1
+ , load/2
]).
-include_lib("kernel/include/logger.hrl").
@@ -42,6 +43,15 @@ apply_edits_async(Uri, Edits) ->
?SERVER ! {apply_edits, Uri, Edits},
ok.
+-spec load(uri(), binary()) -> ok.
+load(Uri, Text) ->
+ Ref = make_ref(),
+ ?SERVER ! {load, self(), Ref, Uri, Text},
+ receive
+ {Ref, done} ->
+ ok
+ end.
+
-spec flush(uri()) -> ok.
flush(Uri) ->
Ref = make_ref(),
@@ -71,6 +81,15 @@ loop() ->
[?SERVER, Uri, {E, R, St}])
end,
Pid ! {Ref, done},
+ loop();
+ {load, Pid, Ref, Uri, Text} ->
+ try
+ do_load(Uri, Text)
+ catch E:R:St ->
+ ?LOG_ERROR("[~p] Crashed while loading ~p: ~p",
+ [?SERVER, Uri, {E, R, St}])
+ end,
+ Pid ! {Ref, done},
loop()
end.
@@ -97,6 +116,17 @@ do_flush(Uri) ->
[?SERVER, Uri, Duration div 1000]),
ok.
+-spec do_load(uri(), binary()) -> ok.
+do_load(Uri, Text) ->
+ ?LOG_DEBUG("[~p] Loading ~p", [?SERVER, Uri]),
+ {Duration, ok} =
+ timer:tc(fun() ->
+ els_indexing:index(Uri, Text, 'deep')
+ end),
+ ?LOG_DEBUG("[~p] Done load ~p [duration: ~pms]",
+ [?SERVER, Uri, Duration div 1000]),
+ ok.
+
-spec receive_all(uri(), binary()) -> binary().
receive_all(Uri, Text0) ->
receive
diff --git a/apps/els_lsp/src/els_text_synchronization.erl b/apps/els_lsp/src/els_text_synchronization.erl
index 0e07e7550..1d2206c97 100644
--- a/apps/els_lsp/src/els_text_synchronization.erl
+++ b/apps/els_lsp/src/els_text_synchronization.erl
@@ -46,18 +46,19 @@ did_change(Params) ->
-spec did_open(map()) -> ok.
did_open(Params) ->
- TextDocument = maps:get(<<"textDocument">>, Params),
- Uri = maps:get(<<"uri">> , TextDocument),
- Text = maps:get(<<"text">> , TextDocument),
- ok = els_indexing:index(Uri, Text, 'deep'),
+ #{<<"textDocument">> := #{ <<"uri">> := Uri
+ , <<"text">> := Text}} = Params,
+ ok = els_index_buffer:load(Uri, Text),
+ ok = els_index_buffer:flush(Uri),
Provider = els_diagnostics_provider,
els_provider:handle_request(Provider, {run_diagnostics, Params}),
ok.
-spec did_save(map()) -> ok.
did_save(Params) ->
- TextDocument = maps:get(<<"textDocument">>, Params),
- Uri = maps:get(<<"uri">> , TextDocument),
+ #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
+ {ok, Text} = file:read_file(els_uri:path(Uri)),
+ ok = els_index_buffer:load(Uri, Text),
ok = els_index_buffer:flush(Uri),
Provider = els_diagnostics_provider,
els_provider:handle_request(Provider, {run_diagnostics, Params}),
From 126c4da13da672e091d4c4a6fcc6cbbc1ae7468c Mon Sep 17 00:00:00 2001
From: Rahul Raina
Date: Mon, 28 Feb 2022 21:37:43 +0800
Subject: [PATCH 032/239] Add Fix for #1202 remove unused include (#1227)
* Add Fix for #1202 remove unused include
* Fixing lint line length
* Fixing failing tests
* Fixing Var name
* Update diagnostic type spec
* Update diagnostic type spec
* Fixing dialyzer errors
---
.../priv/code_navigation/src/code_action.erl | 2 +
apps/els_lsp/src/els_code_action_provider.erl | 67 ++++++++++++-------
apps/els_lsp/src/els_diagnostics.erl | 15 ++++-
apps/els_lsp/src/els_range.erl | 14 ++++
.../src/els_unused_includes_diagnostics.erl | 8 +--
apps/els_lsp/test/els_code_action_SUITE.erl | 31 +++++++++
apps/els_lsp/test/els_diagnostics_SUITE.erl | 6 ++
7 files changed, 113 insertions(+), 30 deletions(-)
diff --git a/apps/els_lsp/priv/code_navigation/src/code_action.erl b/apps/els_lsp/priv/code_navigation/src/code_action.erl
index 04e9c74a2..63d247ba8 100644
--- a/apps/els_lsp/priv/code_navigation/src/code_action.erl
+++ b/apps/els_lsp/priv/code_navigation/src/code_action.erl
@@ -17,3 +17,5 @@ function_c() ->
Foo + Barf.
-define(TIMEOUT, 200).
+
+-include_lib("stdlib/include/assert.hrl").
diff --git a/apps/els_lsp/src/els_code_action_provider.erl b/apps/els_lsp/src/els_code_action_provider.erl
index f37c85d3c..28ade9fc0 100644
--- a/apps/els_lsp/src/els_code_action_provider.erl
+++ b/apps/els_lsp/src/els_code_action_provider.erl
@@ -35,31 +35,35 @@ code_actions(Uri, _Range, #{<<"diagnostics">> := Diagnostics}) ->
lists:flatten([make_code_action(Uri, D) || D <- Diagnostics]).
-spec make_code_action(uri(), map()) -> [map()].
-make_code_action(Uri, #{<<"message">> := Message, <<"range">> := Range}) ->
+make_code_action(Uri,
+ #{<<"message">> := Message, <<"range">> := Range} = D) ->
+ Data = maps:get(<<"data">>, D, <<>>),
make_code_action(
- [ {"function (.*) is unused", fun action_export_function/3}
- , {"variable '(.*)' is unused", fun action_ignore_variable/3}
- , {"variable '(.*)' is unbound", fun action_suggest_variable/3}
+ [ {"function (.*) is unused", fun action_export_function/4}
+ , {"variable '(.*)' is unused", fun action_ignore_variable/4}
+ , {"variable '(.*)' is unbound", fun action_suggest_variable/4}
, {"Module name '(.*)' does not match file name '(.*)'",
- fun action_fix_module_name/3}
- , {"Unused macro: (.*)", fun action_remove_macro/3}
- ], Uri, Range, Message).
-
--spec make_code_action([{string(), Fun}], uri(), range(), binary()) -> [map()]
- when Fun :: fun((uri(), range(), [binary()]) -> [map()]).
-make_code_action([], _Uri, _Range, _Message) ->
+ fun action_fix_module_name/4}
+ , {"Unused macro: (.*)", fun action_remove_macro/4}
+ , {"Unused file: (.*)", fun action_remove_unused/4}
+ ], Uri, Range, Data, Message).
+
+-spec make_code_action([{string(), Fun}], uri(), range(), binary(), binary())
+ -> [map()]
+ when Fun :: fun((uri(), range(), binary(), [binary()]) -> [map()]).
+make_code_action([], _Uri, _Range, _Data, _Message) ->
[];
-make_code_action([{RE, Fun}|Rest], Uri, Range, Message) ->
+make_code_action([{RE, Fun}|Rest], Uri, Range, Data, Message) ->
Actions = case re:run(Message, RE, [{capture, all_but_first, binary}]) of
{match, Matches} ->
- Fun(Uri, Range, Matches);
+ Fun(Uri, Range, Data, Matches);
nomatch ->
[]
end,
- Actions ++ make_code_action(Rest, Uri, Range, Message).
+ Actions ++ make_code_action(Rest, Uri, Range, Data, Message).
--spec action_export_function(uri(), range(), [binary()]) -> [map()].
-action_export_function(Uri, _Range, [UnusedFun]) ->
+-spec action_export_function(uri(), range(), binary(), [binary()]) -> [map()].
+action_export_function(Uri, _Range, _Data, [UnusedFun]) ->
{ok, Document} = els_utils:lookup_document(Uri),
case els_poi:sort(els_dt_document:pois(Document, [module, export])) of
[] ->
@@ -74,8 +78,8 @@ action_export_function(Uri, _Range, [UnusedFun]) ->
, els_protocol:range(#{from => Pos, to => Pos})) ]
end.
--spec action_ignore_variable(uri(), range(), [binary()]) -> [map()].
-action_ignore_variable(Uri, Range, [UnusedVariable]) ->
+-spec action_ignore_variable(uri(), range(), binary(), [binary()]) -> [map()].
+action_ignore_variable(Uri, Range, _Data, [UnusedVariable]) ->
{ok, Document} = els_utils:lookup_document(Uri),
POIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
case ensure_range(els_range:to_poi_range(Range), UnusedVariable, POIs) of
@@ -89,8 +93,8 @@ action_ignore_variable(Uri, Range, [UnusedVariable]) ->
[]
end.
--spec action_suggest_variable(uri(), range(), [binary()]) -> [map()].
-action_suggest_variable(Uri, Range, [Var]) ->
+-spec action_suggest_variable(uri(), range(), binary(), [binary()]) -> [map()].
+action_suggest_variable(Uri, Range, _Data, [Var]) ->
%% Supply a quickfix to replace an unbound variable with the most similar
%% variable name in scope.
{ok, Document} = els_utils:lookup_document(Uri),
@@ -115,8 +119,8 @@ action_suggest_variable(Uri, Range, [Var]) ->
[]
end.
--spec action_fix_module_name(uri(), range(), [binary()]) -> [map()].
-action_fix_module_name(Uri, Range0, [ModName, FileName]) ->
+-spec action_fix_module_name(uri(), range(), binary(), [binary()]) -> [map()].
+action_fix_module_name(Uri, Range0, _Data, [ModName, FileName]) ->
{ok, Document} = els_utils:lookup_document(Uri),
POIs = els_poi:sort(els_dt_document:pois(Document, [module])),
case ensure_range(els_range:to_poi_range(Range0), ModName, POIs) of
@@ -130,8 +134,8 @@ action_fix_module_name(Uri, Range0, [ModName, FileName]) ->
[]
end.
-- spec action_remove_macro(uri(), range(), [binary()]) -> [map()].
-action_remove_macro(Uri, Range, [Macro]) ->
+- spec action_remove_macro(uri(), range(), binary(), [binary()]) -> [map()].
+action_remove_macro(Uri, Range, _Data, [Macro]) ->
%% Supply a quickfix to remove the unused Macro
{ok, Document} = els_utils:lookup_document(Uri),
POIs = els_poi:sort(els_dt_document:pois(Document, [define])),
@@ -147,6 +151,21 @@ action_remove_macro(Uri, Range, [Macro]) ->
[]
end.
+-spec action_remove_unused(uri(), range(), binary(), [binary()]) -> [map()].
+action_remove_unused(Uri, _Range0, Data, [Import]) ->
+ {ok, Document} = els_utils:lookup_document(Uri),
+ case els_range:inclusion_range(Data, Document) of
+ {ok, UnusedRange} ->
+ LineRange = els_range:line(UnusedRange),
+ [ make_edit_action( Uri
+ , <<"Remove unused -include_lib(", Import/binary, ").">>
+ , ?CODE_ACTION_KIND_QUICKFIX
+ , <<>>
+ , els_protocol:range(LineRange)) ];
+ error ->
+ []
+ end.
+
-spec ensure_range(poi_range(), binary(), [poi()]) -> {ok, poi_range()} | error.
ensure_range(#{from := {Line, _}}, SubjectId, POIs) ->
SubjectAtom = binary_to_atom(SubjectId, utf8),
diff --git a/apps/els_lsp/src/els_diagnostics.erl b/apps/els_lsp/src/els_diagnostics.erl
index 9786bdddb..bc25d44e5 100644
--- a/apps/els_lsp/src/els_diagnostics.erl
+++ b/apps/els_lsp/src/els_diagnostics.erl
@@ -18,6 +18,7 @@
, source => binary()
, message := binary()
, relatedInformation => [related_info()]
+ , data => binary()
}.
-type diagnostic_id() :: binary().
-type related_info() :: #{ location := location()
@@ -48,6 +49,7 @@
, default_diagnostics/0
, enabled_diagnostics/0
, make_diagnostic/4
+ , make_diagnostic/5
, run_diagnostics/1
]).
@@ -81,7 +83,8 @@ enabled_diagnostics() ->
Disabled = maps:get("disabled", Config, []),
lists:usort((Default ++ valid(Enabled)) -- valid(Disabled)).
--spec make_diagnostic(range(), binary(), severity(), binary()) -> diagnostic().
+-spec make_diagnostic(range(), binary(), severity(), binary()) ->
+ diagnostic().
make_diagnostic(Range, Message, Severity, Source) ->
#{ range => Range
, message => Message
@@ -89,6 +92,16 @@ make_diagnostic(Range, Message, Severity, Source) ->
, source => Source
}.
+-spec make_diagnostic(range(), binary(), severity(), binary(), binary())
+ -> diagnostic().
+make_diagnostic(Range, Message, Severity, Source, Data) ->
+ #{ range => Range
+ , message => Message
+ , severity => Severity
+ , source => Source
+ , data => Data
+ }.
+
-spec run_diagnostics(uri()) -> [pid()].
run_diagnostics(Uri) ->
[run_diagnostic(Uri, Id) || Id <- enabled_diagnostics()].
diff --git a/apps/els_lsp/src/els_range.erl b/apps/els_lsp/src/els_range.erl
index 019086477..1cea9e8c9 100644
--- a/apps/els_lsp/src/els_range.erl
+++ b/apps/els_lsp/src/els_range.erl
@@ -8,6 +8,7 @@
, range/1
, line/1
, to_poi_range/1
+ , inclusion_range/2
]).
-spec compare(poi_range(), poi_range()) -> boolean().
@@ -66,6 +67,19 @@ to_poi_range(#{<<"start">> := Start, <<"end">> := End}) ->
, to => {LineEnd + 1, CharEnd + 1}
}.
+-spec inclusion_range(uri(), els_dt_document:item()) ->
+ {ok, poi_range()} | error.
+inclusion_range(Uri, Document) ->
+ Path = binary_to_list(els_uri:path(Uri)),
+ case
+ els_compiler_diagnostics:inclusion_range(Path, Document, include) ++
+ els_compiler_diagnostics:inclusion_range(Path, Document, include_lib) of
+ [Range|_] ->
+ {ok, Range};
+ [] ->
+ error
+ end.
+
-spec plus(pos(), string()) -> pos().
plus({Line, Column}, String) ->
{Line, Column + string:length(String)}.
diff --git a/apps/els_lsp/src/els_unused_includes_diagnostics.erl b/apps/els_lsp/src/els_unused_includes_diagnostics.erl
index 6c71b4318..d2a7c9204 100644
--- a/apps/els_lsp/src/els_unused_includes_diagnostics.erl
+++ b/apps/els_lsp/src/els_unused_includes_diagnostics.erl
@@ -41,6 +41,7 @@ run(Uri) ->
, <<"Unused file: ", (filename:basename(UI))/binary>>
, ?DIAGNOSTIC_WARNING
, source()
+ , <> %% Additional data with complete path
) || UI <- UnusedIncludes ]
end.
@@ -112,11 +113,8 @@ expand_includes(Document) ->
-spec inclusion_range(uri(), els_dt_document:item()) -> poi_range().
inclusion_range(Uri, Document) ->
- Path = binary_to_list(els_uri:path(Uri)),
- case
- els_compiler_diagnostics:inclusion_range(Path, Document, include) ++
- els_compiler_diagnostics:inclusion_range(Path, Document, include_lib) of
- [Range|_] ->
+ case els_range:inclusion_range(Uri, Document) of
+ {ok, Range} ->
Range;
_ ->
#{from => {1, 1}, to => {2, 1}}
diff --git a/apps/els_lsp/test/els_code_action_SUITE.erl b/apps/els_lsp/test/els_code_action_SUITE.erl
index 5b301d479..f1ea132d9 100644
--- a/apps/els_lsp/test/els_code_action_SUITE.erl
+++ b/apps/els_lsp/test/els_code_action_SUITE.erl
@@ -15,6 +15,7 @@
, suggest_variable/1
, fix_module_name/1
, remove_unused_macro/1
+ , remove_unused_import/1
]).
%%==============================================================================
@@ -194,3 +195,33 @@ remove_unused_macro(Config) ->
],
?assertEqual(Expected, Result),
ok.
+
+-spec remove_unused_import(config()) -> ok.
+remove_unused_import(Config) ->
+ Uri = ?config(code_action_uri, Config),
+ Range = els_protocol:range(#{from => {?COMMENTS_LINES + 19, 15}
+ , to => {?COMMENTS_LINES + 19, 40}}),
+ LineRange = els_range:line(#{from => {?COMMENTS_LINES + 19, 15}
+ , to => {?COMMENTS_LINES + 19, 40}}),
+ {ok, FileName} = els_utils:find_header(
+ els_utils:filename_to_atom("stdlib/include/assert.hrl")),
+ Diag = #{ message => <<"Unused file: assert.hrl">>
+ , range => Range
+ , severity => 2
+ , source => <<"UnusedIncludes">>
+ , data => FileName
+ },
+ #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
+ Expected =
+ [ #{ edit => #{changes =>
+ #{ binary_to_atom(Uri, utf8) =>
+ [#{ range => els_protocol:range(LineRange)
+ , newText => <<>>
+ }]
+ }}
+ , kind => <<"quickfix">>
+ , title => <<"Remove unused -include_lib(assert.hrl).">>
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
diff --git a/apps/els_lsp/test/els_diagnostics_SUITE.erl b/apps/els_lsp/test/els_diagnostics_SUITE.erl
index 3ca60fcc5..781c90382 100644
--- a/apps/els_lsp/test/els_diagnostics_SUITE.erl
+++ b/apps/els_lsp/test/els_diagnostics_SUITE.erl
@@ -598,8 +598,11 @@ unused_includes(_Config) ->
Path = src_path("diagnostics_unused_includes.erl"),
Source = <<"UnusedIncludes">>,
Errors = [],
+ {ok, FileName} = els_utils:find_header(
+ els_utils:filename_to_atom("et/include/et.hrl")),
Warnings = [#{ message => <<"Unused file: et.hrl">>
, range => {{3, 0}, {3, 34}}
+ , data => FileName
}
],
Hints = [],
@@ -610,8 +613,11 @@ unused_includes_compiler_attribute(_Config) ->
Path = src_path("diagnostics_unused_includes_compiler_attribute.erl"),
Source = <<"UnusedIncludes">>,
Errors = [],
+ {ok, FileName} = els_utils:find_header(
+ els_utils:filename_to_atom("kernel/include/file.hrl")),
Warnings = [ #{ message => <<"Unused file: file.hrl">>
, range => {{3, 0}, {3, 40}}
+ , data => FileName
}
],
Hints = [],
From 44eee382e60233a5151ed25f7fb4a34be1f1b16c Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Wed, 2 Mar 2022 10:34:54 +0100
Subject: [PATCH 033/239] Make it easier to include Erlang LS as a dependency.
(#1229)
* Remove dependency on edoc.hrl file
* Add dummy .app.src file for Erlang LS
To simplify inclusion (of an umbrella app) as a build dependency.
---
apps/els_lsp/src/edoc_report.erl | 3 +--
src/erlang_ls.app.src | 11 +++++++++++
2 files changed, 12 insertions(+), 2 deletions(-)
create mode 100644 src/erlang_ls.app.src
diff --git a/apps/els_lsp/src/edoc_report.erl b/apps/els_lsp/src/edoc_report.erl
index dc6fc486d..b4923de7f 100644
--- a/apps/els_lsp/src/edoc_report.erl
+++ b/apps/els_lsp/src/edoc_report.erl
@@ -53,8 +53,7 @@
-type line() :: non_neg_integer().
-type severity() :: warning | error.
--include_lib("edoc/src/edoc.hrl").
-
+-define(APPLICATION, edoc).
-define(DICT_KEY, edoc_diagnostics).
-spec error(what()) -> ok.
diff --git a/src/erlang_ls.app.src b/src/erlang_ls.app.src
new file mode 100644
index 000000000..d3346a8c7
--- /dev/null
+++ b/src/erlang_ls.app.src
@@ -0,0 +1,11 @@
+{application, erlang_ls, [
+ {description, "Erlang LS"},
+ {vsn, git},
+ {registered, []},
+ {applications, [kernel, stdlib, els_core, els_lsp, els_dap]},
+ {env, []},
+ {modules, []},
+
+ {licenses, ["Apache 2.0"]},
+ {links, []}
+]}.
From 6dcf643a060319f87738836a554452c531c7392d Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Wed, 2 Mar 2022 14:44:29 +0100
Subject: [PATCH 034/239] DAP should return 0 on version command (#1230)
---
apps/els_dap/src/els_dap.erl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/els_dap/src/els_dap.erl b/apps/els_dap/src/els_dap.erl
index 783087edf..54e44b468 100644
--- a/apps/els_dap/src/els_dap.erl
+++ b/apps/els_dap/src/els_dap.erl
@@ -45,7 +45,7 @@ parse_args(Args) ->
case getopt:parse(opt_spec_list(), Args) of
{ok, {[version | _], _BadArgs}} ->
print_version(),
- halt(1);
+ halt(0);
{ok, {ParsedArgs, _BadArgs}} ->
set_args(ParsedArgs);
{error, {invalid_option, _}} ->
From 9b9393ddd41163cf74206642bfbab236d1e3ac91 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Wed, 2 Mar 2022 15:31:56 +0100
Subject: [PATCH 035/239] Use fork of yamerl, to allow inclusion of Erlang LS
as a dependency (#1231)
---
rebar.config | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rebar.config b/rebar.config
index 74993eacf..e445b374d 100644
--- a/rebar.config
+++ b/rebar.config
@@ -8,7 +8,7 @@
{deps, [ {jsx, "3.0.0"}
, {redbug, "2.0.6"}
- , {yamerl, "0.8.1"}
+ , {yamerl, {git, "https://github.com/erlang-ls/yamerl.git", {ref, "9a9f7a2e84554992f2e8e08a8060bfe97776a5b7"}}}
, {docsh, "0.7.2"}
, {elvis_core, "~> 1.3"}
, {rebar3_format, "0.8.2"}
From f9ce810b27893891a155bd2677280c11f811bb75 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Wed, 2 Mar 2022 16:08:44 +0100
Subject: [PATCH 036/239] Add missing upgrade to rebar.lock (#1232)
---
rebar.lock | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/rebar.lock b/rebar.lock
index 7ce421b45..e533a0669 100644
--- a/rebar.lock
+++ b/rebar.lock
@@ -20,7 +20,10 @@
{<<"redbug">>,{pkg,<<"redbug">>,<<"2.0.6">>},0},
{<<"tdiff">>,{pkg,<<"tdiff">>,<<"0.1.2">>},0},
{<<"uuid">>,{pkg,<<"uuid_erl">>,<<"2.0.1">>},0},
- {<<"yamerl">>,{pkg,<<"yamerl">>,<<"0.8.1">>},0},
+ {<<"yamerl">>,
+ {git,"https://github.com/erlang-ls/yamerl.git",
+ {ref,"9a9f7a2e84554992f2e8e08a8060bfe97776a5b7"}},
+ 0},
{<<"zipper">>,{pkg,<<"zipper">>,<<"1.0.1">>},1}]}.
[
{pkg_hash,[
@@ -37,7 +40,6 @@
{<<"redbug">>, <<"A764690B012B67C404562F9C6E1BA47A73892EE17DF5C15F670B1A5BF9D2F25A">>},
{<<"tdiff">>, <<"4E1B30321F1B3D600DF65CD60858EDE1235FE4E5EE042110AB5AD90CD6464AC5">>},
{<<"uuid">>, <<"1FD9079C544D521063897887A1C5B3302DCA98F9BB06AADCDC6FB0663F256797">>},
- {<<"yamerl">>, <<"07DA13FFA1D8E13948943789665C62CCD679DFA7B324A4A2ED3149DF17F453A4">>},
{<<"zipper">>, <<"3CCB4F14B97C06B2749B93D8B6C204A1ECB6FAFC6050CACC3B93B9870C05952A">>}]},
{pkg_hash_ext,[
{<<"bucs">>, <<"FF6A5C72A500AD7AEC1EE3BA164AE3C450EADEE898B0D151E1FACA18AC8D0D62">>},
@@ -53,6 +55,5 @@
{<<"redbug">>, <<"AAD9498671F4AB91EACA5099FE85A61618158A636E6286892C4F7CF4AF171D04">>},
{<<"tdiff">>, <<"E0C2E168F99252A5889768D5C8F1E6510A184592D4CFA06B22778A18D33D7875">>},
{<<"uuid">>, <<"AB57CACCD51F170011E5F444CE865F84B41605E483A9EFCC468C1AFAEC87553B">>},
- {<<"yamerl">>, <<"96CB30F9D64344FED0EF8A92E9F16F207DE6C04DFFF4F366752CA79F5BCEB23F">>},
{<<"zipper">>, <<"6A1FD3E1F0CC1D1DF5642C9A0CE2178036411B0A5C9642851D1DA276BD737C2D">>}]}
].
From dc5ece19bd5116265e2f31a32249591e411f394e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A9ter=20G=C3=B6m=C3=B6ri?=
Date: Tue, 8 Mar 2022 09:00:43 +0100
Subject: [PATCH 037/239] Handle incomplete type definitions better (#1237)
Fixes #1218
---
apps/els_lsp/src/els_erlfmt_ast.erl | 45 ++++++++++++++++---
apps/els_lsp/test/els_parser_SUITE.erl | 39 ++++++++++++++++
apps/els_lsp/test/els_parser_macros_SUITE.erl | 2 +-
3 files changed, 79 insertions(+), 7 deletions(-)
diff --git a/apps/els_lsp/src/els_erlfmt_ast.erl b/apps/els_lsp/src/els_erlfmt_ast.erl
index 6c95b1a8d..59418e130 100644
--- a/apps/els_lsp/src/els_erlfmt_ast.erl
+++ b/apps/els_lsp/src/els_erlfmt_ast.erl
@@ -110,17 +110,25 @@ erlfmt_to_st(Node) ->
%% Representation for types is in general the same as for
%% corresponding values. The `type` node is not used at all. This
%% means new binary operators `|`, `::`, and `..` inside types.
- {attribute, Pos, {atom, _, Tag} = Name, [Def]} when Tag =:= type; Tag =:= opaque ->
+ {attribute, Pos, {atom, _, Tag} = Name, [{op, OPos, '::', Type, Definition}]}
+ when Tag =:= type; Tag =:= opaque ->
put('$erlfmt_ast_context$', type),
- {op, OPos, '::', Type, Definition} = Def,
{TypeName, Args} =
case Type of
{call, _CPos, TypeName0, Args0} ->
{TypeName0, Args0};
- {macro_call, CPos, {_, MPos, _} = MacroName, Args0} ->
- EndLoc = maps:get(end_location, MPos),
- TypeName0 = {macro_call, CPos#{end_location => EndLoc}, MacroName, none},
- {TypeName0, Args0}
+ {macro_call, _, _, _} ->
+ %% Note: in the following example the arguments belong to the macro
+ %% so we set empty type args.
+ %% `-type ?M(A) :: A.'
+ %% The erlang preprocessor also prefers the M/1 macro if both M/0
+ %% and M/1 are defined, but it also allows only M/0. Unfortunately
+ %% erlang_ls doesn't know what macros are defined.
+ {Type, []};
+ _ ->
+ %% whatever stands at the left side of '::', let's keep it.
+ %% erlfmt_parser allows atoms and vars too
+ {Type, []}
end,
Tree =
update_tree_with_meta(
@@ -133,6 +141,31 @@ erlfmt_to_st(Node) ->
Pos),
erase('$erlfmt_ast_context$'),
Tree;
+ {attribute, Pos, {atom, _, Tag} = Name, [Def]} when Tag =:= type; Tag =:= opaque ->
+ %% an incomplete attribute, where `::` operator and the definition missing
+ %% eg "-type t()."
+ put('$erlfmt_ast_context$', type),
+ OPos = element(2, Def),
+ {TypeName, Args} =
+ case Def of
+ {call, _CPos, TypeName0, Args0} ->
+ {TypeName0, Args0};
+ _ ->
+ {Def, []}
+ end,
+ %% Set definition as an empty tuple for which els_parser generates no POIs
+ EmptyDef = erl_syntax:tuple([]),
+ Tree =
+ update_tree_with_meta(
+ erl_syntax:attribute(erlfmt_to_st(Name),
+ [update_tree_with_meta(
+ erl_syntax:tuple([erlfmt_to_st(TypeName),
+ EmptyDef,
+ erl_syntax:list([erlfmt_to_st(A) || A <- Args])]),
+ OPos)]),
+ Pos),
+ erase('$erlfmt_ast_context$'),
+ Tree;
{attribute, Pos, {atom, _, RawName} = Name, Args} when RawName =:= callback;
RawName =:= spec ->
put('$erlfmt_ast_context$', type),
diff --git a/apps/els_lsp/test/els_parser_SUITE.erl b/apps/els_lsp/test/els_parser_SUITE.erl
index 7d0c81c70..ecc18ddc9 100644
--- a/apps/els_lsp/test/els_parser_SUITE.erl
+++ b/apps/els_lsp/test/els_parser_SUITE.erl
@@ -11,6 +11,7 @@
, parse_invalid_code/1
, parse_incomplete_function/1
, parse_incomplete_spec/1
+ , parse_incomplete_type/1
, parse_no_tokens/1
, define/1
, underscore_macro/1
@@ -106,6 +107,44 @@ parse_incomplete_spec(_Config) ->
?assertMatch([#{id := aa}], parse_find_pois(Text, atom)),
ok.
+-spec parse_incomplete_type(config()) -> ok.
+parse_incomplete_type(_Config) ->
+ Text = "-type t(A) :: {A aa bb cc}\n.",
+
+ %% type range ends where the original dot ends, including ignored parts
+ ?assertMatch([#{id := {t, 1}, range := #{from := {1, 1}, to := {2, 2}}}],
+ parse_find_pois(Text, type_definition)),
+ %% only first var is found
+ ?assertMatch([#{id := 'A'}], parse_find_pois(Text, variable)),
+
+ Text2 = "-type t",
+ ?assertMatch({ok, [#{ kind := type_definition, id := {t, 0}}]},
+ els_parser:parse(Text2)),
+ Text3 = "-type ?t",
+ ?assertMatch({ok, [#{ kind := macro, id := t}]},
+ els_parser:parse(Text3)),
+ %% this is not incomplete - there is no way this will become valid erlang
+ %% but erlfmt can parse it
+ Text4 = "-type T",
+ ?assertMatch({ok, [#{ kind := variable, id := 'T'}]},
+ els_parser:parse(Text4)),
+ Text5 = "-type [1, 2]",
+ {ok, []} = els_parser:parse(Text5),
+
+ %% no type args - assume zero args
+ Text11 = "-type t :: 1.",
+ ?assertMatch({ok, [#{ kind := type_definition, id := {t, 0}}]},
+ els_parser:parse(Text11)),
+ %% no macro args - this is 100% valid code
+ Text12= "-type ?t :: 1.",
+ ?assertMatch({ok, [#{ kind := macro, id := t}]},
+ els_parser:parse(Text12)),
+ Text13 = "-type T :: 1.",
+ ?assertMatch({ok, [#{ kind := variable, id := 'T'}]},
+ els_parser:parse(Text13)),
+
+ ok.
+
%% Issue #1171
parse_no_tokens(_Config) ->
%% scanning text containing only whitespaces returns an empty list of tokens,
diff --git a/apps/els_lsp/test/els_parser_macros_SUITE.erl b/apps/els_lsp/test/els_parser_macros_SUITE.erl
index e628031f3..409c62242 100644
--- a/apps/els_lsp/test/els_parser_macros_SUITE.erl
+++ b/apps/els_lsp/test/els_parser_macros_SUITE.erl
@@ -87,7 +87,7 @@ type_name_macro(_Config) ->
Text1 = "-type ?M() :: integer() | t().",
?assertMatch({ok, [#{kind := type_application, id := {t, 0}},
#{kind := type_application, id := {erlang, integer, 0}},
- #{kind := macro, id := 'M'}]},
+ #{kind := macro, id := {'M', 0}}]},
els_parser:parse(Text1)),
%% The macro is parsed as (?M()), rather than (?M)()
From 4e288d3a90dc39465ae0aaf03957aa55f509e774 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kristoffer=20Br=C3=A5nemyr?=
Date: Tue, 8 Mar 2022 09:04:53 +0100
Subject: [PATCH 038/239] Avoid negative line numbers in elvis diagnostics.
(#1233)
---
apps/els_lsp/src/els_elvis_diagnostics.erl | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/apps/els_lsp/src/els_elvis_diagnostics.erl b/apps/els_lsp/src/els_elvis_diagnostics.erl
index 6c31bc65a..8737fc14f 100644
--- a/apps/els_lsp/src/els_elvis_diagnostics.erl
+++ b/apps/els_lsp/src/els_elvis_diagnostics.erl
@@ -91,8 +91,11 @@ format_item(_Name, []) ->
-spec diagnostic(any(), any(), integer(), [any()],
els_diagnostics:severity()) -> [map()].
diagnostic(Name, Msg, Ln, Info, Severity) ->
+ %% Avoid negative line numbers
+ DiagLine = make_protocol_line(Ln),
FMsg = io_lib:format(Msg, Info),
- Range = els_protocol:range(#{from => {Ln, 1}, to => {Ln + 1, 1}}),
+ Range = els_protocol:range(#{ from => {DiagLine, 1}
+ , to => {DiagLine + 1, 1}}),
Message = els_utils:to_binary(FMsg),
[#{ range => Range
, severity => Severity
@@ -102,6 +105,12 @@ diagnostic(Name, Msg, Ln, Info, Severity) ->
, relatedInformation => []
}].
+-spec make_protocol_line(Line :: number()) -> number().
+make_protocol_line(Line) when Line =< 0 ->
+ 1;
+make_protocol_line(Line) ->
+ Line.
+
-spec get_elvis_config_path() -> file:filename_all().
get_elvis_config_path() ->
case els_config:get(elvis_config_path) of
From 14d0df86aa7e52514735df11302e2d96cba23106 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Thu, 10 Mar 2022 15:35:57 +0100
Subject: [PATCH 039/239] Only run EDoc diagnostics on .erl files (#1242)
---
.../code_navigation/src/code_navigation.app.src | 14 ++++++++++++++
apps/els_lsp/src/els_edoc_diagnostics.erl | 9 +++++++++
apps/els_lsp/test/els_diagnostics_SUITE.erl | 16 ++++++++++++++--
3 files changed, 37 insertions(+), 2 deletions(-)
create mode 100644 apps/els_lsp/priv/code_navigation/src/code_navigation.app.src
diff --git a/apps/els_lsp/priv/code_navigation/src/code_navigation.app.src b/apps/els_lsp/priv/code_navigation/src/code_navigation.app.src
new file mode 100644
index 000000000..2de3adc12
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/code_navigation.app.src
@@ -0,0 +1,14 @@
+{application, code_navigation, [
+ {description, "Erlang LS - Test App"},
+ {vsn, git},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {env, []},
+ {modules, []},
+ {maintainers, []},
+ {licenses, ["Apache 2.0"]},
+ {links, []}
+]}.
diff --git a/apps/els_lsp/src/els_edoc_diagnostics.erl b/apps/els_lsp/src/els_edoc_diagnostics.erl
index 1a3dc7ab9..deea482ad 100644
--- a/apps/els_lsp/src/els_edoc_diagnostics.erl
+++ b/apps/els_lsp/src/els_edoc_diagnostics.erl
@@ -41,6 +41,15 @@ is_default() ->
%% warnings and errors.
-spec run(uri()) -> [els_diagnostics:diagnostic()].
run(Uri) ->
+ case filename:extension(Uri) of
+ <<".erl">> ->
+ do_run(Uri);
+ _ ->
+ []
+ end.
+
+-spec do_run(uri()) -> [els_diagnostics:diagnostic()].
+do_run(Uri) ->
Paths = [els_utils:to_list(els_uri:path(Uri))],
Fun = fun(Dir) ->
Options = edoc_options(Dir),
diff --git a/apps/els_lsp/test/els_diagnostics_SUITE.erl b/apps/els_lsp/test/els_diagnostics_SUITE.erl
index 781c90382..43e6412d1 100644
--- a/apps/els_lsp/test/els_diagnostics_SUITE.erl
+++ b/apps/els_lsp/test/els_diagnostics_SUITE.erl
@@ -44,6 +44,7 @@
, module_name_check/1
, module_name_check_whitespace/1
, edoc_main/1
+ , edoc_skip_app_src/1
]).
%%==============================================================================
@@ -126,7 +127,8 @@ init_per_testcase(TestCase, Config) when TestCase =:= gradualizer ->
meck:expect(els_gradualizer_diagnostics, is_default, 0, true),
els_mock_diagnostics:setup(),
els_test_utils:init_per_testcase(TestCase, Config);
-init_per_testcase(TestCase, Config) when TestCase =:= edoc_main ->
+init_per_testcase(TestCase, Config) when TestCase =:= edoc_main;
+ TestCase =:= edoc_skip_app_src ->
meck:new(els_edoc_diagnostics, [passthrough, no_link]),
meck:expect(els_edoc_diagnostics, is_default, 0, true),
els_mock_diagnostics:setup(),
@@ -172,7 +174,8 @@ end_per_testcase(TestCase, Config) when TestCase =:= gradualizer ->
els_test_utils:end_per_testcase(TestCase, Config),
els_mock_diagnostics:teardown(),
ok;
-end_per_testcase(TestCase, Config) when TestCase =:= edoc_main ->
+end_per_testcase(TestCase, Config) when TestCase =:= edoc_main;
+ TestCase =:= edoc_skip_app_src ->
meck:unload(els_edoc_diagnostics),
els_test_utils:end_per_testcase(TestCase, Config),
els_mock_diagnostics:teardown(),
@@ -716,6 +719,15 @@ edoc_main(_Config) ->
Hints = [],
els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ -spec edoc_skip_app_src(config()) -> ok.
+edoc_skip_app_src(_Config) ->
+ Path = src_path("code_navigation.app.src"),
+ Source = <<"Edoc">>,
+ Errors = [],
+ Warnings = [],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+
%%==============================================================================
%% Internal Functions
%%==============================================================================
From e8806e29cf3ddd536c4686277a80a9ad33f046a0 Mon Sep 17 00:00:00 2001
From: Radek Szymczyszyn
Date: Thu, 10 Mar 2022 15:54:01 +0100
Subject: [PATCH 040/239] Update Gradualizer to the the current master (#1239)
---
rebar.config | 2 +-
rebar.lock | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/rebar.config b/rebar.config
index e445b374d..702615e1c 100644
--- a/rebar.config
+++ b/rebar.config
@@ -17,7 +17,7 @@
, {ephemeral, "2.0.4"}
, {tdiff, "0.1.2"}
, {uuid, "2.0.1", {pkg, uuid_erl}}
- , {gradualizer, {git, "https://github.com/josefs/Gradualizer.git", {ref, "e93db1c"}}}
+ , {gradualizer, {git, "https://github.com/josefs/Gradualizer.git", {ref, "6e89b4e"}}}
]
}.
diff --git a/rebar.lock b/rebar.lock
index e533a0669..a5e6d6aed 100644
--- a/rebar.lock
+++ b/rebar.lock
@@ -10,7 +10,7 @@
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},2},
{<<"gradualizer">>,
{git,"https://github.com/josefs/Gradualizer.git",
- {ref,"e93db1c6725760def005c69d72f53b1a889b4c2f"}},
+ {ref,"6e89b4e1cd489637a848cc5ca55058c8a241bf7d"}},
0},
{<<"jsx">>,{pkg,<<"jsx">>,<<"3.0.0">>},0},
{<<"katana_code">>,{pkg,<<"katana_code">>,<<"0.2.1">>},1},
From 0fc151d7fdfca478338777d83057ae1d32d68252 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Thu, 10 Mar 2022 16:24:32 +0100
Subject: [PATCH 041/239] Add support for custom EDoc tags (#1243)
---
apps/els_core/src/els_config.erl | 5 +++-
.../priv/code_navigation/erlang_ls.config | 3 +++
.../code_navigation/src/edoc_diagnostics.erl | 2 +-
.../src/edoc_diagnostics_custom_tags.erl | 18 +++++++++++++
apps/els_lsp/src/edoc_report.erl | 9 +++++++
apps/els_lsp/test/els_diagnostics_SUITE.erl | 25 ++++++++++++++++---
6 files changed, 56 insertions(+), 6 deletions(-)
create mode 100644 apps/els_lsp/priv/code_navigation/src/edoc_diagnostics_custom_tags.erl
diff --git a/apps/els_core/src/els_config.erl b/apps/els_core/src/els_config.erl
index e295c715e..ab2832771 100644
--- a/apps/els_core/src/els_config.erl
+++ b/apps/els_core/src/els_config.erl
@@ -55,7 +55,8 @@
| elvis_config_path
| indexing_enabled
| bsp_enabled
- | compiler_telemetry_enabled.
+ | compiler_telemetry_enabled
+ | edoc_custom_tags.
-type path() :: file:filename().
-type state() :: #{ apps_dirs => [path()]
@@ -132,6 +133,7 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
IncrementalSync = maps:get("incremental_sync", Config, true),
CompilerTelemetryEnabled
= maps:get("compiler_telemetry_enabled", Config, false),
+ EDocCustomTags = maps:get("edoc_custom_tags", Config, []),
IndexingEnabled = maps:get(<<"indexingEnabled">>, InitOptions, true),
@@ -155,6 +157,7 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
ok = set(elvis_config_path, ElvisConfigPath),
ok = set(bsp_enabled, BSPEnabled),
ok = set(compiler_telemetry_enabled, CompilerTelemetryEnabled),
+ ok = set(edoc_custom_tags, EDocCustomTags),
ok = set(incremental_sync, IncrementalSync),
%% Calculated from the above
ok = set(apps_paths , project_paths(RootPath, AppsDirs, false)),
diff --git a/apps/els_lsp/priv/code_navigation/erlang_ls.config b/apps/els_lsp/priv/code_navigation/erlang_ls.config
index 395df7171..d01a177b5 100644
--- a/apps/els_lsp/priv/code_navigation/erlang_ls.config
+++ b/apps/els_lsp/priv/code_navigation/erlang_ls.config
@@ -2,3 +2,6 @@ macros:
- name: DEFINED_WITHOUT_VALUE
- name: DEFINED_WITH_VALUE
value: 1
+edoc_custom_tags:
+ - edoc
+ - generated
diff --git a/apps/els_lsp/priv/code_navigation/src/edoc_diagnostics.erl b/apps/els_lsp/priv/code_navigation/src/edoc_diagnostics.erl
index ee3f20fbd..25c2d0c02 100644
--- a/apps/els_lsp/priv/code_navigation/src/edoc_diagnostics.erl
+++ b/apps/els_lsp/priv/code_navigation/src/edoc_diagnostics.erl
@@ -2,7 +2,7 @@
-export([main/0]).
-%% @edoc Main function
+%% @mydoc Main function
main() ->
internal().
diff --git a/apps/els_lsp/priv/code_navigation/src/edoc_diagnostics_custom_tags.erl b/apps/els_lsp/priv/code_navigation/src/edoc_diagnostics_custom_tags.erl
new file mode 100644
index 000000000..c05c12f8a
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/edoc_diagnostics_custom_tags.erl
@@ -0,0 +1,18 @@
+-module(edoc_diagnostics_custom_tags).
+
+-export([ a/0 ]).
+
+%% @edoc
+%% `edoc' is a custom alias for `doc'
+a() ->
+ ok.
+
+%% @docc
+%% `docc' is not an existing or custom tag
+b() ->
+ ok.
+
+%% @generated
+%% The `generated' tag is used for generated code
+c() ->
+ ok.
diff --git a/apps/els_lsp/src/edoc_report.erl b/apps/els_lsp/src/edoc_report.erl
index b4923de7f..f6c4dfac6 100644
--- a/apps/els_lsp/src/edoc_report.erl
+++ b/apps/els_lsp/src/edoc_report.erl
@@ -4,6 +4,7 @@
%% The main reasons for the fork:
%% * The edoc application does not offer an API to return a
%% list of warnings and errors
+%% * Support for custom EDoc tags
%% =====================================================================
%% Licensed under the Apache License, Version 2.0 (the "License"); you may
%% not use this file except in compliance with the License. You may obtain
@@ -85,6 +86,14 @@ warning(Where, S, Vs) ->
warning(0, Where, S, Vs).
-spec warning(line(), where(), string(), [any()]) -> ok.
+warning(L, Where, "tag @~s not recognized." = S, [Tag] = Vs) ->
+ CustomTags = els_config:get(edoc_custom_tags),
+ case lists:member(atom_to_list(Tag), CustomTags) of
+ true ->
+ ok;
+ false ->
+ report(L, Where, S, Vs, warning)
+ end;
warning(L, Where, S, Vs) ->
report(L, Where, S, Vs, warning).
diff --git a/apps/els_lsp/test/els_diagnostics_SUITE.erl b/apps/els_lsp/test/els_diagnostics_SUITE.erl
index 43e6412d1..ee53775ae 100644
--- a/apps/els_lsp/test/els_diagnostics_SUITE.erl
+++ b/apps/els_lsp/test/els_diagnostics_SUITE.erl
@@ -45,6 +45,7 @@
, module_name_check_whitespace/1
, edoc_main/1
, edoc_skip_app_src/1
+ , edoc_custom_tags/1
]).
%%==============================================================================
@@ -128,7 +129,8 @@ init_per_testcase(TestCase, Config) when TestCase =:= gradualizer ->
els_mock_diagnostics:setup(),
els_test_utils:init_per_testcase(TestCase, Config);
init_per_testcase(TestCase, Config) when TestCase =:= edoc_main;
- TestCase =:= edoc_skip_app_src ->
+ TestCase =:= edoc_skip_app_src;
+ TestCase =:= edoc_custom_tags ->
meck:new(els_edoc_diagnostics, [passthrough, no_link]),
meck:expect(els_edoc_diagnostics, is_default, 0, true),
els_mock_diagnostics:setup(),
@@ -175,7 +177,8 @@ end_per_testcase(TestCase, Config) when TestCase =:= gradualizer ->
els_mock_diagnostics:teardown(),
ok;
end_per_testcase(TestCase, Config) when TestCase =:= edoc_main;
- TestCase =:= edoc_skip_app_src ->
+ TestCase =:= edoc_skip_app_src;
+ TestCase =:= edoc_custom_tags ->
meck:unload(els_edoc_diagnostics),
els_test_utils:end_per_testcase(TestCase, Config),
els_mock_diagnostics:teardown(),
@@ -708,7 +711,7 @@ edoc_main(_Config) ->
}
],
Warnings = [ #{ message =>
- <<"tag @edoc not recognized.">>
+ <<"tag @mydoc not recognized.">>
, range => {{4, 0}, {5, 0}}
}
, #{ message =>
@@ -719,7 +722,7 @@ edoc_main(_Config) ->
Hints = [],
els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
- -spec edoc_skip_app_src(config()) -> ok.
+-spec edoc_skip_app_src(config()) -> ok.
edoc_skip_app_src(_Config) ->
Path = src_path("code_navigation.app.src"),
Source = <<"Edoc">>,
@@ -728,6 +731,20 @@ edoc_skip_app_src(_Config) ->
Hints = [],
els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+-spec edoc_custom_tags(config()) -> ok.
+edoc_custom_tags(_Config) ->
+ %% Custom tags are defined in priv/code_navigation/erlang_ls.config
+ Path = src_path("edoc_diagnostics_custom_tags.erl"),
+ Source = <<"Edoc">>,
+ Errors = [],
+ Warnings = [ #{ message =>
+ <<"tag @docc not recognized.">>
+ , range => {{9, 0}, {10, 0}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+
%%==============================================================================
%% Internal Functions
%%==============================================================================
From 0f95fec819684993a3ff64773319f3c56a1afcd6 Mon Sep 17 00:00:00 2001
From: Radek Szymczyszyn
Date: Thu, 10 Mar 2022 21:19:24 +0100
Subject: [PATCH 042/239] Reload project-specific files on every Graudalizer
diagnostic run (#1240)
The check to determine if project files should be imported into Gradualizer
comes from the time when the integration relied on Gradualizer being available
through ERL_LIBS and would only be started by the diagnostic being called.
Now that Gradualizer is a dependency of ErlangLS,
it's running on each startup since ErlangLS itself.
This means the check never succeeds, therefore the project specific files
necessary for properly setting up the typechecker are never loaded.
This leads to false positives being reported such as missing remote types or
record definitions.
Reimporting the project files on each run might be a bit redundant,
but it seems to work reasonably well in practice.
Dogfooding this solutions shows it doesn't lead to visible unnecessary
load on the system.
---
.../src/els_gradualizer_diagnostics.erl | 43 ++++++-------------
1 file changed, 14 insertions(+), 29 deletions(-)
diff --git a/apps/els_lsp/src/els_gradualizer_diagnostics.erl b/apps/els_lsp/src/els_gradualizer_diagnostics.erl
index 598dba094..e2ad075f9 100644
--- a/apps/els_lsp/src/els_gradualizer_diagnostics.erl
+++ b/apps/els_lsp/src/els_gradualizer_diagnostics.erl
@@ -32,16 +32,20 @@ is_default() ->
-spec run(uri()) -> [els_diagnostics:diagnostic()].
run(Uri) ->
- case start_and_load() of
- true ->
- Path = unicode:characters_to_list(els_uri:path(Uri)),
- Includes = [{i, I} || I <- els_config:get(include_paths)],
- Opts = [return_errors] ++ Includes,
- Errors = gradualizer:type_check_files([Path], Opts),
- lists:flatmap(fun analyzer_error/1, Errors);
- false ->
- []
- end.
+ Files = lists:flatmap(
+ fun(Dir) ->
+ filelib:wildcard(filename:join(Dir, "*.erl"))
+ end,
+ els_config:get(apps_paths) ++ els_config:get(deps_paths)),
+ ?LOG_INFO("Importing files into gradualizer_db: ~p", [Files]),
+ ok = gradualizer_db:import_erl_files(Files,
+ els_config:get(include_paths)),
+ Path = unicode:characters_to_list(els_uri:path(Uri)),
+ Includes = [{i, I} || I <- els_config:get(include_paths)],
+ Opts = [return_errors] ++ Includes,
+ Errors = gradualizer:type_check_files([Path], Opts),
+ ?LOG_INFO("Gradualizer diagnostics: ~p", [Errors]),
+ lists:flatmap(fun analyzer_error/1, Errors).
-spec source() -> binary().
source() ->
@@ -51,25 +55,6 @@ source() ->
%% Internal Functions
%%==============================================================================
--spec start_and_load() -> boolean().
-start_and_load() ->
- try application:ensure_all_started(gradualizer) of
- {ok, [gradualizer]} ->
- Files = lists:flatmap(
- fun(Dir) ->
- filelib:wildcard(filename:join(Dir, "*.erl"))
- end,
- els_config:get(apps_paths) ++ els_config:get(deps_paths)),
- ok = gradualizer_db:import_erl_files(Files,
- els_config:get(include_paths)),
- true;
- {ok, []} ->
- true
- catch E:R ->
- ?LOG_ERROR("Could not start gradualizer: ~p ~p", [E, R]),
- false
- end.
-
-spec analyzer_error(any()) -> any().
analyzer_error({_Path, Error}) ->
FmtOpts = [{fmt_location, brief}, {color, never}],
From 3ef713c535d8e7347f066b42bcf653ed462b55ee Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Thu, 17 Mar 2022 15:31:52 +0100
Subject: [PATCH 043/239] Improve DAP Error Handling (#1246)
* Single message for distribution shutdown, with warning severity
* Treat 'ignored' from net_kernel as regular error, instead of crashing
* Do not silently fail DAP in case of distribution errors
* Fix error responses
---
apps/els_core/src/els_distribution_server.erl | 19 ++-
apps/els_dap/src/els_dap_general_provider.erl | 138 ++++++++++--------
apps/els_dap/src/els_dap_methods.erl | 20 ++-
apps/els_dap/src/els_dap_protocol.erl | 7 +-
4 files changed, 108 insertions(+), 76 deletions(-)
diff --git a/apps/els_core/src/els_distribution_server.erl b/apps/els_core/src/els_distribution_server.erl
index 1abfd4b8c..fee244876 100644
--- a/apps/els_core/src/els_distribution_server.erl
+++ b/apps/els_core/src/els_distribution_server.erl
@@ -57,14 +57,15 @@ start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, unused, []).
%% @doc Turns a non-distributed node into a distributed one
--spec start_distribution(atom()) -> ok.
+-spec start_distribution(atom()) -> ok | {error, any()}.
start_distribution(Name) ->
Cookie = els_config_runtime:get_cookie(),
RemoteNode = els_config_runtime:get_node_name(),
NameType = els_config_runtime:get_name_type(),
start_distribution(Name, RemoteNode, Cookie, NameType).
--spec start_distribution(atom(), atom(), atom(), shortnames | longnames) -> ok.
+-spec start_distribution(atom(), atom(), atom(), shortnames | longnames) ->
+ ok | {error, any()}.
start_distribution(Name, RemoteNode, Cookie, NameType) ->
?LOG_INFO("Enable distribution [name=~p]", [Name]),
case net_kernel:start([Name, NameType]) of
@@ -75,12 +76,14 @@ start_distribution(Name, RemoteNode, Cookie, NameType) ->
CustomCookie ->
erlang:set_cookie(RemoteNode, CustomCookie)
end,
- ?LOG_INFO("Distribution enabled [name=~p]", [Name]);
+ ?LOG_INFO("Distribution enabled [name=~p]", [Name]),
+ ok;
{error, {already_started, _Pid}} ->
- ?LOG_INFO("Distribution already enabled [name=~p]", [Name]);
- {error, {{shutdown, {failed_to_start_child, net_kernel, E1}}, E2}} ->
- ?LOG_INFO("Distribution shutdown [errs=~p]", [{E1, E2}]),
- ?LOG_INFO("Distribution shut down [name=~p]", [Name])
+ ?LOG_INFO("Distribution already enabled [name=~p]", [Name]),
+ ok;
+ {error, Error} ->
+ ?LOG_WARNING("Distribution shutdown [error=~p] [name=~p]", [Error, Name]),
+ {error, Error}
end.
%% @doc Connect to an existing runtime node, if available, or start one.
@@ -152,6 +155,8 @@ connect_and_monitor(Node, Type) ->
erlang:monitor_node(Node, true),
ok;
false ->
+ error;
+ ignored ->
error
end.
diff --git a/apps/els_dap/src/els_dap_general_provider.erl b/apps/els_dap/src/els_dap_general_provider.erl
index 9a41d27c4..44e82a406 100644
--- a/apps/els_dap/src/els_dap_general_provider.erl
+++ b/apps/els_dap/src/els_dap_general_provider.erl
@@ -80,7 +80,8 @@ init() ->
, timeout => 30
, mode => undefined}.
--spec handle_request(request(), state()) -> {result(), state()}.
+-spec handle_request(request(), state()) ->
+ {result(), state()} | {{error, binary()}, state()}.
handle_request({<<"initialize">>, _Params}, State) ->
%% quick fix to satisfy els_config initialization
{ok, RootPath} = file:get_cwd(),
@@ -90,59 +91,63 @@ handle_request({<<"initialize">>, _Params}, State) ->
ok = els_config:initialize(RootUri, Capabilities, InitOptions),
{Capabilities, State};
handle_request({<<"launch">>, #{<<"cwd">> := Cwd} = Params}, State) ->
- #{ <<"projectnode">> := ProjectNode
- , <<"cookie">> := Cookie
- , <<"timeout">> := TimeOut
- , <<"use_long_names">> := UseLongNames} = start_distribution(Params),
- case Params of
- #{ <<"runinterminal">> := Cmd
- } ->
- ParamsR
- = #{ <<"kind">> => <<"integrated">>
- , <<"title">> => ProjectNode
- , <<"cwd">> => Cwd
- , <<"args">> => Cmd
- },
- ?LOG_INFO("Sending runinterminal request: [~p]", [ParamsR]),
- els_dap_server:send_request(<<"runInTerminal">>, ParamsR),
- ok;
- _ ->
- NameTypeParam = case UseLongNames of
- true ->
- "--name";
- false ->
- "--sname"
- end,
- ?LOG_INFO("launching 'rebar3 shell`", []),
- spawn(fun() ->
- els_utils:cmd(
- "rebar3",
- [ "shell"
- , NameTypeParam
- , atom_to_list(ProjectNode)
- , "--setcookie"
- , erlang:binary_to_list(Cookie)
- ]
- )
- end)
- end,
-
- els_dap_server:send_event(<<"initialized">>, #{}),
-
- {#{}, State#{ project_node => ProjectNode
- , launch_params => Params
- , timeout => TimeOut
- }};
+ case start_distribution(Params) of
+ {ok, #{ <<"projectnode">> := ProjectNode
+ , <<"cookie">> := Cookie
+ , <<"timeout">> := TimeOut
+ , <<"use_long_names">> := UseLongNames}} ->
+ case Params of
+ #{ <<"runinterminal">> := Cmd
+ } ->
+ ParamsR
+ = #{ <<"kind">> => <<"integrated">>
+ , <<"title">> => ProjectNode
+ , <<"cwd">> => Cwd
+ , <<"args">> => Cmd
+ },
+ ?LOG_INFO("Sending runinterminal request: [~p]", [ParamsR]),
+ els_dap_server:send_request(<<"runInTerminal">>, ParamsR),
+ ok;
+ _ ->
+ NameTypeParam = case UseLongNames of
+ true ->
+ "--name";
+ false ->
+ "--sname"
+ end,
+ ?LOG_INFO("launching 'rebar3 shell`", []),
+ spawn(fun() ->
+ els_utils:cmd(
+ "rebar3",
+ [ "shell"
+ , NameTypeParam
+ , atom_to_list(ProjectNode)
+ , "--setcookie"
+ , erlang:binary_to_list(Cookie)
+ ]
+ )
+ end)
+ end,
+ els_dap_server:send_event(<<"initialized">>, #{}),
+ {#{}, State#{ project_node => ProjectNode
+ , launch_params => Params
+ , timeout => TimeOut
+ }};
+ {error, Error} ->
+ {{error, distribution_error(Error)}, State}
+ end;
handle_request({<<"attach">>, Params}, State) ->
- #{ <<"projectnode">> := ProjectNode
- , <<"timeout">> := TimeOut} = start_distribution(Params),
-
- els_dap_server:send_event(<<"initialized">>, #{}),
-
- {#{}, State#{ project_node => ProjectNode
- , launch_params => Params
- , timeout => TimeOut
- }};
+ case start_distribution(Params) of
+ {ok, #{ <<"projectnode">> := ProjectNode
+ , <<"timeout">> := TimeOut}} ->
+ els_dap_server:send_event(<<"initialized">>, #{}),
+ {#{}, State#{ project_node => ProjectNode
+ , launch_params => Params
+ , timeout => TimeOut
+ }};
+ {error, Error} ->
+ {{error, distribution_error(Error)}, State}
+ end;
handle_request( {<<"configurationDone">>, _Params}
, #{ project_node := ProjectNode
, launch_params := LaunchParams
@@ -422,7 +427,10 @@ handle_request({<<"disconnect">>, _Params}
<<"launch">> ->
els_dap_rpc:halt(ProjectNode)
end,
- els_utils:halt(0),
+ stop_debugger(),
+ {#{}, State};
+handle_request({<<"disconnect">>, _Params}, State) ->
+ stop_debugger(),
{#{}, State}.
-spec evaluate_condition(els_dap_breakpoints:line_breaks(), module(),
@@ -848,7 +856,7 @@ ensure_connected(Node, Timeout) ->
stop_debugger() ->
%% the project node is down, there is nothing left to do then to exit
els_dap_server:send_event(<<"terminated">>, #{}),
- els_dap_server:send_event(<<"exited">>, #{ <<"exitCode">> => <<"0">>}),
+ els_dap_server:send_event(<<"exited">>, #{ <<"exitCode">> => 0 }),
?LOG_NOTICE("terminating debug adapter"),
els_utils:halt(0).
@@ -887,7 +895,7 @@ check_project_node_name(ProjectNode, true) ->
binary_to_atom(ProjectNode, utf8)
end.
--spec start_distribution(map()) -> map().
+-spec start_distribution(map()) -> {ok, map()} | {error, any()}.
start_distribution(Params) ->
#{<<"cwd">> := Cwd} = Params,
ok = file:set_cwd(Cwd),
@@ -921,8 +929,18 @@ start_distribution(Params) ->
%% start distribution
LocalNode = els_distribution_server:node_name("erlang_ls_dap",
binary_to_list(Name), NameType),
- els_distribution_server:start_distribution(LocalNode, ConfProjectNode,
- Cookie, NameType),
- ?LOG_INFO("Distribution up on: [~p]", [LocalNode]),
+ case els_distribution_server:start_distribution(LocalNode, ConfProjectNode,
+ Cookie, NameType) of
+ ok ->
+ ?LOG_INFO("Distribution up on: [~p]", [LocalNode]),
+ {ok, Config#{ <<"projectnode">> => ConfProjectNode}};
+ {error, Error} ->
+ ?LOG_ERROR("Cannot start distribution for ~p", [LocalNode]),
+ {error, Error}
+ end.
- Config#{ <<"projectnode">> => ConfProjectNode}.
+-spec distribution_error(any()) -> binary().
+distribution_error(Error) ->
+ els_utils:to_binary(
+ lists:flatten(
+ io_lib:format("Could not start Erlang distribution. ~p", [Error]))).
diff --git a/apps/els_dap/src/els_dap_methods.erl b/apps/els_dap/src/els_dap_methods.erl
index 2b6fe1739..0666c23a6 100644
--- a/apps/els_dap/src/els_dap_methods.erl
+++ b/apps/els_dap/src/els_dap_methods.erl
@@ -36,21 +36,27 @@ dispatch(Command, Args, Type, State) ->
Type:Reason:Stack ->
?LOG_ERROR( "Unexpected error [type=~p] [error=~p] [stack=~p]"
, [Type, Reason, Stack]),
- Error = #{ code => ?ERR_UNKNOWN_ERROR_CODE
- , message => <<"Unexpected error while ", Command/binary>>
- },
+ Error = <<"Unexpected error while ", Command/binary>>,
{error_response, Error, State}
end.
-spec do_dispatch(atom(), params(), state()) -> result().
do_dispatch(Command, Args, #{status := initialized} = State) ->
Request = {Command, Args},
- Result = els_provider:handle_request(els_dap_general_provider, Request),
- {response, Result, State};
+ case els_provider:handle_request(els_dap_general_provider, Request) of
+ {error, Error} ->
+ {error_response, Error, State};
+ Result ->
+ {response, Result, State}
+ end;
do_dispatch(<<"initialize">>, Args, State) ->
Request = {<<"initialize">>, Args},
- Result = els_provider:handle_request(els_dap_general_provider, Request),
- {response, Result, State#{status => initialized}};
+ case els_provider:handle_request(els_dap_general_provider, Request) of
+ {error, Error} ->
+ {error_response, Error, State};
+ Result ->
+ {response, Result, State#{status => initialized}}
+ end;
do_dispatch(_Command, _Args, State) ->
Message = <<"The server is not fully initialized yet, please wait.">>,
Result = #{ code => ?ERR_SERVER_NOT_INITIALIZED
diff --git a/apps/els_dap/src/els_dap_protocol.erl b/apps/els_dap/src/els_dap_protocol.erl
index 936f8b78f..ab0271468 100644
--- a/apps/els_dap/src/els_dap_protocol.erl
+++ b/apps/els_dap/src/els_dap_protocol.erl
@@ -60,13 +60,16 @@ response(Seq, Command, Result) ->
?LOG_DEBUG("[Response] [message=~p]", [Message]),
content(jsx:encode(Message)).
--spec error_response(number(), any(), any()) -> binary().
+-spec error_response(number(), any(), binary()) -> binary().
error_response(Seq, Command, Error) ->
Message = #{ type => <<"response">>
, request_seq => Seq
, success => false
, command => Command
- , body => #{ error => Error
+ , body => #{ error => #{ id => Seq
+ , format => Error
+ , showUser => true
+ }
}
},
?LOG_DEBUG("[Response] [message=~p]", [Message]),
From 2bfcc46408761f2b04517a6cf0effbf99ebb57bc Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Fri, 18 Mar 2022 15:20:19 +0100
Subject: [PATCH 044/239] Add support for didChangeWatchedFiles (#1247)
---
apps/els_core/include/els_core.hrl | 12 +++
apps/els_core/src/els_client.erl | 31 +++++--
.../code_navigation/src/watched_file_a.erl | 6 ++
.../code_navigation/src/watched_file_b.erl | 6 ++
apps/els_lsp/src/els_dt_document.erl | 5 ++
apps/els_lsp/src/els_dt_document_index.erl | 8 +-
apps/els_lsp/src/els_dt_signatures.erl | 10 ++-
apps/els_lsp/src/els_methods.erl | 5 +-
apps/els_lsp/src/els_text_synchronization.erl | 20 +++++
apps/els_lsp/test/els_references_SUITE.erl | 90 +++++++++++++++++++
.../watched_file_c.erl | 6 ++
11 files changed, 184 insertions(+), 15 deletions(-)
create mode 100644 apps/els_lsp/priv/code_navigation/src/watched_file_a.erl
create mode 100644 apps/els_lsp/priv/code_navigation/src/watched_file_b.erl
create mode 100644 apps/els_lsp/test/els_references_SUITE_data/watched_file_c.erl
diff --git a/apps/els_core/include/els_core.hrl b/apps/els_core/include/els_core.hrl
index c3b6e8ef1..85e5d4a39 100644
--- a/apps/els_core/include/els_core.hrl
+++ b/apps/els_core/include/els_core.hrl
@@ -575,6 +575,18 @@
, command => els_command:command()
}.
+%%------------------------------------------------------------------------------
+%% Workspace
+%%------------------------------------------------------------------------------
+
+-define(FILE_CHANGE_TYPE_CREATED, 1).
+-define(FILE_CHANGE_TYPE_CHANGED, 2).
+-define(FILE_CHANGE_TYPE_DELETED, 3).
+
+-type file_change_type() :: ?FILE_CHANGE_TYPE_CREATED
+ | ?FILE_CHANGE_TYPE_CHANGED
+ | ?FILE_CHANGE_TYPE_DELETED.
+
%%------------------------------------------------------------------------------
%% Internals
%%------------------------------------------------------------------------------
diff --git a/apps/els_core/src/els_client.erl b/apps/els_core/src/els_client.erl
index f88506c7f..3a59edc21 100644
--- a/apps/els_core/src/els_client.erl
+++ b/apps/els_core/src/els_client.erl
@@ -27,6 +27,7 @@
, definition/3
, did_open/4
, did_save/1
+ , did_change_watched_files/1
, did_close/1
, document_symbol/1
, exit/0
@@ -180,6 +181,10 @@ did_open(Uri, LanguageId, Version, Text) ->
did_save(Uri) ->
gen_server:call(?SERVER, {did_save, {Uri}}).
+-spec did_change_watched_files([{uri(), file_change_type()}]) -> ok.
+did_change_watched_files(Changes) ->
+ gen_server:call(?SERVER, {did_change_watched_files, {Changes}}).
+
-spec did_close(uri()) -> ok.
did_close(Uri) ->
gen_server:call(?SERVER, {did_close, {Uri}}).
@@ -269,13 +274,15 @@ init(#{io_device := IoDevice}) ->
{ok, State}.
-spec handle_call(any(), any(), state()) -> {reply, any(), state()}.
-handle_call({Action, Opts}, _From, State) when Action =:= did_save
- orelse Action =:= did_close
- orelse Action =:= did_open
- orelse Action =:= initialized ->
+handle_call({Action, Opts}, _From, State) when
+ Action =:= did_save;
+ Action =:= did_close;
+ Action =:= did_open;
+ Action =:= did_change_watched_files;
+ Action =:= initialized ->
#state{io_device = IoDevice} = State,
Method = method_lookup(Action),
- Params = notification_params(Opts),
+ Params = notification_params(Action, Opts),
Content = els_protocol:notification(Method, Params),
send(IoDevice, Content),
{reply, ok, State};
@@ -422,6 +429,8 @@ method_lookup(callhierarchy_incomingcalls) -> <<"callHierarchy/incomingCalls">>;
method_lookup(callhierarchy_outgoingcalls) -> <<"callHierarchy/outgoingCalls">>;
method_lookup(workspace_symbol) -> <<"workspace/symbol">>;
method_lookup(workspace_executecommand) -> <<"workspace/executeCommand">>;
+method_lookup(did_change_watched_files) ->
+ <<"workspace/didChangeWatchedFiles">>;
method_lookup(initialize) -> <<"initialize">>;
method_lookup(initialized) -> <<"initialized">>.
@@ -495,18 +504,22 @@ request_params({_Action, {Uri, Line, Char}}) ->
}
}.
--spec notification_params(tuple()) -> map().
-notification_params({Uri}) ->
+-spec notification_params(atom(), tuple()) -> map().
+notification_params(did_change_watched_files, {Changes}) ->
+ #{changes => [#{ uri => Uri
+ , type => Type
+ } || {Uri, Type} <- Changes]};
+notification_params(_Action, {Uri}) ->
TextDocument = #{ uri => Uri },
#{textDocument => TextDocument};
-notification_params({Uri, LanguageId, Version, Text}) ->
+notification_params(_Action, {Uri, LanguageId, Version, Text}) ->
TextDocument = #{ uri => Uri
, languageId => LanguageId
, version => Version
, text => Text
},
#{textDocument => TextDocument};
-notification_params({}) ->
+notification_params(_Action, {}) ->
#{}.
-spec is_notification(map()) -> boolean().
diff --git a/apps/els_lsp/priv/code_navigation/src/watched_file_a.erl b/apps/els_lsp/priv/code_navigation/src/watched_file_a.erl
new file mode 100644
index 000000000..22c990bef
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/watched_file_a.erl
@@ -0,0 +1,6 @@
+-module(watched_file_a).
+
+-export([ main/0 ]).
+
+main() ->
+ ok.
diff --git a/apps/els_lsp/priv/code_navigation/src/watched_file_b.erl b/apps/els_lsp/priv/code_navigation/src/watched_file_b.erl
new file mode 100644
index 000000000..30b1287b6
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/watched_file_b.erl
@@ -0,0 +1,6 @@
+-module(watched_file_b).
+
+-export([ main/0 ]).
+
+main() ->
+ watched_file_a:main().
diff --git a/apps/els_lsp/src/els_dt_document.erl b/apps/els_lsp/src/els_dt_document.erl
index e6bf5ebf6..6cc4e0376 100644
--- a/apps/els_lsp/src/els_dt_document.erl
+++ b/apps/els_lsp/src/els_dt_document.erl
@@ -19,6 +19,7 @@
-export([ insert/1
, lookup/1
+ , delete/1
]).
-export([ new/2
@@ -125,6 +126,10 @@ lookup(Uri) ->
{ok, Items} = els_db:lookup(name(), Uri),
{ok, [to_item(Item) || Item <- Items]}.
+-spec delete(uri()) -> ok.
+delete(Uri) ->
+ els_db:delete(name(), Uri).
+
-spec new(uri(), binary()) -> item().
new(Uri, Text) ->
Extension = filename:extension(Uri),
diff --git a/apps/els_lsp/src/els_dt_document_index.erl b/apps/els_lsp/src/els_dt_document_index.erl
index dcb70cd74..5cb1a9b95 100644
--- a/apps/els_lsp/src/els_dt_document_index.erl
+++ b/apps/els_lsp/src/els_dt_document_index.erl
@@ -17,11 +17,12 @@
%% API
%%==============================================================================
--export([new/3]).
+-export([ new/3 ]).
-export([ find_by_kind/1
, insert/1
, lookup/1
+ , delete_by_uri/1
]).
%%==============================================================================
@@ -78,6 +79,11 @@ lookup(Id) ->
{ok, Items} = els_db:lookup(name(), Id),
{ok, [to_item(Item) || Item <- Items]}.
+-spec delete_by_uri(uri()) -> ok | {error, any()}.
+delete_by_uri(Uri) ->
+ Pattern = #els_dt_document_index{uri = Uri, _ = '_'},
+ ok = els_db:match_delete(name(), Pattern).
+
-spec find_by_kind(els_dt_document:kind()) -> {ok, [item()]}.
find_by_kind(Kind) ->
Pattern = #els_dt_document_index{kind = Kind, _ = '_'},
diff --git a/apps/els_lsp/src/els_dt_signatures.erl b/apps/els_lsp/src/els_dt_signatures.erl
index 9d7cab853..f2a2b8694 100644
--- a/apps/els_lsp/src/els_dt_signatures.erl
+++ b/apps/els_lsp/src/els_dt_signatures.erl
@@ -19,6 +19,7 @@
-export([ insert/1
, lookup/1
+ , delete_by_module/1
]).
%%==============================================================================
@@ -30,8 +31,8 @@
%% Item Definition
%%==============================================================================
--record(els_dt_signatures, { mfa :: mfa() | '_'
- , spec :: binary()
+-record(els_dt_signatures, { mfa :: mfa() | '_' | {atom(), '_', '_'}
+ , spec :: binary() | '_'
}).
-type els_dt_signatures() :: #els_dt_signatures{}.
@@ -74,3 +75,8 @@ insert(Map) when is_map(Map) ->
lookup(MFA) ->
{ok, Items} = els_db:lookup(name(), MFA),
{ok, [to_item(Item) || Item <- Items]}.
+
+-spec delete_by_module(atom()) -> ok.
+delete_by_module(Module) ->
+ Pattern = #els_dt_signatures{mfa = {Module, '_', '_'}, _ = '_'},
+ ok = els_db:match_delete(name(), Pattern).
diff --git a/apps/els_lsp/src/els_methods.erl b/apps/els_lsp/src/els_methods.erl
index 4f0c3f8a4..4716c5cf6 100644
--- a/apps/els_lsp/src/els_methods.erl
+++ b/apps/els_lsp/src/els_methods.erl
@@ -429,9 +429,8 @@ workspace_executecommand(Params, State) ->
%%==============================================================================
-spec workspace_didchangewatchedfiles(map(), state()) -> result().
-workspace_didchangewatchedfiles(_Params, State) ->
- %% Some clients rely on these notifications to be successful.
- %% Let's just ignore them.
+workspace_didchangewatchedfiles(Params, State) ->
+ ok = els_text_synchronization:did_change_watched_files(Params),
{noresponse, State}.
%%==============================================================================
diff --git a/apps/els_lsp/src/els_text_synchronization.erl b/apps/els_lsp/src/els_text_synchronization.erl
index 1d2206c97..abca45edf 100644
--- a/apps/els_lsp/src/els_text_synchronization.erl
+++ b/apps/els_lsp/src/els_text_synchronization.erl
@@ -8,6 +8,7 @@
, did_open/1
, did_save/1
, did_close/1
+ , did_change_watched_files/1
]).
-spec sync_mode() -> text_document_sync_kind().
@@ -64,6 +65,13 @@ did_save(Params) ->
els_provider:handle_request(Provider, {run_diagnostics, Params}),
ok.
+-spec did_change_watched_files(map()) -> ok.
+did_change_watched_files(Params) ->
+ #{<<"changes">> := Changes} = Params,
+ [handle_file_change(Uri, Type)
+ || #{<<"uri">> := Uri, <<"type">> := Type} <- Changes],
+ ok.
+
-spec did_close(map()) -> ok.
did_close(_Params) -> ok.
@@ -75,3 +83,15 @@ to_edit(#{<<"text">> := Text, <<"range">> := Range}) ->
{#{ from => {FromL, FromC}
, to => {ToL, ToC}
}, els_utils:to_list(Text)}.
+
+-spec handle_file_change(uri(), file_change_type()) -> ok.
+handle_file_change(Uri, Type) when Type =:= ?FILE_CHANGE_TYPE_CREATED;
+ Type =:= ?FILE_CHANGE_TYPE_CHANGED ->
+ {ok, Text} = file:read_file(els_uri:path(Uri)),
+ ok = els_index_buffer:load(Uri, Text),
+ ok = els_index_buffer:flush(Uri);
+handle_file_change(Uri, Type) when Type =:= ?FILE_CHANGE_TYPE_DELETED ->
+ ok = els_dt_document:delete(Uri),
+ ok = els_dt_document_index:delete_by_uri(Uri),
+ ok = els_dt_references:delete_by_uri(Uri),
+ ok = els_dt_signatures:delete_by_module(els_uri:module(Uri)).
diff --git a/apps/els_lsp/test/els_references_SUITE.erl b/apps/els_lsp/test/els_references_SUITE.erl
index 508d7030f..eae89560c 100644
--- a/apps/els_lsp/test/els_references_SUITE.erl
+++ b/apps/els_lsp/test/els_references_SUITE.erl
@@ -31,6 +31,9 @@
, type_local/1
, type_remote/1
, type_included/1
+ , refresh_after_watched_file_deleted/1
+ , refresh_after_watched_file_changed/1
+ , refresh_after_watched_file_added/1
]).
%%==============================================================================
@@ -38,6 +41,7 @@
%%==============================================================================
-include_lib("common_test/include/ct.hrl").
-include_lib("stdlib/include/assert.hrl").
+-include_lib("els_core/include/els_core.hrl").
%%==============================================================================
%% Types
@@ -64,10 +68,21 @@ end_per_suite(Config) ->
els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
+init_per_testcase(TestCase, Config0)
+ when TestCase =:= refresh_after_watched_file_changed ->
+ Config = els_test_utils:init_per_testcase(TestCase, Config0),
+ PathB = ?config(watched_file_b_path, Config),
+ {ok, OldContent} = file:read_file(PathB),
+ [{old_content, OldContent}|Config];
init_per_testcase(TestCase, Config) ->
els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
+end_per_testcase(TestCase, Config)
+ when TestCase =:= refresh_after_watched_file_changed ->
+ PathB = ?config(watched_file_b_path, Config),
+ ok = file:write_file(PathB, ?config(old_content, Config)),
+ els_test_utils:end_per_testcase(TestCase, Config);
end_per_testcase(TestCase, Config) ->
els_test_utils:end_per_testcase(TestCase, Config).
@@ -487,6 +502,81 @@ type_included(Config) ->
assert_locations(Locations, ExpectedLocations),
ok.
+-spec refresh_after_watched_file_deleted(config()) -> ok.
+refresh_after_watched_file_deleted(Config) ->
+ %% Before
+ UriA = ?config(watched_file_a_uri, Config),
+ UriB = ?config(watched_file_b_uri, Config),
+ ExpectedLocationsBefore = [ #{ uri => UriB
+ , range => #{from => {6, 3}, to => {6, 22}}
+ }
+ ],
+ #{result := LocationsBefore} = els_client:references(UriA, 5, 2),
+ assert_locations(LocationsBefore, ExpectedLocationsBefore),
+ %% Delete (Simulate a checkout, rebase or similar)
+ els_client:did_change_watched_files([{UriB, ?FILE_CHANGE_TYPE_DELETED}]),
+ %% After
+ #{result := null} = els_client:references(UriA, 5, 2),
+ ok.
+
+-spec refresh_after_watched_file_changed(config()) -> ok.
+refresh_after_watched_file_changed(Config) ->
+ %% Before
+ UriA = ?config(watched_file_a_uri, Config),
+ UriB = ?config(watched_file_b_uri, Config),
+ PathB = ?config(watched_file_b_path, Config),
+ ExpectedLocationsBefore = [ #{ uri => UriB
+ , range => #{from => {6, 3}, to => {6, 22}}
+ }
+ ],
+ #{result := LocationsBefore} = els_client:references(UriA, 5, 2),
+ assert_locations(LocationsBefore, ExpectedLocationsBefore),
+ %% Edit (Simulate a checkout, rebase or similar)
+ NewContent = re:replace(?config(old_content, Config),
+ "watched_file_a:main()",
+ "watched_file_a:main(), watched_file_a:main()"),
+ ok = file:write_file(PathB, NewContent),
+ els_client:did_change_watched_files([{UriB, ?FILE_CHANGE_TYPE_CHANGED}]),
+ %% After
+ ExpectedLocationsAfter = [ #{ uri => UriB
+ , range => #{from => {6, 3}, to => {6, 22}}
+ }
+ , #{ uri => UriB
+ , range => #{from => {6, 26}, to => {6, 45}}
+ }
+ ],
+ #{result := LocationsAfter} = els_client:references(UriA, 5, 2),
+ assert_locations(LocationsAfter, ExpectedLocationsAfter),
+ ok.
+
+-spec refresh_after_watched_file_added(config()) -> ok.
+refresh_after_watched_file_added(Config) ->
+ %% Before
+ UriA = ?config(watched_file_a_uri, Config),
+ UriB = ?config(watched_file_b_uri, Config),
+ ExpectedLocationsBefore = [ #{ uri => UriB
+ , range => #{from => {6, 3}, to => {6, 22}}
+ }
+ ],
+ #{result := LocationsBefore} = els_client:references(UriA, 5, 2),
+ assert_locations(LocationsBefore, ExpectedLocationsBefore),
+ %% Add (Simulate a checkout, rebase or similar)
+ DataDir = ?config(data_dir, Config),
+ PathC = filename:join([DataDir, "watched_file_c.erl"]),
+ UriC = els_uri:uri(els_utils:to_binary(PathC)),
+ els_client:did_change_watched_files([{UriC, ?FILE_CHANGE_TYPE_CREATED}]),
+ %% After
+ ExpectedLocationsAfter = [ #{ uri => UriC
+ , range => #{from => {6, 3}, to => {6, 22}}
+ }
+ , #{ uri => UriB
+ , range => #{from => {6, 3}, to => {6, 22}}
+ }
+ ],
+ #{result := LocationsAfter} = els_client:references(UriA, 5, 2),
+ assert_locations(LocationsAfter, ExpectedLocationsAfter),
+ ok.
+
%%==============================================================================
%% Internal functions
%%==============================================================================
diff --git a/apps/els_lsp/test/els_references_SUITE_data/watched_file_c.erl b/apps/els_lsp/test/els_references_SUITE_data/watched_file_c.erl
new file mode 100644
index 000000000..c49876fff
--- /dev/null
+++ b/apps/els_lsp/test/els_references_SUITE_data/watched_file_c.erl
@@ -0,0 +1,6 @@
+-module(watched_file_c).
+
+-export([ main/0 ]).
+
+main() ->
+ watched_file_a:main().
From 45ab202f0ca729a3bae7d45d5871467e0580853f Mon Sep 17 00:00:00 2001
From: Andrew Mayorov
Date: Mon, 21 Mar 2022 16:06:45 +0300
Subject: [PATCH 045/239] Handle `not_found` result from docsh (#1198)
Also make sure that functions dealing with temporary group leader
here have no chance to get stuck altogether.
Fixes #1177.
---
apps/els_lsp/src/els_docs.erl | 33 ++++++++++++++++++++++++---------
1 file changed, 24 insertions(+), 9 deletions(-)
diff --git a/apps/els_lsp/src/els_docs.erl b/apps/els_lsp/src/els_docs.erl
index ddea8f2e2..8aa40b994 100644
--- a/apps/els_lsp/src/els_docs.erl
+++ b/apps/els_lsp/src/els_docs.erl
@@ -342,9 +342,13 @@ edoc(M, F, A) ->
, [doc, spec]]),
flush_group_leader_proxy(GL),
- {ok, [{{function, F, A}, _Anno,
- _Signature, Desc, _Metadata}|_]} = Res,
- format_edoc(Desc)
+ case Res of
+ {ok, [{{function, F, A}, _Anno,
+ _Signature, Desc, _Metadata}|_]} ->
+ format_edoc(Desc);
+ {not_found, _} ->
+ []
+ end
catch C:E:ST ->
IO = flush_group_leader_proxy(GL),
?LOG_DEBUG("[hover] Error fetching edoc [error=~p]",
@@ -352,6 +356,8 @@ edoc(M, F, A) ->
case IO of
timeout ->
[];
+ noproc ->
+ [];
IO ->
[{text, IO}]
end
@@ -389,15 +395,24 @@ setup_group_leader_proxy() ->
-spec flush_group_leader_proxy(pid()) -> [term()] | term().
flush_group_leader_proxy(OrigGL) ->
GL = group_leader(),
- Ref = monitor(process, GL),
- group_leader(OrigGL, self()),
- GL ! {get, Ref, self()},
- receive
- {Ref, Msg} ->
+ case GL of
+ OrigGL ->
+ % This is the effect of setting a monitor on nonexisting process.
+ noproc;
+ _ ->
+ Ref = monitor(process, GL),
+ group_leader(OrigGL, self()),
+ GL ! {get, Ref, self()},
+ receive
+ {Ref, Msg} ->
demonitor(Ref, [flush]),
Msg;
- {'DOWN', process, Ref, Reason} ->
+ {'DOWN', process, Ref, Reason} ->
Reason
+ after 5000 ->
+ demonitor(Ref, [flush]),
+ timeout
+ end
end.
-spec spawn_group_proxy([any()]) -> ok.
From ad06727867fb33db87d543ac464d3293992b401b Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Thu, 24 Mar 2022 17:28:48 +0100
Subject: [PATCH 046/239] Start apps as permanent (#1249)
---
apps/els_dap/src/els_dap.erl | 2 +-
apps/els_lsp/src/erlang_ls.erl | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/els_dap/src/els_dap.erl b/apps/els_dap/src/els_dap.erl
index 54e44b468..024e98de9 100644
--- a/apps/els_dap/src/els_dap.erl
+++ b/apps/els_dap/src/els_dap.erl
@@ -25,7 +25,7 @@ main(Args) ->
ok = parse_args(Args),
application:set_env(els_core, server, els_dap_server),
configure_logging(),
- {ok, _} = application:ensure_all_started(?APP),
+ {ok, _} = application:ensure_all_started(?APP, permanent),
patch_logging(),
?LOG_INFO("Started Erlang LS - DAP server", []),
receive _ -> ok end.
diff --git a/apps/els_lsp/src/erlang_ls.erl b/apps/els_lsp/src/erlang_ls.erl
index 2db185375..6ce326f07 100644
--- a/apps/els_lsp/src/erlang_ls.erl
+++ b/apps/els_lsp/src/erlang_ls.erl
@@ -23,7 +23,7 @@ main(Args) ->
ok = parse_args(Args),
application:set_env(els_core, server, els_server),
configure_logging(),
- {ok, _} = application:ensure_all_started(?APP),
+ {ok, _} = application:ensure_all_started(?APP, permanent),
patch_logging(),
configure_client_logging(),
?LOG_INFO("Started erlang_ls server", []),
From 9eed7d4e865b6ad8a653cb495944bda53bf82db1 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Fri, 25 Mar 2022 14:50:46 +0100
Subject: [PATCH 047/239] Skip indexing of generated files (#1255)
* Add ability to skip generated files (by tag in their header)
* Allow generated files to be indexed on-demand
* Better indexing report on completion
---
apps/els_core/src/els_config.erl | 3 +
apps/els_core/src/els_config_indexing.erl | 44 +++++++++
apps/els_lsp/src/els_indexing.erl | 97 ++++++++++++++-----
apps/els_lsp/test/els_indexer_SUITE.erl | 74 ++++++++++++++
.../generated_file_by_custom_tag.erl | 7 ++
.../generated_file_by_tag.erl | 7 ++
6 files changed, 206 insertions(+), 26 deletions(-)
create mode 100644 apps/els_core/src/els_config_indexing.erl
create mode 100644 apps/els_lsp/test/els_indexer_SUITE_data/generated_file_by_custom_tag.erl
create mode 100644 apps/els_lsp/test/els_indexer_SUITE_data/generated_file_by_tag.erl
diff --git a/apps/els_core/src/els_config.erl b/apps/els_core/src/els_config.erl
index ab2832771..7c7facf5f 100644
--- a/apps/els_core/src/els_config.erl
+++ b/apps/els_core/src/els_config.erl
@@ -131,6 +131,7 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
ElvisConfigPath = maps:get("elvis_config_path", Config, undefined),
BSPEnabled = maps:get("bsp_enabled", Config, auto),
IncrementalSync = maps:get("incremental_sync", Config, true),
+ Indexing = maps:get("indexing", Config, #{}),
CompilerTelemetryEnabled
= maps:get("compiler_telemetry_enabled", Config, false),
EDocCustomTags = maps:get("edoc_custom_tags", Config, []),
@@ -159,6 +160,8 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
ok = set(compiler_telemetry_enabled, CompilerTelemetryEnabled),
ok = set(edoc_custom_tags, EDocCustomTags),
ok = set(incremental_sync, IncrementalSync),
+ ok = set(indexing, maps:merge( els_config_indexing:default_config()
+ , Indexing)),
%% Calculated from the above
ok = set(apps_paths , project_paths(RootPath, AppsDirs, false)),
ok = set(deps_paths , project_paths(RootPath, DepsDirs, false)),
diff --git a/apps/els_core/src/els_config_indexing.erl b/apps/els_core/src/els_config_indexing.erl
new file mode 100644
index 000000000..525f1faaf
--- /dev/null
+++ b/apps/els_core/src/els_config_indexing.erl
@@ -0,0 +1,44 @@
+-module(els_config_indexing).
+
+-include("els_core.hrl").
+
+-export([ default_config/0 ]).
+
+%% Getters
+-export([ get_skip_generated_files/0
+ , get_generated_files_tag/0
+ ]).
+
+-type config() :: #{ string() => string() }.
+
+-spec default_config() -> config().
+default_config() ->
+ #{ "skip_generated_files" => default_skip_generated_files()
+ , "generated_files_tag" => default_generated_files_tag()
+ }.
+
+-spec get_skip_generated_files() -> boolean().
+get_skip_generated_files() ->
+ Value = maps:get("skip_generated_files",
+ els_config:get(indexing),
+ default_skip_generated_files()),
+ normalize_boolean(Value).
+
+-spec get_generated_files_tag() -> string().
+get_generated_files_tag() ->
+ maps:get("generated_files_tag",
+ els_config:get(indexing),
+ default_generated_files_tag()).
+
+-spec default_skip_generated_files() -> string().
+default_skip_generated_files() ->
+ "false".
+
+-spec default_generated_files_tag() -> string().
+default_generated_files_tag() ->
+ "@generated".
+
+-spec normalize_boolean(boolean() | string()) -> boolean().
+normalize_boolean("true") -> true;
+normalize_boolean("false") -> false;
+normalize_boolean(Bool) when is_boolean(Bool) -> Bool.
diff --git a/apps/els_lsp/src/els_indexing.erl b/apps/els_lsp/src/els_indexing.erl
index 0b3ece720..f103698d6 100644
--- a/apps/els_lsp/src/els_indexing.erl
+++ b/apps/els_lsp/src/els_indexing.erl
@@ -22,11 +22,6 @@
%%==============================================================================
-type mode() :: 'deep' | 'shallow'.
-%%==============================================================================
-%% Macros
-%%==============================================================================
--define(SERVER, ?MODULE).
-
%%==============================================================================
%% Exported functions
%%==============================================================================
@@ -50,9 +45,33 @@ find_and_index_file(FileName) ->
-spec index_file(binary()) -> {ok, uri()}.
index_file(Path) ->
- try_index_file(Path, 'deep'),
+ GeneratedFilesTag = els_config_indexing:get_generated_files_tag(),
+ try_index_file(Path, 'deep', false, GeneratedFilesTag),
{ok, els_uri:uri(Path)}.
+-spec index_if_not_generated(uri(), binary(), mode(), boolean(), string()) ->
+ ok | skipped.
+index_if_not_generated(Uri, Text, Mode, false, _GeneratedFilesTag) ->
+ index(Uri, Text, Mode);
+index_if_not_generated(Uri, Text, Mode, true, GeneratedFilesTag) ->
+ case is_generated_file(Text, GeneratedFilesTag) of
+ true ->
+ ?LOG_DEBUG("Skip indexing for generated file ~p", [Uri]),
+ skipped;
+ false ->
+ ok = index(Uri, Text, Mode)
+ end.
+
+-spec is_generated_file(binary(), string()) -> boolean().
+is_generated_file(Text, Tag) ->
+ [Line|_] = string:split(Text, "\n", leading),
+ case re:run(Line, Tag) of
+ {match, _} ->
+ true;
+ nomatch ->
+ false
+ end.
+
-spec index(uri(), binary(), mode()) -> ok.
index(Uri, Text, Mode) ->
MD5 = erlang:md5(Text),
@@ -132,10 +151,23 @@ start() ->
-spec start(binary(), [{string(), 'deep' | 'shallow'}]) -> ok.
start(Group, Entries) ->
- Task = fun({Dir, Mode}, _) -> index_dir(Dir, Mode) end,
+ SkipGeneratedFiles = els_config_indexing:get_skip_generated_files(),
+ GeneratedFilesTag = els_config_indexing:get_generated_files_tag(),
+ Task = fun({Dir, Mode}, {Succeeded0, Skipped0, Failed0}) ->
+ {Su, Sk, Fa} = index_dir(Dir, Mode,
+ SkipGeneratedFiles, GeneratedFilesTag),
+ {Succeeded0 + Su, Skipped0 + Sk, Failed0 + Fa}
+ end,
Config = #{ task => Task
, entries => Entries
, title => <<"Indexing ", Group/binary>>
+ , initial_state => {0, 0, 0}
+ , on_complete =>
+ fun({Succeeded, Skipped, Failed}) ->
+ ?LOG_INFO("Completed indexing for ~s "
+ "(succeeded: ~p, skipped: ~p, failed: ~p)",
+ [Group, Succeeded, Skipped, Failed])
+ end
},
{ok, _Pid} = els_background_job:new(Config),
ok.
@@ -144,14 +176,17 @@ start(Group, Entries) ->
%% Internal functions
%%==============================================================================
+
%% @doc Try indexing a file.
--spec try_index_file(binary(), mode()) -> ok | {error, any()}.
-try_index_file(FullName, Mode) ->
+-spec try_index_file(binary(), mode(), boolean(), string()) ->
+ ok | skipped | {error, any()}.
+try_index_file(FullName, Mode, SkipGeneratedFiles, GeneratedFilesTag) ->
Uri = els_uri:uri(FullName),
try
?LOG_DEBUG("Indexing file. [filename=~s, uri=~s]", [FullName, Uri]),
{ok, Text} = file:read_file(FullName),
- ok = index(Uri, Text, Mode)
+ index_if_not_generated(Uri, Text, Mode,
+ SkipGeneratedFiles, GeneratedFilesTag)
catch Type:Reason:St ->
?LOG_ERROR("Error indexing file "
"[filename=~s, uri=~s] "
@@ -179,13 +214,23 @@ register_reference(Uri, #{kind := Kind, id := Id, range := Range})
, #{id => Id, uri => Uri, range => Range}
).
--spec index_dir(string(), mode()) -> {non_neg_integer(), non_neg_integer()}.
+-spec index_dir(string(), mode()) ->
+ {non_neg_integer(), non_neg_integer(), non_neg_integer()}.
index_dir(Dir, Mode) ->
+ SkipGeneratedFiles = els_config_indexing:get_skip_generated_files(),
+ GeneratedFilesTag = els_config_indexing:get_generated_files_tag(),
+ index_dir(Dir, Mode, SkipGeneratedFiles, GeneratedFilesTag).
+
+-spec index_dir(string(), mode(), boolean(), string()) ->
+ {non_neg_integer(), non_neg_integer(), non_neg_integer()}.
+index_dir(Dir, Mode, SkipGeneratedFiles, GeneratedFilesTag) ->
?LOG_DEBUG("Indexing directory. [dir=~s] [mode=~s]", [Dir, Mode]),
- F = fun(FileName, {Succeeded, Failed}) ->
- case try_index_file(els_utils:to_binary(FileName), Mode) of
- ok -> {Succeeded + 1, Failed};
- {error, _Error} -> {Succeeded, Failed + 1}
+ F = fun(FileName, {Succeeded, Skipped, Failed}) ->
+ case try_index_file(els_utils:to_binary(FileName), Mode,
+ SkipGeneratedFiles, GeneratedFilesTag) of
+ ok -> {Succeeded + 1, Skipped, Failed};
+ skipped -> {Succeeded, Skipped + 1, Failed};
+ {error, _Error} -> {Succeeded, Skipped, Failed + 1}
end
end,
Filter = fun(Path) ->
@@ -193,18 +238,18 @@ index_dir(Dir, Mode) ->
lists:member(Ext, [".erl", ".hrl", ".escript"])
end,
- {Time, {Succeeded, Failed}} = timer:tc( els_utils
- , fold_files
- , [ F
- , Filter
- , Dir
- , {0, 0}
- ]
- ),
+ {Time, {Succeeded, Skipped, Failed}} = timer:tc( els_utils
+ , fold_files
+ , [ F
+ , Filter
+ , Dir
+ , {0, 0, 0}
+ ]
+ ),
?LOG_DEBUG("Finished indexing directory. [dir=~s] [mode=~s] [time=~p] "
- "[succeeded=~p] "
- "[failed=~p]", [Dir, Mode, Time/1000/1000, Succeeded, Failed]),
- {Succeeded, Failed}.
+ "[succeeded=~p] [skipped=~p] [failed=~p]",
+ [Dir, Mode, Time/1000/1000, Succeeded, Skipped, Failed]),
+ {Succeeded, Skipped, Failed}.
-spec entries_apps() -> [{string(), 'deep' | 'shallow'}].
entries_apps() ->
diff --git a/apps/els_lsp/test/els_indexer_SUITE.erl b/apps/els_lsp/test/els_indexer_SUITE.erl
index 07d24433e..ee0adccb9 100644
--- a/apps/els_lsp/test/els_indexer_SUITE.erl
+++ b/apps/els_lsp/test/els_indexer_SUITE.erl
@@ -13,6 +13,9 @@
, index_erl_file/1
, index_hrl_file/1
, index_unkown_extension/1
+ , do_not_skip_generated_file_by_tag_by_default/1
+ , skip_generated_file_by_tag/1
+ , skip_generated_file_by_custom_tag/1
]).
%%==============================================================================
@@ -20,6 +23,7 @@
%%==============================================================================
-include_lib("common_test/include/ct.hrl").
-include_lib("stdlib/include/assert.hrl").
+-include_lib("els_core/include/els_core.hrl").
%%==============================================================================
%% Types
@@ -42,10 +46,29 @@ end_per_suite(Config) ->
els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
+init_per_testcase(TestCase, Config) when
+ TestCase =:= skip_generated_file_by_tag ->
+ meck:new(els_config_indexing, [passthrough, no_link]),
+ meck:expect(els_config_indexing, get_skip_generated_files, fun() -> true end),
+ els_test_utils:init_per_testcase(TestCase, Config);
+init_per_testcase(TestCase, Config) when
+ TestCase =:= skip_generated_file_by_custom_tag ->
+ meck:new(els_config_indexing, [passthrough, no_link]),
+ meck:expect(els_config_indexing,
+ get_skip_generated_files,
+ fun() -> true end),
+ meck:expect(els_config_indexing,
+ get_generated_files_tag,
+ fun() -> "@customgeneratedtag" end),
+ els_test_utils:init_per_testcase(TestCase, Config);
init_per_testcase(TestCase, Config) ->
els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
+end_per_testcase(TestCase, Config) when
+ TestCase =:= skip_generated_file_by_tag ->
+ meck:unload(els_config_indexing),
+ els_test_utils:end_per_testcase(TestCase, Config);
end_per_testcase(TestCase, Config) ->
els_test_utils:end_per_testcase(TestCase, Config).
@@ -88,3 +111,54 @@ index_unkown_extension(Config) ->
{ok, Uri} = els_indexing:index_file(Path),
{ok, [#{kind := other}]} = els_dt_document:lookup(Uri),
ok.
+
+-spec do_not_skip_generated_file_by_tag_by_default(config()) -> ok.
+do_not_skip_generated_file_by_tag_by_default(Config) ->
+ DataDir = data_dir(Config),
+ GeneratedByTagUri = uri(DataDir, "generated_file_by_tag.erl"),
+ GeneratedByCustomTagUri = uri(DataDir, "generated_file_by_custom_tag.erl"),
+ ?assertEqual({4, 0, 0}, els_indexing:index_dir(DataDir, 'deep')),
+ {ok, [#{ id := generated_file_by_tag
+ , kind := module
+ }
+ ]} = els_dt_document:lookup(GeneratedByTagUri),
+ {ok, [#{ id := generated_file_by_custom_tag
+ , kind := module
+ }
+ ]} = els_dt_document:lookup(GeneratedByCustomTagUri),
+ ok.
+
+-spec skip_generated_file_by_tag(config()) -> ok.
+skip_generated_file_by_tag(Config) ->
+ DataDir = data_dir(Config),
+ GeneratedByTagUri = uri(DataDir, "generated_file_by_tag.erl"),
+ GeneratedByCustomTagUri = uri(DataDir, "generated_file_by_custom_tag.erl"),
+ ?assertEqual({3, 1, 0}, els_indexing:index_dir(DataDir, 'deep')),
+ {ok, []} = els_dt_document:lookup(GeneratedByTagUri),
+ {ok, [#{ id := generated_file_by_custom_tag
+ , kind := module
+ }
+ ]} = els_dt_document:lookup(GeneratedByCustomTagUri),
+ ok.
+
+-spec skip_generated_file_by_custom_tag(config()) -> ok.
+skip_generated_file_by_custom_tag(Config) ->
+ DataDir = data_dir(Config),
+ GeneratedByTagUri = uri(DataDir, "generated_file_by_tag.erl"),
+ GeneratedByCustomTagUri = uri(DataDir, "generated_file_by_custom_tag.erl"),
+ ?assertEqual({3, 1, 0}, els_indexing:index_dir(DataDir, 'deep')),
+ {ok, [#{ id := generated_file_by_tag
+ , kind := module
+ }
+ ]} = els_dt_document:lookup(GeneratedByTagUri),
+ {ok, []} = els_dt_document:lookup(GeneratedByCustomTagUri),
+ ok.
+
+-spec data_dir(proplists:proplist()) -> binary().
+data_dir(Config) ->
+ ?config(data_dir, Config).
+
+-spec uri(binary(), string()) -> uri().
+uri(DataDir, FileName) ->
+ Path = els_utils:to_binary(filename:join(DataDir, FileName)),
+ els_uri:uri(Path).
diff --git a/apps/els_lsp/test/els_indexer_SUITE_data/generated_file_by_custom_tag.erl b/apps/els_lsp/test/els_indexer_SUITE_data/generated_file_by_custom_tag.erl
new file mode 100644
index 000000000..8f5a6a40f
--- /dev/null
+++ b/apps/els_lsp/test/els_indexer_SUITE_data/generated_file_by_custom_tag.erl
@@ -0,0 +1,7 @@
+%% This file is @customgeneratedtag
+-module(generated_file_by_custom_tag).
+
+-export([main/0]).
+
+main() ->
+ ok.
diff --git a/apps/els_lsp/test/els_indexer_SUITE_data/generated_file_by_tag.erl b/apps/els_lsp/test/els_indexer_SUITE_data/generated_file_by_tag.erl
new file mode 100644
index 000000000..4f47a37b6
--- /dev/null
+++ b/apps/els_lsp/test/els_indexer_SUITE_data/generated_file_by_tag.erl
@@ -0,0 +1,7 @@
+%% This file is @generated
+-module(generated_file_by_tag).
+
+-export([main/0]).
+
+main() ->
+ ok.
From b87b84d645a0ec414082016cb102fb31d4382cbf Mon Sep 17 00:00:00 2001
From: Robert Fiko <64466886+robertfiko@users.noreply.github.com>
Date: Fri, 1 Apr 2022 15:24:45 +0200
Subject: [PATCH 048/239] RefactorErl Diagnostics (#1137)
Add experimental support for RefactorErl diagnostics.
---
apps/els_core/src/els_config.erl | 6 +
apps/els_lsp/src/els_diagnostics.erl | 1 +
.../src/els_refactorerl_diagnostics.erl | 90 ++++++++++
apps/els_lsp/src/els_refactorerl_utils.erl | 160 ++++++++++++++++++
apps/els_lsp/test/els_diagnostics_SUITE.erl | 65 +++++++
5 files changed, 322 insertions(+)
create mode 100644 apps/els_lsp/src/els_refactorerl_diagnostics.erl
create mode 100644 apps/els_lsp/src/els_refactorerl_utils.erl
diff --git a/apps/els_core/src/els_config.erl b/apps/els_core/src/els_config.erl
index 7c7facf5f..6fac5b758 100644
--- a/apps/els_core/src/els_config.erl
+++ b/apps/els_core/src/els_config.erl
@@ -56,6 +56,7 @@
| indexing_enabled
| bsp_enabled
| compiler_telemetry_enabled
+ | refactorerl
| edoc_custom_tags.
-type path() :: file:filename().
@@ -77,6 +78,7 @@
, indexing_enabled => boolean()
, bsp_enabled => boolean() | auto
, compiler_telemetry_enabled => boolean()
+ , refactorerl => map() | 'notconfigured'
}.
%%==============================================================================
@@ -138,6 +140,8 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
IndexingEnabled = maps:get(<<"indexingEnabled">>, InitOptions, true),
+ RefactorErl = maps:get("refactorerl", Config, notconfigured),
+
%% Passed by the LSP client
ok = set(root_uri , RootUri),
%% Read from the configuration file
@@ -180,6 +184,8 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
%% Init Options
ok = set(capabilities , Capabilities),
ok = set(indexing_enabled, IndexingEnabled),
+
+ ok = set(refactorerl, RefactorErl),
ok.
-spec start_link() -> {ok, pid()}.
diff --git a/apps/els_lsp/src/els_diagnostics.erl b/apps/els_lsp/src/els_diagnostics.erl
index bc25d44e5..6f76fe639 100644
--- a/apps/els_lsp/src/els_diagnostics.erl
+++ b/apps/els_lsp/src/els_diagnostics.erl
@@ -69,6 +69,7 @@ available_diagnostics() ->
, <<"unused_includes">>
, <<"unused_macros">>
, <<"unused_record_fields">>
+ , <<"refactorerl">>
].
-spec default_diagnostics() -> [diagnostic_id()].
diff --git a/apps/els_lsp/src/els_refactorerl_diagnostics.erl b/apps/els_lsp/src/els_refactorerl_diagnostics.erl
new file mode 100644
index 000000000..8e837d79e
--- /dev/null
+++ b/apps/els_lsp/src/els_refactorerl_diagnostics.erl
@@ -0,0 +1,90 @@
+%%==============================================================================
+%% RefactorErl Diagnostics
+%%==============================================================================
+-module(els_refactorerl_diagnostics).
+
+%%==============================================================================
+%% Behaviours
+%%==============================================================================
+-behaviour(els_diagnostics).
+
+%%==============================================================================
+%% Exports
+%%==============================================================================
+-export([ is_default/0
+ , run/1
+ , source/0
+ ]).
+
+%%==============================================================================
+%% Includes & Defines
+%%==============================================================================
+-include("els_lsp.hrl").
+
+%%==============================================================================
+%% Types
+%%==============================================================================
+-type refactorerl_diagnostic_alias() :: atom().
+-type refactorerl_diagnostic_result() :: {range(), string()}.
+%-type refactorerl_query() :: [char()].
+
+%%==============================================================================
+%% Callback Functions
+%%==============================================================================
+
+-spec is_default() -> boolean().
+is_default() ->
+ false.
+
+-spec run(uri()) -> [els_diagnostics:diagnostic()].
+run(Uri) ->
+ case filename:extension(Uri) of
+ <<".erl">> ->
+ case els_refactorerl_utils:referl_node() of
+ {error, _} ->
+ [];
+ {ok, _} ->
+ case els_refactorerl_utils:add(Uri) of
+ error ->
+ [];
+ ok ->
+ Module = els_uri:module(Uri),
+ Diags = enabled_diagnostics(),
+ Results = els_refactorerl_utils:run_diagnostics(Diags, Module),
+ make_diagnostics(Results)
+ end
+ end;
+ _ ->
+ []
+ end.
+
+-spec source() -> binary().
+source() ->
+ els_refactorerl_utils:source_name().
+
+%%==============================================================================
+%% Internal Functions
+%%==============================================================================
+% @doc
+% Returns the enabled diagnostics by merging default and configed
+-spec enabled_diagnostics() -> [refactorerl_diagnostic_alias()].
+enabled_diagnostics() ->
+ case els_config:get(refactorerl) of
+ #{"diagnostics" := List} ->
+ [list_to_atom(Element) || Element <- List];
+ _ ->
+ []
+ end.
+
+
+% @doc
+% Constructs the ELS diagnostic from RefactorErl result
+-spec make_diagnostics([refactorerl_diagnostic_result()]) -> any().
+make_diagnostics([{Range, Message} | Tail]) ->
+ Severity = ?DIAGNOSTIC_WARNING,
+ Source = source(),
+ Diag = els_diagnostics:make_diagnostic(Range, Message, Severity, Source),
+ [ Diag | make_diagnostics(Tail) ];
+
+make_diagnostics([]) ->
+ [].
diff --git a/apps/els_lsp/src/els_refactorerl_utils.erl b/apps/els_lsp/src/els_refactorerl_utils.erl
new file mode 100644
index 000000000..3a666e168
--- /dev/null
+++ b/apps/els_lsp/src/els_refactorerl_utils.erl
@@ -0,0 +1,160 @@
+%%==============================================================================
+%% Erlang LS & Refactor Erl communication
+%%==============================================================================
+-module(els_refactorerl_utils).
+
+%%==============================================================================
+%% API
+%%==============================================================================
+-export([ referl_node/0
+ , notification/1
+ , notification/2
+ , run_diagnostics/2
+ , source_name/0
+ , add/1
+ ]).
+
+%%==============================================================================
+%% Includes & Defines
+%%==============================================================================
+-include("els_lsp.hrl").
+
+%%==============================================================================
+%% API
+%%==============================================================================
+
+%% @doc
+%% Returns the RefactorErl node, if it can't, it returns error and its cause.
+%% It returns the node given in config, if it is alive.
+%% First it runs the validation functions, the result of validation will be
+%% notified to the user.
+%% If the node once was validated there will be no display messages.
+%%
+%% The configuration can store the node and its status.
+%% - Either a simple NodeString, which needs to be checked and configure
+%% - {Node, Status} where boths are atoms
+%% Possible statuses: validated, disconnected, disabled
+%% 'disabled', when it won't try to reconnect
+%% 'disconnected', when it will try to reconnect
+%% - notconfigured, the node is not configured in the config file
+%%
+%%
+%% Node can be:
+%% - NodeStr
+%% - {Status, Node} where both are atoms.
+%% - Status can be:
+%% - validated: node is running
+%% - disconnected: node is not running
+%% - disabled: RefactorErl is turned off for this session. T
+%% his can happen after an unsuccessfull query attempt.
+-spec referl_node() -> {ok, atom()}
+ | {error, disconnected}
+ | {error, disabled}
+ | {error, other}.
+referl_node() ->
+ case els_config:get(refactorerl) of
+ #{"node" := {Node, validated}} ->
+ {ok, Node};
+
+ #{"node" := {Node, disconnected}} ->
+ connect_node({retry, Node});
+
+ #{"node" := {_Node, disabled}} ->
+ {error, disabled};
+
+ #{"node" := NodeStr} ->
+ RT = els_config_runtime:get_name_type(),
+ Node = els_utils:compose_node_name(NodeStr, RT),
+ connect_node({validate, Node});
+
+ notconfigured ->
+ {error, disabled};
+
+ _ ->
+ {error, other}
+ end.
+
+
+%%@doc
+%% Adds a module to the RefactorErl node. Using the UI router
+%% Returns 'ok' if successfull
+%% Returns 'error' if it fails
+-spec add(uri()) -> error | ok.
+add(Uri) ->
+ case els_refactorerl_utils:referl_node() of
+ {ok, Node} ->
+ Path = [binary_to_list(els_uri:path(Uri))],
+ rpc:call(Node, referl_els, add, [Path]); %% returns error | ok
+ _ ->
+ error
+ end.
+
+%%@doc
+%% Runs list of diagnostic aliases on refactorerl
+-spec run_diagnostics(list(), atom()) -> list().
+run_diagnostics(DiagnosticAliases, Module) ->
+ case els_refactorerl_utils:referl_node() of
+ {ok, Node} ->
+ %% returns error | ok
+ rpc:call(Node, referl_els, run_diagnostics, [DiagnosticAliases, Module]);
+ _ -> % In this case there was probably error.
+ []
+ end.
+
+%%@doc
+%% Util for popping up notifications
+-spec notification(string(), number()) -> atom().
+notification(Msg, Severity) ->
+ Param = #{ type => Severity,
+ message => list_to_binary(Msg) },
+ els_server:send_notification(<<"window/showMessage">>, Param).
+
+-spec notification(string()) -> atom().
+notification(Msg) ->
+ notification(Msg, ?MESSAGE_TYPE_INFO).
+
+%%==============================================================================
+%% Internal Functions
+%%==============================================================================
+
+%%@doc
+%% Checks if the given node is running RefactorErl with ELS interface
+-spec is_refactorerl(atom()) -> boolean().
+is_refactorerl(Node) ->
+ case rpc:call(Node, referl_els, ping, [], 500) of
+ {refactorerl_els, pong} -> true;
+ _ -> false
+ end.
+
+%%@doc
+%% Tries to connect to a node.
+%% When it status is validate, the node hasn't been checked yet,
+%% so it will reports the success, and failure as well,
+%%
+%% when retry, it won't report.
+-spec connect_node({validate | retry, atom()}) -> {error, disconnected}
+ | atom().
+connect_node({Status, Node}) ->
+ Config = els_config:get(refactorerl),
+ case {Status, is_refactorerl(Node)} of
+ {validate, false} ->
+ els_config:set(refactorerl, Config#{"node" => {Node, disconnected}}),
+ {error, disconnected};
+ {retry, false} ->
+ els_config:set(refactorerl, Config#{"node" => {Node, disconnected}}),
+ {error, disconnected};
+ {_, true} ->
+ notification("RefactorErl is connected!", ?MESSAGE_TYPE_INFO),
+ els_config:set(refactorerl, Config#{"node" => {Node, validated}}),
+ {ok, Node}
+ end.
+
+%%==============================================================================
+%% Values
+%%==============================================================================
+
+%%@doc
+%% Common soruce name for all RefactorErl based backend(s)
+-spec source_name() -> binary().
+source_name() ->
+ <<"RefactorErl">>.
\ No newline at end of file
diff --git a/apps/els_lsp/test/els_diagnostics_SUITE.erl b/apps/els_lsp/test/els_diagnostics_SUITE.erl
index ee53775ae..3cf0d48c6 100644
--- a/apps/els_lsp/test/els_diagnostics_SUITE.erl
+++ b/apps/els_lsp/test/els_diagnostics_SUITE.erl
@@ -39,6 +39,7 @@
, unused_includes_compiler_attribute/1
, exclude_unused_includes/1
, unused_macros/1
+ , unused_macros_refactorerl/1
, unused_record_fields/1
, gradualizer/1
, module_name_check/1
@@ -128,6 +129,7 @@ init_per_testcase(TestCase, Config) when TestCase =:= gradualizer ->
meck:expect(els_gradualizer_diagnostics, is_default, 0, true),
els_mock_diagnostics:setup(),
els_test_utils:init_per_testcase(TestCase, Config);
+
init_per_testcase(TestCase, Config) when TestCase =:= edoc_main;
TestCase =:= edoc_skip_app_src;
TestCase =:= edoc_custom_tags ->
@@ -135,6 +137,14 @@ init_per_testcase(TestCase, Config) when TestCase =:= edoc_main;
meck:expect(els_edoc_diagnostics, is_default, 0, true),
els_mock_diagnostics:setup(),
els_test_utils:init_per_testcase(TestCase, Config);
+
+
+% RefactorErl
+init_per_testcase(TestCase, Config)
+ when TestCase =:= unused_macros_refactorerl ->
+ mock_refactorerl(),
+ els_test_utils:init_per_testcase(TestCase, Config);
+
init_per_testcase(TestCase, Config) ->
els_mock_diagnostics:setup(),
els_test_utils:init_per_testcase(TestCase, Config).
@@ -176,6 +186,7 @@ end_per_testcase(TestCase, Config) when TestCase =:= gradualizer ->
els_test_utils:end_per_testcase(TestCase, Config),
els_mock_diagnostics:teardown(),
ok;
+
end_per_testcase(TestCase, Config) when TestCase =:= edoc_main;
TestCase =:= edoc_skip_app_src;
TestCase =:= edoc_custom_tags ->
@@ -183,11 +194,21 @@ end_per_testcase(TestCase, Config) when TestCase =:= edoc_main;
els_test_utils:end_per_testcase(TestCase, Config),
els_mock_diagnostics:teardown(),
ok;
+
+end_per_testcase(TestCase, Config)
+ when TestCase =:= unused_macros_refactorerl ->
+ unmock_refactoerl(),
+ els_test_utils:end_per_testcase(TestCase, Config),
+ els_mock_diagnostics:teardown(),
+ ok;
end_per_testcase(TestCase, Config) ->
els_test_utils:end_per_testcase(TestCase, Config),
els_mock_diagnostics:teardown(),
ok.
+% RefactorErl
+
+
%%==============================================================================
%% Testcases
%%==============================================================================
@@ -679,6 +700,7 @@ gradualizer(_Config) ->
Hints = [],
els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+
-spec module_name_check(config()) -> ok.
module_name_check(_Config) ->
Path = src_path("diagnostics_module_name_check.erl"),
@@ -740,6 +762,23 @@ edoc_custom_tags(_Config) ->
Warnings = [ #{ message =>
<<"tag @docc not recognized.">>
, range => {{9, 0}, {10, 0}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+
+
+% RefactorErl test cases
+-spec unused_macros_refactorerl(config()) -> ok.
+unused_macros_refactorerl(_Config) ->
+ Path = src_path("diagnostics_unused_macros.erl"),
+ Source = <<"RefactorErl">>,
+ Errors = [],
+ Warnings = [ #{ message => <<"Unused macro: UNUSED_MACRO">>
+ , range => {{5, 0}, {5, 35}}
+ },
+ #{ message => <<"Unused macro: UNUSED_MACRO_WITH_ARG">>
+ , range => {{6, 0}, {6, 36}}
}
],
Hints = [],
@@ -818,3 +857,29 @@ src_path(Module) ->
include_path(Header) ->
filename:join(["code_navigation", "include", Header]).
+
+% Mock RefactorErl utils
+mock_refactorerl() ->
+ {ok, HostName} = inet:gethostname(),
+ NodeName = list_to_atom("referl_fake@" ++ HostName),
+
+ meck:new(els_refactorerl_utils, [passthrough, no_link, unstick]),
+ meck:expect(els_refactorerl_utils, run_diagnostics, 2,
+ [ {# {'end' => #{character => 35, line => 5},
+ start => #{character => 0, line => 5}},
+ <<"Unused macro: UNUSED_MACRO">>},
+ {# {'end' => #{character => 36, line => 6},
+ start => #{character => 0, line => 6}},
+ <<"Unused macro: UNUSED_MACRO_WITH_ARG">>}]
+ ),
+ meck:expect(els_refactorerl_utils, referl_node, 0, {ok, NodeName}),
+ meck:expect(els_refactorerl_utils, add, 1, ok),
+ meck:expect(els_refactorerl_utils, source_name, 0, <<"RefactorErl">>),
+
+ meck:new(els_refactorerl_diagnostics, [passthrough, no_link, unstick]),
+ meck:expect(els_refactorerl_diagnostics, is_default, 0, true).
+
+
+unmock_refactoerl() ->
+ meck:unload(els_refactorerl_diagnostics),
+ meck:unload(els_refactorerl_utils).
\ No newline at end of file
From a9727c943ed630c88600ca625310d27d0b12b054 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Fri, 8 Apr 2022 14:57:49 +0200
Subject: [PATCH 049/239] On demand indexing (#1260)
---
apps/els_core/src/els_config.erl | 4 +-
apps/els_core/src/els_utils.erl | 26 +-
apps/els_lsp/src/els_background_job.erl | 12 +-
apps/els_lsp/src/els_buffer_server.erl | 126 ++++++++
apps/els_lsp/src/els_buffer_sup.erl | 47 +++
apps/els_lsp/src/els_completion_provider.erl | 14 +-
apps/els_lsp/src/els_dt_document.erl | 103 ++++++-
apps/els_lsp/src/els_dt_references.erl | 22 +-
apps/els_lsp/src/els_dt_signatures.erl | 20 +-
apps/els_lsp/src/els_index_buffer.erl | 140 ---------
apps/els_lsp/src/els_indexing.erl | 276 ++++++++----------
apps/els_lsp/src/els_sup.erl | 10 +-
apps/els_lsp/src/els_text_search.erl | 46 +++
apps/els_lsp/src/els_text_synchronization.erl | 42 ++-
.../els_lsp/test/els_call_hierarchy_SUITE.erl | 63 ++--
apps/els_lsp/test/els_hover_SUITE.erl | 2 +-
apps/els_lsp/test/els_indexer_SUITE.erl | 12 +-
apps/els_lsp/test/els_indexing_SUITE.erl | 2 +-
.../els_lsp/test/els_rebar3_release_SUITE.erl | 4 +-
apps/els_lsp/test/els_references_SUITE.erl | 52 ++--
apps/els_lsp/test/els_test_utils.erl | 3 +-
21 files changed, 592 insertions(+), 434 deletions(-)
create mode 100644 apps/els_lsp/src/els_buffer_server.erl
create mode 100644 apps/els_lsp/src/els_buffer_sup.erl
delete mode 100644 apps/els_lsp/src/els_index_buffer.erl
create mode 100644 apps/els_lsp/src/els_text_search.erl
diff --git a/apps/els_core/src/els_config.erl b/apps/els_core/src/els_config.erl
index 6fac5b758..c595d6c86 100644
--- a/apps/els_core/src/els_config.erl
+++ b/apps/els_core/src/els_config.erl
@@ -273,8 +273,8 @@ consult_config([Path | Paths], ReportMissingConfig) ->
[Config] -> {Path, Config}
catch
Class:Error ->
- ?LOG_WARNING( "Could not read config file: path=~p class=~p error=~p"
- , [Path, Class, Error]),
+ ?LOG_DEBUG( "Could not read config file: path=~p class=~p error=~p"
+ , [Path, Class, Error]),
consult_config(Paths, ReportMissingConfig)
end.
diff --git a/apps/els_core/src/els_utils.erl b/apps/els_core/src/els_utils.erl
index ded6b42b1..80b02013b 100644
--- a/apps/els_core/src/els_utils.erl
+++ b/apps/els_core/src/els_utils.erl
@@ -110,7 +110,7 @@ find_header(Id) ->
{ok, Uri};
[] ->
FileName = atom_to_list(Id) ++ ".hrl",
- els_indexing:find_and_index_file(FileName)
+ els_indexing:find_and_deeply_index_file(FileName)
end.
%% @doc Look for a module in the DB
@@ -128,14 +128,14 @@ find_module(Id) ->
find_modules(Id) ->
{ok, Candidates} = els_dt_document_index:lookup(Id),
case [Uri || #{kind := module, uri := Uri} <- Candidates] of
- [] ->
- FileName = atom_to_list(Id) ++ ".erl",
- case els_indexing:find_and_index_file(FileName) of
- {ok, Uri} -> {ok, [Uri]};
- Error -> Error
- end;
- Uris ->
- {ok, prioritize_uris(Uris)}
+ [] ->
+ FileName = atom_to_list(Id) ++ ".erl",
+ case els_indexing:find_and_deeply_index_file(FileName) of
+ {ok, Uri} -> {ok, [Uri]};
+ Error -> Error
+ end;
+ Uris ->
+ {ok, prioritize_uris(Uris)}
end.
%% @doc Look for a document in the DB.
@@ -150,13 +150,13 @@ lookup_document(Uri) ->
{ok, Document};
{ok, []} ->
Path = els_uri:path(Uri),
- {ok, Uri} = els_indexing:index_file(Path),
+ {ok, Uri} = els_indexing:shallow_index(Path, app),
case els_dt_document:lookup(Uri) of
{ok, [Document]} ->
{ok, Document};
- Error ->
- ?LOG_INFO("Document lookup failed [error=~p] [uri=~p]", [Error, Uri]),
- {error, Error}
+ {ok, []} ->
+ ?LOG_INFO("Document lookup failed [uri=~p]", [Uri]),
+ {error, document_lookup_failed}
end
end.
diff --git a/apps/els_lsp/src/els_background_job.erl b/apps/els_lsp/src/els_background_job.erl
index d58b71908..a5687d8c3 100644
--- a/apps/els_lsp/src/els_background_job.erl
+++ b/apps/els_lsp/src/els_background_job.erl
@@ -187,13 +187,21 @@ terminate(normal, #{ config := #{on_complete := OnComplete}
?LOG_DEBUG("Background job completed.", []),
OnComplete(InternalState),
ok;
-terminate(Reason, #{ config := #{on_error := OnError}
+terminate(Reason, #{ config := #{ on_error := OnError
+ , title := Title
+ }
, internal_state := InternalState
, token := Token
, total := Total
, progress_enabled := ProgressEnabled
}) ->
- ?LOG_WARNING( "Background job aborted. [reason=~p]", [Reason]),
+ case Reason of
+ shutdown ->
+ ?LOG_DEBUG("Background job terminated.", []);
+ _ ->
+ ?LOG_ERROR( "Background job aborted. [reason=~p] [title=~p",
+ [Reason, Title])
+ end,
notify_end(Token, Total, ProgressEnabled),
OnError(InternalState),
ok.
diff --git a/apps/els_lsp/src/els_buffer_server.erl b/apps/els_lsp/src/els_buffer_server.erl
new file mode 100644
index 000000000..293037323
--- /dev/null
+++ b/apps/els_lsp/src/els_buffer_server.erl
@@ -0,0 +1,126 @@
+%%%=============================================================================
+%%% @doc Buffer edits to an open buffer to avoid re-indexing too often.
+%%% @end
+%%%=============================================================================
+-module(els_buffer_server).
+
+%%==============================================================================
+%% API
+%%==============================================================================
+-export([ new/2
+ , stop/1
+ , apply_edits/2
+ , flush/1
+ ]).
+
+-export([ start_link/2 ]).
+
+%%==============================================================================
+%% Callbacks for the gen_server behaviour
+%%==============================================================================
+-behaviour(gen_server).
+-export([ init/1
+ , handle_call/3
+ , handle_cast/2
+ , handle_info/2
+ ]).
+
+%%==============================================================================
+%% Macro Definitions
+%%==============================================================================
+-define(FLUSH_DELAY, 200). %% ms
+
+%%==============================================================================
+%% Type Definitions
+%%==============================================================================
+-type text() :: binary().
+-type state() :: #{ uri := uri()
+ , text := text()
+ , ref := undefined | reference()
+ , pending := [{pid(), any()}]
+ }.
+-type buffer() :: pid().
+
+%%==============================================================================
+%% Includes
+%%==============================================================================
+-include_lib("kernel/include/logger.hrl").
+-include("els_lsp.hrl").
+
+%%==============================================================================
+%% API
+%%==============================================================================
+-spec new(uri(), text()) -> {ok, pid()}.
+new(Uri, Text) ->
+ supervisor:start_child(els_buffer_sup, [Uri, Text]).
+
+-spec stop(buffer()) -> ok.
+stop(Buffer) ->
+ supervisor:terminate_child(els_buffer_sup, Buffer).
+
+-spec apply_edits(buffer(), [els_text:edit()]) -> ok.
+apply_edits(Buffer, Edits) ->
+ gen_server:cast(Buffer, {apply_edits, Edits}).
+
+-spec flush(buffer()) -> text().
+flush(Buffer) ->
+ gen_server:call(Buffer, {flush}).
+
+-spec start_link(uri(), text()) -> {ok, buffer()}.
+start_link(Uri, Text) ->
+ gen_server:start_link(?MODULE, {Uri, Text}, []).
+
+%%==============================================================================
+%% Callbacks for the gen_server behaviour
+%%==============================================================================
+-spec init({uri(), text()}) -> {ok, state()}.
+init({Uri, Text}) ->
+ {ok, #{ uri => Uri, text => Text, ref => undefined, pending => [] }}.
+
+-spec handle_call(any(), {pid(), any()}, state()) -> {reply, any(), state()}.
+handle_call({flush}, From, State) ->
+ #{uri := Uri, ref := Ref0, pending := Pending0} = State,
+ ?LOG_DEBUG("[~p] Flushing request [uri=~p]", [?MODULE, Uri]),
+ cancel_flush(Ref0),
+ Ref = schedule_flush(),
+ {noreply, State#{ref => Ref, pending => [From|Pending0]}};
+handle_call(Request, _From, State) ->
+ {reply, {not_implemented, Request}, State}.
+
+-spec handle_cast(any(), state()) -> {noreply, state()}.
+handle_cast({apply_edits, Edits}, #{uri := Uri} = State) ->
+ ?LOG_DEBUG("[~p] Applying edits [uri=~p] [edits=~p]", [?MODULE, Uri, Edits]),
+ #{text := Text0, ref := Ref0} = State,
+ cancel_flush(Ref0),
+ Text = els_text:apply_edits(Text0, Edits),
+ Ref = schedule_flush(),
+ {noreply, State#{text => Text, ref => Ref}}.
+
+-spec handle_info(any(), state()) -> {noreply, state()}.
+handle_info(flush, #{uri := Uri, text := Text, pending := Pending0} = State) ->
+ ?LOG_DEBUG("[~p] Flushing [uri=~p]", [?MODULE, Uri]),
+ do_flush(Uri, Text),
+ [gen_server:reply(From, Text) || From <- Pending0],
+ {noreply, State#{pending => [], ref => undefined}};
+handle_info(_Request, State) ->
+ {noreply, State}.
+
+%%==============================================================================
+%% Internal Functions
+%%==============================================================================
+
+-spec schedule_flush() -> reference().
+schedule_flush() ->
+ erlang:send_after(?FLUSH_DELAY, self(), flush).
+
+-spec cancel_flush(undefined | reference()) -> ok.
+cancel_flush(undefined) ->
+ ok;
+cancel_flush(Ref) ->
+ erlang:cancel_timer(Ref),
+ ok.
+
+-spec do_flush(uri(), text()) -> ok.
+do_flush(Uri, Text) ->
+ {ok, Document} = els_utils:lookup_document(Uri),
+ els_indexing:deep_index(Document#{text => Text}).
diff --git a/apps/els_lsp/src/els_buffer_sup.erl b/apps/els_lsp/src/els_buffer_sup.erl
new file mode 100644
index 000000000..c122983ea
--- /dev/null
+++ b/apps/els_lsp/src/els_buffer_sup.erl
@@ -0,0 +1,47 @@
+%%==============================================================================
+%% Supervisor for Buffers
+%%==============================================================================
+-module(els_buffer_sup).
+
+%%==============================================================================
+%% Behaviours
+%%==============================================================================
+-behaviour(supervisor).
+
+%%==============================================================================
+%% Exports
+%%==============================================================================
+
+%% API
+-export([ start_link/0 ]).
+
+%% Supervisor Callbacks
+-export([ init/1 ]).
+
+%%==============================================================================
+%% Defines
+%%==============================================================================
+-define(SERVER, ?MODULE).
+
+%%==============================================================================
+%% API
+%%==============================================================================
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ supervisor:start_link({local, ?SERVER}, ?MODULE, []).
+
+%%==============================================================================
+%% Supervisor callbacks
+%%==============================================================================
+-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
+init([]) ->
+ SupFlags = #{ strategy => simple_one_for_one
+ , intensity => 5
+ , period => 60
+ },
+ ChildSpecs = [#{ id => els_buffer_sup
+ , start => {els_buffer_server, start_link, []}
+ , restart => temporary
+ , shutdown => 5000
+ }],
+ {ok, {SupFlags, ChildSpecs}}.
diff --git a/apps/els_lsp/src/els_completion_provider.erl b/apps/els_lsp/src/els_completion_provider.erl
index a8d5c2650..e8d8fec9b 100644
--- a/apps/els_lsp/src/els_completion_provider.erl
+++ b/apps/els_lsp/src/els_completion_provider.erl
@@ -44,8 +44,18 @@ handle_request({completion, Params}, State) ->
}
, <<"textDocument">> := #{<<"uri">> := Uri}
} = Params,
- ok = els_index_buffer:flush(Uri),
- {ok, #{text := Text} = Document} = els_utils:lookup_document(Uri),
+ %% Ensure there are no pending changes.
+ {ok, Document} = els_utils:lookup_document(Uri),
+ #{buffer := Buffer, text := Text0} = Document,
+ Text = case Buffer of
+ undefined ->
+ %% This clause is only kept due to the current test suites,
+ %% where LSP clients can trigger a completion request
+ %% before a did_open
+ Text0;
+ _ ->
+ els_buffer_server:flush(Buffer)
+ end,
Context = maps:get( <<"context">>
, Params
, #{ <<"triggerKind">> => ?COMPLETION_TRIGGER_KIND_INVOKED }
diff --git a/apps/els_lsp/src/els_dt_document.erl b/apps/els_lsp/src/els_dt_document.erl
index 6cc4e0376..1c37386ca 100644
--- a/apps/els_lsp/src/els_dt_document.erl
+++ b/apps/els_lsp/src/els_dt_document.erl
@@ -22,7 +22,7 @@
, delete/1
]).
--export([ new/2
+-export([ new/3
, pois/1
, pois/2
, get_element_at_pos/3
@@ -31,29 +31,37 @@
, applications_at_pos/3
, wrapping_functions/2
, wrapping_functions/3
+ , find_candidates/1
]).
%%==============================================================================
%% Includes
%%==============================================================================
-include("els_lsp.hrl").
+-include_lib("kernel/include/logger.hrl").
%%==============================================================================
%% Type Definitions
%%==============================================================================
-type id() :: atom().
-type kind() :: module | header | other.
+-type source() :: otp | app | dep.
+-type buffer() :: pid().
+-export_type([source/0]).
%%==============================================================================
%% Item Definition
%%==============================================================================
--record(els_dt_document, { uri :: uri() | '_'
+-record(els_dt_document, { uri :: uri() | '_' | '$1'
, id :: id() | '_'
, kind :: kind() | '_'
, text :: binary() | '_'
, md5 :: binary() | '_'
- , pois :: [poi()] | '_'
+ , pois :: [poi()] | '_' | ondemand
+ , source :: source() | '$2'
+ , buffer :: buffer() | '_' | undefined
+ , words :: sets:set() | '_' | '$3'
}).
-type els_dt_document() :: #els_dt_document{}.
@@ -62,7 +70,10 @@
, kind := kind()
, text := binary()
, md5 => binary()
- , pois => [poi()]
+ , pois => [poi()] | ondemand
+ , source => source()
+ , buffer => buffer() | undefined
+ , words => sets:set()
}.
-export_type([ id/0
, item/0
@@ -91,6 +102,9 @@ from_item(#{ uri := Uri
, text := Text
, md5 := MD5
, pois := POIs
+ , source := Source
+ , buffer := Buffer
+ , words := Words
}) ->
#els_dt_document{ uri = Uri
, id = Id
@@ -98,6 +112,9 @@ from_item(#{ uri := Uri
, text = Text
, md5 = MD5
, pois = POIs
+ , source = Source
+ , buffer = Buffer
+ , words = Words
}.
-spec to_item(els_dt_document()) -> item().
@@ -107,6 +124,9 @@ to_item(#els_dt_document{ uri = Uri
, text = Text
, md5 = MD5
, pois = POIs
+ , source = Source
+ , buffer = Buffer
+ , words = Words
}) ->
#{ uri => Uri
, id => Id
@@ -114,6 +134,9 @@ to_item(#els_dt_document{ uri = Uri
, text => Text
, md5 => MD5
, pois => POIs
+ , source => Source
+ , buffer => Buffer
+ , words => Words
}.
-spec insert(item()) -> ok | {error, any()}.
@@ -130,33 +153,39 @@ lookup(Uri) ->
delete(Uri) ->
els_db:delete(name(), Uri).
--spec new(uri(), binary()) -> item().
-new(Uri, Text) ->
+-spec new(uri(), binary(), source()) -> item().
+new(Uri, Text, Source) ->
Extension = filename:extension(Uri),
Id = binary_to_atom(filename:basename(Uri, Extension), utf8),
case Extension of
<<".erl">> ->
- new(Uri, Text, Id, module);
+ new(Uri, Text, Id, module, Source);
<<".hrl">> ->
- new(Uri, Text, Id, header);
+ new(Uri, Text, Id, header, Source);
_ ->
- new(Uri, Text, Id, other)
+ new(Uri, Text, Id, other, Source)
end.
--spec new(uri(), binary(), atom(), kind()) -> item().
-new(Uri, Text, Id, Kind) ->
- {ok, POIs} = els_parser:parse(Text),
- MD5 = erlang:md5(Text),
+-spec new(uri(), binary(), atom(), kind(), source()) -> item().
+new(Uri, Text, Id, Kind, Source) ->
+ MD5 = erlang:md5(Text),
#{ uri => Uri
, id => Id
, kind => Kind
, text => Text
, md5 => MD5
- , pois => POIs
+ , pois => ondemand
+ , source => Source
+ , buffer => undefined
+ , words => get_words(Text)
}.
%% @doc Returns the list of POIs for the current document
-spec pois(item()) -> [poi()].
+pois(#{ uri := Uri, pois := ondemand }) ->
+ els_indexing:ensure_deeply_indexed(Uri),
+ {ok, #{pois := POIs}} = els_utils:lookup_document(Uri),
+ POIs;
pois(#{ pois := POIs }) ->
POIs.
@@ -201,3 +230,49 @@ wrapping_functions(Document, Line, Column) ->
wrapping_functions(Document, Range) ->
#{start := #{character := Character, line := Line}} = Range,
wrapping_functions(Document, Line, Character).
+
+-spec find_candidates(atom() | string()) -> [uri()].
+find_candidates(Pattern) ->
+ %% ets:fun2ms(fun(#els_dt_document{source = Source, uri = Uri, words = Words})
+ %% when Source =/= otp -> {Uri, Words} end).
+ MS = [{#els_dt_document{ uri = '$1'
+ , id = '_'
+ , kind = '_'
+ , text = '_'
+ , md5 = '_'
+ , pois = '_'
+ , source = '$2'
+ , buffer = '_'
+ , words = '$3'},
+ [{'=/=', '$2', otp}],
+ [{{'$1', '$3'}}]}],
+ All = ets:select(name(), MS),
+ Fun = fun({Uri, Words}) ->
+ case sets:is_element(Pattern, Words) of
+ true -> {true, Uri};
+ false -> false
+ end
+ end,
+ lists:filtermap(Fun, All).
+
+-spec get_words(binary()) -> sets:set().
+get_words(Text) ->
+ case erl_scan:string(els_utils:to_list(Text)) of
+ {ok, Tokens, _EndLocation} ->
+ Fun = fun({atom, _Location, Atom}, Words) ->
+ sets:add_element(Atom, Words);
+ ({string, _Location, String}, Words) ->
+ case filename:extension(String) of
+ ".hrl" ->
+ Id = filename:rootname(filename:basename(String)),
+ sets:add_element(Id, Words);
+ _ ->
+ Words
+ end;
+ (_, Words) ->
+ Words
+ end,
+ lists:foldl(Fun, sets:new(), Tokens);
+ {error, ErrorInfo, _ErrorLocation} ->
+ ?LOG_DEBUG("Errors while get_words ~p", [ErrorInfo])
+ end.
diff --git a/apps/els_lsp/src/els_dt_references.erl b/apps/els_lsp/src/els_dt_references.erl
index 0926f1790..52bd98882 100644
--- a/apps/els_lsp/src/els_dt_references.erl
+++ b/apps/els_lsp/src/els_dt_references.erl
@@ -18,7 +18,6 @@
%%==============================================================================
-export([ delete_by_uri/1
- , find_all/0
, find_by/1
, find_by_id/2
, insert/2
@@ -45,6 +44,15 @@
}.
-export_type([ item/0 ]).
+-type poi_category() :: function
+ | type
+ | macro
+ | record
+ | include
+ | include_lib
+ | behaviour.
+-export_type([ poi_category/0 ]).
+
%%==============================================================================
%% Callbacks for the els_db_table Behaviour
%%==============================================================================
@@ -82,12 +90,6 @@ insert(Kind, Map) when is_map(Map) ->
Record = from_item(Kind, Map),
els_db:write(name(), Record).
-%% @doc Find all
--spec find_all() -> {ok, [item()]} | {error, any()}.
-find_all() ->
- Pattern = #els_dt_references{_ = '_'},
- find_by(Pattern).
-
%% @doc Find by id
-spec find_by_id(poi_kind(), any()) -> {ok, [item()]} | {error, any()}.
find_by_id(Kind, Id) ->
@@ -96,11 +98,13 @@ find_by_id(Kind, Id) ->
find_by(Pattern).
-spec find_by(tuple()) -> {ok, [item()]}.
-find_by(Pattern) ->
+find_by(#els_dt_references{id = Id} = Pattern) ->
+ Uris = els_text_search:find_candidate_uris(Id),
+ [els_indexing:ensure_deeply_indexed(Uri) || Uri <- Uris],
{ok, Items} = els_db:match(name(), Pattern),
{ok, [to_item(Item) || Item <- Items]}.
--spec kind_to_category(poi_kind()) -> function | type | macro | record.
+-spec kind_to_category(poi_kind()) -> poi_category().
kind_to_category(Kind) when Kind =:= application;
Kind =:= export_entry;
Kind =:= function;
diff --git a/apps/els_lsp/src/els_dt_signatures.erl b/apps/els_lsp/src/els_dt_signatures.erl
index f2a2b8694..1a5b20318 100644
--- a/apps/els_lsp/src/els_dt_signatures.erl
+++ b/apps/els_lsp/src/els_dt_signatures.erl
@@ -19,13 +19,14 @@
-export([ insert/1
, lookup/1
- , delete_by_module/1
+ , delete_by_uri/1
]).
%%==============================================================================
%% Includes
%%==============================================================================
-include("els_lsp.hrl").
+-include_lib("kernel/include/logger.hrl").
%%==============================================================================
%% Item Definition
@@ -72,11 +73,18 @@ insert(Map) when is_map(Map) ->
els_db:write(name(), Record).
-spec lookup(mfa()) -> {ok, [item()]}.
-lookup(MFA) ->
+lookup({M, _F, _A} = MFA) ->
+ {ok, _Uris} = els_utils:find_modules(M),
{ok, Items} = els_db:lookup(name(), MFA),
{ok, [to_item(Item) || Item <- Items]}.
--spec delete_by_module(atom()) -> ok.
-delete_by_module(Module) ->
- Pattern = #els_dt_signatures{mfa = {Module, '_', '_'}, _ = '_'},
- ok = els_db:match_delete(name(), Pattern).
+-spec delete_by_uri(uri()) -> ok.
+delete_by_uri(Uri) ->
+ case filename:extension(Uri) of
+ <<".erl">> ->
+ Module = els_uri:module(Uri),
+ Pattern = #els_dt_signatures{mfa = {Module, '_', '_'}, _ = '_'},
+ ok = els_db:match_delete(name(), Pattern);
+ _ ->
+ ok
+ end.
diff --git a/apps/els_lsp/src/els_index_buffer.erl b/apps/els_lsp/src/els_index_buffer.erl
deleted file mode 100644
index 7687cb12d..000000000
--- a/apps/els_lsp/src/els_index_buffer.erl
+++ /dev/null
@@ -1,140 +0,0 @@
-%% @doc Buffer textedits to avoid spamming indexing for every keystroke
-%%
-%% FIXME: Currently implemented as a simple process.
-%% Might be nicer to rewrite this as a gen_server
-%% FIXME: Edits are bottlenecked by this single process, so this could slow
-%% down indexing when making changes in multiple files, this could be
-%% mitigated by using a worker pool or spawning a process per Uri.
--module(els_index_buffer).
-
--export([ start/0
- , stop/0
- , apply_edits_async/2
- , flush/1
- , load/2
- ]).
-
--include_lib("kernel/include/logger.hrl").
--include("els_lsp.hrl").
-
--define(SERVER, ?MODULE).
-
--spec start() -> {ok, pid()} | {error, _}.
-start() ->
- case whereis(?SERVER) of
- undefined ->
- Pid = proc_lib:spawn_link(fun loop/0),
- true = erlang:register(?SERVER, Pid),
- ?LOG_INFO("[~p] Started.", [?SERVER]),
- {ok, Pid};
- Pid ->
- {error, {already_started, Pid}}
- end.
-
--spec stop() -> ok.
-stop() ->
- ?SERVER ! stop,
- erlang:unregister(?SERVER),
- ?LOG_INFO("[~p] Stopped.", [?SERVER]),
- ok.
-
--spec apply_edits_async(uri(), [els_text:edit()]) -> ok.
-apply_edits_async(Uri, Edits) ->
- ?SERVER ! {apply_edits, Uri, Edits},
- ok.
-
--spec load(uri(), binary()) -> ok.
-load(Uri, Text) ->
- Ref = make_ref(),
- ?SERVER ! {load, self(), Ref, Uri, Text},
- receive
- {Ref, done} ->
- ok
- end.
-
--spec flush(uri()) -> ok.
-flush(Uri) ->
- Ref = make_ref(),
- ?SERVER ! {flush, self(), Ref, Uri},
- receive
- {Ref, done} ->
- ok
- end.
-
--spec loop() -> ok.
-loop() ->
- receive
- stop -> ok;
- {apply_edits, Uri, Edits} ->
- try
- do_apply_edits(Uri, Edits)
- catch E:R:St ->
- ?LOG_ERROR("[~p] Crashed while applying edits ~p: ~p",
- [?SERVER, Uri, {E, R, St}])
- end,
- loop();
- {flush, Pid, Ref, Uri} ->
- try
- do_flush(Uri)
- catch E:R:St ->
- ?LOG_ERROR("[~p] Crashed while flushing ~p: ~p",
- [?SERVER, Uri, {E, R, St}])
- end,
- Pid ! {Ref, done},
- loop();
- {load, Pid, Ref, Uri, Text} ->
- try
- do_load(Uri, Text)
- catch E:R:St ->
- ?LOG_ERROR("[~p] Crashed while loading ~p: ~p",
- [?SERVER, Uri, {E, R, St}])
- end,
- Pid ! {Ref, done},
- loop()
- end.
-
--spec do_apply_edits(uri(), [els_text:edit()]) -> ok.
-do_apply_edits(Uri, Edits0) ->
- ?LOG_DEBUG("[~p] Processing index request for ~p", [?SERVER, Uri]),
- {ok, [#{text := Text0}]} = els_dt_document:lookup(Uri),
- ?LOG_DEBUG("[~p] Apply edits: ~p", [?SERVER, Edits0]),
- Text1 = els_text:apply_edits(Text0, Edits0),
- Text = receive_all(Uri, Text1),
- ?LOG_DEBUG("[~p] Started indexing ~p", [?SERVER, Uri]),
- {Duration, ok} = timer:tc(fun() -> els_indexing:index(Uri, Text, 'deep') end),
- ?LOG_DEBUG("[~p] Done indexing ~p [duration: ~pms]",
- [?SERVER, Uri, Duration div 1000]),
- ok.
-
--spec do_flush(uri()) -> ok.
-do_flush(Uri) ->
- ?LOG_DEBUG("[~p] Flushing ~p", [?SERVER, Uri]),
- {ok, [#{text := Text0}]} = els_dt_document:lookup(Uri),
- Text = receive_all(Uri, Text0),
- {Duration, ok} = timer:tc(fun() -> els_indexing:index(Uri, Text, 'deep') end),
- ?LOG_DEBUG("[~p] Done flushing ~p [duration: ~pms]",
- [?SERVER, Uri, Duration div 1000]),
- ok.
-
--spec do_load(uri(), binary()) -> ok.
-do_load(Uri, Text) ->
- ?LOG_DEBUG("[~p] Loading ~p", [?SERVER, Uri]),
- {Duration, ok} =
- timer:tc(fun() ->
- els_indexing:index(Uri, Text, 'deep')
- end),
- ?LOG_DEBUG("[~p] Done load ~p [duration: ~pms]",
- [?SERVER, Uri, Duration div 1000]),
- ok.
-
--spec receive_all(uri(), binary()) -> binary().
-receive_all(Uri, Text0) ->
- receive
- {apply_edits, Uri, Edits} ->
- ?LOG_DEBUG("[~p] Received more edits ~p.", [?SERVER, Uri]),
- ?LOG_DEBUG("[~p] Apply edits: ~p", [?SERVER, Edits]),
- Text = els_text:apply_edits(Text0, Edits),
- receive_all(Uri, Text)
- after
- 0 -> Text0
- end.
diff --git a/apps/els_lsp/src/els_indexing.erl b/apps/els_lsp/src/els_indexing.erl
index f103698d6..dd300ef8a 100644
--- a/apps/els_lsp/src/els_indexing.erl
+++ b/apps/els_lsp/src/els_indexing.erl
@@ -3,12 +3,14 @@
-callback index(els_dt_document:item()) -> ok.
%% API
--export([ find_and_index_file/1
- , index_file/1
- , index/3
+-export([ find_and_deeply_index_file/1
, index_dir/2
, start/0
, maybe_start/0
+ , ensure_deeply_indexed/1
+ , shallow_index/2
+ , deep_index/1
+ , remove/1
]).
%%==============================================================================
@@ -20,48 +22,30 @@
%%==============================================================================
%% Types
%%==============================================================================
--type mode() :: 'deep' | 'shallow'.
%%==============================================================================
%% Exported functions
%%==============================================================================
--spec find_and_index_file(string()) ->
+-spec find_and_deeply_index_file(string()) ->
{ok, uri()} | {error, any()}.
-find_and_index_file(FileName) ->
+find_and_deeply_index_file(FileName) ->
SearchPaths = els_config:get(search_paths),
case file:path_open( SearchPaths
, els_utils:to_binary(FileName)
, [read]
)
of
- {ok, IoDevice, FullName} ->
+ {ok, IoDevice, Path} ->
%% TODO: Avoid opening file twice
file:close(IoDevice),
- index_file(FullName);
+ Uri = els_uri:uri(Path),
+ ensure_deeply_indexed(Uri),
+ {ok, Uri};
{error, Error} ->
{error, Error}
end.
--spec index_file(binary()) -> {ok, uri()}.
-index_file(Path) ->
- GeneratedFilesTag = els_config_indexing:get_generated_files_tag(),
- try_index_file(Path, 'deep', false, GeneratedFilesTag),
- {ok, els_uri:uri(Path)}.
-
--spec index_if_not_generated(uri(), binary(), mode(), boolean(), string()) ->
- ok | skipped.
-index_if_not_generated(Uri, Text, Mode, false, _GeneratedFilesTag) ->
- index(Uri, Text, Mode);
-index_if_not_generated(Uri, Text, Mode, true, GeneratedFilesTag) ->
- case is_generated_file(Text, GeneratedFilesTag) of
- true ->
- ?LOG_DEBUG("Skip indexing for generated file ~p", [Uri]),
- skipped;
- false ->
- ok = index(Uri, Text, Mode)
- end.
-
-spec is_generated_file(binary(), string()) -> boolean().
is_generated_file(Text, Tag) ->
[Line|_] = string:split(Text, "\n", leading),
@@ -72,66 +56,84 @@ is_generated_file(Text, Tag) ->
false
end.
--spec index(uri(), binary(), mode()) -> ok.
-index(Uri, Text, Mode) ->
- MD5 = erlang:md5(Text),
- case els_dt_document:lookup(Uri) of
- {ok, [#{md5 := MD5}]} ->
- ok;
- {ok, LookupResult} ->
- Document = els_dt_document:new(Uri, Text),
- do_index(Document, Mode, LookupResult =/= [])
+-spec ensure_deeply_indexed(uri()) -> ok.
+ensure_deeply_indexed(Uri) ->
+ {ok, #{pois := POIs} = Document} = els_utils:lookup_document(Uri),
+ case POIs of
+ ondemand ->
+ deep_index(Document);
+ _ ->
+ ok
end.
--spec do_index(els_dt_document:item(), mode(), boolean()) -> ok.
-do_index(#{uri := Uri, id := Id, kind := Kind} = Document, Mode, Reset) ->
- case Mode of
- 'deep' ->
- ok = els_dt_document:insert(Document);
- 'shallow' ->
- %% Don't store detailed POIs when "shallow" indexing.
- %% They will be reloaded and inserted when needed
- %% by calling els_utils:lookup_document/1
- ok
- end,
- %% Mapping from document id to uri
- ModuleItem = els_dt_document_index:new(Id, Uri, Kind),
- ok = els_dt_document_index:insert(ModuleItem),
- index_signatures(Document),
- index_references(Document, Mode, Reset).
+-spec deep_index(els_dt_document:item()) -> ok.
+deep_index(Document) ->
+ #{id := Id, uri := Uri, text := Text, source := Source} = Document,
+ {ok, POIs} = els_parser:parse(Text),
+ ok = els_dt_document:insert(Document#{pois => POIs}),
+ index_signatures(Id, Uri, Text, POIs),
+ case Source of
+ otp ->
+ ok;
+ S when S =:= app orelse S =:= dep ->
+ index_references(Id, Uri, POIs)
+ end.
--spec index_signatures(els_dt_document:item()) -> ok.
-index_signatures(#{id := Id, text := Text} = Document) ->
- Specs = els_dt_document:pois(Document, [spec]),
- [ begin
- #{from := From, to := To} = Range,
- Spec = els_text:range(Text, From, To),
- els_dt_signatures:insert(#{ mfa => {Id, F, A} , spec => Spec})
- end
- || #{id := {F, A}, range := Range} <- Specs
- ],
+-spec index_signatures(atom(), uri(), binary(), [poi()]) -> ok.
+index_signatures(Id, Uri, Text, POIs) ->
+ ok = els_dt_signatures:delete_by_uri(Uri),
+ [index_signature(Id, Text, POI) || #{kind := spec} = POI <- POIs],
ok.
--spec index_references(els_dt_document:item(), mode(), boolean()) -> ok.
-index_references(#{uri := Uri} = Document, 'deep', true) ->
- %% Optimization to only do (non-optimized) match_delete when necessary
- ok = els_dt_references:delete_by_uri(Uri),
- index_references(Document, 'deep', false);
-index_references(#{uri := Uri} = Document, 'deep', false) ->
- %% References
- POIs = els_dt_document:pois(Document, [ application
- , behaviour
- , implicit_fun
- , include
- , include_lib
- , type_application
- , import_entry
- ]),
- [register_reference(Uri, POI) || POI <- POIs],
+-spec index_signature(atom(), binary(), poi()) -> ok.
+index_signature(_M, _Text, #{id := undefined}) ->
ok;
-index_references(_Document, 'shallow', _) ->
+index_signature(M, Text, #{id := {F, A}, range := Range}) ->
+ #{from := From, to := To} = Range,
+ Spec = els_text:range(Text, From, To),
+ els_dt_signatures:insert(#{ mfa => {M, F, A}, spec => Spec}).
+
+-spec index_references(atom(), uri(), [poi()]) -> ok.
+index_references(Id, Uri, POIs) ->
+ ok = els_dt_references:delete_by_uri(Uri),
+ ReferenceKinds = [ %% Function
+ application
+ , implicit_fun
+ , import_entry
+ %% Include
+ , include
+ , include_lib
+ %% Behaviour
+ , behaviour
+ %% Type
+ , type_application
+ ],
+ [index_reference(Id, Uri, POI)
+ || #{kind := Kind} = POI <- POIs,
+ lists:member(Kind, ReferenceKinds)],
ok.
+-spec index_reference(atom(), uri(), poi()) -> ok.
+index_reference(M, Uri, #{id := {F, A}} = POI) ->
+ index_reference(M, Uri, POI#{id => {M, F, A}});
+index_reference(_M, Uri, #{kind := Kind, id := Id, range := Range}) ->
+ els_dt_references:insert(Kind, #{id => Id, uri => Uri, range => Range}).
+
+-spec shallow_index(binary(), els_dt_document:source()) -> {ok, uri()}.
+shallow_index(Path, Source) ->
+ Uri = els_uri:uri(Path),
+ {ok, Text} = file:read_file(Path),
+ shallow_index(Uri, Text, Source),
+ {ok, Uri}.
+
+-spec shallow_index(uri(), binary(), els_dt_document:source()) -> ok.
+shallow_index(Uri, Text, Source) ->
+ Document = els_dt_document:new(Uri, Text, Source),
+ ok = els_dt_document:insert(Document),
+ #{id := Id, kind := Kind} = Document,
+ ModuleItem = els_dt_document_index:new(Id, Uri, Kind),
+ ok = els_dt_document_index:insert(ModuleItem).
+
-spec maybe_start() -> true | false.
maybe_start() ->
IndexingEnabled = els_config:get(indexing_enabled),
@@ -145,17 +147,18 @@ maybe_start() ->
-spec start() -> ok.
start() ->
- start(<<"OTP">>, entries_otp()),
- start(<<"Applications">>, entries_apps()),
- start(<<"Dependencies">>, entries_deps()).
-
--spec start(binary(), [{string(), 'deep' | 'shallow'}]) -> ok.
-start(Group, Entries) ->
- SkipGeneratedFiles = els_config_indexing:get_skip_generated_files(),
- GeneratedFilesTag = els_config_indexing:get_generated_files_tag(),
- Task = fun({Dir, Mode}, {Succeeded0, Skipped0, Failed0}) ->
- {Su, Sk, Fa} = index_dir(Dir, Mode,
- SkipGeneratedFiles, GeneratedFilesTag),
+ Skip = els_config_indexing:get_skip_generated_files(),
+ SkipTag = els_config_indexing:get_generated_files_tag(),
+ ?LOG_INFO("Start indexing. [skip=~p] [skip_tag=~p]", [Skip, SkipTag]),
+ start(<<"OTP">>, Skip, SkipTag, els_config:get(otp_paths), otp),
+ start(<<"Applications">>, Skip, SkipTag, els_config:get(apps_paths), app),
+ start(<<"Dependencies">>, Skip, SkipTag, els_config:get(deps_paths), dep).
+
+-spec start(binary(), boolean(), string(), [string()],
+ els_dt_document:source()) -> ok.
+start(Group, Skip, SkipTag, Entries, Source) ->
+ Task = fun(Dir, {Succeeded0, Skipped0, Failed0}) ->
+ {Su, Sk, Fa} = index_dir(Dir, Skip, SkipTag, Source),
{Succeeded0 + Su, Skipped0 + Sk, Failed0 + Fa}
end,
Config = #{ task => Task
@@ -172,65 +175,48 @@ start(Group, Entries) ->
{ok, _Pid} = els_background_job:new(Config),
ok.
+-spec remove(uri()) -> ok.
+remove(Uri) ->
+ ok = els_dt_document:delete(Uri),
+ ok = els_dt_document_index:delete_by_uri(Uri),
+ ok = els_dt_references:delete_by_uri(Uri),
+ ok = els_dt_signatures:delete_by_uri(Uri).
+
%%==============================================================================
%% Internal functions
%%==============================================================================
-
-%% @doc Try indexing a file.
--spec try_index_file(binary(), mode(), boolean(), string()) ->
- ok | skipped | {error, any()}.
-try_index_file(FullName, Mode, SkipGeneratedFiles, GeneratedFilesTag) ->
+-spec shallow_index(binary(), boolean(), string(), els_dt_document:source()) ->
+ ok | skipped.
+shallow_index(FullName, SkipGeneratedFiles, GeneratedFilesTag, Source) ->
Uri = els_uri:uri(FullName),
- try
- ?LOG_DEBUG("Indexing file. [filename=~s, uri=~s]", [FullName, Uri]),
- {ok, Text} = file:read_file(FullName),
- index_if_not_generated(Uri, Text, Mode,
- SkipGeneratedFiles, GeneratedFilesTag)
- catch Type:Reason:St ->
- ?LOG_ERROR("Error indexing file "
- "[filename=~s, uri=~s] "
- "~p:~p:~p", [FullName, Uri, Type, Reason, St]),
- {error, {Type, Reason}}
+ ?LOG_DEBUG("Shallow indexing file. [filename=~s] [uri=~s]",
+ [FullName, Uri]),
+ {ok, Text} = file:read_file(FullName),
+ case SkipGeneratedFiles andalso is_generated_file(Text, GeneratedFilesTag) of
+ true ->
+ ?LOG_DEBUG("Skip indexing for generated file ~p", [Uri]),
+ skipped;
+ false ->
+ shallow_index(Uri, Text, Source)
end.
--spec register_reference(uri(), poi()) -> ok.
-register_reference(Uri, #{id := {F, A}} = POI) ->
- M = els_uri:module(Uri),
- register_reference(Uri, POI#{id => {M, F, A}});
-register_reference(Uri, #{kind := Kind, id := Id, range := Range})
- when %% Include
- Kind =:= include;
- Kind =:= include_lib;
- %% Function
- Kind =:= application;
- Kind =:= implicit_fun;
- Kind =:= import_entry;
- %% Type
- Kind =:= type_application;
- %% Behaviour
- Kind =:= behaviour ->
- els_dt_references:insert( Kind
- , #{id => Id, uri => Uri, range => Range}
- ).
-
--spec index_dir(string(), mode()) ->
+-spec index_dir(string(), els_dt_document:source()) ->
{non_neg_integer(), non_neg_integer(), non_neg_integer()}.
-index_dir(Dir, Mode) ->
- SkipGeneratedFiles = els_config_indexing:get_skip_generated_files(),
- GeneratedFilesTag = els_config_indexing:get_generated_files_tag(),
- index_dir(Dir, Mode, SkipGeneratedFiles, GeneratedFilesTag).
+index_dir(Dir, Source) ->
+ Skip = els_config_indexing:get_skip_generated_files(),
+ SkipTag = els_config_indexing:get_generated_files_tag(),
+ index_dir(Dir, Skip, SkipTag, Source).
--spec index_dir(string(), mode(), boolean(), string()) ->
+-spec index_dir(string(), boolean(), string(), els_dt_document:source()) ->
{non_neg_integer(), non_neg_integer(), non_neg_integer()}.
-index_dir(Dir, Mode, SkipGeneratedFiles, GeneratedFilesTag) ->
- ?LOG_DEBUG("Indexing directory. [dir=~s] [mode=~s]", [Dir, Mode]),
+index_dir(Dir, Skip, SkipTag, Source) ->
+ ?LOG_DEBUG("Indexing directory. [dir=~s]", [Dir]),
F = fun(FileName, {Succeeded, Skipped, Failed}) ->
- case try_index_file(els_utils:to_binary(FileName), Mode,
- SkipGeneratedFiles, GeneratedFilesTag) of
+ BinaryName = els_utils:to_binary(FileName),
+ case shallow_index(BinaryName, Skip, SkipTag, Source) of
ok -> {Succeeded + 1, Skipped, Failed};
- skipped -> {Succeeded, Skipped + 1, Failed};
- {error, _Error} -> {Succeeded, Skipped, Failed + 1}
+ skipped -> {Succeeded, Skipped + 1, Failed}
end
end,
Filter = fun(Path) ->
@@ -246,19 +232,7 @@ index_dir(Dir, Mode, SkipGeneratedFiles, GeneratedFilesTag) ->
, {0, 0, 0}
]
),
- ?LOG_DEBUG("Finished indexing directory. [dir=~s] [mode=~s] [time=~p] "
+ ?LOG_DEBUG("Finished indexing directory. [dir=~s] [time=~p] "
"[succeeded=~p] [skipped=~p] [failed=~p]",
- [Dir, Mode, Time/1000/1000, Succeeded, Skipped, Failed]),
+ [Dir, Time/1000/1000, Succeeded, Skipped, Failed]),
{Succeeded, Skipped, Failed}.
-
--spec entries_apps() -> [{string(), 'deep' | 'shallow'}].
-entries_apps() ->
- [{Dir, 'deep'} || Dir <- els_config:get(apps_paths)].
-
--spec entries_deps() -> [{string(), 'deep' | 'shallow'}].
-entries_deps() ->
- [{Dir, 'deep'} || Dir <- els_config:get(deps_paths)].
-
--spec entries_otp() -> [{string(), 'deep' | 'shallow'}].
-entries_otp() ->
- [{Dir, 'shallow'} || Dir <- els_config:get(otp_paths)].
diff --git a/apps/els_lsp/src/els_sup.erl b/apps/els_lsp/src/els_sup.erl
index 1a6d9efda..8fbe5bec2 100644
--- a/apps/els_lsp/src/els_sup.erl
+++ b/apps/els_lsp/src/els_sup.erl
@@ -67,14 +67,14 @@ init([]) ->
, start => {els_distribution_sup, start_link, []}
, type => supervisor
}
- , #{ id => els_snippets_server
- , start => {els_snippets_server, start_link, []}
+ , #{ id => els_snippets_server
+ , start => {els_snippets_server, start_link, []}
}
- , #{ id => els_bsp_client
+ , #{ id => els_bsp_client
, start => {els_bsp_client, start_link, []}
}
- , #{ id => els_index_buffer
- , start => {els_index_buffer, start, []}
+ , #{ id => els_buffer_sup
+ , start => {els_buffer_sup, start_link, []}
}
, #{ id => els_server
, start => {els_server, start_link, []}
diff --git a/apps/els_lsp/src/els_text_search.erl b/apps/els_lsp/src/els_text_search.erl
new file mode 100644
index 000000000..c55bdd37f
--- /dev/null
+++ b/apps/els_lsp/src/els_text_search.erl
@@ -0,0 +1,46 @@
+%%==============================================================================
+%% Text-based search
+%%==============================================================================
+-module(els_text_search).
+
+%%==============================================================================
+%% API
+%%==============================================================================
+-export([ find_candidate_uris/1 ]).
+
+%%==============================================================================
+%% Includes
+%%==============================================================================
+-include("els_lsp.hrl").
+
+%%==============================================================================
+%% API
+%%==============================================================================
+-spec find_candidate_uris({els_dt_references:poi_category(), any()}) -> [uri()].
+find_candidate_uris(Id) ->
+ Pattern = extract_pattern(Id),
+ els_dt_document:find_candidates(Pattern).
+
+%%==============================================================================
+%% Internal Functions
+%%==============================================================================
+-spec extract_pattern({els_dt_references:poi_category(), any()}) ->
+ atom() | binary().
+extract_pattern({function, {_M, F, _A}}) ->
+ F;
+extract_pattern({type, {_M, F, _A}}) ->
+ F;
+extract_pattern({macro, {Name, _Arity}}) ->
+ Name;
+extract_pattern({macro, Name}) ->
+ Name;
+extract_pattern({include, Id}) ->
+ include_id(Id);
+extract_pattern({include_lib, Id}) ->
+ include_id(Id);
+extract_pattern({behaviour, Name}) ->
+ Name.
+
+-spec include_id(string()) -> string().
+include_id(Id) ->
+ filename:rootname(filename:basename(Id)).
diff --git a/apps/els_lsp/src/els_text_synchronization.erl b/apps/els_lsp/src/els_text_synchronization.erl
index abca45edf..d7a2aea86 100644
--- a/apps/els_lsp/src/els_text_synchronization.erl
+++ b/apps/els_lsp/src/els_text_synchronization.erl
@@ -30,7 +30,10 @@ did_change(Params) ->
%% Full text sync
#{<<"text">> := Text} = Change,
{Duration, ok} =
- timer:tc(fun() -> els_indexing:index(Uri, Text, 'deep') end),
+ timer:tc(fun() ->
+ {ok, Document} = els_utils:lookup_document(Uri),
+ els_indexing:deep_index(Document)
+ end),
?LOG_DEBUG("didChange FULLSYNC [size: ~p] [duration: ~pms]\n",
[size(Text), Duration div 1000]),
ok;
@@ -39,7 +42,10 @@ did_change(Params) ->
?LOG_DEBUG("didChange INCREMENTAL [changes: ~p]", [ContentChanges]),
Edits = [to_edit(Change) || Change <- ContentChanges],
{Duration, ok} =
- timer:tc(fun() -> els_index_buffer:apply_edits_async(Uri, Edits) end),
+ timer:tc(fun() ->
+ {ok, #{buffer := Buffer}} = els_utils:lookup_document(Uri),
+ els_buffer_server:apply_edits(Buffer, Edits)
+ end),
?LOG_DEBUG("didChange INCREMENTAL [duration: ~pms]\n",
[Duration div 1000]),
ok
@@ -49,8 +55,9 @@ did_change(Params) ->
did_open(Params) ->
#{<<"textDocument">> := #{ <<"uri">> := Uri
, <<"text">> := Text}} = Params,
- ok = els_index_buffer:load(Uri, Text),
- ok = els_index_buffer:flush(Uri),
+ {ok, Document} = els_utils:lookup_document(Uri),
+ {ok, Buffer} = els_buffer_server:new(Uri, Text),
+ els_dt_document:insert(Document#{buffer => Buffer}),
Provider = els_diagnostics_provider,
els_provider:handle_request(Provider, {run_diagnostics, Params}),
ok.
@@ -58,9 +65,7 @@ did_open(Params) ->
-spec did_save(map()) -> ok.
did_save(Params) ->
#{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
- {ok, Text} = file:read_file(els_uri:path(Uri)),
- ok = els_index_buffer:load(Uri, Text),
- ok = els_index_buffer:flush(Uri),
+ reload_from_disk(Uri),
Provider = els_diagnostics_provider,
els_provider:handle_request(Provider, {run_diagnostics, Params}),
ok.
@@ -87,11 +92,20 @@ to_edit(#{<<"text">> := Text, <<"range">> := Range}) ->
-spec handle_file_change(uri(), file_change_type()) -> ok.
handle_file_change(Uri, Type) when Type =:= ?FILE_CHANGE_TYPE_CREATED;
Type =:= ?FILE_CHANGE_TYPE_CHANGED ->
- {ok, Text} = file:read_file(els_uri:path(Uri)),
- ok = els_index_buffer:load(Uri, Text),
- ok = els_index_buffer:flush(Uri);
+ reload_from_disk(Uri);
handle_file_change(Uri, Type) when Type =:= ?FILE_CHANGE_TYPE_DELETED ->
- ok = els_dt_document:delete(Uri),
- ok = els_dt_document_index:delete_by_uri(Uri),
- ok = els_dt_references:delete_by_uri(Uri),
- ok = els_dt_signatures:delete_by_module(els_uri:module(Uri)).
+ els_indexing:remove(Uri).
+
+-spec reload_from_disk(uri()) -> ok.
+reload_from_disk(Uri) ->
+ {ok, Text} = file:read_file(els_uri:path(Uri)),
+ {ok, #{buffer := OldBuffer} = Document} = els_utils:lookup_document(Uri),
+ case OldBuffer of
+ undefined ->
+ els_indexing:deep_index(Document#{text => Text});
+ _ ->
+ els_buffer_server:stop(OldBuffer),
+ {ok, B} = els_buffer_server:new(Uri, Text),
+ els_indexing:deep_index(Document#{text => Text, buffer => B})
+ end,
+ ok.
diff --git a/apps/els_lsp/test/els_call_hierarchy_SUITE.erl b/apps/els_lsp/test/els_call_hierarchy_SUITE.erl
index 2242ff508..b5d5330d3 100644
--- a/apps/els_lsp/test/els_call_hierarchy_SUITE.erl
+++ b/apps/els_lsp/test/els_call_hierarchy_SUITE.erl
@@ -91,7 +91,36 @@ incoming_calls(Config) ->
, uri => UriA},
?assertEqual([Item], PrepareResult),
#{result := Result} = els_client:callhierarchy_incomingcalls(Item),
- Calls = [#{ from =>
+ Calls = [ #{ from =>
+ #{ data =>
+ els_utils:base64_encode_term(
+ #{ poi =>
+ #{ data =>
+ #{ args => [{1, "Arg1"}]
+ , wrapping_range =>
+ #{ from => {7, 1}
+ , to => {14, 0}}}
+ , id => {function_a, 1}
+ , kind => function
+ , range => #{from => {7, 1}, to => {7, 11}}}})
+ , detail => <<"call_hierarchy_b [L11]">>
+ , kind => 12
+ , name => <<"function_a/1">>
+ , range =>
+ #{ 'end' => #{character => 29, line => 10}
+ , start => #{character => 2, line => 10}
+ }
+ , selectionRange =>
+ #{ 'end' => #{character => 29, line => 10}
+ , start => #{character => 2, line => 10}
+ }
+ , uri => UriB}
+ , fromRanges =>
+ [#{ 'end' => #{character => 29, line => 10}
+ , start => #{character => 2, line => 10}
+ }]
+ }
+ , #{ from =>
#{ data =>
els_utils:base64_encode_term(
#{ poi =>
@@ -134,37 +163,9 @@ incoming_calls(Config) ->
[#{ 'end' => #{character => 12, line => 15}
, start => #{character => 2, line => 15}
}]}
- , #{ from =>
- #{ data =>
- els_utils:base64_encode_term(
- #{ poi =>
- #{ data =>
- #{ args => [{1, "Arg1"}]
- , wrapping_range =>
- #{ from => {7, 1}
- , to => {14, 0}}}
- , id => {function_a, 1}
- , kind => function
- , range => #{from => {7, 1}, to => {7, 11}}}})
- , detail => <<"call_hierarchy_b [L11]">>
- , kind => 12
- , name => <<"function_a/1">>
- , range =>
- #{ 'end' => #{character => 29, line => 10}
- , start => #{character => 2, line => 10}
- }
- , selectionRange =>
- #{ 'end' => #{character => 29, line => 10}
- , start => #{character => 2, line => 10}
- }
- , uri => UriB}
- , fromRanges =>
- [#{ 'end' => #{character => 29, line => 10}
- , start => #{character => 2, line => 10}
- }]
- }
],
- ?assertEqual(Calls, Result).
+ [?assert(lists:member(Call, Result)) || Call <- Calls],
+ ?assertEqual(length(Calls), length(Result)).
-spec outgoing_calls(config()) -> ok.
outgoing_calls(Config) ->
diff --git a/apps/els_lsp/test/els_hover_SUITE.erl b/apps/els_lsp/test/els_hover_SUITE.erl
index 312c87184..c05887d84 100644
--- a/apps/els_lsp/test/els_hover_SUITE.erl
+++ b/apps/els_lsp/test/els_hover_SUITE.erl
@@ -375,4 +375,4 @@ has_eep48(Module) ->
case catch code:get_doc(Module) of
{ok, _} -> true;
_ -> false
- end.
\ No newline at end of file
+ end.
diff --git a/apps/els_lsp/test/els_indexer_SUITE.erl b/apps/els_lsp/test/els_indexer_SUITE.erl
index ee0adccb9..dd97c4787 100644
--- a/apps/els_lsp/test/els_indexer_SUITE.erl
+++ b/apps/els_lsp/test/els_indexer_SUITE.erl
@@ -92,7 +92,7 @@ index_dir_not_dir(Config) ->
index_erl_file(Config) ->
DataDir = ?config(data_dir, Config),
Path = filename:join(els_utils:to_binary(DataDir), "test.erl"),
- {ok, Uri} = els_indexing:index_file(Path),
+ {ok, Uri} = els_indexing:shallow_index(Path, app),
{ok, [#{id := test, kind := module}]} = els_dt_document:lookup(Uri),
ok.
@@ -100,7 +100,7 @@ index_erl_file(Config) ->
index_hrl_file(Config) ->
DataDir = ?config(data_dir, Config),
Path = filename:join(els_utils:to_binary(DataDir), "test.hrl"),
- {ok, Uri} = els_indexing:index_file(Path),
+ {ok, Uri} = els_indexing:shallow_index(Path, app),
{ok, [#{id := test, kind := header}]} = els_dt_document:lookup(Uri),
ok.
@@ -108,7 +108,7 @@ index_hrl_file(Config) ->
index_unkown_extension(Config) ->
DataDir = ?config(data_dir, Config),
Path = filename:join(els_utils:to_binary(DataDir), "test.foo"),
- {ok, Uri} = els_indexing:index_file(Path),
+ {ok, Uri} = els_indexing:shallow_index(Path, app),
{ok, [#{kind := other}]} = els_dt_document:lookup(Uri),
ok.
@@ -117,7 +117,7 @@ do_not_skip_generated_file_by_tag_by_default(Config) ->
DataDir = data_dir(Config),
GeneratedByTagUri = uri(DataDir, "generated_file_by_tag.erl"),
GeneratedByCustomTagUri = uri(DataDir, "generated_file_by_custom_tag.erl"),
- ?assertEqual({4, 0, 0}, els_indexing:index_dir(DataDir, 'deep')),
+ ?assertEqual({4, 0, 0}, els_indexing:index_dir(DataDir, app)),
{ok, [#{ id := generated_file_by_tag
, kind := module
}
@@ -133,7 +133,7 @@ skip_generated_file_by_tag(Config) ->
DataDir = data_dir(Config),
GeneratedByTagUri = uri(DataDir, "generated_file_by_tag.erl"),
GeneratedByCustomTagUri = uri(DataDir, "generated_file_by_custom_tag.erl"),
- ?assertEqual({3, 1, 0}, els_indexing:index_dir(DataDir, 'deep')),
+ ?assertEqual({3, 1, 0}, els_indexing:index_dir(DataDir, app)),
{ok, []} = els_dt_document:lookup(GeneratedByTagUri),
{ok, [#{ id := generated_file_by_custom_tag
, kind := module
@@ -146,7 +146,7 @@ skip_generated_file_by_custom_tag(Config) ->
DataDir = data_dir(Config),
GeneratedByTagUri = uri(DataDir, "generated_file_by_tag.erl"),
GeneratedByCustomTagUri = uri(DataDir, "generated_file_by_custom_tag.erl"),
- ?assertEqual({3, 1, 0}, els_indexing:index_dir(DataDir, 'deep')),
+ ?assertEqual({3, 1, 0}, els_indexing:index_dir(DataDir, app)),
{ok, [#{ id := generated_file_by_tag
, kind := module
}
diff --git a/apps/els_lsp/test/els_indexing_SUITE.erl b/apps/els_lsp/test/els_indexing_SUITE.erl
index 212024754..40673f9d9 100644
--- a/apps/els_lsp/test/els_indexing_SUITE.erl
+++ b/apps/els_lsp/test/els_indexing_SUITE.erl
@@ -68,7 +68,7 @@ reindex_otp(_Config) ->
-spec do_index_otp() -> ok.
do_index_otp() ->
- [els_indexing:index_dir(Dir, 'shallow') || Dir <- els_config:get(otp_paths)],
+ [els_indexing:index_dir(Dir, otp) || Dir <- els_config:get(otp_paths)],
ok.
-spec otp_apps_exclude() -> [string()].
diff --git a/apps/els_lsp/test/els_rebar3_release_SUITE.erl b/apps/els_lsp/test/els_rebar3_release_SUITE.erl
index fce638d8d..4872e2f8c 100644
--- a/apps/els_lsp/test/els_rebar3_release_SUITE.erl
+++ b/apps/els_lsp/test/els_rebar3_release_SUITE.erl
@@ -65,8 +65,8 @@ init_per_testcase(_TestCase, Config) ->
els_client:initialize(RootUri),
{ok, AppText} = file:read_file(els_uri:path(AppUri)),
els_client:did_open(AppUri, erlang, 1, AppText),
- els_indexing:find_and_index_file("rebar3_release_app.erl"),
- els_indexing:find_and_index_file("rebar3_release_sup.erl"),
+ els_indexing:find_and_deeply_index_file("rebar3_release_app.erl"),
+ els_indexing:find_and_deeply_index_file("rebar3_release_sup.erl"),
[{started, Started}|Config].
-spec end_per_testcase(atom(), config()) -> ok.
diff --git a/apps/els_lsp/test/els_references_SUITE.erl b/apps/els_lsp/test/els_references_SUITE.erl
index eae89560c..0e5e72c09 100644
--- a/apps/els_lsp/test/els_references_SUITE.erl
+++ b/apps/els_lsp/test/els_references_SUITE.erl
@@ -27,7 +27,6 @@
, included_record_field/1
, undefined_record/1
, undefined_record_field/1
- , purge_references/1
, type_local/1
, type_remote/1
, type_included/1
@@ -69,7 +68,8 @@ end_per_suite(Config) ->
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(TestCase, Config0)
- when TestCase =:= refresh_after_watched_file_changed ->
+ when TestCase =:= refresh_after_watched_file_changed;
+ TestCase =:= refresh_after_watched_file_deleted ->
Config = els_test_utils:init_per_testcase(TestCase, Config0),
PathB = ?config(watched_file_b_path, Config),
{ok, OldContent} = file:read_file(PathB),
@@ -79,10 +79,17 @@ init_per_testcase(TestCase, Config) ->
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config)
- when TestCase =:= refresh_after_watched_file_changed ->
+ when TestCase =:= refresh_after_watched_file_changed;
+ TestCase =:= refresh_after_watched_file_deleted ->
PathB = ?config(watched_file_b_path, Config),
ok = file:write_file(PathB, ?config(old_content, Config)),
els_test_utils:end_per_testcase(TestCase, Config);
+end_per_testcase(TestCase, Config)
+ when TestCase =:= refresh_after_watched_file_added ->
+ PathB = ?config(watched_file_b_path, Config),
+ ok = file:delete(filename:join(filename:dirname(PathB),
+ "watched_file_c.erl")),
+ els_test_utils:end_per_testcase(TestCase, Config);
end_per_testcase(TestCase, Config) ->
els_test_utils:end_per_testcase(TestCase, Config).
@@ -413,34 +420,6 @@ undefined_record_field(Config) ->
assert_locations(Locations1, ExpectedLocations),
ok.
-%% Issue #245
--spec purge_references(config()) -> ok.
-purge_references(_Config) ->
- els_db:clear_tables(),
- Uri = <<"file:///tmp/foo.erl">>,
- Text0 = <<"-spec foo() -> ok.\nfoo(_X) -> ok.\nbar() -> foo().">>,
- Text1 = <<"\n-spec foo() -> ok.\nfoo(_X)-> ok.\nbar() -> foo().">>,
- Doc0 = els_dt_document:new(Uri, Text0),
- Doc1 = els_dt_document:new(Uri, Text1),
-
- ok = els_indexing:index(Uri, Text0, 'deep'),
- ?assertEqual({ok, [Doc0]}, els_dt_document:lookup(Uri)),
- ?assertEqual({ok, [#{ id => {foo, foo, 0}
- , range => #{from => {3, 10}, to => {3, 13}}
- , uri => <<"file:///tmp/foo.erl">>
- }]}
- , els_dt_references:find_all()
- ),
-
- ok = els_indexing:index(Uri, Text1, 'deep'),
- ?assertEqual({ok, [Doc1]}, els_dt_document:lookup(Uri)),
- ?assertEqual({ok, [#{ id => {foo, foo, 0}
- , range => #{from => {4, 10}, to => {4, 13}}
- , uri => <<"file:///tmp/foo.erl">>
- }]}
- , els_dt_references:find_all()
- ),
- ok.
-spec type_local(config()) -> ok.
type_local(Config) ->
@@ -507,6 +486,7 @@ refresh_after_watched_file_deleted(Config) ->
%% Before
UriA = ?config(watched_file_a_uri, Config),
UriB = ?config(watched_file_b_uri, Config),
+ PathB = ?config(watched_file_b_path, Config),
ExpectedLocationsBefore = [ #{ uri => UriB
, range => #{from => {6, 3}, to => {6, 22}}
}
@@ -514,6 +494,7 @@ refresh_after_watched_file_deleted(Config) ->
#{result := LocationsBefore} = els_client:references(UriA, 5, 2),
assert_locations(LocationsBefore, ExpectedLocationsBefore),
%% Delete (Simulate a checkout, rebase or similar)
+ ok = file:delete(PathB),
els_client:did_change_watched_files([{UriB, ?FILE_CHANGE_TYPE_DELETED}]),
%% After
#{result := null} = els_client:references(UriA, 5, 2),
@@ -554,6 +535,7 @@ refresh_after_watched_file_added(Config) ->
%% Before
UriA = ?config(watched_file_a_uri, Config),
UriB = ?config(watched_file_b_uri, Config),
+ PathB = ?config(watched_file_b_path, Config),
ExpectedLocationsBefore = [ #{ uri => UriB
, range => #{from => {6, 3}, to => {6, 22}}
}
@@ -563,10 +545,12 @@ refresh_after_watched_file_added(Config) ->
%% Add (Simulate a checkout, rebase or similar)
DataDir = ?config(data_dir, Config),
PathC = filename:join([DataDir, "watched_file_c.erl"]),
- UriC = els_uri:uri(els_utils:to_binary(PathC)),
- els_client:did_change_watched_files([{UriC, ?FILE_CHANGE_TYPE_CREATED}]),
+ NewPathC = filename:join(filename:dirname(PathB), "watched_file_c.erl"),
+ NewUriC = els_uri:uri(NewPathC),
+ {ok, _} = file:copy(PathC, NewPathC),
+ els_client:did_change_watched_files([{NewUriC, ?FILE_CHANGE_TYPE_CREATED}]),
%% After
- ExpectedLocationsAfter = [ #{ uri => UriC
+ ExpectedLocationsAfter = [ #{ uri => NewUriC
, range => #{from => {6, 3}, to => {6, 22}}
}
, #{ uri => UriB
diff --git a/apps/els_lsp/test/els_test_utils.erl b/apps/els_lsp/test/els_test_utils.erl
index 2d96aa3a1..a2fe3d11e 100644
--- a/apps/els_lsp/test/els_test_utils.erl
+++ b/apps/els_lsp/test/els_test_utils.erl
@@ -130,7 +130,8 @@ includes() ->
%% accessing this information from test cases.
-spec index_file(binary()) -> [{atom(), any()}].
index_file(Path) ->
- {ok, Uri} = els_indexing:index_file(Path),
+ Uri = els_uri:uri(Path),
+ ok = els_indexing:ensure_deeply_indexed(Uri),
{ok, Text} = file:read_file(Path),
ConfigId = config_id(Path),
[ {atoms_append(ConfigId, '_path'), Path}
From dc3a5e18b4a09febd614b6157a936bc153daf891 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Tue, 19 Apr 2022 10:40:19 +0200
Subject: [PATCH 050/239] Single Provider Process (#1264)
* Single Provider Process
Erlang LS historically used one process per provider. This was
problematic, since requests handled by different providers could lead
to race conditions, causing a server crash. For example, a completion
request could not see the latest version of the text in case of
incremental text synchronization.
There was very little gain in parallelizing providers, so we decided to
simplify the architecture and have a single process, instead. There's
still room for refactoring and cleanup, but those will be handled as a
follow up.
As part of the architectural change, we also dropped support for BSP
since it has been stalling for too long time. Instead, we will try to
implement a simpler solution which allows the extraction of paths from
the build system (e.g. via the rebar.conifg).
* Drop BSP Support
* Introduce text synchronization provider
* Run providers as a single process
* Remove buffer server, index in the background instead
* Decouple DAP provider from LSP one
* Brutally kill background jobs
* Set standard_io as default value for the io_device
---
apps/els_core/src/els_config.erl | 4 -
apps/els_core/src/els_provider.erl | 186 +++++++----
apps/els_core/src/els_stdio.erl | 2 +-
apps/els_dap/src/els_dap_general_provider.erl | 9 -
apps/els_dap/src/els_dap_methods.erl | 6 +-
apps/els_dap/src/els_dap_provider.erl | 66 ++--
apps/els_dap/src/els_dap_providers_sup.erl | 63 ----
apps/els_dap/src/els_dap_sup.erl | 5 +-
.../test/els_dap_general_provider_SUITE.erl | 136 ++++----
apps/els_lsp/src/els_background_job_sup.erl | 2 +-
apps/els_lsp/src/els_bsp_client.erl | 305 ------------------
apps/els_lsp/src/els_bsp_provider.erl | 262 ---------------
apps/els_lsp/src/els_buffer_server.erl | 126 --------
apps/els_lsp/src/els_buffer_sup.erl | 47 ---
.../src/els_call_hierarchy_provider.erl | 18 +-
apps/els_lsp/src/els_code_action_provider.erl | 11 +-
apps/els_lsp/src/els_code_lens_provider.erl | 41 +--
apps/els_lsp/src/els_completion_provider.erl | 30 +-
apps/els_lsp/src/els_definition_provider.erl | 15 +-
apps/els_lsp/src/els_diagnostics_provider.erl | 77 +----
.../src/els_document_highlight_provider.erl | 16 +-
.../src/els_document_symbol_provider.erl | 16 +-
apps/els_lsp/src/els_dt_document.erl | 9 -
.../src/els_execute_command_provider.erl | 11 +-
.../src/els_folding_range_provider.erl | 16 +-
apps/els_lsp/src/els_formatting_provider.erl | 73 ++---
apps/els_lsp/src/els_general_provider.erl | 61 +---
apps/els_lsp/src/els_hover_provider.erl | 39 +--
.../src/els_implementation_provider.erl | 15 +-
apps/els_lsp/src/els_methods.erl | 101 +++---
apps/els_lsp/src/els_providers_sup.erl | 59 ----
apps/els_lsp/src/els_references_provider.erl | 17 +-
apps/els_lsp/src/els_rename_provider.erl | 7 +-
apps/els_lsp/src/els_server.erl | 17 +-
apps/els_lsp/src/els_sup.erl | 11 +-
apps/els_lsp/src/els_text_synchronization.erl | 55 ++--
.../src/els_text_synchronization_provider.erl | 45 +++
.../src/els_workspace_symbol_provider.erl | 14 +-
apps/els_lsp/test/els_server_SUITE.erl | 5 +-
apps/els_lsp/test/prop_statem.erl | 4 +-
40 files changed, 497 insertions(+), 1505 deletions(-)
delete mode 100644 apps/els_dap/src/els_dap_providers_sup.erl
delete mode 100644 apps/els_lsp/src/els_bsp_client.erl
delete mode 100644 apps/els_lsp/src/els_bsp_provider.erl
delete mode 100644 apps/els_lsp/src/els_buffer_server.erl
delete mode 100644 apps/els_lsp/src/els_buffer_sup.erl
delete mode 100644 apps/els_lsp/src/els_providers_sup.erl
create mode 100644 apps/els_lsp/src/els_text_synchronization_provider.erl
diff --git a/apps/els_core/src/els_config.erl b/apps/els_core/src/els_config.erl
index c595d6c86..79b8184da 100644
--- a/apps/els_core/src/els_config.erl
+++ b/apps/els_core/src/els_config.erl
@@ -54,7 +54,6 @@
| code_reload
| elvis_config_path
| indexing_enabled
- | bsp_enabled
| compiler_telemetry_enabled
| refactorerl
| edoc_custom_tags.
@@ -76,7 +75,6 @@
, search_paths => [path()]
, code_reload => map() | 'disabled'
, indexing_enabled => boolean()
- , bsp_enabled => boolean() | auto
, compiler_telemetry_enabled => boolean()
, refactorerl => map() | 'notconfigured'
}.
@@ -131,7 +129,6 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
CodePathExtraDirs = maps:get("code_path_extra_dirs", Config, []),
ok = add_code_paths(CodePathExtraDirs, RootPath),
ElvisConfigPath = maps:get("elvis_config_path", Config, undefined),
- BSPEnabled = maps:get("bsp_enabled", Config, auto),
IncrementalSync = maps:get("incremental_sync", Config, true),
Indexing = maps:get("indexing", Config, #{}),
CompilerTelemetryEnabled
@@ -160,7 +157,6 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
ok = set('ct-run-test', maps:merge( els_config_ct_run_test:default_config()
, CtRunTest)),
ok = set(elvis_config_path, ElvisConfigPath),
- ok = set(bsp_enabled, BSPEnabled),
ok = set(compiler_telemetry_enabled, CompilerTelemetryEnabled),
ok = set(edoc_custom_tags, EDocCustomTags),
ok = set(incremental_sync, IncrementalSync),
diff --git a/apps/els_core/src/els_provider.erl b/apps/els_core/src/els_provider.erl
index 328a069dc..307d927ef 100644
--- a/apps/els_core/src/els_provider.erl
+++ b/apps/els_core/src/els_provider.erl
@@ -2,10 +2,10 @@
%% API
-export([ handle_request/2
- , start_link/1
+ , start_link/0
, available_providers/0
- , enabled_providers/0
- , cancel_request/2
+ , cancel_request/1
+ , cancel_request_by_uri/1
]).
-behaviour(gen_server).
@@ -20,12 +20,12 @@
%%==============================================================================
-include_lib("kernel/include/logger.hrl").
--callback is_enabled() -> boolean().
--callback init() -> any().
--callback handle_request(request(), any()) -> {any(), any()}.
+-callback handle_request(request(), any()) -> {async, uri(), pid()} |
+ {response, any()} |
+ {diagnostics, uri(), [pid()]} |
+ noresponse.
-callback handle_info(any(), any()) -> any().
--callback cancel_request(pid(), any()) -> any().
--optional_callbacks([init/0, handle_info/2, cancel_request/2]).
+-optional_callbacks([handle_info/2]).
-type config() :: any().
-type provider() :: els_completion_provider
@@ -43,78 +43,134 @@
| els_code_lens_provider
| els_execute_command_provider
| els_rename_provider
- | els_bsp_provider.
+ | els_text_synchronization_provider.
-type request() :: {atom() | binary(), map()}.
--type state() :: #{ provider := provider()
- , internal_state := any()
- }.
-
+-type state() :: #{ in_progress := [progress_entry()]
+ , in_progress_diagnostics := [diagnostic_entry()]
+ }.
+-type progress_entry() :: {uri(), job()}.
+-type diagnostic_entry() :: #{ uri := uri()
+ , pending := [job()]
+ , diagnostics := [els_diagnostics:diagnostic()]
+ }.
+-type job() :: pid().
+%% TODO: Redefining uri() due to a type conflict with request()
+-type uri() :: binary().
-export_type([ config/0
, provider/0
, request/0
, state/0
]).
+%%==============================================================================
+%% Macro Definitions
+%%==============================================================================
+-define(SERVER, ?MODULE).
+
%%==============================================================================
%% External functions
%%==============================================================================
--spec start_link(provider()) -> {ok, pid()}.
-start_link(Provider) ->
- gen_server:start_link({local, Provider}, ?MODULE, Provider, []).
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ gen_server:start_link({local, ?SERVER}, ?MODULE, unused, []).
-spec handle_request(provider(), request()) -> any().
handle_request(Provider, Request) ->
- gen_server:call(Provider, {handle_request, Request}, infinity).
+ gen_server:call(?SERVER, {handle_request, Provider, Request}, infinity).
+
+-spec cancel_request(pid()) -> any().
+cancel_request(Job) ->
+ gen_server:cast(?SERVER, {cancel_request, Job}).
--spec cancel_request(provider(), pid()) -> any().
-cancel_request(Provider, Job) ->
- gen_server:cast(Provider, {cancel_request, Job}).
+-spec cancel_request_by_uri(uri()) -> any().
+cancel_request_by_uri(Uri) ->
+ gen_server:cast(?SERVER, {cancel_request_by_uri, Uri}).
%%==============================================================================
%% gen_server callbacks
%%==============================================================================
--spec init(els_provider:provider()) -> {ok, state()}.
-init(Provider) ->
- ?LOG_INFO("Starting provider ~p", [Provider]),
- InternalState = case erlang:function_exported(Provider, init, 0) of
- true ->
- Provider:init();
- false ->
- #{}
- end,
- {ok, #{provider => Provider, internal_state => InternalState}}.
+-spec init(unused) -> {ok, state()}.
+init(unused) ->
+ {ok, #{in_progress => [], in_progress_diagnostics => []}}.
-spec handle_call(any(), {pid(), any()}, state()) ->
{reply, any(), state()}.
-handle_call({handle_request, Request}, _From, State) ->
- #{internal_state := InternalState, provider := Provider} = State,
- {Reply, NewInternalState} = Provider:handle_request(Request, InternalState),
- {reply, Reply, State#{internal_state => NewInternalState}}.
+handle_call({handle_request, Provider, Request}, _From, State) ->
+ #{in_progress := InProgress, in_progress_diagnostics := InProgressDiagnostics}
+ = State,
+ case Provider:handle_request(Request, State) of
+ {async, Uri, Job} ->
+ {reply, {async, Job}, State#{in_progress => [{Uri, Job}|InProgress]}};
+ {response, Response} ->
+ {reply, {response, Response}, State};
+ {diagnostics, Uri, Jobs} ->
+ Entry = #{uri => Uri, pending => Jobs, diagnostics => []},
+ NewState =
+ State#{in_progress_diagnostics => [Entry|InProgressDiagnostics]},
+ {reply, noresponse, NewState};
+ noresponse ->
+ {reply, noresponse, State}
+ end.
-spec handle_cast(any(), state()) -> {noreply, state()}.
handle_cast({cancel_request, Job}, State) ->
- #{internal_state := InternalState, provider := Provider} = State,
- case erlang:function_exported(Provider, cancel_request, 2) of
- true ->
- NewInternalState = Provider:cancel_request(Job, InternalState),
- {noreply, State#{internal_state => NewInternalState}};
- false ->
- {noreply, State}
- end.
-
--spec handle_info(any(), state()) ->
- {noreply, state()}.
-handle_info(Request, State) ->
- #{provider := Provider, internal_state := InternalState} = State,
- case erlang:function_exported(Provider, handle_info, 2) of
- true ->
- NewInternalState = Provider:handle_info(Request, InternalState),
- {noreply, State#{internal_state => NewInternalState}};
- false ->
- {noreply, State}
- end.
+ ?LOG_DEBUG("Cancelling request [job=~p]", [Job]),
+ els_background_job:stop(Job),
+ #{ in_progress := InProgress } = State,
+ NewState = State#{ in_progress => lists:keydelete(Job, 2, InProgress) },
+ {noreply, NewState};
+handle_cast({cancel_request_by_uri, Uri}, State) ->
+ #{ in_progress := InProgress0 } = State,
+ Fun = fun({U, Job}) ->
+ case U =:= Uri of
+ true ->
+ els_background_job:stop(Job),
+ false;
+ false ->
+ true
+ end
+ end,
+ InProgress = lists:filtermap(Fun, InProgress0),
+ ?LOG_DEBUG("Cancelling requests by Uri [uri=~p]", [Uri]),
+ NewState = State#{in_progress => InProgress},
+ {noreply, NewState}.
+
+-spec handle_info(any(), state()) -> {noreply, state()}.
+handle_info({result, Result, Job}, State) ->
+ ?LOG_DEBUG("Received result [job=~p]", [Job]),
+ #{in_progress := InProgress} = State,
+ els_server:send_response(Job, Result),
+ NewState = State#{in_progress => lists:keydelete(Job, 2, InProgress)},
+ {noreply, NewState};
+%% LSP 3.15 introduce versioning for diagnostics. Until all clients
+%% support it, we need to keep track of old diagnostics and re-publish
+%% them every time we get a new chunk.
+handle_info({diagnostics, Diagnostics, Job}, State) ->
+ #{in_progress_diagnostics := InProgress} = State,
+ ?LOG_DEBUG("Received diagnostics [job=~p]", [Job]),
+ { #{ pending := Jobs
+ , diagnostics := OldDiagnostics
+ , uri := Uri
+ }
+ , Rest
+ } = find_entry(Job, InProgress),
+ NewDiagnostics = Diagnostics ++ OldDiagnostics,
+ els_diagnostics_provider:publish(Uri, NewDiagnostics),
+ NewState = case lists:delete(Job, Jobs) of
+ [] ->
+ State#{in_progress_diagnostics => Rest};
+ Remaining ->
+ State#{in_progress_diagnostics =>
+ [#{ pending => Remaining
+ , diagnostics => NewDiagnostics
+ , uri => Uri
+ }|Rest]}
+ end,
+ {noreply, NewState};
+handle_info(_Request, State) ->
+ {noreply, State}.
-spec available_providers() -> [provider()].
available_providers() ->
@@ -134,10 +190,24 @@ available_providers() ->
, els_execute_command_provider
, els_diagnostics_provider
, els_rename_provider
- , els_bsp_provider
, els_call_hierarchy_provider
+ , els_text_synchronization_provider
].
--spec enabled_providers() -> [provider()].
-enabled_providers() ->
- [Provider || Provider <- available_providers(), Provider:is_enabled()].
+%%==============================================================================
+%% Internal Functions
+%%==============================================================================
+-spec find_entry(job(), [diagnostic_entry()]) ->
+ {diagnostic_entry(), [diagnostic_entry()]}.
+find_entry(Job, InProgress) ->
+ find_entry(Job, InProgress, []).
+
+-spec find_entry(job(), [diagnostic_entry()], [diagnostic_entry()]) ->
+ {diagnostic_entry(), [diagnostic_entry()]}.
+find_entry(Job, [#{pending := Pending} = Entry|Rest], Acc) ->
+ case lists:member(Job, Pending) of
+ true ->
+ {Entry, Rest ++ Acc};
+ false ->
+ find_entry(Job, Rest, [Entry|Acc])
+ end.
diff --git a/apps/els_core/src/els_stdio.erl b/apps/els_core/src/els_stdio.erl
index 342b1b748..50d16207b 100644
--- a/apps/els_core/src/els_stdio.erl
+++ b/apps/els_core/src/els_stdio.erl
@@ -17,7 +17,7 @@
%%==============================================================================
-spec start_listener(function()) -> {ok, pid()}.
start_listener(Cb) ->
- {ok, IoDevice} = application:get_env(els_core, io_device),
+ IoDevice = application:get_env(els_core, io_device, standard_io),
{ok, proc_lib:spawn_link(?MODULE, init, [{Cb, IoDevice}])}.
-spec init({function(), atom() | pid()}) -> no_return().
diff --git a/apps/els_dap/src/els_dap_general_provider.erl b/apps/els_dap/src/els_dap_general_provider.erl
index 44e82a406..c32d2d51d 100644
--- a/apps/els_dap/src/els_dap_general_provider.erl
+++ b/apps/els_dap/src/els_dap_general_provider.erl
@@ -9,10 +9,8 @@
%%==============================================================================
-module(els_dap_general_provider).
--behaviour(els_provider).
-export([ handle_request/2
, handle_info/2
- , is_enabled/0
, init/0
]).
@@ -63,13 +61,6 @@
-type binding_type() :: generic | map_assoc.
-type line() :: non_neg_integer().
-%%==============================================================================
-%% els_provider functions
-%%==============================================================================
-
--spec is_enabled() -> boolean().
-is_enabled() -> true.
-
-spec init() -> state().
init() ->
#{ threads => #{}
diff --git a/apps/els_dap/src/els_dap_methods.erl b/apps/els_dap/src/els_dap_methods.erl
index 0666c23a6..231760d6b 100644
--- a/apps/els_dap/src/els_dap_methods.erl
+++ b/apps/els_dap/src/els_dap_methods.erl
@@ -40,10 +40,10 @@ dispatch(Command, Args, Type, State) ->
{error_response, Error, State}
end.
--spec do_dispatch(atom(), params(), state()) -> result().
+-spec do_dispatch(method_name(), params(), state()) -> result().
do_dispatch(Command, Args, #{status := initialized} = State) ->
Request = {Command, Args},
- case els_provider:handle_request(els_dap_general_provider, Request) of
+ case els_dap_provider:handle_request(els_dap_general_provider, Request) of
{error, Error} ->
{error_response, Error, State};
Result ->
@@ -51,7 +51,7 @@ do_dispatch(Command, Args, #{status := initialized} = State) ->
end;
do_dispatch(<<"initialize">>, Args, State) ->
Request = {<<"initialize">>, Args},
- case els_provider:handle_request(els_dap_general_provider, Request) of
+ case els_dap_provider:handle_request(els_dap_general_provider, Request) of
{error, Error} ->
{error_response, Error, State};
Result ->
diff --git a/apps/els_dap/src/els_dap_provider.erl b/apps/els_dap/src/els_dap_provider.erl
index 5e6963064..866f51d14 100644
--- a/apps/els_dap/src/els_dap_provider.erl
+++ b/apps/els_dap/src/els_dap_provider.erl
@@ -6,9 +6,7 @@
%% API
-export([ handle_request/2
- , start_link/1
- , available_providers/0
- , enabled_providers/0
+ , start_link/0
]).
-behaviour(gen_server).
@@ -31,10 +29,8 @@
-type config() :: any().
-type provider() :: els_dap_general_provider.
--type request() :: {atom(), map()}.
--type state() :: #{ provider := provider()
- , internal_state := any()
- }.
+-type request() :: {binary(), map()}.
+-type state() :: #{ internal_state := any() }.
-export_type([ config/0
, provider/0
@@ -42,47 +38,39 @@
, state/0
]).
+%%==============================================================================
+%% Macro Definitions
+%%==============================================================================
+-define(SERVER, ?MODULE).
+
%%==============================================================================
%% External functions
%%==============================================================================
--spec start_link(provider()) -> {ok, pid()}.
-start_link(Provider) ->
- gen_server:start_link({local, Provider}, ?MODULE, Provider, []).
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ gen_server:start_link({local, ?SERVER}, ?MODULE, unused, []).
-spec handle_request(provider(), request()) -> any().
handle_request(Provider, Request) ->
- gen_server:call(Provider, {handle_request, Provider, Request}, infinity).
-
--spec available_providers() -> [provider()].
-available_providers() ->
- [ els_dap_general_provider
- ].
-
--spec enabled_providers() -> [provider()].
-enabled_providers() ->
- [Provider || Provider <- available_providers(), Provider:is_enabled()].
+ gen_server:call(?SERVER, {handle_request, Provider, Request}, infinity).
%%==============================================================================
%% gen_server callbacks
%%==============================================================================
--spec init(els_provider:provider()) -> {ok, state()}.
-init(Provider) ->
- ?LOG_INFO("Starting provider ~p", [Provider]),
- InternalState = case erlang:function_exported(Provider, init, 0) of
- true ->
- Provider:init();
- false ->
- #{}
- end,
- {ok, #{provider => Provider, internal_state => InternalState}}.
+-spec init(unused) -> {ok, state()}.
+init(unused) ->
+ ?LOG_INFO("Starting DAP provider", []),
+ InternalState = els_dap_general_provider:init(),
+ {ok, #{internal_state => InternalState}}.
-spec handle_call(any(), {pid(), any()}, state()) ->
{reply, any(), state()}.
-handle_call({handle_request, Request}, _From, State) ->
- #{internal_state := InternalState, provider := Provider} = State,
- {Reply, NewInternalState} = Provider:handle_request(Request, InternalState),
+handle_call({handle_request, Provider, Request}, _From, State) ->
+ #{internal_state := InternalState} = State,
+ {Reply, NewInternalState} =
+ Provider:handle_request(Request, InternalState),
{reply, Reply, State#{internal_state => NewInternalState}}.
-spec handle_cast(any(), state()) ->
@@ -93,11 +81,7 @@ handle_cast(_Request, State) ->
-spec handle_info(any(), state()) ->
{noreply, state()}.
handle_info(Request, State) ->
- #{provider := Provider, internal_state := InternalState} = State,
- case erlang:function_exported(Provider, handle_info, 2) of
- true ->
- NewInternalState = Provider:handle_info(Request, InternalState),
- {noreply, State#{internal_state => NewInternalState}};
- false ->
- {noreply, State}
- end.
+ #{internal_state := InternalState} = State,
+ NewInternalState =
+ els_dap_general_provider:handle_info(Request, InternalState),
+ {noreply, State#{internal_state => NewInternalState}}.
diff --git a/apps/els_dap/src/els_dap_providers_sup.erl b/apps/els_dap/src/els_dap_providers_sup.erl
deleted file mode 100644
index 57941e7da..000000000
--- a/apps/els_dap/src/els_dap_providers_sup.erl
+++ /dev/null
@@ -1,63 +0,0 @@
-%%==============================================================================
-%% @doc Erlang DAP Providers' Supervisor
-%% @end
-%%==============================================================================
--module(els_dap_providers_sup).
-
-%%==============================================================================
-%% Behaviours
-%%==============================================================================
--behaviour(supervisor).
-
-%%==============================================================================
-%% Exports
-%%==============================================================================
-
-%% API
--export([ start_link/0 ]).
-
-%% Supervisor Callbacks
--export([ init/1 ]).
-
-%%==============================================================================
-%% Defines
-%%==============================================================================
--define(SERVER, ?MODULE).
-
-%%==============================================================================
-%% Type Definitions
-%%==============================================================================
--type provider_spec() ::
- #{ id := els_dap_provider:provider()
- , start := { els_dap_provider
- , start_link
- , [els_dap_provider:provider()]
- }
- , shutdown := brutal_kill
- }.
-
-%%==============================================================================
-%% API
-%%==============================================================================
--spec start_link() -> {ok, pid()}.
-start_link() ->
- supervisor:start_link({local, ?SERVER}, ?MODULE, []).
-
-%%==============================================================================
-%% Supervisor callbacks
-%%==============================================================================
--spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
-init([]) ->
- SupFlags = #{ strategy => one_for_one
- , intensity => 1
- , period => 5
- },
- ChildSpecs = [provider_specs(P) || P <- els_dap_provider:enabled_providers()],
- {ok, {SupFlags, ChildSpecs}}.
-
--spec provider_specs(els_dap_provider:provider()) -> provider_spec().
-provider_specs(Provider) ->
- #{ id => Provider
- , start => {els_dap_provider, start_link, [Provider]}
- , shutdown => brutal_kill
- }.
diff --git a/apps/els_dap/src/els_dap_sup.erl b/apps/els_dap/src/els_dap_sup.erl
index db85ea309..1631c8631 100644
--- a/apps/els_dap/src/els_dap_sup.erl
+++ b/apps/els_dap/src/els_dap_sup.erl
@@ -52,9 +52,8 @@ init([]) ->
, start => {els_config, start_link, []}
, shutdown => brutal_kill
}
- , #{ id => els_dap_providers_sup
- , start => {els_dap_providers_sup, start_link, []}
- , type => supervisor
+ , #{ id => els_dap_provider
+ , start => {els_dap_provider, start_link, []}
}
, #{ id => els_dap_server
, start => {els_dap_server, start_link, []}
diff --git a/apps/els_dap/test/els_dap_general_provider_SUITE.erl b/apps/els_dap/test/els_dap_general_provider_SUITE.erl
index 01c0c850c..8c2635533 100644
--- a/apps/els_dap/test/els_dap_general_provider_SUITE.erl
+++ b/apps/els_dap/test/els_dap_general_provider_SUITE.erl
@@ -89,7 +89,7 @@ init_per_testcase(TestCase, Config) when
TestCase =:= breakpoints_with_cond orelse
TestCase =:= breakpoints_with_hit orelse
TestCase =:= breakpoints_with_cond_and_hit ->
- {ok, DAPProvider} = els_provider:start_link(els_dap_general_provider),
+ {ok, DAPProvider} = els_dap_provider:start_link(),
{ok, _} = els_config:start_link(),
meck:expect(els_dap_server, send_event, 2, meck:val(ok)),
[{provider, DAPProvider}, {node, node_name()} | Config];
@@ -167,18 +167,18 @@ wait_for_break(NodeName, WantModule, WantLine) ->
%%==============================================================================
-spec initialize(config()) -> ok.
-initialize(Config) ->
- Provider = ?config(provider, Config),
- els_provider:handle_request(Provider, request_initialize(#{})),
+initialize(_Config) ->
+ Provider = els_dap_general_provider,
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
ok.
-spec launch_mfa(config()) -> ok.
launch_mfa(Config) ->
- Provider = ?config(provider, Config),
+ Provider = els_dap_general_provider,
DataDir = ?config(data_dir, Config),
Node = ?config(node, Config),
- els_provider:handle_request(Provider, request_initialize(#{})),
- els_provider:handle_request(
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
+ els_dap_provider:handle_request(
Provider,
request_launch(DataDir, Node, els_dap_test_module, entry, [])
),
@@ -187,11 +187,11 @@ launch_mfa(Config) ->
-spec launch_mfa_with_cookie(config()) -> ok.
launch_mfa_with_cookie(Config) ->
- Provider = ?config(provider, Config),
+ Provider = els_dap_general_provider,
DataDir = ?config(data_dir, Config),
Node = ?config(node, Config),
- els_provider:handle_request(Provider, request_initialize(#{})),
- els_provider:handle_request(
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
+ els_dap_provider:handle_request(
Provider,
request_launch(DataDir, Node, <<"some_cookie">>,
els_dap_test_module, entry, [])
@@ -201,26 +201,26 @@ launch_mfa_with_cookie(Config) ->
-spec configuration_done(config()) -> ok.
configuration_done(Config) ->
- Provider = ?config(provider, Config),
+ Provider = els_dap_general_provider,
DataDir = ?config(data_dir, Config),
Node = ?config(node, Config),
- els_provider:handle_request(Provider, request_initialize(#{})),
- els_provider:handle_request(
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
+ els_dap_provider:handle_request(
Provider,
request_launch(DataDir, Node, els_dap_test_module, entry, [])
),
els_test_utils:wait_until_mock_called(els_dap_server, send_event),
- els_provider:handle_request(Provider, request_configuration_done(#{})),
+ els_dap_provider:handle_request(Provider, request_configuration_done(#{})),
ok.
-spec configuration_done_with_long_names(config()) -> ok.
configuration_done_with_long_names(Config) ->
- Provider = ?config(provider, Config),
+ Provider = els_dap_general_provider,
DataDir = ?config(data_dir, Config),
NodeStr = io_lib:format("~s~p", [?MODULE, erlang:unique_integer()]),
Node = unicode:characters_to_binary(NodeStr),
- els_provider:handle_request(Provider, request_initialize(#{})),
- els_provider:handle_request(
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
+ els_dap_provider:handle_request(
Provider,
request_launch(DataDir, Node, <<"some_cookie">>,
els_dap_test_module, entry, [], use_long_names)
@@ -230,11 +230,11 @@ configuration_done_with_long_names(Config) ->
-spec configuration_done_with_long_names_using_host(config()) -> ok.
configuration_done_with_long_names_using_host(Config) ->
- Provider = ?config(provider, Config),
+ Provider = els_dap_general_provider,
DataDir = ?config(data_dir, Config),
Node = ?config(node, Config),
- els_provider:handle_request(Provider, request_initialize(#{})),
- els_provider:handle_request(
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
+ els_dap_provider:handle_request(
Provider,
request_launch(DataDir, Node, <<"some_cookie">>,
els_dap_test_module, entry, [], use_long_names)
@@ -244,43 +244,43 @@ configuration_done_with_long_names_using_host(Config) ->
-spec configuration_done_with_breakpoint(config()) -> ok.
configuration_done_with_breakpoint(Config) ->
- Provider = ?config(provider, Config),
+ Provider = els_dap_general_provider,
DataDir = ?config(data_dir, Config),
Node = ?config(node, Config),
- els_provider:handle_request(Provider, request_initialize(#{})),
- els_provider:handle_request(
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
+ els_dap_provider:handle_request(
Provider,
request_launch(DataDir, Node, els_dap_test_module, entry, [5])
),
els_test_utils:wait_until_mock_called(els_dap_server, send_event),
- els_provider:handle_request(
+ els_dap_provider:handle_request(
Provider,
request_set_breakpoints( path_to_test_module(DataDir, els_dap_test_module)
, [9, 29])
),
- els_provider:handle_request(Provider, request_configuration_done(#{})),
+ els_dap_provider:handle_request(Provider, request_configuration_done(#{})),
?assertEqual(ok, wait_for_break(Node, els_dap_test_module, 9)),
ok.
-spec frame_variables(config()) -> ok.
-frame_variables(Config) ->
- Provider = ?config(provider, Config),
+frame_variables(_Config) ->
+ Provider = els_dap_general_provider,
%% get thread ID from mocked DAP response
#{ <<"reason">> := <<"breakpoint">>
, <<"threadId">> := ThreadId} =
meck:capture(last, els_dap_server, send_event, [<<"stopped">>, '_'], 2),
%% get stackframe
#{<<"stackFrames">> := [#{<<"id">> := FrameId}]} =
- els_provider:handle_request( Provider
+ els_dap_provider:handle_request( Provider
, request_stack_frames(ThreadId)
),
%% get scope
#{<<"scopes">> := [#{<<"variablesReference">> := VariableRef}]} =
- els_provider:handle_request(Provider, request_scope(FrameId)),
+ els_dap_provider:handle_request(Provider, request_scope(FrameId)),
%% extract variable
#{<<"variables">> := [NVar]} =
- els_provider:handle_request(Provider, request_variable(VariableRef)),
+ els_dap_provider:handle_request(Provider, request_variable(VariableRef)),
%% at this point there should be only one variable present,
?assertMatch(#{ <<"name">> := <<"N">>
, <<"value">> := <<"5">>
@@ -290,32 +290,32 @@ frame_variables(Config) ->
ok.
-spec navigation_and_frames(config()) -> ok.
-navigation_and_frames(Config) ->
+navigation_and_frames(_Config) ->
%% test next, stepIn, continue and check against expected stack frames
- Provider = ?config(provider, Config),
+ Provider = els_dap_general_provider,
#{<<"threads">> := [#{<<"id">> := ThreadId}]} =
- els_provider:handle_request( Provider
+ els_dap_provider:handle_request( Provider
, request_threads()
),
%% next
%%, reset meck history, to capture next call
meck:reset([els_dap_server]),
- els_provider:handle_request(Provider, request_next(ThreadId)),
+ els_dap_provider:handle_request(Provider, request_next(ThreadId)),
els_test_utils:wait_until_mock_called(els_dap_server, send_event),
%% check
#{<<"stackFrames">> := Frames1} =
- els_provider:handle_request( Provider
+ els_dap_provider:handle_request( Provider
, request_stack_frames(ThreadId)
),
?assertMatch([#{ <<"line">> := 11
, <<"name">> := <<"els_dap_test_module:entry/1">>}], Frames1),
%% continue
meck:reset([els_dap_server]),
- els_provider:handle_request(Provider, request_continue(ThreadId)),
+ els_dap_provider:handle_request(Provider, request_continue(ThreadId)),
els_test_utils:wait_until_mock_called(els_dap_server, send_event),
%% check
#{<<"stackFrames">> := Frames2} =
- els_provider:handle_request( Provider
+ els_dap_provider:handle_request( Provider
, request_stack_frames(ThreadId)
),
?assertMatch( [ #{ <<"line">> := 9
@@ -327,11 +327,11 @@ navigation_and_frames(Config) ->
),
%% stepIn
meck:reset([els_dap_server]),
- els_provider:handle_request(Provider, request_step_in(ThreadId)),
+ els_dap_provider:handle_request(Provider, request_step_in(ThreadId)),
els_test_utils:wait_until_mock_called(els_dap_server, send_event),
%% check
#{<<"stackFrames">> := Frames3} =
- els_provider:handle_request( Provider
+ els_dap_provider:handle_request( Provider
, request_stack_frames(ThreadId)
),
?assertMatch( [ #{ <<"line">> := 15
@@ -347,19 +347,19 @@ navigation_and_frames(Config) ->
ok.
-spec set_variable(config()) -> ok.
-set_variable(Config) ->
- Provider = ?config(provider, Config),
+set_variable(_Config) ->
+ Provider = els_dap_general_provider,
#{<<"threads">> := [#{<<"id">> := ThreadId}]} =
- els_provider:handle_request( Provider
+ els_dap_provider:handle_request( Provider
, request_threads()
),
#{<<"stackFrames">> := [#{<<"id">> := FrameId1}]} =
- els_provider:handle_request( Provider
+ els_dap_provider:handle_request( Provider
, request_stack_frames(ThreadId)
),
meck:reset([els_dap_server]),
Result1 =
- els_provider:handle_request( Provider
+ els_dap_provider:handle_request( Provider
, request_evaluate( <<"repl">>
, FrameId1
, <<"N=1">>
@@ -370,12 +370,12 @@ set_variable(Config) ->
%% get variable value through hover evaluate
els_test_utils:wait_until_mock_called(els_dap_server, send_event),
#{<<"stackFrames">> := [#{<<"id">> := FrameId2}]} =
- els_provider:handle_request( Provider
+ els_dap_provider:handle_request( Provider
, request_stack_frames(ThreadId)
),
?assertNotEqual(FrameId1, FrameId2),
Result2 =
- els_provider:handle_request( Provider
+ els_dap_provider:handle_request( Provider
, request_evaluate( <<"hover">>
, FrameId2
, <<"N">>
@@ -384,10 +384,10 @@ set_variable(Config) ->
?assertEqual(#{<<"result">> => <<"1">>}, Result2),
%% get variable value through scopes
#{ <<"scopes">> := [ #{<<"variablesReference">> := VariableRef} ] } =
- els_provider:handle_request(Provider, request_scope(FrameId2)),
+ els_dap_provider:handle_request(Provider, request_scope(FrameId2)),
%% extract variable
#{<<"variables">> := [NVar]} =
- els_provider:handle_request( Provider
+ els_dap_provider:handle_request( Provider
, request_variable(VariableRef)
),
%% at this point there should be only one variable present
@@ -401,17 +401,17 @@ set_variable(Config) ->
-spec breakpoints(config()) -> ok.
breakpoints(Config) ->
- Provider = ?config(provider, Config),
+ Provider = els_dap_general_provider,
NodeName = ?config(node, Config),
Node = binary_to_atom(NodeName, utf8),
DataDir = ?config(data_dir, Config),
- els_provider:handle_request(
+ els_dap_provider:handle_request(
Provider,
request_set_breakpoints( path_to_test_module(DataDir, els_dap_test_module)
, [9])
),
?assertMatch([{{els_dap_test_module, 9}, _}], els_dap_rpc:all_breaks(Node)),
- els_provider:handle_request(
+ els_dap_provider:handle_request(
Provider,
request_set_function_breakpoints([<<"els_dap_test_module:entry/1">>])
),
@@ -419,7 +419,7 @@ breakpoints(Config) ->
[{{els_dap_test_module, 7}, _}, {{els_dap_test_module, 9}, _}],
els_dap_rpc:all_breaks(Node)
),
- els_provider:handle_request(
+ els_dap_provider:handle_request(
Provider,
request_set_breakpoints(path_to_test_module(DataDir, els_dap_test_module)
, [])
@@ -428,12 +428,12 @@ breakpoints(Config) ->
[{{els_dap_test_module, 7}, _}, {{els_dap_test_module, 9}, _}],
els_dap_rpc:all_breaks(Node)
),
- els_provider:handle_request(
+ els_dap_provider:handle_request(
Provider,
request_set_breakpoints(path_to_test_module(DataDir, els_dap_test_module)
, [9])
),
- els_provider:handle_request(
+ els_dap_provider:handle_request(
Provider,
request_set_function_breakpoints([])
),
@@ -474,18 +474,18 @@ breakpoints_with_cond_and_hit(Config) ->
%% Parameterizable base test for breakpoints: sets up a breakpoint with given
%% parameters and checks the value of N when first hit
breakpoints_base(Config, BreakLine, Params, NExp) ->
- Provider = ?config(provider, Config),
+ Provider = els_dap_general_provider,
DataDir = ?config(data_dir, Config),
Node = ?config(node, Config),
- els_provider:handle_request(Provider, request_initialize(#{})),
- els_provider:handle_request(
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
+ els_dap_provider:handle_request(
Provider,
request_launch(DataDir, Node, els_dap_test_module, entry, [10])
),
els_test_utils:wait_until_mock_called(els_dap_server, send_event),
meck:reset([els_dap_server]),
- els_provider:handle_request(
+ els_dap_provider:handle_request(
Provider,
request_set_breakpoints(
path_to_test_module(DataDir, els_dap_test_module),
@@ -493,21 +493,21 @@ breakpoints_base(Config, BreakLine, Params, NExp) ->
)
),
%% hit breakpoint
- els_provider:handle_request(Provider, request_configuration_done(#{})),
+ els_dap_provider:handle_request(Provider, request_configuration_done(#{})),
?assertEqual(ok, wait_for_break(Node, els_dap_test_module, BreakLine)),
%% check value of N
#{<<"threads">> := [#{<<"id">> := ThreadId}]} =
- els_provider:handle_request( Provider
+ els_dap_provider:handle_request( Provider
, request_threads()
),
#{<<"stackFrames">> := [#{<<"id">> := FrameId}|_]} =
- els_provider:handle_request( Provider
+ els_dap_provider:handle_request( Provider
, request_stack_frames(ThreadId)
),
#{<<"scopes">> := [#{<<"variablesReference">> := VariableRef}]} =
- els_provider:handle_request(Provider, request_scope(FrameId)),
+ els_dap_provider:handle_request(Provider, request_scope(FrameId)),
#{<<"variables">> := [NVar]} =
- els_provider:handle_request(Provider, request_variable(VariableRef)),
+ els_dap_provider:handle_request(Provider, request_variable(VariableRef)),
?assertMatch(#{ <<"name">> := <<"N">>
, <<"value">> := NExp
, <<"variablesReference">> := 0
@@ -548,25 +548,25 @@ log_points_empty_cond(Config) ->
%% Parameterizable base test for logpoints: sets up a logpoint with given
%% parameters and checks how many hits it gets before hitting a given breakpoint
log_points_base(Config, LogLine, Params, BreakLine, NumCalls) ->
- Provider = ?config(provider, Config),
+ Provider = els_dap_general_provider,
DataDir = ?config(data_dir, Config),
Node = ?config(node, Config),
- els_provider:handle_request(Provider, request_initialize(#{})),
- els_provider:handle_request(
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
+ els_dap_provider:handle_request(
Provider,
request_launch(DataDir, Node, els_dap_test_module, entry, [10])
),
els_test_utils:wait_until_mock_called(els_dap_server, send_event),
meck:reset([els_dap_server]),
- els_provider:handle_request(
+ els_dap_provider:handle_request(
Provider,
request_set_breakpoints(
path_to_test_module(DataDir, els_dap_test_module),
[{LogLine, Params}, BreakLine]
)
),
- els_provider:handle_request(Provider, request_configuration_done(#{})),
+ els_dap_provider:handle_request(Provider, request_configuration_done(#{})),
?assertEqual(ok, wait_for_break(Node, els_dap_test_module, BreakLine)),
?assertEqual(NumCalls,
meck:num_calls(els_dap_server, send_event, [<<"output">>, '_'])),
diff --git a/apps/els_lsp/src/els_background_job_sup.erl b/apps/els_lsp/src/els_background_job_sup.erl
index 30024608f..034761ed4 100644
--- a/apps/els_lsp/src/els_background_job_sup.erl
+++ b/apps/els_lsp/src/els_background_job_sup.erl
@@ -42,6 +42,6 @@ init([]) ->
ChildSpecs = [#{ id => els_background_job
, start => {els_background_job, start_link, []}
, restart => temporary
- , shutdown => 5000
+ , shutdown => brutal_kill
}],
{ok, {SupFlags, ChildSpecs}}.
diff --git a/apps/els_lsp/src/els_bsp_client.erl b/apps/els_lsp/src/els_bsp_client.erl
deleted file mode 100644
index 425e0b962..000000000
--- a/apps/els_lsp/src/els_bsp_client.erl
+++ /dev/null
@@ -1,305 +0,0 @@
-%%==============================================================================
-%% A client for the Build Server Protocol using the STDIO transport
-%%==============================================================================
-%% https://build-server-protocol.github.io/docs/specification.html
-%%==============================================================================
-
--module(els_bsp_client).
-
-%%==============================================================================
-%% Behaviours
-%%==============================================================================
-
--behaviour(gen_server).
--export([ init/1
- , handle_call/3
- , handle_cast/2
- , handle_info/2
- , terminate/2
- ]).
-
-%%==============================================================================
-%% Exports
-%%==============================================================================
--export([ start_link/0
- , start_server/1
- , stop/0
- , request/1
- , request/2
- , notification/1
- , notification/2
- , wait_response/2
- , check_response/2
- ]).
-
-%%==============================================================================
-%% Includes
-%%==============================================================================
--include("els_lsp.hrl").
--include_lib("kernel/include/logger.hrl").
-
-%%==============================================================================
-%% Defines
-%%==============================================================================
--define(SERVER, ?MODULE).
--define(BSP_WILDCARD, "*.json").
--define(BSP_CONF_DIR, ".bsp").
-
-%%==============================================================================
-%% Record Definitions
-%%==============================================================================
--record(state, { request_id = 1 :: request_id()
- , pending = [] :: [pending_request()]
- , port :: port() | 'undefined'
- , buffer = <<>> :: binary()
- }).
-
-%%==============================================================================
-%% Type Definitions
-%%==============================================================================
--type state() :: #state{}.
--type request_id() :: pos_integer().
--type params() :: #{}.
--type method() :: binary().
--type pending_request() :: [{request_id(), from()}].
--type from() :: {pid(), any()}.
-
-
-%%==============================================================================
-%% Compat Stuff
-%% Since the build server can take arbitrary amounts of time to process things
-%% we really would like to use gen_server:send_request et co that are added in
-%% OTP 23/24, but we also need to support older OTP versions for now - implement
-%% rudimentary scaffolding to fake the new functionality.
-%% Remove whenever only OTP > 23 is supported.
-%%==============================================================================
--type server_ref() :: atom() | pid().
-
--if(?OTP_RELEASE > 23).
--spec do_send_request(server_ref(), any()) -> any().
-do_send_request(ServerRef, Request) ->
- gen_server:send_request(ServerRef, Request).
-
--spec do_wait_response(any(), timeout()) ->
- {reply, any()} |
- timeout |
- {error, {any(), server_ref()}}.
-do_wait_response(RequestId, Timeout) ->
- gen_server:wait_response(RequestId, Timeout).
-
--spec do_check_response(any(), any()) ->
- {reply, any()} |
- no_reply |
- {error, {any(), server_ref()}}.
-do_check_response(Msg, RequestId) ->
- gen_server:check_response(Msg, RequestId).
--else.
--spec do_send_request(server_ref(), any()) -> any().
-do_send_request(ServerRef, Request) ->
- Self = self(),
- Ref = erlang:make_ref(),
- F = fun() ->
- Result = gen_server:call(ServerRef, Request, infinity),
- try Self ! {Ref, Result} catch _:_ -> ok end
- end,
- {Pid, Mon} = erlang:spawn_monitor(F),
- {Pid, Mon, Ref, ServerRef}.
-
--spec do_wait_response(any(), timeout()) ->
- {reply, any()} |
- timeout |
- {error, {any(), server_ref()}}.
-do_wait_response({_Pid, Mon, Ref, ServerRef}, Timeout) ->
- receive
- {Ref, Result} ->
- erlang:demonitor(Mon, [flush]),
- {reply, Result};
- {'DOWN', Mon, _Type, _Object, Info} ->
- {error, {Info, ServerRef}}
- after Timeout ->
- timeout
- end.
-
--spec do_check_response(any(), any()) ->
- {reply, any()} |
- no_reply |
- {error, {any(), server_ref()}}.
-do_check_response(Msg, {_Pid, Mon, Ref, ServerRef}) ->
- case Msg of
- {Ref, Result} ->
- erlang:demonitor(Mon, [flush]),
- {reply, Result};
- {'DOWN', Mon, _Type, _Object, Info} ->
- {error, {Info, ServerRef}};
- _ ->
- no_reply
- end.
--endif.
-
-%%==============================================================================
-%% API
-%%==============================================================================
--spec start_link() -> {ok, pid()}.
-start_link() ->
- gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
-
--spec start_server(uri()) -> {ok, map()} | {error, any()}.
-start_server(RootUri) ->
- gen_server:call(?SERVER, {start_server, RootUri}).
-
--spec stop() -> ok.
-stop() ->
- gen_server:stop(?SERVER).
-
--spec notification(method()) -> any().
-notification(Method) ->
- notification(Method, #{}).
-
--spec notification(method(), params()) -> any().
-notification(Method, Params) ->
- gen_server:cast(?SERVER, {notification, Method, Params}).
-
--spec request(method()) -> any().
-request(Method) ->
- request(Method, #{}).
-
--spec request(method(), params()) -> any().
-request(Method, Params) ->
- do_send_request(?SERVER, {request, Method, Params}).
-
--spec wait_response(any(), timeout()) ->
- {reply, any()} | timeout | {error, {any(), any()}}.
-wait_response(RequestId, Timeout) ->
- do_wait_response(RequestId, Timeout).
-
--spec check_response(any(), any()) ->
- {reply, any()} | no_reply | {error, {any(), any()}}.
-check_response(Msg, RequestId) ->
- do_check_response(Msg, RequestId).
-
-%%==============================================================================
-%% gen_server Callback Functions
-%%==============================================================================
--spec init([]) -> {ok, state()}.
-init([]) ->
- process_flag(trap_exit, true),
- {ok, #state{}}.
-
--spec handle_call(any(), any() , state()) ->
- {noreply, state()} | {reply, any(), state()}.
-handle_call({start_server, RootUri}, _From, State) ->
- RootPath = els_uri:path(RootUri),
- case find_config(RootPath) of
- undefined ->
- ?LOG_INFO("Found no BSP configuration. [root=~p]", [RootPath]),
- {reply, {error, noconfig}, State};
- #{ argv := [Cmd|Params] } = Config ->
- Executable = os:find_executable(binary_to_list(Cmd)),
- Args = [binary_to_list(P) || P <- Params],
- Opts = [{args, Args}, use_stdio, binary],
- ?LOG_INFO( "Start BSP Server [executable=~p] [args=~p]"
- , [Executable, Args]
- ),
- Port = open_port({spawn_executable, Executable}, Opts),
- {reply, {ok, Config}, State#state{port = Port}}
- end;
-handle_call({request, Method, Params}, From, State) ->
- #state{port = Port, request_id = RequestId, pending = Pending} = State,
- ?LOG_INFO( "Sending BSP Request [id=~p] [method=~p] [params=~p]"
- , [RequestId, Method, Params]
- ),
- Payload = els_protocol:request(RequestId, Method, Params),
- port_command(Port, Payload),
- {noreply, State#state{ request_id = RequestId + 1
- , pending = [{RequestId, From} | Pending]
- }}.
-
--spec handle_cast(any(), state()) -> {noreply, state()}.
-handle_cast({notification, Method, Params}, State) ->
- ?LOG_INFO( "Sending BSP Notification [method=~p] [params=~p]"
- , [Method, Params]
- ),
- #state{port = Port} = State,
- Payload = els_protocol:notification(Method, Params),
- port_command(Port, Payload),
- {noreply, State}.
-
--spec handle_info(any(), state()) -> {noreply, state()}.
-handle_info({Port, {data, Data}}, #state{port = Port} = State) ->
- NewState = handle_data(Data, State),
- {noreply, NewState};
-handle_info(_Request, State) ->
- {noreply, State}.
-
--spec terminate(any(), state()) -> ok.
-terminate(_Reason, #state{port = Port} = _State) ->
- case Port of
- undefined ->
- ok;
- _ ->
- port_close(Port)
- end,
- ok.
-
-%%==============================================================================
-%% Internal Functions
-%%==============================================================================
--spec find_config(els_uri:path()) -> map() | undefined.
-find_config(RootDir) ->
- Wildcard = filename:join([RootDir, ?BSP_CONF_DIR, ?BSP_WILDCARD]),
- Candidates = filelib:wildcard(els_utils:to_list(Wildcard)),
- choose_config(Candidates).
-
--spec choose_config([file:filename()]) -> map() | undefined.
-choose_config([]) ->
- undefined;
-choose_config([F|Fs]) ->
- try
- {ok, Content} = file:read_file(F),
- Config = jsx:decode(Content, [return_maps, {labels, atom}]),
- Languages = maps:get(languages, Config),
- case lists:member(<<"erlang">>, Languages) of
- true ->
- Config;
- false ->
- choose_config(Fs)
- end
- catch
- C:E:S ->
- ?LOG_ERROR( "Bad BSP config file. [file=~p] [error=~p]"
- , [F, {C, E, S}]
- ),
- choose_config(Fs)
- end.
-
--spec handle_data(binary(), state()) -> state().
-handle_data(Data, State) ->
- #state{buffer = Buffer, pending = Pending} = State,
- NewData = <>,
- ?LOG_DEBUG( "Received BSP Data [buffer=~p] [data=~p]"
- , [Buffer, Data]
- ),
- {Messages, NewBuffer} = els_jsonrpc:split(NewData),
- NewPending = lists:foldl(fun handle_message/2, Pending, Messages),
- State#state{buffer = NewBuffer, pending = NewPending}.
-
--spec handle_message(message(), [pending_request()]) -> [pending_request()].
-handle_message(#{ id := Id
- , method := Method
- , params := Params
- } = _Request, Pending) ->
- ?LOG_INFO( "Received BSP Request [id=~p] [method=~p] [params=~p]"
- , [Id, Method, Params]
- ),
- %% TODO: Handle server-initiated request
- Pending;
-handle_message(#{id := Id} = Response, Pending) ->
- From = proplists:get_value(Id, Pending),
- gen_server:reply(From, Response),
- lists:keydelete(Id, 1, Pending);
-handle_message(#{ method := Method, params := Params}, State) ->
- ?LOG_INFO( "Received BSP Notification [method=~p] [params=~p]"
- , [Method, Params]
- ),
- %% TODO: Handle server-initiated notification
- State.
diff --git a/apps/els_lsp/src/els_bsp_provider.erl b/apps/els_lsp/src/els_bsp_provider.erl
deleted file mode 100644
index 137cad884..000000000
--- a/apps/els_lsp/src/els_bsp_provider.erl
+++ /dev/null
@@ -1,262 +0,0 @@
--module(els_bsp_provider).
-
--behaviour(els_provider).
-
-%% API
--export([ start/1
- , maybe_start/1
- , info/1
- , request/2
- ]).
-
-%% els_provider functions
--export([ is_enabled/0
- , init/0
- , handle_request/2
- , handle_info/2
- ]).
-
-
-%%==============================================================================
-%% Includes
-%%==============================================================================
--include("els_lsp.hrl").
--include_lib("kernel/include/logger.hrl").
-
-%%==============================================================================
-%% Types
-%%==============================================================================
--type state() :: #{ running := boolean() % BSP server running?
- , root_uri := uri() | undefined % the root uri
- , pending_requests := list() % pending requests
- , pending_sources := list() % pending source info
- }.
--type method() :: binary().
--type params() :: map() | null.
--type request_response() :: {reply, any()} | {error, any()}.
--type request_id() :: {reference(), reference(), atom() | pid()}.
--type from() :: {pid(), reference()} | self.
--type config() :: map().
--type info_item() :: is_running.
-
-%%==============================================================================
-%% API
-%%==============================================================================
--spec start(uri()) -> {ok, config()} | {error, term()}.
-start(RootUri) ->
- els_provider:handle_request(?MODULE, {start, #{ root => RootUri }}).
-
--spec maybe_start(uri()) -> {ok, config()} | {error, term()} | disabled.
-maybe_start(RootUri) ->
- case els_config:get(bsp_enabled) of
- false ->
- disabled;
- X when X =:= true orelse X =:= auto ->
- start(RootUri)
- end.
-
--spec info(info_item()) -> any().
-info(Item) ->
- case els_provider:handle_request(?MODULE, {info, #{ item => Item}}) of
- {ok, Result} ->
- Result;
- {error, badarg} ->
- erlang:error(badarg, [Item])
- end.
-
--spec request(method(), params()) -> request_response().
-request(Method, Params) ->
- RequestId = send_request(Method, Params),
- wait_response(RequestId, infinity).
-
--spec send_request(method(), params()) -> request_id().
-send_request(Method, Params) ->
- Mon = erlang:monitor(process, ?MODULE),
- Ref = erlang:make_ref(),
- From = {self(), Ref},
- Request = #{ from => From, method => Method, params => Params },
- ok = els_provider:handle_request(?MODULE, {send_request, Request}),
- {Ref, Mon, ?MODULE}.
-
--spec wait_response(request_id(), timeout()) -> request_response() | timeout.
-wait_response({Ref, Mon, ServerRef}, Timeout) ->
- receive
- {Ref, Response} ->
- erlang:demonitor(Mon, [flush]),
- Response;
- {'DOWN', Mon, _Type, _Object, Info} ->
- erlang:demonitor(Mon, [flush]),
- {error, {Info, ServerRef}}
- after Timeout ->
- timeout
- end.
-
-%%==============================================================================
-%% els_provider functions
-%%==============================================================================
--spec init() -> state().
-init() ->
- #{ running => false
- , root_uri => undefined
- , pending_requests => []
- , pending_sources => [ apps_paths, deps_paths ]
- }.
-
--spec is_enabled() -> true.
-is_enabled() -> true.
-
--spec handle_request({start, #{ root := uri() }}, state())
- -> {{ok, config()}, state()} | {{error, any()}, state()};
- ({send_request, #{ from := from()
- , method := method()
- , params := params() }}, state())
- -> {ok, state()};
- ({info, info_item()}, state())
- -> {{ok, any()}, state()} | {{error, badarg}, state()}.
-handle_request({start, #{ root := RootUri }}, #{ running := false } = State) ->
- ?LOG_INFO("Starting BSP server in ~p", [RootUri]),
- case els_bsp_client:start_server(RootUri) of
- {ok, Config} ->
- ?LOG_INFO("BSP server started from config ~p", [Config]),
- {{ok, Config}, initialize_bsp(RootUri, State)};
- {error, Reason} ->
- ?LOG_INFO("BSP server startup failed: ~p", [Reason]),
- {{error, Reason}, State}
- end;
-handle_request({send_request, #{ from := From
- , method := Method
- , params := Params }}, State) ->
- case State of
- #{ running := false } ->
- reply_request(From, {error, not_running}),
- {ok, State};
- #{ running := true } ->
- {ok, request(From, Method, Params, State)}
- end;
-handle_request({info, #{ item := Item }}, State) ->
- case Item of
- is_running ->
- {{ok, maps:get(running, State)}, State};
- _ ->
- {{error, badarg}, State}
- end.
-
--spec handle_info(any(), state()) -> state().
-handle_info(Msg, State) ->
- case check_response(Msg, State) of
- {ok, NewState} ->
- NewState;
- no_reply ->
- ?LOG_WARNING("Discarding unrecognized message: ~p", [Msg]),
- State
- end.
-
-%%==============================================================================
-%% Internal functions
-%%==============================================================================
--spec initialize_bsp(uri(), state()) -> state().
-initialize_bsp(Root, State) ->
- {ok, Vsn} = application:get_key(els_lsp, vsn),
- Params = #{ <<"displayName">> => <<"Erlang LS BSP Client">>
- , <<"version">> => list_to_binary(Vsn)
- , <<"bspVersion">> => <<"2.0.0">>
- , <<"rootUri">> => Root
- , <<"capabilities">> => #{ <<"languageIds">> => [<<"erlang">>] }
- , <<"data">> => #{}
- },
- request(<<"build/initialize">>, Params, State#{ running => true
- , root_uri => Root }).
-
--spec request(method(), params(), state()) -> state().
-request(Method, Params, State) ->
- request(self, Method, Params, State).
-
--spec request(from(), method(), params(), state()) -> state().
-request(From, Method, Params, #{ pending_requests := Pending } = State) ->
- RequestId = els_bsp_client:request(Method, Params),
- PendingRequest = {RequestId, From, {Method, Params}},
- State#{ pending_requests => [PendingRequest | Pending] }.
-
--spec handle_response({binary(), any()}, any(), state()) -> state().
-handle_response({<<"build/initialize">>, _}, Response, State) ->
- ?LOG_INFO("BSP Server initialized: ~p", [Response]),
- ok = els_bsp_client:notification(<<"build/initialized">>),
- request(<<"workspace/buildTargets">>, #{}, State);
-handle_response({<<"workspace/buildTargets">>, _}, Response, State0) ->
- Result = maps:get(result, Response, #{}),
- Targets = maps:get(targets, Result, []),
- TargetIds = lists:flatten([ maps:get(id, Target, []) || Target <- Targets ]),
- Params = #{ <<"targets">> => TargetIds },
- State1 = request(<<"buildTarget/sources">>, Params, State0),
- State2 = request(<<"buildTarget/dependencySources">>, Params, State1),
- State2;
-handle_response({<<"buildTarget/sources">>, _}, Response, State) ->
- handle_sources(apps_paths,
- fun(Source) -> maps:get(uri, Source, []) end,
- Response,
- State);
-handle_response({<<"buildTarget/dependencySources">>, _}, Response, State) ->
- handle_sources(deps_paths,
- fun(Source) -> Source end,
- Response,
- State);
-handle_response(Request, Response, State) ->
- ?LOG_WARNING("Unhandled response. [request=~p] [response=~p]",
- [Request, Response]),
- State.
-
--spec handle_sources(atom(), fun((any()) -> uri()), map(), state()) -> state().
-handle_sources(ConfigKey, SourceFun, Response, State) ->
- Result = maps:get(result, Response, #{}),
- Items = maps:get(items, Result, []),
- Sources = lists:flatten([ maps:get(sources, Item, []) || Item <- Items ]),
- Uris = lists:flatten([ SourceFun(Source) || Source <- Sources ]),
- UriMaps = [ uri_string:parse(Uri) || Uri <- Uris ],
- NewPaths = lists:flatten([ maps:get(path, UM, []) || UM <- UriMaps ]),
- OldPaths = els_config:get(ConfigKey),
- AllPaths = lists:usort([ els_utils:to_list(P) || P <- OldPaths ++ NewPaths]),
- els_config:set(ConfigKey, AllPaths),
- PendingSources = maps:get(pending_sources, State) -- [ConfigKey],
- case PendingSources of
- [] ->
- els_indexing:maybe_start();
- _ ->
- ok
- end,
- State#{ pending_sources => PendingSources }.
-
--spec check_response(any(), state()) -> {ok, state()} | no_reply.
-check_response(Msg, #{ pending_requests := Pending } = State) ->
- F = fun({RequestId, From, Request}) ->
- case els_bsp_client:check_response(Msg, RequestId) of
- no_reply ->
- true;
- {reply, _Reply} ->
- false;
- {error, Reason} ->
- ?LOG_ERROR("BSP request error. [from=~p] [request~p] [error=~p]",
- [From, Request, Reason]),
- false
- end
- end,
- case lists:splitwith(F, Pending) of
- {_, []} ->
- no_reply;
- {Left, [{RequestId, From, Request} | Right]} ->
- Result = els_bsp_client:check_response(Msg, RequestId),
- NewState = State#{ pending_requests => Left ++ Right },
- case {From, Result} of
- {self, {reply, Reply}} ->
- {ok, handle_response(Request, Reply, NewState)};
- {self, {error, _Reason}} ->
- {ok, NewState};
- {From, Result} ->
- ok = reply_request(From, Result),
- {ok, NewState}
- end
- end.
-
--spec reply_request(from(), any()) -> ok.
-reply_request({Pid, Ref}, Result) ->
- try Pid ! {Ref, Result} catch _:_ -> ok end,
- ok.
diff --git a/apps/els_lsp/src/els_buffer_server.erl b/apps/els_lsp/src/els_buffer_server.erl
deleted file mode 100644
index 293037323..000000000
--- a/apps/els_lsp/src/els_buffer_server.erl
+++ /dev/null
@@ -1,126 +0,0 @@
-%%%=============================================================================
-%%% @doc Buffer edits to an open buffer to avoid re-indexing too often.
-%%% @end
-%%%=============================================================================
--module(els_buffer_server).
-
-%%==============================================================================
-%% API
-%%==============================================================================
--export([ new/2
- , stop/1
- , apply_edits/2
- , flush/1
- ]).
-
--export([ start_link/2 ]).
-
-%%==============================================================================
-%% Callbacks for the gen_server behaviour
-%%==============================================================================
--behaviour(gen_server).
--export([ init/1
- , handle_call/3
- , handle_cast/2
- , handle_info/2
- ]).
-
-%%==============================================================================
-%% Macro Definitions
-%%==============================================================================
--define(FLUSH_DELAY, 200). %% ms
-
-%%==============================================================================
-%% Type Definitions
-%%==============================================================================
--type text() :: binary().
--type state() :: #{ uri := uri()
- , text := text()
- , ref := undefined | reference()
- , pending := [{pid(), any()}]
- }.
--type buffer() :: pid().
-
-%%==============================================================================
-%% Includes
-%%==============================================================================
--include_lib("kernel/include/logger.hrl").
--include("els_lsp.hrl").
-
-%%==============================================================================
-%% API
-%%==============================================================================
--spec new(uri(), text()) -> {ok, pid()}.
-new(Uri, Text) ->
- supervisor:start_child(els_buffer_sup, [Uri, Text]).
-
--spec stop(buffer()) -> ok.
-stop(Buffer) ->
- supervisor:terminate_child(els_buffer_sup, Buffer).
-
--spec apply_edits(buffer(), [els_text:edit()]) -> ok.
-apply_edits(Buffer, Edits) ->
- gen_server:cast(Buffer, {apply_edits, Edits}).
-
--spec flush(buffer()) -> text().
-flush(Buffer) ->
- gen_server:call(Buffer, {flush}).
-
--spec start_link(uri(), text()) -> {ok, buffer()}.
-start_link(Uri, Text) ->
- gen_server:start_link(?MODULE, {Uri, Text}, []).
-
-%%==============================================================================
-%% Callbacks for the gen_server behaviour
-%%==============================================================================
--spec init({uri(), text()}) -> {ok, state()}.
-init({Uri, Text}) ->
- {ok, #{ uri => Uri, text => Text, ref => undefined, pending => [] }}.
-
--spec handle_call(any(), {pid(), any()}, state()) -> {reply, any(), state()}.
-handle_call({flush}, From, State) ->
- #{uri := Uri, ref := Ref0, pending := Pending0} = State,
- ?LOG_DEBUG("[~p] Flushing request [uri=~p]", [?MODULE, Uri]),
- cancel_flush(Ref0),
- Ref = schedule_flush(),
- {noreply, State#{ref => Ref, pending => [From|Pending0]}};
-handle_call(Request, _From, State) ->
- {reply, {not_implemented, Request}, State}.
-
--spec handle_cast(any(), state()) -> {noreply, state()}.
-handle_cast({apply_edits, Edits}, #{uri := Uri} = State) ->
- ?LOG_DEBUG("[~p] Applying edits [uri=~p] [edits=~p]", [?MODULE, Uri, Edits]),
- #{text := Text0, ref := Ref0} = State,
- cancel_flush(Ref0),
- Text = els_text:apply_edits(Text0, Edits),
- Ref = schedule_flush(),
- {noreply, State#{text => Text, ref => Ref}}.
-
--spec handle_info(any(), state()) -> {noreply, state()}.
-handle_info(flush, #{uri := Uri, text := Text, pending := Pending0} = State) ->
- ?LOG_DEBUG("[~p] Flushing [uri=~p]", [?MODULE, Uri]),
- do_flush(Uri, Text),
- [gen_server:reply(From, Text) || From <- Pending0],
- {noreply, State#{pending => [], ref => undefined}};
-handle_info(_Request, State) ->
- {noreply, State}.
-
-%%==============================================================================
-%% Internal Functions
-%%==============================================================================
-
--spec schedule_flush() -> reference().
-schedule_flush() ->
- erlang:send_after(?FLUSH_DELAY, self(), flush).
-
--spec cancel_flush(undefined | reference()) -> ok.
-cancel_flush(undefined) ->
- ok;
-cancel_flush(Ref) ->
- erlang:cancel_timer(Ref),
- ok.
-
--spec do_flush(uri(), text()) -> ok.
-do_flush(Uri, Text) ->
- {ok, Document} = els_utils:lookup_document(Uri),
- els_indexing:deep_index(Document#{text => Text}).
diff --git a/apps/els_lsp/src/els_buffer_sup.erl b/apps/els_lsp/src/els_buffer_sup.erl
deleted file mode 100644
index c122983ea..000000000
--- a/apps/els_lsp/src/els_buffer_sup.erl
+++ /dev/null
@@ -1,47 +0,0 @@
-%%==============================================================================
-%% Supervisor for Buffers
-%%==============================================================================
--module(els_buffer_sup).
-
-%%==============================================================================
-%% Behaviours
-%%==============================================================================
--behaviour(supervisor).
-
-%%==============================================================================
-%% Exports
-%%==============================================================================
-
-%% API
--export([ start_link/0 ]).
-
-%% Supervisor Callbacks
--export([ init/1 ]).
-
-%%==============================================================================
-%% Defines
-%%==============================================================================
--define(SERVER, ?MODULE).
-
-%%==============================================================================
-%% API
-%%==============================================================================
--spec start_link() -> {ok, pid()}.
-start_link() ->
- supervisor:start_link({local, ?SERVER}, ?MODULE, []).
-
-%%==============================================================================
-%% Supervisor callbacks
-%%==============================================================================
--spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
-init([]) ->
- SupFlags = #{ strategy => simple_one_for_one
- , intensity => 5
- , period => 60
- },
- ChildSpecs = [#{ id => els_buffer_sup
- , start => {els_buffer_server, start_link, []}
- , restart => temporary
- , shutdown => 5000
- }],
- {ok, {SupFlags, ChildSpecs}}.
diff --git a/apps/els_lsp/src/els_call_hierarchy_provider.erl b/apps/els_lsp/src/els_call_hierarchy_provider.erl
index b3caf61a8..9328cd031 100644
--- a/apps/els_lsp/src/els_call_hierarchy_provider.erl
+++ b/apps/els_lsp/src/els_call_hierarchy_provider.erl
@@ -2,8 +2,8 @@
-behaviour(els_provider).
--export([ handle_request/2
- , is_enabled/0
+-export([ is_enabled/0
+ , handle_request/2
]).
%%==============================================================================
@@ -27,21 +27,21 @@
-spec is_enabled() -> boolean().
is_enabled() -> true.
--spec handle_request(any(), state()) -> {any(), state()}.
-handle_request({prepare, Params}, State) ->
+-spec handle_request(any(), state()) -> {response, any()}.
+handle_request({prepare, Params}, _State) ->
{Uri, Line, Char} =
els_text_document_position_params:uri_line_character(Params),
{ok, Document} = els_utils:lookup_document(Uri),
Functions = els_dt_document:wrapping_functions(Document, Line + 1, Char + 1),
Items = [function_to_item(Uri, F) || F <- Functions],
- {Items, State};
-handle_request({incoming_calls, Params}, State) ->
+ {response, Items};
+handle_request({incoming_calls, Params}, _State) ->
#{ <<"item">> := #{<<"uri">> := Uri} = Item } = Params,
POI = els_call_hierarchy_item:poi(Item),
References = els_references_provider:find_references(Uri, POI),
Items = [reference_to_item(Reference) || Reference <- References],
- {incoming_calls(Items), State};
-handle_request({outgoing_calls, Params}, State) ->
+ {response, incoming_calls(Items)};
+handle_request({outgoing_calls, Params}, _State) ->
#{ <<"item">> := Item } = Params,
#{ <<"uri">> := Uri } = Item,
POI = els_call_hierarchy_item:poi(Item),
@@ -56,7 +56,7 @@ handle_request({outgoing_calls, Params}, State) ->
[I|Acc]
end
end, [], Applications),
- {outgoing_calls(lists:reverse(Items)), State}.
+ {response, outgoing_calls(lists:reverse(Items))}.
%%==============================================================================
%% Internal functions
diff --git a/apps/els_lsp/src/els_code_action_provider.erl b/apps/els_lsp/src/els_code_action_provider.erl
index 28ade9fc0..789637a11 100644
--- a/apps/els_lsp/src/els_code_action_provider.erl
+++ b/apps/els_lsp/src/els_code_action_provider.erl
@@ -2,8 +2,8 @@
-behaviour(els_provider).
--export([ handle_request/2
- , is_enabled/0
+-export([ is_enabled/0
+ , handle_request/2
]).
-include("els_lsp.hrl").
@@ -13,17 +13,16 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
-
-spec is_enabled() -> boolean().
is_enabled() -> true.
--spec handle_request(any(), state()) -> {any(), state()}.
-handle_request({document_codeaction, Params}, State) ->
+-spec handle_request(any(), state()) -> {response, any()}.
+handle_request({document_codeaction, Params}, _State) ->
#{ <<"textDocument">> := #{ <<"uri">> := Uri}
, <<"range">> := RangeLSP
, <<"context">> := Context } = Params,
Result = code_actions(Uri, RangeLSP, Context),
- {Result, State}.
+ {response, Result}.
%%==============================================================================
%% Internal Functions
diff --git a/apps/els_lsp/src/els_code_lens_provider.erl b/apps/els_lsp/src/els_code_lens_provider.erl
index 394a55458..1493281f5 100644
--- a/apps/els_lsp/src/els_code_lens_provider.erl
+++ b/apps/els_lsp/src/els_code_lens_provider.erl
@@ -1,27 +1,17 @@
-module(els_code_lens_provider).
-behaviour(els_provider).
--export([ handle_info/2
- , handle_request/2
- , init/0
- , is_enabled/0
+-export([ is_enabled/0
, options/0
- , cancel_request/2
+ , handle_request/2
]).
-include("els_lsp.hrl").
-include_lib("kernel/include/logger.hrl").
--type state() :: #{in_progress => [progress_entry()]}.
--type progress_entry() :: {uri(), job()}.
--type job() :: pid().
-
--define(SERVER, ?MODULE).
-
%%==============================================================================
%% els_provider functions
%%==============================================================================
-
-spec is_enabled() -> boolean().
is_enabled() -> true.
@@ -29,31 +19,12 @@ is_enabled() -> true.
options() ->
#{ resolveProvider => false }.
--spec init() -> state().
-init() ->
- #{ in_progress => [] }.
-
--spec handle_request(any(), state()) -> {job(), state()}.
-handle_request({document_codelens, Params}, State) ->
- #{in_progress := InProgress} = State,
+-spec handle_request(any(), any()) -> {async, uri(), pid()}.
+handle_request({document_codelens, Params}, _State) ->
#{ <<"textDocument">> := #{ <<"uri">> := Uri}} = Params,
?LOG_DEBUG("Starting lenses job [uri=~p]", [Uri]),
Job = run_lenses_job(Uri),
- {Job, State#{in_progress => [{Uri, Job}|InProgress]}}.
-
--spec handle_info(any(), state()) -> state().
-handle_info({result, Lenses, Job}, State) ->
- ?LOG_DEBUG("Received lenses result [job=~p]", [Job]),
- #{ in_progress := InProgress } = State,
- els_server:send_response(Job, Lenses),
- State#{ in_progress => lists:keydelete(Job, 2, InProgress) }.
-
--spec cancel_request(job(), state()) -> state().
-cancel_request(Job, State) ->
- ?LOG_DEBUG("Cancelling lenses [job=~p]", [Job]),
- els_background_job:stop(Job),
- #{ in_progress := InProgress } = State,
- State#{ in_progress => lists:keydelete(Job, 2, InProgress) }.
+ {async, Uri, Job}.
%%==============================================================================
%% Internal Functions
@@ -71,7 +42,7 @@ run_lenses_job(Uri) ->
, title => <<"Lenses">>
, on_complete =>
fun(Lenses) ->
- ?SERVER ! {result, Lenses, self()},
+ els_provider ! {result, Lenses, self()},
ok
end
},
diff --git a/apps/els_lsp/src/els_completion_provider.erl b/apps/els_lsp/src/els_completion_provider.erl
index e8d8fec9b..fba81b286 100644
--- a/apps/els_lsp/src/els_completion_provider.erl
+++ b/apps/els_lsp/src/els_completion_provider.erl
@@ -6,7 +6,6 @@
-include_lib("kernel/include/logger.hrl").
-export([ handle_request/2
- , is_enabled/0
, trigger_characters/0
]).
@@ -23,39 +22,22 @@
-type items() :: [item()].
-type item() :: completion_item().
--type state() :: any().
%%==============================================================================
%% els_provider functions
%%==============================================================================
-
--spec is_enabled() -> boolean().
-is_enabled() ->
- true.
-
-spec trigger_characters() -> [binary()].
trigger_characters() ->
[<<":">>, <<"#">>, <<"?">>, <<".">>, <<"-">>, <<"\"">>].
--spec handle_request(els_provider:request(), state()) -> {any(), state()}.
-handle_request({completion, Params}, State) ->
+-spec handle_request(els_provider:request(), any()) -> {response, any()}.
+handle_request({completion, Params}, _State) ->
#{ <<"position">> := #{ <<"line">> := Line
, <<"character">> := Character
}
, <<"textDocument">> := #{<<"uri">> := Uri}
} = Params,
- %% Ensure there are no pending changes.
- {ok, Document} = els_utils:lookup_document(Uri),
- #{buffer := Buffer, text := Text0} = Document,
- Text = case Buffer of
- undefined ->
- %% This clause is only kept due to the current test suites,
- %% where LSP clients can trigger a completion request
- %% before a did_open
- Text0;
- _ ->
- els_buffer_server:flush(Buffer)
- end,
+ {ok, #{text := Text} = Document} = els_utils:lookup_document(Uri),
Context = maps:get( <<"context">>
, Params
, #{ <<"triggerKind">> => ?COMPLETION_TRIGGER_KIND_INVOKED }
@@ -79,9 +61,9 @@ handle_request({completion, Params}, State) ->
, column => Character
},
Completions = find_completions(Prefix, TriggerKind, Opts),
- {Completions, State};
-handle_request({resolve, CompletionItem}, State) ->
- {resolve(CompletionItem), State}.
+ {response, Completions};
+handle_request({resolve, CompletionItem}, _State) ->
+ {response, resolve(CompletionItem)}.
%%==============================================================================
%% Internal functions
diff --git a/apps/els_lsp/src/els_definition_provider.erl b/apps/els_lsp/src/els_definition_provider.erl
index 0c92ac3b4..631026220 100644
--- a/apps/els_lsp/src/els_definition_provider.erl
+++ b/apps/els_lsp/src/els_definition_provider.erl
@@ -2,24 +2,21 @@
-behaviour(els_provider).
--export([ handle_request/2
- , is_enabled/0
+-export([ is_enabled/0
+ , handle_request/2
]).
-include("els_lsp.hrl").
-
-type state() :: any().
%%==============================================================================
%% els_provider functions
%%==============================================================================
-
-spec is_enabled() -> boolean().
-is_enabled() ->
- true.
+is_enabled() -> true.
--spec handle_request(any(), state()) -> {any(), state()}.
+-spec handle_request(any(), state()) -> {response, any()}.
handle_request({definition, Params}, State) ->
#{ <<"position">> := #{ <<"line">> := Line
, <<"character">> := Character
@@ -36,10 +33,10 @@ handle_request({definition, Params}, State) ->
null ->
els_references_provider:handle_request({references, Params}, State);
GoTo ->
- {GoTo, State}
+ {response, GoTo}
end;
GoTo ->
- {GoTo, State}
+ {response, GoTo}
end.
-spec goto_definition(uri(), [poi()]) -> map() | null.
diff --git a/apps/els_lsp/src/els_diagnostics_provider.erl b/apps/els_lsp/src/els_diagnostics_provider.erl
index 240d9556c..65c2ce14b 100644
--- a/apps/els_lsp/src/els_diagnostics_provider.erl
+++ b/apps/els_lsp/src/els_diagnostics_provider.erl
@@ -2,37 +2,24 @@
-behaviour(els_provider).
--export([ handle_info/2
- , handle_request/2
- , init/0
- , is_enabled/0
+-export([ is_enabled/0
, options/0
+ , handle_request/2
]).
-export([ notify/2
, publish/2
]).
-
%%==============================================================================
%% Includes
%%==============================================================================
-include("els_lsp.hrl").
-include_lib("kernel/include/logger.hrl").
--type state() :: #{in_progress => [progress_entry()]}.
--type progress_entry() :: #{ uri := uri()
- , pending := [job()]
- , diagnostics := [els_diagnostics:diagnostic()]
- }.
--type job() :: pid().
-
--define(SERVER, ?MODULE).
-
%%==============================================================================
%% els_provider functions
%%==============================================================================
-
-spec is_enabled() -> boolean().
is_enabled() -> true.
@@ -40,52 +27,19 @@ is_enabled() -> true.
options() ->
#{}.
--spec init() -> state().
-init() ->
- #{ in_progress => [] }.
-
-%% LSP 3.15 introduce versioning for diagnostics. Until all clients
-%% support it, we need to keep track of old diagnostics and re-publish
-%% them every time we get a new chunk.
--spec handle_info(any(), state()) -> state().
-handle_info({diagnostics, Diagnostics, Job}, State) ->
- ?LOG_DEBUG("Received diagnostics [job=~p]", [Job]),
- #{ in_progress := InProgress } = State,
- { #{ pending := Jobs
- , diagnostics := OldDiagnostics
- , uri := Uri
- }
- , Rest
- } = find_entry(Job, InProgress),
- NewDiagnostics = Diagnostics ++ OldDiagnostics,
- ?MODULE:publish(Uri, NewDiagnostics),
- case lists:delete(Job, Jobs) of
- [] ->
- State#{in_progress => Rest};
- Remaining ->
- State#{in_progress => [#{ pending => Remaining
- , diagnostics => NewDiagnostics
- , uri => Uri
- }|Rest]}
- end;
-handle_info(_Request, State) ->
- State.
-
--spec handle_request(any(), state()) -> {any(), state()}.
-handle_request({run_diagnostics, Params}, State) ->
- #{in_progress := InProgress} = State,
+-spec handle_request(any(), any()) -> {diagnostics, uri(), [pid()]}.
+handle_request({run_diagnostics, Params}, _State) ->
#{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
?LOG_DEBUG("Starting diagnostics jobs [uri=~p]", [Uri]),
Jobs = els_diagnostics:run_diagnostics(Uri),
- Entry = #{uri => Uri, pending => Jobs, diagnostics => []},
- {noresponse, State#{in_progress => [Entry|InProgress]}}.
+ {diagnostics, Uri, Jobs}.
%%==============================================================================
%% API
%%==============================================================================
-spec notify([els_diagnostics:diagnostic()], pid()) -> ok.
notify(Diagnostics, Job) ->
- ?SERVER ! {diagnostics, Diagnostics, Job},
+ els_provider ! {diagnostics, Diagnostics, Job},
ok.
-spec publish(uri(), [els_diagnostics:diagnostic()]) -> ok.
@@ -95,22 +49,3 @@ publish(Uri, Diagnostics) ->
, diagnostics => Diagnostics
},
els_server:send_notification(Method, Params).
-
-%%==============================================================================
-%% Internal Functions
-%%==============================================================================
-
--spec find_entry(job(), [progress_entry()]) ->
- {progress_entry(), [progress_entry()]}.
-find_entry(Job, InProgress) ->
- find_entry(Job, InProgress, []).
-
--spec find_entry(job(), [progress_entry()], [progress_entry()]) ->
- {progress_entry(), [progress_entry()]}.
-find_entry(Job, [#{pending := Pending} = Entry|Rest], Acc) ->
- case lists:member(Job, Pending) of
- true ->
- {Entry, Rest ++ Acc};
- false ->
- find_entry(Job, Rest, [Entry|Acc])
- end.
diff --git a/apps/els_lsp/src/els_document_highlight_provider.erl b/apps/els_lsp/src/els_document_highlight_provider.erl
index ce94f82a2..09d5cc8e0 100644
--- a/apps/els_lsp/src/els_document_highlight_provider.erl
+++ b/apps/els_lsp/src/els_document_highlight_provider.erl
@@ -2,8 +2,8 @@
-behaviour(els_provider).
--export([ handle_request/2
- , is_enabled/0
+-export([ is_enabled/0
+ , handle_request/2
]).
%%==============================================================================
@@ -19,13 +19,11 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
-
-spec is_enabled() -> boolean().
-is_enabled() ->
- true.
+is_enabled() -> true.
--spec handle_request(any(), state()) -> {any(), state()}.
-handle_request({document_highlight, Params}, State) ->
+-spec handle_request(any(), state()) -> {response, any()}.
+handle_request({document_highlight, Params}, _State) ->
#{ <<"position">> := #{ <<"line">> := Line
, <<"character">> := Character
}
@@ -35,8 +33,8 @@ handle_request({document_highlight, Params}, State) ->
case
els_dt_document:get_element_at_pos(Document, Line + 1, Character + 1)
of
- [POI | _] -> {find_highlights(Document, POI), State};
- [] -> {null, State}
+ [POI | _] -> {response, find_highlights(Document, POI)};
+ [] -> {response, null}
end.
%%==============================================================================
diff --git a/apps/els_lsp/src/els_document_symbol_provider.erl b/apps/els_lsp/src/els_document_symbol_provider.erl
index f3bfafb9e..4a35ca18b 100644
--- a/apps/els_lsp/src/els_document_symbol_provider.erl
+++ b/apps/els_lsp/src/els_document_symbol_provider.erl
@@ -2,8 +2,8 @@
-behaviour(els_provider).
--export([ handle_request/2
- , is_enabled/0
+-export([ is_enabled/0
+ , handle_request/2
]).
-include("els_lsp.hrl").
@@ -13,18 +13,16 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
-
-spec is_enabled() -> boolean().
-is_enabled() ->
- true.
+is_enabled() -> true.
--spec handle_request(any(), state()) -> {any(), state()}.
-handle_request({document_symbol, Params}, State) ->
+-spec handle_request(any(), state()) -> {response, any()}.
+handle_request({document_symbol, Params}, _State) ->
#{ <<"textDocument">> := #{ <<"uri">> := Uri}} = Params,
Functions = functions(Uri),
case Functions of
- [] -> {null, State};
- _ -> {Functions, State}
+ [] -> {response, null};
+ _ -> {response, Functions}
end.
%%==============================================================================
diff --git a/apps/els_lsp/src/els_dt_document.erl b/apps/els_lsp/src/els_dt_document.erl
index 1c37386ca..2fcdf0253 100644
--- a/apps/els_lsp/src/els_dt_document.erl
+++ b/apps/els_lsp/src/els_dt_document.erl
@@ -46,7 +46,6 @@
-type id() :: atom().
-type kind() :: module | header | other.
-type source() :: otp | app | dep.
--type buffer() :: pid().
-export_type([source/0]).
%%==============================================================================
@@ -60,7 +59,6 @@
, md5 :: binary() | '_'
, pois :: [poi()] | '_' | ondemand
, source :: source() | '$2'
- , buffer :: buffer() | '_' | undefined
, words :: sets:set() | '_' | '$3'
}).
-type els_dt_document() :: #els_dt_document{}.
@@ -72,7 +70,6 @@
, md5 => binary()
, pois => [poi()] | ondemand
, source => source()
- , buffer => buffer() | undefined
, words => sets:set()
}.
-export_type([ id/0
@@ -103,7 +100,6 @@ from_item(#{ uri := Uri
, md5 := MD5
, pois := POIs
, source := Source
- , buffer := Buffer
, words := Words
}) ->
#els_dt_document{ uri = Uri
@@ -113,7 +109,6 @@ from_item(#{ uri := Uri
, md5 = MD5
, pois = POIs
, source = Source
- , buffer = Buffer
, words = Words
}.
@@ -125,7 +120,6 @@ to_item(#els_dt_document{ uri = Uri
, md5 = MD5
, pois = POIs
, source = Source
- , buffer = Buffer
, words = Words
}) ->
#{ uri => Uri
@@ -135,7 +129,6 @@ to_item(#els_dt_document{ uri = Uri
, md5 => MD5
, pois => POIs
, source => Source
- , buffer => Buffer
, words => Words
}.
@@ -176,7 +169,6 @@ new(Uri, Text, Id, Kind, Source) ->
, md5 => MD5
, pois => ondemand
, source => Source
- , buffer => undefined
, words => get_words(Text)
}.
@@ -242,7 +234,6 @@ find_candidates(Pattern) ->
, md5 = '_'
, pois = '_'
, source = '$2'
- , buffer = '_'
, words = '$3'},
[{'=/=', '$2', otp}],
[{{'$1', '$3'}}]}],
diff --git a/apps/els_lsp/src/els_execute_command_provider.erl b/apps/els_lsp/src/els_execute_command_provider.erl
index 77f54b6d8..15cad428c 100644
--- a/apps/els_lsp/src/els_execute_command_provider.erl
+++ b/apps/els_lsp/src/els_execute_command_provider.erl
@@ -2,9 +2,9 @@
-behaviour(els_provider).
--export([ handle_request/2
- , is_enabled/0
+-export([ is_enabled/0
, options/0
+ , handle_request/2
]).
%%==============================================================================
@@ -18,7 +18,6 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
-
-spec is_enabled() -> boolean().
is_enabled() -> true.
@@ -31,13 +30,13 @@ options() ->
, els_command:with_prefix(<<"function-references">>)
] }.
--spec handle_request(any(), state()) -> {any(), state()}.
-handle_request({workspace_executecommand, Params}, State) ->
+-spec handle_request(any(), state()) -> {response, any()}.
+handle_request({workspace_executecommand, Params}, _State) ->
#{ <<"command">> := PrefixedCommand } = Params,
Arguments = maps:get(<<"arguments">>, Params, []),
Result = execute_command( els_command:without_prefix(PrefixedCommand)
, Arguments),
- {Result, State}.
+ {response, Result}.
%%==============================================================================
%% Internal Functions
diff --git a/apps/els_lsp/src/els_folding_range_provider.erl b/apps/els_lsp/src/els_folding_range_provider.erl
index b9552a9a0..e2741a3de 100644
--- a/apps/els_lsp/src/els_folding_range_provider.erl
+++ b/apps/els_lsp/src/els_folding_range_provider.erl
@@ -4,27 +4,23 @@
-include("els_lsp.hrl").
--export([ handle_request/2
- , is_enabled/0
+-export([ is_enabled/0
+ , handle_request/2
]).
%%==============================================================================
%% Type Definitions
%%==============================================================================
-
-type folding_range_result() :: [folding_range()] | null.
--type state() :: any().
%%==============================================================================
%% els_provider functions
%%==============================================================================
-
-spec is_enabled() -> boolean().
-is_enabled() ->
- true.
+is_enabled() -> true.
--spec handle_request(tuple(), state()) -> {folding_range_result(), state()}.
-handle_request({document_foldingrange, Params}, State) ->
+-spec handle_request(tuple(), any()) -> {response, folding_range_result()}.
+handle_request({document_foldingrange, Params}, _State) ->
#{ <<"textDocument">> := #{<<"uri">> := Uri} } = Params,
{ok, Document} = els_utils:lookup_document(Uri),
POIs = els_dt_document:pois(Document, [folding_range]),
@@ -32,7 +28,7 @@ handle_request({document_foldingrange, Params}, State) ->
[] -> null;
Ranges -> Ranges
end,
- {Response, State}.
+ {response, Response}.
%%==============================================================================
%% Internal functions
diff --git a/apps/els_lsp/src/els_formatting_provider.erl b/apps/els_lsp/src/els_formatting_provider.erl
index 6994549d7..b3adfe601 100644
--- a/apps/els_lsp/src/els_formatting_provider.erl
+++ b/apps/els_lsp/src/els_formatting_provider.erl
@@ -33,12 +33,7 @@
%%==============================================================================
-spec init() -> state().
init() ->
- case els_config:get(bsp_enabled) of
- false ->
- [ fun format_document_local/3 ];
- _ ->
- [ fun format_document_bsp/3, fun format_document_local/3 ]
- end.
+ [ fun format_document_local/3 ].
%% Keep the behaviour happy
-spec is_enabled() -> boolean().
@@ -57,19 +52,19 @@ is_enabled_range() ->
-spec is_enabled_on_type() -> document_ontypeformatting_options().
is_enabled_on_type() -> false.
--spec handle_request(any(), state()) -> {any(), state()}.
-handle_request({document_formatting, Params}, State) ->
+-spec handle_request(any(), state()) -> {response, any()}.
+handle_request({document_formatting, Params}, _State) ->
#{ <<"options">> := Options
, <<"textDocument">> := #{<<"uri">> := Uri}
} = Params,
Path = els_uri:path(Uri),
case els_utils:project_relative(Uri) of
{error, not_relative} ->
- {[], State};
+ {response, []};
RelativePath ->
- format_document(Path, RelativePath, Options, State)
+ format_document(Path, RelativePath, Options)
end;
-handle_request({document_rangeformatting, Params}, State) ->
+handle_request({document_rangeformatting, Params}, _State) ->
#{ <<"range">> := #{ <<"start">> := StartPos
, <<"end">> := EndPos
}
@@ -78,10 +73,9 @@ handle_request({document_rangeformatting, Params}, State) ->
} = Params,
Range = #{ start => StartPos, 'end' => EndPos },
{ok, Document} = els_utils:lookup_document(Uri),
- case rangeformat_document(Uri, Document, Range, Options) of
- {ok, TextEdit} -> {TextEdit, State}
- end;
-handle_request({document_ontypeformatting, Params}, State) ->
+ {ok, TextEdit} = rangeformat_document(Uri, Document, Range, Options),
+ {response, TextEdit};
+handle_request({document_ontypeformatting, Params}, _State) ->
#{ <<"position">> := #{ <<"line">> := Line
, <<"character">> := Character
}
@@ -90,52 +84,25 @@ handle_request({document_ontypeformatting, Params}, State) ->
, <<"textDocument">> := #{<<"uri">> := Uri}
} = Params,
{ok, Document} = els_utils:lookup_document(Uri),
- case ontypeformat_document(Uri, Document, Line + 1, Character + 1, Char
- , Options) of
- {ok, TextEdit} -> {TextEdit, State}
- end.
+ {ok, TextEdit} =
+ ontypeformat_document(Uri, Document, Line + 1, Character + 1,
+ Char, Options),
+ {response, TextEdit}.
%%==============================================================================
%% Internal functions
%%==============================================================================
--spec format_document(binary(), string(), formatting_options(), state()) ->
- {[text_edit()], state()}.
-format_document(Path, RelativePath, Options, Formatters) ->
+-spec format_document(binary(), string(), formatting_options()) ->
+ {[text_edit()]}.
+format_document(Path, RelativePath, Options) ->
Fun = fun(Dir) ->
- NewFormatters = lists:dropwhile(
- fun(F) ->
- not F(Dir, RelativePath, Options)
- end,
- Formatters
- ),
+ format_document_local(Dir, RelativePath, Options),
Outfile = filename:join(Dir, RelativePath),
- {els_text_edit:diff_files(Path, Outfile), NewFormatters}
+ {response, els_text_edit:diff_files(Path, Outfile)}
end,
tempdir:mktmp(Fun).
--spec format_document_bsp(string(), string(), formatting_options()) ->
- boolean().
-format_document_bsp(Dir, RelativePath, _Options) ->
- Method = <<"rebar3/run">>,
- Params = #{ <<"args">> => ["format", "-o", Dir, "-f", RelativePath] },
- try
- case els_bsp_provider:request(Method, Params) of
- {error, Reason} ->
- error(Reason);
- {reply, #{ error := _Error } = Result} ->
- error(Result);
- {reply, Result} ->
- ?LOG_DEBUG("BSP format succeeded. [result=~p]", [Result]),
- true
- end
- catch
- C:E:S ->
- ?LOG_WARNING("format_document_bsp failed. ~p:~p ~p", [C, E, S]),
- false
- end.
-
--spec format_document_local(string(), string(), formatting_options()) ->
- boolean().
+-spec format_document_local(string(), string(), formatting_options()) -> ok.
format_document_local(Dir, RelativePath,
#{ <<"insertSpaces">> := InsertSpaces
, <<"tabSize">> := TabSize } = Options) ->
@@ -147,7 +114,7 @@ format_document_local(Dir, RelativePath,
},
Formatter = rebar3_formatter:new(default_formatter, Opts, unused),
rebar3_formatter:format_file(RelativePath, Formatter),
- true.
+ ok.
-spec rangeformat_document(uri(), map(), range(), formatting_options())
-> {ok, [text_edit()]}.
diff --git a/apps/els_lsp/src/els_general_provider.erl b/apps/els_lsp/src/els_general_provider.erl
index 52737d78c..aebe37684 100644
--- a/apps/els_lsp/src/els_general_provider.erl
+++ b/apps/els_lsp/src/els_general_provider.erl
@@ -1,8 +1,8 @@
-module(els_general_provider).
-behaviour(els_provider).
--export([ handle_request/2
- , is_enabled/0
+-export([ is_enabled/0
+ , handle_request/2
]).
-export([ server_capabilities/0
@@ -46,7 +46,6 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
-
-spec is_enabled() -> boolean().
is_enabled() -> true.
@@ -55,13 +54,13 @@ is_enabled() -> true.
| shutdown_request()
| exit_request()
, state()) ->
- { initialize_result()
+ { response,
+ initialize_result()
| initialized_result()
| shutdown_result()
| exit_result()
- , state()
}.
-handle_request({initialize, Params}, State) ->
+handle_request({initialize, Params}, _State) ->
#{ <<"rootUri">> := RootUri0
, <<"capabilities">> := Capabilities
} = Params,
@@ -77,49 +76,25 @@ handle_request({initialize, Params}, State) ->
_ -> #{}
end,
ok = els_config:initialize(RootUri, Capabilities, InitOptions, true),
- NewState = State#{ root_uri => RootUri, init_options => InitOptions},
- {server_capabilities(), NewState};
-handle_request({initialized, _Params}, State) ->
- #{root_uri := RootUri} = State,
-
+ {response, server_capabilities()};
+handle_request({initialized, _Params}, _State) ->
+ RootUri = els_config:get(root_uri),
NodeName = els_distribution_server:node_name( <<"erlang_ls">>
, filename:basename(RootUri)),
els_distribution_server:start_distribution(NodeName),
?LOG_INFO("Started distribution for: [~p]", [NodeName]),
-
- case els_bsp_provider:maybe_start(RootUri) of
- {error, Reason} ->
- case els_config:get(bsp_enabled) of
- true ->
- ?LOG_ERROR( "BSP server startup failed, shutting down. [reason=~p]"
- , [Reason]
- ),
- els_utils:halt(1);
- auto ->
- ?LOG_INFO("BSP server startup failed. [reason=~p]", [Reason]),
- ok
- end;
- _ ->
- ok
- end,
- case els_bsp_provider:info(is_running) of
- true -> %% The BSP provider will start indexing when it's ready
- ok;
- false -> %% We need to start indexing here
- els_indexing:maybe_start()
- end,
-
- {null, State};
-handle_request({shutdown, _Params}, State) ->
- {null, State};
-handle_request({exit, #{status := Status}}, State) ->
+ els_indexing:maybe_start(),
+ {response, null};
+handle_request({shutdown, _Params}, _State) ->
+ {response, null};
+handle_request({exit, #{status := Status}}, _State) ->
?LOG_INFO("Language server stopping..."),
ExitCode = case Status of
shutdown -> 0;
_ -> 1
end,
els_utils:halt(ExitCode),
- {null, State}.
+ {response, null}.
%%==============================================================================
%% API
@@ -130,12 +105,8 @@ server_capabilities() ->
{ok, Version} = application:get_key(?APP, vsn),
#{ capabilities =>
#{ textDocumentSync =>
- #{ openClose => true
- , change => els_text_synchronization:sync_mode()
- , save => #{includeText => false}
- }
- , hoverProvider =>
- els_hover_provider:is_enabled()
+ els_text_synchronization_provider:options()
+ , hoverProvider => true
, completionProvider =>
#{ resolveProvider => true
, triggerCharacters =>
diff --git a/apps/els_lsp/src/els_hover_provider.erl b/apps/els_lsp/src/els_hover_provider.erl
index b46b90b9c..5d3ebe30c 100644
--- a/apps/els_lsp/src/els_hover_provider.erl
+++ b/apps/els_lsp/src/els_hover_provider.erl
@@ -5,24 +5,16 @@
-behaviour(els_provider).
--export([ handle_info/2
+-export([ is_enabled/0
, handle_request/2
- , is_enabled/0
- , init/0
- , cancel_request/2
]).
-include("els_lsp.hrl").
-include_lib("kernel/include/logger.hrl").
--define(SERVER, ?MODULE).
-
%%==============================================================================
%% Types
%%==============================================================================
--type state() :: #{in_progress => [progress_entry()]}.
--type progress_entry() :: {uri(), job()}.
--type job() :: pid().
%%==============================================================================
%% els_provider functions
@@ -31,13 +23,8 @@
is_enabled() ->
true.
--spec init() -> state().
-init() ->
- #{ in_progress => []}.
-
--spec handle_request(any(), state()) -> {any(), state()}.
-handle_request({hover, Params}, State) ->
- #{in_progress := InProgress} = State,
+-spec handle_request(any(), any()) -> {async, uri(), pid()}.
+handle_request({hover, Params}, _State) ->
#{ <<"position">> := #{ <<"line">> := Line
, <<"character">> := Character
}
@@ -47,23 +34,7 @@ handle_request({hover, Params}, State) ->
, [Uri, Line, Character]
),
Job = run_hover_job(Uri, Line, Character),
- {Job, State#{in_progress => [{Uri, Job}|InProgress]}}.
-
-
--spec handle_info(any(), state()) -> state().
-handle_info({result, HoverResp, Job}, State) ->
- ?LOG_DEBUG("Received hover result [job=~p]", [Job]),
- #{ in_progress := InProgress } = State,
- els_server:send_response(Job, HoverResp),
- State#{ in_progress => lists:keydelete(Job, 2, InProgress) }.
-
--spec cancel_request(job(), state()) -> state().
-cancel_request(Job, State) ->
- ?LOG_DEBUG("Cancelling hover [job=~p]", [Job]),
- els_background_job:stop(Job),
- #{ in_progress := InProgress } = State,
- State#{ in_progress => lists:keydelete(Job, 2, InProgress) }.
-
+ {async, Uri, Job}.
%%==============================================================================
%% Internal Functions
@@ -76,7 +47,7 @@ run_hover_job(Uri, Line, Character) ->
, title => <<"Hover">>
, on_complete =>
fun(HoverResp) ->
- ?SERVER ! {result, HoverResp, self()},
+ els_provider ! {result, HoverResp, self()},
ok
end
},
diff --git a/apps/els_lsp/src/els_implementation_provider.erl b/apps/els_lsp/src/els_implementation_provider.erl
index 0b3234e55..58ff0e944 100644
--- a/apps/els_lsp/src/els_implementation_provider.erl
+++ b/apps/els_lsp/src/els_implementation_provider.erl
@@ -4,21 +4,18 @@
-include("els_lsp.hrl").
--export([ handle_request/2
- , is_enabled/0
+-export([ is_enabled/0
+ , handle_request/2
]).
%%==============================================================================
%% els_provider functions
%%==============================================================================
-
-spec is_enabled() -> boolean().
-is_enabled() ->
- true.
+is_enabled() -> true.
--spec handle_request(tuple(), els_provider:state()) ->
- {[location()], els_provider:state()}.
-handle_request({implementation, Params}, State) ->
+-spec handle_request(tuple(), els_provider:state()) -> {response, [location()]}.
+handle_request({implementation, Params}, _State) ->
#{ <<"position">> := #{ <<"line">> := Line
, <<"character">> := Character
}
@@ -28,7 +25,7 @@ handle_request({implementation, Params}, State) ->
Implementations = find_implementation(Document, Line, Character),
Locations = [#{uri => U, range => els_protocol:range(Range)} ||
{U, #{range := Range}} <- Implementations],
- {Locations, State}.
+ {response, Locations}.
%%==============================================================================
%% Internal functions
diff --git a/apps/els_lsp/src/els_methods.erl b/apps/els_lsp/src/els_methods.erl
index 4716c5cf6..7d3cf1ce5 100644
--- a/apps/els_lsp/src/els_methods.erl
+++ b/apps/els_lsp/src/els_methods.erl
@@ -47,7 +47,7 @@
-type result() :: {response, params() | null, state()}
| {error, params(), state()}
| {noresponse, state()}
- | {noresponse, {els_provider:provider(), pid()}, state()}
+ | {noresponse, pid(), state()}
| {notification, binary(), params(), state()}.
-type request_type() :: notification | request.
@@ -131,7 +131,7 @@ method_to_function_name(Method) ->
initialize(Params, State) ->
Provider = els_general_provider,
Request = {initialize, Params},
- Response = els_provider:handle_request(Provider, Request),
+ {response, Response} = els_provider:handle_request(Provider, Request),
{response, Response, State#{status => initialized}}.
%%==============================================================================
@@ -142,7 +142,7 @@ initialize(Params, State) ->
initialized(Params, State) ->
Provider = els_general_provider,
Request = {initialized, Params},
- _Response = els_provider:handle_request(Provider, Request),
+ {response, _Response} = els_provider:handle_request(Provider, Request),
%% Report to the user the server version
{ok, Version} = application:get_key(?APP, vsn),
?LOG_INFO("initialized: [App=~p] [Version=~p]", [?APP, Version]),
@@ -166,7 +166,7 @@ initialized(Params, State) ->
shutdown(Params, State) ->
Provider = els_general_provider,
Request = {shutdown, Params},
- Response = els_provider:handle_request(Provider, Request),
+ {response, Response} = els_provider:handle_request(Provider, Request),
{response, Response, State#{status => shutdown}}.
%%==============================================================================
@@ -177,7 +177,7 @@ shutdown(Params, State) ->
exit(_Params, State) ->
Provider = els_general_provider,
Request = {exit, #{status => maps:get(status, State, undefined)}},
- _Response = els_provider:handle_request(Provider, Request),
+ {response, _Response} = els_provider:handle_request(Provider, Request),
{noresponse, #{}}.
%%==============================================================================
@@ -186,7 +186,9 @@ exit(_Params, State) ->
-spec textdocument_didopen(params(), state()) -> result().
textdocument_didopen(Params, State) ->
- ok = els_text_synchronization:did_open(Params),
+ Provider = els_text_synchronization_provider,
+ Request = {did_open, Params},
+ noresponse = els_provider:handle_request(Provider, Request),
{noresponse, State}.
%%==============================================================================
@@ -195,7 +197,11 @@ textdocument_didopen(Params, State) ->
-spec textdocument_didchange(params(), state()) -> result().
textdocument_didchange(Params, State) ->
- ok = els_text_synchronization:did_change(Params),
+ #{<<"textDocument">> := #{ <<"uri">> := Uri}} = Params,
+ els_provider:cancel_request_by_uri(Uri),
+ Provider = els_text_synchronization_provider,
+ Request = {did_change, Params},
+ els_provider:handle_request(Provider, Request),
{noresponse, State}.
%%==============================================================================
@@ -204,7 +210,9 @@ textdocument_didchange(Params, State) ->
-spec textdocument_didsave(params(), state()) -> result().
textdocument_didsave(Params, State) ->
- ok = els_text_synchronization:did_save(Params),
+ Provider = els_text_synchronization_provider,
+ Request = {did_save, Params},
+ noresponse = els_provider:handle_request(Provider, Request),
{noresponse, State}.
%%==============================================================================
@@ -213,7 +221,9 @@ textdocument_didsave(Params, State) ->
-spec textdocument_didclose(params(), state()) -> result().
textdocument_didclose(Params, State) ->
- ok = els_text_synchronization:did_close(Params),
+ Provider = els_text_synchronization_provider,
+ Request = {did_close, Params},
+ noresponse = els_provider:handle_request(Provider, Request),
{noresponse, State}.
%%==============================================================================
@@ -224,7 +234,7 @@ textdocument_didclose(Params, State) ->
textdocument_documentsymbol(Params, State) ->
Provider = els_document_symbol_provider,
Request = {document_symbol, Params},
- Response = els_provider:handle_request(Provider, Request),
+ {response, Response} = els_provider:handle_request(Provider, Request),
{response, Response, State}.
%%==============================================================================
@@ -234,8 +244,8 @@ textdocument_documentsymbol(Params, State) ->
-spec textdocument_hover(params(), state()) -> result().
textdocument_hover(Params, State) ->
Provider = els_hover_provider,
- Job = els_provider:handle_request(Provider, {hover, Params}),
- {noresponse, {Provider, Job}, State}.
+ {async, Job} = els_provider:handle_request(Provider, {hover, Params}),
+ {noresponse, Job, State}.
%%==============================================================================
%% textDocument/completion
@@ -244,7 +254,8 @@ textdocument_hover(Params, State) ->
-spec textdocument_completion(params(), state()) -> result().
textdocument_completion(Params, State) ->
Provider = els_completion_provider,
- Response = els_provider:handle_request(Provider, {completion, Params}),
+ {response, Response} =
+ els_provider:handle_request(Provider, {completion, Params}),
{response, Response, State}.
%%==============================================================================
@@ -254,7 +265,8 @@ textdocument_completion(Params, State) ->
-spec completionitem_resolve(params(), state()) -> result().
completionitem_resolve(Params, State) ->
Provider = els_completion_provider,
- Response = els_provider:handle_request(Provider, {resolve, Params}),
+ {response, Response} =
+ els_provider:handle_request(Provider, {resolve, Params}),
{response, Response, State}.
%%==============================================================================
@@ -264,7 +276,8 @@ completionitem_resolve(Params, State) ->
-spec textdocument_definition(params(), state()) -> result().
textdocument_definition(Params, State) ->
Provider = els_definition_provider,
- Response = els_provider:handle_request(Provider, {definition, Params}),
+ {response, Response} =
+ els_provider:handle_request(Provider, {definition, Params}),
{response, Response, State}.
%%==============================================================================
@@ -274,7 +287,8 @@ textdocument_definition(Params, State) ->
-spec textdocument_references(params(), state()) -> result().
textdocument_references(Params, State) ->
Provider = els_references_provider,
- Response = els_provider:handle_request(Provider, {references, Params}),
+ {response, Response} =
+ els_provider:handle_request(Provider, {references, Params}),
{response, Response, State}.
%%==============================================================================
@@ -284,8 +298,8 @@ textdocument_references(Params, State) ->
-spec textdocument_documenthighlight(params(), state()) -> result().
textdocument_documenthighlight(Params, State) ->
Provider = els_document_highlight_provider,
- Response = els_provider:handle_request(Provider,
- {document_highlight, Params}),
+ {response, Response} =
+ els_provider:handle_request(Provider, {document_highlight, Params}),
{response, Response, State}.
%%==============================================================================
@@ -295,8 +309,8 @@ textdocument_documenthighlight(Params, State) ->
-spec textdocument_formatting(params(), state()) -> result().
textdocument_formatting(Params, State) ->
Provider = els_formatting_provider,
- Response = els_provider:handle_request(Provider,
- {document_formatting, Params}),
+ {response, Response} =
+ els_provider:handle_request(Provider, {document_formatting, Params}),
{response, Response, State}.
%%==============================================================================
@@ -306,8 +320,8 @@ textdocument_formatting(Params, State) ->
-spec textdocument_rangeformatting(params(), state()) -> result().
textdocument_rangeformatting(Params, State) ->
Provider = els_formatting_provider,
- Response = els_provider:handle_request(Provider,
- {document_rangeformatting, Params}),
+ {response, Response} =
+ els_provider:handle_request(Provider, {document_rangeformatting, Params}),
{response, Response, State}.
%%==============================================================================
@@ -317,8 +331,8 @@ textdocument_rangeformatting(Params, State) ->
-spec textdocument_ontypeformatting(params(), state()) -> result().
textdocument_ontypeformatting(Params, State) ->
Provider = els_formatting_provider,
- Response = els_provider:handle_request(Provider,
- {document_ontypeformatting, Params}),
+ {response, Response} =
+ els_provider:handle_request(Provider, {document_ontypeformatting, Params}),
{response, Response, State}.
%%==============================================================================
@@ -328,8 +342,8 @@ textdocument_ontypeformatting(Params, State) ->
-spec textdocument_foldingrange(params(), state()) -> result().
textdocument_foldingrange(Params, State) ->
Provider = els_folding_range_provider,
- Response = els_provider:handle_request( Provider
- , {document_foldingrange, Params}),
+ {response, Response} =
+ els_provider:handle_request(Provider, {document_foldingrange, Params}),
{response, Response, State}.
%%==============================================================================
@@ -339,7 +353,8 @@ textdocument_foldingrange(Params, State) ->
-spec textdocument_implementation(params(), state()) -> result().
textdocument_implementation(Params, State) ->
Provider = els_implementation_provider,
- Response = els_provider:handle_request(Provider, {implementation, Params}),
+ {response, Response} =
+ els_provider:handle_request(Provider, {implementation, Params}),
{response, Response, State}.
%%==============================================================================
@@ -359,8 +374,8 @@ workspace_didchangeconfiguration(_Params, State) ->
-spec textdocument_codeaction(params(), state()) -> result().
textdocument_codeaction(Params, State) ->
Provider = els_code_action_provider,
- Response = els_provider:handle_request(Provider,
- {document_codeaction, Params}),
+ {response, Response} =
+ els_provider:handle_request(Provider, {document_codeaction, Params}),
{response, Response, State}.
%%==============================================================================
@@ -370,8 +385,9 @@ textdocument_codeaction(Params, State) ->
-spec textdocument_codelens(params(), state()) -> result().
textdocument_codelens(Params, State) ->
Provider = els_code_lens_provider,
- Job = els_provider:handle_request(Provider, {document_codelens, Params}),
- {noresponse, {Provider, Job}, State}.
+ {async, Job} =
+ els_provider:handle_request(Provider, {document_codelens, Params}),
+ {noresponse, Job, State}.
%%==============================================================================
%% textDocument/rename
@@ -380,7 +396,8 @@ textdocument_codelens(Params, State) ->
-spec textdocument_rename(params(), state()) -> result().
textdocument_rename(Params, State) ->
Provider = els_rename_provider,
- Response = els_provider:handle_request(Provider, {rename, Params}),
+ {response, Response} =
+ els_provider:handle_request(Provider, {rename, Params}),
{response, Response, State}.
%%==============================================================================
@@ -390,7 +407,8 @@ textdocument_rename(Params, State) ->
-spec textdocument_preparecallhierarchy(params(), state()) -> result().
textdocument_preparecallhierarchy(Params, State) ->
Provider = els_call_hierarchy_provider,
- Response = els_provider:handle_request(Provider, {prepare, Params}),
+ {response, Response} =
+ els_provider:handle_request(Provider, {prepare, Params}),
{response, Response, State}.
%%==============================================================================
@@ -400,7 +418,8 @@ textdocument_preparecallhierarchy(Params, State) ->
-spec callhierarchy_incomingcalls(params(), state()) -> result().
callhierarchy_incomingcalls(Params, State) ->
Provider = els_call_hierarchy_provider,
- Response = els_provider:handle_request(Provider, {incoming_calls, Params}),
+ {response, Response} =
+ els_provider:handle_request(Provider, {incoming_calls, Params}),
{response, Response, State}.
%%==============================================================================
@@ -410,7 +429,8 @@ callhierarchy_incomingcalls(Params, State) ->
-spec callhierarchy_outgoingcalls(params(), state()) -> result().
callhierarchy_outgoingcalls(Params, State) ->
Provider = els_call_hierarchy_provider,
- Response = els_provider:handle_request(Provider, {outgoing_calls, Params}),
+ {response, Response} =
+ els_provider:handle_request(Provider, {outgoing_calls, Params}),
{response, Response, State}.
%%==============================================================================
@@ -420,8 +440,8 @@ callhierarchy_outgoingcalls(Params, State) ->
-spec workspace_executecommand(params(), state()) -> result().
workspace_executecommand(Params, State) ->
Provider = els_execute_command_provider,
- Response = els_provider:handle_request(Provider,
- {workspace_executecommand, Params}),
+ {response, Response} =
+ els_provider:handle_request(Provider, {workspace_executecommand, Params}),
{response, Response, State}.
%%==============================================================================
@@ -430,7 +450,9 @@ workspace_executecommand(Params, State) ->
-spec workspace_didchangewatchedfiles(map(), state()) -> result().
workspace_didchangewatchedfiles(Params, State) ->
- ok = els_text_synchronization:did_change_watched_files(Params),
+ Provider = els_text_synchronization_provider,
+ Request = {did_change_watched_files, Params},
+ noresponse = els_provider:handle_request(Provider, Request),
{noresponse, State}.
%%==============================================================================
@@ -440,5 +462,6 @@ workspace_didchangewatchedfiles(Params, State) ->
-spec workspace_symbol(map(), state()) -> result().
workspace_symbol(Params, State) ->
Provider = els_workspace_symbol_provider,
- Response = els_provider:handle_request(Provider, {symbol, Params}),
+ {response, Response} =
+ els_provider:handle_request(Provider, {symbol, Params}),
{response, Response, State}.
diff --git a/apps/els_lsp/src/els_providers_sup.erl b/apps/els_lsp/src/els_providers_sup.erl
deleted file mode 100644
index 80ced1080..000000000
--- a/apps/els_lsp/src/els_providers_sup.erl
+++ /dev/null
@@ -1,59 +0,0 @@
-%%==============================================================================
-%% Providers Supervisor
-%%==============================================================================
--module(els_providers_sup).
-
-%%==============================================================================
-%% Behaviours
-%%==============================================================================
--behaviour(supervisor).
-
-%%==============================================================================
-%% Exports
-%%==============================================================================
-
-%% API
--export([ start_link/0 ]).
-
-%% Supervisor Callbacks
--export([ init/1 ]).
-
-%%==============================================================================
-%% Defines
-%%==============================================================================
--define(SERVER, ?MODULE).
-
-%%==============================================================================
-%% Type Definitions
-%%==============================================================================
--type provider_spec() ::
- #{ id := els_provider:provider()
- , start := {els_provider, start_link, [els_provider:provider()]}
- , shutdown := brutal_kill
- }.
-
-%%==============================================================================
-%% API
-%%==============================================================================
--spec start_link() -> {ok, pid()}.
-start_link() ->
- supervisor:start_link({local, ?SERVER}, ?MODULE, []).
-
-%%==============================================================================
-%% Supervisor callbacks
-%%==============================================================================
--spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
-init([]) ->
- SupFlags = #{ strategy => one_for_one
- , intensity => 5
- , period => 60
- },
- ChildSpecs = [provider_specs(P) || P <- els_provider:enabled_providers()],
- {ok, {SupFlags, ChildSpecs}}.
-
--spec provider_specs(els_provider:provider()) -> provider_spec().
-provider_specs(Provider) ->
- #{ id => Provider
- , start => {els_provider, start_link, [Provider]}
- , shutdown => brutal_kill
- }.
diff --git a/apps/els_lsp/src/els_references_provider.erl b/apps/els_lsp/src/els_references_provider.erl
index 64ae8fee7..a6467e8b0 100644
--- a/apps/els_lsp/src/els_references_provider.erl
+++ b/apps/els_lsp/src/els_references_provider.erl
@@ -2,8 +2,8 @@
-behaviour(els_provider).
--export([ handle_request/2
- , is_enabled/0
+-export([ is_enabled/0
+ , handle_request/2
]).
%% For use in other providers
@@ -20,18 +20,15 @@
%%==============================================================================
%% Types
%%==============================================================================
--type state() :: any().
%%==============================================================================
%% els_provider functions
%%==============================================================================
-
-spec is_enabled() -> boolean().
-is_enabled() ->
- true.
+is_enabled() -> true.
--spec handle_request(any(), state()) -> {[location()] | null, state()}.
-handle_request({references, Params}, State) ->
+-spec handle_request(any(), any()) -> {response, [location()] | null}.
+handle_request({references, Params}, _State) ->
#{ <<"position">> := #{ <<"line">> := Line
, <<"character">> := Character
}
@@ -46,8 +43,8 @@ handle_request({references, Params}, State) ->
[] -> []
end,
case Refs of
- [] -> {null, State};
- Rs -> {Rs, State}
+ [] -> {response, null};
+ Rs -> {response, Rs}
end.
%%==============================================================================
diff --git a/apps/els_lsp/src/els_rename_provider.erl b/apps/els_lsp/src/els_rename_provider.erl
index 535411d54..e647db24d 100644
--- a/apps/els_lsp/src/els_rename_provider.erl
+++ b/apps/els_lsp/src/els_rename_provider.erl
@@ -19,7 +19,6 @@
%%==============================================================================
%% Types
%%==============================================================================
--type state() :: any().
%%==============================================================================
%% els_provider functions
@@ -27,8 +26,8 @@
-spec is_enabled() -> boolean().
is_enabled() -> true.
--spec handle_request(any(), state()) -> {any(), state()}.
-handle_request({rename, Params}, State) ->
+-spec handle_request(any(), any()) -> {response, any()}.
+handle_request({rename, Params}, _State) ->
#{ <<"textDocument">> := #{<<"uri">> := Uri}
, <<"position">> := #{ <<"line">> := Line
, <<"character">> := Character
@@ -38,7 +37,7 @@ handle_request({rename, Params}, State) ->
{ok, Document} = els_utils:lookup_document(Uri),
Elem = els_dt_document:get_element_at_pos(Document, Line + 1, Character + 1),
WorkspaceEdits = workspace_edits(Uri, Elem, NewName),
- {WorkspaceEdits, State}.
+ {response, WorkspaceEdits}.
%%==============================================================================
%% Internal functions
diff --git a/apps/els_lsp/src/els_server.erl b/apps/els_lsp/src/els_server.erl
index fba32d39e..8f9c62dc2 100644
--- a/apps/els_lsp/src/els_server.erl
+++ b/apps/els_lsp/src/els_server.erl
@@ -49,7 +49,7 @@
-record(state, { io_device :: any()
, request_id :: number()
, internal_state :: map()
- , pending :: [{number(), els_provider:provider(), pid()}]
+ , pending :: [{number(), pid()}]
}).
%%==============================================================================
@@ -144,10 +144,9 @@ handle_request(#{ <<"method">> := <<"$/cancelRequest">>
?LOG_DEBUG("Trying to cancel not existing request [params=~p]",
[Params]),
State0;
- {RequestId, Provider, Job} when RequestId =:= Id ->
- ?LOG_DEBUG("[SERVER] Cancelling request [id=~p] [provider=~p] [job=~p]",
- [Id, Provider, Job]),
- els_provider:cancel_request(Provider, Job),
+ {RequestId, Job} when RequestId =:= Id ->
+ ?LOG_DEBUG("[SERVER] Cancelling request [id=~p] [job=~p]", [Id, Job]),
+ els_provider:cancel_request(Job),
State0#state{pending = lists:keydelete(Id, 1, Pending)}
end;
handle_request(#{ <<"method">> := _ReqMethod } = Request
@@ -178,11 +177,11 @@ handle_request(#{ <<"method">> := _ReqMethod } = Request
{noresponse, NewInternalState} ->
?LOG_DEBUG("[SERVER] No response", []),
State0#state{internal_state = NewInternalState};
- {noresponse, {Provider, BackgroundJob}, NewInternalState} ->
+ {noresponse, BackgroundJob, NewInternalState} ->
RequestId = maps:get(<<"id">>, Request),
?LOG_DEBUG("[SERVER] Suspending response [background_job=~p]",
[BackgroundJob]),
- NewPending = [{RequestId, Provider, BackgroundJob}| Pending],
+ NewPending = [{RequestId, BackgroundJob}| Pending],
State0#state{ internal_state = NewInternalState
, pending = NewPending
};
@@ -222,13 +221,13 @@ do_send_request(Method, Params, #state{request_id = RequestId0} = State0) ->
-spec do_send_response(pid(), any(), state()) -> state().
do_send_response(Job, Result, State0) ->
#state{pending = Pending0} = State0,
- case lists:keyfind(Job, 3, Pending0) of
+ case lists:keyfind(Job, 2, Pending0) of
false ->
?LOG_DEBUG(
"[SERVER] Sending delayed response, but no request found [job=~p]",
[Job]),
State0;
- {RequestId, _Provider, J} when J =:= Job ->
+ {RequestId, J} when J =:= Job ->
Response = els_protocol:response(RequestId, Result),
?LOG_DEBUG( "[SERVER] Sending delayed response [job=~p] [response=~p]"
, [Job, Response]
diff --git a/apps/els_lsp/src/els_sup.erl b/apps/els_lsp/src/els_sup.erl
index 8fbe5bec2..1cc2327d2 100644
--- a/apps/els_lsp/src/els_sup.erl
+++ b/apps/els_lsp/src/els_sup.erl
@@ -55,10 +55,6 @@ init([]) ->
, start => {els_db_server, start_link, []}
, shutdown => brutal_kill
}
- , #{ id => els_providers_sup
- , start => {els_providers_sup, start_link, []}
- , type => supervisor
- }
, #{ id => els_background_job_sup
, start => {els_background_job_sup, start_link, []}
, type => supervisor
@@ -70,11 +66,8 @@ init([]) ->
, #{ id => els_snippets_server
, start => {els_snippets_server, start_link, []}
}
- , #{ id => els_bsp_client
- , start => {els_bsp_client, start_link, []}
- }
- , #{ id => els_buffer_sup
- , start => {els_buffer_sup, start_link, []}
+ , #{ id => els_provider
+ , start => {els_provider, start_link, []}
}
, #{ id => els_server
, start => {els_server, start_link, []}
diff --git a/apps/els_lsp/src/els_text_synchronization.erl b/apps/els_lsp/src/els_text_synchronization.erl
index d7a2aea86..d2c061139 100644
--- a/apps/els_lsp/src/els_text_synchronization.erl
+++ b/apps/els_lsp/src/els_text_synchronization.erl
@@ -18,7 +18,7 @@ sync_mode() ->
false -> ?TEXT_DOCUMENT_SYNC_KIND_FULL
end.
--spec did_change(map()) -> ok.
+-spec did_change(map()) -> ok | {ok, pid()}.
did_change(Params) ->
ContentChanges = maps:get(<<"contentChanges">>, Params),
TextDocument = maps:get(<<"textDocument">> , Params),
@@ -27,28 +27,17 @@ did_change(Params) ->
[] ->
ok;
[Change] when not is_map_key(<<"range">>, Change) ->
- %% Full text sync
#{<<"text">> := Text} = Change,
- {Duration, ok} =
- timer:tc(fun() ->
- {ok, Document} = els_utils:lookup_document(Uri),
- els_indexing:deep_index(Document)
- end),
- ?LOG_DEBUG("didChange FULLSYNC [size: ~p] [duration: ~pms]\n",
- [size(Text), Duration div 1000]),
- ok;
+ {ok, Document} = els_utils:lookup_document(Uri),
+ ok = els_dt_document:insert(Document#{text => Text}),
+ background_index(Document#{text => Text});
ContentChanges ->
- %% Incremental sync
?LOG_DEBUG("didChange INCREMENTAL [changes: ~p]", [ContentChanges]),
Edits = [to_edit(Change) || Change <- ContentChanges],
- {Duration, ok} =
- timer:tc(fun() ->
- {ok, #{buffer := Buffer}} = els_utils:lookup_document(Uri),
- els_buffer_server:apply_edits(Buffer, Edits)
- end),
- ?LOG_DEBUG("didChange INCREMENTAL [duration: ~pms]\n",
- [Duration div 1000]),
- ok
+ {ok, #{text := Text0} = Document} = els_utils:lookup_document(Uri),
+ Text = els_text:apply_edits(Text0, Edits),
+ ok = els_dt_document:insert(Document#{text => Text}),
+ background_index(Document#{text => Text})
end.
-spec did_open(map()) -> ok.
@@ -56,18 +45,13 @@ did_open(Params) ->
#{<<"textDocument">> := #{ <<"uri">> := Uri
, <<"text">> := Text}} = Params,
{ok, Document} = els_utils:lookup_document(Uri),
- {ok, Buffer} = els_buffer_server:new(Uri, Text),
- els_dt_document:insert(Document#{buffer => Buffer}),
- Provider = els_diagnostics_provider,
- els_provider:handle_request(Provider, {run_diagnostics, Params}),
+ els_indexing:deep_index(Document#{text => Text}),
ok.
-spec did_save(map()) -> ok.
did_save(Params) ->
#{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
reload_from_disk(Uri),
- Provider = els_diagnostics_provider,
- els_provider:handle_request(Provider, {run_diagnostics, Params}),
ok.
-spec did_change_watched_files(map()) -> ok.
@@ -99,13 +83,16 @@ handle_file_change(Uri, Type) when Type =:= ?FILE_CHANGE_TYPE_DELETED ->
-spec reload_from_disk(uri()) -> ok.
reload_from_disk(Uri) ->
{ok, Text} = file:read_file(els_uri:path(Uri)),
- {ok, #{buffer := OldBuffer} = Document} = els_utils:lookup_document(Uri),
- case OldBuffer of
- undefined ->
- els_indexing:deep_index(Document#{text => Text});
- _ ->
- els_buffer_server:stop(OldBuffer),
- {ok, B} = els_buffer_server:new(Uri, Text),
- els_indexing:deep_index(Document#{text => Text, buffer => B})
- end,
+ {ok, Document} = els_utils:lookup_document(Uri),
+ els_indexing:deep_index(Document#{text => Text}),
ok.
+
+-spec background_index(els_dt_document:item()) -> {ok, pid()}.
+background_index(#{uri := Uri} = Document) ->
+ Config = #{ task => fun (Doc, _State) ->
+ els_indexing:deep_index(Doc)
+ end
+ , entries => [Document]
+ , title => <<"Indexing ", Uri/binary>>
+ },
+ els_background_job:new(Config).
diff --git a/apps/els_lsp/src/els_text_synchronization_provider.erl b/apps/els_lsp/src/els_text_synchronization_provider.erl
new file mode 100644
index 000000000..3a75b8b94
--- /dev/null
+++ b/apps/els_lsp/src/els_text_synchronization_provider.erl
@@ -0,0 +1,45 @@
+-module(els_text_synchronization_provider).
+
+-behaviour(els_provider).
+-export([ handle_request/2
+ , options/0
+ ]).
+
+-include("els_lsp.hrl").
+
+%%==============================================================================
+%% els_provider functions
+%%==============================================================================
+-spec options() -> map().
+options() ->
+ #{ openClose => true
+ , change => els_text_synchronization:sync_mode()
+ , save => #{includeText => false}
+ }.
+
+-spec handle_request(any(), any()) ->
+ {diagnostics, uri(), [pid()]} |
+ noresponse |
+ {async, uri(), pid()}.
+handle_request({did_open, Params}, _State) ->
+ ok = els_text_synchronization:did_open(Params),
+ #{<<"textDocument">> := #{ <<"uri">> := Uri}} = Params,
+ {diagnostics, Uri, els_diagnostics:run_diagnostics(Uri)};
+handle_request({did_change, Params}, _State) ->
+ #{<<"textDocument">> := #{ <<"uri">> := Uri}} = Params,
+ case els_text_synchronization:did_change(Params) of
+ ok ->
+ noresponse;
+ {ok, Job} ->
+ {async, Uri, Job}
+ end;
+handle_request({did_save, Params}, _State) ->
+ ok = els_text_synchronization:did_save(Params),
+ #{<<"textDocument">> := #{ <<"uri">> := Uri}} = Params,
+ {diagnostics, Uri, els_diagnostics:run_diagnostics(Uri)};
+handle_request({did_close, Params}, _State) ->
+ ok = els_text_synchronization:did_close(Params),
+ noresponse;
+handle_request({did_change_watched_files, Params}, _State) ->
+ ok = els_text_synchronization:did_change_watched_files(Params),
+ noresponse.
diff --git a/apps/els_lsp/src/els_workspace_symbol_provider.erl b/apps/els_lsp/src/els_workspace_symbol_provider.erl
index 17bfaf708..ab2f27117 100644
--- a/apps/els_lsp/src/els_workspace_symbol_provider.erl
+++ b/apps/els_lsp/src/els_workspace_symbol_provider.erl
@@ -2,8 +2,8 @@
-behaviour(els_provider).
--export([ handle_request/2
- , is_enabled/0
+-export([ is_enabled/0
+ , handle_request/2
]).
-include("els_lsp.hrl").
@@ -15,18 +15,16 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
-
-spec is_enabled() -> boolean().
-is_enabled() ->
- true.
+is_enabled() -> true.
--spec handle_request(any(), state()) -> {any(), state()}.
-handle_request({symbol, Params}, State) ->
+-spec handle_request(any(), state()) -> {response, any()}.
+handle_request({symbol, Params}, _State) ->
%% TODO: Version 3.15 of the protocol introduces a much nicer way of
%% specifying queries, allowing clients to send the symbol kind.
#{<<"query">> := Query} = Params,
TrimmedQuery = string:trim(Query),
- {modules(TrimmedQuery), State}.
+ {response, modules(TrimmedQuery)}.
%%==============================================================================
%% Internal Functions
diff --git a/apps/els_lsp/test/els_server_SUITE.erl b/apps/els_lsp/test/els_server_SUITE.erl
index 50ab37e8c..776a1cee9 100644
--- a/apps/els_lsp/test/els_server_SUITE.erl
+++ b/apps/els_lsp/test/els_server_SUITE.erl
@@ -103,7 +103,6 @@ wait_until_no_lens_jobs() ->
-spec get_current_lens_jobs() -> [pid()].
get_current_lens_jobs() ->
- #{internal_state := InternalState} =
- sys:get_state(els_code_lens_provider, 30 * 1000),
- #{in_progress := InProgress} = InternalState,
+ State = sys:get_state(els_provider, 30 * 1000),
+ #{in_progress := InProgress} = State,
[Job || {_Uri, Job} <- InProgress].
diff --git a/apps/els_lsp/test/prop_statem.erl b/apps/els_lsp/test/prop_statem.erl
index d1f05d909..6bac2f39f 100644
--- a/apps/els_lsp/test/prop_statem.erl
+++ b/apps/els_lsp/test/prop_statem.erl
@@ -202,6 +202,7 @@ did_open_pre(#{ connected := Connected
Connected andalso InitializedSent.
did_open_next(#{documents := Documents0} = S, _R, [Uri, _, _, _]) ->
+ file:write_file(els_uri:path(Uri), <<"dummy">>),
S#{ documents => Documents0 ++ [Uri]}.
did_open_post(_S, _Args, Res) ->
@@ -222,7 +223,8 @@ did_save_pre(#{ connected := Connected
} = _S) ->
Connected andalso InitializedSent.
-did_save_next(S, _R, _Args) ->
+did_save_next(S, _R, [Uri]) ->
+ file:write_file(els_uri:path(Uri), <<"dummy">>),
S.
did_save_post(_S, _Args, Res) ->
From 252474d4eea82830ea8665eb277e1cdbcc966fae Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Tue, 19 Apr 2022 12:36:03 +0200
Subject: [PATCH 051/239] Revert shutdown strategy for background jobs (#1273)
To have the ability to terminate gracefully
---
apps/els_lsp/src/els_background_job_sup.erl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/els_lsp/src/els_background_job_sup.erl b/apps/els_lsp/src/els_background_job_sup.erl
index 034761ed4..30024608f 100644
--- a/apps/els_lsp/src/els_background_job_sup.erl
+++ b/apps/els_lsp/src/els_background_job_sup.erl
@@ -42,6 +42,6 @@ init([]) ->
ChildSpecs = [#{ id => els_background_job
, start => {els_background_job, start_link, []}
, restart => temporary
- , shutdown => brutal_kill
+ , shutdown => 5000
}],
{ok, {SupFlags, ChildSpecs}}.
From 8ca51705c1ac6596b88c222de04e4c6acfb54d4d Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Wed, 20 Apr 2022 17:50:23 +0200
Subject: [PATCH 052/239] Ensure EPMD is running when launching debugger
(#1276)
---
apps/els_dap/src/els_dap.erl | 2 ++
1 file changed, 2 insertions(+)
diff --git a/apps/els_dap/src/els_dap.erl b/apps/els_dap/src/els_dap.erl
index 024e98de9..86ec65fc0 100644
--- a/apps/els_dap/src/els_dap.erl
+++ b/apps/els_dap/src/els_dap.erl
@@ -25,6 +25,8 @@ main(Args) ->
ok = parse_args(Args),
application:set_env(els_core, server, els_dap_server),
configure_logging(),
+ ?LOG_DEBUG("Ensure EPMD is running", []),
+ 0 = els_utils:cmd("epmd", ["-daemon"]),
{ok, _} = application:ensure_all_started(?APP, permanent),
patch_logging(),
?LOG_INFO("Started Erlang LS - DAP server", []),
From 0f6962e909d9cda444fd96e48347a8a1b8e8a55e Mon Sep 17 00:00:00 2001
From: tks2103
Date: Thu, 21 Apr 2022 02:48:42 -0400
Subject: [PATCH 053/239] Refactor folding_ranges onto POIs, Add support for
records. (#1268)
Co-authored-by: Tanoy Kumar Sinha
---
.../code_navigation/src/folding_ranges.erl | 11 ++
.../src/els_folding_range_provider.erl | 5 +-
apps/els_lsp/src/els_parser.erl | 38 ++++---
.../els_lsp/test/els_call_hierarchy_SUITE.erl | 35 +++++-
apps/els_lsp/test/els_foldingrange_SUITE.erl | 100 +-----------------
5 files changed, 71 insertions(+), 118 deletions(-)
create mode 100644 apps/els_lsp/priv/code_navigation/src/folding_ranges.erl
diff --git a/apps/els_lsp/priv/code_navigation/src/folding_ranges.erl b/apps/els_lsp/priv/code_navigation/src/folding_ranges.erl
new file mode 100644
index 000000000..fdd63d3fb
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/folding_ranges.erl
@@ -0,0 +1,11 @@
+-module(folding_ranges).
+
+function_foldable() ->
+ ?assertEqual(2.0, 4/2).
+
+-record(unfoldable_record, { field_a }).
+
+-record(foldable_record, { field_a
+ , field_b
+ , field_c
+ }).
diff --git a/apps/els_lsp/src/els_folding_range_provider.erl b/apps/els_lsp/src/els_folding_range_provider.erl
index e2741a3de..22540b955 100644
--- a/apps/els_lsp/src/els_folding_range_provider.erl
+++ b/apps/els_lsp/src/els_folding_range_provider.erl
@@ -23,8 +23,9 @@ is_enabled() -> true.
handle_request({document_foldingrange, Params}, _State) ->
#{ <<"textDocument">> := #{<<"uri">> := Uri} } = Params,
{ok, Document} = els_utils:lookup_document(Uri),
- POIs = els_dt_document:pois(Document, [folding_range]),
- Response = case [folding_range(Range) || #{range := Range} <- POIs] of
+ POIs = els_dt_document:pois(Document, [function, record]),
+ Response = case [folding_range(Range)
+ || #{data := #{folding_range := Range = #{}}} <- POIs] of
[] -> null;
Ranges -> Ranges
end,
diff --git a/apps/els_lsp/src/els_parser.erl b/apps/els_lsp/src/els_parser.erl
index 6d5dbb35c..de6ff3d3c 100644
--- a/apps/els_lsp/src/els_parser.erl
+++ b/apps/els_lsp/src/els_parser.erl
@@ -385,9 +385,16 @@ attribute(Tree) ->
-spec record_attribute_pois(tree(), tree(), atom(), tree()) -> [poi()].
record_attribute_pois(Tree, Record, RecordName, Fields) ->
FieldList = record_def_field_name_list(Fields),
- ValueRange = #{ from => get_start_location(Tree),
- to => get_end_location(Tree)},
- Data = #{field_list => FieldList, value_range => ValueRange},
+ {StartLine, StartColumn} = get_start_location(Tree),
+ {EndLine, EndColumn} = get_end_location(Tree),
+ ValueRange = #{ from => {StartLine, StartColumn}
+ , to => {EndLine, EndColumn}
+ },
+ FoldingRange = exceeds_one_line(StartLine, EndLine),
+ Data = #{ field_list => FieldList
+ , value_range => ValueRange
+ , folding_range => FoldingRange
+ },
[poi(erl_syntax:get_pos(Record), record, RecordName, Data)
| record_def_fields(Fields, RecordName)].
@@ -494,27 +501,17 @@ function(Tree) ->
)
|| {I, Clause} <- IndexedClauses,
erl_syntax:type(Clause) =:= clause],
- {StartLine, StartColumn} = StartLocation = get_start_location(Tree),
+ {StartLine, StartColumn} = get_start_location(Tree),
{EndLine, _EndColumn} = get_end_location(Tree),
- %% It only makes sense to fold a function if the function contains
- %% at least one line apart from its signature.
- FoldingRanges = case EndLine > StartLine of
- true ->
- Range = #{ from => {StartLine, ?END_OF_LINE}
- , to => {EndLine, ?END_OF_LINE}
- },
- [ els_poi:new(Range, folding_range, StartLocation) ];
- false ->
- []
- end,
+ FoldingRange = exceeds_one_line(StartLine, EndLine),
FunctionPOI = poi(erl_syntax:get_pos(FunName), function, {F, A},
#{ args => Args
, wrapping_range => #{ from => {StartLine, StartColumn}
, to => {EndLine + 1, 0}
}
+ , folding_range => FoldingRange
}),
lists:append([ [ FunctionPOI ]
- , FoldingRanges
, ClausesPOIs
]).
@@ -1044,6 +1041,15 @@ skip_function_entries(FunList) ->
[FunList]
end.
+%% Helpers for determining valid Folding Ranges
+-spec exceeds_one_line(erl_anno:line(), erl_anno:line()) ->
+ poi_range() | oneliner.
+exceeds_one_line(StartLine, EndLine) when EndLine > StartLine ->
+ #{ from => {StartLine, ?END_OF_LINE}
+ , to => {EndLine, ?END_OF_LINE}
+ };
+exceeds_one_line(_, _) -> oneliner.
+
%% Skip visiting atoms of record names as they are already
%% represented as `record_expr' pois
-spec skip_record_name_atom(tree()) -> [tree()].
diff --git a/apps/els_lsp/test/els_call_hierarchy_SUITE.erl b/apps/els_lsp/test/els_call_hierarchy_SUITE.erl
index b5d5330d3..22a1ca693 100644
--- a/apps/els_lsp/test/els_call_hierarchy_SUITE.erl
+++ b/apps/els_lsp/test/els_call_hierarchy_SUITE.erl
@@ -70,6 +70,9 @@ incoming_calls(Config) ->
, wrapping_range => #{ from => {7, 1}
, to => {17, 0}
}
+ , folding_range => #{ from => {7, ?END_OF_LINE}
+ , to => {16, ?END_OF_LINE}
+ }
}
, id => {function_a, 1}
, kind => function
@@ -99,7 +102,10 @@ incoming_calls(Config) ->
#{ args => [{1, "Arg1"}]
, wrapping_range =>
#{ from => {7, 1}
- , to => {14, 0}}}
+ , to => {14, 0}}
+ , folding_range =>
+ #{ from => {7, ?END_OF_LINE}
+ , to => {13, ?END_OF_LINE}}}
, id => {function_a, 1}
, kind => function
, range => #{from => {7, 1}, to => {7, 11}}}})
@@ -130,6 +136,10 @@ incoming_calls(Config) ->
#{ from => {7, 1}
, to => {17, 0}
}
+ , folding_range =>
+ #{ from => {7, ?END_OF_LINE}
+ , to => {16, ?END_OF_LINE}
+ }
}
, id => {function_a, 1}
, kind => function
@@ -178,6 +188,9 @@ outgoing_calls(Config) ->
, wrapping_range => #{ from => {7, 1}
, to => {17, 0}
}
+ , folding_range => #{ from => {7, ?END_OF_LINE}
+ , to => {16, ?END_OF_LINE}
+ }
}
, id => {function_a, 1}
, kind => function
@@ -205,7 +218,10 @@ outgoing_calls(Config) ->
#{ args => [{1, "Arg1"}]
, wrapping_range => #{ from => {7, 1}
, to => {17, 0}
- }}
+ }
+ , folding_range => #{ from => {7, ?END_OF_LINE}
+ , to => {16, ?END_OF_LINE}
+ }}
, id => {function_a, 1}
, kind => function
, range => #{ from => {7, 1}
@@ -217,7 +233,10 @@ outgoing_calls(Config) ->
#{ args => []
, wrapping_range => #{ from => {18, 1}
, to => {20, 0}
- }}
+ }
+ , folding_range => #{ from => {18, ?END_OF_LINE}
+ , to => {19, ?END_OF_LINE}
+ }}
, id => {function_b, 0}
, kind => function
, range => #{ from => {18, 1}
@@ -229,7 +248,10 @@ outgoing_calls(Config) ->
#{ args => [{1, "Arg1"}]
, wrapping_range => #{ from => {7, 1}
, to => {14, 0}
- }}
+ }
+ , folding_range => #{ from => {7, ?END_OF_LINE}
+ , to => {13, ?END_OF_LINE}
+ }}
, id => {function_a, 1}
, kind => function
, range => #{ from => {7, 1}
@@ -241,7 +263,10 @@ outgoing_calls(Config) ->
#{ args => []
, wrapping_range => #{ from => {18, 1}
, to => {20, 0}
- }}
+ }
+ , folding_range => #{ from => {18, ?END_OF_LINE}
+ , to => {19, ?END_OF_LINE}
+ }}
, id => {function_b, 0}
, kind => function
, range => #{ from => {18, 1}
diff --git a/apps/els_lsp/test/els_foldingrange_SUITE.erl b/apps/els_lsp/test/els_foldingrange_SUITE.erl
index cd0b8bce4..04338423b 100644
--- a/apps/els_lsp/test/els_foldingrange_SUITE.erl
+++ b/apps/els_lsp/test/els_foldingrange_SUITE.erl
@@ -60,106 +60,16 @@ end_per_testcase(TestCase, Config) ->
-spec folding_range(config()) -> ok.
folding_range(Config) ->
#{result := Result} =
- els_client:folding_range(?config(code_navigation_uri, Config)),
+ els_client:folding_range(?config(folding_ranges_uri, Config)),
Expected = [ #{ endCharacter => ?END_OF_LINE
- , endLine => 22
+ , endLine => 3
, startCharacter => ?END_OF_LINE
- , startLine => 20
+ , startLine => 2
}
, #{ endCharacter => ?END_OF_LINE
- , endLine => 25
+ , endLine => 10
, startCharacter => ?END_OF_LINE
- , startLine => 24
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 28
- , startCharacter => ?END_OF_LINE
- , startLine => 27
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 34
- , startCharacter => ?END_OF_LINE
- , startLine => 30
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 39
- , startCharacter => ?END_OF_LINE
- , startLine => 38
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 42
- , startCharacter => ?END_OF_LINE
- , startLine => 41
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 47
- , startCharacter => ?END_OF_LINE
- , startLine => 46
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 52
- , startCharacter => ?END_OF_LINE
- , startLine => 49
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 56
- , startCharacter => ?END_OF_LINE
- , startLine => 55
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 67
- , startCharacter => ?END_OF_LINE
- , startLine => 66
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 75
- , startCharacter => ?END_OF_LINE
- , startLine => 73
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 80
- , startCharacter => ?END_OF_LINE
- , startLine => 78
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 85
- , startCharacter => ?END_OF_LINE
- , startLine => 83
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 89
- , startCharacter => ?END_OF_LINE
- , startLine => 88
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 93
- , startCharacter => ?END_OF_LINE
- , startLine => 92
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 100
- , startCharacter => ?END_OF_LINE
- , startLine => 97
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 107
- , startCharacter => ?END_OF_LINE
- , startLine => 102
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 115
- , startCharacter => ?END_OF_LINE
- , startLine => 113
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 120
- , startCharacter => ?END_OF_LINE
- , startLine => 119
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 123
- , startCharacter => ?END_OF_LINE
- , startLine => 122
+ , startLine => 7
}
],
?assertEqual(Expected, Result),
From c121208606610d9da9375c6e32125c85166028bc Mon Sep 17 00:00:00 2001
From: f2000357 <38951211+f2000357@users.noreply.github.com>
Date: Thu, 21 Apr 2022 02:49:25 -0400
Subject: [PATCH 054/239] Support for adding undefined Function (#1267)
* Support for adding undefined Function
Summary:
This fix supports adding a stub for the undefined function - T112182027
Test Plan:
The commit has a test case in the els_code_action_SUITE
Reviewers:
Roberto Aloi
Subscribers:
Nikita Rubilov
Tasks:
T112182027
Tags:
bootcamp_tasks
* Updated the code to find the undefined function name
Summary:
Test Plan:
Reviewers:
Subscribers:
Tasks:
Tags:
* Fixed Linting errors
Summary:
Test Plan:
Reviewers:
Subscribers:
Tasks:
Tags:
Co-authored-by: Gayathri
---
.../priv/code_navigation/src/code_action.erl | 5 ++-
apps/els_lsp/src/els_code_action_provider.erl | 23 +++++++++++++
apps/els_lsp/test/els_code_action_SUITE.erl | 33 +++++++++++++++++++
3 files changed, 60 insertions(+), 1 deletion(-)
diff --git a/apps/els_lsp/priv/code_navigation/src/code_action.erl b/apps/els_lsp/priv/code_navigation/src/code_action.erl
index 63d247ba8..d6a6f2863 100644
--- a/apps/els_lsp/priv/code_navigation/src/code_action.erl
+++ b/apps/els_lsp/priv/code_navigation/src/code_action.erl
@@ -2,7 +2,7 @@
%% Please only add new cases from bottom, otherwise it might break those tests.
-module(code_action_oops).
--export([function_a/0]).
+-export([function_a/0, function_d/0]).
function_a() ->
A = 123,
@@ -19,3 +19,6 @@ function_c() ->
-define(TIMEOUT, 200).
-include_lib("stdlib/include/assert.hrl").
+function_d() ->
+ e(),
+ ok.
diff --git a/apps/els_lsp/src/els_code_action_provider.erl b/apps/els_lsp/src/els_code_action_provider.erl
index 789637a11..97de91fe4 100644
--- a/apps/els_lsp/src/els_code_action_provider.erl
+++ b/apps/els_lsp/src/els_code_action_provider.erl
@@ -44,6 +44,7 @@ make_code_action(Uri,
, {"Module name '(.*)' does not match file name '(.*)'",
fun action_fix_module_name/4}
, {"Unused macro: (.*)", fun action_remove_macro/4}
+ , {"function (.*) undefined", fun action_create_function/4}
, {"Unused file: (.*)", fun action_remove_unused/4}
], Uri, Range, Data, Message).
@@ -61,6 +62,28 @@ make_code_action([{RE, Fun}|Rest], Uri, Range, Data, Message) ->
end,
Actions ++ make_code_action(Rest, Uri, Range, Data, Message).
+
+
+-spec action_create_function(uri(), range(), binary(), [binary()]) -> [map()].
+action_create_function(Uri, _Range, _Data, [UndefinedFun]) ->
+ {ok, Document} = els_utils:lookup_document(Uri),
+ case els_poi:sort(els_dt_document:pois(Document)) of
+ [] ->
+ [];
+ POIs ->
+ #{range := #{to := {Line, _Col}}} = lists:last(POIs),
+ [FunctionName, _Arity] = string:split(UndefinedFun, "/"),
+ [ make_edit_action( Uri
+ , <<"Add the undefined function ",
+ UndefinedFun/binary>>
+ , ?CODE_ACTION_KIND_QUICKFIX
+ , <<"-spec ", FunctionName/binary, "() -> ok. \n ",
+ FunctionName/binary, "() -> \n \t ok.">>
+ , els_protocol:range(#{from => {Line+1, 1},
+ to => {Line+2, 1}}))]
+ end.
+
+
-spec action_export_function(uri(), range(), binary(), [binary()]) -> [map()].
action_export_function(Uri, _Range, _Data, [UnusedFun]) ->
{ok, Document} = els_utils:lookup_document(Uri),
diff --git a/apps/els_lsp/test/els_code_action_SUITE.erl b/apps/els_lsp/test/els_code_action_SUITE.erl
index f1ea132d9..403ac4aaa 100644
--- a/apps/els_lsp/test/els_code_action_SUITE.erl
+++ b/apps/els_lsp/test/els_code_action_SUITE.erl
@@ -16,6 +16,7 @@
, fix_module_name/1
, remove_unused_macro/1
, remove_unused_import/1
+ , create_undefined_function/1
]).
%%==============================================================================
@@ -225,3 +226,35 @@ remove_unused_import(Config) ->
],
?assertEqual(Expected, Result),
ok.
+
+
+-spec create_undefined_function((config())) -> ok.
+create_undefined_function(Config) ->
+ Uri = ?config(code_action_uri, Config),
+ Range = els_protocol:range(#{from => {?COMMENTS_LINES + 23, 9}
+ , to => {?COMMENTS_LINES + 23, 39}}),
+ LineRange = els_range:line(#{from => {?COMMENTS_LINES + 23, 9}
+ , to => {?COMMENTS_LINES + 23, 39}}),
+ {ok, FileName} = els_utils:find_header(
+ els_utils:filename_to_atom("stdlib/include/assert.hrl")),
+ Diag = #{ message => <<"function e/0 undefined">>
+ , range => Range
+ , severity => 2
+ , source => <<"">>
+ , data => FileName
+ },
+ #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
+ Expected =
+ [ #{ edit => #{changes =>
+ #{ binary_to_atom(Uri, utf8) =>
+ [#{ range => els_protocol:range(LineRange)
+ , newText =>
+ <<"-spec e() -> ok. \n e() -> \n \t ok.">>
+ }]
+ }}
+ , kind => <<"quickfix">>
+ , title => <<"Add the undefined function e/0">>
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
From 8b530cecf4352d5f02b515b35b9edecf4c2162da Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Fri, 22 Apr 2022 17:15:34 +0200
Subject: [PATCH 055/239] Introduce versioning of documents (#1265)
---
apps/els_lsp/src/els_db.erl | 17 ++++
apps/els_lsp/src/els_db_server.erl | 39 ++++++++-
apps/els_lsp/src/els_dt_document.erl | 43 ++++++----
apps/els_lsp/src/els_dt_references.erl | 49 ++++++++++-
apps/els_lsp/src/els_dt_signatures.erl | 50 ++++++++++-
apps/els_lsp/src/els_indexing.erl | 83 ++++++++++++-------
apps/els_lsp/src/els_text_synchronization.erl | 13 +--
7 files changed, 234 insertions(+), 60 deletions(-)
diff --git a/apps/els_lsp/src/els_db.erl b/apps/els_lsp/src/els_db.erl
index 322202dba..07731d98f 100644
--- a/apps/els_lsp/src/els_db.erl
+++ b/apps/els_lsp/src/els_db.erl
@@ -8,10 +8,18 @@
, lookup/2
, match/2
, match_delete/2
+ , select_delete/2
, tables/0
, write/2
+ , conditional_write/4
]).
+%%==============================================================================
+%% Type Definitions
+%%==============================================================================
+-type condition() :: fun((tuple()) -> boolean()).
+-export_type([ condition/0 ]).
+
%%==============================================================================
%% Exported functions
%%==============================================================================
@@ -44,10 +52,19 @@ match(Table, Pattern) when is_tuple(Pattern) ->
match_delete(Table, Pattern) when is_tuple(Pattern) ->
els_db_server:match_delete(Table, Pattern).
+-spec select_delete(atom(), any()) -> ok.
+select_delete(Table, MS) ->
+ els_db_server:select_delete(Table, MS).
+
-spec write(atom(), tuple()) -> ok.
write(Table, Object) when is_tuple(Object) ->
els_db_server:write(Table, Object).
+-spec conditional_write(atom(), any(), tuple(), condition()) ->
+ ok | {error, any()}.
+conditional_write(Table, Key, Object, Condition) when is_tuple(Object) ->
+ els_db_server:conditional_write(Table, Key, Object, Condition).
+
-spec clear_table(atom()) -> ok.
clear_table(Table) ->
els_db_server:clear_table(Table).
diff --git a/apps/els_lsp/src/els_db_server.erl b/apps/els_lsp/src/els_db_server.erl
index ff89b13f6..d5ac8b953 100644
--- a/apps/els_lsp/src/els_db_server.erl
+++ b/apps/els_lsp/src/els_db_server.erl
@@ -2,7 +2,6 @@
%%% @doc The db gen_server.
%%% @end
%%%=============================================================================
-
-module(els_db_server).
%%==============================================================================
@@ -13,9 +12,16 @@
, delete/2
, delete_object/2
, match_delete/2
+ , select_delete/2
, write/2
+ , conditional_write/4
]).
+%%==============================================================================
+%% Includes
+%%==============================================================================
+-include_lib("kernel/include/logger.hrl").
+
%%==============================================================================
%% Callbacks for the gen_server behaviour
%%==============================================================================
@@ -55,10 +61,19 @@ delete_object(Table, Key) ->
match_delete(Table, Pattern) ->
gen_server:call(?SERVER, {match_delete, Table, Pattern}).
+-spec select_delete(atom(), any()) -> ok.
+select_delete(Table, MS) ->
+ gen_server:call(?SERVER, {select_delete, Table, MS}).
+
-spec write(atom(), tuple()) -> ok.
write(Table, Object) ->
gen_server:call(?SERVER, {write, Table, Object}).
+-spec conditional_write(atom(), any(), tuple(), els_db:condition()) ->
+ ok | {error, any()}.
+conditional_write(Table, Key, Object, Condition) ->
+ gen_server:call(?SERVER, {conditional_write, Table, Key, Object, Condition}).
+
%%==============================================================================
%% Callbacks for the gen_server behaviour
%%==============================================================================
@@ -81,9 +96,29 @@ handle_call({delete_object, Table, Key}, _From, State) ->
handle_call({match_delete, Table, Pattern}, _From, State) ->
true = ets:match_delete(Table, Pattern),
{reply, ok, State};
+handle_call({select_delete, Table, MS}, _From, State) ->
+ ets:select_delete(Table, MS),
+ {reply, ok, State};
handle_call({write, Table, Object}, _From, State) ->
true = ets:insert(Table, Object),
- {reply, ok, State}.
+ {reply, ok, State};
+handle_call({conditional_write, Table, Key, Object, Condition}, _From, State) ->
+ case ets:lookup(Table, Key) of
+ [Entry] ->
+ case Condition(Entry) of
+ true ->
+ true = ets:insert(Table, Object),
+ {reply, ok, State};
+ false ->
+ ?LOG_DEBUG("Skip insertion due to invalid condition "
+ "[table=~p] [key=~p]",
+ [Table, Key]),
+ {reply, {error, condition_not_satisfied}, State}
+ end;
+ [] ->
+ true = ets:insert(Table, Object),
+ {reply, ok, State}
+ end.
-spec handle_cast(any(), state()) -> {noreply, state()}.
handle_cast(_Request, State) ->
diff --git a/apps/els_lsp/src/els_dt_document.erl b/apps/els_lsp/src/els_dt_document.erl
index 2fcdf0253..2cc05bc5f 100644
--- a/apps/els_lsp/src/els_dt_document.erl
+++ b/apps/els_lsp/src/els_dt_document.erl
@@ -18,6 +18,7 @@
%%==============================================================================
-export([ insert/1
+ , versioned_insert/1
, lookup/1
, delete/1
]).
@@ -32,6 +33,7 @@
, wrapping_functions/2
, wrapping_functions/3
, find_candidates/1
+ , get_words/1
]).
%%==============================================================================
@@ -46,20 +48,20 @@
-type id() :: atom().
-type kind() :: module | header | other.
-type source() :: otp | app | dep.
+-type version() :: null | integer().
-export_type([source/0]).
%%==============================================================================
%% Item Definition
%%==============================================================================
-
-record(els_dt_document, { uri :: uri() | '_' | '$1'
, id :: id() | '_'
, kind :: kind() | '_'
, text :: binary() | '_'
- , md5 :: binary() | '_'
, pois :: [poi()] | '_' | ondemand
, source :: source() | '$2'
, words :: sets:set() | '_' | '$3'
+ , version :: version() | '_'
}).
-type els_dt_document() :: #els_dt_document{}.
@@ -67,10 +69,10 @@
, id := id()
, kind := kind()
, text := binary()
- , md5 => binary()
, pois => [poi()] | ondemand
, source => source()
, words => sets:set()
+ , version => version()
}.
-export_type([ id/0
, item/0
@@ -97,19 +99,19 @@ from_item(#{ uri := Uri
, id := Id
, kind := Kind
, text := Text
- , md5 := MD5
, pois := POIs
, source := Source
, words := Words
+ , version := Version
}) ->
#els_dt_document{ uri = Uri
, id = Id
, kind = Kind
, text = Text
- , md5 = MD5
, pois = POIs
, source = Source
, words = Words
+ , version = Version
}.
-spec to_item(els_dt_document()) -> item().
@@ -117,19 +119,19 @@ to_item(#els_dt_document{ uri = Uri
, id = Id
, kind = Kind
, text = Text
- , md5 = MD5
, pois = POIs
, source = Source
, words = Words
+ , version = Version
}) ->
#{ uri => Uri
, id => Id
, kind => Kind
, text => Text
- , md5 => MD5
, pois => POIs
, source => Source
, words => Words
+ , version => Version
}.
-spec insert(item()) -> ok | {error, any()}.
@@ -137,6 +139,14 @@ insert(Map) when is_map(Map) ->
Record = from_item(Map),
els_db:write(name(), Record).
+-spec versioned_insert(item()) -> ok | {error, any()}.
+versioned_insert(#{uri := Uri, version := Version} = Map) ->
+ Record = from_item(Map),
+ Condition = fun(#els_dt_document{version = CurrentVersion}) ->
+ CurrentVersion =:= null orelse Version >= CurrentVersion
+ end,
+ els_db:conditional_write(name(), Uri, Record, Condition).
+
-spec lookup(uri()) -> {ok, [item()]}.
lookup(Uri) ->
{ok, Items} = els_db:lookup(name(), Uri),
@@ -150,26 +160,26 @@ delete(Uri) ->
new(Uri, Text, Source) ->
Extension = filename:extension(Uri),
Id = binary_to_atom(filename:basename(Uri, Extension), utf8),
+ Version = null,
case Extension of
<<".erl">> ->
- new(Uri, Text, Id, module, Source);
+ new(Uri, Text, Id, module, Source, Version);
<<".hrl">> ->
- new(Uri, Text, Id, header, Source);
+ new(Uri, Text, Id, header, Source, Version);
_ ->
- new(Uri, Text, Id, other, Source)
+ new(Uri, Text, Id, other, Source, Version)
end.
--spec new(uri(), binary(), atom(), kind(), source()) -> item().
-new(Uri, Text, Id, Kind, Source) ->
- MD5 = erlang:md5(Text),
+-spec new(uri(), binary(), atom(), kind(), source(), version()) -> item().
+new(Uri, Text, Id, Kind, Source, Version) ->
#{ uri => Uri
, id => Id
, kind => Kind
, text => Text
- , md5 => MD5
, pois => ondemand
, source => Source
, words => get_words(Text)
+ , version => Version
}.
%% @doc Returns the list of POIs for the current document
@@ -231,10 +241,11 @@ find_candidates(Pattern) ->
, id = '_'
, kind = '_'
, text = '_'
- , md5 = '_'
, pois = '_'
, source = '$2'
- , words = '$3'},
+ , words = '$3'
+ , version = '_'
+ },
[{'=/=', '$2', otp}],
[{{'$1', '$3'}}]}],
All = ets:select(name(), MS),
diff --git a/apps/els_lsp/src/els_dt_references.erl b/apps/els_lsp/src/els_dt_references.erl
index 52bd98882..3635cdc7d 100644
--- a/apps/els_lsp/src/els_dt_references.erl
+++ b/apps/els_lsp/src/els_dt_references.erl
@@ -18,15 +18,19 @@
%%==============================================================================
-export([ delete_by_uri/1
+ , versioned_delete_by_uri/2
, find_by/1
, find_by_id/2
, insert/2
+ , versioned_insert/2
]).
%%==============================================================================
%% Includes
%%==============================================================================
-include("els_lsp.hrl").
+-include_lib("kernel/include/logger.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
%%==============================================================================
%% Item Definition
@@ -35,12 +39,14 @@
-record(els_dt_references, { id :: any() | '_'
, uri :: uri() | '_'
, range :: poi_range() | '_'
+ , version :: version() | '_'
}).
-type els_dt_references() :: #els_dt_references{}.
-
+-type version() :: null | integer().
-type item() :: #{ id := any()
, uri := uri()
, range := poi_range()
+ , version := version()
}.
-export_type([ item/0 ]).
@@ -69,15 +75,28 @@ opts() ->
%%==============================================================================
-spec from_item(poi_kind(), item()) -> els_dt_references().
-from_item(Kind, #{ id := Id, uri := Uri, range := Range}) ->
+from_item(Kind, #{ id := Id
+ , uri := Uri
+ , range := Range
+ , version := Version
+ }) ->
InternalId = {kind_to_category(Kind), Id},
- #els_dt_references{ id = InternalId, uri = Uri, range = Range}.
+ #els_dt_references{ id = InternalId
+ , uri = Uri
+ , range = Range
+ , version = Version
+ }.
-spec to_item(els_dt_references()) -> item().
-to_item(#els_dt_references{ id = {_Category, Id}, uri = Uri, range = Range }) ->
+to_item(#els_dt_references{ id = {_Category, Id}
+ , uri = Uri
+ , range = Range
+ , version = Version
+ }) ->
#{ id => Id
, uri => Uri
, range => Range
+ , version => Version
}.
-spec delete_by_uri(uri()) -> ok | {error, any()}.
@@ -85,11 +104,33 @@ delete_by_uri(Uri) ->
Pattern = #els_dt_references{uri = Uri, _ = '_'},
ok = els_db:match_delete(name(), Pattern).
+-spec versioned_delete_by_uri(uri(), version()) -> ok.
+versioned_delete_by_uri(Uri, Version) ->
+ MS = ets:fun2ms(fun(#els_dt_references{uri = U, version = CurrentVersion})
+ when U =:= Uri,
+ CurrentVersion =:= null orelse
+ CurrentVersion =< Version
+ ->
+ true;
+
+ (_) ->
+ false
+ end),
+ ok = els_db:select_delete(name(), MS).
+
-spec insert(poi_kind(), item()) -> ok | {error, any()}.
insert(Kind, Map) when is_map(Map) ->
Record = from_item(Kind, Map),
els_db:write(name(), Record).
+-spec versioned_insert(poi_kind(), item()) -> ok | {error, any()}.
+versioned_insert(Kind, #{id := Id, version := Version} = Map) ->
+ Record = from_item(Kind, Map),
+ Condition = fun(#els_dt_references{version = CurrentVersion}) ->
+ CurrentVersion =:= null orelse Version >= CurrentVersion
+ end,
+ els_db:conditional_write(name(), Id, Record, Condition).
+
%% @doc Find by id
-spec find_by_id(poi_kind(), any()) -> {ok, [item()]} | {error, any()}.
find_by_id(Kind, Id) ->
diff --git a/apps/els_lsp/src/els_dt_signatures.erl b/apps/els_lsp/src/els_dt_signatures.erl
index 1a5b20318..1afaa6a92 100644
--- a/apps/els_lsp/src/els_dt_signatures.erl
+++ b/apps/els_lsp/src/els_dt_signatures.erl
@@ -18,8 +18,10 @@
%%==============================================================================
-export([ insert/1
+ , versioned_insert/1
, lookup/1
, delete_by_uri/1
+ , versioned_delete_by_uri/2
]).
%%==============================================================================
@@ -27,6 +29,7 @@
%%==============================================================================
-include("els_lsp.hrl").
-include_lib("kernel/include/logger.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
%%==============================================================================
%% Item Definition
@@ -34,11 +37,13 @@
-record(els_dt_signatures, { mfa :: mfa() | '_' | {atom(), '_', '_'}
, spec :: binary() | '_'
+ , version :: version() | '_'
}).
-type els_dt_signatures() :: #els_dt_signatures{}.
-
+-type version() :: null | integer().
-type item() :: #{ mfa := mfa()
, spec := binary()
+ , version := version()
}.
-export_type([ item/0 ]).
@@ -58,13 +63,23 @@ opts() ->
%%==============================================================================
-spec from_item(item()) -> els_dt_signatures().
-from_item(#{ mfa := MFA, spec := Spec }) ->
- #els_dt_signatures{ mfa = MFA, spec = Spec }.
+from_item(#{ mfa := MFA
+ , spec := Spec
+ , version := Version
+ }) ->
+ #els_dt_signatures{ mfa = MFA
+ , spec = Spec
+ , version = Version
+ }.
-spec to_item(els_dt_signatures()) -> item().
-to_item(#els_dt_signatures{ mfa = MFA, spec = Spec }) ->
+to_item(#els_dt_signatures{ mfa = MFA
+ , spec = Spec
+ , version = Version
+ }) ->
#{ mfa => MFA
, spec => Spec
+ , version => Version
}.
-spec insert(item()) -> ok | {error, any()}.
@@ -72,6 +87,14 @@ insert(Map) when is_map(Map) ->
Record = from_item(Map),
els_db:write(name(), Record).
+-spec versioned_insert(item()) -> ok | {error, any()}.
+versioned_insert(#{mfa := MFA, version := Version} = Map) ->
+ Record = from_item(Map),
+ Condition = fun(#els_dt_signatures{version = CurrentVersion}) ->
+ CurrentVersion =:= null orelse Version >= CurrentVersion
+ end,
+ els_db:conditional_write(name(), MFA, Record, Condition).
+
-spec lookup(mfa()) -> {ok, [item()]}.
lookup({M, _F, _A} = MFA) ->
{ok, _Uris} = els_utils:find_modules(M),
@@ -88,3 +111,22 @@ delete_by_uri(Uri) ->
_ ->
ok
end.
+
+-spec versioned_delete_by_uri(uri(), version()) -> ok.
+versioned_delete_by_uri(Uri, Version) ->
+ case filename:extension(Uri) of
+ <<".erl">> ->
+ Module = els_uri:module(Uri),
+ MS = ets:fun2ms(
+ fun(#els_dt_signatures{mfa = {M, _, _}, version = CurrentVersion})
+ when M =:= Module,
+ CurrentVersion =:= null orelse CurrentVersion < Version
+ ->
+ true;
+ (_) ->
+ false
+ end),
+ ok = els_db:select_delete(name(), MS);
+ _ ->
+ ok
+ end.
diff --git a/apps/els_lsp/src/els_indexing.erl b/apps/els_lsp/src/els_indexing.erl
index dd300ef8a..2ab94896b 100644
--- a/apps/els_lsp/src/els_indexing.erl
+++ b/apps/els_lsp/src/els_indexing.erl
@@ -22,6 +22,7 @@
%%==============================================================================
%% Types
%%==============================================================================
+-type version() :: null | integer().
%%==============================================================================
%% Exported functions
@@ -68,34 +69,49 @@ ensure_deeply_indexed(Uri) ->
-spec deep_index(els_dt_document:item()) -> ok.
deep_index(Document) ->
- #{id := Id, uri := Uri, text := Text, source := Source} = Document,
+ #{ id := Id
+ , uri := Uri
+ , text := Text
+ , source := Source
+ , version := Version
+ } = Document,
{ok, POIs} = els_parser:parse(Text),
- ok = els_dt_document:insert(Document#{pois => POIs}),
- index_signatures(Id, Uri, Text, POIs),
- case Source of
- otp ->
- ok;
- S when S =:= app orelse S =:= dep ->
- index_references(Id, Uri, POIs)
+ Words = els_dt_document:get_words(Text),
+ case els_dt_document:versioned_insert(Document#{ pois => POIs
+ , words => Words}) of
+ ok ->
+ index_signatures(Id, Uri, Text, POIs, Version),
+ case Source of
+ otp ->
+ ok;
+ S when S =:= app orelse S =:= dep ->
+ index_references(Id, Uri, POIs, Version)
+ end;
+ {error, condition_not_satisfied} ->
+ ?LOG_DEBUG("Skip indexing old version [uri=~p]", [Uri]),
+ ok
end.
--spec index_signatures(atom(), uri(), binary(), [poi()]) -> ok.
-index_signatures(Id, Uri, Text, POIs) ->
- ok = els_dt_signatures:delete_by_uri(Uri),
- [index_signature(Id, Text, POI) || #{kind := spec} = POI <- POIs],
+-spec index_signatures(atom(), uri(), binary(), [poi()], version()) -> ok.
+index_signatures(Id, Uri, Text, POIs, Version) ->
+ ok = els_dt_signatures:versioned_delete_by_uri(Uri, Version),
+ [index_signature(Id, Text, POI, Version) || #{kind := spec} = POI <- POIs],
ok.
--spec index_signature(atom(), binary(), poi()) -> ok.
-index_signature(_M, _Text, #{id := undefined}) ->
+-spec index_signature(atom(), binary(), poi(), version()) -> ok.
+index_signature(_M, _Text, #{id := undefined}, _Version) ->
ok;
-index_signature(M, Text, #{id := {F, A}, range := Range}) ->
+index_signature(M, Text, #{id := {F, A}, range := Range}, Version) ->
#{from := From, to := To} = Range,
Spec = els_text:range(Text, From, To),
- els_dt_signatures:insert(#{ mfa => {M, F, A}, spec => Spec}).
+ els_dt_signatures:versioned_insert(#{ mfa => {M, F, A}
+ , spec => Spec
+ , version => Version
+ }).
--spec index_references(atom(), uri(), [poi()]) -> ok.
-index_references(Id, Uri, POIs) ->
- ok = els_dt_references:delete_by_uri(Uri),
+-spec index_references(atom(), uri(), [poi()], version()) -> ok.
+index_references(Id, Uri, POIs, Version) ->
+ ok = els_dt_references:versioned_delete_by_uri(Uri, Version),
ReferenceKinds = [ %% Function
application
, implicit_fun
@@ -108,16 +124,20 @@ index_references(Id, Uri, POIs) ->
%% Type
, type_application
],
- [index_reference(Id, Uri, POI)
+ [index_reference(Id, Uri, POI, Version)
|| #{kind := Kind} = POI <- POIs,
lists:member(Kind, ReferenceKinds)],
ok.
--spec index_reference(atom(), uri(), poi()) -> ok.
-index_reference(M, Uri, #{id := {F, A}} = POI) ->
- index_reference(M, Uri, POI#{id => {M, F, A}});
-index_reference(_M, Uri, #{kind := Kind, id := Id, range := Range}) ->
- els_dt_references:insert(Kind, #{id => Id, uri => Uri, range => Range}).
+-spec index_reference(atom(), uri(), poi(), version()) -> ok.
+index_reference(M, Uri, #{id := {F, A}} = POI, Version) ->
+ index_reference(M, Uri, POI#{id => {M, F, A}}, Version);
+index_reference(_M, Uri, #{kind := Kind, id := Id, range := Range}, Version) ->
+ els_dt_references:versioned_insert(Kind, #{ id => Id
+ , uri => Uri
+ , range => Range
+ , version => Version
+ }).
-spec shallow_index(binary(), els_dt_document:source()) -> {ok, uri()}.
shallow_index(Path, Source) ->
@@ -129,10 +149,15 @@ shallow_index(Path, Source) ->
-spec shallow_index(uri(), binary(), els_dt_document:source()) -> ok.
shallow_index(Uri, Text, Source) ->
Document = els_dt_document:new(Uri, Text, Source),
- ok = els_dt_document:insert(Document),
- #{id := Id, kind := Kind} = Document,
- ModuleItem = els_dt_document_index:new(Id, Uri, Kind),
- ok = els_dt_document_index:insert(ModuleItem).
+ case els_dt_document:versioned_insert(Document) of
+ ok ->
+ #{id := Id, kind := Kind} = Document,
+ ModuleItem = els_dt_document_index:new(Id, Uri, Kind),
+ ok = els_dt_document_index:insert(ModuleItem);
+ {error, condition_not_satisfied} ->
+ ?LOG_DEBUG("Skip indexing old version [uri=~p]", [Uri]),
+ ok
+ end.
-spec maybe_start() -> true | false.
maybe_start() ->
diff --git a/apps/els_lsp/src/els_text_synchronization.erl b/apps/els_lsp/src/els_text_synchronization.erl
index d2c061139..ed0717082 100644
--- a/apps/els_lsp/src/els_text_synchronization.erl
+++ b/apps/els_lsp/src/els_text_synchronization.erl
@@ -23,21 +23,24 @@ did_change(Params) ->
ContentChanges = maps:get(<<"contentChanges">>, Params),
TextDocument = maps:get(<<"textDocument">> , Params),
Uri = maps:get(<<"uri">> , TextDocument),
+ Version = maps:get(<<"version">> , TextDocument),
case ContentChanges of
[] ->
ok;
[Change] when not is_map_key(<<"range">>, Change) ->
#{<<"text">> := Text} = Change,
{ok, Document} = els_utils:lookup_document(Uri),
- ok = els_dt_document:insert(Document#{text => Text}),
- background_index(Document#{text => Text});
- ContentChanges ->
+ NewDocument = Document#{text => Text, version => Version},
+ els_dt_document:insert(NewDocument),
+ background_index(NewDocument);
+ _ ->
?LOG_DEBUG("didChange INCREMENTAL [changes: ~p]", [ContentChanges]),
Edits = [to_edit(Change) || Change <- ContentChanges],
{ok, #{text := Text0} = Document} = els_utils:lookup_document(Uri),
Text = els_text:apply_edits(Text0, Edits),
- ok = els_dt_document:insert(Document#{text => Text}),
- background_index(Document#{text => Text})
+ NewDocument = Document#{text => Text, version => Version},
+ els_dt_document:insert(NewDocument),
+ background_index(NewDocument)
end.
-spec did_open(map()) -> ok.
From abdfa8c9db1546989605907027c04bff74611f13 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Mon, 25 Apr 2022 13:02:49 +0200
Subject: [PATCH 056/239] Do not reload file from disk on save (#1278)
The current version of Erlang LS reloads from disk the file on save.
Since didSave/didChange methods are notifications (i.e. handled asynchronously by the server),
the following race condition could be caused by the sequence:
SAVE -> CHANGE -> SAVE
Especially in presence of auto-save.
While the server is still processing the first SAVE, the version read from disk is the result
of the CHANGE being already applied, so the CHANGE operation could fail as invalid.
There's actually very little meaning in reloading the file from disk during save, since all
changes are sent to the server via CHANGE notifications.
---
apps/els_lsp/src/els_text_synchronization.erl | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/apps/els_lsp/src/els_text_synchronization.erl b/apps/els_lsp/src/els_text_synchronization.erl
index ed0717082..e08afe19d 100644
--- a/apps/els_lsp/src/els_text_synchronization.erl
+++ b/apps/els_lsp/src/els_text_synchronization.erl
@@ -52,9 +52,7 @@ did_open(Params) ->
ok.
-spec did_save(map()) -> ok.
-did_save(Params) ->
- #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
- reload_from_disk(Uri),
+did_save(_Params) ->
ok.
-spec did_change_watched_files(map()) -> ok.
From de0fb7490095d36fb2d7ff8a683e3508a6d65fb4 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Mon, 25 Apr 2022 14:03:28 +0200
Subject: [PATCH 057/239] Use version from didOpen, use text from editor as
source of truth (#1279)
Without this change, the server could read an outdated version of the text from disk and
try to apply changes to it. This was a very common scenario in case a file was already opened
in the editor with pending changes, not yet saved to disk. A reload of the server would cause
the server to incorrectly rely on the version on disk.
The version is also used now, so that eventual background jobs on outdated versions of the code
would abort.
---
apps/els_lsp/src/els_text_synchronization.erl | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/apps/els_lsp/src/els_text_synchronization.erl b/apps/els_lsp/src/els_text_synchronization.erl
index e08afe19d..233233f2b 100644
--- a/apps/els_lsp/src/els_text_synchronization.erl
+++ b/apps/els_lsp/src/els_text_synchronization.erl
@@ -46,9 +46,13 @@ did_change(Params) ->
-spec did_open(map()) -> ok.
did_open(Params) ->
#{<<"textDocument">> := #{ <<"uri">> := Uri
- , <<"text">> := Text}} = Params,
+ , <<"text">> := Text
+ , <<"version">> := Version
+ }} = Params,
{ok, Document} = els_utils:lookup_document(Uri),
- els_indexing:deep_index(Document#{text => Text}),
+ NewDocument = Document#{text => Text, version => Version},
+ els_dt_document:insert(NewDocument),
+ els_indexing:deep_index(NewDocument),
ok.
-spec did_save(map()) -> ok.
From 7b4ada6dcf218be2ffb2f027309f33844b3960f6 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Wed, 27 Apr 2022 17:58:12 +0200
Subject: [PATCH 058/239] Expose errors via stderr (#1281)
---
apps/els_lsp/src/erlang_ls.erl | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/apps/els_lsp/src/erlang_ls.erl b/apps/els_lsp/src/erlang_ls.erl
index 6ce326f07..c8b612d5a 100644
--- a/apps/els_lsp/src/erlang_ls.erl
+++ b/apps/els_lsp/src/erlang_ls.erl
@@ -107,14 +107,21 @@ configure_logging() ->
LogFile = filename:join([log_root(), "server.log"]),
{ok, LoggingLevel} = application:get_env(els_core, log_level),
ok = filelib:ensure_dir(LogFile),
+ [logger:remove_handler(H) || H <- logger:get_handler_ids()],
Handler = #{ config => #{ file => LogFile }
, level => LoggingLevel
, formatter => { logger_formatter
, #{ template => ?LSP_LOG_FORMAT }
}
},
- [logger:remove_handler(H) || H <- logger:get_handler_ids()],
+ StdErrHandler = #{ config => #{ type => standard_error }
+ , level => error
+ , formatter => { logger_formatter
+ , #{ template => ?LSP_LOG_FORMAT }
+ }
+ },
logger:add_handler(els_core_handler, logger_std_h, Handler),
+ logger:add_handler(els_stderr_handler, logger_std_h, StdErrHandler),
logger:set_primary_config(level, LoggingLevel),
ok.
From 4ac4ef1c08591165d55e9f2d569e7df13ab461db Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Thu, 28 Apr 2022 10:16:16 +0200
Subject: [PATCH 059/239] Handle query string in Uri (#1283)
The Uri returned by the `shallow_index` procedure is reconstructed
from the path and could miss the query string originally contained in
the input Uri.
---
apps/els_core/src/els_utils.erl | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/apps/els_core/src/els_utils.erl b/apps/els_core/src/els_utils.erl
index 80b02013b..38026babd 100644
--- a/apps/els_core/src/els_utils.erl
+++ b/apps/els_core/src/els_utils.erl
@@ -144,12 +144,14 @@ find_modules(Id) ->
%% If the module is not in the DB, try to index it.
-spec lookup_document(uri()) ->
{ok, els_dt_document:item()} | {error, any()}.
-lookup_document(Uri) ->
- case els_dt_document:lookup(Uri) of
+lookup_document(Uri0) ->
+ case els_dt_document:lookup(Uri0) of
{ok, [Document]} ->
{ok, Document};
{ok, []} ->
- Path = els_uri:path(Uri),
+ Path = els_uri:path(Uri0),
+ %% The returned Uri could be different from the original input
+ %% (e.g. if the original Uri would contain a query string)
{ok, Uri} = els_indexing:shallow_index(Path, app),
case els_dt_document:lookup(Uri) of
{ok, [Document]} ->
From 2d297441fc6100e8166f4c40c55ba2e3be8a0159 Mon Sep 17 00:00:00 2001
From: Michael Davis
Date: Thu, 28 Apr 2022 08:31:38 -0500
Subject: [PATCH 060/239] [#772] Only complete arguments for snippets. (#1263)
This commit prevents the completion provider from providing arguments
for functions/macros when an editor does not enable snippets. With
snippet support, arguments are useful because they can be tabbed between,
but for an editor which does not implement snippets, the generated
arguments are mostly a hassle: you have to move back to replace the
arguments.
---
apps/els_lsp/src/els_completion_provider.erl | 51 ++++++++++++--------
1 file changed, 32 insertions(+), 19 deletions(-)
diff --git a/apps/els_lsp/src/els_completion_provider.erl b/apps/els_lsp/src/els_completion_provider.erl
index fba81b286..7dbb82368 100644
--- a/apps/els_lsp/src/els_completion_provider.erl
+++ b/apps/els_lsp/src/els_completion_provider.erl
@@ -677,10 +677,16 @@ completion_item(#{kind := Kind, id := {F, A}, data := POIData}, Data, false)
Kind =:= type_definition ->
ArgsNames = maps:get(args, POIData),
Label = io_lib:format("~p/~p", [F, A]),
+ SnippetSupport = snippet_support(),
+ Format =
+ case SnippetSupport of
+ true -> ?INSERT_TEXT_FORMAT_SNIPPET;
+ false -> ?INSERT_TEXT_FORMAT_PLAIN_TEXT
+ end,
#{ label => els_utils:to_binary(Label)
, kind => completion_item_kind(Kind)
- , insertText => snippet_function(F, ArgsNames)
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
+ , insertText => format_function(F, ArgsNames, SnippetSupport)
+ , insertTextFormat => Format
, data => Data
};
completion_item(#{kind := Kind, id := {F, A}}, Data, true)
@@ -699,10 +705,16 @@ completion_item(#{kind := Kind = record, id := Name}, Data, _) ->
};
completion_item(#{kind := Kind = define, id := Name, data := Info}, Data, _) ->
#{args := ArgNames} = Info,
+ SnippetSupport = snippet_support(),
+ Format =
+ case SnippetSupport of
+ true -> ?INSERT_TEXT_FORMAT_SNIPPET;
+ false -> ?INSERT_TEXT_FORMAT_PLAIN_TEXT
+ end,
#{ label => macro_label(Name)
, kind => completion_item_kind(Kind)
- , insertText => snippet_macro(Name, ArgNames)
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
+ , insertText => format_macro(Name, ArgNames, SnippetSupport)
+ , insertTextFormat => Format
, data => Data
}.
@@ -712,29 +724,30 @@ macro_label({Name, Arity}) ->
macro_label(Name) ->
atom_to_binary(Name, utf8).
--spec snippet_function(atom(), [{integer(), string()}]) -> binary().
-snippet_function(Name, Args) ->
- snippet_args(atom_to_label(Name), Args).
+-spec format_function(atom(), [{integer(), string()}], boolean()) -> binary().
+format_function(Name, Args, SnippetSupport) ->
+ format_args(atom_to_label(Name), Args, SnippetSupport).
--spec snippet_macro( atom() | {atom(), non_neg_integer()}
- , [{integer(), string()}]) -> binary().
-snippet_macro({Name0, _Arity}, Args) ->
+-spec format_macro( atom() | {atom(), non_neg_integer()}
+ , [{integer(), string()}]
+ , boolean()) -> binary().
+format_macro({Name0, _Arity}, Args, SnippetSupport) ->
Name = atom_to_binary(Name0, utf8),
- snippet_args(Name, Args);
-snippet_macro(Name, none) ->
+ format_args(Name, Args, SnippetSupport);
+format_macro(Name, none, _SnippetSupport) ->
atom_to_binary(Name, utf8).
--spec snippet_args(binary(), [{integer(), string()}]) -> binary().
-snippet_args(Name, Args0) ->
+-spec format_args(binary(), [{integer(), string()}], boolean()) -> binary().
+format_args(Name, Args0, SnippetSupport) ->
Args =
- case snippet_support() of
+ case SnippetSupport of
false ->
- [A || {_N, A} <- Args0];
+ [];
true ->
- [["${", integer_to_list(N), ":", A, "}"] || {N, A} <- Args0]
+ ArgList = [["${", integer_to_list(N), ":", A, "}"] || {N, A} <- Args0],
+ ["(", string:join(ArgList, ", "), ")"]
end,
- Snippet = [Name, "(", string:join(Args, ", "), ")"],
- els_utils:to_binary(Snippet).
+ els_utils:to_binary([Name | Args]).
-spec snippet_support() -> boolean().
snippet_support() ->
From 77472fb72c78e87a055168801c3e362d5a5ee258 Mon Sep 17 00:00:00 2001
From: Sidharth Kshatriya
Date: Thu, 28 Apr 2022 19:02:17 +0530
Subject: [PATCH 061/239] Allow user to provide custom PREFIX (#1282)
* Allow user to provide custom PREFIX
Example:
```bash
$ PREFIX=~/.local make install
```
* Don't need to pass `-e` switch to `make` any longer
---
Makefile | 2 +-
README.md | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Makefile b/Makefile
index 6f99f95d6..cd0e889ab 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
.PHONY: all
-PREFIX = '/usr/local'
+PREFIX ?= '/usr/local'
all:
@ echo "Building escript..."
diff --git a/README.md b/README.md
index c3d2c3fb6..87a6a31ba 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ To install the produced `erlang_ls` escript in `/usr/local/bin`:
To install to a different directory set the `PREFIX` environment variable:
- PREFIX=/path/to/directory make -e install
+ PREFIX=/path/to/directory make install
## Command-line Arguments
From 25d813a0e4ca8fda39fbca399d93c2e0448fc719 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Mon, 2 May 2022 11:17:08 +0200
Subject: [PATCH 062/239] Include macros, records and type definitions in
document symbols (#1284)
---
.../src/els_document_symbol_provider.erl | 55 ++++++++----
apps/els_lsp/src/els_poi.erl | 1 -
.../test/els_document_symbol_SUITE.erl | 89 +++++++++++++++----
3 files changed, 112 insertions(+), 33 deletions(-)
diff --git a/apps/els_lsp/src/els_document_symbol_provider.erl b/apps/els_lsp/src/els_document_symbol_provider.erl
index 4a35ca18b..44f88e75b 100644
--- a/apps/els_lsp/src/els_document_symbol_provider.erl
+++ b/apps/els_lsp/src/els_document_symbol_provider.erl
@@ -19,26 +19,49 @@ is_enabled() -> true.
-spec handle_request(any(), state()) -> {response, any()}.
handle_request({document_symbol, Params}, _State) ->
#{ <<"textDocument">> := #{ <<"uri">> := Uri}} = Params,
- Functions = functions(Uri),
- case Functions of
+ Symbols = symbols(Uri),
+ case Symbols of
[] -> {response, null};
- _ -> {response, Functions}
+ _ -> {response, Symbols}
end.
%%==============================================================================
%% Internal Functions
%%==============================================================================
--spec functions(uri()) -> [map()].
-functions(Uri) ->
+-spec symbols(uri()) -> [map()].
+symbols(Uri) ->
{ok, Document} = els_utils:lookup_document(Uri),
- POIs = els_dt_document:pois(Document, [function]),
- lists:reverse([ #{ name => function_name(F, A)
- , kind => ?SYMBOLKIND_FUNCTION
- , location => #{ uri => Uri
- , range => els_protocol:range(Range)
- }
- } || #{id := {F, A}, range := Range} <- POIs ]).
-
--spec function_name(atom(), non_neg_integer()) -> binary().
-function_name(F, A) ->
- els_utils:to_binary(io_lib:format("~p/~p", [F, A])).
+ POIs = els_dt_document:pois(Document, [ function
+ , define
+ , record
+ , type_definition
+ ]),
+ lists:reverse([poi_to_symbol(Uri, POI) || POI <- POIs ]).
+
+-spec poi_to_symbol(uri(), poi()) -> symbol_information().
+poi_to_symbol(Uri, POI) ->
+ #{range := Range, kind := Kind, id := Id} = POI,
+ #{ name => symbol_name(Kind, Id)
+ , kind => symbol_kind(Kind)
+ , location => #{ uri => Uri
+ , range => els_protocol:range(Range)
+ }
+ }.
+
+-spec symbol_kind(poi_kind()) -> symbol_kind().
+symbol_kind(function) -> ?SYMBOLKIND_FUNCTION;
+symbol_kind(define) -> ?SYMBOLKIND_CONSTANT;
+symbol_kind(record) -> ?SYMBOLKIND_STRUCT;
+symbol_kind(type_definition) -> ?SYMBOLKIND_TYPE_PARAMETER.
+
+-spec symbol_name(poi_kind(), any()) -> binary().
+symbol_name(function, {F, A}) ->
+ els_utils:to_binary(io_lib:format("~s/~p", [F, A]));
+symbol_name(define, {Name, Arity}) ->
+ els_utils:to_binary(io_lib:format("~s/~p", [Name, Arity]));
+symbol_name(define, Name) when is_atom(Name) ->
+ atom_to_binary(Name, utf8);
+symbol_name(record, Name) when is_atom(Name) ->
+ atom_to_binary(Name, utf8);
+symbol_name(type_definition, {Name, Arity}) ->
+ els_utils:to_binary(io_lib:format("~s/~p", [Name, Arity])).
diff --git a/apps/els_lsp/src/els_poi.erl b/apps/els_lsp/src/els_poi.erl
index a4c2faf88..18018d9f6 100644
--- a/apps/els_lsp/src/els_poi.erl
+++ b/apps/els_lsp/src/els_poi.erl
@@ -35,7 +35,6 @@ new(Range, Kind, Id, Data) ->
, range => Range
}.
-
-spec match_pos([poi()], pos()) -> [poi()].
match_pos(POIs, Pos) ->
[POI || #{range := #{ from := From
diff --git a/apps/els_lsp/test/els_document_symbol_SUITE.erl b/apps/els_lsp/test/els_document_symbol_SUITE.erl
index dbf7028e8..5830c8929 100644
--- a/apps/els_lsp/test/els_document_symbol_SUITE.erl
+++ b/apps/els_lsp/test/els_document_symbol_SUITE.erl
@@ -10,10 +10,9 @@
]).
%% Test cases
--export([ functions/1
+-export([ symbols/1
]).
-
-include("els_lsp.hrl").
%%==============================================================================
@@ -57,21 +56,15 @@ end_per_testcase(TestCase, Config) ->
%%==============================================================================
%% Testcases
%%==============================================================================
--spec functions(config()) -> ok.
-functions(Config) ->
+-spec symbols(config()) -> ok.
+symbols(Config) ->
Uri = ?config(code_navigation_uri, Config),
#{result := Symbols} = els_client:document_symbol(Uri),
- Expected = [ #{ kind => ?SYMBOLKIND_FUNCTION,
- location =>
- #{ range =>
- #{ 'end' => #{character => ToC, line => ToL},
- start => #{character => FromC, line => FromL}
- },
- uri => Uri
- },
- name => Name
- } || {Name, {FromL, FromC}, {ToL, ToC}}
- <- lists:append([functions()])],
+ Expected = lists:append([ expected_functions(Uri)
+ , expected_macros(Uri)
+ , expected_records(Uri)
+ , expected_types(Uri)
+ ]),
?assertEqual(length(Expected), length(Symbols)),
Pairs = lists:zip(lists:sort(Expected), lists:sort(Symbols)),
[?assertEqual(E, S) || {E, S} <- Pairs],
@@ -80,6 +73,54 @@ functions(Config) ->
%%==============================================================================
%% Internal Functions
%%==============================================================================
+expected_functions(Uri) ->
+ [ #{ kind => ?SYMBOLKIND_FUNCTION,
+ location =>
+ #{ range =>
+ #{ 'end' => #{character => ToC, line => ToL},
+ start => #{character => FromC, line => FromL}
+ },
+ uri => Uri
+ },
+ name => Name
+ } || {Name, {FromL, FromC}, {ToL, ToC}} <- lists:append([functions()])].
+
+expected_macros(Uri) ->
+ [ #{ kind => ?SYMBOLKIND_CONSTANT,
+ location =>
+ #{ range =>
+ #{ 'end' => #{character => ToC, line => ToL},
+ start => #{character => FromC, line => FromL}
+ },
+ uri => Uri
+ },
+ name => Name
+ } || {Name, {FromL, FromC}, {ToL, ToC}} <- lists:append([macros()])].
+
+expected_records(Uri) ->
+ [ #{ kind => ?SYMBOLKIND_STRUCT,
+ location =>
+ #{ range =>
+ #{ 'end' => #{character => ToC, line => ToL},
+ start => #{character => FromC, line => FromL}
+ },
+ uri => Uri
+ },
+ name => Name
+ } || {Name, {FromL, FromC}, {ToL, ToC}} <- lists:append([records()])].
+
+expected_types(Uri) ->
+ [ #{ kind => ?SYMBOLKIND_TYPE_PARAMETER,
+ location =>
+ #{ range =>
+ #{ 'end' => #{character => ToC, line => ToL},
+ start => #{character => FromC, line => FromL}
+ },
+ uri => Uri
+ },
+ name => Name
+ } || {Name, {FromL, FromC}, {ToL, ToC}} <- lists:append([types()])].
+
functions() ->
[ {<<"function_a/0">>, {20, 0}, {20, 10}}
, {<<"function_b/0">>, {24, 0}, {24, 10}}
@@ -98,9 +139,25 @@ functions() ->
, {<<"function_m/1">>, {83, 0}, {83, 10}}
, {<<"function_n/0">>, {88, 0}, {88, 10}}
, {<<"function_o/0">>, {92, 0}, {92, 10}}
- , {<<"'PascalCaseFunction'/1">>, {97, 0}, {97, 20}}
+ , {<<"PascalCaseFunction/1">>, {97, 0}, {97, 20}}
, {<<"function_p/1">>, {102, 0}, {102, 10}}
, {<<"function_q/0">>, {113, 0}, {113, 10}}
, {<<"macro_b/2">>, {119, 0}, {119, 7}}
, {<<"function_mb/0">>, {122, 0}, {122, 11}}
].
+
+macros() ->
+ [ {<<"macro_A">>, {44, 8}, {44, 15}}
+ , {<<"MACRO_B">>, {117, 8}, {117, 15}}
+ , {<<"MACRO_A">>, {17, 8}, {17, 15}}
+ , {<<"MACRO_A/1">>, {18, 8}, {18, 15}}
+ ].
+
+records() ->
+ [ {<<"record_a">>, {15, 8}, {15, 16}}
+ , {<<"?MODULE">>, {110, 8}, {110, 15}}
+ ].
+
+types() ->
+ [ {<<"type_a/0">>, {36, 0}, {36, 24}}
+ ].
From 41372533da38753348bdefffc872d5d1a4d8888d Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Mon, 2 May 2022 15:53:41 +0200
Subject: [PATCH 063/239] Get tmp system dir in a portable way during tests
(#1285)
---
apps/els_core/src/els_uri.erl | 9 ++-------
apps/els_core/src/els_utils.erl | 17 ++++++++++++++++-
apps/els_lsp/test/els_proper_gen.erl | 12 +++++++++---
apps/els_lsp/test/prop_statem.erl | 3 ++-
4 files changed, 29 insertions(+), 12 deletions(-)
diff --git a/apps/els_core/src/els_uri.erl b/apps/els_core/src/els_uri.erl
index cabb1eda6..05c7196ab 100644
--- a/apps/els_core/src/els_uri.erl
+++ b/apps/els_core/src/els_uri.erl
@@ -31,7 +31,7 @@ module(Uri) ->
-spec path(uri()) -> path().
path(Uri) ->
- path(Uri, is_windows()).
+ path(Uri, els_utils:is_windows()).
-spec path(uri(), boolean()) -> path().
path(Uri, IsWindows) ->
@@ -57,7 +57,7 @@ path(Uri, IsWindows) ->
-spec uri(path()) -> uri().
uri(Path) ->
[Head | Tail] = filename:split(Path),
- {Host, Path1} = case {is_windows(), Head} of
+ {Host, Path1} = case {els_utils:is_windows(), Head} of
{false, <<"/">>} ->
{<<>>, uri_join(Tail)};
{true, X} when X =:= <<"//">> orelse X =:= <<"\\\\">> ->
@@ -81,11 +81,6 @@ uri(Path) ->
uri_join(List) ->
lists:join(<<"/">>, List).
--spec is_windows() -> boolean().
-is_windows() ->
- {OS, _} = os:type(),
- OS =:= win32.
-
-if(?OTP_RELEASE >= 23).
-spec percent_decode(binary()) -> binary().
percent_decode(Str) ->
diff --git a/apps/els_core/src/els_utils.erl b/apps/els_core/src/els_utils.erl
index 38026babd..4555c7069 100644
--- a/apps/els_core/src/els_utils.erl
+++ b/apps/els_core/src/els_utils.erl
@@ -22,9 +22,10 @@
, base64_decode_term/1
, levenshtein_distance/2
, jaro_distance/2
+ , is_windows/0
+ , system_tmp_dir/0
]).
-
%%==============================================================================
%% Includes
%%==============================================================================
@@ -250,6 +251,20 @@ to_list(X) when is_binary(X) ->
_ -> binary_to_list(X)
end.
+-spec is_windows() -> boolean().
+is_windows() ->
+ {OS, _} = os:type(),
+ OS =:= win32.
+
+-spec system_tmp_dir() -> string().
+system_tmp_dir() ->
+ case is_windows() of
+ true ->
+ os:getenv("TEMP");
+ false ->
+ "/tmp"
+ end.
+
%%==============================================================================
%% Internal functions
%%==============================================================================
diff --git a/apps/els_lsp/test/els_proper_gen.erl b/apps/els_lsp/test/els_proper_gen.erl
index b312fb556..be1e09296 100644
--- a/apps/els_lsp/test/els_proper_gen.erl
+++ b/apps/els_lsp/test/els_proper_gen.erl
@@ -19,17 +19,17 @@
uri() ->
?LET( B
, document()
- , <<"file:///tmp/", B/binary, ".erl">>
+ , els_uri:uri(filename:join([system_tmp_dir(), B ++ ".erl"]))
).
root_uri() ->
- <<"file:///tmp">>.
+ els_uri:uri(system_tmp_dir()).
init_options() ->
#{<<"indexingEnabled">> => false}.
document() ->
- elements([<<"a">>, <<"b">>, <<"c">>]).
+ elements(["a", "b", "c"]).
tokens() ->
?LET( Tokens
@@ -42,3 +42,9 @@ tokens() ->
token() ->
elements(["foo", "Bar", "\"baz\""]).
+
+%%==============================================================================
+%% Internal Functions
+%%==============================================================================
+system_tmp_dir() ->
+ els_utils:to_binary(els_utils:system_tmp_dir()).
diff --git a/apps/els_lsp/test/prop_statem.erl b/apps/els_lsp/test/prop_statem.erl
index 6bac2f39f..a245bca87 100644
--- a/apps/els_lsp/test/prop_statem.erl
+++ b/apps/els_lsp/test/prop_statem.erl
@@ -355,7 +355,8 @@ setup() ->
ServerIo = els_fake_stdio:start(),
ok = application:set_env(els_core, io_device, ServerIo),
application:ensure_all_started(els_lsp),
- file:write_file("/tmp/erlang_ls.config", <<"">>),
+ ConfigFile = filename:join([els_utils:system_tmp_dir(), "erlang_ls.config"]),
+ file:write_file(ConfigFile, <<"">>),
ok.
%%==============================================================================
From 54ade684fcea77a3fefcb585bbb8391bbd07ff49 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Thu, 5 May 2022 10:03:45 +0200
Subject: [PATCH 064/239] Do not crash if module is not available while
fetching specs (#1286)
---
apps/els_core/src/els_utils.erl | 12 +++++++-----
.../priv/code_navigation/src/hover_nonexisting.erl | 6 ++++++
apps/els_lsp/src/els_code_navigation.erl | 9 +++------
apps/els_lsp/test/els_hover_SUITE.erl | 10 ++++++++++
4 files changed, 26 insertions(+), 11 deletions(-)
create mode 100644 apps/els_lsp/priv/code_navigation/src/hover_nonexisting.erl
diff --git a/apps/els_core/src/els_utils.erl b/apps/els_core/src/els_utils.erl
index 4555c7069..53de59039 100644
--- a/apps/els_core/src/els_utils.erl
+++ b/apps/els_core/src/els_utils.erl
@@ -115,17 +115,17 @@ find_header(Id) ->
end.
%% @doc Look for a module in the DB
--spec find_module(atom()) -> {ok, uri()} | {error, any()}.
+-spec find_module(atom()) -> {ok, uri()} | {error, not_found}.
find_module(Id) ->
case find_modules(Id) of
{ok, [Uri | _]} ->
{ok, Uri};
- Else ->
- Else
+ {ok, []} ->
+ {error, not_found}
end.
%% @doc Look for all versions of a module in the DB
--spec find_modules(atom()) -> {ok, [uri()]} | {error, any()}.
+-spec find_modules(atom()) -> {ok, [uri()]}.
find_modules(Id) ->
{ok, Candidates} = els_dt_document_index:lookup(Id),
case [Uri || #{kind := module, uri := Uri} <- Candidates] of
@@ -133,7 +133,9 @@ find_modules(Id) ->
FileName = atom_to_list(Id) ++ ".erl",
case els_indexing:find_and_deeply_index_file(FileName) of
{ok, Uri} -> {ok, [Uri]};
- Error -> Error
+ _Error ->
+ ?LOG_INFO("Finding module failed [filename=~p]", [FileName]),
+ {ok, []}
end;
Uris ->
{ok, prioritize_uris(Uris)}
diff --git a/apps/els_lsp/priv/code_navigation/src/hover_nonexisting.erl b/apps/els_lsp/priv/code_navigation/src/hover_nonexisting.erl
new file mode 100644
index 000000000..780e06404
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/hover_nonexisting.erl
@@ -0,0 +1,6 @@
+-module(hover_nonexisting).
+
+-export([main/0]).
+
+main() ->
+ nonexisting:main().
diff --git a/apps/els_lsp/src/els_code_navigation.erl b/apps/els_lsp/src/els_code_navigation.erl
index b95c53cce..967001ef9 100644
--- a/apps/els_lsp/src/els_code_navigation.erl
+++ b/apps/els_lsp/src/els_code_navigation.erl
@@ -161,10 +161,7 @@ find_in_document([Uri|Uris0], Document, Kind, Data, AlreadyVisited) ->
{ok, U, P} -> {ok, U, P};
{error, not_found} ->
find(lists:usort(include_uris(Document) ++ Uris0), Kind, Data,
- AlreadyVisited);
- {error, Other} ->
- ?LOG_INFO("find_in_document: [uri=~p] [error=~p]", [Uri, Other]),
- {error, not_found}
+ AlreadyVisited)
end;
Definitions ->
{ok, Uri, hd(els_poi:sort(Definitions))}
@@ -188,7 +185,7 @@ beginning() ->
%% @doc check for a match in any of the module imported functions.
-spec maybe_imported(els_dt_document:item(), poi_kind(), any()) ->
- {ok, uri(), poi()} | {error, any()}.
+ {ok, uri(), poi()} | {error, not_found}.
maybe_imported(Document, function, {F, A}) ->
POIs = els_dt_document:pois(Document, [import_entry]),
case [{M, F, A} || #{id := {M, FP, AP}} <- POIs, FP =:= F, AP =:= A] of
@@ -196,7 +193,7 @@ maybe_imported(Document, function, {F, A}) ->
[{M, F, A}|_] ->
case els_utils:find_module(M) of
{ok, Uri0} -> find(Uri0, function, {F, A});
- {error, Error} -> {error, Error}
+ {error, not_found} -> {error, not_found}
end
end;
maybe_imported(_Document, _Kind, _Data) ->
diff --git a/apps/els_lsp/test/els_hover_SUITE.erl b/apps/els_lsp/test/els_hover_SUITE.erl
index c05887d84..9382802a7 100644
--- a/apps/els_lsp/test/els_hover_SUITE.erl
+++ b/apps/els_lsp/test/els_hover_SUITE.erl
@@ -33,6 +33,7 @@
, local_opaque/1
, remote_opaque/1
, nonexisting_type/1
+ , nonexisting_module/1
]).
%%==============================================================================
@@ -369,6 +370,15 @@ nonexisting_type(Config) ->
?assertEqual(Expected, Result),
ok.
+nonexisting_module(Config) ->
+ Uri = ?config(hover_nonexisting_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 6, 12),
+ Expected = #{contents =>
+ #{kind => <<"markdown">>,
+ value => <<"## nonexisting:main/0">>}},
+ ?assertEqual(Expected, Result),
+ ok.
+
has_eep48_edoc() ->
list_to_integer(erlang:system_info(otp_release)) >= 24.
has_eep48(Module) ->
From 95e906ecab4f2824ce55e69e0006c5c5923531a8 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Thu, 5 May 2022 14:33:27 +0200
Subject: [PATCH 065/239] Solidify provider process (#1287)
Since the main supervision strategy is rest_for_one, move the els_provider
process after the els_server one (restarting the els_server process currently
results in a node crash anyway). There is no need to restart the els_server
in case of els_provider failure, especially considering that the provider
process is the most likely to fail.
Also, give the provider a chance to cleanup on restart, by cancelling
pending jobs.
Finally, since it can now happen that, after a restart, the provider
receives messages (results or diagnostics) by background jobs, ensure
that such responses are handled correctly: results are propagated to
the server (no change) while diagnostics are ignored.
---
apps/els_core/src/els_provider.erl | 61 +++++++++++++++++++-----------
apps/els_core/src/els_stdio.erl | 2 +-
apps/els_lsp/src/els_sup.erl | 6 +--
3 files changed, 43 insertions(+), 26 deletions(-)
diff --git a/apps/els_core/src/els_provider.erl b/apps/els_core/src/els_provider.erl
index 307d927ef..51ff7d280 100644
--- a/apps/els_core/src/els_provider.erl
+++ b/apps/els_core/src/els_provider.erl
@@ -13,6 +13,7 @@
, handle_call/3
, handle_cast/2
, handle_info/2
+ , terminate/2
]).
%%==============================================================================
@@ -93,6 +94,9 @@ cancel_request_by_uri(Uri) ->
-spec init(unused) -> {ok, state()}.
init(unused) ->
+ %% Ensure the terminate function is called on shutdown, allowing the
+ %% job to clean up.
+ process_flag(trap_exit, true),
{ok, #{in_progress => [], in_progress_diagnostics => []}}.
-spec handle_call(any(), {pid(), any()}, state()) ->
@@ -150,28 +154,37 @@ handle_info({result, Result, Job}, State) ->
handle_info({diagnostics, Diagnostics, Job}, State) ->
#{in_progress_diagnostics := InProgress} = State,
?LOG_DEBUG("Received diagnostics [job=~p]", [Job]),
- { #{ pending := Jobs
- , diagnostics := OldDiagnostics
- , uri := Uri
- }
- , Rest
- } = find_entry(Job, InProgress),
- NewDiagnostics = Diagnostics ++ OldDiagnostics,
- els_diagnostics_provider:publish(Uri, NewDiagnostics),
- NewState = case lists:delete(Job, Jobs) of
- [] ->
- State#{in_progress_diagnostics => Rest};
- Remaining ->
- State#{in_progress_diagnostics =>
- [#{ pending => Remaining
- , diagnostics => NewDiagnostics
- , uri => Uri
- }|Rest]}
- end,
- {noreply, NewState};
+ case find_entry(Job, InProgress) of
+ {ok, { #{ pending := Jobs
+ , diagnostics := OldDiagnostics
+ , uri := Uri
+ }
+ , Rest
+ }} ->
+ NewDiagnostics = Diagnostics ++ OldDiagnostics,
+ els_diagnostics_provider:publish(Uri, NewDiagnostics),
+ NewState = case lists:delete(Job, Jobs) of
+ [] ->
+ State#{in_progress_diagnostics => Rest};
+ Remaining ->
+ State#{in_progress_diagnostics =>
+ [#{ pending => Remaining
+ , diagnostics => NewDiagnostics
+ , uri => Uri
+ }|Rest]}
+ end,
+ {noreply, NewState};
+ {error, not_found} ->
+ {noreply, State}
+ end;
handle_info(_Request, State) ->
{noreply, State}.
+-spec terminate(any(), state()) -> ok.
+terminate(_Reason, #{in_progress := InProgress}) ->
+ [els_background_job:stop(Job) || {_Uri, Job} <- InProgress],
+ ok.
+
-spec available_providers() -> [provider()].
available_providers() ->
[ els_completion_provider
@@ -198,16 +211,20 @@ available_providers() ->
%% Internal Functions
%%==============================================================================
-spec find_entry(job(), [diagnostic_entry()]) ->
- {diagnostic_entry(), [diagnostic_entry()]}.
+ {ok, {diagnostic_entry(), [diagnostic_entry()]}} |
+ {error, not_found}.
find_entry(Job, InProgress) ->
find_entry(Job, InProgress, []).
-spec find_entry(job(), [diagnostic_entry()], [diagnostic_entry()]) ->
- {diagnostic_entry(), [diagnostic_entry()]}.
+ {ok, {diagnostic_entry(), [diagnostic_entry()]}} |
+ {error, not_found}.
+find_entry(_Job, [], []) ->
+ {error, not_found};
find_entry(Job, [#{pending := Pending} = Entry|Rest], Acc) ->
case lists:member(Job, Pending) of
true ->
- {Entry, Rest ++ Acc};
+ {ok, {Entry, Rest ++ Acc}};
false ->
find_entry(Job, Rest, [Entry|Acc])
end.
diff --git a/apps/els_core/src/els_stdio.erl b/apps/els_core/src/els_stdio.erl
index 50d16207b..b385a7089 100644
--- a/apps/els_core/src/els_stdio.erl
+++ b/apps/els_core/src/els_stdio.erl
@@ -22,7 +22,7 @@ start_listener(Cb) ->
-spec init({function(), atom() | pid()}) -> no_return().
init({Cb, IoDevice}) ->
- ?LOG_INFO("Starting stdio server..."),
+ ?LOG_INFO("Starting stdio server... [io_device=~p]", [IoDevice]),
ok = io:setopts(IoDevice, [binary, {encoding, latin1}]),
{ok, Server} = application:get_env(els_core, server),
ok = Server:set_io_device(IoDevice),
diff --git a/apps/els_lsp/src/els_sup.erl b/apps/els_lsp/src/els_sup.erl
index 1cc2327d2..7dde6399d 100644
--- a/apps/els_lsp/src/els_sup.erl
+++ b/apps/els_lsp/src/els_sup.erl
@@ -66,12 +66,12 @@ init([]) ->
, #{ id => els_snippets_server
, start => {els_snippets_server, start_link, []}
}
- , #{ id => els_provider
- , start => {els_provider, start_link, []}
- }
, #{ id => els_server
, start => {els_server, start_link, []}
}
+ , #{ id => els_provider
+ , start => {els_provider, start_link, []}
+ }
],
{ok, {SupFlags, ChildSpecs}}.
From 4449a636f88cefcd0f09bc026bdbb7f202633a13 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Thu, 5 May 2022 15:50:27 +0200
Subject: [PATCH 066/239] Add debugging for issue #1288 (#1289)
---
.../els_bound_var_in_pattern_diagnostics.erl | 22 ++++++++++++++-----
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/apps/els_lsp/src/els_bound_var_in_pattern_diagnostics.erl b/apps/els_lsp/src/els_bound_var_in_pattern_diagnostics.erl
index 75e303409..eff1ee1d6 100644
--- a/apps/els_lsp/src/els_bound_var_in_pattern_diagnostics.erl
+++ b/apps/els_lsp/src/els_bound_var_in_pattern_diagnostics.erl
@@ -59,12 +59,22 @@ find_vars(Uri) ->
find_vars_in_form(Form) ->
case erl_syntax:type(Form) of
function ->
- AnnotatedForm = erl_syntax_lib:annotate_bindings(Form, []),
- %% There are no bound variables in function heads or guards
- %% so lets descend straight into the bodies
- Clauses = erl_syntax:function_clauses(AnnotatedForm),
- ClauseBodies = lists:map(fun erl_syntax:clause_body/1, Clauses),
- fold_subtrees(ClauseBodies, []);
+ %% #1288: The try catch should allow us to understand the root cause
+ %% of the occasional crashes, which could be due to an incorrect mapping
+ %% between the erlfmt AST and erl_syntax AST
+ try
+ AnnotatedForm = erl_syntax_lib:annotate_bindings(Form, []),
+ %% There are no bound variables in function heads or guards
+ %% so lets descend straight into the bodies
+ Clauses = erl_syntax:function_clauses(AnnotatedForm),
+ ClauseBodies = lists:map(fun erl_syntax:clause_body/1, Clauses),
+ fold_subtrees(ClauseBodies, [])
+ catch C:E:St ->
+ ?LOG_ERROR("Error annotating bindings "
+ "[form=~p] [class=~p] [error=~p] [stacktrace=~p]",
+ [Form, C, E, St]),
+ []
+ end;
_ ->
[]
end.
From 6fad903b0f07cdc1072f8f952287e984c25f6bce Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Tue, 10 May 2022 18:01:49 +0200
Subject: [PATCH 067/239] Avoid race condition around POIs extraction (#1291)
The deep_index function does not store the result of the indexing if a
new version of the document is present in the DB. This could lead to a
race condition where the caller of the ensure_deeply_index function
expects POIs to be expanded but, since the document is re-read from
the DB, they could still be un-expanded.
With this change, the ensure_deeply_index function returns a version
of the document with POIs always expanded, even if they may be
slightly outdated.
---
apps/els_lsp/src/els_dt_document.erl | 3 +--
apps/els_lsp/src/els_indexing.erl | 17 +++++++++--------
apps/els_lsp/src/els_text_synchronization.erl | 3 ++-
apps/els_lsp/test/els_test_utils.erl | 2 +-
4 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/apps/els_lsp/src/els_dt_document.erl b/apps/els_lsp/src/els_dt_document.erl
index 2cc05bc5f..6667caef0 100644
--- a/apps/els_lsp/src/els_dt_document.erl
+++ b/apps/els_lsp/src/els_dt_document.erl
@@ -185,8 +185,7 @@ new(Uri, Text, Id, Kind, Source, Version) ->
%% @doc Returns the list of POIs for the current document
-spec pois(item()) -> [poi()].
pois(#{ uri := Uri, pois := ondemand }) ->
- els_indexing:ensure_deeply_indexed(Uri),
- {ok, #{pois := POIs}} = els_utils:lookup_document(Uri),
+ #{pois := POIs} = els_indexing:ensure_deeply_indexed(Uri),
POIs;
pois(#{ pois := POIs }) ->
POIs.
diff --git a/apps/els_lsp/src/els_indexing.erl b/apps/els_lsp/src/els_indexing.erl
index 2ab94896b..8b2c7a616 100644
--- a/apps/els_lsp/src/els_indexing.erl
+++ b/apps/els_lsp/src/els_indexing.erl
@@ -57,28 +57,28 @@ is_generated_file(Text, Tag) ->
false
end.
--spec ensure_deeply_indexed(uri()) -> ok.
+-spec ensure_deeply_indexed(uri()) -> els_dt_document:item().
ensure_deeply_indexed(Uri) ->
{ok, #{pois := POIs} = Document} = els_utils:lookup_document(Uri),
case POIs of
ondemand ->
deep_index(Document);
_ ->
- ok
+ Document
end.
--spec deep_index(els_dt_document:item()) -> ok.
-deep_index(Document) ->
+-spec deep_index(els_dt_document:item()) -> els_dt_document:item().
+deep_index(Document0) ->
#{ id := Id
, uri := Uri
, text := Text
, source := Source
, version := Version
- } = Document,
+ } = Document0,
{ok, POIs} = els_parser:parse(Text),
Words = els_dt_document:get_words(Text),
- case els_dt_document:versioned_insert(Document#{ pois => POIs
- , words => Words}) of
+ Document = Document0#{pois => POIs, words => Words},
+ case els_dt_document:versioned_insert(Document) of
ok ->
index_signatures(Id, Uri, Text, POIs, Version),
case Source of
@@ -90,7 +90,8 @@ deep_index(Document) ->
{error, condition_not_satisfied} ->
?LOG_DEBUG("Skip indexing old version [uri=~p]", [Uri]),
ok
- end.
+ end,
+ Document.
-spec index_signatures(atom(), uri(), binary(), [poi()], version()) -> ok.
index_signatures(Id, Uri, Text, POIs, Version) ->
diff --git a/apps/els_lsp/src/els_text_synchronization.erl b/apps/els_lsp/src/els_text_synchronization.erl
index 233233f2b..7e2db6b9c 100644
--- a/apps/els_lsp/src/els_text_synchronization.erl
+++ b/apps/els_lsp/src/els_text_synchronization.erl
@@ -95,7 +95,8 @@ reload_from_disk(Uri) ->
-spec background_index(els_dt_document:item()) -> {ok, pid()}.
background_index(#{uri := Uri} = Document) ->
Config = #{ task => fun (Doc, _State) ->
- els_indexing:deep_index(Doc)
+ els_indexing:deep_index(Doc),
+ ok
end
, entries => [Document]
, title => <<"Indexing ", Uri/binary>>
diff --git a/apps/els_lsp/test/els_test_utils.erl b/apps/els_lsp/test/els_test_utils.erl
index a2fe3d11e..bee03ad3f 100644
--- a/apps/els_lsp/test/els_test_utils.erl
+++ b/apps/els_lsp/test/els_test_utils.erl
@@ -131,7 +131,7 @@ includes() ->
-spec index_file(binary()) -> [{atom(), any()}].
index_file(Path) ->
Uri = els_uri:uri(Path),
- ok = els_indexing:ensure_deeply_indexed(Uri),
+ els_indexing:ensure_deeply_indexed(Uri),
{ok, Text} = file:read_file(Path),
ConfigId = config_id(Path),
[ {atoms_append(ConfigId, '_path'), Path}
From 21d86301e50540cce223246d96425dcbfe5dd314 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Tue, 10 May 2022 18:07:16 +0200
Subject: [PATCH 068/239] Refactoring to fix review comments from PR #1212
(#1223)
---
apps/els_lsp/src/els_code_action_provider.erl | 193 +++---------------
apps/els_lsp/src/els_code_actions.erl | 159 +++++++++++++++
2 files changed, 183 insertions(+), 169 deletions(-)
create mode 100644 apps/els_lsp/src/els_code_actions.erl
diff --git a/apps/els_lsp/src/els_code_action_provider.erl b/apps/els_lsp/src/els_code_action_provider.erl
index 97de91fe4..7217543ba 100644
--- a/apps/els_lsp/src/els_code_action_provider.erl
+++ b/apps/els_lsp/src/els_code_action_provider.erl
@@ -31,184 +31,39 @@ handle_request({document_codeaction, Params}, _State) ->
%% @doc Result: `(Command | CodeAction)[] | null'
-spec code_actions(uri(), range(), code_action_context()) -> [map()].
code_actions(Uri, _Range, #{<<"diagnostics">> := Diagnostics}) ->
- lists:flatten([make_code_action(Uri, D) || D <- Diagnostics]).
-
--spec make_code_action(uri(), map()) -> [map()].
-make_code_action(Uri,
- #{<<"message">> := Message, <<"range">> := Range} = D) ->
- Data = maps:get(<<"data">>, D, <<>>),
- make_code_action(
- [ {"function (.*) is unused", fun action_export_function/4}
- , {"variable '(.*)' is unused", fun action_ignore_variable/4}
- , {"variable '(.*)' is unbound", fun action_suggest_variable/4}
+ lists:flatten([make_code_actions(Uri, D) || D <- Diagnostics]).
+
+-spec make_code_actions(uri(), map()) -> [map()].
+make_code_actions(Uri,
+ #{<<"message">> := Message, <<"range">> := Range} = Diagnostic) ->
+ Data = maps:get(<<"data">>, Diagnostic, <<>>),
+ make_code_actions(
+ [ {"function (.*) is unused",
+ fun els_code_actions:export_function/4}
+ , {"variable '(.*)' is unused",
+ fun els_code_actions:ignore_variable/4}
+ , {"variable '(.*)' is unbound",
+ fun els_code_actions:suggest_variable/4}
, {"Module name '(.*)' does not match file name '(.*)'",
- fun action_fix_module_name/4}
- , {"Unused macro: (.*)", fun action_remove_macro/4}
- , {"function (.*) undefined", fun action_create_function/4}
- , {"Unused file: (.*)", fun action_remove_unused/4}
+ fun els_code_actions:fix_module_name/4}
+ , {"Unused macro: (.*)",
+ fun els_code_actions:remove_macro/4}
+ , {"function (.*) undefined",
+ fun els_code_actions:create_function/4}
+ , {"Unused file: (.*)",
+ fun els_code_actions:remove_unused/4}
], Uri, Range, Data, Message).
--spec make_code_action([{string(), Fun}], uri(), range(), binary(), binary())
+-spec make_code_actions([{string(), Fun}], uri(), range(), binary(), binary())
-> [map()]
when Fun :: fun((uri(), range(), binary(), [binary()]) -> [map()]).
-make_code_action([], _Uri, _Range, _Data, _Message) ->
+make_code_actions([], _Uri, _Range, _Data, _Message) ->
[];
-make_code_action([{RE, Fun}|Rest], Uri, Range, Data, Message) ->
+make_code_actions([{RE, Fun}|Rest], Uri, Range, Data, Message) ->
Actions = case re:run(Message, RE, [{capture, all_but_first, binary}]) of
{match, Matches} ->
Fun(Uri, Range, Data, Matches);
nomatch ->
[]
end,
- Actions ++ make_code_action(Rest, Uri, Range, Data, Message).
-
-
-
--spec action_create_function(uri(), range(), binary(), [binary()]) -> [map()].
-action_create_function(Uri, _Range, _Data, [UndefinedFun]) ->
- {ok, Document} = els_utils:lookup_document(Uri),
- case els_poi:sort(els_dt_document:pois(Document)) of
- [] ->
- [];
- POIs ->
- #{range := #{to := {Line, _Col}}} = lists:last(POIs),
- [FunctionName, _Arity] = string:split(UndefinedFun, "/"),
- [ make_edit_action( Uri
- , <<"Add the undefined function ",
- UndefinedFun/binary>>
- , ?CODE_ACTION_KIND_QUICKFIX
- , <<"-spec ", FunctionName/binary, "() -> ok. \n ",
- FunctionName/binary, "() -> \n \t ok.">>
- , els_protocol:range(#{from => {Line+1, 1},
- to => {Line+2, 1}}))]
- end.
-
-
--spec action_export_function(uri(), range(), binary(), [binary()]) -> [map()].
-action_export_function(Uri, _Range, _Data, [UnusedFun]) ->
- {ok, Document} = els_utils:lookup_document(Uri),
- case els_poi:sort(els_dt_document:pois(Document, [module, export])) of
- [] ->
- [];
- POIs ->
- #{range := #{to := {Line, _Col}}} = lists:last(POIs),
- Pos = {Line + 1, 1},
- [ make_edit_action( Uri
- , <<"Export ", UnusedFun/binary>>
- , ?CODE_ACTION_KIND_QUICKFIX
- , <<"-export([", UnusedFun/binary, "]).\n">>
- , els_protocol:range(#{from => Pos, to => Pos})) ]
- end.
-
--spec action_ignore_variable(uri(), range(), binary(), [binary()]) -> [map()].
-action_ignore_variable(Uri, Range, _Data, [UnusedVariable]) ->
- {ok, Document} = els_utils:lookup_document(Uri),
- POIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
- case ensure_range(els_range:to_poi_range(Range), UnusedVariable, POIs) of
- {ok, VarRange} ->
- [ make_edit_action( Uri
- , <<"Add '_' to '", UnusedVariable/binary, "'">>
- , ?CODE_ACTION_KIND_QUICKFIX
- , <<"_", UnusedVariable/binary>>
- , els_protocol:range(VarRange)) ];
- error ->
- []
- end.
-
--spec action_suggest_variable(uri(), range(), binary(), [binary()]) -> [map()].
-action_suggest_variable(Uri, Range, _Data, [Var]) ->
- %% Supply a quickfix to replace an unbound variable with the most similar
- %% variable name in scope.
- {ok, Document} = els_utils:lookup_document(Uri),
- POIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
- case ensure_range(els_range:to_poi_range(Range), Var, POIs) of
- {ok, VarRange} ->
- ScopeRange = els_scope:variable_scope_range(VarRange, Document),
- VarsInScope = [atom_to_binary(Id, utf8) ||
- #{range := R, id := Id} <- POIs,
- els_range:in(R, ScopeRange),
- els_range:compare(R, VarRange)],
- VariableDistances =
- [{els_utils:jaro_distance(V, Var), V} || V <- VarsInScope, V =/= Var],
- [ make_edit_action( Uri
- , <<"Did you mean '", V/binary, "'?">>
- , ?CODE_ACTION_KIND_QUICKFIX
- , V
- , els_protocol:range(VarRange))
- || {Distance, V} <- lists:reverse(lists:usort(VariableDistances)),
- Distance > 0.8];
- error ->
- []
- end.
-
--spec action_fix_module_name(uri(), range(), binary(), [binary()]) -> [map()].
-action_fix_module_name(Uri, Range0, _Data, [ModName, FileName]) ->
- {ok, Document} = els_utils:lookup_document(Uri),
- POIs = els_poi:sort(els_dt_document:pois(Document, [module])),
- case ensure_range(els_range:to_poi_range(Range0), ModName, POIs) of
- {ok, Range} ->
- [ make_edit_action( Uri
- , <<"Change to -module(", FileName/binary, ").">>
- , ?CODE_ACTION_KIND_QUICKFIX
- , FileName
- , els_protocol:range(Range)) ];
- error ->
- []
- end.
-
-- spec action_remove_macro(uri(), range(), binary(), [binary()]) -> [map()].
-action_remove_macro(Uri, Range, _Data, [Macro]) ->
- %% Supply a quickfix to remove the unused Macro
- {ok, Document} = els_utils:lookup_document(Uri),
- POIs = els_poi:sort(els_dt_document:pois(Document, [define])),
- case ensure_range(els_range:to_poi_range(Range), Macro, POIs) of
- {ok, MacroRange} ->
- LineRange = els_range:line(MacroRange),
- [ make_edit_action( Uri
- , <<"Remove unused macro ", Macro/binary, ".">>
- , ?CODE_ACTION_KIND_QUICKFIX
- , <<"">>
- , els_protocol:range(LineRange)) ];
- error ->
- []
- end.
-
--spec action_remove_unused(uri(), range(), binary(), [binary()]) -> [map()].
-action_remove_unused(Uri, _Range0, Data, [Import]) ->
- {ok, Document} = els_utils:lookup_document(Uri),
- case els_range:inclusion_range(Data, Document) of
- {ok, UnusedRange} ->
- LineRange = els_range:line(UnusedRange),
- [ make_edit_action( Uri
- , <<"Remove unused -include_lib(", Import/binary, ").">>
- , ?CODE_ACTION_KIND_QUICKFIX
- , <<>>
- , els_protocol:range(LineRange)) ];
- error ->
- []
- end.
-
--spec ensure_range(poi_range(), binary(), [poi()]) -> {ok, poi_range()} | error.
-ensure_range(#{from := {Line, _}}, SubjectId, POIs) ->
- SubjectAtom = binary_to_atom(SubjectId, utf8),
- Ranges = [R || #{range := R, id := Id} <- POIs,
- els_range:in(R, #{from => {Line, 1}, to => {Line + 1, 1}}),
- Id =:= SubjectAtom],
- case Ranges of
- [] ->
- error;
- [Range|_] ->
- {ok, Range}
- end.
-
--spec make_edit_action(uri(), binary(), binary(), binary(), range())
- -> map().
-make_edit_action(Uri, Title, Kind, Text, Range) ->
- #{ title => Title
- , kind => Kind
- , edit => edit(Uri, Text, Range)
- }.
-
--spec edit(uri(), binary(), range()) -> workspace_edit().
-edit(Uri, Text, Range) ->
- #{changes => #{Uri => [#{newText => Text, range => Range}]}}.
+ Actions ++ make_code_actions(Rest, Uri, Range, Data, Message).
diff --git a/apps/els_lsp/src/els_code_actions.erl b/apps/els_lsp/src/els_code_actions.erl
new file mode 100644
index 000000000..3a7ae1f62
--- /dev/null
+++ b/apps/els_lsp/src/els_code_actions.erl
@@ -0,0 +1,159 @@
+-module(els_code_actions).
+-export([ create_function/4
+ , export_function/4
+ , fix_module_name/4
+ , ignore_variable/4
+ , remove_macro/4
+ , remove_unused/4
+ , suggest_variable/4
+ ]).
+
+-include("els_lsp.hrl").
+
+-spec create_function(uri(), range(), binary(), [binary()]) -> [map()].
+create_function(Uri, _Range, _Data, [UndefinedFun]) ->
+ {ok, Document} = els_utils:lookup_document(Uri),
+ case els_poi:sort(els_dt_document:pois(Document)) of
+ [] ->
+ [];
+ POIs ->
+ #{range := #{to := {Line, _Col}}} = lists:last(POIs),
+ [FunctionName, _Arity] = string:split(UndefinedFun, "/"),
+ [ make_edit_action( Uri
+ , <<"Add the undefined function ",
+ UndefinedFun/binary>>
+ , ?CODE_ACTION_KIND_QUICKFIX
+ , <<"-spec ", FunctionName/binary, "() -> ok. \n ",
+ FunctionName/binary, "() -> \n \t ok.">>
+ , els_protocol:range(#{from => {Line+1, 1},
+ to => {Line+2, 1}}))]
+ end.
+
+-spec export_function(uri(), range(), binary(), [binary()]) -> [map()].
+export_function(Uri, _Range, _Data, [UnusedFun]) ->
+ {ok, Document} = els_utils:lookup_document(Uri),
+ case els_poi:sort(els_dt_document:pois(Document, [module, export])) of
+ [] ->
+ [];
+ POIs ->
+ #{range := #{to := {Line, _Col}}} = lists:last(POIs),
+ Pos = {Line + 1, 1},
+ [ make_edit_action( Uri
+ , <<"Export ", UnusedFun/binary>>
+ , ?CODE_ACTION_KIND_QUICKFIX
+ , <<"-export([", UnusedFun/binary, "]).\n">>
+ , els_protocol:range(#{from => Pos, to => Pos})) ]
+ end.
+
+-spec ignore_variable(uri(), range(), binary(), [binary()]) -> [map()].
+ignore_variable(Uri, Range, _Data, [UnusedVariable]) ->
+ {ok, Document} = els_utils:lookup_document(Uri),
+ POIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
+ case ensure_range(els_range:to_poi_range(Range), UnusedVariable, POIs) of
+ {ok, VarRange} ->
+ [ make_edit_action( Uri
+ , <<"Add '_' to '", UnusedVariable/binary, "'">>
+ , ?CODE_ACTION_KIND_QUICKFIX
+ , <<"_", UnusedVariable/binary>>
+ , els_protocol:range(VarRange)) ];
+ error ->
+ []
+ end.
+
+-spec suggest_variable(uri(), range(), binary(), [binary()]) -> [map()].
+suggest_variable(Uri, Range, _Data, [Var]) ->
+ %% Supply a quickfix to replace an unbound variable with the most similar
+ %% variable name in scope.
+ {ok, Document} = els_utils:lookup_document(Uri),
+ POIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
+ case ensure_range(els_range:to_poi_range(Range), Var, POIs) of
+ {ok, VarRange} ->
+ ScopeRange = els_scope:variable_scope_range(VarRange, Document),
+ VarsInScope = [atom_to_binary(Id, utf8) ||
+ #{range := R, id := Id} <- POIs,
+ els_range:in(R, ScopeRange),
+ els_range:compare(R, VarRange)],
+ VariableDistances =
+ [{els_utils:jaro_distance(V, Var), V} || V <- VarsInScope, V =/= Var],
+ [ make_edit_action( Uri
+ , <<"Did you mean '", V/binary, "'?">>
+ , ?CODE_ACTION_KIND_QUICKFIX
+ , V
+ , els_protocol:range(VarRange))
+ || {Distance, V} <- lists:reverse(lists:usort(VariableDistances)),
+ Distance > 0.8];
+ error ->
+ []
+ end.
+
+-spec fix_module_name(uri(), range(), binary(), [binary()]) -> [map()].
+fix_module_name(Uri, Range0, _Data, [ModName, FileName]) ->
+ {ok, Document} = els_utils:lookup_document(Uri),
+ POIs = els_poi:sort(els_dt_document:pois(Document, [module])),
+ case ensure_range(els_range:to_poi_range(Range0), ModName, POIs) of
+ {ok, Range} ->
+ [ make_edit_action( Uri
+ , <<"Change to -module(", FileName/binary, ").">>
+ , ?CODE_ACTION_KIND_QUICKFIX
+ , FileName
+ , els_protocol:range(Range)) ];
+ error ->
+ []
+ end.
+
+-spec remove_macro(uri(), range(), binary(), [binary()]) -> [map()].
+remove_macro(Uri, Range, _Data, [Macro]) ->
+ %% Supply a quickfix to remove the unused Macro
+ {ok, Document} = els_utils:lookup_document(Uri),
+ POIs = els_poi:sort(els_dt_document:pois(Document, [define])),
+ case ensure_range(els_range:to_poi_range(Range), Macro, POIs) of
+ {ok, MacroRange} ->
+ LineRange = els_range:line(MacroRange),
+ [ make_edit_action( Uri
+ , <<"Remove unused macro ", Macro/binary, ".">>
+ , ?CODE_ACTION_KIND_QUICKFIX
+ , <<"">>
+ , els_protocol:range(LineRange)) ];
+ error ->
+ []
+ end.
+
+-spec remove_unused(uri(), range(), binary(), [binary()]) -> [map()].
+remove_unused(Uri, _Range0, Data, [Import]) ->
+ {ok, Document} = els_utils:lookup_document(Uri),
+ case els_range:inclusion_range(Data, Document) of
+ {ok, UnusedRange} ->
+ LineRange = els_range:line(UnusedRange),
+ [ make_edit_action( Uri
+ , <<"Remove unused -include_lib(", Import/binary, ").">>
+ , ?CODE_ACTION_KIND_QUICKFIX
+ , <<>>
+ , els_protocol:range(LineRange)) ];
+ error ->
+ []
+ end.
+
+-spec ensure_range(poi_range(), binary(), [poi()]) -> {ok, poi_range()} | error.
+ensure_range(#{from := {Line, _}}, SubjectId, POIs) ->
+ SubjectAtom = binary_to_atom(SubjectId, utf8),
+ Ranges = [R || #{range := R, id := Id} <- POIs,
+ els_range:in(R, #{from => {Line, 1}, to => {Line + 1, 1}}),
+ Id =:= SubjectAtom],
+ case Ranges of
+ [] ->
+ error;
+ [Range|_] ->
+ {ok, Range}
+ end.
+
+-spec make_edit_action(uri(), binary(), binary(), binary(), range())
+ -> map().
+make_edit_action(Uri, Title, Kind, Text, Range) ->
+ #{ title => Title
+ , kind => Kind
+ , edit => edit(Uri, Text, Range)
+ }.
+
+-spec edit(uri(), binary(), range()) -> workspace_edit().
+edit(Uri, Text, Range) ->
+ #{changes => #{Uri => [#{newText => Text, range => Range}]}}.
From cfab726faee04dc153f864da799ad8cb1cf53c71 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Tue, 10 May 2022 19:11:12 +0200
Subject: [PATCH 069/239] Track open files (#1292)
* Track open files
It can happen that some files are opened in the editor. If these files
have pending changes (unsaved), we should consider them as the "source
of truth" when it comes to text edits flowing from the IDE to the
language server.
In case of an external operation (e.g. a checkout or rebase in a
version control system), Erlang LS inconditionally reloads changed
files from disk. This is an incorrect behaviour, since subsequent
operations applied via the IDE to a non up-to-date version of the
buffer can result in errors. The language server should ignore such
external operations when the file is opened in the editor.
Eventual inconsistencies between the disk-copy and the IDE-copy of the
file will be resolved in the context of the IDE (e.g. in VS Code the
user is warned about the situation on save) and text edits from
the IDE can always be trusted.
---
apps/els_core/src/els_provider.erl | 7 ++-
apps/els_lsp/src/els_methods.erl | 17 +++++--
apps/els_lsp/src/els_server.erl | 2 +-
apps/els_lsp/test/els_references_SUITE.erl | 54 +++++++++++++++++++++-
4 files changed, 71 insertions(+), 9 deletions(-)
diff --git a/apps/els_core/src/els_provider.erl b/apps/els_core/src/els_provider.erl
index 51ff7d280..013b73009 100644
--- a/apps/els_core/src/els_provider.erl
+++ b/apps/els_core/src/els_provider.erl
@@ -48,7 +48,9 @@
-type request() :: {atom() | binary(), map()}.
-type state() :: #{ in_progress := [progress_entry()]
, in_progress_diagnostics := [diagnostic_entry()]
+ , open_buffers := sets:set(buffer())
}.
+-type buffer() :: uri().
-type progress_entry() :: {uri(), job()}.
-type diagnostic_entry() :: #{ uri := uri()
, pending := [job()]
@@ -97,7 +99,10 @@ init(unused) ->
%% Ensure the terminate function is called on shutdown, allowing the
%% job to clean up.
process_flag(trap_exit, true),
- {ok, #{in_progress => [], in_progress_diagnostics => []}}.
+ {ok, #{ in_progress => []
+ , in_progress_diagnostics => []
+ , open_buffers => sets:new()
+ }}.
-spec handle_call(any(), {pid(), any()}, state()) ->
{reply, any(), state()}.
diff --git a/apps/els_lsp/src/els_methods.erl b/apps/els_lsp/src/els_methods.erl
index 7d3cf1ce5..a0549f78d 100644
--- a/apps/els_lsp/src/els_methods.erl
+++ b/apps/els_lsp/src/els_methods.erl
@@ -185,11 +185,12 @@ exit(_Params, State) ->
%%==============================================================================
-spec textdocument_didopen(params(), state()) -> result().
-textdocument_didopen(Params, State) ->
+textdocument_didopen(Params, #{open_buffers := OpenBuffers} = State) ->
+ #{<<"textDocument">> := #{ <<"uri">> := Uri}} = Params,
Provider = els_text_synchronization_provider,
Request = {did_open, Params},
noresponse = els_provider:handle_request(Provider, Request),
- {noresponse, State}.
+ {noresponse, State#{open_buffers => sets:add_element(Uri, OpenBuffers)}}.
%%==============================================================================
%% textDocument/didchange
@@ -220,11 +221,12 @@ textdocument_didsave(Params, State) ->
%%==============================================================================
-spec textdocument_didclose(params(), state()) -> result().
-textdocument_didclose(Params, State) ->
+textdocument_didclose(Params, #{open_buffers := OpenBuffers} = State) ->
+ #{<<"textDocument">> := #{ <<"uri">> := Uri}} = Params,
Provider = els_text_synchronization_provider,
Request = {did_close, Params},
noresponse = els_provider:handle_request(Provider, Request),
- {noresponse, State}.
+ {noresponse, State#{open_buffers => sets:del_element(Uri, OpenBuffers)}}.
%%==============================================================================
%% textdocument/documentSymbol
@@ -449,7 +451,12 @@ workspace_executecommand(Params, State) ->
%%==============================================================================
-spec workspace_didchangewatchedfiles(map(), state()) -> result().
-workspace_didchangewatchedfiles(Params, State) ->
+workspace_didchangewatchedfiles(Params0, State) ->
+ #{open_buffers := OpenBuffers} = State,
+ #{<<"changes">> := Changes0} = Params0,
+ Changes = [C || #{<<"uri">> := Uri} = C <- Changes0,
+ not sets:is_element(Uri, OpenBuffers)],
+ Params = Params0#{<<"changes">> => Changes},
Provider = els_text_synchronization_provider,
Request = {did_change_watched_files, Params},
noresponse = els_provider:handle_request(Provider, Request),
diff --git a/apps/els_lsp/src/els_server.erl b/apps/els_lsp/src/els_server.erl
index 8f9c62dc2..56dac063a 100644
--- a/apps/els_lsp/src/els_server.erl
+++ b/apps/els_lsp/src/els_server.erl
@@ -103,7 +103,7 @@ reset_internal_state() ->
init([]) ->
?LOG_INFO("Starting els_server..."),
State = #state{ request_id = 0
- , internal_state = #{}
+ , internal_state = #{open_buffers => sets:new()}
, pending = []
},
{ok, State}.
diff --git a/apps/els_lsp/test/els_references_SUITE.erl b/apps/els_lsp/test/els_references_SUITE.erl
index 0e5e72c09..87e303a51 100644
--- a/apps/els_lsp/test/els_references_SUITE.erl
+++ b/apps/els_lsp/test/els_references_SUITE.erl
@@ -33,6 +33,7 @@
, refresh_after_watched_file_deleted/1
, refresh_after_watched_file_changed/1
, refresh_after_watched_file_added/1
+ , ignore_open_watched_file_added/1
]).
%%==============================================================================
@@ -85,7 +86,8 @@ end_per_testcase(TestCase, Config)
ok = file:write_file(PathB, ?config(old_content, Config)),
els_test_utils:end_per_testcase(TestCase, Config);
end_per_testcase(TestCase, Config)
- when TestCase =:= refresh_after_watched_file_added ->
+ when TestCase =:= refresh_after_watched_file_added;
+ TestCase =:= ignore_open_watched_file_added ->
PathB = ?config(watched_file_b_path, Config),
ok = file:delete(filename:join(filename:dirname(PathB),
"watched_file_c.erl")),
@@ -561,13 +563,61 @@ refresh_after_watched_file_added(Config) ->
assert_locations(LocationsAfter, ExpectedLocationsAfter),
ok.
+-spec ignore_open_watched_file_added(config()) -> ok.
+ignore_open_watched_file_added(Config) ->
+ %% Before
+ UriA = ?config(watched_file_a_uri, Config),
+ UriB = ?config(watched_file_b_uri, Config),
+ PathB = ?config(watched_file_b_path, Config),
+ ExpectedLocationsBefore = [ #{ uri => UriB
+ , range => #{from => {6, 3}, to => {6, 22}}
+ }
+ ],
+ #{result := LocationsBefore} = els_client:references(UriA, 5, 2),
+ assert_locations(LocationsBefore, ExpectedLocationsBefore),
+ %% Add (Simulate a checkout, rebase or similar)
+ DataDir = ?config(data_dir, Config),
+ PathC = filename:join([DataDir, "watched_file_c.erl"]),
+ NewPathC = filename:join(filename:dirname(PathB), "watched_file_c.erl"),
+ NewUriC = els_uri:uri(NewPathC),
+ {ok, _} = file:copy(PathC, NewPathC),
+ %% Open file, did_change_watched_files requests should be ignored
+ els_client:did_open(NewUriC, erlang, 1, <<"dummy">>),
+ els_client:did_change_watched_files([{NewUriC, ?FILE_CHANGE_TYPE_CREATED}]),
+ %% After
+ ExpectedLocationsOpen = [ #{ uri => UriB
+ , range => #{from => {6, 3}, to => {6, 22}}
+ }
+ ],
+ #{result := LocationsOpen} = els_client:references(UriA, 5, 2),
+ assert_locations(LocationsOpen, ExpectedLocationsOpen),
+ %% Close file, did_change_watched_files requests should be resumed
+ els_client:did_close(NewUriC),
+ els_client:did_change_watched_files([{NewUriC, ?FILE_CHANGE_TYPE_CREATED}]),
+ %% After
+ ExpectedLocationsClose = [ #{ uri => NewUriC
+ , range => #{from => {6, 3}, to => {6, 22}}
+ }
+ , #{ uri => UriB
+ , range => #{from => {6, 3}, to => {6, 22}}
+ }
+ ],
+ #{result := LocationsClose} = els_client:references(UriA, 5, 2),
+ assert_locations(LocationsClose, ExpectedLocationsClose),
+ ok.
+
%%==============================================================================
%% Internal functions
%%==============================================================================
-spec assert_locations([map()], [map()]) -> ok.
assert_locations(Locations, ExpectedLocations) ->
- ?assertEqual(length(ExpectedLocations), length(Locations)),
+ ?assertEqual(length(ExpectedLocations),
+ length(Locations),
+ { {expected, ExpectedLocations}
+ , {actual, Locations}
+ }
+ ),
Pairs = lists:zip(sort_locations(Locations), ExpectedLocations),
[ begin
#{range := Range} = Location,
From fb3bdf7e978933c242a3b4c6b2f64aad0916b68f Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Thu, 12 May 2022 08:51:01 +0200
Subject: [PATCH 070/239] Add randomness to DAP node name (#1295)
---
apps/els_dap/src/els_dap_general_provider.erl | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/apps/els_dap/src/els_dap_general_provider.erl b/apps/els_dap/src/els_dap_general_provider.erl
index c32d2d51d..f0c2bdc92 100644
--- a/apps/els_dap/src/els_dap_general_provider.erl
+++ b/apps/els_dap/src/els_dap_general_provider.erl
@@ -918,8 +918,11 @@ start_distribution(Params) ->
shortnames
end,
%% start distribution
- LocalNode = els_distribution_server:node_name("erlang_ls_dap",
- binary_to_list(Name), NameType),
+ Prefix = <<"erlang_ls_dap">>,
+ Int = erlang:phash2(erlang:timestamp()),
+ Id = lists:flatten(io_lib:format("~s_~s_~p", [Prefix, Name, Int])),
+ {ok, HostName} = inet:gethostname(),
+ LocalNode = els_distribution_server:node_name(Id, HostName, NameType),
case els_distribution_server:start_distribution(LocalNode, ConfProjectNode,
Cookie, NameType) of
ok ->
From c19fb7239ef6766aa7e583d1c010ac38588a3de3 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Thu, 12 May 2022 15:24:06 +0200
Subject: [PATCH 071/239] Apply erlfmt to the entire codebase (#1297)
---
.github/workflows/build.yml | 2 +
apps/els_core/include/els_core.hrl | 874 +++---
apps/els_core/src/els_client.erl | 684 ++---
apps/els_core/src/els_command.erl | 48 +-
apps/els_core/src/els_config.erl | 582 ++--
apps/els_core/src/els_config_ct_run_test.erl | 36 +-
apps/els_core/src/els_config_indexing.erl | 40 +-
apps/els_core/src/els_config_runtime.erl | 81 +-
apps/els_core/src/els_distribution_server.erl | 271 +-
apps/els_core/src/els_distribution_sup.erl | 36 +-
apps/els_core/src/els_dodger.erl | 1165 ++++----
apps/els_core/src/els_escript.erl | 370 +--
apps/els_core/src/els_io_string.erl | 128 +-
apps/els_core/src/els_jsonrpc.erl | 79 +-
apps/els_core/src/els_protocol.erl | 90 +-
apps/els_core/src/els_provider.erl | 324 +--
apps/els_core/src/els_stdio.erl | 71 +-
apps/els_core/src/els_text.erl | 162 +-
apps/els_core/src/els_uri.erl | 136 +-
apps/els_core/src/els_utils.erl | 879 +++---
apps/els_core/test/els_fake_stdio.erl | 136 +-
apps/els_dap/src/els_dap.erl | 164 +-
apps/els_dap/src/els_dap_agent.erl | 16 +-
apps/els_dap/src/els_dap_app.erl | 11 +-
apps/els_dap/src/els_dap_breakpoints.erl | 189 +-
apps/els_dap/src/els_dap_general_provider.erl | 1694 ++++++------
apps/els_dap/src/els_dap_methods.erl | 87 +-
apps/els_dap/src/els_dap_protocol.erl | 97 +-
apps/els_dap/src/els_dap_provider.erl | 69 +-
apps/els_dap/src/els_dap_rpc.erl | 145 +-
apps/els_dap/src/els_dap_server.erl | 160 +-
apps/els_dap/src/els_dap_sup.erl | 96 +-
apps/els_dap/test/els_dap_SUITE.erl | 94 +-
.../test/els_dap_general_provider_SUITE.erl | 1056 +++----
apps/els_dap/test/els_dap_test_utils.erl | 102 +-
apps/els_lsp/include/els_lsp.hrl | 4 +-
apps/els_lsp/src/edoc_report.erl | 82 +-
apps/els_lsp/src/els_app.erl | 13 +-
apps/els_lsp/src/els_background_job.erl | 367 +--
apps/els_lsp/src/els_background_job_sup.erl | 30 +-
.../els_bound_var_in_pattern_diagnostics.erl | 181 +-
apps/els_lsp/src/els_call_hierarchy_item.erl | 81 +-
.../src/els_call_hierarchy_provider.erl | 130 +-
apps/els_lsp/src/els_code_action_provider.erl | 81 +-
apps/els_lsp/src/els_code_actions.erl | 290 +-
apps/els_lsp/src/els_code_lens.erl | 156 +-
.../els_lsp/src/els_code_lens_ct_run_test.erl | 59 +-
.../src/els_code_lens_function_references.erl | 35 +-
apps/els_lsp/src/els_code_lens_provider.erl | 57 +-
.../els_lsp/src/els_code_lens_server_info.erl | 29 +-
.../els_code_lens_show_behaviour_usages.erl | 41 +-
.../src/els_code_lens_suggest_spec.erl | 96 +-
apps/els_lsp/src/els_code_navigation.erl | 349 +--
apps/els_lsp/src/els_command_ct_run_test.erl | 84 +-
apps/els_lsp/src/els_compiler_diagnostics.erl | 1036 +++----
apps/els_lsp/src/els_completion_provider.erl | 1301 +++++----
apps/els_lsp/src/els_crossref_diagnostics.erl | 148 +-
apps/els_lsp/src/els_db.erl | 62 +-
apps/els_lsp/src/els_db_server.erl | 116 +-
apps/els_lsp/src/els_db_table.erl | 27 +-
apps/els_lsp/src/els_definition_provider.erl | 115 +-
apps/els_lsp/src/els_diagnostics.erl | 212 +-
apps/els_lsp/src/els_diagnostics_provider.erl | 41 +-
apps/els_lsp/src/els_diagnostics_utils.erl | 226 +-
apps/els_lsp/src/els_dialyzer_diagnostics.erl | 96 +-
apps/els_lsp/src/els_docs.erl | 492 ++--
.../src/els_document_highlight_provider.erl | 150 +-
.../src/els_document_symbol_provider.erl | 60 +-
apps/els_lsp/src/els_dt_document.erl | 366 +--
apps/els_lsp/src/els_dt_document_index.erl | 73 +-
apps/els_lsp/src/els_dt_references.erl | 215 +-
apps/els_lsp/src/els_dt_signatures.erl | 148 +-
apps/els_lsp/src/els_edoc_diagnostics.erl | 94 +-
apps/els_lsp/src/els_eep48_docs.erl | 972 ++++---
apps/els_lsp/src/els_elvis_diagnostics.erl | 129 +-
apps/els_lsp/src/els_erlfmt_ast.erl | 566 ++--
.../src/els_execute_command_provider.erl | 144 +-
.../src/els_folding_range_provider.erl | 41 +-
apps/els_lsp/src/els_formatting_provider.erl | 157 +-
apps/els_lsp/src/els_fungraph.erl | 44 +-
apps/els_lsp/src/els_general_provider.erl | 214 +-
.../src/els_gradualizer_diagnostics.erl | 90 +-
apps/els_lsp/src/els_group_leader_server.erl | 120 +-
apps/els_lsp/src/els_group_leader_sup.erl | 30 +-
apps/els_lsp/src/els_hover_provider.erl | 78 +-
.../src/els_implementation_provider.erl | 166 +-
apps/els_lsp/src/els_incomplete_parser.erl | 37 +-
apps/els_lsp/src/els_indexing.erl | 393 +--
apps/els_lsp/src/els_log_notification.erl | 21 +-
apps/els_lsp/src/els_markup_content.erl | 89 +-
apps/els_lsp/src/els_methods.erl | 477 ++--
apps/els_lsp/src/els_parser.erl | 1873 +++++++------
apps/els_lsp/src/els_poi.erl | 46 +-
apps/els_lsp/src/els_progress.erl | 32 +-
apps/els_lsp/src/els_range.erl | 124 +-
.../src/els_refactorerl_diagnostics.erl | 75 +-
apps/els_lsp/src/els_refactorerl_utils.erl | 143 +-
apps/els_lsp/src/els_references_provider.erl | 248 +-
apps/els_lsp/src/els_rename_provider.erl | 528 ++--
apps/els_lsp/src/els_scope.erl | 217 +-
apps/els_lsp/src/els_server.erl | 297 +-
apps/els_lsp/src/els_snippets_server.erl | 116 +-
apps/els_lsp/src/els_sup.erl | 134 +-
.../src/els_text_document_position_params.erl | 21 +-
apps/els_lsp/src/els_text_edit.erl | 85 +-
apps/els_lsp/src/els_text_search.erl | 24 +-
apps/els_lsp/src/els_text_synchronization.erl | 152 +-
.../src/els_text_synchronization_provider.erl | 56 +-
apps/els_lsp/src/els_typer.erl | 595 ++--
.../src/els_unused_includes_diagnostics.erl | 187 +-
.../src/els_unused_macros_diagnostics.erl | 70 +-
.../els_unused_record_fields_diagnostics.erl | 57 +-
apps/els_lsp/src/els_work_done_progress.erl | 142 +-
.../src/els_workspace_symbol_provider.erl | 87 +-
apps/els_lsp/src/erlang_ls.erl | 182 +-
.../els_lsp/test/els_call_hierarchy_SUITE.erl | 560 ++--
apps/els_lsp/test/els_code_action_SUITE.erl | 483 ++--
apps/els_lsp/test/els_code_lens_SUITE.erl | 287 +-
apps/els_lsp/test/els_completion_SUITE.erl | 2434 +++++++++--------
apps/els_lsp/test/els_definition_SUITE.erl | 847 +++---
apps/els_lsp/test/els_diagnostics_SUITE.erl | 1530 ++++++-----
.../test/els_document_highlight_SUITE.erl | 550 ++--
.../test/els_document_symbol_SUITE.erl | 225 +-
.../test/els_execute_command_SUITE.erl | 350 ++-
apps/els_lsp/test/els_foldingrange_SUITE.erl | 63 +-
apps/els_lsp/test/els_formatter_SUITE.erl | 82 +-
apps/els_lsp/test/els_fungraph_SUITE.erl | 36 +-
apps/els_lsp/test/els_hover_SUITE.erl | 678 +++--
.../els_lsp/test/els_implementation_SUITE.erl | 98 +-
apps/els_lsp/test/els_indexer_SUITE.erl | 210 +-
apps/els_lsp/test/els_indexing_SUITE.erl | 119 +-
.../els_lsp/test/els_initialization_SUITE.erl | 270 +-
apps/els_lsp/test/els_io_string_SUITE.erl | 56 +-
apps/els_lsp/test/els_mock_diagnostics.erl | 57 +-
apps/els_lsp/test/els_parser_SUITE.erl | 471 ++--
apps/els_lsp/test/els_parser_macros_SUITE.erl | 306 ++-
apps/els_lsp/test/els_progress_SUITE.erl | 115 +-
apps/els_lsp/test/els_proper_gen.erl | 32 +-
.../els_lsp/test/els_rebar3_release_SUITE.erl | 88 +-
apps/els_lsp/test/els_references_SUITE.erl | 1104 ++++----
apps/els_lsp/test/els_rename_SUITE.erl | 1132 +++++---
apps/els_lsp/test/els_server_SUITE.erl | 119 +-
apps/els_lsp/test/els_test.erl | 213 +-
apps/els_lsp/test/els_test_utils.erl | 196 +-
apps/els_lsp/test/els_text_SUITE.erl | 262 +-
apps/els_lsp/test/els_text_edit_SUITE.erl | 76 +-
.../test/els_workspace_symbol_SUITE.erl | 215 +-
apps/els_lsp/test/erlang_ls_SUITE.erl | 77 +-
apps/els_lsp/test/prop_statem.erl | 359 +--
elvis.config | 199 +-
rebar.config | 139 +-
151 files changed, 22240 insertions(+), 18374 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 82efff2d7..7fed5db24 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -49,6 +49,8 @@ jobs:
with:
name: els_dap
path: _build/dap/bin/els_dap
+ - name: Check formatting
+ run: rebar3 fmt -c
- name: Lint
run: rebar3 lint
- name: Generate Dialyzer PLT for usage in CT Tests
diff --git a/apps/els_core/include/els_core.hrl b/apps/els_core/include/els_core.hrl
index 85e5d4a39..9fed5171e 100644
--- a/apps/els_core/include/els_core.hrl
+++ b/apps/els_core/include/els_core.hrl
@@ -17,58 +17,60 @@
%%------------------------------------------------------------------------------
%% Abstract Message
%%------------------------------------------------------------------------------
--type message() :: #{ jsonrpc := jsonrpc_vsn()
- }.
+-type message() :: #{jsonrpc := jsonrpc_vsn()}.
%%------------------------------------------------------------------------------
%% Request Message
%%------------------------------------------------------------------------------
--type request() :: #{ jsonrpc := jsonrpc_vsn()
- , id := number() | binary()
- , method := binary()
- , params => [any()] | map()
- }.
+-type request() :: #{
+ jsonrpc := jsonrpc_vsn(),
+ id := number() | binary(),
+ method := binary(),
+ params => [any()] | map()
+}.
%%------------------------------------------------------------------------------
%% Response Message
%%------------------------------------------------------------------------------
--type response() :: #{ jsonrpc := jsonrpc_vsn()
- , id := number() | binary() | null
- , result => any()
- , error => error(any())
- }.
+-type response() :: #{
+ jsonrpc := jsonrpc_vsn(),
+ id := number() | binary() | null,
+ result => any(),
+ error => error(any())
+}.
--type error(Type) :: #{ code := number()
- , message := binary()
- , data => Type
- }.
+-type error(Type) :: #{
+ code := number(),
+ message := binary(),
+ data => Type
+}.
%% Defined by JSON RPC
--define(ERR_PARSE_ERROR , -32700).
--define(ERR_INVALID_REQUEST , -32600).
--define(ERR_METHOD_NOT_FOUND , -32601).
--define(ERR_INVALID_PARAMS , -32602).
--define(ERR_INTERNAL_ERROR , -32603).
--define(ERR_SERVER_ERROR_START , -32099).
--define(ERR_SERVER_ERROR_END , -32000).
--define(ERR_SERVER_NOT_INITIALIZED , -32002).
--define(ERR_UNKNOWN_ERROR_CODE , -32001).
+-define(ERR_PARSE_ERROR, -32700).
+-define(ERR_INVALID_REQUEST, -32600).
+-define(ERR_METHOD_NOT_FOUND, -32601).
+-define(ERR_INVALID_PARAMS, -32602).
+-define(ERR_INTERNAL_ERROR, -32603).
+-define(ERR_SERVER_ERROR_START, -32099).
+-define(ERR_SERVER_ERROR_END, -32000).
+-define(ERR_SERVER_NOT_INITIALIZED, -32002).
+-define(ERR_UNKNOWN_ERROR_CODE, -32001).
%% Defined by the protocol
--define(ERR_REQUEST_CANCELLED , -32800).
+-define(ERR_REQUEST_CANCELLED, -32800).
%%------------------------------------------------------------------------------
%% Notification Message
%%------------------------------------------------------------------------------
--type notification(Method, Params) :: #{ jsonrpc := jsonrpc_vsn()
- , method := Method
- , params => Params
- }.
+-type notification(Method, Params) :: #{
+ jsonrpc := jsonrpc_vsn(),
+ method := Method,
+ params => Params
+}.
%%------------------------------------------------------------------------------
%% Cancellation Support
%%------------------------------------------------------------------------------
--type cancel_params() :: #{ id := number() | binary()
- }.
+-type cancel_params() :: #{id := number() | binary()}.
%%==============================================================================
%% Language Server Protocol
@@ -77,11 +79,12 @@
%%------------------------------------------------------------------------------
%% Position
%%------------------------------------------------------------------------------
--type line() :: number().
--type column() :: number().
--type position() :: #{ line := line()
- , character := column()
- }.
+-type line() :: number().
+-type column() :: number().
+-type position() :: #{
+ line := line(),
+ character := column()
+}.
%% This is used for defining folding ranges. It is not possible to just use
%% positions as this one `{Line + 1, 0}' in a folding range, because of
@@ -94,58 +97,64 @@
%%------------------------------------------------------------------------------
%% Range
%%------------------------------------------------------------------------------
--type range() :: #{ start := position()
- , 'end' := position()
- }.
+-type range() :: #{
+ start := position(),
+ 'end' := position()
+}.
%%------------------------------------------------------------------------------
%% Location
%%------------------------------------------------------------------------------
--type location() :: #{ uri := uri()
- , range := range()
- }.
+-type location() :: #{
+ uri := uri(),
+ range := range()
+}.
%%------------------------------------------------------------------------------
%% Folding Range
%%------------------------------------------------------------------------------
--type folding_range() :: #{ startLine := pos_integer()
- , startCharacter := pos_integer()
- , endLine := pos_integer()
- , endCharacter := pos_integer()
- }.
+-type folding_range() :: #{
+ startLine := pos_integer(),
+ startCharacter := pos_integer(),
+ endLine := pos_integer(),
+ endCharacter := pos_integer()
+}.
%%------------------------------------------------------------------------------
%% Diagnostics
%%------------------------------------------------------------------------------
--define(DIAGNOSTIC_ERROR , 1).
--define(DIAGNOSTIC_WARNING , 2).
--define(DIAGNOSTIC_INFO , 3).
--define(DIAGNOSTIC_HINT , 4).
+-define(DIAGNOSTIC_ERROR, 1).
+-define(DIAGNOSTIC_WARNING, 2).
+-define(DIAGNOSTIC_INFO, 3).
+-define(DIAGNOSTIC_HINT, 4).
%%------------------------------------------------------------------------------
%% Insert Text Format
%%------------------------------------------------------------------------------
-define(INSERT_TEXT_FORMAT_PLAIN_TEXT, 1).
--define(INSERT_TEXT_FORMAT_SNIPPET, 2).
--type insert_text_format() :: ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- | ?INSERT_TEXT_FORMAT_SNIPPET.
+-define(INSERT_TEXT_FORMAT_SNIPPET, 2).
+-type insert_text_format() ::
+ ?INSERT_TEXT_FORMAT_PLAIN_TEXT
+ | ?INSERT_TEXT_FORMAT_SNIPPET.
%%------------------------------------------------------------------------------
%% Text Edit
%%------------------------------------------------------------------------------
--type text_edit() :: #{ range := range()
- , newText := binary()
- }.
+-type text_edit() :: #{
+ range := range(),
+ newText := binary()
+}.
%%------------------------------------------------------------------------------
%% Text Document Edit
%%------------------------------------------------------------------------------
--type text_document_edit() :: #{ textDocument := versioned_text_document_id()
- , edits := [text_edit()]
- }.
+-type text_document_edit() :: #{
+ textDocument := versioned_text_document_id(),
+ edits := [text_edit()]
+}.
%%------------------------------------------------------------------------------
%% Workspace Edit
@@ -153,25 +162,25 @@
-type document_change() :: text_document_edit().
--type workspace_edit() :: #{ changes => #{ uri() := [text_edit()]
- }
- , documentChanges => [document_change()]
- }.
-
+-type workspace_edit() :: #{
+ changes => #{uri() := [text_edit()]},
+ documentChanges => [document_change()]
+}.
%%------------------------------------------------------------------------------
%% Text Document Identifier
%%------------------------------------------------------------------------------
--type text_document_id() :: #{ uri := uri() }.
+-type text_document_id() :: #{uri := uri()}.
%%------------------------------------------------------------------------------
%% Text Document Item
%%------------------------------------------------------------------------------
--type text_document_item() :: #{ uri := uri()
- , languageId := binary()
- , version := number()
- , text := binary()
- }.
+-type text_document_item() :: #{
+ uri := uri(),
+ languageId := binary(),
+ version := number(),
+ text := binary()
+}.
%%------------------------------------------------------------------------------
%% Text Document Sync Kind
@@ -181,65 +190,70 @@
-define(TEXT_DOCUMENT_SYNC_KIND_FULL, 1).
-define(TEXT_DOCUMENT_SYNC_KIND_INCREMENTAL, 2).
--type text_document_sync_kind() :: ?TEXT_DOCUMENT_SYNC_KIND_NONE
- | ?TEXT_DOCUMENT_SYNC_KIND_FULL
- | ?TEXT_DOCUMENT_SYNC_KIND_INCREMENTAL.
+-type text_document_sync_kind() ::
+ ?TEXT_DOCUMENT_SYNC_KIND_NONE
+ | ?TEXT_DOCUMENT_SYNC_KIND_FULL
+ | ?TEXT_DOCUMENT_SYNC_KIND_INCREMENTAL.
%%------------------------------------------------------------------------------
%% Text Document Sync Kind
%%------------------------------------------------------------------------------
--define(COMPLETION_TRIGGER_KIND_INVOKED, 1).
--define(COMPLETION_TRIGGER_KIND_CHARACTER, 2).
+-define(COMPLETION_TRIGGER_KIND_INVOKED, 1).
+-define(COMPLETION_TRIGGER_KIND_CHARACTER, 2).
-define(COMPLETION_TRIGGER_KIND_FOR_INCOMPLETE_COMPLETIONS, 3).
%%------------------------------------------------------------------------------
%% Versioned Text Document Identifier
%%------------------------------------------------------------------------------
--type versioned_text_document_id() :: #{ version := number() | null
- }.
+-type versioned_text_document_id() :: #{version := number() | null}.
%%------------------------------------------------------------------------------
%% Text Document Position Params
%%------------------------------------------------------------------------------
--type text_document_position_params() :: #{ textDocument := text_document_id()
- , position := position()
- }.
+-type text_document_position_params() :: #{
+ textDocument := text_document_id(),
+ position := position()
+}.
%%------------------------------------------------------------------------------
%% Document Filter
%%------------------------------------------------------------------------------
--type document_filter() :: #{ language => binary()
- , scheme => binary()
- , pattern => binary()
- }.
+-type document_filter() :: #{
+ language => binary(),
+ scheme => binary(),
+ pattern => binary()
+}.
-type document_selector() :: [document_filter()].
%%------------------------------------------------------------------------------
%% Markup Content
%%------------------------------------------------------------------------------
--define(PLAINTEXT , plaintext).
--define(MARKDOWN , markdown).
+-define(PLAINTEXT, plaintext).
+-define(MARKDOWN, markdown).
--type markup_kind() :: ?PLAINTEXT
- | ?MARKDOWN.
+-type markup_kind() ::
+ ?PLAINTEXT
+ | ?MARKDOWN.
--type markup_content() :: #{ kind := markup_kind()
- , value := binary()
- }.
+-type markup_content() :: #{
+ kind := markup_kind(),
+ value := binary()
+}.
%%------------------------------------------------------------------------------
%% Document Highlight Kind
%%------------------------------------------------------------------------------
--define(DOCUMENT_HIGHLIGHT_KIND_TEXT, 1).
--define(DOCUMENT_HIGHLIGHT_KIND_READ, 2).
+-define(DOCUMENT_HIGHLIGHT_KIND_TEXT, 1).
+-define(DOCUMENT_HIGHLIGHT_KIND_READ, 2).
-define(DOCUMENT_HIGHLIGHT_KIND_WRITE, 3).
--type document_highlight_kind() :: ?DOCUMENT_HIGHLIGHT_KIND_TEXT
- | ?DOCUMENT_HIGHLIGHT_KIND_READ
- | ?DOCUMENT_HIGHLIGHT_KIND_WRITE.
+-type document_highlight_kind() ::
+ ?DOCUMENT_HIGHLIGHT_KIND_TEXT
+ | ?DOCUMENT_HIGHLIGHT_KIND_READ
+ | ?DOCUMENT_HIGHLIGHT_KIND_WRITE.
%%==============================================================================
%% Actual Protocol
@@ -248,275 +262,274 @@
%%------------------------------------------------------------------------------
%% Initialize Request
%%------------------------------------------------------------------------------
--type workspace_folder() :: #{ uri => uri()
- , name => binary()
- }.
+-type workspace_folder() :: #{
+ uri => uri(),
+ name => binary()
+}.
--define(COMPLETION_ITEM_KIND_TEXT, 1).
--define(COMPLETION_ITEM_KIND_METHOD, 2).
--define(COMPLETION_ITEM_KIND_FUNCTION, 3).
+-define(COMPLETION_ITEM_KIND_TEXT, 1).
+-define(COMPLETION_ITEM_KIND_METHOD, 2).
+-define(COMPLETION_ITEM_KIND_FUNCTION, 3).
-define(COMPLETION_ITEM_KIND_CONSTRUCTOR, 4).
--define(COMPLETION_ITEM_KIND_FIELD, 5).
--define(COMPLETION_ITEM_KIND_VARIABLE, 6).
--define(COMPLETION_ITEM_KIND_CLASS, 7).
--define(COMPLETION_ITEM_KIND_INTERFACE, 8).
--define(COMPLETION_ITEM_KIND_MODULE, 9).
--define(COMPLETION_ITEM_KIND_PROPERTY, 10).
--define(COMPLETION_ITEM_KIND_UNIT, 11).
--define(COMPLETION_ITEM_KIND_VALUE, 12).
--define(COMPLETION_ITEM_KIND_ENUM, 13).
--define(COMPLETION_ITEM_KIND_KEYWORD, 14).
--define(COMPLETION_ITEM_KIND_SNIPPET, 15).
--define(COMPLETION_ITEM_KIND_COLOR, 16).
--define(COMPLETION_ITEM_KIND_FILE, 17).
--define(COMPLETION_ITEM_KIND_REFERENCE, 18).
--define(COMPLETION_ITEM_KIND_FOLDER, 19).
+-define(COMPLETION_ITEM_KIND_FIELD, 5).
+-define(COMPLETION_ITEM_KIND_VARIABLE, 6).
+-define(COMPLETION_ITEM_KIND_CLASS, 7).
+-define(COMPLETION_ITEM_KIND_INTERFACE, 8).
+-define(COMPLETION_ITEM_KIND_MODULE, 9).
+-define(COMPLETION_ITEM_KIND_PROPERTY, 10).
+-define(COMPLETION_ITEM_KIND_UNIT, 11).
+-define(COMPLETION_ITEM_KIND_VALUE, 12).
+-define(COMPLETION_ITEM_KIND_ENUM, 13).
+-define(COMPLETION_ITEM_KIND_KEYWORD, 14).
+-define(COMPLETION_ITEM_KIND_SNIPPET, 15).
+-define(COMPLETION_ITEM_KIND_COLOR, 16).
+-define(COMPLETION_ITEM_KIND_FILE, 17).
+-define(COMPLETION_ITEM_KIND_REFERENCE, 18).
+-define(COMPLETION_ITEM_KIND_FOLDER, 19).
-define(COMPLETION_ITEM_KIND_ENUM_MEMBER, 20).
--define(COMPLETION_ITEM_KIND_CONSTANT, 21).
--define(COMPLETION_ITEM_KIND_STRUCT, 22).
--define(COMPLETION_ITEM_KIND_EVENT, 23).
--define(COMPLETION_ITEM_KIND_OPERATOR, 24).
--define(COMPLETION_ITEM_KIND_TYPE_PARAM, 25).
-
--type completion_item_kind() :: ?COMPLETION_ITEM_KIND_TEXT
- | ?COMPLETION_ITEM_KIND_METHOD
- | ?COMPLETION_ITEM_KIND_FUNCTION
- | ?COMPLETION_ITEM_KIND_CONSTRUCTOR
- | ?COMPLETION_ITEM_KIND_FIELD
- | ?COMPLETION_ITEM_KIND_VARIABLE
- | ?COMPLETION_ITEM_KIND_CLASS
- | ?COMPLETION_ITEM_KIND_INTERFACE
- | ?COMPLETION_ITEM_KIND_MODULE
- | ?COMPLETION_ITEM_KIND_PROPERTY
- | ?COMPLETION_ITEM_KIND_UNIT
- | ?COMPLETION_ITEM_KIND_VALUE
- | ?COMPLETION_ITEM_KIND_ENUM
- | ?COMPLETION_ITEM_KIND_KEYWORD
- | ?COMPLETION_ITEM_KIND_SNIPPET
- | ?COMPLETION_ITEM_KIND_COLOR
- | ?COMPLETION_ITEM_KIND_FILE
- | ?COMPLETION_ITEM_KIND_REFERENCE
- | ?COMPLETION_ITEM_KIND_FOLDER
- | ?COMPLETION_ITEM_KIND_ENUM_MEMBER
- | ?COMPLETION_ITEM_KIND_CONSTANT
- | ?COMPLETION_ITEM_KIND_STRUCT
- | ?COMPLETION_ITEM_KIND_EVENT
- | ?COMPLETION_ITEM_KIND_OPERATOR
- | ?COMPLETION_ITEM_KIND_TYPE_PARAM.
-
--type completion_item() :: #{ label := binary()
- , kind => completion_item_kind()
- , insertText => binary()
- , insertTextFormat => insert_text_format()
- , data => map()
- }.
+-define(COMPLETION_ITEM_KIND_CONSTANT, 21).
+-define(COMPLETION_ITEM_KIND_STRUCT, 22).
+-define(COMPLETION_ITEM_KIND_EVENT, 23).
+-define(COMPLETION_ITEM_KIND_OPERATOR, 24).
+-define(COMPLETION_ITEM_KIND_TYPE_PARAM, 25).
+
+-type completion_item_kind() ::
+ ?COMPLETION_ITEM_KIND_TEXT
+ | ?COMPLETION_ITEM_KIND_METHOD
+ | ?COMPLETION_ITEM_KIND_FUNCTION
+ | ?COMPLETION_ITEM_KIND_CONSTRUCTOR
+ | ?COMPLETION_ITEM_KIND_FIELD
+ | ?COMPLETION_ITEM_KIND_VARIABLE
+ | ?COMPLETION_ITEM_KIND_CLASS
+ | ?COMPLETION_ITEM_KIND_INTERFACE
+ | ?COMPLETION_ITEM_KIND_MODULE
+ | ?COMPLETION_ITEM_KIND_PROPERTY
+ | ?COMPLETION_ITEM_KIND_UNIT
+ | ?COMPLETION_ITEM_KIND_VALUE
+ | ?COMPLETION_ITEM_KIND_ENUM
+ | ?COMPLETION_ITEM_KIND_KEYWORD
+ | ?COMPLETION_ITEM_KIND_SNIPPET
+ | ?COMPLETION_ITEM_KIND_COLOR
+ | ?COMPLETION_ITEM_KIND_FILE
+ | ?COMPLETION_ITEM_KIND_REFERENCE
+ | ?COMPLETION_ITEM_KIND_FOLDER
+ | ?COMPLETION_ITEM_KIND_ENUM_MEMBER
+ | ?COMPLETION_ITEM_KIND_CONSTANT
+ | ?COMPLETION_ITEM_KIND_STRUCT
+ | ?COMPLETION_ITEM_KIND_EVENT
+ | ?COMPLETION_ITEM_KIND_OPERATOR
+ | ?COMPLETION_ITEM_KIND_TYPE_PARAM.
+
+-type completion_item() :: #{
+ label := binary(),
+ kind => completion_item_kind(),
+ insertText => binary(),
+ insertTextFormat => insert_text_format(),
+ data => map()
+}.
-type client_capabilities() ::
- #{ workspace => workspace_client_capabilities()
- , textDocument => text_document_client_capabilities()
- , experimental => any()
- }.
+ #{
+ workspace => workspace_client_capabilities(),
+ textDocument => text_document_client_capabilities(),
+ experimental => any()
+ }.
-type workspace_client_capabilities() ::
- #{ applyEdit => boolean()
- , workspaceEdit =>
- #{ documentChanges => boolean()
- }
- , didChangeConfiguration =>
- #{ dynamicRegistration => boolean()
- }
- , didChangeWatchedFiles =>
- #{ dynamicRegistration => boolean()
- }
- , symbol =>
- #{ dynamicRegistration => boolean()
- , symbolKind =>
- #{ valueSet => [symbol_kind()]
- }
- }
- , executeCommand =>
- #{ dynamicRegistration => boolean()
- }
- , workspaceFolders => boolean()
- , configuration => boolean()
- }.
+ #{
+ applyEdit => boolean(),
+ workspaceEdit =>
+ #{documentChanges => boolean()},
+ didChangeConfiguration =>
+ #{dynamicRegistration => boolean()},
+ didChangeWatchedFiles =>
+ #{dynamicRegistration => boolean()},
+ symbol =>
+ #{
+ dynamicRegistration => boolean(),
+ symbolKind =>
+ #{valueSet => [symbol_kind()]}
+ },
+ executeCommand =>
+ #{dynamicRegistration => boolean()},
+ workspaceFolders => boolean(),
+ configuration => boolean()
+ }.
-type text_document_client_capabilities() ::
- #{ synchronization =>
- #{ dynamicRegistration => boolean()
- , willSave => boolean()
- , willSaveWaitUntil => boolean()
- , didSave => boolean()
- }
- , completion =>
- #{ dynamicRegistration => boolean()
- , completionItem =>
- #{ snippetSupport => boolean()
- , commitCharactersSupport => boolean()
- , documentationFormat => markup_kind()
- , deprecatedSupport => boolean()
- }
- , completionItemKind =>
- #{ valueSet => [completion_item_kind()]
- }
- , contextSupport => boolean()
- }
- , hover =>
- #{ dynamicRegistration => boolean()
- , contentFormat => [markup_kind()]
- }
- , signatureHelp =>
- #{ dynamicRegistration => boolean()
- , signatureInformation =>
- #{ documentationFormat => [markup_kind()]
- }
- }
- , references =>
- #{ dynamicRegistration => boolean()
- }
- , documentHighlight =>
- #{ dynamicRegistration => boolean()
- }
- , documentSymbol =>
- #{ dynamicRegistration => boolean()
- , symbolKind =>
- #{ valueSet => [symbol_kind()]
- }
- }
- , formatting =>
- #{ dynamicRegistration => boolean()
- }
- , rangeFormatting =>
- #{ dynamicRegistration => boolean()
- }
- , onTypeFormatting =>
- #{ dynamicRegistration => boolean()
- }
- , definition =>
- #{ dynamicRegistration => boolean()
- }
- , typeDefinition =>
- #{ dynamicRegistration => boolean()
- }
- , implementation =>
- #{ dynamicRegistration => boolean()
- }
- , codeAction =>
- #{ dynamicRegistration => boolean()
- , codeActionLiteralSupport =>
- #{ codeActionKind :=
- #{ valueSet := [code_action_kind()]
- }
- }
- }
- , codeLens =>
- #{ dynamicRegistration => boolean()
- }
- , documentLink =>
- #{ dynamicRegistration => boolean()
- }
- , colorProvider =>
- #{ dynamicRegistration => boolean()
- }
- , rename =>
- #{ dynamicRegistration => boolean()
- }
- , publishDiagnostics =>
- #{ relatedInformation => boolean()
- }
- , foldingRange =>
- #{ dynamicRegistration => boolean()
- , rangeLimit => number()
- , lineFoldingOnly => boolean()
- }
- }.
+ #{
+ synchronization =>
+ #{
+ dynamicRegistration => boolean(),
+ willSave => boolean(),
+ willSaveWaitUntil => boolean(),
+ didSave => boolean()
+ },
+ completion =>
+ #{
+ dynamicRegistration => boolean(),
+ completionItem =>
+ #{
+ snippetSupport => boolean(),
+ commitCharactersSupport => boolean(),
+ documentationFormat => markup_kind(),
+ deprecatedSupport => boolean()
+ },
+ completionItemKind =>
+ #{valueSet => [completion_item_kind()]},
+ contextSupport => boolean()
+ },
+ hover =>
+ #{
+ dynamicRegistration => boolean(),
+ contentFormat => [markup_kind()]
+ },
+ signatureHelp =>
+ #{
+ dynamicRegistration => boolean(),
+ signatureInformation =>
+ #{documentationFormat => [markup_kind()]}
+ },
+ references =>
+ #{dynamicRegistration => boolean()},
+ documentHighlight =>
+ #{dynamicRegistration => boolean()},
+ documentSymbol =>
+ #{
+ dynamicRegistration => boolean(),
+ symbolKind =>
+ #{valueSet => [symbol_kind()]}
+ },
+ formatting =>
+ #{dynamicRegistration => boolean()},
+ rangeFormatting =>
+ #{dynamicRegistration => boolean()},
+ onTypeFormatting =>
+ #{dynamicRegistration => boolean()},
+ definition =>
+ #{dynamicRegistration => boolean()},
+ typeDefinition =>
+ #{dynamicRegistration => boolean()},
+ implementation =>
+ #{dynamicRegistration => boolean()},
+ codeAction =>
+ #{
+ dynamicRegistration => boolean(),
+ codeActionLiteralSupport =>
+ #{
+ codeActionKind :=
+ #{valueSet := [code_action_kind()]}
+ }
+ },
+ codeLens =>
+ #{dynamicRegistration => boolean()},
+ documentLink =>
+ #{dynamicRegistration => boolean()},
+ colorProvider =>
+ #{dynamicRegistration => boolean()},
+ rename =>
+ #{dynamicRegistration => boolean()},
+ publishDiagnostics =>
+ #{relatedInformation => boolean()},
+ foldingRange =>
+ #{
+ dynamicRegistration => boolean(),
+ rangeLimit => number(),
+ lineFoldingOnly => boolean()
+ }
+ }.
%%------------------------------------------------------------------------------
%% ShowMessage Notification
%%-----------------------------------------------------------------------------
--type show_message_notification() :: notification( show_message_method()
- , show_message_params()
- ).
+-type show_message_notification() :: notification(
+ show_message_method(),
+ show_message_params()
+).
-type show_message_method() :: 'window/showMessage'.
--type show_message_params() :: #{ type := show_message_type()
- , message := binary()
- }.
+-type show_message_params() :: #{
+ type := show_message_type(),
+ message := binary()
+}.
--define(MESSAGE_TYPE_ERROR , 1).
--define(MESSAGE_TYPE_WARNING , 2).
--define(MESSAGE_TYPE_INFO , 3).
--define(MESSAGE_TYPE_LOG , 4).
+-define(MESSAGE_TYPE_ERROR, 1).
+-define(MESSAGE_TYPE_WARNING, 2).
+-define(MESSAGE_TYPE_INFO, 3).
+-define(MESSAGE_TYPE_LOG, 4).
--type show_message_type() :: ?MESSAGE_TYPE_ERROR
- | ?MESSAGE_TYPE_WARNING
- | ?MESSAGE_TYPE_INFO
- | ?MESSAGE_TYPE_LOG.
+-type show_message_type() ::
+ ?MESSAGE_TYPE_ERROR
+ | ?MESSAGE_TYPE_WARNING
+ | ?MESSAGE_TYPE_INFO
+ | ?MESSAGE_TYPE_LOG.
%%------------------------------------------------------------------------------
%% Symbol Kinds
%%------------------------------------------------------------------------------
--define(SYMBOLKIND_FILE , 1).
--define(SYMBOLKIND_MODULE , 2).
--define(SYMBOLKIND_NAMESPACE , 3).
--define(SYMBOLKIND_PACKAGE , 4).
--define(SYMBOLKIND_CLASS , 5).
--define(SYMBOLKIND_METHOD , 6).
--define(SYMBOLKIND_PROPERTY , 7).
--define(SYMBOLKIND_FIELD , 8).
--define(SYMBOLKIND_CONSTRUCTOR , 9).
--define(SYMBOLKIND_ENUM , 10).
--define(SYMBOLKIND_INTERFACE , 11).
--define(SYMBOLKIND_FUNCTION , 12).
--define(SYMBOLKIND_VARIABLE , 13).
--define(SYMBOLKIND_CONSTANT , 14).
--define(SYMBOLKIND_STRING , 15).
--define(SYMBOLKIND_NUMBER , 16).
--define(SYMBOLKIND_BOOLEAN , 17).
--define(SYMBOLKIND_ARRAY , 18).
--define(SYMBOLKIND_OBJECT , 19).
--define(SYMBOLKIND_KEY , 20).
--define(SYMBOLKIND_NULL , 21).
--define(SYMBOLKIND_ENUM_MEMBER , 22).
--define(SYMBOLKIND_STRUCT , 23).
--define(SYMBOLKIND_EVENT , 24).
--define(SYMBOLKIND_OPERATOR , 25).
--define(SYMBOLKIND_TYPE_PARAMETER , 26).
-
--type symbol_kind() :: ?SYMBOLKIND_FILE
- | ?SYMBOLKIND_MODULE
- | ?SYMBOLKIND_NAMESPACE
- | ?SYMBOLKIND_PACKAGE
- | ?SYMBOLKIND_CLASS
- | ?SYMBOLKIND_METHOD
- | ?SYMBOLKIND_PROPERTY
- | ?SYMBOLKIND_FIELD
- | ?SYMBOLKIND_CONSTRUCTOR
- | ?SYMBOLKIND_ENUM
- | ?SYMBOLKIND_INTERFACE
- | ?SYMBOLKIND_FUNCTION
- | ?SYMBOLKIND_VARIABLE
- | ?SYMBOLKIND_CONSTANT
- | ?SYMBOLKIND_STRING
- | ?SYMBOLKIND_NUMBER
- | ?SYMBOLKIND_BOOLEAN
- | ?SYMBOLKIND_ARRAY
- | ?SYMBOLKIND_OBJECT
- | ?SYMBOLKIND_KEY
- | ?SYMBOLKIND_NULL
- | ?SYMBOLKIND_ENUM_MEMBER
- | ?SYMBOLKIND_STRUCT
- | ?SYMBOLKIND_EVENT
- | ?SYMBOLKIND_OPERATOR
- | ?SYMBOLKIND_TYPE_PARAMETER.
-
--type symbol_information() :: #{ name := binary()
- , kind := symbol_kind()
- , deprecated => boolean()
- , location := location()
- , containerName => binary()
- }.
+-define(SYMBOLKIND_FILE, 1).
+-define(SYMBOLKIND_MODULE, 2).
+-define(SYMBOLKIND_NAMESPACE, 3).
+-define(SYMBOLKIND_PACKAGE, 4).
+-define(SYMBOLKIND_CLASS, 5).
+-define(SYMBOLKIND_METHOD, 6).
+-define(SYMBOLKIND_PROPERTY, 7).
+-define(SYMBOLKIND_FIELD, 8).
+-define(SYMBOLKIND_CONSTRUCTOR, 9).
+-define(SYMBOLKIND_ENUM, 10).
+-define(SYMBOLKIND_INTERFACE, 11).
+-define(SYMBOLKIND_FUNCTION, 12).
+-define(SYMBOLKIND_VARIABLE, 13).
+-define(SYMBOLKIND_CONSTANT, 14).
+-define(SYMBOLKIND_STRING, 15).
+-define(SYMBOLKIND_NUMBER, 16).
+-define(SYMBOLKIND_BOOLEAN, 17).
+-define(SYMBOLKIND_ARRAY, 18).
+-define(SYMBOLKIND_OBJECT, 19).
+-define(SYMBOLKIND_KEY, 20).
+-define(SYMBOLKIND_NULL, 21).
+-define(SYMBOLKIND_ENUM_MEMBER, 22).
+-define(SYMBOLKIND_STRUCT, 23).
+-define(SYMBOLKIND_EVENT, 24).
+-define(SYMBOLKIND_OPERATOR, 25).
+-define(SYMBOLKIND_TYPE_PARAMETER, 26).
+
+-type symbol_kind() ::
+ ?SYMBOLKIND_FILE
+ | ?SYMBOLKIND_MODULE
+ | ?SYMBOLKIND_NAMESPACE
+ | ?SYMBOLKIND_PACKAGE
+ | ?SYMBOLKIND_CLASS
+ | ?SYMBOLKIND_METHOD
+ | ?SYMBOLKIND_PROPERTY
+ | ?SYMBOLKIND_FIELD
+ | ?SYMBOLKIND_CONSTRUCTOR
+ | ?SYMBOLKIND_ENUM
+ | ?SYMBOLKIND_INTERFACE
+ | ?SYMBOLKIND_FUNCTION
+ | ?SYMBOLKIND_VARIABLE
+ | ?SYMBOLKIND_CONSTANT
+ | ?SYMBOLKIND_STRING
+ | ?SYMBOLKIND_NUMBER
+ | ?SYMBOLKIND_BOOLEAN
+ | ?SYMBOLKIND_ARRAY
+ | ?SYMBOLKIND_OBJECT
+ | ?SYMBOLKIND_KEY
+ | ?SYMBOLKIND_NULL
+ | ?SYMBOLKIND_ENUM_MEMBER
+ | ?SYMBOLKIND_STRUCT
+ | ?SYMBOLKIND_EVENT
+ | ?SYMBOLKIND_OPERATOR
+ | ?SYMBOLKIND_TYPE_PARAMETER.
+
+-type symbol_information() :: #{
+ name := binary(),
+ kind := symbol_kind(),
+ deprecated => boolean(),
+ location := location(),
+ containerName => binary()
+}.
-define(SYMBOLTAG_DEPRECATED, 1).
@@ -525,32 +538,38 @@
%%------------------------------------------------------------------------------
%% Signatures
%%------------------------------------------------------------------------------
--type parameter_information() :: #{ label := binary()
- , documentation => binary()
- }.
--type signature_information() :: #{ label := binary()
- , documentation => binary()
- , parameters => [parameter_information()]
- }.
--type signature_help() :: #{ signatures := [signature_information()]
- , active_signature => number()
- , active_parameters => number()
- }.
+-type parameter_information() :: #{
+ label := binary(),
+ documentation => binary()
+}.
+-type signature_information() :: #{
+ label := binary(),
+ documentation => binary(),
+ parameters => [parameter_information()]
+}.
+-type signature_help() :: #{
+ signatures := [signature_information()],
+ active_signature => number(),
+ active_parameters => number()
+}.
%%------------------------------------------------------------------------------
%% Formatting
%%------------------------------------------------------------------------------
--type formatting_options() :: #{ tabSize := integer()
- , insertSpaces := boolean()
- %% Spec says further properties must
- %% meet the following signature
- %% [key: string]: boolean | number | string;
- }.
+-type formatting_options() :: #{
+ tabSize := integer(),
+ insertSpaces := boolean()
+ %% Spec says further properties must
+ %% meet the following signature
+ %% [key: string]: boolean | number | string;
+}.
--type document_ontypeformatting_options() :: false |
- #{ first_trigger_character := string()
- , more_trigger_character => string()
- }.
+-type document_ontypeformatting_options() ::
+ false
+ | #{
+ first_trigger_character := string(),
+ more_trigger_character => string()
+ }.
%%------------------------------------------------------------------------------
%% Code Actions
@@ -559,21 +578,24 @@
-define(CODE_ACTION_KIND_QUICKFIX, <<"quickfix">>).
-type code_action_kind() :: binary().
--type code_action_context() :: #{ diagnostics := [els_diagnostics:diagnostic()]
- , only => [code_action_kind()]
- }.
+-type code_action_context() :: #{
+ diagnostics := [els_diagnostics:diagnostic()],
+ only => [code_action_kind()]
+}.
--type code_action_params() :: #{ textDocument := text_document_id()
- , range := range()
- , context := code_action_context()
- }.
+-type code_action_params() :: #{
+ textDocument := text_document_id(),
+ range := range(),
+ context := code_action_context()
+}.
--type code_action() :: #{ title := binary()
- , kind => code_action_kind()
- , diagnostics => [els_diagnostics:diagnostic()]
- , edit => workspace_edit()
- , command => els_command:command()
- }.
+-type code_action() :: #{
+ title := binary(),
+ kind => code_action_kind(),
+ diagnostics => [els_diagnostics:diagnostic()],
+ edit => workspace_edit(),
+ command => els_command:command()
+}.
%%------------------------------------------------------------------------------
%% Workspace
@@ -583,53 +605,59 @@
-define(FILE_CHANGE_TYPE_CHANGED, 2).
-define(FILE_CHANGE_TYPE_DELETED, 3).
--type file_change_type() :: ?FILE_CHANGE_TYPE_CREATED
- | ?FILE_CHANGE_TYPE_CHANGED
- | ?FILE_CHANGE_TYPE_DELETED.
+-type file_change_type() ::
+ ?FILE_CHANGE_TYPE_CREATED
+ | ?FILE_CHANGE_TYPE_CHANGED
+ | ?FILE_CHANGE_TYPE_DELETED.
%%------------------------------------------------------------------------------
%% Internals
%%------------------------------------------------------------------------------
--type pos() :: {integer(), integer()}.
--type uri() :: binary().
--type poi_kind() :: application
- | atom
- | behaviour
- | callback
- | define
- | export
- | export_entry
- | export_type
- | export_type_entry
- | folding_range
- | function
- | function_clause
- | implicit_fun
- | import_entry
- | include
- | include_lib
- | macro
- | module
- | parse_transform
- | record
- | record_def_field
- | record_expr
- | record_field
- | spec
- | type_application
- | type_definition
- | variable.
--type poi_range() :: #{ from := pos(), to := pos() }.
--type poi_id() :: atom()
- | {atom(), atom()} %% record_def_field, record_field
- | string() %% include, include_lib
- | {atom(), arity()}
- | {module(), atom(), arity()}.
--type poi() :: #{ kind := poi_kind()
- , id := poi_id()
- , data := any()
- , range := poi_range()
- }.
--type tree() :: erl_syntax:syntaxTree().
+-type pos() :: {integer(), integer()}.
+-type uri() :: binary().
+-type poi_kind() ::
+ application
+ | atom
+ | behaviour
+ | callback
+ | define
+ | export
+ | export_entry
+ | export_type
+ | export_type_entry
+ | folding_range
+ | function
+ | function_clause
+ | implicit_fun
+ | import_entry
+ | include
+ | include_lib
+ | macro
+ | module
+ | parse_transform
+ | record
+ | record_def_field
+ | record_expr
+ | record_field
+ | spec
+ | type_application
+ | type_definition
+ | variable.
+-type poi_range() :: #{from := pos(), to := pos()}.
+-type poi_id() ::
+ atom()
+ %% record_def_field, record_field
+ | {atom(), atom()}
+ %% include, include_lib
+ | string()
+ | {atom(), arity()}
+ | {module(), atom(), arity()}.
+-type poi() :: #{
+ kind := poi_kind(),
+ id := poi_id(),
+ data := any(),
+ range := poi_range()
+}.
+-type tree() :: erl_syntax:syntaxTree().
-endif.
diff --git a/apps/els_core/src/els_client.erl b/apps/els_core/src/els_client.erl
index 3a59edc21..a3137e38d 100644
--- a/apps/els_core/src/els_client.erl
+++ b/apps/els_core/src/els_client.erl
@@ -8,55 +8,57 @@
%%==============================================================================
-behaviour(gen_server).
%% gen_server callbacks
--export([ init/1
- , handle_call/3
- , handle_cast/2
- , code_change/3
- ]).
+-export([
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ code_change/3
+]).
%%==============================================================================
%% Exports
%%==============================================================================
%% API
--export([ '$_cancelrequest'/0
- , '$_cancelrequest'/1
- , '$_settracenotification'/0
- , '$_unexpectedrequest'/0
- , completion/5
- , completionitem_resolve/1
- , definition/3
- , did_open/4
- , did_save/1
- , did_change_watched_files/1
- , did_close/1
- , document_symbol/1
- , exit/0
- , hover/3
- , implementation/3
- , initialize/1
- , initialize/2
- , initialized/0
- , references/3
- , document_highlight/3
- , document_codeaction/3
- , document_codelens/1
- , document_formatting/3
- , document_rangeformatting/3
- , document_ontypeformatting/4
- , document_rename/4
- , folding_range/1
- , shutdown/0
- , start_link/1
- , stop/0
- , workspace_symbol/1
- , workspace_executecommand/2
- , preparecallhierarchy/3
- , callhierarchy_incomingcalls/1
- , callhierarchy_outgoingcalls/1
- , get_notifications/0
- ]).
-
--export([ handle_responses/1 ]).
+-export([
+ '$_cancelrequest'/0,
+ '$_cancelrequest'/1,
+ '$_settracenotification'/0,
+ '$_unexpectedrequest'/0,
+ completion/5,
+ completionitem_resolve/1,
+ definition/3,
+ did_open/4,
+ did_save/1,
+ did_change_watched_files/1,
+ did_close/1,
+ document_symbol/1,
+ exit/0,
+ hover/3,
+ implementation/3,
+ initialize/1,
+ initialize/2,
+ initialized/0,
+ references/3,
+ document_highlight/3,
+ document_codeaction/3,
+ document_codelens/1,
+ document_formatting/3,
+ document_rangeformatting/3,
+ document_ontypeformatting/4,
+ document_rename/4,
+ folding_range/1,
+ shutdown/0,
+ start_link/1,
+ stop/0,
+ workspace_symbol/1,
+ workspace_executecommand/2,
+ preparecallhierarchy/3,
+ callhierarchy_incomingcalls/1,
+ callhierarchy_outgoingcalls/1,
+ get_notifications/0
+]).
+
+-export([handle_responses/1]).
%%==============================================================================
%% Includes
@@ -73,205 +75,210 @@
%%==============================================================================
%% Record Definitions
%%==============================================================================
--record(state, { io_device :: atom() | pid()
- , request_id = 1 :: request_id()
- , pending = []
- , notifications = []
- , requests = []
- }).
+-record(state, {
+ io_device :: atom() | pid(),
+ request_id = 1 :: request_id(),
+ pending = [],
+ notifications = [],
+ requests = []
+}).
%%==============================================================================
%% Type Definitions
%%==============================================================================
--type state() :: #state{}.
+-type state() :: #state{}.
-type init_options() :: #{}.
--type request_id() :: pos_integer().
+-type request_id() :: pos_integer().
%%==============================================================================
%% API
%%==============================================================================
-spec '$_cancelrequest'() -> ok.
'$_cancelrequest'() ->
- gen_server:call(?SERVER, {'$_cancelrequest'}).
+ gen_server:call(?SERVER, {'$_cancelrequest'}).
-spec '$_cancelrequest'(request_id()) -> ok.
'$_cancelrequest'(Id) ->
- gen_server:call(?SERVER, {'$_cancelrequest', Id}).
+ gen_server:call(?SERVER, {'$_cancelrequest', Id}).
-spec '$_settracenotification'() -> ok.
'$_settracenotification'() ->
- gen_server:call(?SERVER, {'$_settracenotification'}).
+ gen_server:call(?SERVER, {'$_settracenotification'}).
-spec '$_unexpectedrequest'() -> ok.
'$_unexpectedrequest'() ->
- gen_server:call(?SERVER, {'$_unexpectedrequest'}).
+ gen_server:call(?SERVER, {'$_unexpectedrequest'}).
%% TODO: More accurate and consistent parameters list
--spec completion( uri()
- , non_neg_integer()
- , non_neg_integer()
- , integer()
- , binary()
- ) ->
- ok.
+-spec completion(
+ uri(),
+ non_neg_integer(),
+ non_neg_integer(),
+ integer(),
+ binary()
+) ->
+ ok.
completion(Uri, Line, Char, TriggerKind, TriggerCharacter) ->
- Opts = {Uri, Line, Char, TriggerKind, TriggerCharacter},
- gen_server:call(?SERVER, {completion, Opts}).
+ Opts = {Uri, Line, Char, TriggerKind, TriggerCharacter},
+ gen_server:call(?SERVER, {completion, Opts}).
-spec completionitem_resolve(completion_item()) -> ok.
completionitem_resolve(CompletionItem) ->
- gen_server:call(?SERVER, {completionitem_resolve, CompletionItem}).
+ gen_server:call(?SERVER, {completionitem_resolve, CompletionItem}).
-spec definition(uri(), non_neg_integer(), non_neg_integer()) -> ok.
definition(Uri, Line, Char) ->
- gen_server:call(?SERVER, {definition, {Uri, Line, Char}}).
+ gen_server:call(?SERVER, {definition, {Uri, Line, Char}}).
-spec hover(uri(), non_neg_integer(), non_neg_integer()) -> ok.
hover(Uri, Line, Char) ->
- gen_server:call(?SERVER, {hover, {Uri, Line, Char}}).
+ gen_server:call(?SERVER, {hover, {Uri, Line, Char}}).
-spec implementation(uri(), non_neg_integer(), non_neg_integer()) -> ok.
implementation(Uri, Line, Char) ->
- gen_server:call(?SERVER, {implementation, {Uri, Line, Char}}).
+ gen_server:call(?SERVER, {implementation, {Uri, Line, Char}}).
-spec references(uri(), non_neg_integer(), non_neg_integer()) -> ok.
references(Uri, Line, Char) ->
- gen_server:call(?SERVER, {references, {Uri, Line, Char}}).
+ gen_server:call(?SERVER, {references, {Uri, Line, Char}}).
-spec document_highlight(uri(), non_neg_integer(), non_neg_integer()) -> ok.
document_highlight(Uri, Line, Char) ->
- gen_server:call(?SERVER, {document_highlight, {Uri, Line, Char}}).
+ gen_server:call(?SERVER, {document_highlight, {Uri, Line, Char}}).
-spec document_codeaction(uri(), range(), [els_diagnostics:diagnostic()]) -> ok.
document_codeaction(Uri, Range, Diagnostics) ->
- gen_server:call(?SERVER, {document_codeaction, {Uri, Range, Diagnostics}}).
+ gen_server:call(?SERVER, {document_codeaction, {Uri, Range, Diagnostics}}).
-spec document_codelens(uri()) -> ok.
document_codelens(Uri) ->
- gen_server:call(?SERVER, {document_codelens, {Uri}}).
+ gen_server:call(?SERVER, {document_codelens, {Uri}}).
-spec document_formatting(uri(), non_neg_integer(), boolean()) ->
- ok.
+ ok.
document_formatting(Uri, TabSize, InsertSpaces) ->
- gen_server:call(?SERVER, {document_formatting, {Uri, TabSize, InsertSpaces}}).
+ gen_server:call(?SERVER, {document_formatting, {Uri, TabSize, InsertSpaces}}).
-spec document_rangeformatting(uri(), range(), formatting_options()) ->
- ok.
+ ok.
document_rangeformatting(Uri, Range, FormattingOptions) ->
- gen_server:call(?SERVER, {document_rangeformatting,
- {Uri, Range, FormattingOptions}}).
-
--spec document_ontypeformatting(uri(), position(), string()
- , formatting_options()) ->
- ok.
+ gen_server:call(?SERVER, {document_rangeformatting, {Uri, Range, FormattingOptions}}).
+
+-spec document_ontypeformatting(
+ uri(),
+ position(),
+ string(),
+ formatting_options()
+) ->
+ ok.
document_ontypeformatting(Uri, Position, Char, FormattingOptions) ->
- gen_server:call(?SERVER, {document_ontypeformatting,
- {Uri, Position, Char, FormattingOptions}}).
+ gen_server:call(?SERVER, {document_ontypeformatting, {Uri, Position, Char, FormattingOptions}}).
-spec document_rename(uri(), non_neg_integer(), non_neg_integer(), binary()) ->
- ok.
+ ok.
document_rename(Uri, Line, Character, NewName) ->
- gen_server:call(?SERVER, {rename, {Uri, Line, Character, NewName}}).
+ gen_server:call(?SERVER, {rename, {Uri, Line, Character, NewName}}).
-spec did_open(uri(), binary(), number(), binary()) -> ok.
did_open(Uri, LanguageId, Version, Text) ->
- gen_server:call(?SERVER, {did_open, {Uri, LanguageId, Version, Text}}).
+ gen_server:call(?SERVER, {did_open, {Uri, LanguageId, Version, Text}}).
-spec did_save(uri()) -> ok.
did_save(Uri) ->
- gen_server:call(?SERVER, {did_save, {Uri}}).
+ gen_server:call(?SERVER, {did_save, {Uri}}).
-spec did_change_watched_files([{uri(), file_change_type()}]) -> ok.
did_change_watched_files(Changes) ->
- gen_server:call(?SERVER, {did_change_watched_files, {Changes}}).
+ gen_server:call(?SERVER, {did_change_watched_files, {Changes}}).
-spec did_close(uri()) -> ok.
did_close(Uri) ->
- gen_server:call(?SERVER, {did_close, {Uri}}).
+ gen_server:call(?SERVER, {did_close, {Uri}}).
-spec document_symbol(uri()) ->
- ok.
+ ok.
document_symbol(Uri) ->
- gen_server:call(?SERVER, {document_symbol, {Uri}}).
+ gen_server:call(?SERVER, {document_symbol, {Uri}}).
-spec folding_range(uri()) -> ok.
folding_range(Uri) ->
- gen_server:call(?SERVER, {folding_range, {Uri}}).
+ gen_server:call(?SERVER, {folding_range, {Uri}}).
-spec initialize(uri()) -> map().
initialize(RootUri) ->
- initialize(RootUri, #{}).
+ initialize(RootUri, #{}).
-spec initialize(uri(), init_options()) -> map().
initialize(RootUri, InitOptions) ->
- gen_server:call(?SERVER, {initialize, {RootUri, InitOptions}}, ?TIMEOUT).
+ gen_server:call(?SERVER, {initialize, {RootUri, InitOptions}}, ?TIMEOUT).
-spec initialized() -> map().
initialized() ->
- gen_server:call(?SERVER, {initialized, {}}).
+ gen_server:call(?SERVER, {initialized, {}}).
-spec shutdown() -> map().
shutdown() ->
- gen_server:call(?SERVER, {shutdown}).
+ gen_server:call(?SERVER, {shutdown}).
-spec exit() -> ok.
exit() ->
- gen_server:call(?SERVER, {exit}).
+ gen_server:call(?SERVER, {exit}).
-spec start_link(any()) -> {ok, pid()}.
start_link(Args) ->
- gen_server:start_link({local, ?SERVER}, ?MODULE, Args, []).
+ gen_server:start_link({local, ?SERVER}, ?MODULE, Args, []).
-spec stop() -> ok.
stop() ->
- gen_server:stop(?SERVER).
+ gen_server:stop(?SERVER).
-spec workspace_symbol(string()) ->
- ok.
+ ok.
workspace_symbol(Query) ->
- gen_server:call(?SERVER, {workspace_symbol, {Query}}).
+ gen_server:call(?SERVER, {workspace_symbol, {Query}}).
-spec workspace_executecommand(string(), [map()]) ->
- ok.
+ ok.
workspace_executecommand(Command, Args) ->
- gen_server:call(?SERVER, {workspace_executecommand, {Command, Args}}).
+ gen_server:call(?SERVER, {workspace_executecommand, {Command, Args}}).
-spec preparecallhierarchy(uri(), non_neg_integer(), non_neg_integer()) -> ok.
preparecallhierarchy(Uri, Line, Char) ->
- Args = {Uri, Line, Char},
- gen_server:call(?SERVER, {preparecallhierarchy, Args}).
+ Args = {Uri, Line, Char},
+ gen_server:call(?SERVER, {preparecallhierarchy, Args}).
-spec callhierarchy_incomingcalls(els_call_hierarchy_item:item()) -> ok.
callhierarchy_incomingcalls(Item) ->
- Args = {Item},
- gen_server:call(?SERVER, {callhierarchy_incomingcalls, Args}).
+ Args = {Item},
+ gen_server:call(?SERVER, {callhierarchy_incomingcalls, Args}).
-spec callhierarchy_outgoingcalls(els_call_hierarchy_item:item()) -> ok.
callhierarchy_outgoingcalls(Item) ->
- Args = {Item},
- gen_server:call(?SERVER, {callhierarchy_outgoingcalls, Args}).
+ Args = {Item},
+ gen_server:call(?SERVER, {callhierarchy_outgoingcalls, Args}).
-spec get_notifications() -> [any()].
get_notifications() ->
- gen_server:call(?SERVER, {get_notifications}).
+ gen_server:call(?SERVER, {get_notifications}).
-spec handle_responses([map()]) -> ok.
handle_responses(Responses) ->
- gen_server:cast(?SERVER, {handle_responses, Responses}).
+ gen_server:cast(?SERVER, {handle_responses, Responses}).
%%==============================================================================
%% gen_server Callback Functions
%%==============================================================================
-spec init(any()) -> {ok, state()}.
init(#{io_device := IoDevice}) ->
- Args = [ []
- , IoDevice
- , fun handle_responses/1
- , els_jsonrpc:default_opts()
- ],
- _Pid = proc_lib:spawn_link(els_stdio, loop, Args),
- State = #state{io_device = IoDevice},
- {ok, State}.
+ Args = [
+ [],
+ IoDevice,
+ fun handle_responses/1,
+ els_jsonrpc:default_opts()
+ ],
+ _Pid = proc_lib:spawn_link(els_stdio, loop, Args),
+ State = #state{io_device = IoDevice},
+ {ok, State}.
-spec handle_call(any(), any(), state()) -> {reply, any(), state()}.
handle_call({Action, Opts}, _From, State) when
@@ -279,87 +286,94 @@ handle_call({Action, Opts}, _From, State) when
Action =:= did_close;
Action =:= did_open;
Action =:= did_change_watched_files;
- Action =:= initialized ->
- #state{io_device = IoDevice} = State,
- Method = method_lookup(Action),
- Params = notification_params(Action, Opts),
- Content = els_protocol:notification(Method, Params),
- send(IoDevice, Content),
- {reply, ok, State};
+ Action =:= initialized
+->
+ #state{io_device = IoDevice} = State,
+ Method = method_lookup(Action),
+ Params = notification_params(Action, Opts),
+ Content = els_protocol:notification(Method, Params),
+ send(IoDevice, Content),
+ {reply, ok, State};
handle_call({exit}, _From, State) ->
- #state{io_device = IoDevice} = State,
- RequestId = State#state.request_id,
- Method = <<"exit">>,
- Params = #{},
- Content = els_protocol:request(RequestId, Method, Params),
- send(IoDevice, Content),
- {reply, ok, State};
+ #state{io_device = IoDevice} = State,
+ RequestId = State#state.request_id,
+ Method = <<"exit">>,
+ Params = #{},
+ Content = els_protocol:request(RequestId, Method, Params),
+ send(IoDevice, Content),
+ {reply, ok, State};
handle_call({shutdown}, From, State) ->
- #state{io_device = IoDevice} = State,
- RequestId = State#state.request_id,
- Method = <<"shutdown">>,
- Content = els_protocol:request(RequestId, Method),
- send(IoDevice, Content),
- {noreply, State#state{ request_id = RequestId + 1
- , pending = [{RequestId, From} | State#state.pending]
- }};
+ #state{io_device = IoDevice} = State,
+ RequestId = State#state.request_id,
+ Method = <<"shutdown">>,
+ Content = els_protocol:request(RequestId, Method),
+ send(IoDevice, Content),
+ {noreply, State#state{
+ request_id = RequestId + 1,
+ pending = [{RequestId, From} | State#state.pending]
+ }};
handle_call({'$_cancelrequest'}, _From, State) ->
- #state{request_id = Id} = State,
- do_cancel_request(Id - 1, State),
- {reply, ok, State};
+ #state{request_id = Id} = State,
+ do_cancel_request(Id - 1, State),
+ {reply, ok, State};
handle_call({'$_cancelrequest', Id}, _From, State) ->
- do_cancel_request(Id, State),
- {reply, ok, State};
+ do_cancel_request(Id, State),
+ {reply, ok, State};
handle_call({'$_settracenotification'}, _From, State) ->
- #state{io_device = IoDevice} = State,
- Method = <<"$/setTraceNotification">>,
- Params = #{value => <<"verbose">>},
- Content = els_protocol:notification(Method, Params),
- send(IoDevice, Content),
- {reply, ok, State};
+ #state{io_device = IoDevice} = State,
+ Method = <<"$/setTraceNotification">>,
+ Params = #{value => <<"verbose">>},
+ Content = els_protocol:notification(Method, Params),
+ send(IoDevice, Content),
+ {reply, ok, State};
handle_call({'$_unexpectedrequest'}, From, State) ->
- #state{io_device = IoDevice} = State,
- RequestId = State#state.request_id,
- Method = <<"$/unexpectedRequest">>,
- Params = #{},
- Content = els_protocol:request(RequestId, Method, Params),
- send(IoDevice, Content),
- {noreply, State#state{ request_id = RequestId + 1
- , pending = [{RequestId, From} | State#state.pending]
- }};
+ #state{io_device = IoDevice} = State,
+ RequestId = State#state.request_id,
+ Method = <<"$/unexpectedRequest">>,
+ Params = #{},
+ Content = els_protocol:request(RequestId, Method, Params),
+ send(IoDevice, Content),
+ {noreply, State#state{
+ request_id = RequestId + 1,
+ pending = [{RequestId, From} | State#state.pending]
+ }};
handle_call({get_notifications}, _From, State) ->
- #state{notifications = Notifications} = State,
- {reply, Notifications, State#state { notifications = []}};
+ #state{notifications = Notifications} = State,
+ {reply, Notifications, State#state{notifications = []}};
handle_call(Input = {Action, _}, From, State) ->
- #state{ io_device = IoDevice
- , request_id = RequestId
- } = State,
- Method = method_lookup(Action),
- Params = request_params(Input),
- Content = els_protocol:request(RequestId, Method, Params),
- send(IoDevice, Content),
- {noreply, State#state{ request_id = RequestId + 1
- , pending = [{RequestId, From} | State#state.pending]
- }}.
+ #state{
+ io_device = IoDevice,
+ request_id = RequestId
+ } = State,
+ Method = method_lookup(Action),
+ Params = request_params(Input),
+ Content = els_protocol:request(RequestId, Method, Params),
+ send(IoDevice, Content),
+ {noreply, State#state{
+ request_id = RequestId + 1,
+ pending = [{RequestId, From} | State#state.pending]
+ }}.
-spec handle_cast(any(), state()) -> {noreply, state()}.
handle_cast({handle_responses, Responses}, State) ->
- #state{ pending = Pending0
- , notifications = Notifications0
- , requests = Requests0
- } = State,
- {Pending, Notifications, Requests}
- = do_handle_messages(Responses, Pending0, Notifications0, Requests0),
- {noreply, State#state{ pending = Pending
- , notifications = Notifications
- , requests = Requests
- }};
+ #state{
+ pending = Pending0,
+ notifications = Notifications0,
+ requests = Requests0
+ } = State,
+ {Pending, Notifications, Requests} =
+ do_handle_messages(Responses, Pending0, Notifications0, Requests0),
+ {noreply, State#state{
+ pending = Pending,
+ notifications = Notifications,
+ requests = Requests
+ }};
handle_cast(_Request, State) ->
- {noreply, State}.
+ {noreply, State}.
-spec code_change(any(), state(), any()) -> {ok, state()}.
code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+ {ok, State}.
%%==============================================================================
%% Internal Functions
@@ -367,181 +381,209 @@ code_change(_OldVsn, State, _Extra) ->
%% @doc handle messages received from the transport layer
-spec do_handle_messages([map()], [any()], [any()], [any()]) ->
- {[any()], [any()], [any()]}.
+ {[any()], [any()], [any()]}.
do_handle_messages([], Pending, Notifications, Requests) ->
- {Pending, Notifications, Requests};
-do_handle_messages([Message|Messages], Pending, Notifications, Requests) ->
- case is_response(Message) of
- true ->
- RequestId = maps:get(id, Message),
- ?LOG_DEBUG("[CLIENT] Handling Response [response=~p]", [Message]),
- case lists:keyfind(RequestId, 1, Pending) of
- {RequestId, From} ->
- gen_server:reply(From, Message),
- do_handle_messages( Messages
- , lists:keydelete(RequestId, 1, Pending)
- , Notifications
- , Requests
- );
- false ->
- do_handle_messages(Messages, Pending, Notifications, Requests)
- end;
- false ->
- case is_notification(Message) of
+ {Pending, Notifications, Requests};
+do_handle_messages([Message | Messages], Pending, Notifications, Requests) ->
+ case is_response(Message) of
true ->
- ?LOG_DEBUG( "[CLIENT] Discarding Notification [message=~p]"
- , [Message]),
- do_handle_messages( Messages
- , Pending
- , [Message|Notifications]
- , Requests);
+ RequestId = maps:get(id, Message),
+ ?LOG_DEBUG("[CLIENT] Handling Response [response=~p]", [Message]),
+ case lists:keyfind(RequestId, 1, Pending) of
+ {RequestId, From} ->
+ gen_server:reply(From, Message),
+ do_handle_messages(
+ Messages,
+ lists:keydelete(RequestId, 1, Pending),
+ Notifications,
+ Requests
+ );
+ false ->
+ do_handle_messages(Messages, Pending, Notifications, Requests)
+ end;
false ->
- ?LOG_DEBUG( "[CLIENT] Discarding Server Request [message=~p]"
- , [Message]),
- do_handle_messages( Messages
- , Pending
- , Notifications
- , [Message|Requests])
- end
- end.
+ case is_notification(Message) of
+ true ->
+ ?LOG_DEBUG(
+ "[CLIENT] Discarding Notification [message=~p]",
+ [Message]
+ ),
+ do_handle_messages(
+ Messages,
+ Pending,
+ [Message | Notifications],
+ Requests
+ );
+ false ->
+ ?LOG_DEBUG(
+ "[CLIENT] Discarding Server Request [message=~p]",
+ [Message]
+ ),
+ do_handle_messages(
+ Messages,
+ Pending,
+ Notifications,
+ [Message | Requests]
+ )
+ end
+ end.
-spec method_lookup(atom()) -> binary().
-method_lookup(completion) -> <<"textDocument/completion">>;
-method_lookup(completionitem_resolve) -> <<"completionItem/resolve">>;
-method_lookup(definition) -> <<"textDocument/definition">>;
-method_lookup(document_symbol) -> <<"textDocument/documentSymbol">>;
-method_lookup(references) -> <<"textDocument/references">>;
-method_lookup(document_highlight) -> <<"textDocument/documentHighlight">>;
-method_lookup(document_codeaction) -> <<"textDocument/codeAction">>;
-method_lookup(document_codelens) -> <<"textDocument/codeLens">>;
-method_lookup(document_formatting) -> <<"textDocument/formatting">>;
+method_lookup(completion) -> <<"textDocument/completion">>;
+method_lookup(completionitem_resolve) -> <<"completionItem/resolve">>;
+method_lookup(definition) -> <<"textDocument/definition">>;
+method_lookup(document_symbol) -> <<"textDocument/documentSymbol">>;
+method_lookup(references) -> <<"textDocument/references">>;
+method_lookup(document_highlight) -> <<"textDocument/documentHighlight">>;
+method_lookup(document_codeaction) -> <<"textDocument/codeAction">>;
+method_lookup(document_codelens) -> <<"textDocument/codeLens">>;
+method_lookup(document_formatting) -> <<"textDocument/formatting">>;
method_lookup(document_rangeformatting) -> <<"textDocument/rangeFormatting">>;
method_lookup(document_ontypeormatting) -> <<"textDocument/onTypeFormatting">>;
-method_lookup(rename) -> <<"textDocument/rename">>;
-method_lookup(did_open) -> <<"textDocument/didOpen">>;
-method_lookup(did_save) -> <<"textDocument/didSave">>;
-method_lookup(did_close) -> <<"textDocument/didClose">>;
-method_lookup(hover) -> <<"textDocument/hover">>;
-method_lookup(implementation) -> <<"textDocument/implementation">>;
-method_lookup(folding_range) -> <<"textDocument/foldingRange">>;
+method_lookup(rename) -> <<"textDocument/rename">>;
+method_lookup(did_open) -> <<"textDocument/didOpen">>;
+method_lookup(did_save) -> <<"textDocument/didSave">>;
+method_lookup(did_close) -> <<"textDocument/didClose">>;
+method_lookup(hover) -> <<"textDocument/hover">>;
+method_lookup(implementation) -> <<"textDocument/implementation">>;
+method_lookup(folding_range) -> <<"textDocument/foldingRange">>;
method_lookup(preparecallhierarchy) -> <<"textDocument/prepareCallHierarchy">>;
method_lookup(callhierarchy_incomingcalls) -> <<"callHierarchy/incomingCalls">>;
method_lookup(callhierarchy_outgoingcalls) -> <<"callHierarchy/outgoingCalls">>;
-method_lookup(workspace_symbol) -> <<"workspace/symbol">>;
+method_lookup(workspace_symbol) -> <<"workspace/symbol">>;
method_lookup(workspace_executecommand) -> <<"workspace/executeCommand">>;
-method_lookup(did_change_watched_files) ->
- <<"workspace/didChangeWatchedFiles">>;
-method_lookup(initialize) -> <<"initialize">>;
-method_lookup(initialized) -> <<"initialized">>.
+method_lookup(did_change_watched_files) -> <<"workspace/didChangeWatchedFiles">>;
+method_lookup(initialize) -> <<"initialize">>;
+method_lookup(initialized) -> <<"initialized">>.
-spec request_params(tuple()) -> any().
request_params({document_symbol, {Uri}}) ->
- TextDocument = #{ uri => Uri },
- #{ textDocument => TextDocument };
+ TextDocument = #{uri => Uri},
+ #{textDocument => TextDocument};
request_params({workspace_symbol, {Query}}) ->
- #{ query => Query };
+ #{query => Query};
request_params({workspace_executecommand, {Command, Args}}) ->
- #{ command => Command
- , arguments => Args };
-request_params({ completion
- , {Uri, Line, Char, TriggerKind, TriggerCharacter}}) ->
- #{ textDocument => #{ uri => Uri }
- , position => #{ line => Line - 1
- , character => Char - 1
- }
- , context => #{ triggerKind => TriggerKind
- , triggerCharacter => TriggerCharacter
- }
- };
+ #{
+ command => Command,
+ arguments => Args
+ };
+request_params({completion, {Uri, Line, Char, TriggerKind, TriggerCharacter}}) ->
+ #{
+ textDocument => #{uri => Uri},
+ position => #{
+ line => Line - 1,
+ character => Char - 1
+ },
+ context => #{
+ triggerKind => TriggerKind,
+ triggerCharacter => TriggerCharacter
+ }
+ };
request_params({completionitem_resolve, CompletionItem}) ->
- CompletionItem;
+ CompletionItem;
request_params({initialize, {RootUri, InitOptions}}) ->
- ContentFormat = [ ?MARKDOWN , ?PLAINTEXT ],
- TextDocument = #{ <<"completion">> =>
- #{ <<"contextSupport">> => 'true'
- , <<"completionItem">> =>
- #{ <<"snippetSupport">> => 'true' }
- }
- , <<"hover">> =>
- #{ <<"contentFormat">> => ContentFormat }
- },
- #{ <<"rootUri">> => RootUri
- , <<"initializationOptions">> => InitOptions
- , <<"capabilities">> => #{ <<"textDocument">> => TextDocument }
- };
-request_params({ document_codeaction, {Uri, Range, Diagnostics}}) ->
- #{ textDocument => #{ uri => Uri }
- , range => Range
- , context => #{ diagnostics => Diagnostics }
- };
-request_params({ document_codelens, {Uri}}) ->
- #{ textDocument => #{ uri => Uri }};
-request_params({ document_formatting
- , {Uri, TabSize, InsertSpaces}}) ->
- #{ textDocument => #{ uri => Uri }
- , options => #{ tabSize => TabSize
- , insertSpaces => InsertSpaces
- }
- };
+ ContentFormat = [?MARKDOWN, ?PLAINTEXT],
+ TextDocument = #{
+ <<"completion">> =>
+ #{
+ <<"contextSupport">> => 'true',
+ <<"completionItem">> =>
+ #{<<"snippetSupport">> => 'true'}
+ },
+ <<"hover">> =>
+ #{<<"contentFormat">> => ContentFormat}
+ },
+ #{
+ <<"rootUri">> => RootUri,
+ <<"initializationOptions">> => InitOptions,
+ <<"capabilities">> => #{<<"textDocument">> => TextDocument}
+ };
+request_params({document_codeaction, {Uri, Range, Diagnostics}}) ->
+ #{
+ textDocument => #{uri => Uri},
+ range => Range,
+ context => #{diagnostics => Diagnostics}
+ };
+request_params({document_codelens, {Uri}}) ->
+ #{textDocument => #{uri => Uri}};
+request_params({document_formatting, {Uri, TabSize, InsertSpaces}}) ->
+ #{
+ textDocument => #{uri => Uri},
+ options => #{
+ tabSize => TabSize,
+ insertSpaces => InsertSpaces
+ }
+ };
request_params({rename, {Uri, Line, Character, NewName}}) ->
- #{ textDocument => #{ uri => Uri }
- , position => #{ line => Line
- , character => Character
- }
- , newName => NewName
- };
+ #{
+ textDocument => #{uri => Uri},
+ position => #{
+ line => Line,
+ character => Character
+ },
+ newName => NewName
+ };
request_params({folding_range, {Uri}}) ->
- TextDocument = #{ uri => Uri },
- #{ textDocument => TextDocument };
+ TextDocument = #{uri => Uri},
+ #{textDocument => TextDocument};
request_params({callhierarchy_incomingcalls, {Item}}) ->
- #{item => Item};
+ #{item => Item};
request_params({callhierarchy_outgoingcalls, {Item}}) ->
- #{item => Item};
+ #{item => Item};
request_params({_Action, {Uri, Line, Char}}) ->
- #{ textDocument => #{ uri => Uri }
- , position => #{ line => Line - 1
- , character => Char - 1
- }
- }.
+ #{
+ textDocument => #{uri => Uri},
+ position => #{
+ line => Line - 1,
+ character => Char - 1
+ }
+ }.
-spec notification_params(atom(), tuple()) -> map().
notification_params(did_change_watched_files, {Changes}) ->
- #{changes => [#{ uri => Uri
- , type => Type
- } || {Uri, Type} <- Changes]};
+ #{
+ changes => [
+ #{
+ uri => Uri,
+ type => Type
+ }
+ || {Uri, Type} <- Changes
+ ]
+ };
notification_params(_Action, {Uri}) ->
- TextDocument = #{ uri => Uri },
- #{textDocument => TextDocument};
+ TextDocument = #{uri => Uri},
+ #{textDocument => TextDocument};
notification_params(_Action, {Uri, LanguageId, Version, Text}) ->
- TextDocument = #{ uri => Uri
- , languageId => LanguageId
- , version => Version
- , text => Text
- },
- #{textDocument => TextDocument};
+ TextDocument = #{
+ uri => Uri,
+ languageId => LanguageId,
+ version => Version,
+ text => Text
+ },
+ #{textDocument => TextDocument};
notification_params(_Action, {}) ->
- #{}.
+ #{}.
-spec is_notification(map()) -> boolean().
is_notification(#{id := _Id}) ->
- false;
+ false;
is_notification(_) ->
- true.
+ true.
-spec is_response(map()) -> boolean().
is_response(#{method := _Method}) ->
- false;
+ false;
is_response(_) ->
- true.
+ true.
-spec do_cancel_request(request_id(), state()) -> ok.
do_cancel_request(Id, State) ->
- #state{io_device = IoDevice} = State,
- Method = <<"$/cancelRequest">>,
- Params = #{id => Id},
- Content = els_protocol:notification(Method, Params),
- send(IoDevice, Content).
+ #state{io_device = IoDevice} = State,
+ Method = <<"$/cancelRequest">>,
+ Params = #{id => Id},
+ Content = els_protocol:notification(Method, Params),
+ send(IoDevice, Content).
-spec send(atom() | pid(), binary()) -> ok.
send(IoDevice, Payload) ->
- els_stdio:send(IoDevice, Payload).
+ els_stdio:send(IoDevice, Payload).
diff --git a/apps/els_core/src/els_command.erl b/apps/els_core/src/els_command.erl
index b751ade32..8e7f13953 100644
--- a/apps/els_core/src/els_command.erl
+++ b/apps/els_core/src/els_command.erl
@@ -6,28 +6,31 @@
%%==============================================================================
%% API
%%==============================================================================
--export([ with_prefix/1
- , without_prefix/1
- ]).
+-export([
+ with_prefix/1,
+ without_prefix/1
+]).
%%==============================================================================
%% Constructors
%%==============================================================================
--export([ make_command/3 ]).
+-export([make_command/3]).
%%==============================================================================
%% Type Definitions
%%==============================================================================
--type command() :: #{ title := binary()
- , command := command_id()
- , arguments => [any()]
- }.
+-type command() :: #{
+ title := binary(),
+ command := command_id(),
+ arguments => [any()]
+}.
-type command_id() :: binary().
--export_type([ command/0
- , command_id/0
- ]).
+-export_type([
+ command/0,
+ command_id/0
+]).
%%==============================================================================
%% API
@@ -36,16 +39,16 @@
%% @doc Add a server-unique prefix to a command.
-spec with_prefix(command_id()) -> command_id().
with_prefix(Id) ->
- Prefix = server_prefix(),
- <>.
+ Prefix = server_prefix(),
+ <>.
%% @doc Strip a server-unique prefix from a command.
-spec without_prefix(command_id()) -> command_id().
without_prefix(Id0) ->
- case binary:split(Id0, <<":">>) of
- [_, Id] -> Id;
- [Id] -> Id
- end.
+ case binary:split(Id0, <<":">>) of
+ [_, Id] -> Id;
+ [Id] -> Id
+ end.
%%==============================================================================
%% Constructors
@@ -53,10 +56,11 @@ without_prefix(Id0) ->
-spec make_command(binary(), command_id(), [map()]) -> command().
make_command(Title, CommandId, Args) ->
- #{ title => Title
- , command => with_prefix(CommandId)
- , arguments => Args
- }.
+ #{
+ title => Title,
+ command => with_prefix(CommandId),
+ arguments => Args
+ }.
%%==============================================================================
%% Internal Functions
@@ -69,4 +73,4 @@ make_command(Title, CommandId, Args) ->
%% erlang_ls instances at the same time against a single client.
-spec server_prefix() -> binary().
server_prefix() ->
- els_utils:to_binary(os:getpid()).
+ els_utils:to_binary(os:getpid()).
diff --git a/apps/els_core/src/els_config.erl b/apps/els_core/src/els_config.erl
index 79b8184da..741207016 100644
--- a/apps/els_core/src/els_config.erl
+++ b/apps/els_core/src/els_config.erl
@@ -1,19 +1,21 @@
-module(els_config).
%% API
--export([ do_initialize/4
- , initialize/3
- , initialize/4
- , get/1
- , set/2
- , start_link/0
- ]).
+-export([
+ do_initialize/4,
+ initialize/3,
+ initialize/4,
+ get/1,
+ set/2,
+ start_link/0
+]).
%% gen_server callbacks
--export([ init/1
- , handle_call/3
- , handle_cast/2
- ]).
+-export([
+ init/1,
+ handle_call/3,
+ handle_cast/2
+]).
%%==============================================================================
%% Includes
@@ -26,58 +28,59 @@
%%==============================================================================
-define(DEFAULT_CONFIG_FILE, "erlang_ls.config").
-define(ALTERNATIVE_CONFIG_FILE, "erlang_ls.yaml").
--define( DEFAULT_EXCLUDED_OTP_APPS
- , [ "megaco"
- , "diameter"
- , "snmp"
- , "wx"
- ]
- ).
+-define(DEFAULT_EXCLUDED_OTP_APPS, [
+ "megaco",
+ "diameter",
+ "snmp",
+ "wx"
+]).
-define(SERVER, ?MODULE).
%% TODO: Refine names to avoid confusion
--type key() :: apps_dirs
- | apps_paths
- | capabilities
- | diagnostics
- | deps_dirs
- | deps_paths
- | include_dirs
- | include_paths
- | lenses
- | otp_path
- | otp_paths
- | otp_apps_exclude
- | plt_path
- | root_uri
- | search_paths
- | code_reload
- | elvis_config_path
- | indexing_enabled
- | compiler_telemetry_enabled
- | refactorerl
- | edoc_custom_tags.
-
--type path() :: file:filename().
--type state() :: #{ apps_dirs => [path()]
- , apps_paths => [path()]
- , lenses => [els_code_lens:lens_id()]
- , diagnostics => [els_diagnostics:diagnostic_id()]
- , deps_dirs => [path()]
- , deps_paths => [path()]
- , include_dirs => [path()]
- , include_paths => [path()]
- , otp_path => path()
- , otp_paths => [path()]
- , otp_apps_exclude => [string()]
- , plt_path => path()
- , root_uri => uri()
- , search_paths => [path()]
- , code_reload => map() | 'disabled'
- , indexing_enabled => boolean()
- , compiler_telemetry_enabled => boolean()
- , refactorerl => map() | 'notconfigured'
- }.
+-type key() ::
+ apps_dirs
+ | apps_paths
+ | capabilities
+ | diagnostics
+ | deps_dirs
+ | deps_paths
+ | include_dirs
+ | include_paths
+ | lenses
+ | otp_path
+ | otp_paths
+ | otp_apps_exclude
+ | plt_path
+ | root_uri
+ | search_paths
+ | code_reload
+ | elvis_config_path
+ | indexing_enabled
+ | compiler_telemetry_enabled
+ | refactorerl
+ | edoc_custom_tags.
+
+-type path() :: file:filename().
+-type state() :: #{
+ apps_dirs => [path()],
+ apps_paths => [path()],
+ lenses => [els_code_lens:lens_id()],
+ diagnostics => [els_diagnostics:diagnostic_id()],
+ deps_dirs => [path()],
+ deps_paths => [path()],
+ include_dirs => [path()],
+ include_paths => [path()],
+ otp_path => path(),
+ otp_paths => [path()],
+ otp_apps_exclude => [string()],
+ plt_path => path(),
+ root_uri => uri(),
+ search_paths => [path()],
+ code_reload => map() | 'disabled',
+ indexing_enabled => boolean(),
+ compiler_telemetry_enabled => boolean(),
+ refactorerl => map() | 'notconfigured'
+}.
%%==============================================================================
%% Exported functions
@@ -85,116 +88,139 @@
-spec initialize(uri(), map(), map()) -> ok.
initialize(RootUri, Capabilities, InitOptions) ->
- initialize(RootUri, Capabilities, InitOptions, _ReportMissingConfig = false).
+ initialize(RootUri, Capabilities, InitOptions, _ReportMissingConfig = false).
-spec initialize(uri(), map(), map(), boolean()) -> ok.
initialize(RootUri, Capabilities, InitOptions, ReportMissingConfig) ->
- RootPath = els_utils:to_list(els_uri:path(RootUri)),
- ConfigPaths = config_paths(RootPath, InitOptions),
- {GlobalConfigPath, GlobalConfig} = consult_config(global_config_paths(),
- false),
- {LocalConfigPath, LocalConfig} = consult_config(ConfigPaths,
- ReportMissingConfig),
- ConfigPath = case LocalConfigPath of
- undefined -> GlobalConfigPath;
- _ -> LocalConfigPath
- end,
- %% Augment Config onto GlobalConfig
- Config = maps:merge(GlobalConfig, LocalConfig),
- do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}).
-
--spec do_initialize(uri(), map(), map(), {undefined|path(), map()}) -> ok.
+ RootPath = els_utils:to_list(els_uri:path(RootUri)),
+ ConfigPaths = config_paths(RootPath, InitOptions),
+ {GlobalConfigPath, GlobalConfig} = consult_config(
+ global_config_paths(),
+ false
+ ),
+ {LocalConfigPath, LocalConfig} = consult_config(
+ ConfigPaths,
+ ReportMissingConfig
+ ),
+ ConfigPath =
+ case LocalConfigPath of
+ undefined -> GlobalConfigPath;
+ _ -> LocalConfigPath
+ end,
+ %% Augment Config onto GlobalConfig
+ Config = maps:merge(GlobalConfig, LocalConfig),
+ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}).
+
+-spec do_initialize(uri(), map(), map(), {undefined | path(), map()}) -> ok.
do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
- RootPath = els_utils:to_list(els_uri:path(RootUri)),
- OtpPath = maps:get("otp_path", Config, code:root_dir()),
- ?LOG_INFO("OTP Path: ~p", [OtpPath]),
- DepsDirs = maps:get("deps_dirs", Config, []),
- AppsDirs = maps:get("apps_dirs", Config, ["."]),
- IncludeDirs = maps:get("include_dirs", Config, ["include"]),
- ExcludeUnusedIncludes = maps:get("exclude_unused_includes", Config, []),
- Macros = maps:get("macros", Config, []),
- DialyzerPltPath = maps:get("plt_path", Config, undefined),
- OtpAppsExclude = maps:get( "otp_apps_exclude"
- , Config
- , ?DEFAULT_EXCLUDED_OTP_APPS
- ),
- Lenses = maps:get("lenses", Config, #{}),
- Diagnostics = maps:get("diagnostics", Config, #{}),
- ExcludePathsSpecs = [[OtpPath, "lib", P ++ "*"] || P <- OtpAppsExclude],
- ExcludePaths = els_utils:resolve_paths(ExcludePathsSpecs, RootPath, true),
- ?LOG_INFO("Excluded OTP Applications: ~p", [OtpAppsExclude]),
- CodeReload = maps:get("code_reload", Config, disabled),
- Runtime = maps:get("runtime", Config, #{}),
- CtRunTest = maps:get("ct-run-test", Config, #{}),
- CodePathExtraDirs = maps:get("code_path_extra_dirs", Config, []),
- ok = add_code_paths(CodePathExtraDirs, RootPath),
- ElvisConfigPath = maps:get("elvis_config_path", Config, undefined),
- IncrementalSync = maps:get("incremental_sync", Config, true),
- Indexing = maps:get("indexing", Config, #{}),
- CompilerTelemetryEnabled
- = maps:get("compiler_telemetry_enabled", Config, false),
- EDocCustomTags = maps:get("edoc_custom_tags", Config, []),
-
- IndexingEnabled = maps:get(<<"indexingEnabled">>, InitOptions, true),
-
- RefactorErl = maps:get("refactorerl", Config, notconfigured),
-
- %% Passed by the LSP client
- ok = set(root_uri , RootUri),
- %% Read from the configuration file
- ok = set(config_path , ConfigPath),
- ok = set(otp_path , OtpPath),
- ok = set(deps_dirs , DepsDirs),
- ok = set(apps_dirs , AppsDirs),
- ok = set(include_dirs , IncludeDirs),
- ok = set(exclude_unused_includes , ExcludeUnusedIncludes),
- ok = set(macros , Macros),
- ok = set(plt_path , DialyzerPltPath),
- ok = set(code_reload , CodeReload),
- ?LOG_INFO("Config=~p", [Config]),
- ok = set(runtime, maps:merge( els_config_runtime:default_config()
- , Runtime)),
- ok = set('ct-run-test', maps:merge( els_config_ct_run_test:default_config()
- , CtRunTest)),
- ok = set(elvis_config_path, ElvisConfigPath),
- ok = set(compiler_telemetry_enabled, CompilerTelemetryEnabled),
- ok = set(edoc_custom_tags, EDocCustomTags),
- ok = set(incremental_sync, IncrementalSync),
- ok = set(indexing, maps:merge( els_config_indexing:default_config()
- , Indexing)),
- %% Calculated from the above
- ok = set(apps_paths , project_paths(RootPath, AppsDirs, false)),
- ok = set(deps_paths , project_paths(RootPath, DepsDirs, false)),
- ok = set(include_paths , include_paths(RootPath, IncludeDirs, false)),
- ok = set(otp_paths , otp_paths(OtpPath, false) -- ExcludePaths),
- ok = set(lenses , Lenses),
- ok = set(diagnostics , Diagnostics),
- %% All (including subdirs) paths used to search files with file:path_open/3
- ok = set( search_paths
- , lists:append([ project_paths(RootPath, AppsDirs, true)
- , project_paths(RootPath, DepsDirs, true)
- , include_paths(RootPath, IncludeDirs, false)
- , otp_paths(OtpPath, true)
- ])
- ),
- %% Init Options
- ok = set(capabilities , Capabilities),
- ok = set(indexing_enabled, IndexingEnabled),
-
- ok = set(refactorerl, RefactorErl),
- ok.
+ RootPath = els_utils:to_list(els_uri:path(RootUri)),
+ OtpPath = maps:get("otp_path", Config, code:root_dir()),
+ ?LOG_INFO("OTP Path: ~p", [OtpPath]),
+ DepsDirs = maps:get("deps_dirs", Config, []),
+ AppsDirs = maps:get("apps_dirs", Config, ["."]),
+ IncludeDirs = maps:get("include_dirs", Config, ["include"]),
+ ExcludeUnusedIncludes = maps:get("exclude_unused_includes", Config, []),
+ Macros = maps:get("macros", Config, []),
+ DialyzerPltPath = maps:get("plt_path", Config, undefined),
+ OtpAppsExclude = maps:get(
+ "otp_apps_exclude",
+ Config,
+ ?DEFAULT_EXCLUDED_OTP_APPS
+ ),
+ Lenses = maps:get("lenses", Config, #{}),
+ Diagnostics = maps:get("diagnostics", Config, #{}),
+ ExcludePathsSpecs = [[OtpPath, "lib", P ++ "*"] || P <- OtpAppsExclude],
+ ExcludePaths = els_utils:resolve_paths(ExcludePathsSpecs, RootPath, true),
+ ?LOG_INFO("Excluded OTP Applications: ~p", [OtpAppsExclude]),
+ CodeReload = maps:get("code_reload", Config, disabled),
+ Runtime = maps:get("runtime", Config, #{}),
+ CtRunTest = maps:get("ct-run-test", Config, #{}),
+ CodePathExtraDirs = maps:get("code_path_extra_dirs", Config, []),
+ ok = add_code_paths(CodePathExtraDirs, RootPath),
+ ElvisConfigPath = maps:get("elvis_config_path", Config, undefined),
+ IncrementalSync = maps:get("incremental_sync", Config, true),
+ Indexing = maps:get("indexing", Config, #{}),
+ CompilerTelemetryEnabled =
+ maps:get("compiler_telemetry_enabled", Config, false),
+ EDocCustomTags = maps:get("edoc_custom_tags", Config, []),
+
+ IndexingEnabled = maps:get(<<"indexingEnabled">>, InitOptions, true),
+
+ RefactorErl = maps:get("refactorerl", Config, notconfigured),
+
+ %% Passed by the LSP client
+ ok = set(root_uri, RootUri),
+ %% Read from the configuration file
+ ok = set(config_path, ConfigPath),
+ ok = set(otp_path, OtpPath),
+ ok = set(deps_dirs, DepsDirs),
+ ok = set(apps_dirs, AppsDirs),
+ ok = set(include_dirs, IncludeDirs),
+ ok = set(exclude_unused_includes, ExcludeUnusedIncludes),
+ ok = set(macros, Macros),
+ ok = set(plt_path, DialyzerPltPath),
+ ok = set(code_reload, CodeReload),
+ ?LOG_INFO("Config=~p", [Config]),
+ ok = set(
+ runtime,
+ maps:merge(
+ els_config_runtime:default_config(),
+ Runtime
+ )
+ ),
+ ok = set(
+ 'ct-run-test',
+ maps:merge(
+ els_config_ct_run_test:default_config(),
+ CtRunTest
+ )
+ ),
+ ok = set(elvis_config_path, ElvisConfigPath),
+ ok = set(compiler_telemetry_enabled, CompilerTelemetryEnabled),
+ ok = set(edoc_custom_tags, EDocCustomTags),
+ ok = set(incremental_sync, IncrementalSync),
+ ok = set(
+ indexing,
+ maps:merge(
+ els_config_indexing:default_config(),
+ Indexing
+ )
+ ),
+ %% Calculated from the above
+ ok = set(apps_paths, project_paths(RootPath, AppsDirs, false)),
+ ok = set(deps_paths, project_paths(RootPath, DepsDirs, false)),
+ ok = set(include_paths, include_paths(RootPath, IncludeDirs, false)),
+ ok = set(otp_paths, otp_paths(OtpPath, false) -- ExcludePaths),
+ ok = set(lenses, Lenses),
+ ok = set(diagnostics, Diagnostics),
+ %% All (including subdirs) paths used to search files with file:path_open/3
+ ok = set(
+ search_paths,
+ lists:append([
+ project_paths(RootPath, AppsDirs, true),
+ project_paths(RootPath, DepsDirs, true),
+ include_paths(RootPath, IncludeDirs, false),
+ otp_paths(OtpPath, true)
+ ])
+ ),
+ %% Init Options
+ ok = set(capabilities, Capabilities),
+ ok = set(indexing_enabled, IndexingEnabled),
+
+ ok = set(refactorerl, RefactorErl),
+ ok.
-spec start_link() -> {ok, pid()}.
start_link() ->
- gen_server:start_link({local, ?SERVER}, ?MODULE, {}, []).
+ gen_server:start_link({local, ?SERVER}, ?MODULE, {}, []).
-spec get(key()) -> any().
get(Key) ->
- gen_server:call(?SERVER, {get, Key}).
+ gen_server:call(?SERVER, {get, Key}).
-spec set(key(), any()) -> ok.
set(Key, Value) ->
- gen_server:call(?SERVER, {set, Key, Value}).
+ gen_server:call(?SERVER, {set, Key, Value}).
%%==============================================================================
%% gen_server Callback Functions
@@ -202,16 +228,16 @@ set(Key, Value) ->
-spec init({}) -> {ok, state()}.
init({}) ->
- {ok, #{}}.
+ {ok, #{}}.
-spec handle_call(any(), any(), state()) ->
- {reply, any(), state()}.
+ {reply, any(), state()}.
handle_call({get, Key}, _From, State) ->
- Value = maps:get(Key, State, undefined),
- {reply, Value, State};
+ Value = maps:get(Key, State, undefined),
+ {reply, Value, State};
handle_call({set, Key, Value}, _From, State0) ->
- State = maps:put(Key, Value, State0),
- {reply, ok, State}.
+ State = maps:put(Key, Value, State0),
+ {reply, ok, State}.
-spec handle_cast(any(), state()) -> {noreply, state()}.
handle_cast(_Msg, State) -> {noreply, State}.
@@ -221,135 +247,167 @@ handle_cast(_Msg, State) -> {noreply, State}.
%%==============================================================================
-spec config_paths(path(), map()) -> [path()].
-config_paths( RootPath
- , #{<<"erlang">> := #{<<"config_path">> := ConfigPath0}}) ->
- ConfigPath = els_utils:to_list(ConfigPath0),
- lists:append([ possible_config_paths(ConfigPath)
- , possible_config_paths(filename:join([RootPath, ConfigPath]))
- , default_config_paths(RootPath)]);
+config_paths(
+ RootPath,
+ #{<<"erlang">> := #{<<"config_path">> := ConfigPath0}}
+) ->
+ ConfigPath = els_utils:to_list(ConfigPath0),
+ lists:append([
+ possible_config_paths(ConfigPath),
+ possible_config_paths(filename:join([RootPath, ConfigPath])),
+ default_config_paths(RootPath)
+ ]);
config_paths(RootPath, _Config) ->
- default_config_paths(RootPath).
+ default_config_paths(RootPath).
-spec default_config_paths(path()) -> [path()].
default_config_paths(RootPath) ->
- [ filename:join([RootPath, ?DEFAULT_CONFIG_FILE])
- , filename:join([RootPath, ?ALTERNATIVE_CONFIG_FILE])
- ].
+ [
+ filename:join([RootPath, ?DEFAULT_CONFIG_FILE]),
+ filename:join([RootPath, ?ALTERNATIVE_CONFIG_FILE])
+ ].
-spec global_config_paths() -> [path()].
global_config_paths() ->
- GlobalConfigDir = filename:basedir(user_config, "erlang_ls"),
- [ filename:join([GlobalConfigDir, ?DEFAULT_CONFIG_FILE])
- , filename:join([GlobalConfigDir, ?ALTERNATIVE_CONFIG_FILE])
- ].
+ GlobalConfigDir = filename:basedir(user_config, "erlang_ls"),
+ [
+ filename:join([GlobalConfigDir, ?DEFAULT_CONFIG_FILE]),
+ filename:join([GlobalConfigDir, ?ALTERNATIVE_CONFIG_FILE])
+ ].
%% @doc Bare `Path' as well as with default config file name suffix.
-spec possible_config_paths(path()) -> [path()].
possible_config_paths(Path) ->
- [ Path
- , filename:join([Path, ?DEFAULT_CONFIG_FILE])
- , filename:join([Path, ?ALTERNATIVE_CONFIG_FILE])
- ].
+ [
+ Path,
+ filename:join([Path, ?DEFAULT_CONFIG_FILE]),
+ filename:join([Path, ?ALTERNATIVE_CONFIG_FILE])
+ ].
--spec consult_config([path()], boolean()) -> {undefined|path(), map()}.
+-spec consult_config([path()], boolean()) -> {undefined | path(), map()}.
consult_config([], ReportMissingConfig) ->
- ?LOG_INFO("No config file found."),
- case ReportMissingConfig of
- true ->
- report_missing_config();
- false ->
- ok
- end,
- {undefined, #{}};
+ ?LOG_INFO("No config file found."),
+ case ReportMissingConfig of
+ true ->
+ report_missing_config();
+ false ->
+ ok
+ end,
+ {undefined, #{}};
consult_config([Path | Paths], ReportMissingConfig) ->
- ?LOG_INFO("Reading config file. path=~p", [Path]),
- Options = [{map_node_format, map}],
- try yamerl:decode_file(Path, Options) of
- [] -> {Path, #{}};
- [Config] -> {Path, Config}
- catch
- Class:Error ->
- ?LOG_DEBUG( "Could not read config file: path=~p class=~p error=~p"
- , [Path, Class, Error]),
- consult_config(Paths, ReportMissingConfig)
- end.
+ ?LOG_INFO("Reading config file. path=~p", [Path]),
+ Options = [{map_node_format, map}],
+ try yamerl:decode_file(Path, Options) of
+ [] -> {Path, #{}};
+ [Config] -> {Path, Config}
+ catch
+ Class:Error ->
+ ?LOG_DEBUG(
+ "Could not read config file: path=~p class=~p error=~p",
+ [Path, Class, Error]
+ ),
+ consult_config(Paths, ReportMissingConfig)
+ end.
-spec report_missing_config() -> ok.
report_missing_config() ->
- Msg =
- io_lib:format("The current project is missing an erlang_ls.config file. "
- "Need help configuring Erlang LS for your project? "
- "Visit: https://erlang-ls.github.io/configuration/", []),
- els_server:send_notification(<<"window/showMessage">>,
- #{ type => ?MESSAGE_TYPE_WARNING,
- message => els_utils:to_binary(Msg)
- }),
- ok.
+ Msg =
+ io_lib:format(
+ "The current project is missing an erlang_ls.config file. "
+ "Need help configuring Erlang LS for your project? "
+ "Visit: https://erlang-ls.github.io/configuration/",
+ []
+ ),
+ els_server:send_notification(
+ <<"window/showMessage">>,
+ #{
+ type => ?MESSAGE_TYPE_WARNING,
+ message => els_utils:to_binary(Msg)
+ }
+ ),
+ ok.
-spec include_paths(path(), string(), boolean()) -> [string()].
include_paths(RootPath, IncludeDirs, Recursive) ->
- Paths = [ els_utils:resolve_paths([[RootPath, Dir]], RootPath, Recursive)
- || Dir <- IncludeDirs
- ],
- lists:append(Paths).
+ Paths = [
+ els_utils:resolve_paths([[RootPath, Dir]], RootPath, Recursive)
+ || Dir <- IncludeDirs
+ ],
+ lists:append(Paths).
-spec project_paths(path(), [string()], boolean()) -> [string()].
project_paths(RootPath, Dirs, Recursive) ->
- Paths = [ els_utils:resolve_paths( [ [RootPath, Dir, "src"]
- , [RootPath, Dir, "test"]
- , [RootPath, Dir, "include"]
- ]
- , RootPath
- , Recursive
- )
- || Dir <- Dirs
- ],
- case Recursive of
- false ->
- lists:append(Paths);
- true ->
- Filter = fun(Path) ->
- string:find(Path, "SUITE_data", trailing) =:= nomatch
- end,
- lists:filter(Filter, lists:append(Paths))
- end.
+ Paths = [
+ els_utils:resolve_paths(
+ [
+ [RootPath, Dir, "src"],
+ [RootPath, Dir, "test"],
+ [RootPath, Dir, "include"]
+ ],
+ RootPath,
+ Recursive
+ )
+ || Dir <- Dirs
+ ],
+ case Recursive of
+ false ->
+ lists:append(Paths);
+ true ->
+ Filter = fun(Path) ->
+ string:find(Path, "SUITE_data", trailing) =:= nomatch
+ end,
+ lists:filter(Filter, lists:append(Paths))
+ end.
-spec otp_paths(path(), boolean()) -> [string()].
otp_paths(OtpPath, Recursive) ->
- els_utils:resolve_paths( [ [OtpPath, "lib", "*", "src"]
- , [OtpPath, "lib", "*", "include"]
- ]
- , OtpPath
- , Recursive
- ).
-
--spec add_code_paths(Dirs :: list(string()),
- RooDir :: string()) ->
- ok.
+ els_utils:resolve_paths(
+ [
+ [OtpPath, "lib", "*", "src"],
+ [OtpPath, "lib", "*", "include"]
+ ],
+ OtpPath,
+ Recursive
+ ).
+
+-spec add_code_paths(
+ Dirs :: list(string()),
+ RooDir :: string()
+) ->
+ ok.
add_code_paths(WCDirs, RootDir) ->
- AddADir = fun(ADir) ->
- ?LOG_INFO("Adding code path: ~p", [ADir]),
- true = code:add_path(ADir)
- end,
- AllNames = lists:foldl(fun(Elem, AccIn) ->
- AccIn ++ filelib:wildcard(Elem, RootDir)
- end, [], WCDirs),
- Dirs = [ [$/ | safe_relative_path(Dir, RootDir)]
- || Name <- AllNames,
- filelib:is_dir([$/ | Dir] = filename:absname(Name, RootDir))
- ],
- lists:foreach(AddADir, Dirs).
+ AddADir = fun(ADir) ->
+ ?LOG_INFO("Adding code path: ~p", [ADir]),
+ true = code:add_path(ADir)
+ end,
+ AllNames = lists:foldl(
+ fun(Elem, AccIn) ->
+ AccIn ++ filelib:wildcard(Elem, RootDir)
+ end,
+ [],
+ WCDirs
+ ),
+ Dirs = [
+ [$/ | safe_relative_path(Dir, RootDir)]
+ || Name <- AllNames,
+ filelib:is_dir([$/ | Dir] = filename:absname(Name, RootDir))
+ ],
+ lists:foreach(AddADir, Dirs).
-if(?OTP_RELEASE >= 23).
--spec safe_relative_path(Dir :: file:name_all(),
- RootDir :: file:name_all()) ->
- Path :: file:name_all().
+-spec safe_relative_path(
+ Dir :: file:name_all(),
+ RootDir :: file:name_all()
+) ->
+ Path :: file:name_all().
safe_relative_path(Dir, RootDir) ->
- filelib:safe_relative_path(Dir, RootDir).
+ filelib:safe_relative_path(Dir, RootDir).
-else.
--spec safe_relative_path(FileName :: file:name_all(),
- RootDir :: file:name_all()) ->
- Path :: file:name_all().
+-spec safe_relative_path(
+ FileName :: file:name_all(),
+ RootDir :: file:name_all()
+) ->
+ Path :: file:name_all().
safe_relative_path(Dir, _) ->
- filename:safe_relative_path(Dir).
+ filename:safe_relative_path(Dir).
-endif.
diff --git a/apps/els_core/src/els_config_ct_run_test.erl b/apps/els_core/src/els_config_ct_run_test.erl
index 8db423a10..7e24459b1 100644
--- a/apps/els_core/src/els_config_ct_run_test.erl
+++ b/apps/els_core/src/els_config_ct_run_test.erl
@@ -3,37 +3,41 @@
-include_lib("els_core/include/els_core.hrl").
%% We may introduce a behaviour for config modules in the future
--export([ default_config/0 ]).
+-export([default_config/0]).
%% Getters
--export([ get_module/0
- , get_function/0
- ]).
+-export([
+ get_module/0,
+ get_function/0
+]).
--type config() :: #{ string() => string() }.
+-type config() :: #{string() => string()}.
-spec default_config() -> config().
default_config() ->
- #{ "module" => default_module()
- , "function" => default_function()
- }.
+ #{
+ "module" => default_module(),
+ "function" => default_function()
+ }.
-spec get_module() -> atom().
get_module() ->
- Value = maps:get("module", els_config:get('ct-run-test'), default_module()),
- list_to_atom(Value).
+ Value = maps:get("module", els_config:get('ct-run-test'), default_module()),
+ list_to_atom(Value).
-spec get_function() -> atom().
get_function() ->
- Value = maps:get( "function"
- , els_config:get('ct-run-test')
- , default_function()),
- list_to_atom(Value).
+ Value = maps:get(
+ "function",
+ els_config:get('ct-run-test'),
+ default_function()
+ ),
+ list_to_atom(Value).
-spec default_module() -> string().
default_module() ->
- "rebar3_erlang_ls_agent".
+ "rebar3_erlang_ls_agent".
-spec default_function() -> string().
default_function() ->
- "run_ct_test".
+ "run_ct_test".
diff --git a/apps/els_core/src/els_config_indexing.erl b/apps/els_core/src/els_config_indexing.erl
index 525f1faaf..0547647aa 100644
--- a/apps/els_core/src/els_config_indexing.erl
+++ b/apps/els_core/src/els_config_indexing.erl
@@ -2,41 +2,47 @@
-include("els_core.hrl").
--export([ default_config/0 ]).
+-export([default_config/0]).
%% Getters
--export([ get_skip_generated_files/0
- , get_generated_files_tag/0
- ]).
+-export([
+ get_skip_generated_files/0,
+ get_generated_files_tag/0
+]).
--type config() :: #{ string() => string() }.
+-type config() :: #{string() => string()}.
-spec default_config() -> config().
default_config() ->
- #{ "skip_generated_files" => default_skip_generated_files()
- , "generated_files_tag" => default_generated_files_tag()
- }.
+ #{
+ "skip_generated_files" => default_skip_generated_files(),
+ "generated_files_tag" => default_generated_files_tag()
+ }.
-spec get_skip_generated_files() -> boolean().
get_skip_generated_files() ->
- Value = maps:get("skip_generated_files",
- els_config:get(indexing),
- default_skip_generated_files()),
- normalize_boolean(Value).
+ Value = maps:get(
+ "skip_generated_files",
+ els_config:get(indexing),
+ default_skip_generated_files()
+ ),
+ normalize_boolean(Value).
-spec get_generated_files_tag() -> string().
get_generated_files_tag() ->
- maps:get("generated_files_tag",
- els_config:get(indexing),
- default_generated_files_tag()).
+ maps:get(
+ "generated_files_tag",
+ els_config:get(indexing),
+ default_generated_files_tag()
+ ).
-spec default_skip_generated_files() -> string().
default_skip_generated_files() ->
- "false".
+ "false".
-spec default_generated_files_tag() -> string().
default_generated_files_tag() ->
- "@generated".
+ "@generated".
-spec normalize_boolean(boolean() | string()) -> boolean().
normalize_boolean("true") -> true;
diff --git a/apps/els_core/src/els_config_runtime.erl b/apps/els_core/src/els_config_runtime.erl
index 786bd99d0..34dcbf5a5 100644
--- a/apps/els_core/src/els_config_runtime.erl
+++ b/apps/els_core/src/els_config_runtime.erl
@@ -3,79 +3,82 @@
-include("els_core.hrl").
%% We may introduce a behaviour for config modules in the future
--export([ default_config/0 ]).
+-export([default_config/0]).
%% Getters
--export([ get_node_name/0
- , get_otp_path/0
- , get_start_cmd/0
- , get_start_args/0
- , get_name_type/0
- , get_cookie/0
- ]).
+-export([
+ get_node_name/0,
+ get_otp_path/0,
+ get_start_cmd/0,
+ get_start_args/0,
+ get_name_type/0,
+ get_cookie/0
+]).
--type config() :: #{ string() => string() }.
+-type config() :: #{string() => string()}.
-spec default_config() -> config().
default_config() ->
- #{ "node_name" => default_node_name()
- , "otp_path" => default_otp_path()
- , "start_cmd" => default_start_cmd()
- , "start_args" => default_start_args()
- }.
+ #{
+ "node_name" => default_node_name(),
+ "otp_path" => default_otp_path(),
+ "start_cmd" => default_start_cmd(),
+ "start_args" => default_start_args()
+ }.
-spec get_node_name() -> atom().
get_node_name() ->
- Value = maps:get("node_name", els_config:get(runtime), default_node_name()),
- els_utils:compose_node_name(Value, get_name_type()).
+ Value = maps:get("node_name", els_config:get(runtime), default_node_name()),
+ els_utils:compose_node_name(Value, get_name_type()).
-spec get_otp_path() -> string().
get_otp_path() ->
- maps:get("otp_path", els_config:get(runtime), default_otp_path()).
+ maps:get("otp_path", els_config:get(runtime), default_otp_path()).
-spec get_start_cmd() -> string().
get_start_cmd() ->
- maps:get("start_cmd", els_config:get(runtime), default_start_cmd()).
+ maps:get("start_cmd", els_config:get(runtime), default_start_cmd()).
-spec get_start_args() -> [string()].
get_start_args() ->
- Value = maps:get("start_args", els_config:get(runtime), default_start_args()),
- string:tokens(Value, " ").
+ Value = maps:get("start_args", els_config:get(runtime), default_start_args()),
+ string:tokens(Value, " ").
-spec get_name_type() -> shortnames | longnames.
get_name_type() ->
- case maps:get("use_long_names", els_config:get(runtime), false) of
- false ->
- shortnames;
- true ->
- longnames
- end.
+ case maps:get("use_long_names", els_config:get(runtime), false) of
+ false ->
+ shortnames;
+ true ->
+ longnames
+ end.
-spec get_cookie() -> atom().
get_cookie() ->
- case maps:get("cookie", els_config:get(runtime), undefined) of
- undefined ->
- erlang:get_cookie();
- Cookie ->
- list_to_atom(Cookie)
+ case maps:get("cookie", els_config:get(runtime), undefined) of
+ undefined ->
+ erlang:get_cookie();
+ Cookie ->
+ list_to_atom(Cookie)
end.
-spec default_node_name() -> string().
default_node_name() ->
- RootUri = els_config:get(root_uri),
- {ok, Hostname} = inet:gethostname(),
- NodeName = els_distribution_server:normalize_node_name(
- filename:basename(els_uri:path(RootUri))),
- NodeName ++ "@" ++ Hostname.
+ RootUri = els_config:get(root_uri),
+ {ok, Hostname} = inet:gethostname(),
+ NodeName = els_distribution_server:normalize_node_name(
+ filename:basename(els_uri:path(RootUri))
+ ),
+ NodeName ++ "@" ++ Hostname.
-spec default_otp_path() -> string().
default_otp_path() ->
- filename:dirname(filename:dirname(code:root_dir())).
+ filename:dirname(filename:dirname(code:root_dir())).
-spec default_start_cmd() -> string().
default_start_cmd() ->
- "rebar3".
+ "rebar3".
-spec default_start_args() -> string().
default_start_args() ->
- "erlang_ls".
+ "erlang_ls".
diff --git a/apps/els_core/src/els_distribution_server.erl b/apps/els_core/src/els_distribution_server.erl
index fee244876..6b30da914 100644
--- a/apps/els_core/src/els_distribution_server.erl
+++ b/apps/els_core/src/els_distribution_server.erl
@@ -8,28 +8,30 @@
%%==============================================================================
%% API
%%==============================================================================
--export([ start_link/0
- , start_distribution/1
- , start_distribution/4
- , connect/0
- , wait_connect_and_monitor/1
- , wait_connect_and_monitor/3
- , rpc_call/3
- , rpc_call/4
- , node_name/2
- , node_name/3
- , normalize_node_name/1
- ]).
+-export([
+ start_link/0,
+ start_distribution/1,
+ start_distribution/4,
+ connect/0,
+ wait_connect_and_monitor/1,
+ wait_connect_and_monitor/3,
+ rpc_call/3,
+ rpc_call/4,
+ node_name/2,
+ node_name/3,
+ normalize_node_name/1
+]).
%%==============================================================================
%% Callbacks for the gen_server behaviour
%%==============================================================================
-behaviour(gen_server).
--export([ init/1
- , handle_call/3
- , handle_cast/2
- , handle_info/2
- ]).
+-export([
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2
+]).
%%==============================================================================
%% Includes
@@ -54,197 +56,202 @@
%%==============================================================================
-spec start_link() -> {ok, pid()}.
start_link() ->
- gen_server:start_link({local, ?SERVER}, ?MODULE, unused, []).
+ gen_server:start_link({local, ?SERVER}, ?MODULE, unused, []).
%% @doc Turns a non-distributed node into a distributed one
-spec start_distribution(atom()) -> ok | {error, any()}.
start_distribution(Name) ->
- Cookie = els_config_runtime:get_cookie(),
- RemoteNode = els_config_runtime:get_node_name(),
- NameType = els_config_runtime:get_name_type(),
- start_distribution(Name, RemoteNode, Cookie, NameType).
+ Cookie = els_config_runtime:get_cookie(),
+ RemoteNode = els_config_runtime:get_node_name(),
+ NameType = els_config_runtime:get_name_type(),
+ start_distribution(Name, RemoteNode, Cookie, NameType).
-spec start_distribution(atom(), atom(), atom(), shortnames | longnames) ->
- ok | {error, any()}.
+ ok | {error, any()}.
start_distribution(Name, RemoteNode, Cookie, NameType) ->
- ?LOG_INFO("Enable distribution [name=~p]", [Name]),
- case net_kernel:start([Name, NameType]) of
- {ok, _Pid} ->
- case Cookie of
- nocookie ->
- ok;
- CustomCookie ->
- erlang:set_cookie(RemoteNode, CustomCookie)
- end,
- ?LOG_INFO("Distribution enabled [name=~p]", [Name]),
- ok;
- {error, {already_started, _Pid}} ->
- ?LOG_INFO("Distribution already enabled [name=~p]", [Name]),
- ok;
- {error, Error} ->
- ?LOG_WARNING("Distribution shutdown [error=~p] [name=~p]", [Error, Name]),
- {error, Error}
- end.
+ ?LOG_INFO("Enable distribution [name=~p]", [Name]),
+ case net_kernel:start([Name, NameType]) of
+ {ok, _Pid} ->
+ case Cookie of
+ nocookie ->
+ ok;
+ CustomCookie ->
+ erlang:set_cookie(RemoteNode, CustomCookie)
+ end,
+ ?LOG_INFO("Distribution enabled [name=~p]", [Name]),
+ ok;
+ {error, {already_started, _Pid}} ->
+ ?LOG_INFO("Distribution already enabled [name=~p]", [Name]),
+ ok;
+ {error, Error} ->
+ ?LOG_WARNING("Distribution shutdown [error=~p] [name=~p]", [Error, Name]),
+ {error, Error}
+ end.
%% @doc Connect to an existing runtime node, if available, or start one.
-spec connect() -> ok.
connect() ->
- gen_server:call(?SERVER, {connect}, infinity).
+ gen_server:call(?SERVER, {connect}, infinity).
%% @doc Make a RPC call towards the runtime node.
-spec rpc_call(atom(), atom(), [any()]) -> {any(), binary()}.
rpc_call(M, F, A) ->
- rpc_call(M, F, A, ?RPC_TIMEOUT).
+ rpc_call(M, F, A, ?RPC_TIMEOUT).
%% @doc Make a RPC call towards the runtime node.
-spec rpc_call(atom(), atom(), [any()], timeout()) -> {any(), binary()}.
rpc_call(M, F, A, Timeout) ->
- gen_server:call(?SERVER, {rpc_call, M, F, A, Timeout}, Timeout).
+ gen_server:call(?SERVER, {rpc_call, M, F, A, Timeout}, Timeout).
%%==============================================================================
%% Callbacks for the gen_server behaviour
%%==============================================================================
-spec init(unused) -> {ok, state()}.
init(unused) ->
- ?LOG_INFO("Ensure EPMD is running", []),
- ok = ensure_epmd(),
- {ok, #{}}.
+ ?LOG_INFO("Ensure EPMD is running", []),
+ ok = ensure_epmd(),
+ {ok, #{}}.
-spec handle_call(any(), {pid(), any()}, state()) ->
- {reply, any(), state()} | {noreply, state()}.
+ {reply, any(), state()} | {noreply, state()}.
handle_call({connect}, _From, State) ->
- Node = els_config_runtime:get_node_name(),
- case connect_and_monitor(Node, not_hidden) of
- ok ->
- ok;
- error ->
- ok = start(Node)
- end,
- {reply, ok, State};
+ Node = els_config_runtime:get_node_name(),
+ case connect_and_monitor(Node, not_hidden) of
+ ok ->
+ ok;
+ error ->
+ ok = start(Node)
+ end,
+ {reply, ok, State};
handle_call({rpc_call, M, F, A, Timeout}, _From, State) ->
- {ok, P} = els_group_leader_server:new(),
- Node = els_config_runtime:get_node_name(),
- ?LOG_INFO("RPC Call [node=~p] [mfa=~p]", [Node, {M, F, A}]),
- Result = rpc:call(Node, M, F, A, Timeout),
- Output = els_group_leader_server:flush(P),
- ok = els_group_leader_server:stop(P),
- {reply, {Result, Output}, State};
+ {ok, P} = els_group_leader_server:new(),
+ Node = els_config_runtime:get_node_name(),
+ ?LOG_INFO("RPC Call [node=~p] [mfa=~p]", [Node, {M, F, A}]),
+ Result = rpc:call(Node, M, F, A, Timeout),
+ Output = els_group_leader_server:flush(P),
+ ok = els_group_leader_server:stop(P),
+ {reply, {Result, Output}, State};
handle_call(_Request, _From, State) ->
- {noreply, State}.
+ {noreply, State}.
-spec handle_cast(any(), state()) -> {noreply, state()}.
handle_cast(_Request, State) ->
- {noreply, State}.
+ {noreply, State}.
-spec handle_info(any(), state()) -> {noreply, state()}.
handle_info({nodedown, Node}, State) ->
- ?LOG_ERROR("Runtime node down [node=~p]", [Node]),
- {noreply, State};
+ ?LOG_ERROR("Runtime node down [node=~p]", [Node]),
+ {noreply, State};
handle_info(Request, State) ->
- ?LOG_WARNING("Unexpected request [request=~p]", [Request]),
- {noreply, State}.
+ ?LOG_WARNING("Unexpected request [request=~p]", [Request]),
+ {noreply, State}.
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec connect_and_monitor(atom(), hidden | not_hidden) -> ok | error.
connect_and_monitor(Node, Type) ->
- case connect_node(Node, Type) of
- true ->
- ?LOG_INFO("Connected to node [node=~p]", [Node]),
- erlang:monitor_node(Node, true),
- ok;
- false ->
- error;
- ignored ->
- error
- end.
+ case connect_node(Node, Type) of
+ true ->
+ ?LOG_INFO("Connected to node [node=~p]", [Node]),
+ erlang:monitor_node(Node, true),
+ ok;
+ false ->
+ error;
+ ignored ->
+ error
+ end.
-spec start(atom()) -> ok.
start(Node) ->
- Cmd = els_config_runtime:get_start_cmd(),
- Args = els_config_runtime:get_start_args(),
- Path = els_config_runtime:get_otp_path(),
- ?LOG_INFO( "Starting new Erlang node [node=~p] [cmd=~p] [args=~p] [path=~p]"
- , [Node, Cmd, Args, Path]
- ),
- spawn_link(fun() -> els_utils:cmd(Cmd, Args, Path) end),
- wait_connect_and_monitor(Node),
- ok.
-
--spec wait_connect_and_monitor(atom()) -> ok | error.
+ Cmd = els_config_runtime:get_start_cmd(),
+ Args = els_config_runtime:get_start_args(),
+ Path = els_config_runtime:get_otp_path(),
+ ?LOG_INFO(
+ "Starting new Erlang node [node=~p] [cmd=~p] [args=~p] [path=~p]",
+ [Node, Cmd, Args, Path]
+ ),
+ spawn_link(fun() -> els_utils:cmd(Cmd, Args, Path) end),
+ wait_connect_and_monitor(Node),
+ ok.
+
+-spec wait_connect_and_monitor(atom()) -> ok | error.
wait_connect_and_monitor(Node) ->
- wait_connect_and_monitor(Node, ?WAIT_ATTEMPTS, not_hidden).
+ wait_connect_and_monitor(Node, ?WAIT_ATTEMPTS, not_hidden).
-spec wait_connect_and_monitor(
- Node :: atom(),
- Attempts :: pos_integer(),
- Type :: hidden | not_hidden
-) -> ok | error.
+ Node :: atom(),
+ Attempts :: pos_integer(),
+ Type :: hidden | not_hidden
+) -> ok | error.
wait_connect_and_monitor(Node, Attempts, Type) ->
- wait_connect_and_monitor(Node, Type, Attempts, Attempts).
+ wait_connect_and_monitor(Node, Type, Attempts, Attempts).
-spec wait_connect_and_monitor(
- Node :: atom(),
- Type :: hidden | not_hidden,
- Attempts :: pos_integer(),
- MaxAttempts :: pos_integer()
+ Node :: atom(),
+ Type :: hidden | not_hidden,
+ Attempts :: pos_integer(),
+ MaxAttempts :: pos_integer()
) -> ok | error.
wait_connect_and_monitor(Node, _, 0, MaxAttempts) ->
- ?LOG_ERROR( "Failed to connect to node ~p after ~p attempts"
- , [Node, MaxAttempts]),
- error;
+ ?LOG_ERROR(
+ "Failed to connect to node ~p after ~p attempts",
+ [Node, MaxAttempts]
+ ),
+ error;
wait_connect_and_monitor(Node, Type, Attempts, MaxAttempts) ->
- timer:sleep(?WAIT_INTERVAL),
- case connect_and_monitor(Node, Type) of
- ok ->
- ok;
- error ->
- ?LOG_WARNING( "Trying to connect to node ~p (~p/~p)"
- , [Node, MaxAttempts - Attempts + 1, MaxAttempts]),
- wait_connect_and_monitor(Node, Type, Attempts - 1, MaxAttempts)
- end.
+ timer:sleep(?WAIT_INTERVAL),
+ case connect_and_monitor(Node, Type) of
+ ok ->
+ ok;
+ error ->
+ ?LOG_WARNING(
+ "Trying to connect to node ~p (~p/~p)",
+ [Node, MaxAttempts - Attempts + 1, MaxAttempts]
+ ),
+ wait_connect_and_monitor(Node, Type, Attempts - 1, MaxAttempts)
+ end.
%% @doc Ensure the Erlang Port Mapper Daemon (EPMD) is up and running
-spec ensure_epmd() -> ok.
ensure_epmd() ->
- 0 = els_utils:cmd("epmd", ["-daemon"]),
- ok.
-
+ 0 = els_utils:cmd("epmd", ["-daemon"]),
+ ok.
-spec node_name(binary(), binary()) -> atom().
node_name(Prefix, Name0) ->
- Name = normalize_node_name(Name0),
- Int = erlang:phash2(erlang:timestamp()),
- Id = lists:flatten(io_lib:format("~s_~s_~p", [Prefix, Name, Int])),
- {ok, HostName} = inet:gethostname(),
- node_name(Id, HostName, els_config_runtime:get_name_type()).
+ Name = normalize_node_name(Name0),
+ Int = erlang:phash2(erlang:timestamp()),
+ Id = lists:flatten(io_lib:format("~s_~s_~p", [Prefix, Name, Int])),
+ {ok, HostName} = inet:gethostname(),
+ node_name(Id, HostName, els_config_runtime:get_name_type()).
-spec node_name(string(), string(), 'longnames' | 'shortnames') -> atom().
node_name(Id, HostName, shortnames) ->
- list_to_atom(Id ++ "@" ++ HostName);
+ list_to_atom(Id ++ "@" ++ HostName);
node_name(Id, HostName, longnames) ->
- Domain = proplists:get_value(domain, inet:get_rc(), ""),
- list_to_atom(Id ++ "@" ++ HostName ++ "." ++ Domain).
+ Domain = proplists:get_value(domain, inet:get_rc(), ""),
+ list_to_atom(Id ++ "@" ++ HostName ++ "." ++ Domain).
-spec normalize_node_name(string() | binary()) -> string().
normalize_node_name(Name) ->
- %% Replace invalid characters with _
- re:replace(Name, "[^0-9A-Za-z_\\-]", "_", [global, {return, list}]).
+ %% Replace invalid characters with _
+ re:replace(Name, "[^0-9A-Za-z_\\-]", "_", [global, {return, list}]).
--spec connect_node(node(), hidden | not_hidden) -> boolean() | ignored.
+-spec connect_node(node(), hidden | not_hidden) -> boolean() | ignored.
connect_node(Node, not_hidden) ->
- net_kernel:connect_node(Node);
+ net_kernel:connect_node(Node);
connect_node(Node, hidden) ->
- net_kernel:hidden_connect_node(Node).
+ net_kernel:hidden_connect_node(Node).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
default_node_name_test_() ->
- [ ?_assertEqual("foobar", normalize_node_name("foobar"))
- , ?_assertEqual("foo_bar", normalize_node_name("foo.bar"))
- , ?_assertEqual("_", normalize_node_name("&"))
- ].
+ [
+ ?_assertEqual("foobar", normalize_node_name("foobar")),
+ ?_assertEqual("foo_bar", normalize_node_name("foo.bar")),
+ ?_assertEqual("_", normalize_node_name("&"))
+ ].
-endif.
diff --git a/apps/els_core/src/els_distribution_sup.erl b/apps/els_core/src/els_distribution_sup.erl
index c91768657..0e71e4522 100644
--- a/apps/els_core/src/els_distribution_sup.erl
+++ b/apps/els_core/src/els_distribution_sup.erl
@@ -15,10 +15,10 @@
%%==============================================================================
%% API
--export([ start_link/0 ]).
+-export([start_link/0]).
%% Supervisor Callbacks
--export([ init/1 ]).
+-export([init/1]).
%%==============================================================================
%% Defines
@@ -30,23 +30,27 @@
%%==============================================================================
-spec start_link() -> {ok, pid()}.
start_link() ->
- supervisor:start_link({local, ?SERVER}, ?MODULE, []).
+ supervisor:start_link({local, ?SERVER}, ?MODULE, []).
%%==============================================================================
%% supervisors callbacks
%%==============================================================================
-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init([]) ->
- SupFlags = #{ strategy => one_for_one
- , intensity => 5
- , period => 60
- },
- ChildSpecs = [ #{ id => els_distribution_server
- , start => {els_distribution_server, start_link, []}
- }
- , #{ id => els_group_leader_sup
- , start => {els_group_leader_sup, start_link, []}
- , type => supervisor
- }
- ],
- {ok, {SupFlags, ChildSpecs}}.
+ SupFlags = #{
+ strategy => one_for_one,
+ intensity => 5,
+ period => 60
+ },
+ ChildSpecs = [
+ #{
+ id => els_distribution_server,
+ start => {els_distribution_server, start_link, []}
+ },
+ #{
+ id => els_group_leader_sup,
+ start => {els_group_leader_sup, start_link, []},
+ type => supervisor
+ }
+ ],
+ {ok, {SupFlags, ChildSpecs}}.
diff --git a/apps/els_core/src/els_dodger.erl b/apps/els_core/src/els_dodger.erl
index ed695d26e..3277ecdc6 100644
--- a/apps/els_core/src/els_dodger.erl
+++ b/apps/els_core/src/els_dodger.erl
@@ -41,7 +41,6 @@
%% //stdlib/epp} before the parser sees them), an extended syntax tree
%% is created, using the {@link erl_syntax} module.
-
%% NOTES:
%%
%% * It's OK if the result does not parse - then at least nothing
@@ -77,12 +76,24 @@
-module(els_dodger).
--export([parse_file/1, quick_parse_file/1, parse_file/2,
- quick_parse_file/2, parse/1, quick_parse/1, parse/2,
- quick_parse/2, parse/3, parse/4, quick_parse/3, parse_form/2,
- parse_form/3, quick_parse_form/2, quick_parse_form/3,
- format_error/1, tokens_to_string/1, normal_parser/2]).
-
+-export([
+ parse_file/1,
+ quick_parse_file/1,
+ parse_file/2,
+ quick_parse_file/2,
+ parse/1,
+ quick_parse/1,
+ parse/2,
+ quick_parse/2,
+ parse/3, parse/4,
+ quick_parse/3,
+ parse_form/2,
+ parse_form/3,
+ quick_parse_form/2, quick_parse_form/3,
+ format_error/1,
+ tokens_to_string/1,
+ normal_parser/2
+]).
%% The following should be: 1) pseudo-uniquely identifiable, and 2)
%% cause nice looking error messages when the parser has to give up.
@@ -92,7 +103,6 @@
-define(var_prefix, "?,").
-define(pp_form, '?preprocessor declaration?').
-
%% @type errorinfo() = {ErrorLine::integer(),
%% Module::atom(),
%% Descriptor::term()}.
@@ -114,10 +124,10 @@
%% @equiv parse_file(File, [])
-spec parse_file(file:filename()) ->
- {'ok', erl_syntax:forms()} | {'error', errorinfo()}.
+ {'ok', erl_syntax:forms()} | {'error', errorinfo()}.
parse_file(File) ->
- parse_file(File, []).
+ parse_file(File, []).
%% @spec parse_file(File, Options) -> {ok, Forms} | {error, errorinfo()}
%% File = file:filename()
@@ -154,10 +164,10 @@ parse_file(File) ->
%% @see erl_syntax:is_form/1
-spec parse_file(file:filename(), [option()]) ->
- {'ok', erl_syntax:forms()} | {'error', errorinfo()}.
+ {'ok', erl_syntax:forms()} | {'error', errorinfo()}.
parse_file(File, Options) ->
- parse_file(File, fun parse/3, Options).
+ parse_file(File, fun parse/3, Options).
%% @spec quick_parse_file(File) -> {ok, Forms} | {error, errorinfo()}
%% File = file:filename()
@@ -166,10 +176,10 @@ parse_file(File, Options) ->
%% @equiv quick_parse_file(File, [])
-spec quick_parse_file(file:filename()) ->
- {'ok', erl_syntax:forms()} | {'error', errorinfo()}.
+ {'ok', erl_syntax:forms()} | {'error', errorinfo()}.
quick_parse_file(File) ->
- quick_parse_file(File, []).
+ quick_parse_file(File, []).
%% @spec quick_parse_file(File, Options) ->
%% {ok, Forms} | {error, errorinfo()}
@@ -192,52 +202,56 @@ quick_parse_file(File) ->
%% @see parse_file/2
-spec quick_parse_file(file:filename(), [option()]) ->
- {'ok', erl_syntax:forms()} | {'error', errorinfo()}.
+ {'ok', erl_syntax:forms()} | {'error', errorinfo()}.
quick_parse_file(File, Options) ->
- parse_file(File, fun quick_parse/3, Options ++ [no_fail]).
+ parse_file(File, fun quick_parse/3, Options ++ [no_fail]).
-spec parse_file(file:filename(), function(), [option()]) -> any().
parse_file(File, Parser, Options) ->
- case do_parse_file(utf8, File, Parser, Options) of
- {ok, Forms}=Ret ->
- case find_invalid_unicode(Forms) of
- none ->
- Ret;
- invalid_unicode ->
- case epp:read_encoding(File) of
- utf8 ->
- Ret;
- _ ->
- do_parse_file(latin1, File, Parser, Options)
- end
- end;
- Else ->
- Else
- end.
+ case do_parse_file(utf8, File, Parser, Options) of
+ {ok, Forms} = Ret ->
+ case find_invalid_unicode(Forms) of
+ none ->
+ Ret;
+ invalid_unicode ->
+ case epp:read_encoding(File) of
+ utf8 ->
+ Ret;
+ _ ->
+ do_parse_file(latin1, File, Parser, Options)
+ end
+ end;
+ Else ->
+ Else
+ end.
-spec do_parse_file(utf8 | latin1, file:filename(), function(), [option()]) ->
- any().
+ any().
do_parse_file(DefEncoding, File, Parser, Options) ->
- case file:open(File, [read]) of
- {ok, Dev} ->
- _ = epp:set_encoding(Dev, DefEncoding),
- try Parser(Dev, 1, Options)
- after ok = file:close(Dev)
- end;
- {error, Error} ->
- {error, {0, file, Error}} % defer to file:format_error/1
- end.
+ case file:open(File, [read]) of
+ {ok, Dev} ->
+ _ = epp:set_encoding(Dev, DefEncoding),
+ try
+ Parser(Dev, 1, Options)
+ after
+ ok = file:close(Dev)
+ end;
+ {error, Error} ->
+ % defer to file:format_error/1
+ {error, {0, file, Error}}
+ end.
-spec find_invalid_unicode([any()]) -> invalid_unicode | none.
-find_invalid_unicode([H|T]) ->
- case H of
- {error, {_Line, file_io_server, invalid_unicode}} ->
- invalid_unicode;
- _Other ->
- find_invalid_unicode(T)
- end;
-find_invalid_unicode([]) -> none.
+find_invalid_unicode([H | T]) ->
+ case H of
+ {error, {_Line, file_io_server, invalid_unicode}} ->
+ invalid_unicode;
+ _Other ->
+ find_invalid_unicode(T)
+ end;
+find_invalid_unicode([]) ->
+ none.
%% =====================================================================
%% @spec parse(IODevice) -> {ok, Forms} | {error, errorinfo()}
@@ -246,7 +260,7 @@ find_invalid_unicode([]) -> none.
-spec parse(file:io_device()) -> {'ok', erl_syntax:forms()}.
parse(Dev) ->
- parse(Dev, 1).
+ parse(Dev, 1).
%% @spec parse(IODevice, StartLoc) -> {ok, Forms} | {error, errorinfo()}
%% IODevice = pid()
@@ -257,10 +271,10 @@ parse(Dev) ->
%% @see parse/1
-spec parse(file:io_device(), location()) ->
- {'ok', erl_syntax:forms()}.
+ {'ok', erl_syntax:forms()}.
parse(Dev, Loc) ->
- parse(Dev, Loc, []).
+ parse(Dev, Loc, []).
%% @spec parse(IODevice, StartLoc, Options) ->
%% {ok, Forms} | {error, errorinfo()}
@@ -280,19 +294,19 @@ parse(Dev, Loc) ->
%% @see quick_parse/3
-spec parse(file:io_device(), location(), [option()]) ->
- {'ok', erl_syntax:forms()}.
+ {'ok', erl_syntax:forms()}.
parse(Dev, Loc0, Options) ->
- parse(Dev, Loc0, fun parse_form/3, Options).
+ parse(Dev, Loc0, fun parse_form/3, Options).
%% @spec quick_parse(IODevice) -> {ok, Forms} | {error, errorinfo()}
%% @equiv quick_parse(IODevice, 1)
-spec quick_parse(file:io_device()) ->
- {'ok', erl_syntax:forms()}.
+ {'ok', erl_syntax:forms()}.
quick_parse(Dev) ->
- quick_parse(Dev, 1).
+ quick_parse(Dev, 1).
%% @spec quick_parse(IODevice, StartLoc) ->
%% {ok, Forms} | {error, errorinfo()}
@@ -304,10 +318,10 @@ quick_parse(Dev) ->
%% @see quick_parse/1
-spec quick_parse(file:io_device(), erl_anno:location()) ->
- {'ok', erl_syntax:forms()}.
+ {'ok', erl_syntax:forms()}.
quick_parse(Dev, Loc) ->
- quick_parse(Dev, Loc, []).
+ quick_parse(Dev, Loc, []).
%% @spec (IODevice, StartLoc, Options) ->
%% {ok, Forms} | {error, errorinfo()}
@@ -325,32 +339,36 @@ quick_parse(Dev, Loc) ->
%% @see parse/3
-spec quick_parse(file:io_device(), integer(), [option()]) ->
- {'ok', erl_syntax:forms()}.
+ {'ok', erl_syntax:forms()}.
quick_parse(Dev, L0, Options) ->
- parse(Dev, L0, fun quick_parse_form/3, Options).
+ parse(Dev, L0, fun quick_parse_form/3, Options).
-spec parse(file:io_device(), location(), function(), [option()]) ->
- {'ok', erl_syntax:forms()}.
+ {'ok', erl_syntax:forms()}.
parse(Dev, L0, Parser, Options) ->
- parse(Dev, L0, [], Parser, Options).
-
--spec parse(file:io_device(), location(),
- erl_syntax:forms(), function(), [option()]) ->
- {'ok', erl_syntax:forms()}.
+ parse(Dev, L0, [], Parser, Options).
+
+-spec parse(
+ file:io_device(),
+ location(),
+ erl_syntax:forms(),
+ function(),
+ [option()]
+) ->
+ {'ok', erl_syntax:forms()}.
parse(Dev, L0, Fs, Parser, Options) ->
- case Parser(Dev, L0, Options) of
- {ok, none, L1} ->
- parse(Dev, L1, Fs, Parser, Options);
- {ok, F, L1} ->
- parse(Dev, L1, [F | Fs], Parser, Options);
- {error, IoErr, L1} ->
- parse(Dev, L1, [{error, IoErr} | Fs], Parser, Options);
- {eof, _L1} ->
- {ok, lists:reverse(Fs)}
- end.
-
+ case Parser(Dev, L0, Options) of
+ {ok, none, L1} ->
+ parse(Dev, L1, Fs, Parser, Options);
+ {ok, F, L1} ->
+ parse(Dev, L1, [F | Fs], Parser, Options);
+ {error, IoErr, L1} ->
+ parse(Dev, L1, [{error, IoErr} | Fs], Parser, Options);
+ {eof, _L1} ->
+ {ok, lists:reverse(Fs)}
+ end.
%% =====================================================================
%% @spec parse_form(IODevice, StartLoc) -> {ok, Form, LineNo}
@@ -366,11 +384,12 @@ parse(Dev, L0, Fs, Parser, Options) ->
%% @see quick_parse_form/2
-spec parse_form(file:io_device(), erl_anno:location()) ->
- {'ok', erl_syntax:forms(), integer()}
- | {'eof', integer()} | {'error', errorinfo(), integer()}.
+ {'ok', erl_syntax:forms(), integer()}
+ | {'eof', integer()}
+ | {'error', errorinfo(), integer()}.
parse_form(Dev, Loc0) ->
- parse_form(Dev, Loc0, []).
+ parse_form(Dev, Loc0, []).
%% @spec parse_form(IODevice, StartLoc, Options) ->
%% {ok, Form, LineNo}
@@ -396,11 +415,12 @@ parse_form(Dev, Loc0) ->
%% @see quick_parse_form/3
-spec parse_form(file:io_device(), integer(), [option()]) ->
- {'ok', erl_syntax:forms(), integer()}
- | {'eof', integer()} | {'error', errorinfo(), integer()}.
+ {'ok', erl_syntax:forms(), integer()}
+ | {'eof', integer()}
+ | {'error', errorinfo(), integer()}.
parse_form(Dev, L0, Options) ->
- parse_form(Dev, L0, fun normal_parser/2, Options).
+ parse_form(Dev, L0, fun normal_parser/2, Options).
%% @spec quick_parse_form(IODevice, StartLine) ->
%% {ok, Form, LineNo}
@@ -416,11 +436,12 @@ parse_form(Dev, L0, Options) ->
%% @see parse_form/2
-spec quick_parse_form(file:io_device(), integer()) ->
- {'ok', erl_syntax:forms(), integer()}
- | {'eof', integer()} | {'error', errorinfo(), integer()}.
+ {'ok', erl_syntax:forms(), integer()}
+ | {'eof', integer()}
+ | {'error', errorinfo(), integer()}.
quick_parse_form(Dev, L0) ->
- quick_parse_form(Dev, L0, []).
+ quick_parse_form(Dev, L0, []).
%% @spec quick_parse_form(IODevice, StartLine, Options) ->
%% {ok, Form, LineNo}
@@ -441,420 +462,552 @@ quick_parse_form(Dev, L0) ->
%% @see parse_form/3
-spec quick_parse_form(file:io_device(), integer(), [option()]) ->
- {'ok', erl_syntax:forms() | none, integer()}
- | {'eof', integer()} | {'error', errorinfo(), integer()}.
+ {'ok', erl_syntax:forms() | none, integer()}
+ | {'eof', integer()}
+ | {'error', errorinfo(), integer()}.
quick_parse_form(Dev, L0, Options) ->
- parse_form(Dev, L0, fun quick_parser/2, Options).
+ parse_form(Dev, L0, fun quick_parser/2, Options).
-record(opt, {clever = false :: boolean()}).
-type opt() :: #opt{}.
-spec parse_form(file:io_device(), location(), function(), [option()]) ->
- {'ok', erl_syntax:forms() | none, integer()}
- | {'eof', integer()} | {'error', errorinfo(), integer()}.
+ {'ok', erl_syntax:forms() | none, integer()}
+ | {'eof', integer()}
+ | {'error', errorinfo(), integer()}.
parse_form(Dev, L0, Parser, Options) ->
- NoFail = proplists:get_bool(no_fail, Options),
- Opt = #opt{clever = proplists:get_bool(clever, Options)},
- case io:scan_erl_form(Dev, "", L0) of
- {ok, Ts, L1} ->
- case catch {ok, Parser(Ts, Opt)} of
- {'EXIT', Term} ->
- {error, io_error(L1, {unknown, Term}), L1};
- {error, Term} ->
- IoErr = io_error(L1, Term),
- {error, IoErr, L1};
- {parse_error, _IoErr} when NoFail ->
- {ok, erl_syntax:set_pos(
- erl_syntax:text(tokens_to_string(Ts)),
- start_pos(Ts, L1)),
- L1};
- {parse_error, IoErr} ->
- {error, IoErr, L1};
- {ok, F} ->
- {ok, F, L1}
- end;
- {error, _IoErr, _L1} = Err -> Err;
- {error, _Reason} -> {eof, L0}; % This is probably encoding problem
- {eof, _L1} = Eof -> Eof
- end.
+ NoFail = proplists:get_bool(no_fail, Options),
+ Opt = #opt{clever = proplists:get_bool(clever, Options)},
+ case io:scan_erl_form(Dev, "", L0) of
+ {ok, Ts, L1} ->
+ case catch {ok, Parser(Ts, Opt)} of
+ {'EXIT', Term} ->
+ {error, io_error(L1, {unknown, Term}), L1};
+ {error, Term} ->
+ IoErr = io_error(L1, Term),
+ {error, IoErr, L1};
+ {parse_error, _IoErr} when NoFail ->
+ {ok,
+ erl_syntax:set_pos(
+ erl_syntax:text(tokens_to_string(Ts)),
+ start_pos(Ts, L1)
+ ),
+ L1};
+ {parse_error, IoErr} ->
+ {error, IoErr, L1};
+ {ok, F} ->
+ {ok, F, L1}
+ end;
+ {error, _IoErr, _L1} = Err ->
+ Err;
+ % This is probably encoding problem
+ {error, _Reason} ->
+ {eof, L0};
+ {eof, _L1} = Eof ->
+ Eof
+ end.
-spec io_error(location(), any()) -> {location(), module(), any()}.
io_error(L, Desc) ->
- {L, ?MODULE, Desc}.
+ {L, ?MODULE, Desc}.
-spec start_pos([erl_scan:token()], location()) -> location().
start_pos([T | _Ts], _L) ->
- erl_anno:line(element(2, T));
+ erl_anno:line(element(2, T));
start_pos([], L) ->
- L.
+ L.
%% Exception-throwing wrapper for the standard Erlang parser stage
-spec parse_tokens([erl_scan:token()]) -> erl_syntax:syntaxTree().
parse_tokens(Ts) ->
- parse_tokens(Ts, fun fix_form/1).
+ parse_tokens(Ts, fun fix_form/1).
-spec parse_tokens([erl_scan:token()], function()) -> erl_syntax:syntaxTree().
parse_tokens(Ts, Fix) ->
- case erl_parse:parse_form(Ts) of
- {ok, Form} ->
- Form;
- {error, IoErr} ->
- case Fix(Ts) of
- {form, Form} ->
- Form;
- {retry, Ts1, Fix1} ->
- parse_tokens(Ts1, Fix1);
- error ->
- throw({parse_error, IoErr})
- end
- end.
+ case erl_parse:parse_form(Ts) of
+ {ok, Form} ->
+ Form;
+ {error, IoErr} ->
+ case Fix(Ts) of
+ {form, Form} ->
+ Form;
+ {retry, Ts1, Fix1} ->
+ parse_tokens(Ts1, Fix1);
+ error ->
+ throw({parse_error, IoErr})
+ end
+ end.
%% ---------------------------------------------------------------------
%% Quick scanning/parsing - deletes macro definitions and other
%% preprocessor directives, and replaces all macro calls with atoms.
-spec quick_parser([erl_scan:token()], [option()]) ->
- erl_syntax:syntaxTree() | none.
+ erl_syntax:syntaxTree() | none.
quick_parser(Ts, _Opt) ->
- filter_form(parse_tokens(quickscan_form(Ts))).
+ filter_form(parse_tokens(quickscan_form(Ts))).
-spec quickscan_form([erl_scan:token()]) -> [erl_scan:token()].
quickscan_form([{'-', _L}, {atom, La, define} | _Ts]) ->
- kill_form(La);
+ kill_form(La);
quickscan_form([{'-', _L}, {atom, La, undef} | _Ts]) ->
- kill_form(La);
+ kill_form(La);
quickscan_form([{'-', _L}, {atom, La, include} | _Ts]) ->
- kill_form(La);
+ kill_form(La);
quickscan_form([{'-', _L}, {atom, La, include_lib} | _Ts]) ->
- kill_form(La);
+ kill_form(La);
quickscan_form([{'-', _L}, {atom, La, ifdef} | _Ts]) ->
- kill_form(La);
+ kill_form(La);
quickscan_form([{'-', _L}, {atom, La, ifndef} | _Ts]) ->
- kill_form(La);
+ kill_form(La);
quickscan_form([{'-', _L}, {'if', La} | _Ts]) ->
- kill_form(La);
+ kill_form(La);
quickscan_form([{'-', _L}, {atom, La, elif} | _Ts]) ->
- kill_form(La);
+ kill_form(La);
quickscan_form([{'-', _L}, {atom, La, else} | _Ts]) ->
- kill_form(La);
+ kill_form(La);
quickscan_form([{'-', _L}, {atom, La, endif} | _Ts]) ->
- kill_form(La);
-quickscan_form([{'-', L}, {'?', _}, {Type, _, _}=N | [{'(', _} | _]=Ts])
- when Type =:= atom; Type =:= var ->
- %% minus, macro and open parenthesis at start of form - assume that
- %% the macro takes no arguments; e.g. `-?foo(...).'
- quickscan_macros_1(N, Ts, [{'-', L}]);
-quickscan_form([{'?', _L}, {Type, _, _}=N | [{'(', _} | _]=Ts])
- when Type =:= atom; Type =:= var ->
- %% macro and open parenthesis at start of form - assume that the
- %% macro takes no arguments (see scan_macros for details)
- quickscan_macros_1(N, Ts, []);
+ kill_form(La);
+quickscan_form([{'-', L}, {'?', _}, {Type, _, _} = N | [{'(', _} | _] = Ts]) when
+ Type =:= atom; Type =:= var
+->
+ %% minus, macro and open parenthesis at start of form - assume that
+ %% the macro takes no arguments; e.g. `-?foo(...).'
+ quickscan_macros_1(N, Ts, [{'-', L}]);
+quickscan_form([{'?', _L}, {Type, _, _} = N | [{'(', _} | _] = Ts]) when
+ Type =:= atom; Type =:= var
+->
+ %% macro and open parenthesis at start of form - assume that the
+ %% macro takes no arguments (see scan_macros for details)
+ quickscan_macros_1(N, Ts, []);
quickscan_form(Ts) ->
- quickscan_macros(Ts).
+ quickscan_macros(Ts).
-spec kill_form(location()) -> [erl_scan:token()].
kill_form(L) ->
- [{atom, L, ?pp_form}, {'(', L}, {')', L}, {'->', L}, {atom, L, kill},
- {dot, L}].
+ [
+ {atom, L, ?pp_form},
+ {'(', L},
+ {')', L},
+ {'->', L},
+ {atom, L, kill},
+ {dot, L}
+ ].
-spec quickscan_macros([erl_scan:token()]) -> [erl_scan:token()].
quickscan_macros(Ts) ->
- quickscan_macros(Ts, []).
+ quickscan_macros(Ts, []).
-spec quickscan_macros([erl_scan:token()], [erl_scan:token()]) ->
- [erl_scan:token()].
-quickscan_macros([{'?', _}, {Type, _, A} | Ts], [{string, L, S} | As])
- when Type =:= atom; Type =:= var ->
- %% macro after a string literal: change to a single string
- {_, Ts1} = skip_macro_args(Ts),
- S1 = S ++ quick_macro_string(A),
- quickscan_macros(Ts1, [{string, L, S1} | As]);
-quickscan_macros([{'?', _}, {Type, _, _}=N | [{'(', _}|_]=Ts],
- [{':', _}|_]=As)
- when Type =:= atom; Type =:= var ->
- %% macro and open parenthesis after colon - check the token
- %% following the arguments (see scan_macros for details)
- Ts1 = case skip_macro_args(Ts) of
- {_, [{'->', _} | _] = Ts2} -> Ts2;
- {_, [{'when', _} | _] = Ts2} -> Ts2;
- _ -> Ts %% assume macro without arguments
+ [erl_scan:token()].
+quickscan_macros([{'?', _}, {Type, _, A} | Ts], [{string, L, S} | As]) when
+ Type =:= atom; Type =:= var
+->
+ %% macro after a string literal: change to a single string
+ {_, Ts1} = skip_macro_args(Ts),
+ S1 = S ++ quick_macro_string(A),
+ quickscan_macros(Ts1, [{string, L, S1} | As]);
+quickscan_macros(
+ [{'?', _}, {Type, _, _} = N | [{'(', _} | _] = Ts],
+ [{':', _} | _] = As
+) when
+ Type =:= atom; Type =:= var
+->
+ %% macro and open parenthesis after colon - check the token
+ %% following the arguments (see scan_macros for details)
+ Ts1 =
+ case skip_macro_args(Ts) of
+ {_, [{'->', _} | _] = Ts2} -> Ts2;
+ {_, [{'when', _} | _] = Ts2} -> Ts2;
+ %% assume macro without arguments
+ _ -> Ts
end,
- quickscan_macros_1(N, Ts1, As);
-quickscan_macros([{'?', _}, {Type, _, _}=N | Ts], As)
- when Type =:= atom; Type =:= var ->
- %% macro with or without arguments
- {_, Ts1} = skip_macro_args(Ts),
- quickscan_macros_1(N, Ts1, As);
+ quickscan_macros_1(N, Ts1, As);
+quickscan_macros([{'?', _}, {Type, _, _} = N | Ts], As) when
+ Type =:= atom; Type =:= var
+->
+ %% macro with or without arguments
+ {_, Ts1} = skip_macro_args(Ts),
+ quickscan_macros_1(N, Ts1, As);
quickscan_macros([T | Ts], As) ->
- quickscan_macros(Ts, [T | As]);
+ quickscan_macros(Ts, [T | As]);
quickscan_macros([], As) ->
- lists:reverse(As).
+ lists:reverse(As).
-spec quickscan_macros_1(tuple(), [erl_scan:token()], [erl_scan:token()]) ->
- [erl_scan:token()].
+ [erl_scan:token()].
%% (after a macro has been found and the arglist skipped, if any)
quickscan_macros_1({_Type, _, A}, [{string, L, S} | Ts], As) ->
- %% string literal following macro: change to single string
- S1 = quick_macro_string(A) ++ S,
- quickscan_macros(Ts, [{string, L, S1} | As]);
+ %% string literal following macro: change to single string
+ S1 = quick_macro_string(A) ++ S,
+ quickscan_macros(Ts, [{string, L, S1} | As]);
quickscan_macros_1({_Type, L, A}, Ts, As) ->
- %% normal case - just replace the macro with an atom
- quickscan_macros(Ts, [{atom, L, quick_macro_atom(A)} | As]).
+ %% normal case - just replace the macro with an atom
+ quickscan_macros(Ts, [{atom, L, quick_macro_atom(A)} | As]).
-spec quick_macro_atom(atom()) -> atom().
quick_macro_atom(A) ->
- list_to_atom("?" ++ atom_to_list(A)).
+ list_to_atom("?" ++ atom_to_list(A)).
-spec quick_macro_string(atom()) -> string().
quick_macro_string(A) ->
- "(?" ++ atom_to_list(A) ++ ")".
+ "(?" ++ atom_to_list(A) ++ ")".
%% Skipping to the end of a macro call, tracking open/close constructs.
%% @spec (Tokens) -> {Skipped, Rest}
-spec skip_macro_args([erl_scan:token()]) ->
- {[erl_scan:token()], [erl_scan:token()]}.
-skip_macro_args([{'(', _}=T | Ts]) ->
- skip_macro_args(Ts, [')'], [T]);
+ {[erl_scan:token()], [erl_scan:token()]}.
+skip_macro_args([{'(', _} = T | Ts]) ->
+ skip_macro_args(Ts, [')'], [T]);
skip_macro_args(Ts) ->
- {[], Ts}.
+ {[], Ts}.
-spec skip_macro_args([erl_scan:token()], [atom()], [erl_scan:token()]) ->
- {[erl_scan:token()], [erl_scan:token()]}.
-skip_macro_args([{'(', _}=T | Ts], Es, As) ->
- skip_macro_args(Ts, [')' | Es], [T | As]);
-skip_macro_args([{'{', _}=T | Ts], Es, As) ->
- skip_macro_args(Ts, ['}' | Es], [T | As]);
-skip_macro_args([{'[', _}=T | Ts], Es, As) ->
- skip_macro_args(Ts, [']' | Es], [T | As]);
-skip_macro_args([{'<<', _}=T | Ts], Es, As) ->
- skip_macro_args(Ts, ['>>' | Es], [T | As]);
-skip_macro_args([{'begin', _}=T | Ts], Es, As) ->
- skip_macro_args(Ts, ['end' | Es], [T | As]);
-skip_macro_args([{'if', _}=T | Ts], Es, As) ->
- skip_macro_args(Ts, ['end' | Es], [T | As]);
-skip_macro_args([{'case', _}=T | Ts], Es, As) ->
- skip_macro_args(Ts, ['end' | Es], [T | As]);
-skip_macro_args([{'receive', _}=T | Ts], Es, As) ->
- skip_macro_args(Ts, ['end' | Es], [T | As]);
-skip_macro_args([{'try', _}=T | Ts], Es, As) ->
- skip_macro_args(Ts, ['end' | Es], [T | As]);
-skip_macro_args([{'cond', _}=T | Ts], Es, As) ->
- skip_macro_args(Ts, ['end' | Es], [T | As]);
-skip_macro_args([{E, _}=T | Ts], [E], As) -> %final close
- {lists:reverse([T | As]), Ts};
-skip_macro_args([{E, _}=T | Ts], [E | Es], As) -> %matching close
- skip_macro_args(Ts, Es, [T | As]);
+ {[erl_scan:token()], [erl_scan:token()]}.
+skip_macro_args([{'(', _} = T | Ts], Es, As) ->
+ skip_macro_args(Ts, [')' | Es], [T | As]);
+skip_macro_args([{'{', _} = T | Ts], Es, As) ->
+ skip_macro_args(Ts, ['}' | Es], [T | As]);
+skip_macro_args([{'[', _} = T | Ts], Es, As) ->
+ skip_macro_args(Ts, [']' | Es], [T | As]);
+skip_macro_args([{'<<', _} = T | Ts], Es, As) ->
+ skip_macro_args(Ts, ['>>' | Es], [T | As]);
+skip_macro_args([{'begin', _} = T | Ts], Es, As) ->
+ skip_macro_args(Ts, ['end' | Es], [T | As]);
+skip_macro_args([{'if', _} = T | Ts], Es, As) ->
+ skip_macro_args(Ts, ['end' | Es], [T | As]);
+skip_macro_args([{'case', _} = T | Ts], Es, As) ->
+ skip_macro_args(Ts, ['end' | Es], [T | As]);
+skip_macro_args([{'receive', _} = T | Ts], Es, As) ->
+ skip_macro_args(Ts, ['end' | Es], [T | As]);
+skip_macro_args([{'try', _} = T | Ts], Es, As) ->
+ skip_macro_args(Ts, ['end' | Es], [T | As]);
+skip_macro_args([{'cond', _} = T | Ts], Es, As) ->
+ skip_macro_args(Ts, ['end' | Es], [T | As]);
+%final close
+skip_macro_args([{E, _} = T | Ts], [E], As) ->
+ {lists:reverse([T | As]), Ts};
+%matching close
+skip_macro_args([{E, _} = T | Ts], [E | Es], As) ->
+ skip_macro_args(Ts, Es, [T | As]);
skip_macro_args([T | Ts], Es, As) ->
- skip_macro_args(Ts, Es, [T | As]);
+ skip_macro_args(Ts, Es, [T | As]);
skip_macro_args([], _Es, _As) ->
- throw({error, macro_args}).
+ throw({error, macro_args}).
-spec filter_form(erl_syntax:syntaxTree()) -> erl_syntax:syntaxTree() | none.
-filter_form({function, _, ?pp_form, _,
- [{clause, _, [], [], [{atom, _, kill}]}]}) ->
- none;
+filter_form({function, _, ?pp_form, _, [{clause, _, [], [], [{atom, _, kill}]}]}) ->
+ none;
filter_form(T) ->
- T.
-
+ T.
%% ---------------------------------------------------------------------
%% Normal parsing - try to preserve all information
-spec normal_parser([erl_scan:token()], opt()) -> erl_syntax:syntaxTree().
normal_parser(Ts0, Opt) ->
- case scan_form(Ts0, Opt) of
- Ts when is_list(Ts) ->
- rewrite_form(parse_tokens(Ts));
- Node ->
- Node
- end.
+ case scan_form(Ts0, Opt) of
+ Ts when is_list(Ts) ->
+ rewrite_form(parse_tokens(Ts));
+ Node ->
+ Node
+ end.
-spec scan_form([erl_scan:token()], opt()) ->
- [erl_scan:token()] | erl_syntax:syntaxTree().
+ [erl_scan:token()] | erl_syntax:syntaxTree().
scan_form([{'-', _L}, {atom, La, define} | Ts], Opt) ->
- [{atom, La, ?pp_form}, {'(', La}, {')', La}, {'->', La},
- {atom, La, define} | scan_macros(Ts, Opt)];
+ [
+ {atom, La, ?pp_form},
+ {'(', La},
+ {')', La},
+ {'->', La},
+ {atom, La, define}
+ | scan_macros(Ts, Opt)
+ ];
scan_form([{'-', _L}, {atom, La, undef} | Ts], Opt) ->
- [{atom, La, ?pp_form}, {'(', La}, {')', La}, {'->', La},
- {atom, La, undef} | scan_macros(Ts, Opt)];
+ [
+ {atom, La, ?pp_form},
+ {'(', La},
+ {')', La},
+ {'->', La},
+ {atom, La, undef}
+ | scan_macros(Ts, Opt)
+ ];
scan_form([{'-', _L}, {atom, La, include} | Ts], Opt) ->
- [{atom, La, ?pp_form}, {'(', La}, {')', La}, {'->', La},
- {atom, La, include} | scan_macros(Ts, Opt)];
+ [
+ {atom, La, ?pp_form},
+ {'(', La},
+ {')', La},
+ {'->', La},
+ {atom, La, include}
+ | scan_macros(Ts, Opt)
+ ];
scan_form([{'-', _L}, {atom, La, include_lib} | Ts], Opt) ->
- [{atom, La, ?pp_form}, {'(', La}, {')', La}, {'->', La},
- {atom, La, include_lib} | scan_macros(Ts, Opt)];
+ [
+ {atom, La, ?pp_form},
+ {'(', La},
+ {')', La},
+ {'->', La},
+ {atom, La, include_lib}
+ | scan_macros(Ts, Opt)
+ ];
scan_form([{'-', _L}, {atom, La, ifdef} | Ts], Opt) ->
- [{atom, La, ?pp_form}, {'(', La}, {')', La}, {'->', La},
- {atom, La, ifdef} | scan_macros(Ts, Opt)];
+ [
+ {atom, La, ?pp_form},
+ {'(', La},
+ {')', La},
+ {'->', La},
+ {atom, La, ifdef}
+ | scan_macros(Ts, Opt)
+ ];
scan_form([{'-', _L}, {atom, La, ifndef} | Ts], Opt) ->
- [{atom, La, ?pp_form}, {'(', La}, {')', La}, {'->', La},
- {atom, La, ifndef} | scan_macros(Ts, Opt)];
+ [
+ {atom, La, ?pp_form},
+ {'(', La},
+ {')', La},
+ {'->', La},
+ {atom, La, ifndef}
+ | scan_macros(Ts, Opt)
+ ];
scan_form([{'-', _L}, {'if', La} | Ts], Opt) ->
- [{atom, La, ?pp_form}, {'(', La}, {')', La}, {'->', La},
- {atom, La, 'if'} | scan_macros(Ts, Opt)];
+ [
+ {atom, La, ?pp_form},
+ {'(', La},
+ {')', La},
+ {'->', La},
+ {atom, La, 'if'}
+ | scan_macros(Ts, Opt)
+ ];
scan_form([{'-', _L}, {atom, La, elif} | Ts], Opt) ->
- [{atom, La, ?pp_form}, {'(', La}, {')', La}, {'->', La},
- {atom, La, 'elif'} | scan_macros(Ts, Opt)];
+ [
+ {atom, La, ?pp_form},
+ {'(', La},
+ {')', La},
+ {'->', La},
+ {atom, La, 'elif'}
+ | scan_macros(Ts, Opt)
+ ];
scan_form([{'-', _L}, {atom, La, else} | Ts], Opt) ->
- [{atom, La, ?pp_form}, {'(', La}, {')', La}, {'->', La},
- {atom, La, else} | scan_macros(Ts, Opt)];
+ [
+ {atom, La, ?pp_form},
+ {'(', La},
+ {')', La},
+ {'->', La},
+ {atom, La, else}
+ | scan_macros(Ts, Opt)
+ ];
scan_form([{'-', _L}, {atom, La, endif} | Ts], Opt) ->
- [{atom, La, ?pp_form}, {'(', La}, {')', La}, {'->', La},
- {atom, La, endif} | scan_macros(Ts, Opt)];
+ [
+ {atom, La, ?pp_form},
+ {'(', La},
+ {')', La},
+ {'->', La},
+ {atom, La, endif}
+ | scan_macros(Ts, Opt)
+ ];
scan_form([{'-', _L}, {atom, La, error} | Ts], _Opt) ->
- Desc = build_info_string("-error", Ts),
- ErrorInfo = {La, ?MODULE, {error, Desc}},
- erl_syntax:error_marker(ErrorInfo);
+ Desc = build_info_string("-error", Ts),
+ ErrorInfo = {La, ?MODULE, {error, Desc}},
+ erl_syntax:error_marker(ErrorInfo);
scan_form([{'-', _L}, {atom, La, warning} | Ts], _Opt) ->
- Desc = build_info_string("-warning", Ts),
- ErrorInfo = {La, ?MODULE, {warning, Desc}},
- erl_syntax:error_marker(ErrorInfo);
-scan_form([{'-', L}, {'?', L1}, {Type, _, _}=N | [{'(', _} | _]=Ts], Opt)
- when Type =:= atom; Type =:= var ->
- %% minus, macro and open parenthesis at start of form - assume that
- %% the macro takes no arguments; e.g. `-?foo(...).'
- macro(L1, N, Ts, [{'-', L}], Opt);
-scan_form([{'?', L}, {Type, _, _}=N | [{'(', _} | _]=Ts], Opt)
- when Type =:= atom; Type =:= var ->
- %% macro and open parenthesis at start of form - assume that the
- %% macro takes no arguments; probably a function declaration on the
- %% form `?m(...) -> ...', which will not parse if it is rewritten as
- %% `(?m(...)) -> ...', so it must be handled as `(?m)(...) -> ...'
- macro(L, N, Ts, [], Opt);
+ Desc = build_info_string("-warning", Ts),
+ ErrorInfo = {La, ?MODULE, {warning, Desc}},
+ erl_syntax:error_marker(ErrorInfo);
+scan_form([{'-', L}, {'?', L1}, {Type, _, _} = N | [{'(', _} | _] = Ts], Opt) when
+ Type =:= atom; Type =:= var
+->
+ %% minus, macro and open parenthesis at start of form - assume that
+ %% the macro takes no arguments; e.g. `-?foo(...).'
+ macro(L1, N, Ts, [{'-', L}], Opt);
+scan_form([{'?', L}, {Type, _, _} = N | [{'(', _} | _] = Ts], Opt) when
+ Type =:= atom; Type =:= var
+->
+ %% macro and open parenthesis at start of form - assume that the
+ %% macro takes no arguments; probably a function declaration on the
+ %% form `?m(...) -> ...', which will not parse if it is rewritten as
+ %% `(?m(...)) -> ...', so it must be handled as `(?m)(...) -> ...'
+ macro(L, N, Ts, [], Opt);
scan_form(Ts, Opt) ->
- scan_macros(Ts, Opt).
+ scan_macros(Ts, Opt).
-spec build_info_string(string(), [erl_scan:token()]) -> string().
build_info_string(Prefix, Ts0) ->
- Ts = lists:droplast(Ts0),
- String = lists:droplast(tokens_to_string(Ts)),
- Prefix ++ " " ++ String ++ ".".
+ Ts = lists:droplast(Ts0),
+ String = lists:droplast(tokens_to_string(Ts)),
+ Prefix ++ " " ++ String ++ ".".
-spec scan_macros([erl_scan:token()], opt()) -> [erl_scan:token()].
scan_macros(Ts, Opt) ->
- scan_macros(Ts, [], Opt).
+ scan_macros(Ts, [], Opt).
-spec scan_macros([erl_scan:token()], [erl_scan:token()], opt()) ->
- [erl_scan:token()].
-scan_macros([{'?', _}=M, {Type, _, _}=N | Ts], [{string, L, _}=S | As],
- #opt{clever = true}=Opt)
- when Type =:= atom; Type =:= var ->
- %% macro after a string literal: be clever and insert ++
- scan_macros([M, N | Ts], [{'++', L}, S | As], Opt);
-scan_macros([{'?', L}, {Type, _, _}=N | [{'(', _}|_]=Ts],
- [{':', _}|_]=As, Opt)
- when Type =:= atom; Type =:= var ->
- %% macro and open parentheses after colon - probably a call
- %% `m:?F(...)' so the argument list might belong to the call, not
- %% the macro - but it could also be a try-clause pattern
- %% `...:?T(...) ->' - we need to check the token following the
- %% arguments to decide
- {Args, Rest} = skip_macro_args(Ts),
- case Rest of
- [{'->', _} | _] ->
- macro_call(Args, L, N, Rest, As, Opt);
- [{'when', _} | _] ->
- macro_call(Args, L, N, Rest, As, Opt);
- _ ->
- macro(L, N, Ts, As, Opt)
- end;
-scan_macros([{'?', L}, {Type, _, _}=N | [{'(', _}|_]=Ts], As, Opt)
- when Type =:= atom; Type =:= var ->
- %% macro with arguments
- {Args, Rest} = skip_macro_args(Ts),
- macro_call(Args, L, N, Rest, As, Opt);
-scan_macros([{'?', L }, {Type, _, _}=N | Ts], As, Opt)
- when Type =:= atom; Type =:= var ->
- %% macro without arguments
- macro(L, N, Ts, As, Opt);
+ [erl_scan:token()].
+scan_macros(
+ [{'?', _} = M, {Type, _, _} = N | Ts],
+ [{string, L, _} = S | As],
+ #opt{clever = true} = Opt
+) when
+ Type =:= atom; Type =:= var
+->
+ %% macro after a string literal: be clever and insert ++
+ scan_macros([M, N | Ts], [{'++', L}, S | As], Opt);
+scan_macros(
+ [{'?', L}, {Type, _, _} = N | [{'(', _} | _] = Ts],
+ [{':', _} | _] = As,
+ Opt
+) when
+ Type =:= atom; Type =:= var
+->
+ %% macro and open parentheses after colon - probably a call
+ %% `m:?F(...)' so the argument list might belong to the call, not
+ %% the macro - but it could also be a try-clause pattern
+ %% `...:?T(...) ->' - we need to check the token following the
+ %% arguments to decide
+ {Args, Rest} = skip_macro_args(Ts),
+ case Rest of
+ [{'->', _} | _] ->
+ macro_call(Args, L, N, Rest, As, Opt);
+ [{'when', _} | _] ->
+ macro_call(Args, L, N, Rest, As, Opt);
+ _ ->
+ macro(L, N, Ts, As, Opt)
+ end;
+scan_macros([{'?', L}, {Type, _, _} = N | [{'(', _} | _] = Ts], As, Opt) when
+ Type =:= atom; Type =:= var
+->
+ %% macro with arguments
+ {Args, Rest} = skip_macro_args(Ts),
+ macro_call(Args, L, N, Rest, As, Opt);
+scan_macros([{'?', L}, {Type, _, _} = N | Ts], As, Opt) when
+ Type =:= atom; Type =:= var
+->
+ %% macro without arguments
+ macro(L, N, Ts, As, Opt);
scan_macros([T | Ts], As, Opt) ->
- scan_macros(Ts, [T | As], Opt);
+ scan_macros(Ts, [T | As], Opt);
scan_macros([], As, _Opt) ->
- lists:reverse(As).
+ lists:reverse(As).
%% Rewriting to a call which will be recognized by the post-parse pass
%% (we insert parentheses to preserve the precedences when parsing).
--spec macro(location(), tuple(), [erl_scan:token()],
- [erl_scan:token()], opt()) ->
- [erl_scan:token()].
+-spec macro(
+ location(),
+ tuple(),
+ [erl_scan:token()],
+ [erl_scan:token()],
+ opt()
+) ->
+ [erl_scan:token()].
macro(L, {Type, _, A}, Rest, As, Opt) ->
- scan_macros_1([], Rest, [{atom, L, macro_atom(Type, A)} | As], Opt).
-
--spec macro_call([erl_scan:token()], location(), tuple(),
- [erl_scan:token()], [erl_scan:token()], opt()) ->
- [erl_scan:token()].
+ scan_macros_1([], Rest, [{atom, L, macro_atom(Type, A)} | As], Opt).
+
+-spec macro_call(
+ [erl_scan:token()],
+ location(),
+ tuple(),
+ [erl_scan:token()],
+ [erl_scan:token()],
+ opt()
+) ->
+ [erl_scan:token()].
macro_call([{'(', _}, {')', _}], L, {_, Ln, _} = N, Rest, As, Opt) ->
- {Open, Close} = parentheses(As),
- scan_macros_1([], Rest,
- %% {'?macro_call', N }
- lists:reverse(Open ++ [{'{', L},
- {atom, L, ?macro_call},
- {',', L},
- N,
- {'}', Ln}] ++ Close,
- As), Opt);
-macro_call([{'(', _} | Args], L, {_, Ln, _}=N, Rest, As, Opt) ->
- {Open, Close} = parentheses(As),
- %% drop closing parenthesis
- {')', _} = lists:last(Args), %% assert
- Args1 = lists:droplast(Args),
- %% note that we must scan the argument list; it may not be skipped
- scan_macros_1(Args1 ++ [{'}', Ln} | Close],
- Rest,
- %% {'?macro_call', N, Arg1, ... }
- lists:reverse(Open ++ [{'{', L},
- {atom, L, ?macro_call},
- {',', L},
- N,
- {',', Ln}],
- As), Opt).
+ {Open, Close} = parentheses(As),
+ scan_macros_1(
+ [],
+ Rest,
+ %% {'?macro_call', N }
+ lists:reverse(
+ Open ++
+ [
+ {'{', L},
+ {atom, L, ?macro_call},
+ {',', L},
+ N,
+ {'}', Ln}
+ ] ++ Close,
+ As
+ ),
+ Opt
+ );
+macro_call([{'(', _} | Args], L, {_, Ln, _} = N, Rest, As, Opt) ->
+ {Open, Close} = parentheses(As),
+ %% drop closing parenthesis
+
+ %% assert
+ {')', _} = lists:last(Args),
+ Args1 = lists:droplast(Args),
+ %% note that we must scan the argument list; it may not be skipped
+ scan_macros_1(
+ Args1 ++ [{'}', Ln} | Close],
+ Rest,
+ %% {'?macro_call', N, Arg1, ... }
+ lists:reverse(
+ Open ++
+ [
+ {'{', L},
+ {atom, L, ?macro_call},
+ {',', L},
+ N,
+ {',', Ln}
+ ],
+ As
+ ),
+ Opt
+ ).
-spec macro_atom(atom | var, atom()) -> atom().
macro_atom(atom, A) ->
- list_to_atom(?atom_prefix ++ atom_to_list(A));
+ list_to_atom(?atom_prefix ++ atom_to_list(A));
macro_atom(var, A) ->
- list_to_atom(?var_prefix ++ atom_to_list(A)).
+ list_to_atom(?var_prefix ++ atom_to_list(A)).
-spec parentheses([erl_scan:token()]) -> {[erl_scan:token()], [erl_scan:token()]}.
%% don't insert parentheses after a string token, to avoid turning
%% `"string" ?macro' into a "function application" `"string"(...)'
%% (see note at top of file)
parentheses([{string, _, _} | _]) ->
- {[], []};
+ {[], []};
parentheses(_) ->
- {[{'(', 0}], [{')', 0}]}.
-
--spec scan_macros_1([erl_scan:token()], [erl_scan:token()],
- [erl_scan:token()], opt()) ->
- [erl_scan:token()].
+ {[{'(', 0}], [{')', 0}]}.
+
+-spec scan_macros_1(
+ [erl_scan:token()],
+ [erl_scan:token()],
+ [erl_scan:token()],
+ opt()
+) ->
+ [erl_scan:token()].
%% (after a macro has been found and the arglist skipped, if any)
-scan_macros_1(Args, [{string, L, _} | _]=Rest, As,
- #opt{clever = true}=Opt) ->
- %% string literal following macro: be clever and insert ++
- scan_macros(Args ++ [{'++', L} | Rest], As, Opt);
+scan_macros_1(
+ Args,
+ [{string, L, _} | _] = Rest,
+ As,
+ #opt{clever = true} = Opt
+) ->
+ %% string literal following macro: be clever and insert ++
+ scan_macros(Args ++ [{'++', L} | Rest], As, Opt);
scan_macros_1(Args, Rest, As, Opt) ->
- %% normal case - continue scanning
- scan_macros(Args ++ Rest, As, Opt).
+ %% normal case - continue scanning
+ scan_macros(Args ++ Rest, As, Opt).
-spec rewrite_form(erl_syntax:syntaxTree()) -> erl_syntax:syntaxTree().
-rewrite_form({function, L, ?pp_form, _,
- [{clause, _, [], [], [{call, _, A, As}]}]}) ->
- erl_syntax:set_pos(erl_syntax:attribute(A, rewrite_list(As)), L);
+rewrite_form({function, L, ?pp_form, _, [{clause, _, [], [], [{call, _, A, As}]}]}) ->
+ erl_syntax:set_pos(erl_syntax:attribute(A, rewrite_list(As)), L);
rewrite_form({function, L, ?pp_form, _, [{clause, _, [], [], [A]}]}) ->
- erl_syntax:set_pos(erl_syntax:attribute(A), L);
+ erl_syntax:set_pos(erl_syntax:attribute(A), L);
rewrite_form(T) ->
- rewrite(T).
+ rewrite(T).
-spec rewrite_list([erl_syntax:syntaxTree()]) -> [erl_syntax:syntaxTree()].
rewrite_list([T | Ts]) ->
- [rewrite(T) | rewrite_list(Ts)];
+ [rewrite(T) | rewrite_list(Ts)];
rewrite_list([]) ->
- [].
+ [].
%% Note: as soon as we start using erl_syntax:subtrees/1 and similar
%% functions, we cannot assume that we know the exact representation of
@@ -863,60 +1016,66 @@ rewrite_list([]) ->
-spec rewrite(erl_syntax:syntaxTree()) -> erl_syntax:syntaxTree().
rewrite(Node) ->
- case erl_syntax:type(Node) of
- atom ->
- case atom_to_list(erl_syntax:atom_value(Node)) of
- ?atom_prefix ++ As ->
- A1 = list_to_atom(As),
- N = erl_syntax:copy_pos(Node, erl_syntax:atom(A1)),
- erl_syntax:copy_pos(Node, erl_syntax:macro(N));
- ?var_prefix ++ As ->
- A1 = list_to_atom(As),
- N = erl_syntax:copy_pos(Node, erl_syntax:variable(A1)),
- erl_syntax:copy_pos(Node, erl_syntax:macro(N));
- _ ->
- Node
- end;
- Type when Type =:= tuple;
- Type =:= tuple_type ->
- case tuple_elements(Node, Type) of
- [MagicWord, A | As] ->
- case erl_syntax:type(MagicWord) of
- atom ->
- case erl_syntax:atom_value(MagicWord) of
- ?macro_call ->
- M = erl_syntax:macro(A, rewrite_list(As)),
- erl_syntax:copy_pos(Node, M);
+ case erl_syntax:type(Node) of
+ atom ->
+ case atom_to_list(erl_syntax:atom_value(Node)) of
+ ?atom_prefix ++ As ->
+ A1 = list_to_atom(As),
+ N = erl_syntax:copy_pos(Node, erl_syntax:atom(A1)),
+ erl_syntax:copy_pos(Node, erl_syntax:macro(N));
+ ?var_prefix ++ As ->
+ A1 = list_to_atom(As),
+ N = erl_syntax:copy_pos(Node, erl_syntax:variable(A1)),
+ erl_syntax:copy_pos(Node, erl_syntax:macro(N));
+ _ ->
+ Node
+ end;
+ Type when
+ Type =:= tuple;
+ Type =:= tuple_type
+ ->
+ case tuple_elements(Node, Type) of
+ [MagicWord, A | As] ->
+ case erl_syntax:type(MagicWord) of
+ atom ->
+ case erl_syntax:atom_value(MagicWord) of
+ ?macro_call ->
+ M = erl_syntax:macro(A, rewrite_list(As)),
+ erl_syntax:copy_pos(Node, M);
+ _ ->
+ rewrite_1(Node)
+ end;
+ _ ->
+ rewrite_1(Node)
+ end;
_ ->
- rewrite_1(Node)
- end;
- _ ->
- rewrite_1(Node)
- end;
+ rewrite_1(Node)
+ end;
_ ->
- rewrite_1(Node)
- end;
- _ ->
- rewrite_1(Node)
- end.
+ rewrite_1(Node)
+ end.
-spec tuple_elements(erl_syntax:syntaxTree(), atom()) -> [erl_syntax:syntaxTree()].
tuple_elements(Node, tuple) ->
- erl_syntax:tuple_elements(Node);
+ erl_syntax:tuple_elements(Node);
tuple_elements(Node, tuple_type) ->
- erl_syntax:tuple_type_elements(Node).
+ erl_syntax:tuple_type_elements(Node).
-spec rewrite_1(erl_syntax:syntaxTree()) -> erl_syntax:syntaxTree().
rewrite_1(Node) ->
- case subtrees(Node) of
- [] ->
- Node;
- Gs ->
- Node1 = erl_syntax:make_tree(erl_syntax:type(Node),
- [[rewrite(T) || T <- Ts]
- || Ts <- Gs]),
- erl_syntax:copy_pos(Node, Node1)
- end.
+ case subtrees(Node) of
+ [] ->
+ Node;
+ Gs ->
+ Node1 = erl_syntax:make_tree(
+ erl_syntax:type(Node),
+ [
+ [rewrite(T) || T <- Ts]
+ || Ts <- Gs
+ ]
+ ),
+ erl_syntax:copy_pos(Node, Node1)
+ end.
%% @doc Return the list of all subtrees of a syntax tree with special handling
%% for type attributes.
@@ -932,83 +1091,113 @@ rewrite_1(Node) ->
%% special expressions representing macros.
-spec subtrees(erl_syntax:syntaxTree()) -> [[erl_syntax:syntaxTree()]].
subtrees(Node) ->
- case is_type_attribute(Node) of
- {true, AttrName} ->
- [[erl_syntax:attribute_name(Node)],
- type_attribute_arguments(Node, AttrName)];
- false ->
- erl_syntax:subtrees(Node)
- end.
+ case is_type_attribute(Node) of
+ {true, AttrName} ->
+ [
+ [erl_syntax:attribute_name(Node)],
+ type_attribute_arguments(Node, AttrName)
+ ];
+ false ->
+ erl_syntax:subtrees(Node)
+ end.
-spec is_type_attribute(erl_syntax:syntaxTree()) -> {true, atom()} | false.
is_type_attribute(Node) ->
- case erl_syntax:type(Node) of
- attribute ->
- NameNode = erl_syntax:attribute_name(Node),
- case erl_syntax:type(NameNode) of
- atom ->
- AttrName = erl_syntax:atom_value(NameNode),
- case lists:member(AttrName, [callback, spec, type, opaque]) of
- true ->
- {true, AttrName};
- false ->
- false
- end;
+ case erl_syntax:type(Node) of
+ attribute ->
+ NameNode = erl_syntax:attribute_name(Node),
+ case erl_syntax:type(NameNode) of
+ atom ->
+ AttrName = erl_syntax:atom_value(NameNode),
+ case lists:member(AttrName, [callback, spec, type, opaque]) of
+ true ->
+ {true, AttrName};
+ false ->
+ false
+ end;
+ _ ->
+ false
+ end;
_ ->
- false
- end;
- _ ->
- false
- end.
-
--spec type_attribute_arguments(erl_syntax:syntaxTree(), atom())
- -> [erl_syntax:syntaxTree()].
-type_attribute_arguments(Node, AttrName) when AttrName =:= callback;
- AttrName =:= spec ->
- [Arg] = erl_syntax:attribute_arguments(Node),
- {FA, DefinitionClauses} = erl_syntax:concrete(Arg),
- [erl_syntax:tuple([erl_syntax:abstract(FA),
- erl_syntax:list(DefinitionClauses)])];
-type_attribute_arguments(Node, AttrName) when AttrName =:= opaque;
- AttrName =:= type ->
- [Arg] = erl_syntax:attribute_arguments(Node),
- {TypeName, Definition, TypeArgs} = erl_syntax:concrete(Arg),
- [erl_syntax:tuple([erl_syntax:abstract(TypeName),
- Definition,
- erl_syntax:list(TypeArgs)])].
-
-
+ false
+ end.
+
+-spec type_attribute_arguments(erl_syntax:syntaxTree(), atom()) ->
+ [erl_syntax:syntaxTree()].
+type_attribute_arguments(Node, AttrName) when
+ AttrName =:= callback;
+ AttrName =:= spec
+->
+ [Arg] = erl_syntax:attribute_arguments(Node),
+ {FA, DefinitionClauses} = erl_syntax:concrete(Arg),
+ [
+ erl_syntax:tuple([
+ erl_syntax:abstract(FA),
+ erl_syntax:list(DefinitionClauses)
+ ])
+ ];
+type_attribute_arguments(Node, AttrName) when
+ AttrName =:= opaque;
+ AttrName =:= type
+->
+ [Arg] = erl_syntax:attribute_arguments(Node),
+ {TypeName, Definition, TypeArgs} = erl_syntax:concrete(Arg),
+ [
+ erl_syntax:tuple([
+ erl_syntax:abstract(TypeName),
+ Definition,
+ erl_syntax:list(TypeArgs)
+ ])
+ ].
%% attempting a rescue operation on a token sequence for a single form
%% if it could not be parsed after the normal treatment
-spec fix_form([erl_scan:token()]) ->
- error | {retry, [erl_scan:token()], function()}.
-fix_form([{atom, _, ?pp_form}, {'(', _}, {')', _}, {'->', _},
- {atom, _, define}, {'(', _} | _]=Ts) ->
- case lists:reverse(Ts) of
- [{dot, _}, {')', _} | _] ->
- {retry, Ts, fun fix_define/1};
- [{dot, L} | Ts1] ->
- Ts2 = lists:reverse([{dot, L}, {')', L} | Ts1]),
- {retry, Ts2, fun fix_define/1};
- _ ->
- error
- end;
+ error | {retry, [erl_scan:token()], function()}.
+fix_form(
+ [
+ {atom, _, ?pp_form},
+ {'(', _},
+ {')', _},
+ {'->', _},
+ {atom, _, define},
+ {'(', _}
+ | _
+ ] = Ts
+) ->
+ case lists:reverse(Ts) of
+ [{dot, _}, {')', _} | _] ->
+ {retry, Ts, fun fix_define/1};
+ [{dot, L} | Ts1] ->
+ Ts2 = lists:reverse([{dot, L}, {')', L} | Ts1]),
+ {retry, Ts2, fun fix_define/1};
+ _ ->
+ error
+ end;
fix_form(_Ts) ->
- error.
+ error.
-spec fix_define([erl_scan:token()]) ->
- error | {form, erl_syntax:syntaxTree()}.
-fix_define([{atom, L, ?pp_form}, {'(', _}, {')', _}, {'->', _},
- {atom, La, define}, {'(', _}, N, {',', _} | Ts]) ->
- [{dot, _}, {')', _} | Ts1] = lists:reverse(Ts),
- S = tokens_to_string(lists:reverse(Ts1)),
- A = erl_syntax:set_pos(erl_syntax:atom(define), La),
- Txt = erl_syntax:set_pos(erl_syntax:text(S), La),
- {form, erl_syntax:set_pos(erl_syntax:attribute(A, [N, Txt]), L)};
+ error | {form, erl_syntax:syntaxTree()}.
+fix_define([
+ {atom, L, ?pp_form},
+ {'(', _},
+ {')', _},
+ {'->', _},
+ {atom, La, define},
+ {'(', _},
+ N,
+ {',', _}
+ | Ts
+]) ->
+ [{dot, _}, {')', _} | Ts1] = lists:reverse(Ts),
+ S = tokens_to_string(lists:reverse(Ts1)),
+ A = erl_syntax:set_pos(erl_syntax:atom(define), La),
+ Txt = erl_syntax:set_pos(erl_syntax:text(S), La),
+ {form, erl_syntax:set_pos(erl_syntax:attribute(A, [N, Txt]), L)};
fix_define(_Ts) ->
- error.
+ error.
%% @spec tokens_to_string(Tokens::[term()]) -> string()
%%
@@ -1018,24 +1207,23 @@ fix_define(_Ts) ->
-spec tokens_to_string([term()]) -> string().
tokens_to_string([{atom, _, A} | Ts]) ->
- io_lib:write_atom(A) ++ " " ++ tokens_to_string(Ts);
+ io_lib:write_atom(A) ++ " " ++ tokens_to_string(Ts);
tokens_to_string([{string, _, S} | Ts]) ->
- io_lib:write_string(S) ++ " " ++ tokens_to_string(Ts);
+ io_lib:write_string(S) ++ " " ++ tokens_to_string(Ts);
tokens_to_string([{char, _, C} | Ts]) ->
- io_lib:write_char(C) ++ " " ++ tokens_to_string(Ts);
+ io_lib:write_char(C) ++ " " ++ tokens_to_string(Ts);
tokens_to_string([{float, _, F} | Ts]) ->
- float_to_list(F) ++ " " ++ tokens_to_string(Ts);
+ float_to_list(F) ++ " " ++ tokens_to_string(Ts);
tokens_to_string([{integer, _, N} | Ts]) ->
- integer_to_list(N) ++ " " ++ tokens_to_string(Ts);
+ integer_to_list(N) ++ " " ++ tokens_to_string(Ts);
tokens_to_string([{var, _, A} | Ts]) ->
- atom_to_list(A) ++ " " ++ tokens_to_string(Ts);
+ atom_to_list(A) ++ " " ++ tokens_to_string(Ts);
tokens_to_string([{dot, _} | Ts]) ->
- ".\n" ++ tokens_to_string(Ts);
+ ".\n" ++ tokens_to_string(Ts);
tokens_to_string([{A, _} | Ts]) ->
- atom_to_list(A) ++ " " ++ tokens_to_string(Ts);
+ atom_to_list(A) ++ " " ++ tokens_to_string(Ts);
tokens_to_string([]) ->
- "".
-
+ "".
%% @spec format_error(Descriptor::term()) -> string()
%% @hidden
@@ -1045,17 +1233,16 @@ tokens_to_string([]) ->
-spec format_error(term()) -> string().
format_error(macro_args) ->
- errormsg("macro call missing end parenthesis");
+ errormsg("macro call missing end parenthesis");
format_error({error, Error}) ->
- Error;
+ Error;
format_error({warning, Error}) ->
- Error;
+ Error;
format_error({unknown, Reason}) ->
- errormsg(io_lib:format("unknown error: ~tP", [Reason, 15])).
+ errormsg(io_lib:format("unknown error: ~tP", [Reason, 15])).
-spec errormsg(string()) -> string().
errormsg(String) ->
- io_lib:format("~s: ~ts", [?MODULE, String]).
-
+ io_lib:format("~s: ~ts", [?MODULE, String]).
%% =====================================================================
diff --git a/apps/els_core/src/els_escript.erl b/apps/els_core/src/els_escript.erl
index 667dd6544..416c2b210 100644
--- a/apps/els_core/src/els_escript.erl
+++ b/apps/els_core/src/els_escript.erl
@@ -26,12 +26,14 @@
-module(els_escript).
--export([ extract/1 ]).
+-export([extract/1]).
--record(state, {file :: file:filename(),
- module :: module(),
- forms_or_bin,
- exports_main :: boolean()}).
+-record(state, {
+ file :: file:filename(),
+ module :: module(),
+ forms_or_bin,
+ exports_main :: boolean()
+}).
-type state() :: #state{}.
-type shebang() :: string().
@@ -41,135 +43,146 @@
-spec extract(file:filename()) -> any().
extract(File) ->
- {HeaderSz, NextLineNo, Fd, _Sections} = parse_header(File),
- case compile_source(File, Fd, NextLineNo, HeaderSz) of
- {ok, _Bin, Warnings} ->
- {ok, Warnings};
- {error, Errors, Warnings} ->
- {error, Errors, Warnings}
- end.
+ {HeaderSz, NextLineNo, Fd, _Sections} = parse_header(File),
+ case compile_source(File, Fd, NextLineNo, HeaderSz) of
+ {ok, _Bin, Warnings} ->
+ {ok, Warnings};
+ {error, Errors, Warnings} ->
+ {error, Errors, Warnings}
+ end.
--spec compile_source( file:filename()
- , any()
- , pos_integer()
- , pos_integer()) ->
- any().
+-spec compile_source(
+ file:filename(),
+ any(),
+ pos_integer(),
+ pos_integer()
+) ->
+ any().
compile_source(File, Fd, NextLineNo, HeaderSz) ->
- Forms = do_parse_file(File, Fd, NextLineNo, HeaderSz),
- ok = file:close(Fd),
- case compile:forms(Forms, [return_warnings, return_errors, debug_info]) of
- {ok, _, BeamBin, Warnings} ->
- {ok, BeamBin, Warnings};
- {error, Errors, Warnings} ->
- {error, Errors, Warnings}
- end.
+ Forms = do_parse_file(File, Fd, NextLineNo, HeaderSz),
+ ok = file:close(Fd),
+ case compile:forms(Forms, [return_warnings, return_errors, debug_info]) of
+ {ok, _, BeamBin, Warnings} ->
+ {ok, BeamBin, Warnings};
+ {error, Errors, Warnings} ->
+ {error, Errors, Warnings}
+ end.
-spec do_parse_file(any(), any(), pos_integer(), any()) ->
- [any()].
+ [any()].
do_parse_file(File, Fd, NextLineNo, HeaderSz) ->
- S = initial_state(File),
- #state{forms_or_bin = FormsOrBin} =
- parse_source(S, File, Fd, NextLineNo, HeaderSz),
- FormsOrBin.
+ S = initial_state(File),
+ #state{forms_or_bin = FormsOrBin} =
+ parse_source(S, File, Fd, NextLineNo, HeaderSz),
+ FormsOrBin.
-spec initial_state(_) -> state().
initial_state(File) ->
- #state{file = File,
- exports_main = false}.
+ #state{
+ file = File,
+ exports_main = false
+ }.
%% Skip header and make a heuristic guess about the script type
-spec parse_header(file:filename()) -> {any(), any(), any(), sections()}.
parse_header(File) ->
- {ok, Fd} = file:open(File, [read]),
+ {ok, Fd} = file:open(File, [read]),
- %% Skip shebang on first line
- Line1 = get_line(Fd),
- case classify_line(Line1) of
- shebang ->
- find_first_body_line(Fd, #sections{shebang = Line1});
- _ ->
- find_first_body_line(Fd, #sections{})
- end.
+ %% Skip shebang on first line
+ Line1 = get_line(Fd),
+ case classify_line(Line1) of
+ shebang ->
+ find_first_body_line(Fd, #sections{shebang = Line1});
+ _ ->
+ find_first_body_line(Fd, #sections{})
+ end.
-spec find_first_body_line(_, sections()) -> {any(), any(), any(), sections()}.
find_first_body_line(Fd, Sections) ->
- {ok, HeaderSz1} = file:position(Fd, cur),
- %% Look for special comment on second line
- Line2 = get_line(Fd),
- {ok, HeaderSz2} = file:position(Fd, cur),
- case classify_line(Line2) of
- emu_args ->
- %% Skip special comment on second line
- {HeaderSz2, 3, Fd, Sections};
- comment ->
- %% Look for special comment on third line
- Line3 = get_line(Fd),
- {ok, HeaderSz3} = file:position(Fd, cur),
- Line3Type = classify_line(Line3),
- case Line3Type of
+ {ok, HeaderSz1} = file:position(Fd, cur),
+ %% Look for special comment on second line
+ Line2 = get_line(Fd),
+ {ok, HeaderSz2} = file:position(Fd, cur),
+ case classify_line(Line2) of
emu_args ->
- %% Skip special comment on third line
- {HeaderSz3, 4, Fd, Sections};
+ %% Skip special comment on second line
+ {HeaderSz2, 3, Fd, Sections};
+ comment ->
+ %% Look for special comment on third line
+ Line3 = get_line(Fd),
+ {ok, HeaderSz3} = file:position(Fd, cur),
+ Line3Type = classify_line(Line3),
+ case Line3Type of
+ emu_args ->
+ %% Skip special comment on third line
+ {HeaderSz3, 4, Fd, Sections};
+ _ ->
+ %% Skip shebang on first line and comment on second
+ {HeaderSz2, 3, Fd, Sections}
+ end;
_ ->
- %% Skip shebang on first line and comment on second
- {HeaderSz2, 3, Fd, Sections}
- end;
- _ ->
- %% Just skip shebang on first line
- {HeaderSz1, 2, Fd,
- Sections#sections{}}
- end.
+ %% Just skip shebang on first line
+ {HeaderSz1, 2, Fd, Sections#sections{}}
+ end.
-spec classify_line(_) -> atom().
classify_line(Line) ->
- case Line of
- "#!" ++ _ -> shebang;
- "PK" ++ _ -> archive;
- "FOR1" ++ _ -> beam;
- "%%!" ++ _ -> emu_args;
- "%" ++ _ -> comment;
- _ -> undefined
- end.
+ case Line of
+ "#!" ++ _ -> shebang;
+ "PK" ++ _ -> archive;
+ "FOR1" ++ _ -> beam;
+ "%%!" ++ _ -> emu_args;
+ "%" ++ _ -> comment;
+ _ -> undefined
+ end.
-spec get_line(_) -> any().
get_line(P) ->
- case io:get_line(P, '') of
- eof ->
- throw("Premature end of file reached");
- Line ->
- Line
- end.
+ case io:get_line(P, '') of
+ eof ->
+ throw("Premature end of file reached");
+ Line ->
+ Line
+ end.
-spec parse_source(state(), _, _, pos_integer(), _) -> state().
parse_source(S, File, Fd, StartLine, HeaderSz) ->
- {PreDefMacros, DefModule} = pre_def_macros(File),
- IncludePath = [],
- %% Read the encoding on the second line, if there is any:
- {ok, _} = file:position(Fd, 0),
- _ = io:get_line(Fd, ''),
- Encoding = epp:set_encoding(Fd),
- {ok, _} = file:position(Fd, HeaderSz),
- {ok, Epp} = epp_open(File, Fd, StartLine, IncludePath, PreDefMacros),
- _ = [io:setopts(Fd, [{encoding, Encoding}]) || Encoding =/= none],
- {ok, FileForm} = epp:parse_erl_form(Epp),
- OptModRes = epp:parse_erl_form(Epp),
- S2 =
- case OptModRes of
- {ok, {attribute, _, module, M} = Form} ->
- epp_parse_file(Epp, S#state{module = M}, [Form, FileForm]);
- {ok, _} ->
- ModForm = {attribute, erl_anno:new(1), module, DefModule},
- epp_parse_file2(Epp, S#state{module = DefModule}, [ModForm, FileForm],
- OptModRes);
- {error, _} ->
- epp_parse_file2(Epp, S#state{module = DefModule}, [FileForm],
- OptModRes);
- {eof, LastLine} ->
- S#state{forms_or_bin = [FileForm, {eof, LastLine}]}
- end,
- ok = epp:close(Epp),
- ok = file:close(Fd),
- check_source(S2).
+ {PreDefMacros, DefModule} = pre_def_macros(File),
+ IncludePath = [],
+ %% Read the encoding on the second line, if there is any:
+ {ok, _} = file:position(Fd, 0),
+ _ = io:get_line(Fd, ''),
+ Encoding = epp:set_encoding(Fd),
+ {ok, _} = file:position(Fd, HeaderSz),
+ {ok, Epp} = epp_open(File, Fd, StartLine, IncludePath, PreDefMacros),
+ _ = [io:setopts(Fd, [{encoding, Encoding}]) || Encoding =/= none],
+ {ok, FileForm} = epp:parse_erl_form(Epp),
+ OptModRes = epp:parse_erl_form(Epp),
+ S2 =
+ case OptModRes of
+ {ok, {attribute, _, module, M} = Form} ->
+ epp_parse_file(Epp, S#state{module = M}, [Form, FileForm]);
+ {ok, _} ->
+ ModForm = {attribute, erl_anno:new(1), module, DefModule},
+ epp_parse_file2(
+ Epp,
+ S#state{module = DefModule},
+ [ModForm, FileForm],
+ OptModRes
+ );
+ {error, _} ->
+ epp_parse_file2(
+ Epp,
+ S#state{module = DefModule},
+ [FileForm],
+ OptModRes
+ );
+ {eof, LastLine} ->
+ S#state{forms_or_bin = [FileForm, {eof, LastLine}]}
+ end,
+ ok = epp:close(Epp),
+ ok = file:close(Fd),
+ check_source(S2).
-spec epp_open(_, _, pos_integer(), _, _) -> {ok, term()}.
-if(?OTP_RELEASE < 24).
@@ -196,83 +209,96 @@ epp_open(File, Fd, StartLine, IncludePath, PreDefMacros) ->
-spec epp_open24(_, _, pos_integer(), _, _) -> {ok, term()}.
epp_open24(File, Fd, StartLine, IncludePath, PreDefMacros) ->
%% We use apply in order to fool dialyzer not not analyze this path
- apply(epp, open,
- [[{fd, Fd}, {name, File}, {location, StartLine},
- {includes, IncludePath}, {macros, PreDefMacros}]]).
-
-
+ apply(
+ epp,
+ open,
+ [
+ [
+ {fd, Fd},
+ {name, File},
+ {location, StartLine},
+ {includes, IncludePath},
+ {macros, PreDefMacros}
+ ]
+ ]
+ ).
-spec check_source(state()) -> state().
check_source(S) ->
- case S of
- #state{exports_main = ExpMain,
- forms_or_bin = [FileForm2, ModForm2 | Forms]} ->
- %% Optionally add export of main/1
- Forms2 =
- case ExpMain of
- false -> [{attribute, erl_anno:new(0), export, [{main, 1}]} | Forms];
- true -> Forms
- end,
- Forms3 = [FileForm2, ModForm2 | Forms2],
- S#state{forms_or_bin = Forms3}
- end.
+ case S of
+ #state{
+ exports_main = ExpMain,
+ forms_or_bin = [FileForm2, ModForm2 | Forms]
+ } ->
+ %% Optionally add export of main/1
+ Forms2 =
+ case ExpMain of
+ false -> [{attribute, erl_anno:new(0), export, [{main, 1}]} | Forms];
+ true -> Forms
+ end,
+ Forms3 = [FileForm2, ModForm2 | Forms2],
+ S#state{forms_or_bin = Forms3}
+ end.
-spec pre_def_macros(_) -> {any(), any()}.
pre_def_macros(File) ->
- {MegaSecs, Secs, MicroSecs} = erlang:timestamp(),
- Unique = erlang:unique_integer([positive]),
- Replace = fun(Char) ->
- case Char of
- $\. -> $\_;
- _ -> Char
- end
- end,
- CleanBase = lists:map(Replace, filename:basename(File)),
- ModuleStr =
- CleanBase ++ "__" ++
- "escript__" ++
- integer_to_list(MegaSecs) ++ "__" ++
- integer_to_list(Secs) ++ "__" ++
- integer_to_list(MicroSecs) ++ "__" ++
- integer_to_list(Unique),
- Module = list_to_atom(ModuleStr),
- PreDefMacros = [{'MODULE', Module, redefine},
- {'MODULE_STRING', ModuleStr, redefine}],
- {PreDefMacros, Module}.
+ {MegaSecs, Secs, MicroSecs} = erlang:timestamp(),
+ Unique = erlang:unique_integer([positive]),
+ Replace = fun(Char) ->
+ case Char of
+ $\. -> $\_;
+ _ -> Char
+ end
+ end,
+ CleanBase = lists:map(Replace, filename:basename(File)),
+ ModuleStr =
+ CleanBase ++ "__" ++
+ "escript__" ++
+ integer_to_list(MegaSecs) ++ "__" ++
+ integer_to_list(Secs) ++ "__" ++
+ integer_to_list(MicroSecs) ++ "__" ++
+ integer_to_list(Unique),
+ Module = list_to_atom(ModuleStr),
+ PreDefMacros = [
+ {'MODULE', Module, redefine},
+ {'MODULE_STRING', ModuleStr, redefine}
+ ],
+ {PreDefMacros, Module}.
-spec epp_parse_file(_, state(), [any()]) -> state().
epp_parse_file(Epp, S, Forms) ->
- Parsed = epp:parse_erl_form(Epp),
- epp_parse_file2(Epp, S, Forms, Parsed).
+ Parsed = epp:parse_erl_form(Epp),
+ epp_parse_file2(Epp, S, Forms, Parsed).
-spec epp_parse_file2(_, state(), [any()], any()) -> state().
epp_parse_file2(Epp, S, Forms, Parsed) ->
- case Parsed of
- {ok, {attribute, Ln, mode, Mode} = Form} ->
- case is_valid(Mode) of
- true ->
- epp_parse_file(Epp, S, [Form | Forms]);
- false ->
- Args = lists:flatten(
- io_lib:format("illegal mode attribute: ~p", [Mode])),
- Error = {error, {Ln, erl_parse, Args}},
- epp_parse_file(Epp, S, [Error | Forms])
- end;
- {ok, {attribute, _, export, Fs} = Form} ->
- case lists:member({main, 1}, Fs) of
- false ->
- epp_parse_file(Epp, S, [Form | Forms]);
- true ->
- epp_parse_file(Epp, S#state{exports_main = true}, [Form | Forms])
- end;
- {ok, Form} ->
- epp_parse_file(Epp, S, [Form | Forms]);
- {error, _} = Form ->
- epp_parse_file(Epp, S, [Form | Forms]);
- {eof, LastLine} ->
- S#state{forms_or_bin = lists:reverse([{eof, LastLine} | Forms])}
- end.
+ case Parsed of
+ {ok, {attribute, Ln, mode, Mode} = Form} ->
+ case is_valid(Mode) of
+ true ->
+ epp_parse_file(Epp, S, [Form | Forms]);
+ false ->
+ Args = lists:flatten(
+ io_lib:format("illegal mode attribute: ~p", [Mode])
+ ),
+ Error = {error, {Ln, erl_parse, Args}},
+ epp_parse_file(Epp, S, [Error | Forms])
+ end;
+ {ok, {attribute, _, export, Fs} = Form} ->
+ case lists:member({main, 1}, Fs) of
+ false ->
+ epp_parse_file(Epp, S, [Form | Forms]);
+ true ->
+ epp_parse_file(Epp, S#state{exports_main = true}, [Form | Forms])
+ end;
+ {ok, Form} ->
+ epp_parse_file(Epp, S, [Form | Forms]);
+ {error, _} = Form ->
+ epp_parse_file(Epp, S, [Form | Forms]);
+ {eof, LastLine} ->
+ S#state{forms_or_bin = lists:reverse([{eof, LastLine} | Forms])}
+ end.
-spec is_valid(atom()) -> boolean().
is_valid(Mode) ->
- lists:member(Mode, [compile, debug, interpret, native]).
+ lists:member(Mode, [compile, debug, interpret, native]).
diff --git a/apps/els_core/src/els_io_string.erl b/apps/els_core/src/els_io_string.erl
index edbbd4b06..9d4807104 100644
--- a/apps/els_core/src/els_io_string.erl
+++ b/apps/els_core/src/els_io_string.erl
@@ -2,15 +2,17 @@
-export([new/1]).
--export([ start_link/1
- , init/1
- , loop/1
- , skip/3
- ]).
-
--type state() :: #{ buffer := string()
- , original := string()
- }.
+-export([
+ start_link/1,
+ init/1,
+ loop/1,
+ skip/3
+]).
+
+-type state() :: #{
+ buffer := string(),
+ original := string()
+}.
%%------------------------------------------------------------------------------
%% API
@@ -18,9 +20,9 @@
-spec new(string() | binary()) -> pid().
new(Str) when is_binary(Str) ->
- new(els_utils:to_list(Str));
+ new(els_utils:to_list(Str));
new(Str) ->
- start_link(Str).
+ start_link(Str).
%%------------------------------------------------------------------------------
%% IO server
@@ -31,109 +33,109 @@ new(Str) ->
-spec start_link(string()) -> pid().
start_link(Str) ->
- spawn_link(?MODULE, init, [Str]).
+ spawn_link(?MODULE, init, [Str]).
-spec init(string()) -> ok.
init(Str) ->
- State = #{buffer => Str, original => Str},
- ?MODULE:loop(State).
+ State = #{buffer => Str, original => Str},
+ ?MODULE:loop(State).
-spec loop(state()) -> ok.
loop(#{buffer := Str} = State) ->
- receive
- {io_request, From, ReplyAs, Request} ->
- {Reply, NewStr} = request(Request, Str),
- reply(From, ReplyAs, Reply),
- ?MODULE:loop(State#{buffer := NewStr});
- {file_request, From, Ref, close} ->
- file_reply(From, Ref, ok);
- {file_request, From, Ref, {position, Pos}} ->
- {Reply, NewState} = file_position(Pos, State),
- file_reply(From, Ref, Reply),
- ?MODULE:loop(NewState);
- _Unknown ->
- ?MODULE:loop(State)
- end.
+ receive
+ {io_request, From, ReplyAs, Request} ->
+ {Reply, NewStr} = request(Request, Str),
+ reply(From, ReplyAs, Reply),
+ ?MODULE:loop(State#{buffer := NewStr});
+ {file_request, From, Ref, close} ->
+ file_reply(From, Ref, ok);
+ {file_request, From, Ref, {position, Pos}} ->
+ {Reply, NewState} = file_position(Pos, State),
+ file_reply(From, Ref, Reply),
+ ?MODULE:loop(NewState);
+ _Unknown ->
+ ?MODULE:loop(State)
+ end.
-spec reply(pid(), pid(), any()) -> any().
reply(From, ReplyAs, Reply) ->
- From ! {io_reply, ReplyAs, Reply}.
+ From ! {io_reply, ReplyAs, Reply}.
-spec file_reply(pid(), pid(), any()) -> any().
file_reply(From, ReplyAs, Reply) ->
- From ! {file_reply, ReplyAs, Reply}.
+ From ! {file_reply, ReplyAs, Reply}.
-spec file_position(integer(), state()) -> {any(), state()}.
file_position(Pos, #{original := Original} = State) ->
- Buffer = lists:nthtail(Pos, Original),
- {{ok, Pos}, State#{buffer => Buffer}}.
+ Buffer = lists:nthtail(Pos, Original),
+ {{ok, Pos}, State#{buffer => Buffer}}.
-spec request(any(), string()) -> {string() | {error, request}, string()}.
request({get_chars, _Encoding, _Prompt, N}, Str) ->
- get_chars(N, Str);
+ get_chars(N, Str);
request({get_line, _Encoding, _Prompt}, Str) ->
- get_line(Str);
+ get_line(Str);
request({get_until, _Encoding, _Prompt, Module, Function, Xargs}, Str) ->
- get_until(Module, Function, Xargs, Str);
+ get_until(Module, Function, Xargs, Str);
request(_Other, State) ->
- {{error, request}, State}.
+ {{error, request}, State}.
-spec get_chars(integer(), string()) -> {string() | eof, string()}.
get_chars(_N, []) ->
- {eof, []};
+ {eof, []};
get_chars(1, [Ch | Str]) ->
- {[Ch], Str};
+ {[Ch], Str};
get_chars(N, Str) ->
- do_get_chars(N, Str, []).
+ do_get_chars(N, Str, []).
-spec do_get_chars(integer(), string(), string()) -> {string(), string()}.
do_get_chars(0, Str, Result) ->
- {lists:flatten(Result), Str};
+ {lists:flatten(Result), Str};
do_get_chars(_N, [], Result) ->
- {Result, []};
+ {Result, []};
do_get_chars(N, [Ch | NewStr], Result) ->
- do_get_chars(N - 1, NewStr, [Result, Ch]).
+ do_get_chars(N - 1, NewStr, [Result, Ch]).
-spec get_line(string()) -> {string() | eof, string()}.
get_line([]) ->
- {eof, []};
+ {eof, []};
get_line(Str) ->
- do_get_line(Str, []).
+ do_get_line(Str, []).
-spec do_get_line(string(), string()) -> {string() | eof, string()}.
do_get_line([], Result) ->
- {lists:flatten(Result), []};
+ {lists:flatten(Result), []};
do_get_line("\r\n" ++ RestStr, Result) ->
- {lists:flatten(Result), RestStr};
+ {lists:flatten(Result), RestStr};
do_get_line("\n" ++ RestStr, Result) ->
- {lists:flatten(Result), RestStr};
+ {lists:flatten(Result), RestStr};
do_get_line("\r" ++ RestStr, Result) ->
- {lists:flatten(Result), RestStr};
+ {lists:flatten(Result), RestStr};
do_get_line([Ch | RestStr], Result) ->
- do_get_line(RestStr, [Result, Ch]).
+ do_get_line(RestStr, [Result, Ch]).
-spec get_until(module(), atom(), list(), term()) ->
- {term(), string()}.
+ {term(), string()}.
get_until(Module, Function, XArgs, Str) ->
- apply_get_until(Module, Function, [], Str, XArgs).
+ apply_get_until(Module, Function, [], Str, XArgs).
-spec apply_get_until(module(), atom(), any(), string() | eof, list()) ->
- {term(), string()}.
+ {term(), string()}.
apply_get_until(Module, Function, State, String, XArgs) ->
- case apply(Module, Function, [State, String | XArgs]) of
- {done, Result, NewStr} ->
- {Result, NewStr};
- {more, NewState} ->
- apply_get_until(Module, Function, NewState, eof, XArgs)
- end.
+ case apply(Module, Function, [State, String | XArgs]) of
+ {done, Result, NewStr} ->
+ {Result, NewStr};
+ {more, NewState} ->
+ apply_get_until(Module, Function, NewState, eof, XArgs)
+ end.
-spec skip(string() | {cont, integer(), string()}, term(), integer()) ->
- {more, {cont, integer(), string()}} | {done, integer(), string()}.
+ {more, {cont, integer(), string()}} | {done, integer(), string()}.
skip(Str, _Data, Length) when is_list(Str) ->
- {more, {cont, Length, Str}};
+ {more, {cont, Length, Str}};
skip({cont, 0, Str}, _Data, Length) ->
- {done, Length, Str};
+ {done, Length, Str};
skip({cont, Length, []}, _Data, Length) ->
- {done, eof, []};
+ {done, eof, []};
skip({cont, Length, [_ | RestStr]}, _Data, _Length) ->
- {more, {cont, Length - 1, RestStr}}.
+ {more, {cont, Length - 1, RestStr}}.
diff --git a/apps/els_core/src/els_jsonrpc.erl b/apps/els_core/src/els_jsonrpc.erl
index 2f5d039c8..9c5671718 100644
--- a/apps/els_core/src/els_jsonrpc.erl
+++ b/apps/els_core/src/els_jsonrpc.erl
@@ -6,10 +6,11 @@
%%==============================================================================
%% Exports
%%==============================================================================
--export([ default_opts/0
- , split/1
- , split/2
- ]).
+-export([
+ default_opts/0,
+ split/1,
+ split/2
+]).
%%==============================================================================
%% Includes
@@ -19,7 +20,7 @@
%%==============================================================================
%% Types
%%==============================================================================
--type more() :: {more, undefined | non_neg_integer()}.
+-type more() :: {more, undefined | non_neg_integer()}.
-type header() :: {atom() | binary(), binary()}.
%%==============================================================================
@@ -27,55 +28,55 @@
%%==============================================================================
-spec split(binary()) -> {[map()], binary()}.
split(Data) ->
- split(Data, default_opts()).
+ split(Data, default_opts()).
-spec split(binary(), [any()]) -> {[map()], binary()}.
split(Data, DecodeOpts) ->
- split(Data, DecodeOpts, []).
+ split(Data, DecodeOpts, []).
-spec split(binary(), [any()], [map()]) -> {[map()], binary()}.
split(Data, DecodeOpts, Responses) ->
- case peel_content(Data) of
- {ok, Body, Rest} ->
- Response = jsx:decode(Body, DecodeOpts),
- split(Rest, DecodeOpts, [Response|Responses]);
- {more, _Length} ->
- {lists:reverse(Responses), Data}
- end.
+ case peel_content(Data) of
+ {ok, Body, Rest} ->
+ Response = jsx:decode(Body, DecodeOpts),
+ split(Rest, DecodeOpts, [Response | Responses]);
+ {more, _Length} ->
+ {lists:reverse(Responses), Data}
+ end.
-spec peel_content(binary()) -> {ok, binary(), binary()} | more().
peel_content(Data) ->
- case peel_headers(Data) of
- {ok, Headers, Data1} ->
- BinLength = proplists:get_value('Content-Length', Headers),
- Length = binary_to_integer(BinLength),
- case Data1 of
- <> ->
- {ok, Body, Rest};
- Data1 ->
- {more, Length - byte_size(Data1)}
- end;
- {more, Length} ->
- {more, Length}
- end.
+ case peel_headers(Data) of
+ {ok, Headers, Data1} ->
+ BinLength = proplists:get_value('Content-Length', Headers),
+ Length = binary_to_integer(BinLength),
+ case Data1 of
+ <> ->
+ {ok, Body, Rest};
+ Data1 ->
+ {more, Length - byte_size(Data1)}
+ end;
+ {more, Length} ->
+ {more, Length}
+ end.
-spec peel_headers(binary()) -> {ok, [header()], binary()} | more().
peel_headers(Data) ->
- peel_headers(Data, []).
+ peel_headers(Data, []).
-spec peel_headers(binary(), [header()]) -> {ok, [header()], binary()} | more().
peel_headers(Data, Headers) ->
- case erlang:decode_packet(httph_bin, Data, []) of
- {ok, http_eoh, Rest} ->
- {ok, lists:reverse(Headers), Rest};
- {ok, {http_header, _Bit, Field, _UnmodifiedField, Value}, Rest} ->
- peel_headers(Rest, [{Field, Value}|Headers]);
- {more, Length} ->
- {more, Length};
- {error, Reason} ->
- erlang:error(Reason, [Data, Headers])
- end.
+ case erlang:decode_packet(httph_bin, Data, []) of
+ {ok, http_eoh, Rest} ->
+ {ok, lists:reverse(Headers), Rest};
+ {ok, {http_header, _Bit, Field, _UnmodifiedField, Value}, Rest} ->
+ peel_headers(Rest, [{Field, Value} | Headers]);
+ {more, Length} ->
+ {more, Length};
+ {error, Reason} ->
+ erlang:error(Reason, [Data, Headers])
+ end.
-spec default_opts() -> [any()].
default_opts() ->
- [return_maps, {labels, atom}].
+ [return_maps, {labels, atom}].
diff --git a/apps/els_core/src/els_protocol.erl b/apps/els_core/src/els_protocol.erl
index f8860eb4b..4491da46e 100644
--- a/apps/els_core/src/els_protocol.erl
+++ b/apps/els_core/src/els_protocol.erl
@@ -7,16 +7,16 @@
%% Exports
%%==============================================================================
%% Messaging API
--export([ notification/2
- , request/2
- , request/3
- , response/2
- , error/2
- ]).
+-export([
+ notification/2,
+ request/2,
+ request/3,
+ response/2,
+ error/2
+]).
%% Data Structures
--export([ range/1
- ]).
+-export([range/1]).
%%==============================================================================
%% Includes
@@ -29,63 +29,69 @@
%%==============================================================================
-spec notification(binary(), any()) -> binary().
notification(Method, Params) ->
- Message = #{ jsonrpc => ?JSONRPC_VSN
- , method => Method
- , params => Params
- },
- content(jsx:encode(Message)).
+ Message = #{
+ jsonrpc => ?JSONRPC_VSN,
+ method => Method,
+ params => Params
+ },
+ content(jsx:encode(Message)).
-spec request(number(), binary()) -> binary().
request(RequestId, Method) ->
- Message = #{ jsonrpc => ?JSONRPC_VSN
- , method => Method
- , id => RequestId
- },
- content(jsx:encode(Message)).
+ Message = #{
+ jsonrpc => ?JSONRPC_VSN,
+ method => Method,
+ id => RequestId
+ },
+ content(jsx:encode(Message)).
-spec request(number(), binary(), any()) -> binary().
request(RequestId, Method, Params) ->
- Message = #{ jsonrpc => ?JSONRPC_VSN
- , method => Method
- , id => RequestId
- , params => Params
- },
- content(jsx:encode(Message)).
+ Message = #{
+ jsonrpc => ?JSONRPC_VSN,
+ method => Method,
+ id => RequestId,
+ params => Params
+ },
+ content(jsx:encode(Message)).
-spec response(number(), any()) -> binary().
response(RequestId, Result) ->
- Message = #{ jsonrpc => ?JSONRPC_VSN
- , id => RequestId
- , result => Result
- },
- ?LOG_DEBUG("[Response] [message=~p]", [Message]),
- content(jsx:encode(Message)).
+ Message = #{
+ jsonrpc => ?JSONRPC_VSN,
+ id => RequestId,
+ result => Result
+ },
+ ?LOG_DEBUG("[Response] [message=~p]", [Message]),
+ content(jsx:encode(Message)).
-spec error(number(), any()) -> binary().
error(RequestId, Error) ->
- Message = #{ jsonrpc => ?JSONRPC_VSN
- , id => RequestId
- , error => Error
- },
- ?LOG_DEBUG("[Response] [message=~p]", [Message]),
- content(jsx:encode(Message)).
+ Message = #{
+ jsonrpc => ?JSONRPC_VSN,
+ id => RequestId,
+ error => Error
+ },
+ ?LOG_DEBUG("[Response] [message=~p]", [Message]),
+ content(jsx:encode(Message)).
%%==============================================================================
%% Data Structures
%%==============================================================================
-spec range(poi_range()) -> range().
-range(#{ from := {FromL, FromC}, to := {ToL, ToC} }) ->
- #{ start => #{line => FromL - 1, character => FromC - 1}
- , 'end' => #{line => ToL - 1, character => ToC - 1}
- }.
+range(#{from := {FromL, FromC}, to := {ToL, ToC}}) ->
+ #{
+ start => #{line => FromL - 1, character => FromC - 1},
+ 'end' => #{line => ToL - 1, character => ToC - 1}
+ }.
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec content(binary()) -> binary().
content(Body) ->
- els_utils:to_binary([headers(Body), "\r\n", Body]).
+ els_utils:to_binary([headers(Body), "\r\n", Body]).
-spec headers(binary()) -> iolist().
headers(Body) ->
- io_lib:format("Content-Length: ~p\r\n", [byte_size(Body)]).
+ io_lib:format("Content-Length: ~p\r\n", [byte_size(Body)]).
diff --git a/apps/els_core/src/els_provider.erl b/apps/els_core/src/els_provider.erl
index 013b73009..f4391f4e7 100644
--- a/apps/els_core/src/els_provider.erl
+++ b/apps/els_core/src/els_provider.erl
@@ -1,69 +1,76 @@
-module(els_provider).
%% API
--export([ handle_request/2
- , start_link/0
- , available_providers/0
- , cancel_request/1
- , cancel_request_by_uri/1
- ]).
+-export([
+ handle_request/2,
+ start_link/0,
+ available_providers/0,
+ cancel_request/1,
+ cancel_request_by_uri/1
+]).
-behaviour(gen_server).
--export([ init/1
- , handle_call/3
- , handle_cast/2
- , handle_info/2
- , terminate/2
- ]).
+-export([
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2
+]).
%%==============================================================================
%% Includes
%%==============================================================================
-include_lib("kernel/include/logger.hrl").
--callback handle_request(request(), any()) -> {async, uri(), pid()} |
- {response, any()} |
- {diagnostics, uri(), [pid()]} |
- noresponse.
+-callback handle_request(request(), any()) ->
+ {async, uri(), pid()}
+ | {response, any()}
+ | {diagnostics, uri(), [pid()]}
+ | noresponse.
-callback handle_info(any(), any()) -> any().
-optional_callbacks([handle_info/2]).
--type config() :: any().
--type provider() :: els_completion_provider
- | els_definition_provider
- | els_document_symbol_provider
- | els_hover_provider
- | els_references_provider
- | els_formatting_provider
- | els_document_highlight_provider
- | els_workspace_symbol_provider
- | els_folding_range_provider
- | els_implementation_provider
- | els_code_action_provider
- | els_general_provider
- | els_code_lens_provider
- | els_execute_command_provider
- | els_rename_provider
- | els_text_synchronization_provider.
--type request() :: {atom() | binary(), map()}.
--type state() :: #{ in_progress := [progress_entry()]
- , in_progress_diagnostics := [diagnostic_entry()]
- , open_buffers := sets:set(buffer())
- }.
+-type config() :: any().
+-type provider() ::
+ els_completion_provider
+ | els_definition_provider
+ | els_document_symbol_provider
+ | els_hover_provider
+ | els_references_provider
+ | els_formatting_provider
+ | els_document_highlight_provider
+ | els_workspace_symbol_provider
+ | els_folding_range_provider
+ | els_implementation_provider
+ | els_code_action_provider
+ | els_general_provider
+ | els_code_lens_provider
+ | els_execute_command_provider
+ | els_rename_provider
+ | els_text_synchronization_provider.
+-type request() :: {atom() | binary(), map()}.
+-type state() :: #{
+ in_progress := [progress_entry()],
+ in_progress_diagnostics := [diagnostic_entry()],
+ open_buffers := sets:set(buffer())
+}.
-type buffer() :: uri().
-type progress_entry() :: {uri(), job()}.
--type diagnostic_entry() :: #{ uri := uri()
- , pending := [job()]
- , diagnostics := [els_diagnostics:diagnostic()]
- }.
+-type diagnostic_entry() :: #{
+ uri := uri(),
+ pending := [job()],
+ diagnostics := [els_diagnostics:diagnostic()]
+}.
-type job() :: pid().
%% TODO: Redefining uri() due to a type conflict with request()
-type uri() :: binary().
--export_type([ config/0
- , provider/0
- , request/0
- , state/0
- ]).
+-export_type([
+ config/0,
+ provider/0,
+ request/0,
+ state/0
+]).
%%==============================================================================
%% Macro Definitions
@@ -76,19 +83,19 @@
-spec start_link() -> {ok, pid()}.
start_link() ->
- gen_server:start_link({local, ?SERVER}, ?MODULE, unused, []).
+ gen_server:start_link({local, ?SERVER}, ?MODULE, unused, []).
-spec handle_request(provider(), request()) -> any().
handle_request(Provider, Request) ->
- gen_server:call(?SERVER, {handle_request, Provider, Request}, infinity).
+ gen_server:call(?SERVER, {handle_request, Provider, Request}, infinity).
-spec cancel_request(pid()) -> any().
cancel_request(Job) ->
- gen_server:cast(?SERVER, {cancel_request, Job}).
+ gen_server:cast(?SERVER, {cancel_request, Job}).
-spec cancel_request_by_uri(uri()) -> any().
cancel_request_by_uri(Uri) ->
- gen_server:cast(?SERVER, {cancel_request_by_uri, Uri}).
+ gen_server:cast(?SERVER, {cancel_request_by_uri, Uri}).
%%==============================================================================
%% gen_server callbacks
@@ -96,140 +103,151 @@ cancel_request_by_uri(Uri) ->
-spec init(unused) -> {ok, state()}.
init(unused) ->
- %% Ensure the terminate function is called on shutdown, allowing the
- %% job to clean up.
- process_flag(trap_exit, true),
- {ok, #{ in_progress => []
- , in_progress_diagnostics => []
- , open_buffers => sets:new()
- }}.
+ %% Ensure the terminate function is called on shutdown, allowing the
+ %% job to clean up.
+ process_flag(trap_exit, true),
+ {ok, #{
+ in_progress => [],
+ in_progress_diagnostics => [],
+ open_buffers => sets:new()
+ }}.
-spec handle_call(any(), {pid(), any()}, state()) ->
- {reply, any(), state()}.
+ {reply, any(), state()}.
handle_call({handle_request, Provider, Request}, _From, State) ->
- #{in_progress := InProgress, in_progress_diagnostics := InProgressDiagnostics}
- = State,
- case Provider:handle_request(Request, State) of
- {async, Uri, Job} ->
- {reply, {async, Job}, State#{in_progress => [{Uri, Job}|InProgress]}};
- {response, Response} ->
- {reply, {response, Response}, State};
- {diagnostics, Uri, Jobs} ->
- Entry = #{uri => Uri, pending => Jobs, diagnostics => []},
- NewState =
- State#{in_progress_diagnostics => [Entry|InProgressDiagnostics]},
- {reply, noresponse, NewState};
- noresponse ->
- {reply, noresponse, State}
- end.
+ #{in_progress := InProgress, in_progress_diagnostics := InProgressDiagnostics} =
+ State,
+ case Provider:handle_request(Request, State) of
+ {async, Uri, Job} ->
+ {reply, {async, Job}, State#{in_progress => [{Uri, Job} | InProgress]}};
+ {response, Response} ->
+ {reply, {response, Response}, State};
+ {diagnostics, Uri, Jobs} ->
+ Entry = #{uri => Uri, pending => Jobs, diagnostics => []},
+ NewState =
+ State#{in_progress_diagnostics => [Entry | InProgressDiagnostics]},
+ {reply, noresponse, NewState};
+ noresponse ->
+ {reply, noresponse, State}
+ end.
-spec handle_cast(any(), state()) -> {noreply, state()}.
handle_cast({cancel_request, Job}, State) ->
- ?LOG_DEBUG("Cancelling request [job=~p]", [Job]),
- els_background_job:stop(Job),
- #{ in_progress := InProgress } = State,
- NewState = State#{ in_progress => lists:keydelete(Job, 2, InProgress) },
- {noreply, NewState};
+ ?LOG_DEBUG("Cancelling request [job=~p]", [Job]),
+ els_background_job:stop(Job),
+ #{in_progress := InProgress} = State,
+ NewState = State#{in_progress => lists:keydelete(Job, 2, InProgress)},
+ {noreply, NewState};
handle_cast({cancel_request_by_uri, Uri}, State) ->
- #{ in_progress := InProgress0 } = State,
- Fun = fun({U, Job}) ->
- case U =:= Uri of
- true ->
+ #{in_progress := InProgress0} = State,
+ Fun = fun({U, Job}) ->
+ case U =:= Uri of
+ true ->
els_background_job:stop(Job),
false;
- false ->
+ false ->
true
- end
- end,
- InProgress = lists:filtermap(Fun, InProgress0),
- ?LOG_DEBUG("Cancelling requests by Uri [uri=~p]", [Uri]),
- NewState = State#{in_progress => InProgress},
- {noreply, NewState}.
+ end
+ end,
+ InProgress = lists:filtermap(Fun, InProgress0),
+ ?LOG_DEBUG("Cancelling requests by Uri [uri=~p]", [Uri]),
+ NewState = State#{in_progress => InProgress},
+ {noreply, NewState}.
-spec handle_info(any(), state()) -> {noreply, state()}.
handle_info({result, Result, Job}, State) ->
- ?LOG_DEBUG("Received result [job=~p]", [Job]),
- #{in_progress := InProgress} = State,
- els_server:send_response(Job, Result),
- NewState = State#{in_progress => lists:keydelete(Job, 2, InProgress)},
- {noreply, NewState};
+ ?LOG_DEBUG("Received result [job=~p]", [Job]),
+ #{in_progress := InProgress} = State,
+ els_server:send_response(Job, Result),
+ NewState = State#{in_progress => lists:keydelete(Job, 2, InProgress)},
+ {noreply, NewState};
%% LSP 3.15 introduce versioning for diagnostics. Until all clients
%% support it, we need to keep track of old diagnostics and re-publish
%% them every time we get a new chunk.
handle_info({diagnostics, Diagnostics, Job}, State) ->
- #{in_progress_diagnostics := InProgress} = State,
- ?LOG_DEBUG("Received diagnostics [job=~p]", [Job]),
+ #{in_progress_diagnostics := InProgress} = State,
+ ?LOG_DEBUG("Received diagnostics [job=~p]", [Job]),
case find_entry(Job, InProgress) of
- {ok, { #{ pending := Jobs
- , diagnostics := OldDiagnostics
- , uri := Uri
- }
- , Rest
- }} ->
- NewDiagnostics = Diagnostics ++ OldDiagnostics,
- els_diagnostics_provider:publish(Uri, NewDiagnostics),
- NewState = case lists:delete(Job, Jobs) of
- [] ->
- State#{in_progress_diagnostics => Rest};
- Remaining ->
- State#{in_progress_diagnostics =>
- [#{ pending => Remaining
- , diagnostics => NewDiagnostics
- , uri => Uri
- }|Rest]}
- end,
- {noreply, NewState};
- {error, not_found} ->
- {noreply, State}
+ {ok, {
+ #{
+ pending := Jobs,
+ diagnostics := OldDiagnostics,
+ uri := Uri
+ },
+ Rest
+ }} ->
+ NewDiagnostics = Diagnostics ++ OldDiagnostics,
+ els_diagnostics_provider:publish(Uri, NewDiagnostics),
+ NewState =
+ case lists:delete(Job, Jobs) of
+ [] ->
+ State#{in_progress_diagnostics => Rest};
+ Remaining ->
+ State#{
+ in_progress_diagnostics =>
+ [
+ #{
+ pending => Remaining,
+ diagnostics => NewDiagnostics,
+ uri => Uri
+ }
+ | Rest
+ ]
+ }
+ end,
+ {noreply, NewState};
+ {error, not_found} ->
+ {noreply, State}
end;
handle_info(_Request, State) ->
- {noreply, State}.
+ {noreply, State}.
-spec terminate(any(), state()) -> ok.
terminate(_Reason, #{in_progress := InProgress}) ->
- [els_background_job:stop(Job) || {_Uri, Job} <- InProgress],
- ok.
+ [els_background_job:stop(Job) || {_Uri, Job} <- InProgress],
+ ok.
-spec available_providers() -> [provider()].
available_providers() ->
- [ els_completion_provider
- , els_definition_provider
- , els_document_symbol_provider
- , els_hover_provider
- , els_references_provider
- , els_formatting_provider
- , els_document_highlight_provider
- , els_workspace_symbol_provider
- , els_folding_range_provider
- , els_implementation_provider
- , els_code_action_provider
- , els_general_provider
- , els_code_lens_provider
- , els_execute_command_provider
- , els_diagnostics_provider
- , els_rename_provider
- , els_call_hierarchy_provider
- , els_text_synchronization_provider
- ].
+ [
+ els_completion_provider,
+ els_definition_provider,
+ els_document_symbol_provider,
+ els_hover_provider,
+ els_references_provider,
+ els_formatting_provider,
+ els_document_highlight_provider,
+ els_workspace_symbol_provider,
+ els_folding_range_provider,
+ els_implementation_provider,
+ els_code_action_provider,
+ els_general_provider,
+ els_code_lens_provider,
+ els_execute_command_provider,
+ els_diagnostics_provider,
+ els_rename_provider,
+ els_call_hierarchy_provider,
+ els_text_synchronization_provider
+ ].
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec find_entry(job(), [diagnostic_entry()]) ->
- {ok, {diagnostic_entry(), [diagnostic_entry()]}} |
- {error, not_found}.
+ {ok, {diagnostic_entry(), [diagnostic_entry()]}}
+ | {error, not_found}.
find_entry(Job, InProgress) ->
- find_entry(Job, InProgress, []).
+ find_entry(Job, InProgress, []).
-spec find_entry(job(), [diagnostic_entry()], [diagnostic_entry()]) ->
- {ok, {diagnostic_entry(), [diagnostic_entry()]}} |
- {error, not_found}.
+ {ok, {diagnostic_entry(), [diagnostic_entry()]}}
+ | {error, not_found}.
find_entry(_Job, [], []) ->
- {error, not_found};
-find_entry(Job, [#{pending := Pending} = Entry|Rest], Acc) ->
- case lists:member(Job, Pending) of
- true ->
- {ok, {Entry, Rest ++ Acc}};
- false ->
- find_entry(Job, Rest, [Entry|Acc])
- end.
+ {error, not_found};
+find_entry(Job, [#{pending := Pending} = Entry | Rest], Acc) ->
+ case lists:member(Job, Pending) of
+ true ->
+ {ok, {Entry, Rest ++ Acc}};
+ false ->
+ find_entry(Job, Rest, [Entry | Acc])
+ end.
diff --git a/apps/els_core/src/els_stdio.erl b/apps/els_core/src/els_stdio.erl
index b385a7089..33adadd2d 100644
--- a/apps/els_core/src/els_stdio.erl
+++ b/apps/els_core/src/els_stdio.erl
@@ -1,11 +1,12 @@
-module(els_stdio).
--export([ start_listener/1
- , init/1
- , send/2
- ]).
+-export([
+ start_listener/1,
+ init/1,
+ send/2
+]).
--export([ loop/4 ]).
+-export([loop/4]).
%%==============================================================================
%% Includes
@@ -17,20 +18,20 @@
%%==============================================================================
-spec start_listener(function()) -> {ok, pid()}.
start_listener(Cb) ->
- IoDevice = application:get_env(els_core, io_device, standard_io),
- {ok, proc_lib:spawn_link(?MODULE, init, [{Cb, IoDevice}])}.
+ IoDevice = application:get_env(els_core, io_device, standard_io),
+ {ok, proc_lib:spawn_link(?MODULE, init, [{Cb, IoDevice}])}.
-spec init({function(), atom() | pid()}) -> no_return().
init({Cb, IoDevice}) ->
- ?LOG_INFO("Starting stdio server... [io_device=~p]", [IoDevice]),
- ok = io:setopts(IoDevice, [binary, {encoding, latin1}]),
- {ok, Server} = application:get_env(els_core, server),
- ok = Server:set_io_device(IoDevice),
- ?MODULE:loop([], IoDevice, Cb, [return_maps]).
+ ?LOG_INFO("Starting stdio server... [io_device=~p]", [IoDevice]),
+ ok = io:setopts(IoDevice, [binary, {encoding, latin1}]),
+ {ok, Server} = application:get_env(els_core, server),
+ ok = Server:set_io_device(IoDevice),
+ ?MODULE:loop([], IoDevice, Cb, [return_maps]).
-spec send(atom() | pid(), binary()) -> ok.
send(IoDevice, Payload) ->
- io:format(IoDevice, "~s", [Payload]).
+ io:format(IoDevice, "~s", [Payload]).
%%==============================================================================
%% Listener loop function
@@ -38,30 +39,32 @@ send(IoDevice, Payload) ->
-spec loop([binary()], any(), function(), [any()]) -> no_return().
loop(Lines, IoDevice, Cb, JsonOpts) ->
- case io:get_line(IoDevice, "") of
- <<"\n">> ->
- Headers = parse_headers(Lines),
- BinLength = proplists:get_value(<<"content-length">>, Headers),
- Length = binary_to_integer(BinLength),
- %% Use file:read/2 since it reads bytes
- {ok, Payload} = file:read(IoDevice, Length),
- Request = jsx:decode(Payload, JsonOpts),
- Cb([Request]),
- ?MODULE:loop([], IoDevice, Cb, JsonOpts);
- eof ->
- Cb([#{
- <<"method">> => <<"exit">>,
- <<"params">> => []
- }]);
- Line ->
- ?MODULE:loop([Line | Lines], IoDevice, Cb, JsonOpts)
- end.
+ case io:get_line(IoDevice, "") of
+ <<"\n">> ->
+ Headers = parse_headers(Lines),
+ BinLength = proplists:get_value(<<"content-length">>, Headers),
+ Length = binary_to_integer(BinLength),
+ %% Use file:read/2 since it reads bytes
+ {ok, Payload} = file:read(IoDevice, Length),
+ Request = jsx:decode(Payload, JsonOpts),
+ Cb([Request]),
+ ?MODULE:loop([], IoDevice, Cb, JsonOpts);
+ eof ->
+ Cb([
+ #{
+ <<"method">> => <<"exit">>,
+ <<"params">> => []
+ }
+ ]);
+ Line ->
+ ?MODULE:loop([Line | Lines], IoDevice, Cb, JsonOpts)
+ end.
-spec parse_headers([binary()]) -> [{binary(), binary()}].
parse_headers(Lines) ->
- [parse_header(Line) || Line <- Lines].
+ [parse_header(Line) || Line <- Lines].
-spec parse_header(binary()) -> {binary(), binary()}.
parse_header(Line) ->
- [Name, Value] = binary:split(Line, <<":">>),
- {string:trim(string:lowercase(Name)), string:trim(Value)}.
+ [Name, Value] = binary:split(Line, <<":">>),
+ {string:trim(string:lowercase(Name)), string:trim(Value)}.
diff --git a/apps/els_core/src/els_text.erl b/apps/els_core/src/els_text.erl
index 37fe2cd93..f4299fe99 100644
--- a/apps/els_core/src/els_text.erl
+++ b/apps/els_core/src/els_text.erl
@@ -3,124 +3,136 @@
%%==============================================================================
-module(els_text).
--export([ last_token/1
- , line/2
- , line/3
- , range/3
- , split_at_line/2
- , tokens/1
- , apply_edits/2
- ]).
+-export([
+ last_token/1,
+ line/2,
+ line/3,
+ range/3,
+ split_at_line/2,
+ tokens/1,
+ apply_edits/2
+]).
-export_type([edit/0]).
-include("els_core.hrl").
--type edit() :: {poi_range(), string()}.
--type lines() :: [string() | binary()].
--type text() :: binary().
--type line_num() :: non_neg_integer().
+-type edit() :: {poi_range(), string()}.
+-type lines() :: [string() | binary()].
+-type text() :: binary().
+-type line_num() :: non_neg_integer().
-type column_num() :: pos_integer().
--type token() :: erl_scan:token().
+-type token() :: erl_scan:token().
%% @doc Extract the N-th line from a text.
-spec line(text(), line_num()) -> text().
line(Text, LineNum) ->
- Lines = binary:split(Text, [<<"\r\n">>, <<"\n">>], [global]),
- lists:nth(LineNum + 1, Lines).
+ Lines = binary:split(Text, [<<"\r\n">>, <<"\n">>], [global]),
+ lists:nth(LineNum + 1, Lines).
%% @doc Extract the N-th line from a text, up to the given column number.
-spec line(text(), line_num(), column_num()) -> text().
line(Text, LineNum, ColumnNum) ->
- Line = line(Text, LineNum),
- binary:part(Line, {0, ColumnNum}).
+ Line = line(Text, LineNum),
+ binary:part(Line, {0, ColumnNum}).
%% @doc Extract a snippet from a text, from [StartLoc..EndLoc).
-spec range(text(), {line_num(), column_num()}, {line_num(), column_num()}) ->
- text().
+ text().
range(Text, StartLoc, EndLoc) ->
- LineStarts = line_starts(Text),
- StartPos = pos(LineStarts, StartLoc),
- EndPos = pos(LineStarts, EndLoc),
- binary:part(Text, StartPos, EndPos - StartPos).
+ LineStarts = line_starts(Text),
+ StartPos = pos(LineStarts, StartLoc),
+ EndPos = pos(LineStarts, EndLoc),
+ binary:part(Text, StartPos, EndPos - StartPos).
-spec split_at_line(text(), line_num()) -> {text(), text()}.
split_at_line(Text, Line) ->
- StartPos = pos(line_starts(Text), {Line + 1, 1}),
- <> = Text,
- {Left, Right}.
+ StartPos = pos(line_starts(Text), {Line + 1, 1}),
+ <> = Text,
+ {Left, Right}.
%% @doc Return tokens from text.
-spec tokens(text()) -> [any()].
tokens(Text) ->
- case erl_scan:string(els_utils:to_list(Text)) of
- {ok, Tokens, _} -> Tokens;
- {error, _, _} -> []
- end.
+ case erl_scan:string(els_utils:to_list(Text)) of
+ {ok, Tokens, _} -> Tokens;
+ {error, _, _} -> []
+ end.
%% @doc Extract the last token from the given text.
-spec last_token(text()) -> token() | {error, empty}.
last_token(Text) ->
- case tokens(Text) of
- [] -> {error, empty};
- Tokens -> lists:last(Tokens)
- end.
+ case tokens(Text) of
+ [] -> {error, empty};
+ Tokens -> lists:last(Tokens)
+ end.
-spec apply_edits(text(), [edit()]) -> text().
apply_edits(Text, []) ->
- Text;
+ Text;
apply_edits(Text, Edits) when is_binary(Text) ->
- Lines = lists:foldl(fun(Edit, Acc) ->
- apply_edit(Acc, 0, Edit)
- end, bin_to_lines(Text), Edits),
- lines_to_bin(Lines).
+ Lines = lists:foldl(
+ fun(Edit, Acc) ->
+ apply_edit(Acc, 0, Edit)
+ end,
+ bin_to_lines(Text),
+ Edits
+ ),
+ lines_to_bin(Lines).
-spec apply_edit(lines(), line_num(), edit()) -> lines().
apply_edit([], L, {#{from := {FromL, _}}, _} = Edit) when L < FromL ->
- %% End of lines
- %% Add empty line
- [[] | apply_edit([], L + 1, Edit)];
+ %% End of lines
+ %% Add empty line
+ [[] | apply_edit([], L + 1, Edit)];
apply_edit([], L, {#{from := {L, FromC}}, Insert}) ->
- %% End of lines
- Padding = lists:duplicate(FromC, $ ),
- string:split(Padding ++ Insert, "\n");
-apply_edit([CurrLine|RestLines], L, {#{from := {FromL, _}}, _} = Edit)
- when L < FromL ->
- %% Go to next line
- [CurrLine|apply_edit(RestLines, L + 1, Edit)];
-apply_edit([CurrLine0|RestLines], L,
- {#{from := {L, FromC}, to := {L, ToC}}, Insert}) ->
- CurrLine = ensure_string(CurrLine0),
- %% One line edit
- {Prefix, Rest} = lists:split(FromC, CurrLine),
- {_, Suffix} = lists:split(ToC - FromC, Rest),
- string:split(Prefix ++ Insert ++ Suffix, "\n") ++ RestLines;
-apply_edit([CurrLine0|RestLines], L,
- {#{from := {L, FromC}, to := {ToL, ToC}}, Insert}) ->
- %% Multiline edit
- CurrLine = ensure_string(CurrLine0),
- {Prefix, _} = lists:split(FromC, CurrLine),
- case lists:split(ToL - L - 1, RestLines) of
- {_, []} ->
- string:split(Prefix ++ Insert, "\n") ++ RestLines;
- {_, [CurrSuffix|SuffixLines]} ->
- {_, Suffix} = lists:split(ToC, ensure_string(CurrSuffix)),
- string:split(Prefix ++ Insert ++ Suffix, "\n") ++ SuffixLines
- end.
+ %% End of lines
+ Padding = lists:duplicate(FromC, $\s),
+ string:split(Padding ++ Insert, "\n");
+apply_edit([CurrLine | RestLines], L, {#{from := {FromL, _}}, _} = Edit) when
+ L < FromL
+->
+ %% Go to next line
+ [CurrLine | apply_edit(RestLines, L + 1, Edit)];
+apply_edit(
+ [CurrLine0 | RestLines],
+ L,
+ {#{from := {L, FromC}, to := {L, ToC}}, Insert}
+) ->
+ CurrLine = ensure_string(CurrLine0),
+ %% One line edit
+ {Prefix, Rest} = lists:split(FromC, CurrLine),
+ {_, Suffix} = lists:split(ToC - FromC, Rest),
+ string:split(Prefix ++ Insert ++ Suffix, "\n") ++ RestLines;
+apply_edit(
+ [CurrLine0 | RestLines],
+ L,
+ {#{from := {L, FromC}, to := {ToL, ToC}}, Insert}
+) ->
+ %% Multiline edit
+ CurrLine = ensure_string(CurrLine0),
+ {Prefix, _} = lists:split(FromC, CurrLine),
+ case lists:split(ToL - L - 1, RestLines) of
+ {_, []} ->
+ string:split(Prefix ++ Insert, "\n") ++ RestLines;
+ {_, [CurrSuffix | SuffixLines]} ->
+ {_, Suffix} = lists:split(ToC, ensure_string(CurrSuffix)),
+ string:split(Prefix ++ Insert ++ Suffix, "\n") ++ SuffixLines
+ end.
-spec lines_to_bin(lines()) -> text().
lines_to_bin(Lines) ->
- els_utils:to_binary(lists:join("\n", Lines)).
+ els_utils:to_binary(lists:join("\n", Lines)).
-spec bin_to_lines(text()) -> lines().
bin_to_lines(Text) ->
- [Bin || Bin <- binary:split(Text, [<<"\r\n">>, <<"\n">>], [global])].
+ [Bin || Bin <- binary:split(Text, [<<"\r\n">>, <<"\n">>], [global])].
-spec ensure_string(binary() | string()) -> string().
ensure_string(Text) when is_binary(Text) ->
- els_utils:to_list(Text);
+ els_utils:to_list(Text);
ensure_string(Text) ->
- Text.
+ Text.
%%==============================================================================
%% Internal functions
@@ -128,10 +140,10 @@ ensure_string(Text) ->
-spec line_starts(text()) -> [{integer(), any()}].
line_starts(Text) ->
- [{-1, 1} | binary:matches(Text, <<"\n">>)].
+ [{-1, 1} | binary:matches(Text, <<"\n">>)].
-spec pos([{integer(), any()}], {line_num(), column_num()}) ->
- pos_integer().
+ pos_integer().
pos(LineStarts, {LineNum, ColumnNum}) ->
- {LinePos, _} = lists:nth(LineNum, LineStarts),
- LinePos + ColumnNum.
+ {LinePos, _} = lists:nth(LineNum, LineStarts),
+ LinePos + ColumnNum.
diff --git a/apps/els_core/src/els_uri.erl b/apps/els_core/src/els_uri.erl
index 05c7196ab..f0de15bcb 100644
--- a/apps/els_core/src/els_uri.erl
+++ b/apps/els_core/src/els_uri.erl
@@ -8,17 +8,18 @@
%%==============================================================================
%% Exports
%%==============================================================================
--export([ module/1
- , path/1
- , uri/1
- ]).
+-export([
+ module/1,
+ path/1,
+ uri/1
+]).
%%==============================================================================
%% Types
%%==============================================================================
-type path() :: binary().
--export_type([ path/0 ]).
+-export_type([path/0]).
%%==============================================================================
%% Includes
@@ -27,87 +28,102 @@
-spec module(uri()) -> atom().
module(Uri) ->
- binary_to_atom(filename:basename(path(Uri), <<".erl">>), utf8).
+ binary_to_atom(filename:basename(path(Uri), <<".erl">>), utf8).
-spec path(uri()) -> path().
path(Uri) ->
- path(Uri, els_utils:is_windows()).
+ path(Uri, els_utils:is_windows()).
-spec path(uri(), boolean()) -> path().
path(Uri, IsWindows) ->
- #{ host := Host
- , path := Path0
- , scheme := <<"file">>
- } = uri_string:normalize(Uri, [return_map]),
- Path = percent_decode(Path0),
- case {IsWindows, Host} of
- {true, <<>>} ->
- % Windows drive letter, have to strip the initial slash
- re:replace(
- Path, "^/([a-zA-Z]:)(.*)", "\\1\\2", [{return, binary}]
- );
- {true, _} ->
- <<"//", Host/binary, Path/binary>>;
- {false, <<>>} ->
- Path;
- {false, _} ->
- error(badarg)
- end.
+ #{
+ host := Host,
+ path := Path0,
+ scheme := <<"file">>
+ } = uri_string:normalize(Uri, [return_map]),
+ Path = percent_decode(Path0),
+ case {IsWindows, Host} of
+ {true, <<>>} ->
+ % Windows drive letter, have to strip the initial slash
+ re:replace(
+ Path, "^/([a-zA-Z]:)(.*)", "\\1\\2", [{return, binary}]
+ );
+ {true, _} ->
+ <<"//", Host/binary, Path/binary>>;
+ {false, <<>>} ->
+ Path;
+ {false, _} ->
+ error(badarg)
+ end.
-spec uri(path()) -> uri().
uri(Path) ->
- [Head | Tail] = filename:split(Path),
- {Host, Path1} = case {els_utils:is_windows(), Head} of
- {false, <<"/">>} ->
- {<<>>, uri_join(Tail)};
- {true, X} when X =:= <<"//">> orelse X =:= <<"\\\\">> ->
- [H | T] = Tail,
- {H, uri_join(T)};
- {true, _} ->
- % Strip the trailing slash from the first component
- H1 = string:slice(Head, 0, 2),
- {<<>>, uri_join([H1|Tail])}
- end,
+ [Head | Tail] = filename:split(Path),
+ {Host, Path1} =
+ case {els_utils:is_windows(), Head} of
+ {false, <<"/">>} ->
+ {<<>>, uri_join(Tail)};
+ {true, X} when X =:= <<"//">> orelse X =:= <<"\\\\">> ->
+ [H | T] = Tail,
+ {H, uri_join(T)};
+ {true, _} ->
+ % Strip the trailing slash from the first component
+ H1 = string:slice(Head, 0, 2),
+ {<<>>, uri_join([H1 | Tail])}
+ end,
- els_utils:to_binary(
- uri_string:recompose(#{
- scheme => <<"file">>,
- host => Host,
- path => [$/, Path1]
- })
- ).
+ els_utils:to_binary(
+ uri_string:recompose(#{
+ scheme => <<"file">>,
+ host => Host,
+ path => [$/, Path1]
+ })
+ ).
-spec uri_join([path()]) -> iolist().
uri_join(List) ->
- lists:join(<<"/">>, List).
+ lists:join(<<"/">>, List).
-if(?OTP_RELEASE >= 23).
-spec percent_decode(binary()) -> binary().
percent_decode(Str) ->
- uri_string:percent_decode(Str).
+ uri_string:percent_decode(Str).
-else.
-spec percent_decode(binary()) -> binary().
percent_decode(Str) ->
- http_uri:decode(Str).
+ http_uri:decode(Str).
-endif.
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
path_uri_test_() ->
- [ ?_assertEqual( <<"/foo/bar.erl">>
- , path(<<"file:///foo/bar.erl">>))
- , ?_assertEqual( <<"/foo/bar baz.erl">>
- , path(<<"file:///foo/bar%20baz.erl">>))
- , ?_assertEqual( <<"/foo/bar.erl">>
- , path(uri(path(<<"file:///foo/bar.erl">>))))
- , ?_assertEqual( <<"/foo/bar baz.erl">>
- , path(uri(<<"/foo/bar baz.erl">>)))
- , ?_assertEqual( <<"file:///foo/bar%20baz.erl">>
- , uri(<<"/foo/bar baz.erl">>))
- ].
+ [
+ ?_assertEqual(
+ <<"/foo/bar.erl">>,
+ path(<<"file:///foo/bar.erl">>)
+ ),
+ ?_assertEqual(
+ <<"/foo/bar baz.erl">>,
+ path(<<"file:///foo/bar%20baz.erl">>)
+ ),
+ ?_assertEqual(
+ <<"/foo/bar.erl">>,
+ path(uri(path(<<"file:///foo/bar.erl">>)))
+ ),
+ ?_assertEqual(
+ <<"/foo/bar baz.erl">>,
+ path(uri(<<"/foo/bar baz.erl">>))
+ ),
+ ?_assertEqual(
+ <<"file:///foo/bar%20baz.erl">>,
+ uri(<<"/foo/bar baz.erl">>)
+ )
+ ].
path_windows_test() ->
- ?assertEqual(<<"C:/foo/bar.erl">>,
- path(<<"file:///C%3A/foo/bar.erl">>, true)).
+ ?assertEqual(
+ <<"C:/foo/bar.erl">>,
+ path(<<"file:///C%3A/foo/bar.erl">>, true)
+ ).
-endif.
diff --git a/apps/els_core/src/els_utils.erl b/apps/els_core/src/els_utils.erl
index 53de59039..edb562436 100644
--- a/apps/els_core/src/els_utils.erl
+++ b/apps/els_core/src/els_utils.erl
@@ -1,30 +1,31 @@
-module(els_utils).
--export([ cmd/2
- , cmd/3
- , filename_to_atom/1
- , find_header/1
- , find_module/1
- , find_modules/1
- , fold_files/4
- , halt/1
- , lookup_document/1
- , include_id/1
- , include_lib_id/1
- , macro_string_to_term/1
- , project_relative/1
- , resolve_paths/3
- , to_binary/1
- , to_list/1
- , compose_node_name/2
- , function_signature/1
- , base64_encode_term/1
- , base64_decode_term/1
- , levenshtein_distance/2
- , jaro_distance/2
- , is_windows/0
- , system_tmp_dir/0
- ]).
+-export([
+ cmd/2,
+ cmd/3,
+ filename_to_atom/1,
+ find_header/1,
+ find_module/1,
+ find_modules/1,
+ fold_files/4,
+ halt/1,
+ lookup_document/1,
+ include_id/1,
+ include_lib_id/1,
+ macro_string_to_term/1,
+ project_relative/1,
+ resolve_paths/3,
+ to_binary/1,
+ to_list/1,
+ compose_node_name/2,
+ function_signature/1,
+ base64_encode_term/1,
+ base64_decode_term/1,
+ levenshtein_distance/2,
+ jaro_distance/2,
+ is_windows/0,
+ system_tmp_dir/0
+]).
%%==============================================================================
%% Includes
@@ -40,164 +41,167 @@
-spec cmd(string(), [string()]) -> integer() | no_return().
cmd(Cmd, Args) ->
- cmd(Cmd, Args, []).
+ cmd(Cmd, Args, []).
% @doc Replacement for os:cmd that allows for spaces in args and paths
-spec cmd(string(), [string()], string()) -> integer() | no_return().
cmd(Cmd, Args, Path) ->
- ?LOG_INFO("Running OS command [command=~p] [args=~p]", [Cmd, Args]),
- Executable = case filename:basename(Cmd) of
- Cmd ->
- cmd_path(Cmd);
- _ ->
- %% The command already contains a path
- Cmd
- end,
- Tag = make_ref(),
- F =
- fun() ->
- P = open_port(
- {spawn_executable, Executable},
- [ binary
- , use_stdio
- , stream
- , exit_status
- , hide
- , {args, Args}
- %% TODO: Windows-friendly version?
- , {env, [{"PATH", Path ++ ":" ++ os:getenv("PATH")}]}
- ]
- ),
- exit({Tag, cmd_receive(P)})
- end,
- {Pid, Ref} = erlang:spawn_monitor(F),
- receive
- {'DOWN', Ref, process, Pid, {Tag, Data}} ->
- Data;
- {'DOWN', Ref, process, Pid, Reason} ->
- exit(Reason)
- end.
+ ?LOG_INFO("Running OS command [command=~p] [args=~p]", [Cmd, Args]),
+ Executable =
+ case filename:basename(Cmd) of
+ Cmd ->
+ cmd_path(Cmd);
+ _ ->
+ %% The command already contains a path
+ Cmd
+ end,
+ Tag = make_ref(),
+ F =
+ fun() ->
+ P = open_port(
+ {spawn_executable, Executable},
+ [
+ binary,
+ use_stdio,
+ stream,
+ exit_status,
+ hide,
+ {args, Args},
+ %% TODO: Windows-friendly version?
+ {env, [{"PATH", Path ++ ":" ++ os:getenv("PATH")}]}
+ ]
+ ),
+ exit({Tag, cmd_receive(P)})
+ end,
+ {Pid, Ref} = erlang:spawn_monitor(F),
+ receive
+ {'DOWN', Ref, process, Pid, {Tag, Data}} ->
+ Data;
+ {'DOWN', Ref, process, Pid, Reason} ->
+ exit(Reason)
+ end.
%% @doc Return the path for a command
-spec cmd_path(string()) -> string().
cmd_path(Cmd) ->
- ErtsBinDir = filename:dirname(escript:script_name()),
- case os:find_executable(Cmd, ErtsBinDir) of
- false ->
- case os:find_executable(Cmd) of
+ ErtsBinDir = filename:dirname(escript:script_name()),
+ case os:find_executable(Cmd, ErtsBinDir) of
false ->
- Fmt = "Could not find command ~p",
- Args = [Cmd],
- Msg = lists:flatten(io_lib:format(Fmt, Args)),
- error(Msg);
- GlobalEpmd ->
- GlobalEpmd
- end;
- Epmd ->
- Epmd
- end.
+ case os:find_executable(Cmd) of
+ false ->
+ Fmt = "Could not find command ~p",
+ Args = [Cmd],
+ Msg = lists:flatten(io_lib:format(Fmt, Args)),
+ error(Msg);
+ GlobalEpmd ->
+ GlobalEpmd
+ end;
+ Epmd ->
+ Epmd
+ end.
%% @doc Convert an 'include'/'include_lib' POI ID to a document index ID
-spec filename_to_atom(string()) -> atom().
filename_to_atom(FileName) ->
- list_to_atom(filename:basename(FileName, filename:extension(FileName))).
+ list_to_atom(filename:basename(FileName, filename:extension(FileName))).
%% @doc Look for a header in the DB
-spec find_header(atom()) -> {ok, uri()} | {error, any()}.
find_header(Id) ->
- {ok, Candidates} = els_dt_document_index:lookup(Id),
- case [Uri || #{kind := header, uri := Uri} <- Candidates] of
- [Uri | _] ->
- {ok, Uri};
- [] ->
- FileName = atom_to_list(Id) ++ ".hrl",
- els_indexing:find_and_deeply_index_file(FileName)
- end.
+ {ok, Candidates} = els_dt_document_index:lookup(Id),
+ case [Uri || #{kind := header, uri := Uri} <- Candidates] of
+ [Uri | _] ->
+ {ok, Uri};
+ [] ->
+ FileName = atom_to_list(Id) ++ ".hrl",
+ els_indexing:find_and_deeply_index_file(FileName)
+ end.
%% @doc Look for a module in the DB
-spec find_module(atom()) -> {ok, uri()} | {error, not_found}.
find_module(Id) ->
- case find_modules(Id) of
- {ok, [Uri | _]} ->
- {ok, Uri};
- {ok, []} ->
- {error, not_found}
- end.
+ case find_modules(Id) of
+ {ok, [Uri | _]} ->
+ {ok, Uri};
+ {ok, []} ->
+ {error, not_found}
+ end.
%% @doc Look for all versions of a module in the DB
-spec find_modules(atom()) -> {ok, [uri()]}.
find_modules(Id) ->
- {ok, Candidates} = els_dt_document_index:lookup(Id),
- case [Uri || #{kind := module, uri := Uri} <- Candidates] of
- [] ->
- FileName = atom_to_list(Id) ++ ".erl",
- case els_indexing:find_and_deeply_index_file(FileName) of
- {ok, Uri} -> {ok, [Uri]};
- _Error ->
- ?LOG_INFO("Finding module failed [filename=~p]", [FileName]),
- {ok, []}
- end;
- Uris ->
- {ok, prioritize_uris(Uris)}
- end.
+ {ok, Candidates} = els_dt_document_index:lookup(Id),
+ case [Uri || #{kind := module, uri := Uri} <- Candidates] of
+ [] ->
+ FileName = atom_to_list(Id) ++ ".erl",
+ case els_indexing:find_and_deeply_index_file(FileName) of
+ {ok, Uri} ->
+ {ok, [Uri]};
+ _Error ->
+ ?LOG_INFO("Finding module failed [filename=~p]", [FileName]),
+ {ok, []}
+ end;
+ Uris ->
+ {ok, prioritize_uris(Uris)}
+ end.
%% @doc Look for a document in the DB.
%%
%% Look for a given document in the DB and return it.
%% If the module is not in the DB, try to index it.
-spec lookup_document(uri()) ->
- {ok, els_dt_document:item()} | {error, any()}.
+ {ok, els_dt_document:item()} | {error, any()}.
lookup_document(Uri0) ->
- case els_dt_document:lookup(Uri0) of
- {ok, [Document]} ->
- {ok, Document};
- {ok, []} ->
- Path = els_uri:path(Uri0),
- %% The returned Uri could be different from the original input
- %% (e.g. if the original Uri would contain a query string)
- {ok, Uri} = els_indexing:shallow_index(Path, app),
- case els_dt_document:lookup(Uri) of
+ case els_dt_document:lookup(Uri0) of
{ok, [Document]} ->
- {ok, Document};
+ {ok, Document};
{ok, []} ->
- ?LOG_INFO("Document lookup failed [uri=~p]", [Uri]),
- {error, document_lookup_failed}
- end
- end.
+ Path = els_uri:path(Uri0),
+ %% The returned Uri could be different from the original input
+ %% (e.g. if the original Uri would contain a query string)
+ {ok, Uri} = els_indexing:shallow_index(Path, app),
+ case els_dt_document:lookup(Uri) of
+ {ok, [Document]} ->
+ {ok, Document};
+ {ok, []} ->
+ ?LOG_INFO("Document lookup failed [uri=~p]", [Uri]),
+ {error, document_lookup_failed}
+ end
+ end.
%% @doc Convert path to an 'include' POI id
-spec include_id(file:filename_all()) -> string().
include_id(Path) ->
- els_utils:to_list(filename:basename(Path)).
+ els_utils:to_list(filename:basename(Path)).
%% @doc Convert path to an 'include_lib' POI id
-spec include_lib_id(file:filename_all()) -> string().
include_lib_id(Path) ->
- Components = filename:split(Path),
- Length = length(Components),
- End = Length - 1,
- Beginning = max(1, Length - 2),
- [H|T] = lists:sublist(Components, Beginning, End),
- %% Strip the app version number from the path
- Id = filename:join([re:replace(H, "-.*", "", [{return, list}]) | T]),
- els_utils:to_list(Id).
+ Components = filename:split(Path),
+ Length = length(Components),
+ End = Length - 1,
+ Beginning = max(1, Length - 2),
+ [H | T] = lists:sublist(Components, Beginning, End),
+ %% Strip the app version number from the path
+ Id = filename:join([re:replace(H, "-.*", "", [{return, list}]) | T]),
+ els_utils:to_list(Id).
-spec macro_string_to_term(list()) -> any().
macro_string_to_term(Value) ->
- try
- {ok, Tokens, _End} = erl_scan:string(Value ++ "."),
- {ok, Term} = erl_parse:parse_term(Tokens),
- Term
- catch
- _Class:Exception ->
- Fmt =
- "Error parsing custom defined macro, "
- "falling back to 'true'"
- "[value=~p] [exception=~p]",
- Args = [Value, Exception],
- ?LOG_ERROR(Fmt, Args),
- true
- end.
+ try
+ {ok, Tokens, _End} = erl_scan:string(Value ++ "."),
+ {ok, Term} = erl_parse:parse_term(Tokens),
+ Term
+ catch
+ _Class:Exception ->
+ Fmt =
+ "Error parsing custom defined macro, "
+ "falling back to 'true'"
+ "[value=~p] [exception=~p]",
+ Args = [Value, Exception],
+ ?LOG_ERROR(Fmt, Args),
+ true
+ end.
%% @doc Folds over all files in a directory
%%
@@ -205,7 +209,7 @@ macro_string_to_term(Value) ->
%% skipping all symlinks.
-spec fold_files(function(), function(), path(), any()) -> any().
fold_files(F, Filter, Dir, Acc) ->
- do_fold_dir(F, Filter, Dir, Acc).
+ do_fold_dir(F, Filter, Dir, Acc).
%% @doc Resolve paths based on path specs
%%
@@ -214,58 +218,59 @@ fold_files(F, Filter, Dir, Acc) ->
%% symlinks will be ignored.
-spec resolve_paths([[path()]], path(), boolean()) -> [[path()]].
resolve_paths(PathSpecs, RootPath, Recursive) ->
- lists:append([ resolve_path(PathSpec, RootPath, Recursive)
- || PathSpec <- PathSpecs
- ]).
+ lists:append([
+ resolve_path(PathSpec, RootPath, Recursive)
+ || PathSpec <- PathSpecs
+ ]).
-spec halt(non_neg_integer()) -> ok.
halt(ExitCode) ->
- ok = init:stop(ExitCode).
+ ok = init:stop(ExitCode).
%% @doc Returns a project-relative file path for a given URI
-spec project_relative(uri()) -> file:filename() | {error, not_relative}.
project_relative(Uri) ->
- RootUri = els_config:get(root_uri),
- Size = byte_size(RootUri),
- case Uri of
- <> ->
- Trimmed = string:trim(Relative, leading, [$/, $\\ ]),
- to_list(Trimmed);
- _ ->
- {error, not_relative}
- end.
+ RootUri = els_config:get(root_uri),
+ Size = byte_size(RootUri),
+ case Uri of
+ <> ->
+ Trimmed = string:trim(Relative, leading, [$/, $\\]),
+ to_list(Trimmed);
+ _ ->
+ {error, not_relative}
+ end.
-spec to_binary(unicode:chardata()) -> binary().
to_binary(X) when is_binary(X) ->
- X;
+ X;
to_binary(X) when is_list(X) ->
- case unicode:characters_to_binary(X) of
- Result when is_binary(Result) -> Result;
- _ -> iolist_to_binary(X)
- end.
+ case unicode:characters_to_binary(X) of
+ Result when is_binary(Result) -> Result;
+ _ -> iolist_to_binary(X)
+ end.
-spec to_list(unicode:chardata()) -> string().
to_list(X) when is_list(X) ->
- X;
+ X;
to_list(X) when is_binary(X) ->
- case unicode:characters_to_list(X) of
- Result when is_list(Result) -> Result;
- _ -> binary_to_list(X)
- end.
+ case unicode:characters_to_list(X) of
+ Result when is_list(Result) -> Result;
+ _ -> binary_to_list(X)
+ end.
-spec is_windows() -> boolean().
is_windows() ->
- {OS, _} = os:type(),
- OS =:= win32.
+ {OS, _} = os:type(),
+ OS =:= win32.
-spec system_tmp_dir() -> string().
system_tmp_dir() ->
- case is_windows() of
- true ->
- os:getenv("TEMP");
- false ->
- "/tmp"
- end.
+ case is_windows() of
+ true ->
+ os:getenv("TEMP");
+ false ->
+ "/tmp"
+ end.
%%==============================================================================
%% Internal functions
@@ -274,123 +279,128 @@ system_tmp_dir() ->
%% Folding over files
-spec do_fold_files(function(), function(), path(), [path()], any()) -> any().
-do_fold_files(_F, _Filter, _Dir, [], Acc0) ->
- Acc0;
+do_fold_files(_F, _Filter, _Dir, [], Acc0) ->
+ Acc0;
do_fold_files(F, Filter, Dir, [File | Rest], Acc0) ->
- Path = filename:join(Dir, File),
- %% Symbolic links are not regular files
- Acc = case filelib:is_regular(Path) of
- true -> do_fold_file(F, Filter, Path, Acc0);
- false -> Acc0
- end,
- do_fold_files(F, Filter, Dir, Rest, Acc).
+ Path = filename:join(Dir, File),
+ %% Symbolic links are not regular files
+ Acc =
+ case filelib:is_regular(Path) of
+ true -> do_fold_file(F, Filter, Path, Acc0);
+ false -> Acc0
+ end,
+ do_fold_files(F, Filter, Dir, Rest, Acc).
-spec do_fold_file(function(), function(), path(), any()) ->
- any().
+ any().
do_fold_file(F, Filter, Path, Acc) ->
- case Filter(Path) of
- true -> F(Path, Acc);
- false -> Acc
- end.
+ case Filter(Path) of
+ true -> F(Path, Acc);
+ false -> Acc
+ end.
-spec do_fold_dir(function(), function(), path(), any()) ->
- any().
+ any().
do_fold_dir(F, Filter, Dir, Acc) ->
- case not is_symlink(Dir) andalso filelib:is_dir(Dir) of
- true ->
- {ok, Files} = file:list_dir(Dir),
- do_fold_files(F, Filter, Dir, Files, Acc);
- false ->
- Acc
- end.
+ case not is_symlink(Dir) andalso filelib:is_dir(Dir) of
+ true ->
+ {ok, Files} = file:list_dir(Dir),
+ do_fold_files(F, Filter, Dir, Files, Acc);
+ false ->
+ Acc
+ end.
-spec is_symlink(path()) -> boolean().
is_symlink(Path) ->
- case file:read_link(Path) of
- {ok, _} -> true;
- {error, _} -> false
- end.
+ case file:read_link(Path) of
+ {ok, _} -> true;
+ {error, _} -> false
+ end.
%% @doc Resolve paths recursively
-spec resolve_path([path()], path(), boolean()) -> [path()].
resolve_path(PathSpec, RootPath, Recursive) ->
- Path = filename:join(PathSpec),
- Paths = filelib:wildcard(Path),
-
- case Recursive of
- true ->
- lists:append([ [make_normalized_path(P) | subdirs(P)]
- || P <- Paths, not contains_symlink(P, RootPath)
- ]);
- false ->
- [make_normalized_path(P) || P <- Paths, not contains_symlink(P, RootPath)]
- end.
+ Path = filename:join(PathSpec),
+ Paths = filelib:wildcard(Path),
+
+ case Recursive of
+ true ->
+ lists:append([
+ [make_normalized_path(P) | subdirs(P)]
+ || P <- Paths, not contains_symlink(P, RootPath)
+ ]);
+ false ->
+ [make_normalized_path(P) || P <- Paths, not contains_symlink(P, RootPath)]
+ end.
%% Returns all subdirectories for the provided path
-spec subdirs(path()) -> [path()].
subdirs(Path) ->
- subdirs(Path, []).
+ subdirs(Path, []).
-spec subdirs(path(), [path()]) -> [path()].
subdirs(Path, Subdirs) ->
- case file:list_dir(Path) of
- {ok, Files} -> subdirs_(Path, Files, Subdirs);
- {error, _} -> Subdirs
- end.
+ case file:list_dir(Path) of
+ {ok, Files} -> subdirs_(Path, Files, Subdirs);
+ {error, _} -> Subdirs
+ end.
-spec subdirs_(path(), [path()], [path()]) -> [path()].
subdirs_(Path, Files, Subdirs) ->
- Fold = fun(F, Acc) ->
- FullPath = filename:join([Path, F]),
- case
- not is_symlink(FullPath)
- andalso filelib:is_dir(FullPath)
- of
- true -> subdirs(FullPath, [FullPath | Acc]);
- false -> Acc
- end
- end,
- lists:foldl(Fold, Subdirs, Files).
+ Fold = fun(F, Acc) ->
+ FullPath = filename:join([Path, F]),
+ case
+ not is_symlink(FullPath) andalso
+ filelib:is_dir(FullPath)
+ of
+ true -> subdirs(FullPath, [FullPath | Acc]);
+ false -> Acc
+ end
+ end,
+ lists:foldl(Fold, Subdirs, Files).
-spec contains_symlink(path(), path()) -> boolean().
contains_symlink(RootPath, RootPath) ->
- false;
+ false;
contains_symlink([], _RootPath) ->
- false;
+ false;
contains_symlink(Path, RootPath) ->
- Parts = filename:split(Path),
- case lists:droplast(Parts) of
- [] -> false;
- ParentParts ->
- Parent = filename:join(ParentParts),
- ((not (Parent == RootPath)) and (is_symlink(Parent)))
- orelse contains_symlink(Parent, RootPath)
- end.
+ Parts = filename:split(Path),
+ case lists:droplast(Parts) of
+ [] ->
+ false;
+ ParentParts ->
+ Parent = filename:join(ParentParts),
+ ((not (Parent == RootPath)) and (is_symlink(Parent))) orelse
+ contains_symlink(Parent, RootPath)
+ end.
-spec cmd_receive(port()) -> integer().
cmd_receive(Port) ->
- receive
- {Port, {exit_status, ExitCode}} ->
- ExitCode;
- {Port, _} ->
- cmd_receive(Port)
- end.
+ receive
+ {Port, {exit_status, ExitCode}} ->
+ ExitCode;
+ {Port, _} ->
+ cmd_receive(Port)
+ end.
%% @doc Prioritize files
%% Prefer files below root and prefer files in src dir.
-spec prioritize_uris([uri()]) -> [uri()].
prioritize_uris(Uris) ->
- Root = els_config:get(root_uri),
- Order = fun(nomatch) -> 3;
- (Cont) ->
- case string:find(Cont, "/src/") of
+ Root = els_config:get(root_uri),
+ Order = fun
+ (nomatch) ->
+ 3;
+ (Cont) ->
+ case string:find(Cont, "/src/") of
nomatch -> 1;
_ -> 0
- end
- end,
- Prio = [{Order(string:prefix(Uri, Root)), Uri} || Uri <- Uris],
- [Uri || {_, Uri} <- lists:sort(Prio)].
+ end
+ end,
+ Prio = [{Order(string:prefix(Uri, Root)), Uri} || Uri <- Uris],
+ [Uri || {_, Uri} <- lists:sort(Prio)].
%%==============================================================================
%% This section excerpted from the rebar3 sources, rebar_dir.erl
@@ -400,100 +410,103 @@ prioritize_uris(Uris) ->
%% @doc make a path absolute
-spec make_absolute_path(path()) -> path().
make_absolute_path(Path) ->
- case filename:pathtype(Path) of
- absolute ->
- Path;
- relative ->
- {ok, Dir} = file:get_cwd(),
- filename:join([Dir, Path]);
- volumerelative ->
- Volume = hd(filename:split(Path)),
- {ok, Dir} = file:get_cwd(Volume),
- filename:join([Dir, Path])
- end.
+ case filename:pathtype(Path) of
+ absolute ->
+ Path;
+ relative ->
+ {ok, Dir} = file:get_cwd(),
+ filename:join([Dir, Path]);
+ volumerelative ->
+ Volume = hd(filename:split(Path)),
+ {ok, Dir} = file:get_cwd(Volume),
+ filename:join([Dir, Path])
+ end.
%% @doc normalizing a path removes all of the `..' and the
%% `.' segments it may contain.
-spec make_normalized_path(path()) -> path().
make_normalized_path(Path) ->
- AbsPath = make_absolute_path(Path),
- Components = filename:split(AbsPath),
- make_normalized_path(Components, []).
+ AbsPath = make_absolute_path(Path),
+ Components = filename:split(AbsPath),
+ make_normalized_path(Components, []).
%% @private drops path fragments for normalization
-spec make_normalized_path([file:name_all()], [file:name_all()]) -> path().
make_normalized_path([], NormalizedPath) ->
- filename:join(lists:reverse(NormalizedPath));
+ filename:join(lists:reverse(NormalizedPath));
make_normalized_path(["." | []], []) ->
- ".";
+ ".";
make_normalized_path(["." | T], NormalizedPath) ->
- make_normalized_path(T, NormalizedPath);
+ make_normalized_path(T, NormalizedPath);
make_normalized_path([".." | T], []) ->
- make_normalized_path(T, [".."]);
+ make_normalized_path(T, [".."]);
make_normalized_path([".." | T], [Head | Tail]) when Head =/= ".." ->
- make_normalized_path(T, Tail);
+ make_normalized_path(T, Tail);
make_normalized_path([H | T], NormalizedPath) ->
- make_normalized_path(T, [H | NormalizedPath]).
+ make_normalized_path(T, [H | NormalizedPath]).
-spec compose_node_name(Name :: string(), Type :: shortnames | longnames) ->
- NodeName :: atom().
+ NodeName :: atom().
compose_node_name(Name, Type) ->
- NodeName = case lists:member($@, Name) of
- true ->
- Name;
- _ ->
- {ok, HostName} = inet:gethostname(),
- Name ++ [$@ | HostName]
- end,
- case Type of
- shortnames ->
- list_to_atom(NodeName);
- longnames ->
- Domain = proplists:get_value(domain, inet:get_rc(), ""),
- list_to_atom(NodeName ++ "." ++ Domain)
- end.
+ NodeName =
+ case lists:member($@, Name) of
+ true ->
+ Name;
+ _ ->
+ {ok, HostName} = inet:gethostname(),
+ Name ++ [$@ | HostName]
+ end,
+ case Type of
+ shortnames ->
+ list_to_atom(NodeName);
+ longnames ->
+ Domain = proplists:get_value(domain, inet:get_rc(), ""),
+ list_to_atom(NodeName ++ "." ++ Domain)
+ end.
%% @doc Given an MFA or a FA, return a printable version of the
%% function signature, in binary format.
--spec function_signature( {atom(), atom(), non_neg_integer()} |
- {atom(), non_neg_integer()}) ->
- binary().
+-spec function_signature(
+ {atom(), atom(), non_neg_integer()}
+ | {atom(), non_neg_integer()}
+) ->
+ binary().
function_signature({M, F, A}) ->
- els_utils:to_binary(io_lib:format("~p:~ts/~p", [M, F, A]));
+ els_utils:to_binary(io_lib:format("~p:~ts/~p", [M, F, A]));
function_signature({F, A}) ->
- els_utils:to_binary(io_lib:format("~ts/~p", [F, A])).
+ els_utils:to_binary(io_lib:format("~ts/~p", [F, A])).
-spec base64_encode_term(any()) -> binary().
base64_encode_term(Term) ->
- els_utils:to_binary(base64:encode_to_string(term_to_binary(Term))).
+ els_utils:to_binary(base64:encode_to_string(term_to_binary(Term))).
-spec base64_decode_term(binary()) -> any().
base64_decode_term(Base64) ->
- binary_to_term(base64:decode(Base64)).
+ binary_to_term(base64:decode(Base64)).
-spec levenshtein_distance(binary(), binary()) -> integer().
levenshtein_distance(S, T) ->
- {Distance, _} = levenshtein_distance(to_list(S), to_list(T), #{}),
- Distance.
+ {Distance, _} = levenshtein_distance(to_list(S), to_list(T), #{}),
+ Distance.
-spec levenshtein_distance(string(), string(), map()) -> {integer(), map()}.
levenshtein_distance([] = S, T, Cache) ->
- {length(T), maps:put({S, T}, length(T), Cache)};
+ {length(T), maps:put({S, T}, length(T), Cache)};
levenshtein_distance(S, [] = T, Cache) ->
- {length(S), maps:put({S, T}, length(S), Cache)};
-levenshtein_distance([X|S], [X|T], Cache) ->
- levenshtein_distance(S, T, Cache);
-levenshtein_distance([_SH|ST] = S, [_TH|TT] = T, Cache) ->
- case maps:find({S, T}, Cache) of
- {ok, Distance} ->
- {Distance, Cache};
- error ->
- {L1, C1} = levenshtein_distance(S, TT, Cache),
- {L2, C2} = levenshtein_distance(ST, T, C1),
- {L3, C3} = levenshtein_distance(ST, TT, C2),
- L = 1 + lists:min([L1, L2, L3]),
- {L, maps:put({S, T}, L, C3)}
- end.
+ {length(S), maps:put({S, T}, length(S), Cache)};
+levenshtein_distance([X | S], [X | T], Cache) ->
+ levenshtein_distance(S, T, Cache);
+levenshtein_distance([_SH | ST] = S, [_TH | TT] = T, Cache) ->
+ case maps:find({S, T}, Cache) of
+ {ok, Distance} ->
+ {Distance, Cache};
+ error ->
+ {L1, C1} = levenshtein_distance(S, TT, Cache),
+ {L2, C2} = levenshtein_distance(ST, T, C1),
+ {L3, C3} = levenshtein_distance(ST, TT, C2),
+ L = 1 + lists:min([L1, L2, L3]),
+ {L, maps:put({S, T}, L, C3)}
+ end.
%%% Jaro distance
@@ -508,137 +521,197 @@ levenshtein_distance([_SH|ST] = S, [_TH|TT] = T, Cache) ->
%%
%% @end
-spec jaro_distance(S, S) -> float() when S :: string() | binary().
-jaro_distance(Str, Str) -> 1.0;
-jaro_distance(_, "") -> 0.0;
-jaro_distance("", _) -> 0.0;
-jaro_distance(Str1, Str2) when is_binary(Str1),
- is_binary(Str2) ->
- jaro_distance(binary_to_list(Str1),
- binary_to_list(Str2));
-jaro_distance(Str1, Str2) when is_list(Str1),
- is_list(Str2) ->
- Len1 = length(Str1),
- Len2 = length(Str2),
- case jaro_match(Str1, Len1, Str2, Len2) of
- {0, _Trans} ->
- 0.0;
- {Comm, Trans} ->
- (Comm / Len1 + Comm / Len2 + (Comm - Trans) / Comm) / 3
- end.
+jaro_distance(Str, Str) ->
+ 1.0;
+jaro_distance(_, "") ->
+ 0.0;
+jaro_distance("", _) ->
+ 0.0;
+jaro_distance(Str1, Str2) when
+ is_binary(Str1),
+ is_binary(Str2)
+->
+ jaro_distance(
+ binary_to_list(Str1),
+ binary_to_list(Str2)
+ );
+jaro_distance(Str1, Str2) when
+ is_list(Str1),
+ is_list(Str2)
+->
+ Len1 = length(Str1),
+ Len2 = length(Str2),
+ case jaro_match(Str1, Len1, Str2, Len2) of
+ {0, _Trans} ->
+ 0.0;
+ {Comm, Trans} ->
+ (Comm / Len1 + Comm / Len2 + (Comm - Trans) / Comm) / 3
+ end.
-type jaro_state() :: {integer(), integer(), integer()}.
-type jaro_range() :: {integer(), integer()}.
-spec jaro_match(string(), integer(), string(), integer()) ->
- {integer(), integer()}.
+ {integer(), integer()}.
jaro_match(Chars1, Len1, Chars2, Len2) when Len1 < Len2 ->
- jaro_match(Chars1, Chars2, (Len2 div 2) - 1);
+ jaro_match(Chars1, Chars2, (Len2 div 2) - 1);
jaro_match(Chars1, Len1, Chars2, _Len2) ->
- jaro_match(Chars2, Chars1, (Len1 div 2) - 1).
+ jaro_match(Chars2, Chars1, (Len1 div 2) - 1).
-spec jaro_match(string(), string(), integer()) -> {integer(), integer()}.
jaro_match(Chars1, Chars2, Lim) ->
- jaro_match(Chars1, Chars2, {0, Lim}, {0, 0, -1}, 0).
+ jaro_match(Chars1, Chars2, {0, Lim}, {0, 0, -1}, 0).
-spec jaro_match(string(), string(), jaro_range(), jaro_state(), integer()) ->
- {integer(), integer()}.
-jaro_match([Char|Rest], Chars0, Range, State0, Idx) ->
- {Chars, State} = jaro_submatch(Char, Chars0, Range, State0, Idx),
- case Range of
- {Lim, Lim} ->
- jaro_match(Rest, tl(Chars), Range, State, Idx + 1);
- {Pre, Lim} ->
- jaro_match(Rest, Chars, {Pre + 1, Lim}, State, Idx + 1)
- end;
+ {integer(), integer()}.
+jaro_match([Char | Rest], Chars0, Range, State0, Idx) ->
+ {Chars, State} = jaro_submatch(Char, Chars0, Range, State0, Idx),
+ case Range of
+ {Lim, Lim} ->
+ jaro_match(Rest, tl(Chars), Range, State, Idx + 1);
+ {Pre, Lim} ->
+ jaro_match(Rest, Chars, {Pre + 1, Lim}, State, Idx + 1)
+ end;
jaro_match([], _, _, {Comm, Trans, _}, _) ->
- {Comm, Trans}.
+ {Comm, Trans}.
-spec jaro_submatch(char(), string(), jaro_range(), jaro_state(), integer()) ->
- {string(), jaro_state()}.
+ {string(), jaro_state()}.
jaro_submatch(Char, Chars0, {Pre, _} = Range, State, Idx) ->
- case jaro_detect(Char, Chars0, Range) of
- undefined ->
- {Chars0, State};
- {SubIdx, Chars} ->
- {Chars, jaro_proceed(State, Idx - Pre + SubIdx)}
- end.
+ case jaro_detect(Char, Chars0, Range) of
+ undefined ->
+ {Chars0, State};
+ {SubIdx, Chars} ->
+ {Chars, jaro_proceed(State, Idx - Pre + SubIdx)}
+ end.
-spec jaro_detect(char(), string(), jaro_range()) ->
- {integer(), string()} | undefined.
+ {integer(), string()} | undefined.
jaro_detect(Char, Chars, {Pre, Lim}) ->
- jaro_detect(Char, Chars, Pre + 1 + Lim, 0, []).
+ jaro_detect(Char, Chars, Pre + 1 + Lim, 0, []).
-spec jaro_detect(char(), string(), integer(), integer(), list()) ->
- {integer(), string()} | undefined.
+ {integer(), string()} | undefined.
jaro_detect(_Char, _Chars, 0, _Idx, _Acc) ->
- undefined;
+ undefined;
jaro_detect(_Char, [], _Lim, _Idx, _Acc) ->
- undefined;
+ undefined;
jaro_detect(Char, [Char | Rest], _Lim, Idx, Acc) ->
- {Idx, lists:reverse(Acc) ++ [undefined | Rest]};
+ {Idx, lists:reverse(Acc) ++ [undefined | Rest]};
jaro_detect(Char, [Other | Rest], Lim, Idx, Acc) ->
- jaro_detect(Char, Rest, Lim - 1, Idx + 1, [Other | Acc]).
+ jaro_detect(Char, Rest, Lim - 1, Idx + 1, [Other | Acc]).
-spec jaro_proceed(jaro_state(), integer()) -> jaro_state().
jaro_proceed({Comm, Trans, Former}, Current) when Current < Former ->
- {Comm + 1, Trans + 1, Current};
+ {Comm + 1, Trans + 1, Current};
jaro_proceed({Comm, Trans, _Former}, Current) ->
- {Comm + 1, Trans, Current}.
+ {Comm + 1, Trans, Current}.
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
jaro_distance_test_() ->
- [ ?_assertEqual( jaro_distance("same", "same")
- , 1.0)
- , ?_assertEqual( jaro_distance("any", "")
- , 0.0)
- , ?_assertEqual( jaro_distance("", "any")
- , 0.0)
- , ?_assertEqual( jaro_distance("martha", "marhta")
- , 0.9444444444444445)
- , ?_assertEqual( jaro_distance("martha", "marhha")
- , 0.888888888888889)
- , ?_assertEqual( jaro_distance("marhha", "martha")
- , 0.888888888888889)
- , ?_assertEqual( jaro_distance("dwayne", "duane")
- , 0.8222222222222223)
- , ?_assertEqual( jaro_distance("dixon", "dicksonx")
- , 0.7666666666666666)
- , ?_assertEqual( jaro_distance("xdicksonx", "dixon")
- , 0.7851851851851852)
- , ?_assertEqual( jaro_distance("shackleford", "shackelford")
- , 0.9696969696969697)
- , ?_assertEqual( jaro_distance("dunningham", "cunnigham")
- , 0.8962962962962964)
- , ?_assertEqual( jaro_distance("nichleson", "nichulson")
- , 0.9259259259259259)
- , ?_assertEqual( jaro_distance("jones", "johnson")
- , 0.7904761904761904)
- , ?_assertEqual( jaro_distance("massey", "massie")
- , 0.888888888888889)
- , ?_assertEqual( jaro_distance("abroms", "abrams")
- , 0.888888888888889)
- , ?_assertEqual( jaro_distance("hardin", "martinez")
- , 0.7222222222222222)
- , ?_assertEqual( jaro_distance("itman", "smith")
- , 0.4666666666666666)
- , ?_assertEqual( jaro_distance("jeraldine", "geraldine")
- , 0.9259259259259259)
- , ?_assertEqual( jaro_distance("michelle", "michael")
- , 0.8690476190476191)
- , ?_assertEqual( jaro_distance("julies", "julius")
- , 0.888888888888889)
- , ?_assertEqual( jaro_distance("tanya", "tonya")
- , 0.8666666666666667)
- , ?_assertEqual( jaro_distance("sean", "susan")
- , 0.7833333333333333)
- , ?_assertEqual( jaro_distance("jon", "john")
- , 0.9166666666666666)
- , ?_assertEqual( jaro_distance("jon", "jan")
- , 0.7777777777777777)
- , ?_assertEqual( jaro_distance("семена", "стремя")
- , 0.6666666666666666)
+ [
+ ?_assertEqual(
+ jaro_distance("same", "same"),
+ 1.0
+ ),
+ ?_assertEqual(
+ jaro_distance("any", ""),
+ 0.0
+ ),
+ ?_assertEqual(
+ jaro_distance("", "any"),
+ 0.0
+ ),
+ ?_assertEqual(
+ jaro_distance("martha", "marhta"),
+ 0.9444444444444445
+ ),
+ ?_assertEqual(
+ jaro_distance("martha", "marhha"),
+ 0.888888888888889
+ ),
+ ?_assertEqual(
+ jaro_distance("marhha", "martha"),
+ 0.888888888888889
+ ),
+ ?_assertEqual(
+ jaro_distance("dwayne", "duane"),
+ 0.8222222222222223
+ ),
+ ?_assertEqual(
+ jaro_distance("dixon", "dicksonx"),
+ 0.7666666666666666
+ ),
+ ?_assertEqual(
+ jaro_distance("xdicksonx", "dixon"),
+ 0.7851851851851852
+ ),
+ ?_assertEqual(
+ jaro_distance("shackleford", "shackelford"),
+ 0.9696969696969697
+ ),
+ ?_assertEqual(
+ jaro_distance("dunningham", "cunnigham"),
+ 0.8962962962962964
+ ),
+ ?_assertEqual(
+ jaro_distance("nichleson", "nichulson"),
+ 0.9259259259259259
+ ),
+ ?_assertEqual(
+ jaro_distance("jones", "johnson"),
+ 0.7904761904761904
+ ),
+ ?_assertEqual(
+ jaro_distance("massey", "massie"),
+ 0.888888888888889
+ ),
+ ?_assertEqual(
+ jaro_distance("abroms", "abrams"),
+ 0.888888888888889
+ ),
+ ?_assertEqual(
+ jaro_distance("hardin", "martinez"),
+ 0.7222222222222222
+ ),
+ ?_assertEqual(
+ jaro_distance("itman", "smith"),
+ 0.4666666666666666
+ ),
+ ?_assertEqual(
+ jaro_distance("jeraldine", "geraldine"),
+ 0.9259259259259259
+ ),
+ ?_assertEqual(
+ jaro_distance("michelle", "michael"),
+ 0.8690476190476191
+ ),
+ ?_assertEqual(
+ jaro_distance("julies", "julius"),
+ 0.888888888888889
+ ),
+ ?_assertEqual(
+ jaro_distance("tanya", "tonya"),
+ 0.8666666666666667
+ ),
+ ?_assertEqual(
+ jaro_distance("sean", "susan"),
+ 0.7833333333333333
+ ),
+ ?_assertEqual(
+ jaro_distance("jon", "john"),
+ 0.9166666666666666
+ ),
+ ?_assertEqual(
+ jaro_distance("jon", "jan"),
+ 0.7777777777777777
+ ),
+ ?_assertEqual(
+ jaro_distance("семена", "стремя"),
+ 0.6666666666666666
+ )
].
-endif.
diff --git a/apps/els_core/test/els_fake_stdio.erl b/apps/els_core/test/els_fake_stdio.erl
index d188bc2d3..93441ef7d 100644
--- a/apps/els_core/test/els_fake_stdio.erl
+++ b/apps/els_core/test/els_fake_stdio.erl
@@ -1,101 +1,103 @@
-module(els_fake_stdio).
--export([ start/0
- , connect/2
- , loop/1
- ]).
+-export([
+ start/0,
+ connect/2,
+ loop/1
+]).
--record(state, { buffer = <<>> :: binary()
- , connected :: pid()
- , pending = [] :: [any()]
- }).
+-record(state, {
+ buffer = <<>> :: binary(),
+ connected :: pid(),
+ pending = [] :: [any()]
+}).
-type state() :: #state{}.
-spec start() -> pid().
start() ->
- proc_lib:spawn_link(?MODULE, loop, [#state{}]).
+ proc_lib:spawn_link(?MODULE, loop, [#state{}]).
-spec connect(pid(), pid()) -> ok.
connect(Pid, IoDevice) ->
- Pid ! {connect, IoDevice},
- ok.
+ Pid ! {connect, IoDevice},
+ ok.
-spec loop(state()) -> ok.
loop(State0) ->
- State1 = process_pending(State0),
- receive
- {connect, IoDevice} ->
- loop(State1#state{connected = IoDevice});
- {custom_request, From, Ref, Request} ->
- State = handle_locally(From, Ref, Request, State1),
- loop(State);
- {io_request, From, Ref, Request} ->
- State = dispatch(From, Ref, Request, State1),
- loop(State)
- end.
+ State1 = process_pending(State0),
+ receive
+ {connect, IoDevice} ->
+ loop(State1#state{connected = IoDevice});
+ {custom_request, From, Ref, Request} ->
+ State = handle_locally(From, Ref, Request, State1),
+ loop(State);
+ {io_request, From, Ref, Request} ->
+ State = dispatch(From, Ref, Request, State1),
+ loop(State)
+ end.
-spec dispatch(pid(), any(), any(), state()) -> ok.
dispatch(From, Ref, Request, State0) ->
- case is_custom(Request) of
- true -> redirect(From, Ref, Request, State0);
- false -> handle_locally(From, Ref, Request, State0)
- end.
+ case is_custom(Request) of
+ true -> redirect(From, Ref, Request, State0);
+ false -> handle_locally(From, Ref, Request, State0)
+ end.
-spec is_custom(any()) -> boolean().
is_custom({put_chars, _Encoding, _Chars}) ->
- true;
+ true;
is_custom({put_chars, _Encoding, _M, _F, _Args}) ->
- true;
+ true;
is_custom(_) ->
- false.
+ false.
-spec redirect(pid(), any(), any(), state()) -> state().
redirect(From, Ref, Request, State) ->
- Connected = State#state.connected,
- Connected ! {custom_request, From, Ref, Request},
- State.
+ Connected = State#state.connected,
+ Connected ! {custom_request, From, Ref, Request},
+ State.
-spec handle_locally(pid(), any(), any(), state()) -> state().
handle_locally(From, Ref, Request, State0) ->
- case handle_request(Request, State0) of
- {noreply, State} ->
- pending(From, Ref, Request, State);
- {reply, Reply, State} ->
- reply(From, Ref, Reply),
- State
- end.
+ case handle_request(Request, State0) of
+ {noreply, State} ->
+ pending(From, Ref, Request, State);
+ {reply, Reply, State} ->
+ reply(From, Ref, Reply),
+ State
+ end.
-spec handle_request(any(), state()) ->
- {reply, any(), state()} | {noreply, state()}.
+ {reply, any(), state()} | {noreply, state()}.
handle_request({setopts, _Opts}, State) ->
- {reply, ok, State};
+ {reply, ok, State};
handle_request({put_chars, Encoding, M, F, Args}, State0) ->
- Chars = apply(M, F, Args),
- handle_request({put_chars, Encoding, Chars}, State0);
+ Chars = apply(M, F, Args),
+ handle_request({put_chars, Encoding, Chars}, State0);
handle_request({put_chars, Encoding, Chars}, State0) ->
- EncodedChars = unicode:characters_to_list(Chars, Encoding),
- CharsBin = els_utils:to_binary(EncodedChars),
- Buffer = State0#state.buffer,
- State = State0#state{buffer = <>},
- {reply, ok, State};
+ EncodedChars = unicode:characters_to_list(Chars, Encoding),
+ CharsBin = els_utils:to_binary(EncodedChars),
+ Buffer = State0#state.buffer,
+ State = State0#state{buffer = <>},
+ {reply, ok, State};
handle_request({get_line, _Encoding, _Prompt}, State0) ->
- case binary:split(State0#state.buffer, <<"\n">>, [trim]) of
- [Line0, Rest] ->
- Line = string:trim(Line0),
- {reply, <>, State0#state{buffer = Rest}};
- _ ->
- {noreply, State0}
- end;
+ case binary:split(State0#state.buffer, <<"\n">>, [trim]) of
+ [Line0, Rest] ->
+ Line = string:trim(Line0),
+ {reply, <>, State0#state{buffer = Rest}};
+ _ ->
+ {noreply, State0}
+ end;
handle_request({get_chars, Encoding, _Prompt, Count}, State) ->
- handle_request({get_chars, Encoding, Count}, State);
+ handle_request({get_chars, Encoding, Count}, State);
handle_request({get_chars, _Encoding, Count}, State0) ->
- case State0#state.buffer of
- <> ->
- {reply, Data, State0#state{buffer = Rest}};
- _ ->
- {noreply, State0}
- end.
+ case State0#state.buffer of
+ <> ->
+ {reply, Data, State0#state{buffer = Rest}};
+ _ ->
+ {noreply, State0}
+ end.
-spec reply(pid(), any(), any()) -> any().
reply(From, ReplyAs, Reply) ->
@@ -103,11 +105,11 @@ reply(From, ReplyAs, Reply) ->
-spec pending(pid(), any(), any(), state()) -> state().
pending(From, Ref, Request, #state{pending = Pending} = State) ->
- State#state{pending = [{From, Ref, Request} | Pending]}.
+ State#state{pending = [{From, Ref, Request} | Pending]}.
-spec process_pending(state()) -> state().
process_pending(#state{pending = Pending} = State) ->
- FoldFun = fun({From, Ref, Request}, Acc) ->
- handle_locally(From, Ref, Request, Acc)
- end,
- lists:foldl(FoldFun, State#state{pending = []}, Pending).
+ FoldFun = fun({From, Ref, Request}, Acc) ->
+ handle_locally(From, Ref, Request, Acc)
+ end,
+ lists:foldl(FoldFun, State#state{pending = []}, Pending).
diff --git a/apps/els_dap/src/els_dap.erl b/apps/els_dap/src/els_dap.erl
index 86ec65fc0..0f491b154 100644
--- a/apps/els_dap/src/els_dap.erl
+++ b/apps/els_dap/src/els_dap.erl
@@ -3,11 +3,12 @@
%%=============================================================================
-module(els_dap).
--export([ main/1 ]).
+-export([main/1]).
--export([ parse_args/1
- , log_root/0
- ]).
+-export([
+ parse_args/1,
+ log_root/0
+]).
%%==============================================================================
%% Includes
@@ -19,24 +20,26 @@
-spec main([any()]) -> ok.
main(Args) ->
- application:load(getopt),
- application:load(els_core),
- application:load(els_dap),
- ok = parse_args(Args),
- application:set_env(els_core, server, els_dap_server),
- configure_logging(),
- ?LOG_DEBUG("Ensure EPMD is running", []),
- 0 = els_utils:cmd("epmd", ["-daemon"]),
- {ok, _} = application:ensure_all_started(?APP, permanent),
- patch_logging(),
- ?LOG_INFO("Started Erlang LS - DAP server", []),
- receive _ -> ok end.
+ application:load(getopt),
+ application:load(els_core),
+ application:load(els_dap),
+ ok = parse_args(Args),
+ application:set_env(els_core, server, els_dap_server),
+ configure_logging(),
+ ?LOG_DEBUG("Ensure EPMD is running", []),
+ 0 = els_utils:cmd("epmd", ["-daemon"]),
+ {ok, _} = application:ensure_all_started(?APP, permanent),
+ patch_logging(),
+ ?LOG_INFO("Started Erlang LS - DAP server", []),
+ receive
+ _ -> ok
+ end.
-spec print_version() -> ok.
print_version() ->
- {ok, Vsn} = application:get_key(?APP, vsn),
- io:format("Version: ~s~n", [Vsn]),
- ok.
+ {ok, Vsn} = application:get_key(?APP, vsn),
+ io:format("Version: ~s~n", [Vsn]),
+ ok.
%%==============================================================================
%% Argument parsing
@@ -44,51 +47,41 @@ print_version() ->
-spec parse_args([string()]) -> ok.
parse_args(Args) ->
- case getopt:parse(opt_spec_list(), Args) of
- {ok, {[version | _], _BadArgs}} ->
- print_version(),
- halt(0);
- {ok, {ParsedArgs, _BadArgs}} ->
- set_args(ParsedArgs);
- {error, {invalid_option, _}} ->
- getopt:usage(opt_spec_list(), "Erlang LS - DAP"),
- halt(1)
- end.
+ case getopt:parse(opt_spec_list(), Args) of
+ {ok, {[version | _], _BadArgs}} ->
+ print_version(),
+ halt(0);
+ {ok, {ParsedArgs, _BadArgs}} ->
+ set_args(ParsedArgs);
+ {error, {invalid_option, _}} ->
+ getopt:usage(opt_spec_list(), "Erlang LS - DAP"),
+ halt(1)
+ end.
-spec opt_spec_list() -> [getopt:option_spec()].
opt_spec_list() ->
- [ { version
- , $v
- , "version"
- , undefined
- , "Print the current version of Erlang LS - DAP"
- }
- , { log_dir
- , $d
- , "log-dir"
- , {string, filename:basedir(user_log, "els_dap")}
- , "Directory where logs will be written."
- }
- , { log_level
- , $l
- , "log-level"
- , {string, ?DEFAULT_LOGGING_LEVEL}
- , "The log level that should be used."
- }
- ].
+ [
+ {version, $v, "version", undefined, "Print the current version of Erlang LS - DAP"},
+ {log_dir, $d, "log-dir", {string, filename:basedir(user_log, "els_dap")},
+ "Directory where logs will be written."},
+ {log_level, $l, "log-level", {string, ?DEFAULT_LOGGING_LEVEL},
+ "The log level that should be used."}
+ ].
-spec set_args([] | [getopt:compound_option()]) -> ok.
-set_args([]) -> ok;
-set_args([version | Rest]) -> set_args(Rest);
+set_args([]) ->
+ ok;
+set_args([version | Rest]) ->
+ set_args(Rest);
set_args([{Arg, Val} | Rest]) ->
- set(Arg, Val),
- set_args(Rest).
+ set(Arg, Val),
+ set_args(Rest).
-spec set(atom(), getopt:arg_value()) -> ok.
set(log_dir, Dir) ->
- application:set_env(els_core, log_dir, Dir);
+ application:set_env(els_core, log_dir, Dir);
set(log_level, Level) ->
- application:set_env(els_core, log_level, list_to_atom(Level)).
+ application:set_env(els_core, log_level, list_to_atom(Level)).
%%==============================================================================
%% Logger configuration
@@ -96,35 +89,46 @@ set(log_level, Level) ->
-spec configure_logging() -> ok.
configure_logging() ->
- LogFile = filename:join([log_root(), "dap_server.log"]),
- {ok, LoggingLevel} = application:get_env(els_core, log_level),
- ok = filelib:ensure_dir(LogFile),
- Handler = #{ config => #{ file => LogFile }
- , level => LoggingLevel
- , formatter => { logger_formatter
- , #{ template => [ "[", time, "] "
- , file, ":", line, " "
- , pid, " "
- , "[", level, "] "
- , msg, "\n"
- ]
- }
- }
- },
- [logger:remove_handler(H) || H <- logger:get_handler_ids()],
- logger:add_handler(els_core_handler, logger_std_h, Handler),
- logger:set_primary_config(level, LoggingLevel),
- ok.
+ LogFile = filename:join([log_root(), "dap_server.log"]),
+ {ok, LoggingLevel} = application:get_env(els_core, log_level),
+ ok = filelib:ensure_dir(LogFile),
+ Handler = #{
+ config => #{file => LogFile},
+ level => LoggingLevel,
+ formatter =>
+ {logger_formatter, #{
+ template => [
+ "[",
+ time,
+ "] ",
+ file,
+ ":",
+ line,
+ " ",
+ pid,
+ " ",
+ "[",
+ level,
+ "] ",
+ msg,
+ "\n"
+ ]
+ }}
+ },
+ [logger:remove_handler(H) || H <- logger:get_handler_ids()],
+ logger:add_handler(els_core_handler, logger_std_h, Handler),
+ logger:set_primary_config(level, LoggingLevel),
+ ok.
-spec patch_logging() -> ok.
patch_logging() ->
- %% The ssl_handler is added by ranch -> ssl
- logger:remove_handler(ssl_handler),
- ok.
+ %% The ssl_handler is added by ranch -> ssl
+ logger:remove_handler(ssl_handler),
+ ok.
-spec log_root() -> string().
log_root() ->
- {ok, LogDir} = application:get_env(els_core, log_dir),
- {ok, CurrentDir} = file:get_cwd(),
- Dirname = filename:basename(CurrentDir),
- filename:join([LogDir, Dirname]).
+ {ok, LogDir} = application:get_env(els_core, log_dir),
+ {ok, CurrentDir} = file:get_cwd(),
+ Dirname = filename:basename(CurrentDir),
+ filename:join([LogDir, Dirname]).
diff --git a/apps/els_dap/src/els_dap_agent.erl b/apps/els_dap/src/els_dap_agent.erl
index a3613ff91..41fe0cebc 100644
--- a/apps/els_dap/src/els_dap_agent.erl
+++ b/apps/els_dap/src/els_dap_agent.erl
@@ -6,17 +6,17 @@
%%=============================================================================
-module(els_dap_agent).
--export([ int_cb/2, meta_eval/2 ]).
+-export([int_cb/2, meta_eval/2]).
-spec int_cb(pid(), pid()) -> ok.
int_cb(Thread, ProviderPid) ->
- ProviderPid ! {int_cb, Thread},
- ok.
+ ProviderPid ! {int_cb, Thread},
+ ok.
-spec meta_eval(pid(), string()) -> any().
meta_eval(Meta, Command) ->
- _ = int:meta(Meta, eval, {ignored_module, Command}),
- receive
- {Meta, {eval_rsp, Return}} ->
- Return
- end.
+ _ = int:meta(Meta, eval, {ignored_module, Command}),
+ receive
+ {Meta, {eval_rsp, Return}} ->
+ Return
+ end.
diff --git a/apps/els_dap/src/els_dap_app.erl b/apps/els_dap/src/els_dap_app.erl
index fe37dc1bc..4386962b1 100644
--- a/apps/els_dap/src/els_dap_app.erl
+++ b/apps/els_dap/src/els_dap_app.erl
@@ -12,17 +12,18 @@
%% Exports
%%==============================================================================
%% Application Callbacks
--export([ start/2
- , stop/1
- ]).
+-export([
+ start/2,
+ stop/1
+]).
%%==============================================================================
%% Application Callbacks
%%==============================================================================
-spec start(normal, any()) -> {ok, pid()}.
start(_StartType, _StartArgs) ->
- els_dap_sup:start_link().
+ els_dap_sup:start_link().
-spec stop(any()) -> ok.
stop(_State) ->
- ok.
+ ok.
diff --git a/apps/els_dap/src/els_dap_breakpoints.erl b/apps/els_dap/src/els_dap_breakpoints.erl
index a5dc3959b..fb2b7c741 100644
--- a/apps/els_dap/src/els_dap_breakpoints.erl
+++ b/apps/els_dap/src/els_dap_breakpoints.erl
@@ -1,10 +1,12 @@
-module(els_dap_breakpoints).
--export([ build_source_breakpoints/1
- , get_function_breaks/2
- , get_line_breaks/2
- , do_line_breakpoints/4
- , do_function_breaks/4
- , type/3]).
+-export([
+ build_source_breakpoints/1,
+ get_function_breaks/2,
+ get_line_breaks/2,
+ do_line_breakpoints/4,
+ do_function_breaks/4,
+ type/3
+]).
%%==============================================================================
%% Includes
@@ -16,110 +18,125 @@
%%==============================================================================
-type breakpoints() :: #{
- module() => #{
- line => #{
- line() => line_breaks()
- },
- function => [function_break()]
- }
+ module() => #{
+ line => #{
+ line() => line_breaks()
+ },
+ function => [function_break()]
+ }
}.
-type line() :: non_neg_integer().
--type line_breaks() :: #{ condition => expression()
- , hitcond => expression()
- , logexpr => expression()
- }.
+-type line_breaks() :: #{
+ condition => expression(),
+ hitcond => expression(),
+ logexpr => expression()
+}.
-type expression() :: string().
-type function_break() :: {atom(), non_neg_integer()}.
--export_type([ breakpoints/0
- , line_breaks/0]).
+-export_type([
+ breakpoints/0,
+ line_breaks/0
+]).
-spec type(breakpoints(), module(), line()) -> line_breaks().
type(Breakpoints, Module, Line) ->
- ?LOG_DEBUG("checking breakpoint type for ~s:~b", [Module, Line]),
- case Breakpoints of
- #{Module := #{line := #{Line := Break}}} ->
- Break;
- _ ->
- %% function breaks get handled like regular ones
- #{}
- end.
+ ?LOG_DEBUG("checking breakpoint type for ~s:~b", [Module, Line]),
+ case Breakpoints of
+ #{Module := #{line := #{Line := Break}}} ->
+ Break;
+ _ ->
+ %% function breaks get handled like regular ones
+ #{}
+ end.
%% @doc build regular, conditional, hit and log breakpoints from setBreakpoint
%% request
-spec build_source_breakpoints(Params :: map()) ->
- {module(), #{line() => line_breaks()}}.
+ {module(), #{line() => line_breaks()}}.
build_source_breakpoints(Params) ->
- #{<<"source">> := #{<<"path">> := Path}} = Params,
- Module = els_uri:module(els_uri:uri(Path)),
- SourceBreakpoints = maps:get(<<"breakpoints">>, Params, []),
- _SourceModified = maps:get(<<"sourceModified">>, Params, false),
- {Module, maps:from_list(lists:map(fun build_source_breakpoint/1,
- SourceBreakpoints))}.
+ #{<<"source">> := #{<<"path">> := Path}} = Params,
+ Module = els_uri:module(els_uri:uri(Path)),
+ SourceBreakpoints = maps:get(<<"breakpoints">>, Params, []),
+ _SourceModified = maps:get(<<"sourceModified">>, Params, false),
+ {Module,
+ maps:from_list(
+ lists:map(
+ fun build_source_breakpoint/1,
+ SourceBreakpoints
+ )
+ )}.
-spec build_source_breakpoint(map()) ->
- { line()
- , #{ condition => expression()
- , hitcond => expression()
- , logexpr => expression()
- }
- }.
+ {line(), #{
+ condition => expression(),
+ hitcond => expression(),
+ logexpr => expression()
+ }}.
build_source_breakpoint(#{<<"line">> := Line} = Breakpoint) ->
- Cond = case Breakpoint of
- #{<<"condition">> := CondExpr} when CondExpr =/= <<>> ->
- #{condition => CondExpr};
- _ -> #{}
- end,
- Hit = case Breakpoint of
- #{<<"hitCondition">> := HitExpr} when HitExpr =/= <<>> ->
- #{hitcond => HitExpr};
- _ -> #{}
- end,
- Log = case Breakpoint of
- #{<<"logMessage">> := LogExpr} when LogExpr =/= <<>> ->
- #{logexpr => LogExpr};
- _ -> #{}
- end,
- {Line, lists:foldl(fun maps:merge/2, #{}, [Cond, Hit, Log])}.
+ Cond =
+ case Breakpoint of
+ #{<<"condition">> := CondExpr} when CondExpr =/= <<>> ->
+ #{condition => CondExpr};
+ _ ->
+ #{}
+ end,
+ Hit =
+ case Breakpoint of
+ #{<<"hitCondition">> := HitExpr} when HitExpr =/= <<>> ->
+ #{hitcond => HitExpr};
+ _ ->
+ #{}
+ end,
+ Log =
+ case Breakpoint of
+ #{<<"logMessage">> := LogExpr} when LogExpr =/= <<>> ->
+ #{logexpr => LogExpr};
+ _ ->
+ #{}
+ end,
+ {Line, lists:foldl(fun maps:merge/2, #{}, [Cond, Hit, Log])}.
-spec get_function_breaks(module(), breakpoints()) -> [function_break()].
get_function_breaks(Module, Breaks) ->
- case Breaks of
- #{Module := #{function := Functions}} -> Functions;
- _ -> []
- end.
+ case Breaks of
+ #{Module := #{function := Functions}} -> Functions;
+ _ -> []
+ end.
-spec get_line_breaks(module(), breakpoints()) -> #{line() => line_breaks()}.
get_line_breaks(Module, Breaks) ->
- case Breaks of
- #{Module := #{line := Lines}} -> Lines;
- _ -> []
- end.
+ case Breaks of
+ #{Module := #{line := Lines}} -> Lines;
+ _ -> []
+ end.
--spec do_line_breakpoints(node(), module(),
- #{line() => line_breaks()}, breakpoints()) ->
- breakpoints().
+-spec do_line_breakpoints(
+ node(),
+ module(),
+ #{line() => line_breaks()},
+ breakpoints()
+) ->
+ breakpoints().
do_line_breakpoints(Node, Module, LineBreakPoints, Breaks) ->
- maps:map(
- fun
- (Line, _) -> els_dap_rpc:break(Node, Module, Line)
- end,
- LineBreakPoints
- ),
- case Breaks of
- #{Module := ModBreaks} ->
- Breaks#{Module => ModBreaks#{line => LineBreakPoints}};
- _ ->
- Breaks#{Module => #{line => LineBreakPoints, function => []}}
- end.
+ maps:map(
+ fun(Line, _) -> els_dap_rpc:break(Node, Module, Line) end,
+ LineBreakPoints
+ ),
+ case Breaks of
+ #{Module := ModBreaks} ->
+ Breaks#{Module => ModBreaks#{line => LineBreakPoints}};
+ _ ->
+ Breaks#{Module => #{line => LineBreakPoints, function => []}}
+ end.
-spec do_function_breaks(node(), module(), [function_break()], breakpoints()) ->
- breakpoints().
+ breakpoints().
do_function_breaks(Node, Module, FBreaks, Breaks) ->
- [els_dap_rpc:break_in(Node, Module, Func, Arity) || {Func, Arity} <- FBreaks],
- case Breaks of
- #{Module := ModBreaks} ->
- Breaks#{Module => ModBreaks#{function => FBreaks}};
- _ ->
- Breaks#{Module => #{line => #{}, function => FBreaks}}
- end.
+ [els_dap_rpc:break_in(Node, Module, Func, Arity) || {Func, Arity} <- FBreaks],
+ case Breaks of
+ #{Module := ModBreaks} ->
+ Breaks#{Module => ModBreaks#{function => FBreaks}};
+ _ ->
+ Breaks#{Module => #{line => #{}, function => FBreaks}}
+ end.
diff --git a/apps/els_dap/src/els_dap_general_provider.erl b/apps/els_dap/src/els_dap_general_provider.erl
index f0c2bdc92..c7955ed88 100644
--- a/apps/els_dap/src/els_dap_general_provider.erl
+++ b/apps/els_dap/src/els_dap_general_provider.erl
@@ -9,13 +9,13 @@
%%==============================================================================
-module(els_dap_general_provider).
--export([ handle_request/2
- , handle_info/2
- , init/0
- ]).
+-export([
+ handle_request/2,
+ handle_info/2,
+ init/0
+]).
--export([ capabilities/0
- ]).
+-export([capabilities/0]).
%%==============================================================================
%% Includes
@@ -28,530 +28,651 @@
%% Protocol
-type capabilities() :: #{}.
--type request() :: {Command :: binary(), Params :: map()}.
--type result() :: #{}.
+-type request() :: {Command :: binary(), Params :: map()}.
+-type result() :: #{}.
%% Internal
--type frame_id() :: pos_integer().
--type frame() :: #{ module := module()
- , function := atom()
- , arguments := [any()]
- , source := binary()
- , line := line()
- , bindings := any()
- }.
--type thread() :: #{ pid := pid()
- , frames := #{frame_id() => frame()}
- }.
--type thread_id() :: integer().
--type mode() :: undefined | running | stepping.
--type state() :: #{ threads => #{thread_id() => thread()}
- , project_node => atom()
- , launch_params => #{}
- , scope_bindings =>
- #{pos_integer() => {binding_type(), bindings()}}
- , breakpoints := els_dap_breakpoints:breakpoints()
- , hits => #{line() => non_neg_integer()}
- , timeout := timeout()
- , mode := mode()
- }.
--type bindings() :: [{varname(), term()}].
--type varname() :: atom() | string().
+-type frame_id() :: pos_integer().
+-type frame() :: #{
+ module := module(),
+ function := atom(),
+ arguments := [any()],
+ source := binary(),
+ line := line(),
+ bindings := any()
+}.
+-type thread() :: #{
+ pid := pid(),
+ frames := #{frame_id() => frame()}
+}.
+-type thread_id() :: integer().
+-type mode() :: undefined | running | stepping.
+-type state() :: #{
+ threads => #{thread_id() => thread()},
+ project_node => atom(),
+ launch_params => #{},
+ scope_bindings =>
+ #{pos_integer() => {binding_type(), bindings()}},
+ breakpoints := els_dap_breakpoints:breakpoints(),
+ hits => #{line() => non_neg_integer()},
+ timeout := timeout(),
+ mode := mode()
+}.
+-type bindings() :: [{varname(), term()}].
+-type varname() :: atom() | string().
%% extendable bindings type for customized pretty printing
-type binding_type() :: generic | map_assoc.
--type line() :: non_neg_integer().
+-type line() :: non_neg_integer().
-spec init() -> state().
init() ->
- #{ threads => #{}
- , launch_params => #{}
- , scope_bindings => #{}
- , breakpoints => #{}
- , hits => #{}
- , timeout => 30
- , mode => undefined}.
+ #{
+ threads => #{},
+ launch_params => #{},
+ scope_bindings => #{},
+ breakpoints => #{},
+ hits => #{},
+ timeout => 30,
+ mode => undefined
+ }.
-spec handle_request(request(), state()) ->
- {result(), state()} | {{error, binary()}, state()}.
+ {result(), state()} | {{error, binary()}, state()}.
handle_request({<<"initialize">>, _Params}, State) ->
- %% quick fix to satisfy els_config initialization
- {ok, RootPath} = file:get_cwd(),
- RootUri = els_uri:uri(els_utils:to_binary(RootPath)),
- InitOptions = #{},
- Capabilities = capabilities(),
- ok = els_config:initialize(RootUri, Capabilities, InitOptions),
- {Capabilities, State};
+ %% quick fix to satisfy els_config initialization
+ {ok, RootPath} = file:get_cwd(),
+ RootUri = els_uri:uri(els_utils:to_binary(RootPath)),
+ InitOptions = #{},
+ Capabilities = capabilities(),
+ ok = els_config:initialize(RootUri, Capabilities, InitOptions),
+ {Capabilities, State};
handle_request({<<"launch">>, #{<<"cwd">> := Cwd} = Params}, State) ->
- case start_distribution(Params) of
- {ok, #{ <<"projectnode">> := ProjectNode
- , <<"cookie">> := Cookie
- , <<"timeout">> := TimeOut
- , <<"use_long_names">> := UseLongNames}} ->
- case Params of
- #{ <<"runinterminal">> := Cmd
- } ->
- ParamsR
- = #{ <<"kind">> => <<"integrated">>
- , <<"title">> => ProjectNode
- , <<"cwd">> => Cwd
- , <<"args">> => Cmd
- },
- ?LOG_INFO("Sending runinterminal request: [~p]", [ParamsR]),
- els_dap_server:send_request(<<"runInTerminal">>, ParamsR),
- ok;
- _ ->
- NameTypeParam = case UseLongNames of
+ case start_distribution(Params) of
+ {ok, #{
+ <<"projectnode">> := ProjectNode,
+ <<"cookie">> := Cookie,
+ <<"timeout">> := TimeOut,
+ <<"use_long_names">> := UseLongNames
+ }} ->
+ case Params of
+ #{<<"runinterminal">> := Cmd} ->
+ ParamsR =
+ #{
+ <<"kind">> => <<"integrated">>,
+ <<"title">> => ProjectNode,
+ <<"cwd">> => Cwd,
+ <<"args">> => Cmd
+ },
+ ?LOG_INFO("Sending runinterminal request: [~p]", [ParamsR]),
+ els_dap_server:send_request(<<"runInTerminal">>, ParamsR),
+ ok;
+ _ ->
+ NameTypeParam =
+ case UseLongNames of
true ->
- "--name";
+ "--name";
false ->
- "--sname"
- end,
- ?LOG_INFO("launching 'rebar3 shell`", []),
- spawn(fun() ->
- els_utils:cmd(
- "rebar3",
- [ "shell"
- , NameTypeParam
- , atom_to_list(ProjectNode)
- , "--setcookie"
- , erlang:binary_to_list(Cookie)
- ]
- )
- end)
- end,
- els_dap_server:send_event(<<"initialized">>, #{}),
- {#{}, State#{ project_node => ProjectNode
- , launch_params => Params
- , timeout => TimeOut
- }};
- {error, Error} ->
- {{error, distribution_error(Error)}, State}
- end;
+ "--sname"
+ end,
+ ?LOG_INFO("launching 'rebar3 shell`", []),
+ spawn(fun() ->
+ els_utils:cmd(
+ "rebar3",
+ [
+ "shell",
+ NameTypeParam,
+ atom_to_list(ProjectNode),
+ "--setcookie",
+ erlang:binary_to_list(Cookie)
+ ]
+ )
+ end)
+ end,
+ els_dap_server:send_event(<<"initialized">>, #{}),
+ {#{}, State#{
+ project_node => ProjectNode,
+ launch_params => Params,
+ timeout => TimeOut
+ }};
+ {error, Error} ->
+ {{error, distribution_error(Error)}, State}
+ end;
handle_request({<<"attach">>, Params}, State) ->
- case start_distribution(Params) of
- {ok, #{ <<"projectnode">> := ProjectNode
- , <<"timeout">> := TimeOut}} ->
- els_dap_server:send_event(<<"initialized">>, #{}),
- {#{}, State#{ project_node => ProjectNode
- , launch_params => Params
- , timeout => TimeOut
- }};
- {error, Error} ->
- {{error, distribution_error(Error)}, State}
- end;
-handle_request( {<<"configurationDone">>, _Params}
- , #{ project_node := ProjectNode
- , launch_params := LaunchParams
- , timeout := Timeout} = State
- ) ->
- ensure_connected(ProjectNode, Timeout),
- %% TODO: Fetch stack_trace mode from Launch Config
- els_dap_rpc:stack_trace(ProjectNode, all),
- MFA = {els_dap_agent, int_cb, [self()]},
- els_dap_rpc:auto_attach(ProjectNode, [break], MFA),
-
- case LaunchParams of
- #{ <<"module">> := Module
- , <<"function">> := Function
- , <<"args">> := Args
- } ->
- M = binary_to_atom(Module, utf8),
- F = binary_to_atom(Function, utf8),
- A = els_dap_rpc:eval(ProjectNode, Args, []),
- ?LOG_INFO("Launching MFA: [~p]", [{M, F, A}]),
- rpc:cast(ProjectNode, M, F, A);
- _ -> ok
- end,
- {#{}, State#{mode => running}};
-handle_request( {<<"setBreakpoints">>, Params}
- , #{ project_node := ProjectNode
- , breakpoints := Breakpoints0
- , timeout := Timeout} = State
- ) ->
- ensure_connected(ProjectNode, Timeout),
- {Module, LineBreaks} = els_dap_breakpoints:build_source_breakpoints(Params),
-
-
- {module, Module} = els_dap_rpc:i(ProjectNode, Module),
-
- %% purge all breakpoints from the module
- els_dap_rpc:no_break(ProjectNode, Module),
- Breakpoints1 =
- els_dap_breakpoints:do_line_breakpoints(ProjectNode, Module,
- LineBreaks, Breakpoints0),
- BreakpointsRsps = [
- #{<<"verified">> => true, <<"line">> => Line}
- || {{_, Line}, _} <- els_dap_rpc:all_breaks(ProjectNode, Module)
- ],
-
- FunctionBreaks =
- els_dap_breakpoints:get_function_breaks(Module, Breakpoints1),
- Breakpoints2 =
- els_dap_breakpoints:do_function_breaks(ProjectNode, Module,
- FunctionBreaks, Breakpoints1),
-
- { #{<<"breakpoints">> => BreakpointsRsps}
- , State#{ breakpoints => Breakpoints2}
- };
+ case start_distribution(Params) of
+ {ok, #{
+ <<"projectnode">> := ProjectNode,
+ <<"timeout">> := TimeOut
+ }} ->
+ els_dap_server:send_event(<<"initialized">>, #{}),
+ {#{}, State#{
+ project_node => ProjectNode,
+ launch_params => Params,
+ timeout => TimeOut
+ }};
+ {error, Error} ->
+ {{error, distribution_error(Error)}, State}
+ end;
+handle_request(
+ {<<"configurationDone">>, _Params},
+ #{
+ project_node := ProjectNode,
+ launch_params := LaunchParams,
+ timeout := Timeout
+ } = State
+) ->
+ ensure_connected(ProjectNode, Timeout),
+ %% TODO: Fetch stack_trace mode from Launch Config
+ els_dap_rpc:stack_trace(ProjectNode, all),
+ MFA = {els_dap_agent, int_cb, [self()]},
+ els_dap_rpc:auto_attach(ProjectNode, [break], MFA),
+
+ case LaunchParams of
+ #{
+ <<"module">> := Module,
+ <<"function">> := Function,
+ <<"args">> := Args
+ } ->
+ M = binary_to_atom(Module, utf8),
+ F = binary_to_atom(Function, utf8),
+ A = els_dap_rpc:eval(ProjectNode, Args, []),
+ ?LOG_INFO("Launching MFA: [~p]", [{M, F, A}]),
+ rpc:cast(ProjectNode, M, F, A);
+ _ ->
+ ok
+ end,
+ {#{}, State#{mode => running}};
+handle_request(
+ {<<"setBreakpoints">>, Params},
+ #{
+ project_node := ProjectNode,
+ breakpoints := Breakpoints0,
+ timeout := Timeout
+ } = State
+) ->
+ ensure_connected(ProjectNode, Timeout),
+ {Module, LineBreaks} = els_dap_breakpoints:build_source_breakpoints(Params),
+
+ {module, Module} = els_dap_rpc:i(ProjectNode, Module),
+
+ %% purge all breakpoints from the module
+ els_dap_rpc:no_break(ProjectNode, Module),
+ Breakpoints1 =
+ els_dap_breakpoints:do_line_breakpoints(
+ ProjectNode,
+ Module,
+ LineBreaks,
+ Breakpoints0
+ ),
+ BreakpointsRsps = [
+ #{<<"verified">> => true, <<"line">> => Line}
+ || {{_, Line}, _} <- els_dap_rpc:all_breaks(ProjectNode, Module)
+ ],
+
+ FunctionBreaks =
+ els_dap_breakpoints:get_function_breaks(Module, Breakpoints1),
+ Breakpoints2 =
+ els_dap_breakpoints:do_function_breaks(
+ ProjectNode,
+ Module,
+ FunctionBreaks,
+ Breakpoints1
+ ),
+
+ {#{<<"breakpoints">> => BreakpointsRsps}, State#{breakpoints => Breakpoints2}};
handle_request({<<"setExceptionBreakpoints">>, _Params}, State) ->
- {#{}, State};
-handle_request({<<"setFunctionBreakpoints">>, Params}
- , #{ project_node := ProjectNode
- , breakpoints := Breakpoints0
- , timeout := Timeout} = State
- ) ->
- ensure_connected(ProjectNode, Timeout),
- FunctionBreakPoints = maps:get(<<"breakpoints">>, Params, []),
- MFAs = [
- begin
- Spec = {Mod, _, _} = parse_mfa(MFA),
- els_dap_rpc:i(ProjectNode, Mod),
- Spec
- end
- || #{<<"name">> := MFA, <<"enabled">> := Enabled} <- FunctionBreakPoints,
- Enabled andalso parse_mfa(MFA) =/= error
- ],
-
- ModFuncBreaks = lists:foldl(
- fun({M, F, A}, Acc) ->
- case Acc of
- #{M := FBreaks} -> Acc#{M => [{F, A} | FBreaks]};
- _ -> Acc#{M => [{F, A}]}
- end
- end,
- #{},
- MFAs
- ),
-
- els_dap_rpc:no_break(ProjectNode),
- Breakpoints1 = maps:fold(
- fun (Mod, Breaks, Acc) ->
- Acc#{Mod => Breaks#{function => []}}
- end,
- #{},
- Breakpoints0
- ),
-
- Breakpoints2 = maps:fold(
- fun(Module, FunctionBreaks, Acc) ->
- els_dap_breakpoints:do_function_breaks(ProjectNode, Module,
- FunctionBreaks, Acc)
- end,
- Breakpoints1,
- ModFuncBreaks
- ),
- BreakpointsRsps = [
- #{
- <<"verified">> => true,
- <<"line">> => Line,
- <<"source">> => #{<<"path">> => source(Module, ProjectNode)}
- }
- || {{Module, Line}, [Status, _, _, _]} <-
- els_dap_rpc:all_breaks(ProjectNode),
- Status =:= active
- ],
-
- %% replay line breaks
- Breakpoints3 = maps:fold(
- fun(Module, _, Acc) ->
- Lines = els_dap_breakpoints:get_line_breaks(Module, Acc),
- els_dap_breakpoints:do_line_breakpoints(ProjectNode, Module,
- Lines, Acc)
- end,
- Breakpoints2,
- Breakpoints2
- ),
-
- { #{<<"breakpoints">> => BreakpointsRsps}
- , State#{breakpoints => Breakpoints3}
- };
-handle_request({<<"threads">>, _Params}, #{threads := Threads0} = State) ->
- Threads =
- [ #{ <<"id">> => Id
- , <<"name">> => format_term(Pid)
- } || {Id, #{pid := Pid} = _Thread} <- maps:to_list(Threads0)
+ {#{}, State};
+handle_request(
+ {<<"setFunctionBreakpoints">>, Params},
+ #{
+ project_node := ProjectNode,
+ breakpoints := Breakpoints0,
+ timeout := Timeout
+ } = State
+) ->
+ ensure_connected(ProjectNode, Timeout),
+ FunctionBreakPoints = maps:get(<<"breakpoints">>, Params, []),
+ MFAs = [
+ begin
+ Spec = {Mod, _, _} = parse_mfa(MFA),
+ els_dap_rpc:i(ProjectNode, Mod),
+ Spec
+ end
+ || #{<<"name">> := MFA, <<"enabled">> := Enabled} <- FunctionBreakPoints,
+ Enabled andalso parse_mfa(MFA) =/= error
+ ],
+
+ ModFuncBreaks = lists:foldl(
+ fun({M, F, A}, Acc) ->
+ case Acc of
+ #{M := FBreaks} -> Acc#{M => [{F, A} | FBreaks]};
+ _ -> Acc#{M => [{F, A}]}
+ end
+ end,
+ #{},
+ MFAs
+ ),
+
+ els_dap_rpc:no_break(ProjectNode),
+ Breakpoints1 = maps:fold(
+ fun(Mod, Breaks, Acc) ->
+ Acc#{Mod => Breaks#{function => []}}
+ end,
+ #{},
+ Breakpoints0
+ ),
+
+ Breakpoints2 = maps:fold(
+ fun(Module, FunctionBreaks, Acc) ->
+ els_dap_breakpoints:do_function_breaks(
+ ProjectNode,
+ Module,
+ FunctionBreaks,
+ Acc
+ )
+ end,
+ Breakpoints1,
+ ModFuncBreaks
+ ),
+ BreakpointsRsps = [
+ #{
+ <<"verified">> => true,
+ <<"line">> => Line,
+ <<"source">> => #{<<"path">> => source(Module, ProjectNode)}
+ }
+ || {{Module, Line}, [Status, _, _, _]} <-
+ els_dap_rpc:all_breaks(ProjectNode),
+ Status =:= active
],
- {#{<<"threads">> => Threads}, State};
+
+ %% replay line breaks
+ Breakpoints3 = maps:fold(
+ fun(Module, _, Acc) ->
+ Lines = els_dap_breakpoints:get_line_breaks(Module, Acc),
+ els_dap_breakpoints:do_line_breakpoints(
+ ProjectNode,
+ Module,
+ Lines,
+ Acc
+ )
+ end,
+ Breakpoints2,
+ Breakpoints2
+ ),
+
+ {#{<<"breakpoints">> => BreakpointsRsps}, State#{breakpoints => Breakpoints3}};
+handle_request({<<"threads">>, _Params}, #{threads := Threads0} = State) ->
+ Threads =
+ [
+ #{
+ <<"id">> => Id,
+ <<"name">> => format_term(Pid)
+ }
+ || {Id, #{pid := Pid} = _Thread} <- maps:to_list(Threads0)
+ ],
+ {#{<<"threads">> => Threads}, State};
handle_request({<<"stackTrace">>, Params}, #{threads := Threads} = State) ->
- #{<<"threadId">> := ThreadId} = Params,
- Thread = maps:get(ThreadId, Threads),
- Frames = maps:get(frames, Thread),
- StackFrames =
- [ #{ <<"id">> => Id
- , <<"name">> => format_mfa(M, F, length(A))
- , <<"source">> => #{<<"path">> => Source}
- , <<"line">> => Line
- , <<"column">> => 0
- }
- || { Id
- , #{ module := M
- , function := F
- , arguments := A
- , line := Line
- , source := Source
+ #{<<"threadId">> := ThreadId} = Params,
+ Thread = maps:get(ThreadId, Threads),
+ Frames = maps:get(frames, Thread),
+ StackFrames =
+ [
+ #{
+ <<"id">> => Id,
+ <<"name">> => format_mfa(M, F, length(A)),
+ <<"source">> => #{<<"path">> => Source},
+ <<"line">> => Line,
+ <<"column">> => 0
}
- } <- maps:to_list(Frames)
- ],
- {#{<<"stackFrames">> => StackFrames}, State};
-handle_request({<<"scopes">>, #{<<"frameId">> := FrameId} }
- , #{ threads := Threads
- , scope_bindings := ExistingScopes} = State) ->
- case frame_by_id(FrameId, maps:values(Threads)) of
- undefined -> {#{<<"scopes">> => []}, State};
- Frame ->
- Bindings = maps:get(bindings, Frame),
- Ref = erlang:unique_integer([positive]),
- {#{<<"scopes">> => [
+ || {Id, #{
+ module := M,
+ function := F,
+ arguments := A,
+ line := Line,
+ source := Source
+ }} <- maps:to_list(Frames)
+ ],
+ {#{<<"stackFrames">> => StackFrames}, State};
+handle_request(
+ {<<"scopes">>, #{<<"frameId">> := FrameId}},
+ #{
+ threads := Threads,
+ scope_bindings := ExistingScopes
+ } = State
+) ->
+ case frame_by_id(FrameId, maps:values(Threads)) of
+ undefined ->
+ {#{<<"scopes">> => []}, State};
+ Frame ->
+ Bindings = maps:get(bindings, Frame),
+ Ref = erlang:unique_integer([positive]),
+ {
+ #{
+ <<"scopes">> => [
+ #{
+ <<"name">> => <<"Locals">>,
+ <<"presentationHint">> => <<"locals">>,
+ <<"variablesReference">> => Ref
+ }
+ ]
+ },
+ State#{scope_bindings => ExistingScopes#{Ref => {generic, Bindings}}}
+ }
+ end;
+handle_request(
+ {<<"next">>, Params},
+ #{
+ threads := Threads,
+ project_node := ProjectNode
+ } = State
+) ->
+ #{<<"threadId">> := ThreadId} = Params,
+ Pid = to_pid(ThreadId, Threads),
+ ok = els_dap_rpc:next(ProjectNode, Pid),
+ {#{}, State};
+handle_request(
+ {<<"pause">>, _},
+ State
+) ->
+ %% pause is not supported by the OTP debugger
+ %% but we cannot disable it in the UI either
+ {#{}, State};
+handle_request(
+ {<<"continue">>, Params},
+ #{
+ threads := Threads,
+ project_node := ProjectNode
+ } = State
+) ->
+ #{<<"threadId">> := ThreadId} = Params,
+ Pid = to_pid(ThreadId, Threads),
+ ok = els_dap_rpc:continue(ProjectNode, Pid),
+ {#{<<"allThreadsContinued">> => false}, State#{mode => running}};
+handle_request(
+ {<<"stepIn">>, Params},
+ #{
+ threads := Threads,
+ project_node := ProjectNode
+ } = State
+) ->
+ #{<<"threadId">> := ThreadId} = Params,
+ Pid = to_pid(ThreadId, Threads),
+ ok = els_dap_rpc:step(ProjectNode, Pid),
+ {#{}, State};
+handle_request(
+ {<<"stepOut">>, Params},
+ #{
+ threads := Threads,
+ project_node := ProjectNode
+ } = State
+) ->
+ #{<<"threadId">> := ThreadId} = Params,
+ Pid = to_pid(ThreadId, Threads),
+ ok = els_dap_rpc:next(ProjectNode, Pid),
+ {#{}, State};
+handle_request(
+ {<<"evaluate">>,
#{
- <<"name">> => <<"Locals">>,
- <<"presentationHint">> => <<"locals">>,
- <<"variablesReference">> => Ref
- }
- ]}, State#{scope_bindings => ExistingScopes#{Ref => {generic, Bindings}}}}
- end;
-handle_request( {<<"next">>, Params}
- , #{ threads := Threads
- , project_node := ProjectNode
- } = State
- ) ->
- #{<<"threadId">> := ThreadId} = Params,
- Pid = to_pid(ThreadId, Threads),
- ok = els_dap_rpc:next(ProjectNode, Pid),
- {#{}, State};
-handle_request( {<<"pause">>, _}
- , State
- ) ->
- %% pause is not supported by the OTP debugger
- %% but we cannot disable it in the UI either
- {#{}, State};
-handle_request( {<<"continue">>, Params}
- , #{ threads := Threads
- , project_node := ProjectNode
- } = State
- ) ->
- #{<<"threadId">> := ThreadId} = Params,
- Pid = to_pid(ThreadId, Threads),
- ok = els_dap_rpc:continue(ProjectNode, Pid),
- { #{<<"allThreadsContinued">> => false}
- , State#{mode => running}
- };
-handle_request( {<<"stepIn">>, Params}
- , #{ threads := Threads
- , project_node := ProjectNode
- } = State
- ) ->
- #{<<"threadId">> := ThreadId} = Params,
- Pid = to_pid(ThreadId, Threads),
- ok = els_dap_rpc:step(ProjectNode, Pid),
- {#{}, State};
-handle_request( {<<"stepOut">>, Params}
- , #{ threads := Threads
- , project_node := ProjectNode
- } = State
- ) ->
- #{<<"threadId">> := ThreadId} = Params,
- Pid = to_pid(ThreadId, Threads),
- ok = els_dap_rpc:next(ProjectNode, Pid),
- {#{}, State};
-handle_request({<<"evaluate">>, #{ <<"context">> := <<"hover">>
- , <<"frameId">> := FrameId
- , <<"expression">> := Input
- } = _Params}
- , #{ threads := Threads } = State
+ <<"context">> := <<"hover">>,
+ <<"frameId">> := FrameId,
+ <<"expression">> := Input
+ } = _Params},
+ #{threads := Threads} = State
) ->
- %% hover makes only sense for variables
- %% use the expression as fallback
- case frame_by_id(FrameId, maps:values(Threads)) of
- undefined -> {#{<<"result">> => <<"not available">>}, State};
- Frame ->
- Bindings = maps:get(bindings, Frame),
- VarName = erlang:list_to_atom(els_utils:to_list(Input)),
- case proplists:lookup(VarName, Bindings) of
- {VarName, VarValue} ->
- build_evaluate_response(VarValue, State);
- none ->
- {#{<<"result">> => <<"not available">>}, State}
- end
- end;
-handle_request({<<"evaluate">>, #{ <<"context">> := Context
- , <<"frameId">> := FrameId
- , <<"expression">> := Input
- } = _Params}
- , #{ threads := Threads
- , project_node := ProjectNode
- } = State
+ %% hover makes only sense for variables
+ %% use the expression as fallback
+ case frame_by_id(FrameId, maps:values(Threads)) of
+ undefined ->
+ {#{<<"result">> => <<"not available">>}, State};
+ Frame ->
+ Bindings = maps:get(bindings, Frame),
+ VarName = erlang:list_to_atom(els_utils:to_list(Input)),
+ case proplists:lookup(VarName, Bindings) of
+ {VarName, VarValue} ->
+ build_evaluate_response(VarValue, State);
+ none ->
+ {#{<<"result">> => <<"not available">>}, State}
+ end
+ end;
+handle_request(
+ {<<"evaluate">>,
+ #{
+ <<"context">> := Context,
+ <<"frameId">> := FrameId,
+ <<"expression">> := Input
+ } = _Params},
+ #{
+ threads := Threads,
+ project_node := ProjectNode
+ } = State
) when Context =:= <<"watch">> orelse Context =:= <<"repl">> ->
- %% repl and watch can use whole expressions,
- %% but we still want structured variable scopes
- case pid_by_frame_id(FrameId, maps:values(Threads)) of
- undefined ->
- {#{<<"result">> => <<"not available">>}, State};
- Pid ->
- Update =
- case Context of
- <<"watch">> -> no_update;
- <<"repl">> -> update
- end,
- Return = safe_eval(ProjectNode, Pid, Input, Update),
- build_evaluate_response(Return, State)
- end;
-handle_request({<<"variables">>, #{<<"variablesReference">> := Ref
- } = _Params}
- , #{ scope_bindings := AllBindings
- } = State) ->
- #{Ref := {Type, Bindings}} = AllBindings,
- RestBindings = maps:remove(Ref, AllBindings),
- {Variables, MoreBindings} = build_variables(Type, Bindings),
- { #{<<"variables">> => Variables}
- , State#{ scope_bindings => maps:merge(RestBindings, MoreBindings)}};
-handle_request({<<"disconnect">>, _Params}
- , #{project_node := ProjectNode,
- threads := Threads,
- launch_params := #{<<"request">> := Request} = State}) ->
- case Request of
- <<"attach">> ->
- els_dap_rpc:no_break(ProjectNode),
- [els_dap_rpc:continue(ProjectNode, Pid) ||
- {_ThreadID, #{pid := Pid}} <- maps:to_list(Threads)],
- [els_dap_rpc:n(ProjectNode, Module) ||
- Module <- els_dap_rpc:interpreted(ProjectNode)];
- <<"launch">> ->
- els_dap_rpc:halt(ProjectNode)
- end,
- stop_debugger(),
- {#{}, State};
+ %% repl and watch can use whole expressions,
+ %% but we still want structured variable scopes
+ case pid_by_frame_id(FrameId, maps:values(Threads)) of
+ undefined ->
+ {#{<<"result">> => <<"not available">>}, State};
+ Pid ->
+ Update =
+ case Context of
+ <<"watch">> -> no_update;
+ <<"repl">> -> update
+ end,
+ Return = safe_eval(ProjectNode, Pid, Input, Update),
+ build_evaluate_response(Return, State)
+ end;
+handle_request(
+ {<<"variables">>, #{<<"variablesReference">> := Ref} = _Params},
+ #{scope_bindings := AllBindings} = State
+) ->
+ #{Ref := {Type, Bindings}} = AllBindings,
+ RestBindings = maps:remove(Ref, AllBindings),
+ {Variables, MoreBindings} = build_variables(Type, Bindings),
+ {#{<<"variables">> => Variables}, State#{
+ scope_bindings => maps:merge(RestBindings, MoreBindings)
+ }};
+handle_request(
+ {<<"disconnect">>, _Params},
+ #{
+ project_node := ProjectNode,
+ threads := Threads,
+ launch_params := #{<<"request">> := Request} = State
+ }
+) ->
+ case Request of
+ <<"attach">> ->
+ els_dap_rpc:no_break(ProjectNode),
+ [
+ els_dap_rpc:continue(ProjectNode, Pid)
+ || {_ThreadID, #{pid := Pid}} <- maps:to_list(Threads)
+ ],
+ [
+ els_dap_rpc:n(ProjectNode, Module)
+ || Module <- els_dap_rpc:interpreted(ProjectNode)
+ ];
+ <<"launch">> ->
+ els_dap_rpc:halt(ProjectNode)
+ end,
+ stop_debugger(),
+ {#{}, State};
handle_request({<<"disconnect">>, _Params}, State) ->
- stop_debugger(),
- {#{}, State}.
+ stop_debugger(),
+ {#{}, State}.
--spec evaluate_condition(els_dap_breakpoints:line_breaks(), module(),
- integer(), atom(), pid()) -> boolean().
+-spec evaluate_condition(
+ els_dap_breakpoints:line_breaks(),
+ module(),
+ integer(),
+ atom(),
+ pid()
+) -> boolean().
evaluate_condition(Breakpt, Module, Line, ProjectNode, ThreadPid) ->
- %% evaluate condition if exists, otherwise treat as 'true'
- case Breakpt of
- #{condition := CondExpr} ->
- CondEval = safe_eval(ProjectNode, ThreadPid, CondExpr, no_update),
- case CondEval of
- true -> true;
- false -> false;
+ %% evaluate condition if exists, otherwise treat as 'true'
+ case Breakpt of
+ #{condition := CondExpr} ->
+ CondEval = safe_eval(ProjectNode, ThreadPid, CondExpr, no_update),
+ case CondEval of
+ true ->
+ true;
+ false ->
+ false;
+ _ ->
+ WarnCond = unicode:characters_to_binary(
+ io_lib:format(
+ "~s:~b - Breakpoint condition evaluated to non-Boolean: ~w~n",
+ [source(Module, ProjectNode), Line, CondEval]
+ )
+ ),
+ els_dap_server:send_event(
+ <<"output">>,
+ #{
+ <<"output">> => WarnCond,
+ <<"category">> => <<"stdout">>
+ }
+ ),
+ false
+ end;
_ ->
- WarnCond = unicode:characters_to_binary(
- io_lib:format(
- "~s:~b - Breakpoint condition evaluated to non-Boolean: ~w~n",
- [source(Module, ProjectNode), Line, CondEval])),
- els_dap_server:send_event( <<"output">>
- , #{ <<"output">> => WarnCond
- , <<"category">> => <<"stdout">>
- }),
- false
- end;
- _ -> true
+ true
end.
--spec evaluate_hitcond(els_dap_breakpoints:line_breaks(), integer(), module(),
- integer(), atom(), pid()) -> boolean().
+-spec evaluate_hitcond(
+ els_dap_breakpoints:line_breaks(),
+ integer(),
+ module(),
+ integer(),
+ atom(),
+ pid()
+) -> boolean().
evaluate_hitcond(Breakpt, HitCount, Module, Line, ProjectNode, ThreadPid) ->
- %% evaluate condition if exists, otherwise treat as 'true'
- case Breakpt of
- #{hitcond := HitExpr} ->
- HitEval = safe_eval(ProjectNode, ThreadPid, HitExpr, no_update),
- case HitEval of
- N when is_integer(N), N>0 -> (HitCount rem N =:= 0);
+ %% evaluate condition if exists, otherwise treat as 'true'
+ case Breakpt of
+ #{hitcond := HitExpr} ->
+ HitEval = safe_eval(ProjectNode, ThreadPid, HitExpr, no_update),
+ case HitEval of
+ N when is_integer(N), N > 0 -> (HitCount rem N =:= 0);
+ _ ->
+ WarnHit = unicode:characters_to_binary(
+ io_lib:format(
+ "~s:~b - Breakpoint hit condition not a non-negative int: ~w~n",
+ [source(Module, ProjectNode), Line, HitEval]
+ )
+ ),
+ els_dap_server:send_event(
+ <<"output">>,
+ #{
+ <<"output">> => WarnHit,
+ <<"category">> => <<"stdout">>
+ }
+ ),
+ true
+ end;
_ ->
- WarnHit = unicode:characters_to_binary(
- io_lib:format(
- "~s:~b - Breakpoint hit condition not a non-negative int: ~w~n",
- [source(Module, ProjectNode), Line, HitEval])),
- els_dap_server:send_event( <<"output">>
- , #{ <<"output">> => WarnHit
- , <<"category">> => <<"stdout">>
- }),
- true
- end;
- _ -> true
- end.
-
--spec check_stop(els_dap_breakpoints:line_breaks(), boolean(), module(),
- integer(), atom(), pid()) -> boolean().
+ true
+ end.
+
+-spec check_stop(
+ els_dap_breakpoints:line_breaks(),
+ boolean(),
+ module(),
+ integer(),
+ atom(),
+ pid()
+) -> boolean().
check_stop(Breakpt, IsHit, Module, Line, ProjectNode, ThreadPid) ->
- case Breakpt of
- #{logexpr := LogExpr} ->
- case IsHit of
- true ->
- Return = safe_eval(ProjectNode, ThreadPid, LogExpr, no_update),
- LogMessage = unicode:characters_to_binary(
- io_lib:format("~s:~b - ~p~n",
- [source(Module, ProjectNode), Line, Return])),
- els_dap_server:send_event( <<"output">>
- , #{ <<"output">> => LogMessage
- , <<"category">> => <<"stdout">>
- }),
- false;
- false -> false
- end;
- _ -> IsHit
- end.
+ case Breakpt of
+ #{logexpr := LogExpr} ->
+ case IsHit of
+ true ->
+ Return = safe_eval(ProjectNode, ThreadPid, LogExpr, no_update),
+ LogMessage = unicode:characters_to_binary(
+ io_lib:format(
+ "~s:~b - ~p~n",
+ [source(Module, ProjectNode), Line, Return]
+ )
+ ),
+ els_dap_server:send_event(
+ <<"output">>,
+ #{
+ <<"output">> => LogMessage,
+ <<"category">> => <<"stdout">>
+ }
+ ),
+ false;
+ false ->
+ false
+ end;
+ _ ->
+ IsHit
+ end.
-spec debug_stop(thread_id()) -> mode().
debug_stop(ThreadId) ->
- els_dap_server:send_event( <<"stopped">>
- , #{ <<"reason">> => <<"breakpoint">>
- , <<"threadId">> => ThreadId
- }),
- stepping.
+ els_dap_server:send_event(
+ <<"stopped">>,
+ #{
+ <<"reason">> => <<"breakpoint">>,
+ <<"threadId">> => ThreadId
+ }
+ ),
+ stepping.
-spec debug_previous_mode(mode(), atom(), pid(), thread_id()) -> mode().
debug_previous_mode(Mode0, ProjectNode, ThreadPid, ThreadId) ->
- case Mode0 of
- running ->
- els_dap_rpc:continue(ProjectNode, ThreadPid),
- Mode0;
- _ ->
- debug_stop(ThreadId)
- end.
+ case Mode0 of
+ running ->
+ els_dap_rpc:continue(ProjectNode, ThreadPid),
+ Mode0;
+ _ ->
+ debug_stop(ThreadId)
+ end.
-spec handle_info(any(), state()) -> state() | no_return().
-handle_info( {int_cb, ThreadPid}
- , #{ threads := Threads
- , project_node := ProjectNode
- , breakpoints := Breakpoints
- , hits := Hits0
- , mode := Mode0
- } = State
- ) ->
- ?LOG_DEBUG("Int CB called. thread=~p", [ThreadPid]),
- ThreadId = id(ThreadPid),
- Thread = #{ pid => ThreadPid
- , frames => stack_frames(ThreadPid, ProjectNode)
- },
- {Module, Line} = break_module_line(ThreadPid, ProjectNode),
- Breakpt = els_dap_breakpoints:type(Breakpoints, Module, Line),
- Condition = evaluate_condition(Breakpt, Module, Line, ProjectNode, ThreadPid),
- %% update hit count for current line if condition is true
- HitCount = maps:get(Line, Hits0, 0) + 1,
- Hits1 = case Condition of
- true -> maps:put(Line, HitCount, Hits0);
- false -> Hits0
- end,
- %% check if there is hit expression, if yes check along with condition
- IsHit = Condition andalso
- evaluate_hitcond(Breakpt, HitCount, Module, Line, ProjectNode, ThreadPid),
- %% finally, either stop or log
- Stop = check_stop(Breakpt, IsHit, Module, Line, ProjectNode, ThreadPid),
- Mode1 = case Stop of
- true -> debug_stop(ThreadId);
- false -> debug_previous_mode(Mode0, ProjectNode, ThreadPid, ThreadId)
- end,
- State#{
- threads => maps:put(ThreadId, Thread, Threads),
- mode => Mode1,
- hits => Hits1
- };
+handle_info(
+ {int_cb, ThreadPid},
+ #{
+ threads := Threads,
+ project_node := ProjectNode,
+ breakpoints := Breakpoints,
+ hits := Hits0,
+ mode := Mode0
+ } = State
+) ->
+ ?LOG_DEBUG("Int CB called. thread=~p", [ThreadPid]),
+ ThreadId = id(ThreadPid),
+ Thread = #{
+ pid => ThreadPid,
+ frames => stack_frames(ThreadPid, ProjectNode)
+ },
+ {Module, Line} = break_module_line(ThreadPid, ProjectNode),
+ Breakpt = els_dap_breakpoints:type(Breakpoints, Module, Line),
+ Condition = evaluate_condition(Breakpt, Module, Line, ProjectNode, ThreadPid),
+ %% update hit count for current line if condition is true
+ HitCount = maps:get(Line, Hits0, 0) + 1,
+ Hits1 =
+ case Condition of
+ true -> maps:put(Line, HitCount, Hits0);
+ false -> Hits0
+ end,
+ %% check if there is hit expression, if yes check along with condition
+ IsHit =
+ Condition andalso
+ evaluate_hitcond(Breakpt, HitCount, Module, Line, ProjectNode, ThreadPid),
+ %% finally, either stop or log
+ Stop = check_stop(Breakpt, IsHit, Module, Line, ProjectNode, ThreadPid),
+ Mode1 =
+ case Stop of
+ true -> debug_stop(ThreadId);
+ false -> debug_previous_mode(Mode0, ProjectNode, ThreadPid, ThreadId)
+ end,
+ State#{
+ threads => maps:put(ThreadId, Thread, Threads),
+ mode => Mode1,
+ hits => Hits1
+ };
handle_info({nodedown, Node}, State) ->
- %% the project node is down, there is nothing left to do then to exit
- ?LOG_NOTICE("project node ~p terminated, ending debug session", [Node]),
- stop_debugger(),
- State.
+ %% the project node is down, there is nothing left to do then to exit
+ ?LOG_NOTICE("project node ~p terminated, ending debug session", [Node]),
+ stop_debugger(),
+ State.
%%==============================================================================
%% API
@@ -559,382 +680,423 @@ handle_info({nodedown, Node}, State) ->
-spec capabilities() -> capabilities().
capabilities() ->
- #{ <<"supportsConfigurationDoneRequest">> => true
- , <<"supportsEvaluateForHovers">> => true
- , <<"supportsFunctionBreakpoints">> => true
- , <<"supportsConditionalBreakpoints">> => true
- , <<"supportsHitConditionalBreakpoints">> => true
- , <<"supportsLogPoints">> => true}.
+ #{
+ <<"supportsConfigurationDoneRequest">> => true,
+ <<"supportsEvaluateForHovers">> => true,
+ <<"supportsFunctionBreakpoints">> => true,
+ <<"supportsConditionalBreakpoints">> => true,
+ <<"supportsHitConditionalBreakpoints">> => true,
+ <<"supportsLogPoints">> => true
+ }.
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec inject_dap_agent(atom()) -> ok.
inject_dap_agent(Node) ->
- Module = els_dap_agent,
- {Module, Bin, File} = code:get_object_code(Module),
- {_Replies, _} = els_dap_rpc:load_binary(Node, Module, File, Bin),
- ok.
+ Module = els_dap_agent,
+ {Module, Bin, File} = code:get_object_code(Module),
+ {_Replies, _} = els_dap_rpc:load_binary(Node, Module, File, Bin),
+ ok.
-spec id(pid()) -> integer().
id(Pid) ->
- erlang:phash2(Pid).
+ erlang:phash2(Pid).
-spec stack_frames(pid(), atom()) -> #{frame_id() => frame()}.
stack_frames(Pid, Node) ->
- {ok, Meta} = els_dap_rpc:get_meta(Node, Pid),
- [{Level, {M, F, A}} | Rest] =
- els_dap_rpc:meta(Node, Meta, backtrace, all),
- Bindings = els_dap_rpc:meta(Node, Meta, bindings, Level),
- StackFrameId = erlang:unique_integer([positive]),
- StackFrame = #{ module => M
- , function => F
- , arguments => A
- , source => source(M, Node)
- , line => break_line(Pid, Node)
- , bindings => Bindings},
- collect_frames(Node, Meta, Level, Rest, #{StackFrameId => StackFrame}).
+ {ok, Meta} = els_dap_rpc:get_meta(Node, Pid),
+ [{Level, {M, F, A}} | Rest] =
+ els_dap_rpc:meta(Node, Meta, backtrace, all),
+ Bindings = els_dap_rpc:meta(Node, Meta, bindings, Level),
+ StackFrameId = erlang:unique_integer([positive]),
+ StackFrame = #{
+ module => M,
+ function => F,
+ arguments => A,
+ source => source(M, Node),
+ line => break_line(Pid, Node),
+ bindings => Bindings
+ },
+ collect_frames(Node, Meta, Level, Rest, #{StackFrameId => StackFrame}).
-spec break_module_line(pid(), atom()) -> {module(), integer()}.
break_module_line(Pid, Node) ->
- Snapshots = els_dap_rpc:snapshot(Node),
- {Pid, _Function, break, Location} = lists:keyfind(Pid, 1, Snapshots),
- Location.
+ Snapshots = els_dap_rpc:snapshot(Node),
+ {Pid, _Function, break, Location} = lists:keyfind(Pid, 1, Snapshots),
+ Location.
-spec break_line(pid(), atom()) -> integer().
break_line(Pid, Node) ->
- {_, Line} = break_module_line(Pid, Node),
- Line.
+ {_, Line} = break_module_line(Pid, Node),
+ Line.
-spec source(atom(), atom()) -> binary().
source(Module, Node) ->
- Source = els_dap_rpc:file(Node, Module),
- els_dap_rpc:clear(Node),
- unicode:characters_to_binary(Source).
+ Source = els_dap_rpc:file(Node, Module),
+ els_dap_rpc:clear(Node),
+ unicode:characters_to_binary(Source).
-spec to_pid(pos_integer(), #{thread_id() => thread()}) -> pid().
to_pid(ThreadId, Threads) ->
- Thread = maps:get(ThreadId, Threads),
- maps:get(pid, Thread).
+ Thread = maps:get(ThreadId, Threads),
+ maps:get(pid, Thread).
-spec frame_by_id(frame_id(), [thread()]) -> frame() | undefined.
frame_by_id(FrameId, Threads) ->
- case [ maps:get(FrameId, Frames)
- || #{frames := Frames} <- Threads, maps:is_key(FrameId, Frames)
- ] of
- [Frame] -> Frame;
- _ -> undefined
- end.
+ case
+ [
+ maps:get(FrameId, Frames)
+ || #{frames := Frames} <- Threads, maps:is_key(FrameId, Frames)
+ ]
+ of
+ [Frame] -> Frame;
+ _ -> undefined
+ end.
-spec pid_by_frame_id(frame_id(), [thread()]) -> pid() | undefined.
pid_by_frame_id(FrameId, Threads) ->
- case [ Pid
- || #{frames := Frames, pid := Pid} <- Threads
- , maps:is_key(FrameId, Frames)
- ] of
- [Proc] -> Proc;
- _ ->
- undefined
- end.
+ case
+ [
+ Pid
+ || #{frames := Frames, pid := Pid} <- Threads,
+ maps:is_key(FrameId, Frames)
+ ]
+ of
+ [Proc] -> Proc;
+ _ -> undefined
+ end.
-spec format_mfa(module(), atom(), integer()) -> binary().
format_mfa(M, F, A) ->
- els_utils:to_binary(io_lib:format("~p:~p/~p", [M, F, A])).
+ els_utils:to_binary(io_lib:format("~p:~p/~p", [M, F, A])).
-spec parse_mfa(string()) -> {module(), atom(), non_neg_integer()} | error.
parse_mfa(MFABinary) ->
- MFA = unicode:characters_to_list(MFABinary),
- case erl_scan:string(MFA) of
- {ok, [ {'fun', _}
- , {atom, _, Module}
- , {':', _}
- , {atom, _, Function}
- , {'/', _}
- , {integer, _, Arity}], _} when Arity >= 0 ->
- {Module, Function, Arity};
- {ok, [ {atom, _, Module}
- , {':', _}
- , {atom, _, Function}
- , {'/', _}
- , {integer, _, Arity}], _} when Arity >= 0 ->
- {Module, Function, Arity};
- _ ->
- error
- end.
+ MFA = unicode:characters_to_list(MFABinary),
+ case erl_scan:string(MFA) of
+ {ok,
+ [
+ {'fun', _},
+ {atom, _, Module},
+ {':', _},
+ {atom, _, Function},
+ {'/', _},
+ {integer, _, Arity}
+ ],
+ _} when Arity >= 0 ->
+ {Module, Function, Arity};
+ {ok,
+ [
+ {atom, _, Module},
+ {':', _},
+ {atom, _, Function},
+ {'/', _},
+ {integer, _, Arity}
+ ],
+ _} when Arity >= 0 ->
+ {Module, Function, Arity};
+ _ ->
+ error
+ end.
-spec build_variables(binding_type(), bindings()) ->
- {[any()], #{pos_integer() => bindings()}}.
+ {[any()], #{pos_integer() => bindings()}}.
build_variables(Type, Bindings) ->
- build_variables(Type, Bindings, {[], #{}}).
+ build_variables(Type, Bindings, {[], #{}}).
--spec build_variables(binding_type(), bindings(), Acc) -> Acc
- when Acc :: {[any()], #{pos_integer() => bindings()}}.
+-spec build_variables(binding_type(), bindings(), Acc) -> Acc when
+ Acc :: {[any()], #{pos_integer() => bindings()}}.
build_variables(_, [], Acc) ->
- Acc;
+ Acc;
build_variables(generic, [{Name, Value} | Rest], Acc) when is_list(Value) ->
- build_variables(
- generic,
- Rest,
- add_var_to_acc(Name, Value, build_list_bindings(Value), Acc)
- );
+ build_variables(
+ generic,
+ Rest,
+ add_var_to_acc(Name, Value, build_list_bindings(Value), Acc)
+ );
build_variables(generic, [{Name, Value} | Rest], Acc) when is_tuple(Value) ->
- build_variables(
- generic,
- Rest,
- add_var_to_acc(Name, Value, build_tuple_bindings(Value), Acc)
- );
+ build_variables(
+ generic,
+ Rest,
+ add_var_to_acc(Name, Value, build_tuple_bindings(Value), Acc)
+ );
build_variables(generic, [{Name, Value} | Rest], Acc) when is_map(Value) ->
- build_variables(
- generic,
- Rest,
- add_var_to_acc(Name, Value, build_map_bindings(Value), Acc)
- );
+ build_variables(
+ generic,
+ Rest,
+ add_var_to_acc(Name, Value, build_map_bindings(Value), Acc)
+ );
build_variables(generic, [{Name, Value} | Rest], Acc) ->
- build_variables(
- generic,
- Rest,
- add_var_to_acc(Name, Value, none, Acc)
- );
+ build_variables(
+ generic,
+ Rest,
+ add_var_to_acc(Name, Value, none, Acc)
+ );
build_variables(map_assoc, [{Name, Assocs} | Rest], Acc) ->
- {_, [{'Value', Value}, {'Key', Key}]} = Assocs,
- build_variables(
- map_assoc,
- Rest,
- add_var_to_acc(Name, {Key, Value}, Assocs, Acc)
- ).
+ {_, [{'Value', Value}, {'Key', Key}]} = Assocs,
+ build_variables(
+ map_assoc,
+ Rest,
+ add_var_to_acc(Name, {Key, Value}, Assocs, Acc)
+ ).
-spec add_var_to_acc(
- varname(),
- term(),
- none | {binding_type(), bindings()},
- Acc
-) -> Acc
- when Acc :: {[any()], #{non_neg_integer() => bindings()}}.
+ varname(),
+ term(),
+ none | {binding_type(), bindings()},
+ Acc
+) -> Acc when
+ Acc :: {[any()], #{non_neg_integer() => bindings()}}.
add_var_to_acc(Name, Value, none, {VarAcc, BindAcc}) ->
- { [build_variable(Name, Value, 0) | VarAcc]
- , BindAcc};
+ {[build_variable(Name, Value, 0) | VarAcc], BindAcc};
add_var_to_acc(Name, Value, Bindings, {VarAcc, BindAcc}) ->
- Ref = erlang:unique_integer([positive]),
- { [build_variable(Name, Value, Ref) | VarAcc]
- , BindAcc#{ Ref => Bindings}
- }.
+ Ref = erlang:unique_integer([positive]),
+ {[build_variable(Name, Value, Ref) | VarAcc], BindAcc#{Ref => Bindings}}.
-spec build_variable(varname(), term(), non_neg_integer()) -> any().
build_variable(Name, Value, Ref) ->
- %% print whole term to enable copying if the value
- #{ <<"name">> => unicode:characters_to_binary(io_lib:format("~s", [Name]))
- , <<"value">> => format_term(Value)
- , <<"variablesReference">> => Ref }.
+ %% print whole term to enable copying if the value
+ #{
+ <<"name">> => unicode:characters_to_binary(io_lib:format("~s", [Name])),
+ <<"value">> => format_term(Value),
+ <<"variablesReference">> => Ref
+ }.
-spec build_list_bindings(
- maybe_improper_list()
+ maybe_improper_list()
) -> {binding_type(), bindings()}.
build_list_bindings(List) ->
- build_maybe_improper_list_bindings(List, 0, []).
+ build_maybe_improper_list_bindings(List, 0, []).
-spec build_tuple_bindings(tuple()) -> {binding_type(), bindings()}.
build_tuple_bindings(Tuple) ->
- build_list_bindings(erlang:tuple_to_list(Tuple)).
+ build_list_bindings(erlang:tuple_to_list(Tuple)).
-spec build_map_bindings(map()) -> {binding_type(), bindings()}.
build_map_bindings(Map) ->
- {_, Bindings} =
- lists:foldl(
- fun ({Key, Value}, {Cnt, Acc}) ->
- Name =
- unicode:characters_to_binary(
- io_lib:format("~s => ~s", [format_term(Key), format_term(Value)])),
- { Cnt + 1
- , [{ Name
- , {generic, [{'Value', Value}, {'Key', Key}]}
- } | Acc]
- }
- end,
- {0, []}, maps:to_list(Map)),
- {map_assoc, Bindings}.
+ {_, Bindings} =
+ lists:foldl(
+ fun({Key, Value}, {Cnt, Acc}) ->
+ Name =
+ unicode:characters_to_binary(
+ io_lib:format("~s => ~s", [format_term(Key), format_term(Value)])
+ ),
+ {Cnt + 1, [{Name, {generic, [{'Value', Value}, {'Key', Key}]}} | Acc]}
+ end,
+ {0, []},
+ maps:to_list(Map)
+ ),
+ {map_assoc, Bindings}.
-spec build_maybe_improper_list_bindings(
- maybe_improper_list(),
- non_neg_integer(),
- bindings()
+ maybe_improper_list(),
+ non_neg_integer(),
+ bindings()
) -> {binding_type(), bindings()}.
build_maybe_improper_list_bindings([], _, Acc) ->
- {generic, Acc};
+ {generic, Acc};
build_maybe_improper_list_bindings([E | Tail], Cnt, Acc) ->
- Binding = {erlang:integer_to_list(Cnt), E},
- build_maybe_improper_list_bindings(Tail, Cnt + 1, [Binding | Acc]);
+ Binding = {erlang:integer_to_list(Cnt), E},
+ build_maybe_improper_list_bindings(Tail, Cnt + 1, [Binding | Acc]);
build_maybe_improper_list_bindings(ImproperTail, _Cnt, Acc) ->
- Binding = {"improper tail", ImproperTail},
- build_maybe_improper_list_bindings([], 0, [Binding | Acc]).
+ Binding = {"improper tail", ImproperTail},
+ build_maybe_improper_list_bindings([], 0, [Binding | Acc]).
-spec is_structured(term()) -> boolean().
is_structured(Term) when
- is_list(Term) orelse
- is_map(Term) orelse
- is_tuple(Term) -> true;
-is_structured(_) -> false.
+ is_list(Term) orelse
+ is_map(Term) orelse
+ is_tuple(Term)
+->
+ true;
+is_structured(_) ->
+ false.
-spec build_evaluate_response(term(), state()) -> {any(), state()}.
build_evaluate_response(
- ResultValue,
- State = #{scope_bindings := ExistingScopes}
+ ResultValue,
+ State = #{scope_bindings := ExistingScopes}
) ->
- ResultBinary = format_term(ResultValue),
- case is_structured(ResultValue) of
+ ResultBinary = format_term(ResultValue),
+ case is_structured(ResultValue) of
true ->
- {_, SubScope} = build_variables(generic, [{undefined, ResultValue}]),
- %% there is onlye one sub-scope returned
- [Ref] = maps:keys(SubScope),
- NewScopes = maps:merge(ExistingScopes, SubScope),
- { #{<<"result">> => ResultBinary, <<"variablesReference">> => Ref}
- , State#{scope_bindings => NewScopes}
- };
+ {_, SubScope} = build_variables(generic, [{undefined, ResultValue}]),
+ %% there is onlye one sub-scope returned
+ [Ref] = maps:keys(SubScope),
+ NewScopes = maps:merge(ExistingScopes, SubScope),
+ {#{<<"result">> => ResultBinary, <<"variablesReference">> => Ref}, State#{
+ scope_bindings => NewScopes
+ }};
false ->
- { #{<<"result">> => ResultBinary}
- , State
- }
- end.
+ {#{<<"result">> => ResultBinary}, State}
+ end.
-spec format_term(term()) -> binary().
format_term(T) ->
- %% print on one line and print strings
- %% as printable characters (if possible)
- els_utils:to_binary(
- [ string:trim(Line)
- || Line <- string:split(io_lib:format("~tp", [T]), "\n", all)]).
-
--spec collect_frames(node(), pid(), pos_integer(), Backtrace, Acc) -> Acc
- when Acc :: #{frame_id() => frame()},
- Backtrace :: [{pos_integer(), {module(), atom(), non_neg_integer()}}].
-collect_frames(_, _, _, [], Acc) -> Acc;
+ %% print on one line and print strings
+ %% as printable characters (if possible)
+ els_utils:to_binary(
+ [
+ string:trim(Line)
+ || Line <- string:split(io_lib:format("~tp", [T]), "\n", all)
+ ]
+ ).
+
+-spec collect_frames(node(), pid(), pos_integer(), Backtrace, Acc) -> Acc when
+ Acc :: #{frame_id() => frame()},
+ Backtrace :: [{pos_integer(), {module(), atom(), non_neg_integer()}}].
+collect_frames(_, _, _, [], Acc) ->
+ Acc;
collect_frames(Node, Meta, Level, [{NextLevel, {M, F, A}} | Rest], Acc) ->
- case els_dap_rpc:meta(Node, Meta, stack_frame, {up, Level}) of
- {NextLevel, {_, Line}, Bindings} ->
- StackFrameId = erlang:unique_integer([positive]),
- StackFrame = #{ module => M
- , function => F
- , arguments => A
- , source => source(M, Node)
- , line => Line
- , bindings => Bindings},
- collect_frames( Node
- , Meta
- , NextLevel
- , Rest
- , Acc#{StackFrameId => StackFrame}
- );
- BadFrame ->
- ?LOG_ERROR( "Received a bad frame: ~p expected level ~p and module ~p"
- , [BadFrame, NextLevel, M]),
- Acc
- end.
+ case els_dap_rpc:meta(Node, Meta, stack_frame, {up, Level}) of
+ {NextLevel, {_, Line}, Bindings} ->
+ StackFrameId = erlang:unique_integer([positive]),
+ StackFrame = #{
+ module => M,
+ function => F,
+ arguments => A,
+ source => source(M, Node),
+ line => Line,
+ bindings => Bindings
+ },
+ collect_frames(
+ Node,
+ Meta,
+ NextLevel,
+ Rest,
+ Acc#{StackFrameId => StackFrame}
+ );
+ BadFrame ->
+ ?LOG_ERROR(
+ "Received a bad frame: ~p expected level ~p and module ~p",
+ [BadFrame, NextLevel, M]
+ ),
+ Acc
+ end.
-spec ensure_connected(node(), timeout()) -> ok.
ensure_connected(Node, Timeout) ->
- case is_node_connected(Node) of
- true -> ok;
- false ->
- % connect and monitore project node
- case els_distribution_server:wait_connect_and_monitor( Node
- , Timeout
- , hidden) of
- ok -> inject_dap_agent(Node);
- _ -> stop_debugger()
- end
- end.
+ case is_node_connected(Node) of
+ true ->
+ ok;
+ false ->
+ % connect and monitore project node
+ case
+ els_distribution_server:wait_connect_and_monitor(
+ Node,
+ Timeout,
+ hidden
+ )
+ of
+ ok -> inject_dap_agent(Node);
+ _ -> stop_debugger()
+ end
+ end.
-spec stop_debugger() -> no_return().
stop_debugger() ->
- %% the project node is down, there is nothing left to do then to exit
- els_dap_server:send_event(<<"terminated">>, #{}),
- els_dap_server:send_event(<<"exited">>, #{ <<"exitCode">> => 0 }),
- ?LOG_NOTICE("terminating debug adapter"),
- els_utils:halt(0).
+ %% the project node is down, there is nothing left to do then to exit
+ els_dap_server:send_event(<<"terminated">>, #{}),
+ els_dap_server:send_event(<<"exited">>, #{<<"exitCode">> => 0}),
+ ?LOG_NOTICE("terminating debug adapter"),
+ els_utils:halt(0).
-spec is_node_connected(node()) -> boolean().
is_node_connected(Node) ->
- lists:member(Node, erlang:nodes(connected)).
+ lists:member(Node, erlang:nodes(connected)).
-spec safe_eval(node(), pid(), string(), update | no_update) -> term().
safe_eval(ProjectNode, Debugged, Expression, Update) ->
- {ok, Meta} = els_dap_rpc:get_meta(ProjectNode, Debugged),
- Command = els_utils:to_list(Expression),
- Return = els_dap_rpc:meta_eval(ProjectNode, Meta, Command),
- case Update of
- update -> ok;
- no_update ->
- receive
- {int_cb, Debugged} -> ok
- end
- end,
- Return.
+ {ok, Meta} = els_dap_rpc:get_meta(ProjectNode, Debugged),
+ Command = els_utils:to_list(Expression),
+ Return = els_dap_rpc:meta_eval(ProjectNode, Meta, Command),
+ case Update of
+ update ->
+ ok;
+ no_update ->
+ receive
+ {int_cb, Debugged} -> ok
+ end
+ end,
+ Return.
-spec check_project_node_name(binary(), boolean()) -> atom().
check_project_node_name(ProjectNode, false) ->
- binary_to_atom(ProjectNode, utf8);
+ binary_to_atom(ProjectNode, utf8);
check_project_node_name(ProjectNode, true) ->
- case binary:match(ProjectNode, <<"@">>) of
- nomatch ->
- {ok, HostName} = inet:gethostname(),
- BinHostName = list_to_binary(HostName),
- DomainStr = proplists:get_value(domain, inet:get_rc(), ""),
- Domain = list_to_binary(DomainStr),
- BinName = <>,
- binary_to_atom(BinName, utf8);
- _ ->
- binary_to_atom(ProjectNode, utf8)
- end.
+ case binary:match(ProjectNode, <<"@">>) of
+ nomatch ->
+ {ok, HostName} = inet:gethostname(),
+ BinHostName = list_to_binary(HostName),
+ DomainStr = proplists:get_value(domain, inet:get_rc(), ""),
+ Domain = list_to_binary(DomainStr),
+ BinName = <>,
+ binary_to_atom(BinName, utf8);
+ _ ->
+ binary_to_atom(ProjectNode, utf8)
+ end.
-spec start_distribution(map()) -> {ok, map()} | {error, any()}.
start_distribution(Params) ->
- #{<<"cwd">> := Cwd} = Params,
- ok = file:set_cwd(Cwd),
- Name = filename:basename(Cwd),
-
- %% get default and final launch config
- DefaultConfig = #{
- <<"projectnode">> =>
- atom_to_binary(
- els_distribution_server:node_name(<<"erlang_ls_dap_project">>, Name),
- utf8
- ),
- <<"cookie">> => atom_to_binary(erlang:get_cookie(), utf8),
- <<"timeout">> => 30,
- <<"use_long_names">> => false
- },
- Config = maps:merge(DefaultConfig, Params),
- #{ <<"projectnode">> := RawProjectNode
- , <<"cookie">> := ConfCookie
- , <<"use_long_names">> := UseLongNames} = Config,
- ConfProjectNode = check_project_node_name(RawProjectNode, UseLongNames),
- ?LOG_INFO("Configured Project Node Name: ~p", [ConfProjectNode]),
- Cookie = binary_to_atom(ConfCookie, utf8),
-
- NameType = case UseLongNames of
- true ->
- longnames;
- false ->
- shortnames
- end,
- %% start distribution
- Prefix = <<"erlang_ls_dap">>,
- Int = erlang:phash2(erlang:timestamp()),
- Id = lists:flatten(io_lib:format("~s_~s_~p", [Prefix, Name, Int])),
- {ok, HostName} = inet:gethostname(),
- LocalNode = els_distribution_server:node_name(Id, HostName, NameType),
- case els_distribution_server:start_distribution(LocalNode, ConfProjectNode,
- Cookie, NameType) of
- ok ->
- ?LOG_INFO("Distribution up on: [~p]", [LocalNode]),
- {ok, Config#{ <<"projectnode">> => ConfProjectNode}};
- {error, Error} ->
- ?LOG_ERROR("Cannot start distribution for ~p", [LocalNode]),
- {error, Error}
- end.
+ #{<<"cwd">> := Cwd} = Params,
+ ok = file:set_cwd(Cwd),
+ Name = filename:basename(Cwd),
+
+ %% get default and final launch config
+ DefaultConfig = #{
+ <<"projectnode">> =>
+ atom_to_binary(
+ els_distribution_server:node_name(<<"erlang_ls_dap_project">>, Name),
+ utf8
+ ),
+ <<"cookie">> => atom_to_binary(erlang:get_cookie(), utf8),
+ <<"timeout">> => 30,
+ <<"use_long_names">> => false
+ },
+ Config = maps:merge(DefaultConfig, Params),
+ #{
+ <<"projectnode">> := RawProjectNode,
+ <<"cookie">> := ConfCookie,
+ <<"use_long_names">> := UseLongNames
+ } = Config,
+ ConfProjectNode = check_project_node_name(RawProjectNode, UseLongNames),
+ ?LOG_INFO("Configured Project Node Name: ~p", [ConfProjectNode]),
+ Cookie = binary_to_atom(ConfCookie, utf8),
+
+ NameType =
+ case UseLongNames of
+ true ->
+ longnames;
+ false ->
+ shortnames
+ end,
+ %% start distribution
+ Prefix = <<"erlang_ls_dap">>,
+ Int = erlang:phash2(erlang:timestamp()),
+ Id = lists:flatten(io_lib:format("~s_~s_~p", [Prefix, Name, Int])),
+ {ok, HostName} = inet:gethostname(),
+ LocalNode = els_distribution_server:node_name(Id, HostName, NameType),
+ case
+ els_distribution_server:start_distribution(
+ LocalNode,
+ ConfProjectNode,
+ Cookie,
+ NameType
+ )
+ of
+ ok ->
+ ?LOG_INFO("Distribution up on: [~p]", [LocalNode]),
+ {ok, Config#{<<"projectnode">> => ConfProjectNode}};
+ {error, Error} ->
+ ?LOG_ERROR("Cannot start distribution for ~p", [LocalNode]),
+ {error, Error}
+ end.
-spec distribution_error(any()) -> binary().
distribution_error(Error) ->
- els_utils:to_binary(
- lists:flatten(
- io_lib:format("Could not start Erlang distribution. ~p", [Error]))).
+ els_utils:to_binary(
+ lists:flatten(
+ io_lib:format("Could not start Erlang distribution. ~p", [Error])
+ )
+ ).
diff --git a/apps/els_dap/src/els_dap_methods.erl b/apps/els_dap/src/els_dap_methods.erl
index 231760d6b..7804eefea 100644
--- a/apps/els_dap/src/els_dap_methods.erl
+++ b/apps/els_dap/src/els_dap_methods.erl
@@ -6,7 +6,7 @@
%%=============================================================================
-module(els_dap_methods).
--export([ dispatch/4 ]).
+-export([dispatch/4]).
%%==============================================================================
%% Includes
@@ -14,13 +14,14 @@
-include("els_dap.hrl").
-include_lib("kernel/include/logger.hrl").
--type method_name() :: binary().
--type state() :: map().
--type params() :: map().
--type result() :: {response, params() | null, state()}
- | {error_response, binary(), state()}
- | {noresponse, state()}
- | {notification, binary(), params(), state()}.
+-type method_name() :: binary().
+-type state() :: map().
+-type params() :: map().
+-type result() ::
+ {response, params() | null, state()}
+ | {error_response, binary(), state()}
+ | {noresponse, state()}
+ | {notification, binary(), params(), state()}.
-type request_type() :: notification | request.
%%==============================================================================
@@ -28,44 +29,48 @@
%%==============================================================================
-spec dispatch(method_name(), params(), request_type(), state()) -> result().
dispatch(Command, Args, Type, State) ->
- ?LOG_DEBUG("Dispatching request [command=~p] [args=~p]", [Command, Args]),
- try do_dispatch(Command, Args, State)
- catch
- error:function_clause ->
- not_implemented_method(Command, State);
- Type:Reason:Stack ->
- ?LOG_ERROR( "Unexpected error [type=~p] [error=~p] [stack=~p]"
- , [Type, Reason, Stack]),
- Error = <<"Unexpected error while ", Command/binary>>,
- {error_response, Error, State}
- end.
+ ?LOG_DEBUG("Dispatching request [command=~p] [args=~p]", [Command, Args]),
+ try
+ do_dispatch(Command, Args, State)
+ catch
+ error:function_clause ->
+ not_implemented_method(Command, State);
+ Type:Reason:Stack ->
+ ?LOG_ERROR(
+ "Unexpected error [type=~p] [error=~p] [stack=~p]",
+ [Type, Reason, Stack]
+ ),
+ Error = <<"Unexpected error while ", Command/binary>>,
+ {error_response, Error, State}
+ end.
-spec do_dispatch(method_name(), params(), state()) -> result().
do_dispatch(Command, Args, #{status := initialized} = State) ->
- Request = {Command, Args},
- case els_dap_provider:handle_request(els_dap_general_provider, Request) of
- {error, Error} ->
- {error_response, Error, State};
- Result ->
- {response, Result, State}
- end;
+ Request = {Command, Args},
+ case els_dap_provider:handle_request(els_dap_general_provider, Request) of
+ {error, Error} ->
+ {error_response, Error, State};
+ Result ->
+ {response, Result, State}
+ end;
do_dispatch(<<"initialize">>, Args, State) ->
- Request = {<<"initialize">>, Args},
- case els_dap_provider:handle_request(els_dap_general_provider, Request) of
- {error, Error} ->
- {error_response, Error, State};
- Result ->
- {response, Result, State#{status => initialized}}
- end;
+ Request = {<<"initialize">>, Args},
+ case els_dap_provider:handle_request(els_dap_general_provider, Request) of
+ {error, Error} ->
+ {error_response, Error, State};
+ Result ->
+ {response, Result, State#{status => initialized}}
+ end;
do_dispatch(_Command, _Args, State) ->
- Message = <<"The server is not fully initialized yet, please wait.">>,
- Result = #{ code => ?ERR_SERVER_NOT_INITIALIZED
- , message => Message
- },
- {error, Result, State}.
+ Message = <<"The server is not fully initialized yet, please wait.">>,
+ Result = #{
+ code => ?ERR_SERVER_NOT_INITIALIZED,
+ message => Message
+ },
+ {error, Result, State}.
-spec not_implemented_method(method_name(), state()) -> result().
not_implemented_method(Command, State) ->
- ?LOG_WARNING("[Command not implemented] [command=~s]", [Command]),
- Error = <<"Command not implemented: ", Command/binary>>,
- {error_response, Error, State}.
+ ?LOG_WARNING("[Command not implemented] [command=~s]", [Command]),
+ Error = <<"Command not implemented: ", Command/binary>>,
+ {error_response, Error, State}.
diff --git a/apps/els_dap/src/els_dap_protocol.erl b/apps/els_dap/src/els_dap_protocol.erl
index ab0271468..096190090 100644
--- a/apps/els_dap/src/els_dap_protocol.erl
+++ b/apps/els_dap/src/els_dap_protocol.erl
@@ -11,15 +11,15 @@
%% Exports
%%==============================================================================
%% Messaging API
--export([ event/3
- , request/3
- , response/3
- , error_response/3
- ]).
+-export([
+ event/3,
+ request/3,
+ response/3,
+ error_response/3
+]).
%% Data Structures
--export([ range/1
- ]).
+-export([range/1]).
%%==============================================================================
%% Includes
@@ -33,64 +33,71 @@
-spec event(number(), binary(), any()) -> binary().
%% TODO: Body is optional
event(Seq, EventType, Body) ->
- Message = #{ type => <<"event">>
- , seq => Seq
- , event => EventType
- , body => Body
- },
- content(jsx:encode(Message)).
+ Message = #{
+ type => <<"event">>,
+ seq => Seq,
+ event => EventType,
+ body => Body
+ },
+ content(jsx:encode(Message)).
-spec request(number(), binary(), any()) -> binary().
request(RequestSeq, Method, Params) ->
- Message = #{ type => <<"request">>
- , seq => RequestSeq
- , command => Method
- , arguments => Params
- },
- content(jsx:encode(Message)).
+ Message = #{
+ type => <<"request">>,
+ seq => RequestSeq,
+ command => Method,
+ arguments => Params
+ },
+ content(jsx:encode(Message)).
-spec response(number(), any(), any()) -> binary().
response(Seq, Command, Result) ->
- Message = #{ type => <<"response">>
- , request_seq => Seq
- , success => true
- , command => Command
- , body => Result
- },
- ?LOG_DEBUG("[Response] [message=~p]", [Message]),
- content(jsx:encode(Message)).
+ Message = #{
+ type => <<"response">>,
+ request_seq => Seq,
+ success => true,
+ command => Command,
+ body => Result
+ },
+ ?LOG_DEBUG("[Response] [message=~p]", [Message]),
+ content(jsx:encode(Message)).
-spec error_response(number(), any(), binary()) -> binary().
error_response(Seq, Command, Error) ->
- Message = #{ type => <<"response">>
- , request_seq => Seq
- , success => false
- , command => Command
- , body => #{ error => #{ id => Seq
- , format => Error
- , showUser => true
- }
- }
- },
- ?LOG_DEBUG("[Response] [message=~p]", [Message]),
- content(jsx:encode(Message)).
+ Message = #{
+ type => <<"response">>,
+ request_seq => Seq,
+ success => false,
+ command => Command,
+ body => #{
+ error => #{
+ id => Seq,
+ format => Error,
+ showUser => true
+ }
+ }
+ },
+ ?LOG_DEBUG("[Response] [message=~p]", [Message]),
+ content(jsx:encode(Message)).
%%==============================================================================
%% Data Structures
%%==============================================================================
-spec range(poi_range()) -> range().
-range(#{ from := {FromL, FromC}, to := {ToL, ToC} }) ->
- #{ start => #{line => FromL - 1, character => FromC - 1}
- , 'end' => #{line => ToL - 1, character => ToC - 1}
- }.
+range(#{from := {FromL, FromC}, to := {ToL, ToC}}) ->
+ #{
+ start => #{line => FromL - 1, character => FromC - 1},
+ 'end' => #{line => ToL - 1, character => ToC - 1}
+ }.
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec content(binary()) -> binary().
content(Body) ->
-els_utils:to_binary([headers(Body), "\r\n", Body]).
+ els_utils:to_binary([headers(Body), "\r\n", Body]).
-spec headers(binary()) -> iolist().
headers(Body) ->
- io_lib:format("Content-Length: ~p\r\n", [byte_size(Body)]).
+ io_lib:format("Content-Length: ~p\r\n", [byte_size(Body)]).
diff --git a/apps/els_dap/src/els_dap_provider.erl b/apps/els_dap/src/els_dap_provider.erl
index 866f51d14..25a00a003 100644
--- a/apps/els_dap/src/els_dap_provider.erl
+++ b/apps/els_dap/src/els_dap_provider.erl
@@ -5,16 +5,18 @@
-module(els_dap_provider).
%% API
--export([ handle_request/2
- , start_link/0
- ]).
+-export([
+ handle_request/2,
+ start_link/0
+]).
-behaviour(gen_server).
--export([ init/1
- , handle_call/3
- , handle_cast/2
- , handle_info/2
- ]).
+-export([
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2
+]).
%%==============================================================================
%% Includes
@@ -27,16 +29,17 @@
-callback handle_info(any(), any()) -> any().
-optional_callbacks([init/0, handle_info/2]).
--type config() :: any().
+-type config() :: any().
-type provider() :: els_dap_general_provider.
--type request() :: {binary(), map()}.
--type state() :: #{ internal_state := any() }.
+-type request() :: {binary(), map()}.
+-type state() :: #{internal_state := any()}.
--export_type([ config/0
- , provider/0
- , request/0
- , state/0
- ]).
+-export_type([
+ config/0,
+ provider/0,
+ request/0,
+ state/0
+]).
%%==============================================================================
%% Macro Definitions
@@ -49,11 +52,11 @@
-spec start_link() -> {ok, pid()}.
start_link() ->
- gen_server:start_link({local, ?SERVER}, ?MODULE, unused, []).
+ gen_server:start_link({local, ?SERVER}, ?MODULE, unused, []).
-spec handle_request(provider(), request()) -> any().
handle_request(Provider, Request) ->
- gen_server:call(?SERVER, {handle_request, Provider, Request}, infinity).
+ gen_server:call(?SERVER, {handle_request, Provider, Request}, infinity).
%%==============================================================================
%% gen_server callbacks
@@ -61,27 +64,27 @@ handle_request(Provider, Request) ->
-spec init(unused) -> {ok, state()}.
init(unused) ->
- ?LOG_INFO("Starting DAP provider", []),
- InternalState = els_dap_general_provider:init(),
- {ok, #{internal_state => InternalState}}.
+ ?LOG_INFO("Starting DAP provider", []),
+ InternalState = els_dap_general_provider:init(),
+ {ok, #{internal_state => InternalState}}.
-spec handle_call(any(), {pid(), any()}, state()) ->
- {reply, any(), state()}.
+ {reply, any(), state()}.
handle_call({handle_request, Provider, Request}, _From, State) ->
- #{internal_state := InternalState} = State,
- {Reply, NewInternalState} =
- Provider:handle_request(Request, InternalState),
- {reply, Reply, State#{internal_state => NewInternalState}}.
+ #{internal_state := InternalState} = State,
+ {Reply, NewInternalState} =
+ Provider:handle_request(Request, InternalState),
+ {reply, Reply, State#{internal_state => NewInternalState}}.
-spec handle_cast(any(), state()) ->
- {noreply, state()}.
+ {noreply, state()}.
handle_cast(_Request, State) ->
- {noreply, State}.
+ {noreply, State}.
-spec handle_info(any(), state()) ->
- {noreply, state()}.
+ {noreply, state()}.
handle_info(Request, State) ->
- #{internal_state := InternalState} = State,
- NewInternalState =
- els_dap_general_provider:handle_info(Request, InternalState),
- {noreply, State#{internal_state => NewInternalState}}.
+ #{internal_state := InternalState} = State,
+ NewInternalState =
+ els_dap_general_provider:handle_info(Request, InternalState),
+ {noreply, State#{internal_state => NewInternalState}}.
diff --git a/apps/els_dap/src/els_dap_rpc.erl b/apps/els_dap/src/els_dap_rpc.erl
index bf269e325..91027e45c 100644
--- a/apps/els_dap/src/els_dap_rpc.erl
+++ b/apps/els_dap/src/els_dap_rpc.erl
@@ -1,146 +1,147 @@
-module(els_dap_rpc).
--export([ interpreted/1
- , n/2
- , all_breaks/1
- , all_breaks/2
- , auto_attach/3
- , break/3
- , break_in/4
- , clear/1
- , continue/2
- , eval/3
- , file/2
- , get_meta/2
- , halt/1
- , i/2
- , load_binary/4
- , meta/4
- , meta_eval/3
- , module_info/3
- , next/2
- , no_break/1
- , no_break/2
- , snapshot/1
- , stack_trace/2
- , step/2
- ]).
+-export([
+ interpreted/1,
+ n/2,
+ all_breaks/1,
+ all_breaks/2,
+ auto_attach/3,
+ break/3,
+ break_in/4,
+ clear/1,
+ continue/2,
+ eval/3,
+ file/2,
+ get_meta/2,
+ halt/1,
+ i/2,
+ load_binary/4,
+ meta/4,
+ meta_eval/3,
+ module_info/3,
+ next/2,
+ no_break/1,
+ no_break/2,
+ snapshot/1,
+ stack_trace/2,
+ step/2
+]).
-spec interpreted(node()) -> any().
interpreted(Node) ->
- rpc:call(Node, int, interpreted, []).
+ rpc:call(Node, int, interpreted, []).
-spec n(node(), any()) -> any().
n(Node, Module) ->
- rpc:call(Node, int, n, [Module]).
+ rpc:call(Node, int, n, [Module]).
-spec all_breaks(node()) -> any().
all_breaks(Node) ->
- rpc:call(Node, int, all_breaks, []).
+ rpc:call(Node, int, all_breaks, []).
-spec all_breaks(node(), atom()) -> any().
all_breaks(Node, Module) ->
- rpc:call(Node, int, all_breaks, [Module]).
+ rpc:call(Node, int, all_breaks, [Module]).
-spec auto_attach(node(), [atom()], {module(), atom(), [any()]}) -> any().
auto_attach(Node, Flags, MFA) ->
- rpc:call(Node, int, auto_attach, [Flags, MFA]).
+ rpc:call(Node, int, auto_attach, [Flags, MFA]).
--spec break(node(), module(), integer()) -> any().
+-spec break(node(), module(), integer()) -> any().
break(Node, Module, Line) ->
- rpc:call(Node, int, break, [Module, Line]).
+ rpc:call(Node, int, break, [Module, Line]).
-spec break_in(node(), module(), atom(), non_neg_integer()) -> any().
break_in(Node, Module, Func, Arity) ->
- rpc:call(Node, int, break_in, [ Module, Func, Arity]).
+ rpc:call(Node, int, break_in, [Module, Func, Arity]).
-spec clear(node()) -> ok.
clear(Node) ->
- rpc:call(Node, int, clear, []).
+ rpc:call(Node, int, clear, []).
-spec continue(node(), pid()) -> any().
continue(Node, Pid) ->
- rpc:call(Node, int, continue, [Pid]).
+ rpc:call(Node, int, continue, [Pid]).
-spec eval(node(), string(), [any()]) -> any().
eval(Node, Input, Bindings) ->
- {ok, Tokens, _} = erl_scan:string(unicode:characters_to_list(Input) ++ "."),
- {ok, Exprs} = erl_parse:parse_exprs(Tokens),
+ {ok, Tokens, _} = erl_scan:string(unicode:characters_to_list(Input) ++ "."),
+ {ok, Exprs} = erl_parse:parse_exprs(Tokens),
- case rpc:call(Node, erl_eval, exprs, [Exprs, Bindings]) of
- {value, Value, _NewBindings} -> Value;
- {badrpc, Error} -> Error
- end.
+ case rpc:call(Node, erl_eval, exprs, [Exprs, Bindings]) of
+ {value, Value, _NewBindings} -> Value;
+ {badrpc, Error} -> Error
+ end.
-spec file(node(), module()) -> file:filename().
file(Node, Module) ->
- MaybeSource =
- case rpc:call(Node, int, file, [Module]) of
- {error, not_loaded} ->
- BeamName = atom_to_list(Module) ++ ".beam",
- case rpc:call(Node, code, where_is_file, [BeamName]) of
- non_existing -> {error, not_found};
- BeamFile -> rpc:call(Node, filelib, find_source, [BeamFile])
- end;
- IntSource ->
- {ok, IntSource}
- end,
- case MaybeSource of
- {ok, Source} ->
- Source;
- {error, not_found} ->
- CompileOpts = module_info(Node, Module, compile),
- proplists:get_value(source, CompileOpts)
- end.
+ MaybeSource =
+ case rpc:call(Node, int, file, [Module]) of
+ {error, not_loaded} ->
+ BeamName = atom_to_list(Module) ++ ".beam",
+ case rpc:call(Node, code, where_is_file, [BeamName]) of
+ non_existing -> {error, not_found};
+ BeamFile -> rpc:call(Node, filelib, find_source, [BeamFile])
+ end;
+ IntSource ->
+ {ok, IntSource}
+ end,
+ case MaybeSource of
+ {ok, Source} ->
+ Source;
+ {error, not_found} ->
+ CompileOpts = module_info(Node, Module, compile),
+ proplists:get_value(source, CompileOpts)
+ end.
-spec get_meta(node(), pid()) -> {ok, pid()}.
get_meta(Node, Pid) ->
- rpc:call(Node, dbg_iserver, safe_call, [{get_meta, Pid}]).
+ rpc:call(Node, dbg_iserver, safe_call, [{get_meta, Pid}]).
-spec halt(node()) -> true.
halt(Node) ->
- rpc:cast(Node, erlang, halt, []).
+ rpc:cast(Node, erlang, halt, []).
-spec i(node(), module()) -> any().
i(Node, Module) ->
- rpc:call(Node, int, i, [Module]).
+ rpc:call(Node, int, i, [Module]).
-spec load_binary(node(), module(), string(), binary()) -> any().
load_binary(Node, Module, File, Bin) ->
- rpc:call(Node, code, load_binary, [Module, File, Bin]).
+ rpc:call(Node, code, load_binary, [Module, File, Bin]).
-spec meta(node(), pid(), atom(), any()) -> any().
meta(Node, Meta, Flag, Opt) ->
- rpc:call(Node, int, meta, [Meta, Flag, Opt]).
+ rpc:call(Node, int, meta, [Meta, Flag, Opt]).
-spec meta_eval(node(), pid(), string()) -> any().
meta_eval(Node, Meta, Command) ->
- rpc:call(Node, els_dap_agent, meta_eval, [Meta, Command]).
+ rpc:call(Node, els_dap_agent, meta_eval, [Meta, Command]).
-spec next(node(), pid()) -> any().
next(Node, Pid) ->
- rpc:call(Node, int, next, [Pid]).
+ rpc:call(Node, int, next, [Pid]).
-spec no_break(node()) -> ok.
no_break(Node) ->
- rpc:call(Node, int, no_break, []).
+ rpc:call(Node, int, no_break, []).
-spec no_break(node(), atom()) -> ok.
no_break(Node, Module) ->
- rpc:call(Node, int, no_break, [Module]).
+ rpc:call(Node, int, no_break, [Module]).
-spec module_info(node(), module(), atom()) -> any().
module_info(Node, Module, What) ->
- rpc:call(Node, Module, module_info, [What]).
+ rpc:call(Node, Module, module_info, [What]).
-spec snapshot(node()) -> any().
snapshot(Node) ->
- rpc:call(Node, int, snapshot, []).
+ rpc:call(Node, int, snapshot, []).
-spec stack_trace(node(), any()) -> any().
stack_trace(Node, Flag) ->
- rpc:call(Node, int, stack_trace, [Flag]).
+ rpc:call(Node, int, stack_trace, [Flag]).
-spec step(node(), pid()) -> any().
step(Node, Pid) ->
- rpc:call(Node, int, step, [Pid]).
+ rpc:call(Node, int, step, [Pid]).
diff --git a/apps/els_dap/src/els_dap_server.erl b/apps/els_dap/src/els_dap_server.erl
index e1cfca48b..62503764d 100644
--- a/apps/els_dap/src/els_dap_server.erl
+++ b/apps/els_dap/src/els_dap_server.erl
@@ -17,25 +17,25 @@
%% Exports
%%==============================================================================
--export([ start_link/0
- ]).
+-export([start_link/0]).
%% gen_server callbacks
--export([ init/1
- , handle_call/3
- , handle_cast/2
- ]).
+-export([
+ init/1,
+ handle_call/3,
+ handle_cast/2
+]).
%% API
--export([ process_requests/1
- , set_io_device/1
- , send_event/2
- , send_request/2
- ]).
+-export([
+ process_requests/1,
+ set_io_device/1,
+ send_event/2,
+ send_request/2
+]).
%% Testing
--export([ reset_internal_state/0
- ]).
+-export([reset_internal_state/0]).
%%==============================================================================
%% Includes
@@ -50,10 +50,11 @@
%%==============================================================================
%% Record Definitions
%%==============================================================================
--record(state, { io_device :: any()
- , seq :: number()
- , internal_state :: map()
- }).
+-record(state, {
+ io_device :: any(),
+ seq :: number(),
+ internal_state :: map()
+}).
%%==============================================================================
%% Type Definitions
@@ -65,111 +66,118 @@
%%==============================================================================
-spec start_link() -> {ok, pid()}.
start_link() ->
- {ok, Pid} = gen_server:start_link({local, ?SERVER}, ?MODULE, [], []),
- Cb = fun(Requests) ->
- gen_server:cast(Pid, {process_requests, Requests})
- end,
- {ok, _} = els_stdio:start_listener(Cb),
- {ok, Pid}.
+ {ok, Pid} = gen_server:start_link({local, ?SERVER}, ?MODULE, [], []),
+ Cb = fun(Requests) ->
+ gen_server:cast(Pid, {process_requests, Requests})
+ end,
+ {ok, _} = els_stdio:start_listener(Cb),
+ {ok, Pid}.
-spec process_requests([any()]) -> ok.
process_requests(Requests) ->
- gen_server:cast(?SERVER, {process_requests, Requests}).
+ gen_server:cast(?SERVER, {process_requests, Requests}).
-spec set_io_device(atom() | pid()) -> ok.
set_io_device(IoDevice) ->
- gen_server:call(?SERVER, {set_io_device, IoDevice}).
+ gen_server:call(?SERVER, {set_io_device, IoDevice}).
-spec send_event(binary(), map()) -> ok.
send_event(EventType, Body) ->
- gen_server:cast(?SERVER, {event, EventType, Body}).
+ gen_server:cast(?SERVER, {event, EventType, Body}).
-spec send_request(binary(), map()) -> ok.
send_request(Method, Params) ->
- gen_server:cast(?SERVER, {request, Method, Params}).
+ gen_server:cast(?SERVER, {request, Method, Params}).
%%==============================================================================
%% Testing
%%==============================================================================
-spec reset_internal_state() -> ok.
reset_internal_state() ->
- gen_server:call(?MODULE, {reset_internal_state}).
+ gen_server:call(?MODULE, {reset_internal_state}).
%%==============================================================================
%% gen_server callbacks
%%==============================================================================
-spec init([]) -> {ok, state()}.
init([]) ->
- ?LOG_INFO("Starting els_dap_server..."),
- State = #state{ seq = 0
- , internal_state = #{}
- },
- {ok, State}.
+ ?LOG_INFO("Starting els_dap_server..."),
+ State = #state{
+ seq = 0,
+ internal_state = #{}
+ },
+ {ok, State}.
-spec handle_call(any(), any(), state()) -> {reply, any(), state()}.
handle_call({set_io_device, IoDevice}, _From, State) ->
- {reply, ok, State#state{io_device = IoDevice}};
+ {reply, ok, State#state{io_device = IoDevice}};
handle_call({reset_internal_state}, _From, State) ->
- {reply, ok, State#state{internal_state = #{}}}.
+ {reply, ok, State#state{internal_state = #{}}}.
-spec handle_cast(any(), state()) -> {noreply, state()}.
handle_cast({process_requests, Requests}, State0) ->
- State = lists:foldl(fun handle_request/2, State0, Requests),
- {noreply, State};
+ State = lists:foldl(fun handle_request/2, State0, Requests),
+ {noreply, State};
handle_cast({event, EventType, Body}, State0) ->
- State = do_send_event(EventType, Body, State0),
- {noreply, State};
+ State = do_send_event(EventType, Body, State0),
+ {noreply, State};
handle_cast({request, Method, Params}, State0) ->
- State = do_send_request(Method, Params, State0),
- {noreply, State};
+ State = do_send_request(Method, Params, State0),
+ {noreply, State};
handle_cast(_, State) ->
- {noreply, State}.
+ {noreply, State}.
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec handle_request(map(), state()) -> state().
-handle_request(#{ <<"seq">> := Seq
- , <<"type">> := <<"request">>
- , <<"command">> := Command
- } = Request, #state{internal_state = InternalState} = State0) ->
- Args = maps:get(<<"arguments">>, Request, #{}),
- case els_dap_methods:dispatch(Command, Args, request, InternalState) of
- {response, Result, NewInternalState} ->
- Response = els_dap_protocol:response(Seq, Command, Result),
- ?LOG_DEBUG("[SERVER] Sending response [response=~s]", [Response]),
- send(Response, State0),
- State0#state{internal_state = NewInternalState};
- {error_response, Error, NewInternalState} ->
- Response = els_dap_protocol:error_response(Seq, Command, Error),
- ?LOG_DEBUG("[SERVER] Sending error response [response=~s]", [Response]),
- send(Response, State0),
- State0#state{internal_state = NewInternalState}
- end;
+handle_request(
+ #{
+ <<"seq">> := Seq,
+ <<"type">> := <<"request">>,
+ <<"command">> := Command
+ } = Request,
+ #state{internal_state = InternalState} = State0
+) ->
+ Args = maps:get(<<"arguments">>, Request, #{}),
+ case els_dap_methods:dispatch(Command, Args, request, InternalState) of
+ {response, Result, NewInternalState} ->
+ Response = els_dap_protocol:response(Seq, Command, Result),
+ ?LOG_DEBUG("[SERVER] Sending response [response=~s]", [Response]),
+ send(Response, State0),
+ State0#state{internal_state = NewInternalState};
+ {error_response, Error, NewInternalState} ->
+ Response = els_dap_protocol:error_response(Seq, Command, Error),
+ ?LOG_DEBUG("[SERVER] Sending error response [response=~s]", [Response]),
+ send(Response, State0),
+ State0#state{internal_state = NewInternalState}
+ end;
handle_request(Response, State0) ->
- ?LOG_DEBUG( "[SERVER] got request response [response=~p]"
- , [Response]
- ),
- State0.
+ ?LOG_DEBUG(
+ "[SERVER] got request response [response=~p]",
+ [Response]
+ ),
+ State0.
-spec do_send_event(binary(), map(), state()) -> state().
do_send_event(EventType, Body, #state{seq = Seq0} = State0) ->
- Seq = Seq0 + 1,
- Event = els_dap_protocol:event(Seq, EventType, Body),
- ?LOG_DEBUG( "[SERVER] Sending event [type=~s]", [EventType]),
- send(Event, State0),
- State0#state{seq = Seq}.
+ Seq = Seq0 + 1,
+ Event = els_dap_protocol:event(Seq, EventType, Body),
+ ?LOG_DEBUG("[SERVER] Sending event [type=~s]", [EventType]),
+ send(Event, State0),
+ State0#state{seq = Seq}.
-spec do_send_request(binary(), map(), state()) -> state().
do_send_request(Method, Params, #state{seq = RequestId0} = State0) ->
- RequestId = RequestId0 + 1,
- Request = els_dap_protocol:request(RequestId, Method, Params),
- ?LOG_DEBUG( "[SERVER] Sending request [request=~p]"
- , [Request]
- ),
- send(Request, State0),
- State0#state{seq = RequestId}.
+ RequestId = RequestId0 + 1,
+ Request = els_dap_protocol:request(RequestId, Method, Params),
+ ?LOG_DEBUG(
+ "[SERVER] Sending request [request=~p]",
+ [Request]
+ ),
+ send(Request, State0),
+ State0#state{seq = RequestId}.
-spec send(binary(), state()) -> ok.
send(Payload, #state{io_device = IoDevice}) ->
- els_stdio:send(IoDevice, Payload).
+ els_stdio:send(IoDevice, Payload).
diff --git a/apps/els_dap/src/els_dap_sup.erl b/apps/els_dap/src/els_dap_sup.erl
index 1631c8631..405a190c4 100644
--- a/apps/els_dap/src/els_dap_sup.erl
+++ b/apps/els_dap/src/els_dap_sup.erl
@@ -14,10 +14,10 @@
%%==============================================================================
%% API
--export([ start_link/0 ]).
+-export([start_link/0]).
%% Supervisor Callbacks
--export([ init/1 ]).
+-export([init/1]).
%%==============================================================================
%% Includes
@@ -34,32 +34,37 @@
%%==============================================================================
-spec start_link() -> {ok, pid()}.
start_link() ->
- supervisor:start_link({local, ?SERVER}, ?MODULE, []).
+ supervisor:start_link({local, ?SERVER}, ?MODULE, []).
%%==============================================================================
%% supervisors callbacks
%%==============================================================================
-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init([]) ->
- SupFlags = #{ strategy => rest_for_one
- , intensity => 5
- , period => 60
- },
- {ok, Vsn} = application:get_key(vsn),
- ?LOG_INFO("Starting session (version ~p)", [Vsn]),
- restrict_stdio_access(),
- ChildSpecs = [ #{ id => els_config
- , start => {els_config, start_link, []}
- , shutdown => brutal_kill
- }
- , #{ id => els_dap_provider
- , start => {els_dap_provider, start_link, []}
- }
- , #{ id => els_dap_server
- , start => {els_dap_server, start_link, []}
- }
- ],
- {ok, {SupFlags, ChildSpecs}}.
+ SupFlags = #{
+ strategy => rest_for_one,
+ intensity => 5,
+ period => 60
+ },
+ {ok, Vsn} = application:get_key(vsn),
+ ?LOG_INFO("Starting session (version ~p)", [Vsn]),
+ restrict_stdio_access(),
+ ChildSpecs = [
+ #{
+ id => els_config,
+ start => {els_config, start_link, []},
+ shutdown => brutal_kill
+ },
+ #{
+ id => els_dap_provider,
+ start => {els_dap_provider, start_link, []}
+ },
+ #{
+ id => els_dap_server,
+ start => {els_dap_server, start_link, []}
+ }
+ ],
+ {ok, {SupFlags, ChildSpecs}}.
%% @doc Restrict access to standard I/O
%%
@@ -74,31 +79,32 @@ init([]) ->
%% which can print warnings to standard output.
-spec restrict_stdio_access() -> ok.
restrict_stdio_access() ->
- ?LOG_INFO("Use group leader as io_device"),
- case application:get_env(els_core, io_device, standard_io) of
- standard_io ->
- application:set_env(els_core, io_device, erlang:group_leader());
- _ -> ok
- end,
+ ?LOG_INFO("Use group leader as io_device"),
+ case application:get_env(els_core, io_device, standard_io) of
+ standard_io ->
+ application:set_env(els_core, io_device, erlang:group_leader());
+ _ ->
+ ok
+ end,
- ?LOG_INFO("Replace group leader to avoid unwanted output to stdout"),
- Pid = erlang:spawn(fun noop_group_leader/0),
- erlang:group_leader(Pid, self()),
- ok.
+ ?LOG_INFO("Replace group leader to avoid unwanted output to stdout"),
+ Pid = erlang:spawn(fun noop_group_leader/0),
+ erlang:group_leader(Pid, self()),
+ ok.
%% @doc Simulate a group leader but do nothing
-spec noop_group_leader() -> no_return().
noop_group_leader() ->
- receive
- Message ->
- ?LOG_INFO("noop_group_leader got [message=~p]", [Message]),
- case Message of
- {io_request, From, ReplyAs, getopts} ->
- From ! {io_reply, ReplyAs, []};
- {io_request, From, ReplyAs, _} ->
- From ! {io_reply, ReplyAs, ok};
- _ ->
- ok
- end,
- noop_group_leader()
- end.
+ receive
+ Message ->
+ ?LOG_INFO("noop_group_leader got [message=~p]", [Message]),
+ case Message of
+ {io_request, From, ReplyAs, getopts} ->
+ From ! {io_reply, ReplyAs, []};
+ {io_request, From, ReplyAs, _} ->
+ From ! {io_reply, ReplyAs, ok};
+ _ ->
+ ok
+ end,
+ noop_group_leader()
+ end.
diff --git a/apps/els_dap/test/els_dap_SUITE.erl b/apps/els_dap/test/els_dap_SUITE.erl
index 3155a7a03..8c3d87807 100644
--- a/apps/els_dap/test/els_dap_SUITE.erl
+++ b/apps/els_dap/test/els_dap_SUITE.erl
@@ -3,19 +3,21 @@
-include("els_dap.hrl").
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , groups/0
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ groups/0,
+ all/0
+]).
%% Test cases
--export([ parse_args/1
- , log_root/1
- ]).
+-export([
+ parse_args/1,
+ log_root/1
+]).
%%==============================================================================
%% Includes
@@ -33,74 +35,76 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec groups() -> [atom()].
groups() ->
- [].
+ [].
-spec init_per_suite(config()) -> config().
init_per_suite(_Config) ->
- [].
+ [].
-spec end_per_suite(config()) -> ok.
end_per_suite(_Config) ->
- ok.
+ ok.
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(_TestCase, _Config) ->
- [].
+ [].
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(_TestCase, _Config) ->
- unset_all_env(els_core),
- ok.
+ unset_all_env(els_core),
+ ok.
%%==============================================================================
%% Helpers
%%==============================================================================
-spec unset_all_env(atom()) -> ok.
unset_all_env(Application) ->
- Envs = application:get_all_env(Application),
- unset_env(Application, Envs).
+ Envs = application:get_all_env(Application),
+ unset_env(Application, Envs).
-spec unset_env(atom(), list({atom(), term()})) -> ok.
unset_env(_Application, []) ->
- ok;
+ ok;
unset_env(Application, [{Par, _Val} | Rest]) ->
- application:unset_env(Application, Par),
- unset_env(Application, Rest).
+ application:unset_env(Application, Par),
+ unset_env(Application, Rest).
%%==============================================================================
%% Testcases
%%==============================================================================
-spec parse_args(config()) -> ok.
parse_args(_Config) ->
- Args =
- [ "--log-dir"
- , "/test"
- , "--log-level"
- , "error"
- ],
- els_dap:parse_args(Args),
- ?assertEqual('error', application:get_env(els_core, log_level, undefined)),
- ok.
+ Args =
+ [
+ "--log-dir",
+ "/test",
+ "--log-level",
+ "error"
+ ],
+ els_dap:parse_args(Args),
+ ?assertEqual('error', application:get_env(els_core, log_level, undefined)),
+ ok.
-spec log_root(config()) -> ok.
log_root(_Config) ->
- meck:new(file, [unstick]),
- meck:expect(file, get_cwd, fun() -> {ok, "/root/els_dap"} end),
-
- Args =
- [ "--log-dir"
- , "/somewhere_else/logs"
- ],
- els_dap:parse_args(Args),
- ?assertEqual("/somewhere_else/logs/els_dap", els_dap:log_root()),
-
- meck:unload(file),
- ok.
+ meck:new(file, [unstick]),
+ meck:expect(file, get_cwd, fun() -> {ok, "/root/els_dap"} end),
+
+ Args =
+ [
+ "--log-dir",
+ "/somewhere_else/logs"
+ ],
+ els_dap:parse_args(Args),
+ ?assertEqual("/somewhere_else/logs/els_dap", els_dap:log_root()),
+
+ meck:unload(file),
+ ok.
diff --git a/apps/els_dap/test/els_dap_general_provider_SUITE.erl b/apps/els_dap/test/els_dap_general_provider_SUITE.erl
index 8c2635533..bf0fb8d6c 100644
--- a/apps/els_dap/test/els_dap_general_provider_SUITE.erl
+++ b/apps/els_dap/test/els_dap_general_provider_SUITE.erl
@@ -1,39 +1,41 @@
-module(els_dap_general_provider_SUITE).
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , groups/0
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ groups/0,
+ all/0
+]).
%% Test cases
--export([ initialize/1
- , launch_mfa/1
- , launch_mfa_with_cookie/1
- , configuration_done/1
- , configuration_done_with_long_names/1
- , configuration_done_with_long_names_using_host/1
- , configuration_done_with_breakpoint/1
- , frame_variables/1
- , navigation_and_frames/1
- , set_variable/1
- , breakpoints/1
- , project_node_exit/1
- , breakpoints_with_cond/1
- , breakpoints_with_hit/1
- , breakpoints_with_cond_and_hit/1
- , log_points/1
- , log_points_with_lt_condition/1
- , log_points_with_eq_condition/1
- , log_points_with_hit/1
- , log_points_with_hit1/1
- , log_points_with_cond_and_hit/1
- , log_points_empty_cond/1
- ]).
+-export([
+ initialize/1,
+ launch_mfa/1,
+ launch_mfa_with_cookie/1,
+ configuration_done/1,
+ configuration_done_with_long_names/1,
+ configuration_done_with_long_names_using_host/1,
+ configuration_done_with_breakpoint/1,
+ frame_variables/1,
+ navigation_and_frames/1,
+ set_variable/1,
+ breakpoints/1,
+ project_node_exit/1,
+ breakpoints_with_cond/1,
+ breakpoints_with_hit/1,
+ breakpoints_with_cond_and_hit/1,
+ log_points/1,
+ log_points_with_lt_condition/1,
+ log_points_with_eq_condition/1,
+ log_points_with_hit/1,
+ log_points_with_hit1/1,
+ log_points_with_cond_and_hit/1,
+ log_points_empty_cond/1
+]).
%%==============================================================================
%% Includes
@@ -51,116 +53,117 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_dap_test_utils:all(?MODULE).
+ els_dap_test_utils:all(?MODULE).
-spec groups() -> [atom()].
groups() ->
- [].
+ [].
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- Config.
+ Config.
-spec end_per_suite(config()) -> ok.
end_per_suite(_Config) ->
- meck:unload().
+ meck:unload().
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(TestCase, Config) when
- TestCase =:= undefined orelse
- TestCase =:= initialize orelse
- TestCase =:= launch_mfa orelse
- TestCase =:= launch_mfa_with_cookie orelse
- TestCase =:= configuration_done orelse
- TestCase =:= configuration_done_with_long_names orelse
- TestCase =:= configuration_done_with_long_names_using_host orelse
- TestCase =:= configuration_done_with_breakpoint orelse
- TestCase =:= log_points orelse
- TestCase =:= log_points_with_lt_condition orelse
- TestCase =:= log_points_with_eq_condition orelse
- TestCase =:= log_points_with_hit orelse
- TestCase =:= log_points_with_hit1 orelse
- TestCase =:= log_points_with_cond_and_hit orelse
- TestCase =:= log_points_empty_cond orelse
- TestCase =:= breakpoints_with_cond orelse
- TestCase =:= breakpoints_with_hit orelse
- TestCase =:= breakpoints_with_cond_and_hit ->
- {ok, DAPProvider} = els_dap_provider:start_link(),
- {ok, _} = els_config:start_link(),
- meck:expect(els_dap_server, send_event, 2, meck:val(ok)),
- [{provider, DAPProvider}, {node, node_name()} | Config];
+ TestCase =:= undefined orelse
+ TestCase =:= initialize orelse
+ TestCase =:= launch_mfa orelse
+ TestCase =:= launch_mfa_with_cookie orelse
+ TestCase =:= configuration_done orelse
+ TestCase =:= configuration_done_with_long_names orelse
+ TestCase =:= configuration_done_with_long_names_using_host orelse
+ TestCase =:= configuration_done_with_breakpoint orelse
+ TestCase =:= log_points orelse
+ TestCase =:= log_points_with_lt_condition orelse
+ TestCase =:= log_points_with_eq_condition orelse
+ TestCase =:= log_points_with_hit orelse
+ TestCase =:= log_points_with_hit1 orelse
+ TestCase =:= log_points_with_cond_and_hit orelse
+ TestCase =:= log_points_empty_cond orelse
+ TestCase =:= breakpoints_with_cond orelse
+ TestCase =:= breakpoints_with_hit orelse
+ TestCase =:= breakpoints_with_cond_and_hit
+->
+ {ok, DAPProvider} = els_dap_provider:start_link(),
+ {ok, _} = els_config:start_link(),
+ meck:expect(els_dap_server, send_event, 2, meck:val(ok)),
+ [{provider, DAPProvider}, {node, node_name()} | Config];
init_per_testcase(_TestCase, Config0) ->
- Config1 = init_per_testcase(undefined, Config0),
- %% initialize dap, equivalent to configuration_done_with_breakpoint
- try configuration_done_with_breakpoint(Config1) of
- ok -> Config1;
- R -> {user_skip, {error, dap_initialization, R}}
- catch
- Class:Reason ->
- {user_skip, {error, dap_initialization, Class, Reason}}
- end.
+ Config1 = init_per_testcase(undefined, Config0),
+ %% initialize dap, equivalent to configuration_done_with_breakpoint
+ try configuration_done_with_breakpoint(Config1) of
+ ok -> Config1;
+ R -> {user_skip, {error, dap_initialization, R}}
+ catch
+ Class:Reason ->
+ {user_skip, {error, dap_initialization, Class, Reason}}
+ end.
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(_TestCase, Config) ->
- NodeName = ?config(node, Config),
- Node = binary_to_atom(NodeName, utf8),
- unset_all_env(els_core),
- ok = gen_server:stop(?config(provider, Config)),
- gen_server:stop(els_config),
- %% kill the project node
- rpc:cast(Node, erlang, halt, []),
- ok.
+ NodeName = ?config(node, Config),
+ Node = binary_to_atom(NodeName, utf8),
+ unset_all_env(els_core),
+ ok = gen_server:stop(?config(provider, Config)),
+ gen_server:stop(els_config),
+ %% kill the project node
+ rpc:cast(Node, erlang, halt, []),
+ ok.
%%==============================================================================
%% Helpers
%%==============================================================================
-spec unset_all_env(atom()) -> ok.
unset_all_env(Application) ->
- Envs = application:get_all_env(Application),
- unset_env(Application, Envs).
+ Envs = application:get_all_env(Application),
+ unset_env(Application, Envs).
-spec unset_env(atom(), list({atom(), term()})) -> ok.
unset_env(_Application, []) ->
- ok;
+ ok;
unset_env(Application, [{Par, _Val} | Rest]) ->
- application:unset_env(Application, Par),
- unset_env(Application, Rest).
+ application:unset_env(Application, Par),
+ unset_env(Application, Rest).
-spec node_name() -> node().
node_name() ->
- unicode:characters_to_binary(
- io_lib:format("~s~p@localhost", [?MODULE, erlang:unique_integer()])
- ).
+ unicode:characters_to_binary(
+ io_lib:format("~s~p@localhost", [?MODULE, erlang:unique_integer()])
+ ).
-spec path_to_test_module(file:name(), module()) -> file:name().
path_to_test_module(AppDir, Module) ->
- unicode:characters_to_binary(
- io_lib:format("~s.erl", [filename:join([AppDir, "src", Module])])
- ).
+ unicode:characters_to_binary(
+ io_lib:format("~s.erl", [filename:join([AppDir, "src", Module])])
+ ).
-spec wait_for_break(binary(), module(), non_neg_integer()) -> boolean().
wait_for_break(NodeName, WantModule, WantLine) ->
- Node = binary_to_atom(NodeName, utf8),
- Checker =
- fun() ->
- Snapshots = rpc:call(Node, int, snapshot, []),
- lists:any(
- fun
- ({_, _, break, {Module, Line}}) when
- Module =:= WantModule andalso Line =:= WantLine
- ->
- true;
- (_) ->
- false
+ Node = binary_to_atom(NodeName, utf8),
+ Checker =
+ fun() ->
+ Snapshots = rpc:call(Node, int, snapshot, []),
+ lists:any(
+ fun
+ ({_, _, break, {Module, Line}}) when
+ Module =:= WantModule andalso Line =:= WantLine
+ ->
+ true;
+ (_) ->
+ false
+ end,
+ Snapshots
+ )
end,
- Snapshots
- )
- end,
- els_dap_test_utils:wait_for_fun(Checker, 200, 20).
+ els_dap_test_utils:wait_for_fun(Checker, 200, 20).
%%==============================================================================
%% Testcases
@@ -168,495 +171,582 @@ wait_for_break(NodeName, WantModule, WantLine) ->
-spec initialize(config()) -> ok.
initialize(_Config) ->
- Provider = els_dap_general_provider,
- els_dap_provider:handle_request(Provider, request_initialize(#{})),
- ok.
+ Provider = els_dap_general_provider,
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
+ ok.
-spec launch_mfa(config()) -> ok.
launch_mfa(Config) ->
- Provider = els_dap_general_provider,
- DataDir = ?config(data_dir, Config),
- Node = ?config(node, Config),
- els_dap_provider:handle_request(Provider, request_initialize(#{})),
- els_dap_provider:handle_request(
- Provider,
- request_launch(DataDir, Node, els_dap_test_module, entry, [])
- ),
- els_test_utils:wait_until_mock_called(els_dap_server, send_event),
- ok.
+ Provider = els_dap_general_provider,
+ DataDir = ?config(data_dir, Config),
+ Node = ?config(node, Config),
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
+ els_dap_provider:handle_request(
+ Provider,
+ request_launch(DataDir, Node, els_dap_test_module, entry, [])
+ ),
+ els_test_utils:wait_until_mock_called(els_dap_server, send_event),
+ ok.
-spec launch_mfa_with_cookie(config()) -> ok.
launch_mfa_with_cookie(Config) ->
- Provider = els_dap_general_provider,
- DataDir = ?config(data_dir, Config),
- Node = ?config(node, Config),
- els_dap_provider:handle_request(Provider, request_initialize(#{})),
- els_dap_provider:handle_request(
- Provider,
- request_launch(DataDir, Node, <<"some_cookie">>,
- els_dap_test_module, entry, [])
- ),
- els_test_utils:wait_until_mock_called(els_dap_server, send_event),
- ok.
+ Provider = els_dap_general_provider,
+ DataDir = ?config(data_dir, Config),
+ Node = ?config(node, Config),
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
+ els_dap_provider:handle_request(
+ Provider,
+ request_launch(
+ DataDir,
+ Node,
+ <<"some_cookie">>,
+ els_dap_test_module,
+ entry,
+ []
+ )
+ ),
+ els_test_utils:wait_until_mock_called(els_dap_server, send_event),
+ ok.
-spec configuration_done(config()) -> ok.
configuration_done(Config) ->
- Provider = els_dap_general_provider,
- DataDir = ?config(data_dir, Config),
- Node = ?config(node, Config),
- els_dap_provider:handle_request(Provider, request_initialize(#{})),
- els_dap_provider:handle_request(
- Provider,
- request_launch(DataDir, Node, els_dap_test_module, entry, [])
- ),
- els_test_utils:wait_until_mock_called(els_dap_server, send_event),
- els_dap_provider:handle_request(Provider, request_configuration_done(#{})),
- ok.
+ Provider = els_dap_general_provider,
+ DataDir = ?config(data_dir, Config),
+ Node = ?config(node, Config),
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
+ els_dap_provider:handle_request(
+ Provider,
+ request_launch(DataDir, Node, els_dap_test_module, entry, [])
+ ),
+ els_test_utils:wait_until_mock_called(els_dap_server, send_event),
+ els_dap_provider:handle_request(Provider, request_configuration_done(#{})),
+ ok.
-spec configuration_done_with_long_names(config()) -> ok.
configuration_done_with_long_names(Config) ->
- Provider = els_dap_general_provider,
- DataDir = ?config(data_dir, Config),
- NodeStr = io_lib:format("~s~p", [?MODULE, erlang:unique_integer()]),
- Node = unicode:characters_to_binary(NodeStr),
- els_dap_provider:handle_request(Provider, request_initialize(#{})),
- els_dap_provider:handle_request(
- Provider,
- request_launch(DataDir, Node, <<"some_cookie">>,
- els_dap_test_module, entry, [], use_long_names)
- ),
- els_test_utils:wait_until_mock_called(els_dap_server, send_event),
- ok.
+ Provider = els_dap_general_provider,
+ DataDir = ?config(data_dir, Config),
+ NodeStr = io_lib:format("~s~p", [?MODULE, erlang:unique_integer()]),
+ Node = unicode:characters_to_binary(NodeStr),
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
+ els_dap_provider:handle_request(
+ Provider,
+ request_launch(
+ DataDir,
+ Node,
+ <<"some_cookie">>,
+ els_dap_test_module,
+ entry,
+ [],
+ use_long_names
+ )
+ ),
+ els_test_utils:wait_until_mock_called(els_dap_server, send_event),
+ ok.
-spec configuration_done_with_long_names_using_host(config()) -> ok.
configuration_done_with_long_names_using_host(Config) ->
- Provider = els_dap_general_provider,
- DataDir = ?config(data_dir, Config),
- Node = ?config(node, Config),
- els_dap_provider:handle_request(Provider, request_initialize(#{})),
- els_dap_provider:handle_request(
- Provider,
- request_launch(DataDir, Node, <<"some_cookie">>,
- els_dap_test_module, entry, [], use_long_names)
- ),
- els_test_utils:wait_until_mock_called(els_dap_server, send_event),
- ok.
+ Provider = els_dap_general_provider,
+ DataDir = ?config(data_dir, Config),
+ Node = ?config(node, Config),
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
+ els_dap_provider:handle_request(
+ Provider,
+ request_launch(
+ DataDir,
+ Node,
+ <<"some_cookie">>,
+ els_dap_test_module,
+ entry,
+ [],
+ use_long_names
+ )
+ ),
+ els_test_utils:wait_until_mock_called(els_dap_server, send_event),
+ ok.
-spec configuration_done_with_breakpoint(config()) -> ok.
configuration_done_with_breakpoint(Config) ->
- Provider = els_dap_general_provider,
- DataDir = ?config(data_dir, Config),
- Node = ?config(node, Config),
- els_dap_provider:handle_request(Provider, request_initialize(#{})),
- els_dap_provider:handle_request(
- Provider,
- request_launch(DataDir, Node, els_dap_test_module, entry, [5])
- ),
- els_test_utils:wait_until_mock_called(els_dap_server, send_event),
-
- els_dap_provider:handle_request(
- Provider,
- request_set_breakpoints( path_to_test_module(DataDir, els_dap_test_module)
- , [9, 29])
- ),
- els_dap_provider:handle_request(Provider, request_configuration_done(#{})),
- ?assertEqual(ok, wait_for_break(Node, els_dap_test_module, 9)),
- ok.
+ Provider = els_dap_general_provider,
+ DataDir = ?config(data_dir, Config),
+ Node = ?config(node, Config),
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
+ els_dap_provider:handle_request(
+ Provider,
+ request_launch(DataDir, Node, els_dap_test_module, entry, [5])
+ ),
+ els_test_utils:wait_until_mock_called(els_dap_server, send_event),
+
+ els_dap_provider:handle_request(
+ Provider,
+ request_set_breakpoints(
+ path_to_test_module(DataDir, els_dap_test_module),
+ [9, 29]
+ )
+ ),
+ els_dap_provider:handle_request(Provider, request_configuration_done(#{})),
+ ?assertEqual(ok, wait_for_break(Node, els_dap_test_module, 9)),
+ ok.
-spec frame_variables(config()) -> ok.
frame_variables(_Config) ->
- Provider = els_dap_general_provider,
- %% get thread ID from mocked DAP response
- #{ <<"reason">> := <<"breakpoint">>
- , <<"threadId">> := ThreadId} =
- meck:capture(last, els_dap_server, send_event, [<<"stopped">>, '_'], 2),
- %% get stackframe
- #{<<"stackFrames">> := [#{<<"id">> := FrameId}]} =
- els_dap_provider:handle_request( Provider
- , request_stack_frames(ThreadId)
- ),
- %% get scope
- #{<<"scopes">> := [#{<<"variablesReference">> := VariableRef}]} =
- els_dap_provider:handle_request(Provider, request_scope(FrameId)),
- %% extract variable
- #{<<"variables">> := [NVar]} =
- els_dap_provider:handle_request(Provider, request_variable(VariableRef)),
- %% at this point there should be only one variable present,
- ?assertMatch(#{ <<"name">> := <<"N">>
- , <<"value">> := <<"5">>
- , <<"variablesReference">> := 0
- }
- , NVar),
- ok.
+ Provider = els_dap_general_provider,
+ %% get thread ID from mocked DAP response
+ #{
+ <<"reason">> := <<"breakpoint">>,
+ <<"threadId">> := ThreadId
+ } =
+ meck:capture(last, els_dap_server, send_event, [<<"stopped">>, '_'], 2),
+ %% get stackframe
+ #{<<"stackFrames">> := [#{<<"id">> := FrameId}]} =
+ els_dap_provider:handle_request(
+ Provider,
+ request_stack_frames(ThreadId)
+ ),
+ %% get scope
+ #{<<"scopes">> := [#{<<"variablesReference">> := VariableRef}]} =
+ els_dap_provider:handle_request(Provider, request_scope(FrameId)),
+ %% extract variable
+ #{<<"variables">> := [NVar]} =
+ els_dap_provider:handle_request(Provider, request_variable(VariableRef)),
+ %% at this point there should be only one variable present,
+ ?assertMatch(
+ #{
+ <<"name">> := <<"N">>,
+ <<"value">> := <<"5">>,
+ <<"variablesReference">> := 0
+ },
+ NVar
+ ),
+ ok.
-spec navigation_and_frames(config()) -> ok.
navigation_and_frames(_Config) ->
- %% test next, stepIn, continue and check against expected stack frames
- Provider = els_dap_general_provider,
- #{<<"threads">> := [#{<<"id">> := ThreadId}]} =
- els_dap_provider:handle_request( Provider
- , request_threads()
- ),
- %% next
- %%, reset meck history, to capture next call
- meck:reset([els_dap_server]),
- els_dap_provider:handle_request(Provider, request_next(ThreadId)),
- els_test_utils:wait_until_mock_called(els_dap_server, send_event),
- %% check
- #{<<"stackFrames">> := Frames1} =
- els_dap_provider:handle_request( Provider
- , request_stack_frames(ThreadId)
- ),
- ?assertMatch([#{ <<"line">> := 11
- , <<"name">> := <<"els_dap_test_module:entry/1">>}], Frames1),
- %% continue
- meck:reset([els_dap_server]),
- els_dap_provider:handle_request(Provider, request_continue(ThreadId)),
- els_test_utils:wait_until_mock_called(els_dap_server, send_event),
- %% check
- #{<<"stackFrames">> := Frames2} =
- els_dap_provider:handle_request( Provider
- , request_stack_frames(ThreadId)
- ),
- ?assertMatch( [ #{ <<"line">> := 9
- , <<"name">> := <<"els_dap_test_module:entry/1">>}
- , #{ <<"line">> := 11
- , <<"name">> := <<"els_dap_test_module:entry/1">>}
- ]
- , Frames2
- ),
- %% stepIn
- meck:reset([els_dap_server]),
- els_dap_provider:handle_request(Provider, request_step_in(ThreadId)),
- els_test_utils:wait_until_mock_called(els_dap_server, send_event),
- %% check
- #{<<"stackFrames">> := Frames3} =
- els_dap_provider:handle_request( Provider
- , request_stack_frames(ThreadId)
- ),
- ?assertMatch( [ #{ <<"line">> := 15
- , <<"name">> := <<"els_dap_test_module:ds/0">>
- },
- #{ <<"line">> := 9
- , <<"name">> := <<"els_dap_test_module:entry/1">>},
- #{ <<"line">> := 11
- , <<"name">> := <<"els_dap_test_module:entry/1">>}
- ]
- , Frames3
- ),
- ok.
+ %% test next, stepIn, continue and check against expected stack frames
+ Provider = els_dap_general_provider,
+ #{<<"threads">> := [#{<<"id">> := ThreadId}]} =
+ els_dap_provider:handle_request(
+ Provider,
+ request_threads()
+ ),
+ %% next
+ %%, reset meck history, to capture next call
+ meck:reset([els_dap_server]),
+ els_dap_provider:handle_request(Provider, request_next(ThreadId)),
+ els_test_utils:wait_until_mock_called(els_dap_server, send_event),
+ %% check
+ #{<<"stackFrames">> := Frames1} =
+ els_dap_provider:handle_request(
+ Provider,
+ request_stack_frames(ThreadId)
+ ),
+ ?assertMatch(
+ [
+ #{
+ <<"line">> := 11,
+ <<"name">> := <<"els_dap_test_module:entry/1">>
+ }
+ ],
+ Frames1
+ ),
+ %% continue
+ meck:reset([els_dap_server]),
+ els_dap_provider:handle_request(Provider, request_continue(ThreadId)),
+ els_test_utils:wait_until_mock_called(els_dap_server, send_event),
+ %% check
+ #{<<"stackFrames">> := Frames2} =
+ els_dap_provider:handle_request(
+ Provider,
+ request_stack_frames(ThreadId)
+ ),
+ ?assertMatch(
+ [
+ #{
+ <<"line">> := 9,
+ <<"name">> := <<"els_dap_test_module:entry/1">>
+ },
+ #{
+ <<"line">> := 11,
+ <<"name">> := <<"els_dap_test_module:entry/1">>
+ }
+ ],
+ Frames2
+ ),
+ %% stepIn
+ meck:reset([els_dap_server]),
+ els_dap_provider:handle_request(Provider, request_step_in(ThreadId)),
+ els_test_utils:wait_until_mock_called(els_dap_server, send_event),
+ %% check
+ #{<<"stackFrames">> := Frames3} =
+ els_dap_provider:handle_request(
+ Provider,
+ request_stack_frames(ThreadId)
+ ),
+ ?assertMatch(
+ [
+ #{
+ <<"line">> := 15,
+ <<"name">> := <<"els_dap_test_module:ds/0">>
+ },
+ #{
+ <<"line">> := 9,
+ <<"name">> := <<"els_dap_test_module:entry/1">>
+ },
+ #{
+ <<"line">> := 11,
+ <<"name">> := <<"els_dap_test_module:entry/1">>
+ }
+ ],
+ Frames3
+ ),
+ ok.
-spec set_variable(config()) -> ok.
set_variable(_Config) ->
- Provider = els_dap_general_provider,
- #{<<"threads">> := [#{<<"id">> := ThreadId}]} =
- els_dap_provider:handle_request( Provider
- , request_threads()
- ),
- #{<<"stackFrames">> := [#{<<"id">> := FrameId1}]} =
- els_dap_provider:handle_request( Provider
- , request_stack_frames(ThreadId)
- ),
- meck:reset([els_dap_server]),
- Result1 =
- els_dap_provider:handle_request( Provider
- , request_evaluate( <<"repl">>
- , FrameId1
- , <<"N=1">>
- )
- ),
- ?assertEqual(#{<<"result">> => <<"1">>}, Result1),
-
- %% get variable value through hover evaluate
- els_test_utils:wait_until_mock_called(els_dap_server, send_event),
- #{<<"stackFrames">> := [#{<<"id">> := FrameId2}]} =
- els_dap_provider:handle_request( Provider
- , request_stack_frames(ThreadId)
- ),
- ?assertNotEqual(FrameId1, FrameId2),
- Result2 =
- els_dap_provider:handle_request( Provider
- , request_evaluate( <<"hover">>
- , FrameId2
- , <<"N">>
- )
- ),
- ?assertEqual(#{<<"result">> => <<"1">>}, Result2),
- %% get variable value through scopes
- #{ <<"scopes">> := [ #{<<"variablesReference">> := VariableRef} ] } =
- els_dap_provider:handle_request(Provider, request_scope(FrameId2)),
- %% extract variable
- #{<<"variables">> := [NVar]} =
- els_dap_provider:handle_request( Provider
- , request_variable(VariableRef)
- ),
- %% at this point there should be only one variable present
- ?assertMatch( #{ <<"name">> := <<"N">>
- , <<"value">> := <<"1">>
- , <<"variablesReference">> := 0
- }
- , NVar
- ),
+ Provider = els_dap_general_provider,
+ #{<<"threads">> := [#{<<"id">> := ThreadId}]} =
+ els_dap_provider:handle_request(
+ Provider,
+ request_threads()
+ ),
+ #{<<"stackFrames">> := [#{<<"id">> := FrameId1}]} =
+ els_dap_provider:handle_request(
+ Provider,
+ request_stack_frames(ThreadId)
+ ),
+ meck:reset([els_dap_server]),
+ Result1 =
+ els_dap_provider:handle_request(
+ Provider,
+ request_evaluate(
+ <<"repl">>,
+ FrameId1,
+ <<"N=1">>
+ )
+ ),
+ ?assertEqual(#{<<"result">> => <<"1">>}, Result1),
+
+ %% get variable value through hover evaluate
+ els_test_utils:wait_until_mock_called(els_dap_server, send_event),
+ #{<<"stackFrames">> := [#{<<"id">> := FrameId2}]} =
+ els_dap_provider:handle_request(
+ Provider,
+ request_stack_frames(ThreadId)
+ ),
+ ?assertNotEqual(FrameId1, FrameId2),
+ Result2 =
+ els_dap_provider:handle_request(
+ Provider,
+ request_evaluate(
+ <<"hover">>,
+ FrameId2,
+ <<"N">>
+ )
+ ),
+ ?assertEqual(#{<<"result">> => <<"1">>}, Result2),
+ %% get variable value through scopes
+ #{<<"scopes">> := [#{<<"variablesReference">> := VariableRef}]} =
+ els_dap_provider:handle_request(Provider, request_scope(FrameId2)),
+ %% extract variable
+ #{<<"variables">> := [NVar]} =
+ els_dap_provider:handle_request(
+ Provider,
+ request_variable(VariableRef)
+ ),
+ %% at this point there should be only one variable present
+ ?assertMatch(
+ #{
+ <<"name">> := <<"N">>,
+ <<"value">> := <<"1">>,
+ <<"variablesReference">> := 0
+ },
+ NVar
+ ),
ok.
-spec breakpoints(config()) -> ok.
breakpoints(Config) ->
- Provider = els_dap_general_provider,
- NodeName = ?config(node, Config),
- Node = binary_to_atom(NodeName, utf8),
- DataDir = ?config(data_dir, Config),
- els_dap_provider:handle_request(
- Provider,
- request_set_breakpoints( path_to_test_module(DataDir, els_dap_test_module)
- , [9])
- ),
- ?assertMatch([{{els_dap_test_module, 9}, _}], els_dap_rpc:all_breaks(Node)),
- els_dap_provider:handle_request(
- Provider,
- request_set_function_breakpoints([<<"els_dap_test_module:entry/1">>])
- ),
- ?assertMatch(
- [{{els_dap_test_module, 7}, _}, {{els_dap_test_module, 9}, _}],
- els_dap_rpc:all_breaks(Node)
- ),
- els_dap_provider:handle_request(
- Provider,
- request_set_breakpoints(path_to_test_module(DataDir, els_dap_test_module)
- , [])
- ),
- ?assertMatch(
- [{{els_dap_test_module, 7}, _}, {{els_dap_test_module, 9}, _}],
- els_dap_rpc:all_breaks(Node)
- ),
- els_dap_provider:handle_request(
- Provider,
- request_set_breakpoints(path_to_test_module(DataDir, els_dap_test_module)
- , [9])
- ),
- els_dap_provider:handle_request(
- Provider,
- request_set_function_breakpoints([])
- ),
- ?assertMatch([{{els_dap_test_module, 9}, _}], els_dap_rpc:all_breaks(Node)),
- ok.
+ Provider = els_dap_general_provider,
+ NodeName = ?config(node, Config),
+ Node = binary_to_atom(NodeName, utf8),
+ DataDir = ?config(data_dir, Config),
+ els_dap_provider:handle_request(
+ Provider,
+ request_set_breakpoints(
+ path_to_test_module(DataDir, els_dap_test_module),
+ [9]
+ )
+ ),
+ ?assertMatch([{{els_dap_test_module, 9}, _}], els_dap_rpc:all_breaks(Node)),
+ els_dap_provider:handle_request(
+ Provider,
+ request_set_function_breakpoints([<<"els_dap_test_module:entry/1">>])
+ ),
+ ?assertMatch(
+ [{{els_dap_test_module, 7}, _}, {{els_dap_test_module, 9}, _}],
+ els_dap_rpc:all_breaks(Node)
+ ),
+ els_dap_provider:handle_request(
+ Provider,
+ request_set_breakpoints(
+ path_to_test_module(DataDir, els_dap_test_module),
+ []
+ )
+ ),
+ ?assertMatch(
+ [{{els_dap_test_module, 7}, _}, {{els_dap_test_module, 9}, _}],
+ els_dap_rpc:all_breaks(Node)
+ ),
+ els_dap_provider:handle_request(
+ Provider,
+ request_set_breakpoints(
+ path_to_test_module(DataDir, els_dap_test_module),
+ [9]
+ )
+ ),
+ els_dap_provider:handle_request(
+ Provider,
+ request_set_function_breakpoints([])
+ ),
+ ?assertMatch([{{els_dap_test_module, 9}, _}], els_dap_rpc:all_breaks(Node)),
+ ok.
-spec project_node_exit(config()) -> ok.
project_node_exit(Config) ->
- NodeName = ?config(node, Config),
- Node = binary_to_atom(NodeName, utf8),
- meck:expect(els_utils, halt, 1, meck:val(ok)),
- meck:reset(els_dap_server),
- erlang:monitor_node(Node, true),
- %% kill node and wait for nodedown message
- rpc:cast(Node, erlang, halt, []),
- receive
- {nodedown, Node} -> ok
- end,
- %% wait until els_utils:halt has been called
- els_test_utils:wait_until_mock_called(els_utils, halt).
- %% there is a race condition in CI, important is that the process stops
- % ?assert(meck:called(els_dap_server, send_event, [<<"terminated">>, '_'])),
- % ?assert(meck:called(els_dap_server, send_event, [<<"exited">>, '_'])).
+ NodeName = ?config(node, Config),
+ Node = binary_to_atom(NodeName, utf8),
+ meck:expect(els_utils, halt, 1, meck:val(ok)),
+ meck:reset(els_dap_server),
+ erlang:monitor_node(Node, true),
+ %% kill node and wait for nodedown message
+ rpc:cast(Node, erlang, halt, []),
+ receive
+ {nodedown, Node} -> ok
+ end,
+ %% wait until els_utils:halt has been called
+ els_test_utils:wait_until_mock_called(els_utils, halt).
+%% there is a race condition in CI, important is that the process stops
+% ?assert(meck:called(els_dap_server, send_event, [<<"terminated">>, '_'])),
+% ?assert(meck:called(els_dap_server, send_event, [<<"exited">>, '_'])).
-spec breakpoints_with_cond(config()) -> ok.
breakpoints_with_cond(Config) ->
- breakpoints_base(Config, 9, #{condition => <<"N =:= 5">>}, <<"5">>).
+ breakpoints_base(Config, 9, #{condition => <<"N =:= 5">>}, <<"5">>).
-spec breakpoints_with_hit(config()) -> ok.
breakpoints_with_hit(Config) ->
- breakpoints_base(Config, 9, #{hitcond => <<"3">>}, <<"8">>).
+ breakpoints_base(Config, 9, #{hitcond => <<"3">>}, <<"8">>).
-spec breakpoints_with_cond_and_hit(config()) -> ok.
breakpoints_with_cond_and_hit(Config) ->
- Params = #{condition => <<"N < 7">>, hitcond => <<"3">>},
- breakpoints_base(Config, 9, Params, <<"4">>).
+ Params = #{condition => <<"N < 7">>, hitcond => <<"3">>},
+ breakpoints_base(Config, 9, Params, <<"4">>).
%% Parameterizable base test for breakpoints: sets up a breakpoint with given
%% parameters and checks the value of N when first hit
breakpoints_base(Config, BreakLine, Params, NExp) ->
- Provider = els_dap_general_provider,
- DataDir = ?config(data_dir, Config),
- Node = ?config(node, Config),
- els_dap_provider:handle_request(Provider, request_initialize(#{})),
- els_dap_provider:handle_request(
- Provider,
- request_launch(DataDir, Node, els_dap_test_module, entry, [10])
- ),
- els_test_utils:wait_until_mock_called(els_dap_server, send_event),
- meck:reset([els_dap_server]),
-
- els_dap_provider:handle_request(
- Provider,
- request_set_breakpoints(
- path_to_test_module(DataDir, els_dap_test_module),
- [{BreakLine, Params}]
- )
- ),
- %% hit breakpoint
- els_dap_provider:handle_request(Provider, request_configuration_done(#{})),
- ?assertEqual(ok, wait_for_break(Node, els_dap_test_module, BreakLine)),
- %% check value of N
- #{<<"threads">> := [#{<<"id">> := ThreadId}]} =
- els_dap_provider:handle_request( Provider
- , request_threads()
- ),
- #{<<"stackFrames">> := [#{<<"id">> := FrameId}|_]} =
- els_dap_provider:handle_request( Provider
- , request_stack_frames(ThreadId)
- ),
- #{<<"scopes">> := [#{<<"variablesReference">> := VariableRef}]} =
- els_dap_provider:handle_request(Provider, request_scope(FrameId)),
- #{<<"variables">> := [NVar]} =
- els_dap_provider:handle_request(Provider, request_variable(VariableRef)),
- ?assertMatch(#{ <<"name">> := <<"N">>
- , <<"value">> := NExp
- , <<"variablesReference">> := 0
- }
- , NVar),
- ok.
+ Provider = els_dap_general_provider,
+ DataDir = ?config(data_dir, Config),
+ Node = ?config(node, Config),
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
+ els_dap_provider:handle_request(
+ Provider,
+ request_launch(DataDir, Node, els_dap_test_module, entry, [10])
+ ),
+ els_test_utils:wait_until_mock_called(els_dap_server, send_event),
+ meck:reset([els_dap_server]),
+
+ els_dap_provider:handle_request(
+ Provider,
+ request_set_breakpoints(
+ path_to_test_module(DataDir, els_dap_test_module),
+ [{BreakLine, Params}]
+ )
+ ),
+ %% hit breakpoint
+ els_dap_provider:handle_request(Provider, request_configuration_done(#{})),
+ ?assertEqual(ok, wait_for_break(Node, els_dap_test_module, BreakLine)),
+ %% check value of N
+ #{<<"threads">> := [#{<<"id">> := ThreadId}]} =
+ els_dap_provider:handle_request(
+ Provider,
+ request_threads()
+ ),
+ #{<<"stackFrames">> := [#{<<"id">> := FrameId} | _]} =
+ els_dap_provider:handle_request(
+ Provider,
+ request_stack_frames(ThreadId)
+ ),
+ #{<<"scopes">> := [#{<<"variablesReference">> := VariableRef}]} =
+ els_dap_provider:handle_request(Provider, request_scope(FrameId)),
+ #{<<"variables">> := [NVar]} =
+ els_dap_provider:handle_request(Provider, request_variable(VariableRef)),
+ ?assertMatch(
+ #{
+ <<"name">> := <<"N">>,
+ <<"value">> := NExp,
+ <<"variablesReference">> := 0
+ },
+ NVar
+ ),
+ ok.
-spec log_points(config()) -> ok.
log_points(Config) ->
- log_points_base(Config, 9, #{log => <<"N">>}, 11, 1).
+ log_points_base(Config, 9, #{log => <<"N">>}, 11, 1).
-spec log_points_with_lt_condition(config()) -> ok.
log_points_with_lt_condition(Config) ->
- log_points_base(Config, 9, #{log => <<"N">>, condition => <<"N < 5">>}, 7, 4).
+ log_points_base(Config, 9, #{log => <<"N">>, condition => <<"N < 5">>}, 7, 4).
-spec log_points_with_eq_condition(config()) -> ok.
log_points_with_eq_condition(Config) ->
- Params = #{log => <<"N">>, condition => <<"N =:= 5">>},
- log_points_base(Config, 9, Params, 7, 1).
+ Params = #{log => <<"N">>, condition => <<"N =:= 5">>},
+ log_points_base(Config, 9, Params, 7, 1).
-spec log_points_with_hit(config()) -> ok.
log_points_with_hit(Config) ->
- log_points_base(Config, 9, #{log => <<"N">>, hitcond => <<"3">>}, 7, 3).
+ log_points_base(Config, 9, #{log => <<"N">>, hitcond => <<"3">>}, 7, 3).
-spec log_points_with_hit1(config()) -> ok.
log_points_with_hit1(Config) ->
- log_points_base(Config, 9, #{log => <<"N">>, hitcond => <<"1">>}, 7, 10).
+ log_points_base(Config, 9, #{log => <<"N">>, hitcond => <<"1">>}, 7, 10).
-spec log_points_with_cond_and_hit(config()) -> ok.
log_points_with_cond_and_hit(Config) ->
- Params = #{log => <<"N">>, condition => <<"N < 5">>, hitcond => <<"2">>},
- log_points_base(Config, 9, Params, 7, 2).
+ Params = #{log => <<"N">>, condition => <<"N < 5">>, hitcond => <<"2">>},
+ log_points_base(Config, 9, Params, 7, 2).
-spec log_points_empty_cond(config()) -> ok.
log_points_empty_cond(Config) ->
- log_points_base(Config, 9, #{log => <<"N">>, condition => <<>>}, 11, 1).
+ log_points_base(Config, 9, #{log => <<"N">>, condition => <<>>}, 11, 1).
%% Parameterizable base test for logpoints: sets up a logpoint with given
%% parameters and checks how many hits it gets before hitting a given breakpoint
log_points_base(Config, LogLine, Params, BreakLine, NumCalls) ->
- Provider = els_dap_general_provider,
- DataDir = ?config(data_dir, Config),
- Node = ?config(node, Config),
- els_dap_provider:handle_request(Provider, request_initialize(#{})),
- els_dap_provider:handle_request(
- Provider,
- request_launch(DataDir, Node, els_dap_test_module, entry, [10])
- ),
- els_test_utils:wait_until_mock_called(els_dap_server, send_event),
- meck:reset([els_dap_server]),
-
- els_dap_provider:handle_request(
- Provider,
- request_set_breakpoints(
- path_to_test_module(DataDir, els_dap_test_module),
- [{LogLine, Params}, BreakLine]
- )
- ),
- els_dap_provider:handle_request(Provider, request_configuration_done(#{})),
- ?assertEqual(ok, wait_for_break(Node, els_dap_test_module, BreakLine)),
- ?assertEqual(NumCalls,
- meck:num_calls(els_dap_server, send_event, [<<"output">>, '_'])),
- ok.
+ Provider = els_dap_general_provider,
+ DataDir = ?config(data_dir, Config),
+ Node = ?config(node, Config),
+ els_dap_provider:handle_request(Provider, request_initialize(#{})),
+ els_dap_provider:handle_request(
+ Provider,
+ request_launch(DataDir, Node, els_dap_test_module, entry, [10])
+ ),
+ els_test_utils:wait_until_mock_called(els_dap_server, send_event),
+ meck:reset([els_dap_server]),
+
+ els_dap_provider:handle_request(
+ Provider,
+ request_set_breakpoints(
+ path_to_test_module(DataDir, els_dap_test_module),
+ [{LogLine, Params}, BreakLine]
+ )
+ ),
+ els_dap_provider:handle_request(Provider, request_configuration_done(#{})),
+ ?assertEqual(ok, wait_for_break(Node, els_dap_test_module, BreakLine)),
+ ?assertEqual(
+ NumCalls,
+ meck:num_calls(els_dap_server, send_event, [<<"output">>, '_'])
+ ),
+ ok.
%%==============================================================================
%% Requests
%%==============================================================================
request_initialize(Params) ->
- {<<"initialize">>, Params}.
+ {<<"initialize">>, Params}.
request_launch(Params) ->
- {<<"launch">>, Params}.
+ {<<"launch">>, Params}.
request_launch(AppDir, Node, M, F, A) ->
- request_launch(
- #{ <<"projectnode">> => Node
- , <<"cwd">> => list_to_binary(AppDir)
- , <<"module">> => atom_to_binary(M, utf8)
- , <<"function">> => atom_to_binary(F, utf8)
- , <<"args">> => unicode:characters_to_binary(io_lib:format("~w", [A]))
- }).
+ request_launch(
+ #{
+ <<"projectnode">> => Node,
+ <<"cwd">> => list_to_binary(AppDir),
+ <<"module">> => atom_to_binary(M, utf8),
+ <<"function">> => atom_to_binary(F, utf8),
+ <<"args">> => unicode:characters_to_binary(io_lib:format("~w", [A]))
+ }
+ ).
request_launch(AppDir, Node, Cookie, M, F, A) ->
- {<<"launch">>, Params} = request_launch(AppDir, Node, M, F, A),
- {<<"launch">>, Params#{<<"cookie">> => Cookie}}.
+ {<<"launch">>, Params} = request_launch(AppDir, Node, M, F, A),
+ {<<"launch">>, Params#{<<"cookie">> => Cookie}}.
request_launch(AppDir, Node, Cookie, M, F, A, use_long_names) ->
{<<"launch">>, Params} = request_launch(AppDir, Node, M, F, A),
- {<<"launch">>, Params#{<<"cookie">> => Cookie,
- <<"use_long_names">> => true}}.
+ {<<"launch">>, Params#{
+ <<"cookie">> => Cookie,
+ <<"use_long_names">> => true
+ }}.
request_configuration_done(Params) ->
- {<<"configurationDone">>, Params}.
+ {<<"configurationDone">>, Params}.
request_set_breakpoints(File, Specs) ->
- { <<"setBreakpoints">>
- , #{ <<"source">> => #{<<"path">> => File}
- , <<"sourceModified">> => false
- , <<"breakpoints">> => lists:map(fun map_spec/1, Specs)
- }}.
+ {<<"setBreakpoints">>, #{
+ <<"source">> => #{<<"path">> => File},
+ <<"sourceModified">> => false,
+ <<"breakpoints">> => lists:map(fun map_spec/1, Specs)
+ }}.
map_spec({Line, Params}) ->
- Cond = case Params of
- #{condition := CondExpr} -> #{<<"condition">> => CondExpr};
- _ -> #{}
- end,
- Hit = case Params of
- #{hitcond := HitExpr} -> #{<<"hitCondition">> => HitExpr};
- _ -> #{}
- end,
- Log = case Params of
- #{log := LogMsg} -> #{<<"logMessage">> => LogMsg};
- _ -> #{}
- end,
- lists:foldl(fun maps:merge/2, #{<<"line">> => Line}, [Cond, Hit, Log]);
-map_spec(Line) -> #{<<"line">> => Line}.
+ Cond =
+ case Params of
+ #{condition := CondExpr} -> #{<<"condition">> => CondExpr};
+ _ -> #{}
+ end,
+ Hit =
+ case Params of
+ #{hitcond := HitExpr} -> #{<<"hitCondition">> => HitExpr};
+ _ -> #{}
+ end,
+ Log =
+ case Params of
+ #{log := LogMsg} -> #{<<"logMessage">> => LogMsg};
+ _ -> #{}
+ end,
+ lists:foldl(fun maps:merge/2, #{<<"line">> => Line}, [Cond, Hit, Log]);
+map_spec(Line) ->
+ #{<<"line">> => Line}.
request_set_function_breakpoints(MFAs) ->
- {<<"setFunctionBreakpoints">>, #{
- <<"breakpoints">> => [#{ <<"name">> => MFA
- , <<"enabled">> => true} || MFA <- MFAs]
- }}.
+ {<<"setFunctionBreakpoints">>, #{
+ <<"breakpoints">> => [
+ #{
+ <<"name">> => MFA,
+ <<"enabled">> => true
+ }
+ || MFA <- MFAs
+ ]
+ }}.
request_stack_frames(ThreadId) ->
- {<<"stackTrace">>, #{<<"threadId">> => ThreadId}}.
+ {<<"stackTrace">>, #{<<"threadId">> => ThreadId}}.
request_scope(FrameId) ->
- {<<"scopes">>, #{<<"frameId">> => FrameId}}.
+ {<<"scopes">>, #{<<"frameId">> => FrameId}}.
request_variable(Ref) ->
- {<<"variables">>, #{<<"variablesReference">> => Ref}}.
+ {<<"variables">>, #{<<"variablesReference">> => Ref}}.
request_threads() ->
- {<<"threads">>, #{}}.
+ {<<"threads">>, #{}}.
request_step_in(ThreadId) ->
- {<<"stepIn">>, #{<<"threadId">> => ThreadId}}.
+ {<<"stepIn">>, #{<<"threadId">> => ThreadId}}.
request_next(ThreadId) ->
- {<<"next">>, #{<<"threadId">> => ThreadId}}.
+ {<<"next">>, #{<<"threadId">> => ThreadId}}.
request_continue(ThreadId) ->
- {<<"continue">>, #{<<"threadId">> => ThreadId}}.
+ {<<"continue">>, #{<<"threadId">> => ThreadId}}.
request_evaluate(Context, FrameId, Expression) ->
- {<<"evaluate">>,
- #{ <<"context">> => Context
- , <<"frameId">> => FrameId
- , <<"expression">> => Expression
- }
- }.
+ {<<"evaluate">>, #{
+ <<"context">> => Context,
+ <<"frameId">> => FrameId,
+ <<"expression">> => Expression
+ }}.
diff --git a/apps/els_dap/test/els_dap_test_utils.erl b/apps/els_dap/test/els_dap_test_utils.erl
index 17f80ac77..14a14ba51 100644
--- a/apps/els_dap/test/els_dap_test_utils.erl
+++ b/apps/els_dap/test/els_dap_test_utils.erl
@@ -1,14 +1,15 @@
-module(els_dap_test_utils).
--export([ all/1
- , all/2
- , end_per_suite/1
- , end_per_testcase/2
- , init_per_suite/1
- , init_per_testcase/2
- , wait_for/2
- , wait_for_fun/3
- ]).
+-export([
+ all/1,
+ all/2,
+ end_per_suite/1,
+ end_per_testcase/2,
+ init_per_suite/1,
+ init_per_testcase/2,
+ wait_for/2,
+ wait_for_fun/3
+]).
-include_lib("common_test/include/ct.hrl").
@@ -31,61 +32,68 @@ all(Module) -> all(Module, []).
-spec all(module(), [atom()]) -> [atom()].
all(Module, Functions) ->
- ExcludedFuns = [init_per_suite, end_per_suite, all, module_info | Functions],
- Exports = Module:module_info(exports),
- [F || {F, 1} <- Exports, not lists:member(F, ExcludedFuns)].
+ ExcludedFuns = [init_per_suite, end_per_suite, all, module_info | Functions],
+ Exports = Module:module_info(exports),
+ [F || {F, 1} <- Exports, not lists:member(F, ExcludedFuns)].
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- PrivDir = code:priv_dir(els_dap),
- RootPath = filename:join([ els_utils:to_binary(PrivDir)
- , ?TEST_APP]),
- RootUri = els_uri:uri(RootPath),
- application:load(els_core),
- [ {root_uri, RootUri}
- , {root_path, RootPath}
- | Config ].
+ PrivDir = code:priv_dir(els_dap),
+ RootPath = filename:join([
+ els_utils:to_binary(PrivDir),
+ ?TEST_APP
+ ]),
+ RootUri = els_uri:uri(RootPath),
+ application:load(els_core),
+ [
+ {root_uri, RootUri},
+ {root_path, RootPath}
+ | Config
+ ].
-spec end_per_suite(config()) -> ok.
end_per_suite(_Config) ->
- ok.
+ ok.
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(_TestCase, Config) ->
- meck:new(els_distribution_server, [no_link, passthrough]),
- meck:expect(els_distribution_server, connect, 0, ok),
- Started = els_test_utils:start(),
- RootUri = ?config(root_uri, Config),
- els_client:initialize(RootUri, #{indexingEnabled => false}),
- els_client:initialized(),
-[ {started, Started}
- | Config].
+ meck:new(els_distribution_server, [no_link, passthrough]),
+ meck:expect(els_distribution_server, connect, 0, ok),
+ Started = els_test_utils:start(),
+ RootUri = ?config(root_uri, Config),
+ els_client:initialize(RootUri, #{indexingEnabled => false}),
+ els_client:initialized(),
+ [
+ {started, Started}
+ | Config
+ ].
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(_TestCase, Config) ->
- meck:unload(els_distribution_server),
- [application:stop(App) || App <- ?config(started, Config)],
- ok.
+ meck:unload(els_distribution_server),
+ [application:stop(App) || App <- ?config(started, Config)],
+ ok.
-spec wait_for(any(), non_neg_integer()) -> ok.
wait_for(_Message, Timeout) when Timeout =< 0 ->
- timeout;
+ timeout;
wait_for(Message, Timeout) ->
- receive Message -> ok
- after 10 -> wait_for(Message, Timeout - 10)
- end.
+ receive
+ Message -> ok
+ after 10 -> wait_for(Message, Timeout - 10)
+ end.
-spec wait_for_fun(term(), non_neg_integer(), non_neg_integer()) ->
- {ok, any()} | ok | timeout.
+ {ok, any()} | ok | timeout.
wait_for_fun(_CheckFun, _WaitTime, 0) ->
- timeout;
+ timeout;
wait_for_fun(CheckFun, WaitTime, Retries) ->
- case CheckFun() of
- true ->
- ok;
- {true, Value} ->
- {ok, Value};
- false ->
- timer:sleep(WaitTime),
- wait_for_fun(CheckFun, WaitTime, Retries - 1)
- end.
+ case CheckFun() of
+ true ->
+ ok;
+ {true, Value} ->
+ {ok, Value};
+ false ->
+ timer:sleep(WaitTime),
+ wait_for_fun(CheckFun, WaitTime, Retries - 1)
+ end.
diff --git a/apps/els_lsp/include/els_lsp.hrl b/apps/els_lsp/include/els_lsp.hrl
index b99d27981..cc3e3b7b0 100644
--- a/apps/els_lsp/include/els_lsp.hrl
+++ b/apps/els_lsp/include/els_lsp.hrl
@@ -5,6 +5,8 @@
-define(APP, els_lsp).
--define(LSP_LOG_FORMAT, ["[", time, "] ", "[", level, "] ", msg, " [", mfa, " L", line, "] ", pid, "\n"]).
+-define(LSP_LOG_FORMAT, [
+ "[", time, "] ", "[", level, "] ", msg, " [", mfa, " L", line, "] ", pid, "\n"
+]).
-endif.
diff --git a/apps/els_lsp/src/edoc_report.erl b/apps/els_lsp/src/edoc_report.erl
index f6c4dfac6..6963a8512 100644
--- a/apps/els_lsp/src/edoc_report.erl
+++ b/apps/els_lsp/src/edoc_report.erl
@@ -38,16 +38,18 @@
-module(edoc_report).
-compile({no_auto_import, [error/1, error/2, error/3]}).
--export([error/1,
- error/2,
- error/3,
- report/2,
- report/3,
- report/4,
- warning/1,
- warning/2,
- warning/3,
- warning/4]).
+-export([
+ error/1,
+ error/2,
+ error/3,
+ report/2,
+ report/3,
+ report/4,
+ warning/1,
+ warning/2,
+ warning/3,
+ warning/4
+]).
-type where() :: any().
-type what() :: any().
@@ -59,72 +61,74 @@
-spec error(what()) -> ok.
error(What) ->
- error([], What).
+ error([], What).
-spec error(where(), what()) -> ok.
error(Where, What) ->
- error(0, Where, What).
+ error(0, Where, What).
-spec error(line(), where(), any()) -> ok.
error(Line, Where, S) when is_list(S) ->
- report(Line, Where, S, [], error);
+ report(Line, Where, S, [], error);
error(Line, Where, {S, D}) when is_list(S) ->
- report(Line, Where, S, D, error);
+ report(Line, Where, S, D, error);
error(Line, Where, {format_error, M, D}) ->
- report(Line, Where, M:format_error(D), [], error).
+ report(Line, Where, M:format_error(D), [], error).
-spec warning(string()) -> ok.
warning(S) ->
- warning(S, []).
+ warning(S, []).
-spec warning(string(), [any()]) -> ok.
warning(S, Vs) ->
- warning([], S, Vs).
+ warning([], S, Vs).
-spec warning(where(), string(), [any()]) -> ok.
warning(Where, S, Vs) ->
- warning(0, Where, S, Vs).
+ warning(0, Where, S, Vs).
-spec warning(line(), where(), string(), [any()]) -> ok.
warning(L, Where, "tag @~s not recognized." = S, [Tag] = Vs) ->
- CustomTags = els_config:get(edoc_custom_tags),
- case lists:member(atom_to_list(Tag), CustomTags) of
- true ->
- ok;
- false ->
- report(L, Where, S, Vs, warning)
- end;
+ CustomTags = els_config:get(edoc_custom_tags),
+ case lists:member(atom_to_list(Tag), CustomTags) of
+ true ->
+ ok;
+ false ->
+ report(L, Where, S, Vs, warning)
+ end;
warning(L, Where, S, Vs) ->
- report(L, Where, S, Vs, warning).
+ report(L, Where, S, Vs, warning).
-spec report(string(), [any()]) -> ok.
report(S, Vs) ->
- report([], S, Vs).
+ report([], S, Vs).
-spec report(where(), string(), [any()]) -> ok.
report(Where, S, Vs) ->
- report(0, Where, S, Vs).
+ report(0, Where, S, Vs).
-spec report(line(), where(), string(), [any()]) -> ok.
report(L, Where, S, Vs) ->
- report(L, Where, S, Vs, error).
+ report(L, Where, S, Vs, error).
-spec report(line(), where(), string(), [any()], severity()) -> ok.
report(L, Where, S, Vs, Severity) ->
- put(?DICT_KEY, [{L, where(Where), S, Vs, Severity}|get(?DICT_KEY)]).
+ put(?DICT_KEY, [{L, where(Where), S, Vs, Severity} | get(?DICT_KEY)]).
--spec where([any()] |
- {string(), module | footer | header | {atom(), non_neg_integer()}})
- -> string().
+-spec where(
+ [any()]
+ | {string(), module | footer | header | {atom(), non_neg_integer()}}
+) ->
+ string().
where({File, module}) ->
- io_lib:fwrite("~ts, in module header: ", [File]);
+ io_lib:fwrite("~ts, in module header: ", [File]);
where({File, footer}) ->
- io_lib:fwrite("~ts, in module footer: ", [File]);
+ io_lib:fwrite("~ts, in module footer: ", [File]);
where({File, header}) ->
- io_lib:fwrite("~ts, in header file: ", [File]);
+ io_lib:fwrite("~ts, in header file: ", [File]);
where({File, {F, A}}) ->
- io_lib:fwrite("~ts, function ~ts/~w: ", [File, F, A]);
+ io_lib:fwrite("~ts, function ~ts/~w: ", [File, F, A]);
where([]) ->
- io_lib:fwrite("~s: ", [?APPLICATION]);
+ io_lib:fwrite("~s: ", [?APPLICATION]);
where(File) when is_list(File) ->
- File ++ ": ".
+ File ++ ": ".
diff --git a/apps/els_lsp/src/els_app.erl b/apps/els_lsp/src/els_app.erl
index a8b66be29..054863c39 100644
--- a/apps/els_lsp/src/els_app.erl
+++ b/apps/els_lsp/src/els_app.erl
@@ -12,18 +12,19 @@
%% Exports
%%==============================================================================
%% Application Callbacks
--export([ start/2
- , stop/1
- ]).
+-export([
+ start/2,
+ stop/1
+]).
%%==============================================================================
%% Application Callbacks
%%==============================================================================
-spec start(normal, any()) -> {ok, pid()}.
start(_StartType, _StartArgs) ->
- ok = application:set_env(elvis_core, no_output, true),
- els_sup:start_link().
+ ok = application:set_env(elvis_core, no_output, true),
+ els_sup:start_link().
-spec stop(any()) -> ok.
stop(_State) ->
- ok.
+ ok.
diff --git a/apps/els_lsp/src/els_background_job.erl b/apps/els_lsp/src/els_background_job.erl
index a5687d8c3..c761ddae5 100644
--- a/apps/els_lsp/src/els_background_job.erl
+++ b/apps/els_lsp/src/els_background_job.erl
@@ -6,25 +6,26 @@
%%==============================================================================
%% API
%%==============================================================================
--export([ new/1
- , list/0
- , stop/1
- , stop_all/0
- ]).
+-export([
+ new/1,
+ list/0,
+ stop/1,
+ stop_all/0
+]).
--export([ start_link/1
- ]).
+-export([start_link/1]).
%%==============================================================================
%% Callbacks for gen_server
%%==============================================================================
-behaviour(gen_server).
--export([ init/1
- , handle_call/3
- , handle_cast/2
- , handle_info/2
- , terminate/2
- ]).
+-export([
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2
+]).
%%==============================================================================
%% Includes
@@ -34,30 +35,34 @@
%%==============================================================================
%% Macro Definitions
%%==============================================================================
--define(SPINNING_WHEEL_INTERVAL, 100). %% ms
+
+%% ms
+-define(SPINNING_WHEEL_INTERVAL, 100).
%%==============================================================================
%% Types
%%==============================================================================
-type entry() :: any().
--type config() :: #{ task := fun((entry(), any()) -> any())
- , entries := [any()]
- , on_complete => fun()
- , on_error => fun()
- , title := binary()
- , show_percentages => boolean()
- , initial_state => any()
- }.
--type state() :: #{ config := config()
- , progress_enabled := boolean()
- , show_percentages := boolean()
- , token := els_progress:token()
- , current := non_neg_integer()
- , step := pos_integer()
- , total := non_neg_integer()
- , internal_state := any()
- , spinning_wheel := pid() | undefined
- }.
+-type config() :: #{
+ task := fun((entry(), any()) -> any()),
+ entries := [any()],
+ on_complete => fun(),
+ on_error => fun(),
+ title := binary(),
+ show_percentages => boolean(),
+ initial_state => any()
+}.
+-type state() :: #{
+ config := config(),
+ progress_enabled := boolean(),
+ show_percentages := boolean(),
+ token := els_progress:token(),
+ current := non_neg_integer(),
+ step := pos_integer(),
+ total := non_neg_integer(),
+ internal_state := any(),
+ spinning_wheel := pid() | undefined
+}.
%%==============================================================================
%% API
@@ -66,145 +71,162 @@
%% @doc Create a new background job
-spec new(config()) -> {ok, pid()}.
new(Config) ->
- supervisor:start_child(els_background_job_sup, [Config]).
+ supervisor:start_child(els_background_job_sup, [Config]).
%% @doc Return the list of running background jobs
-spec list() -> [pid()].
list() ->
- [Pid || {_Id, Pid, _Type, _Modules}
- <- supervisor:which_children(els_background_job_sup)].
+ [
+ Pid
+ || {_Id, Pid, _Type, _Modules} <-
+ supervisor:which_children(els_background_job_sup)
+ ].
%% @doc Terminate a background job
-spec stop(pid()) -> ok.
stop(Pid) ->
- supervisor:terminate_child(els_background_job_sup, Pid).
+ supervisor:terminate_child(els_background_job_sup, Pid).
%% @doc Terminate all background jobs
-spec stop_all() -> ok.
stop_all() ->
- [ok = supervisor:terminate_child(els_background_job_sup, Pid) ||
- Pid <- list()],
- ok.
+ [
+ ok = supervisor:terminate_child(els_background_job_sup, Pid)
+ || Pid <- list()
+ ],
+ ok.
%% @doc Start the server responsible for a background job
%%
%% To be used by the supervisor
-spec start_link(config()) -> {ok, pid()}.
start_link(Config) ->
- gen_server:start_link(?MODULE, Config, []).
+ gen_server:start_link(?MODULE, Config, []).
%%==============================================================================
%% Callbacks for gen_server
%%==============================================================================
-spec init(config()) -> {ok, state()}.
init(#{entries := Entries, title := Title} = Config) ->
- ?LOG_DEBUG("Background job started ~s", [Title]),
- %% Ensure the terminate function is called on shutdown, allowing the
- %% job to clean up.
- process_flag(trap_exit, true),
- ProgressEnabled = els_work_done_progress:is_supported(),
- Total = length(Entries),
- Step = step(Total),
- Token = els_work_done_progress:send_create_request(),
- OnComplete = maps:get(on_complete, Config, fun noop/1),
- OnError = maps:get(on_error, Config, fun noop/1),
- ShowPercentages = maps:get(show_percentages, Config, true),
- notify_begin(Token, Title, Total, ProgressEnabled, ShowPercentages),
- SpinningWheel = case {ProgressEnabled, ShowPercentages} of
- {true, false} ->
- spawn_link(fun() -> spinning_wheel(Token) end);
- {_, _} ->
- undefined
- end,
- self() ! exec,
- {ok, #{ config => Config#{ on_complete => OnComplete
- , on_error => OnError
- }
- , progress_enabled => ProgressEnabled
- , show_percentages => ShowPercentages
- , token => Token
- , current => 0
- , step => Step
- , total => Total
- , internal_state => maps:get(initial_state, Config, undefined)
- , spinning_wheel => SpinningWheel
- }}.
+ ?LOG_DEBUG("Background job started ~s", [Title]),
+ %% Ensure the terminate function is called on shutdown, allowing the
+ %% job to clean up.
+ process_flag(trap_exit, true),
+ ProgressEnabled = els_work_done_progress:is_supported(),
+ Total = length(Entries),
+ Step = step(Total),
+ Token = els_work_done_progress:send_create_request(),
+ OnComplete = maps:get(on_complete, Config, fun noop/1),
+ OnError = maps:get(on_error, Config, fun noop/1),
+ ShowPercentages = maps:get(show_percentages, Config, true),
+ notify_begin(Token, Title, Total, ProgressEnabled, ShowPercentages),
+ SpinningWheel =
+ case {ProgressEnabled, ShowPercentages} of
+ {true, false} ->
+ spawn_link(fun() -> spinning_wheel(Token) end);
+ {_, _} ->
+ undefined
+ end,
+ self() ! exec,
+ {ok, #{
+ config => Config#{
+ on_complete => OnComplete,
+ on_error => OnError
+ },
+ progress_enabled => ProgressEnabled,
+ show_percentages => ShowPercentages,
+ token => Token,
+ current => 0,
+ step => Step,
+ total => Total,
+ internal_state => maps:get(initial_state, Config, undefined),
+ spinning_wheel => SpinningWheel
+ }}.
-spec handle_call(any(), {pid(), any()}, state()) ->
- {noreply, state()}.
+ {noreply, state()}.
handle_call(_Request, _From, State) ->
- {noreply, State}.
+ {noreply, State}.
-spec handle_cast(any(), any()) ->
- {noreply, state()}.
+ {noreply, state()}.
handle_cast(_Request, State) ->
- {noreply, State}.
+ {noreply, State}.
-spec handle_info(any(), any()) ->
- {noreply, state()}.
+ {noreply, state()}.
handle_info(exec, State) ->
- #{ config := #{ entries := Entries, task := Task} = Config
- , progress_enabled := ProgressEnabled
- , show_percentages := ShowPercentages
- , token := Token
- , current := Current
- , step := Step
- , total := Total
- , internal_state := InternalState
- } = State,
- case Entries of
- [] ->
- notify_end(Token, Total, ProgressEnabled),
- {stop, normal, State};
- [Entry|Rest] ->
- NewInternalState = Task(Entry, InternalState),
- notify_report( Token
- , Current
- , Step
- , Total
- , ProgressEnabled
- , ShowPercentages),
- self() ! exec,
- {noreply, State#{ config => Config#{ entries => Rest }
- , current => Current + 1
- , internal_state => NewInternalState
- }}
- end;
+ #{
+ config := #{entries := Entries, task := Task} = Config,
+ progress_enabled := ProgressEnabled,
+ show_percentages := ShowPercentages,
+ token := Token,
+ current := Current,
+ step := Step,
+ total := Total,
+ internal_state := InternalState
+ } = State,
+ case Entries of
+ [] ->
+ notify_end(Token, Total, ProgressEnabled),
+ {stop, normal, State};
+ [Entry | Rest] ->
+ NewInternalState = Task(Entry, InternalState),
+ notify_report(
+ Token,
+ Current,
+ Step,
+ Total,
+ ProgressEnabled,
+ ShowPercentages
+ ),
+ self() ! exec,
+ {noreply, State#{
+ config => Config#{entries => Rest},
+ current => Current + 1,
+ internal_state => NewInternalState
+ }}
+ end;
handle_info(_Request, State) ->
- {noreply, State}.
+ {noreply, State}.
-spec terminate(any(), state()) -> ok.
-terminate(normal, #{ config := #{on_complete := OnComplete}
- , internal_state := InternalState
- , spinning_wheel := SpinningWheel
- }) ->
- case SpinningWheel of
- undefined ->
- ok;
- Pid ->
- exit(Pid, kill)
- end,
- ?LOG_DEBUG("Background job completed.", []),
- OnComplete(InternalState),
- ok;
-terminate(Reason, #{ config := #{ on_error := OnError
- , title := Title
- }
- , internal_state := InternalState
- , token := Token
- , total := Total
- , progress_enabled := ProgressEnabled
- }) ->
- case Reason of
- shutdown ->
- ?LOG_DEBUG("Background job terminated.", []);
- _ ->
- ?LOG_ERROR( "Background job aborted. [reason=~p] [title=~p",
- [Reason, Title])
- end,
- notify_end(Token, Total, ProgressEnabled),
- OnError(InternalState),
- ok.
+terminate(normal, #{
+ config := #{on_complete := OnComplete},
+ internal_state := InternalState,
+ spinning_wheel := SpinningWheel
+}) ->
+ case SpinningWheel of
+ undefined ->
+ ok;
+ Pid ->
+ exit(Pid, kill)
+ end,
+ ?LOG_DEBUG("Background job completed.", []),
+ OnComplete(InternalState),
+ ok;
+terminate(Reason, #{
+ config := #{
+ on_error := OnError,
+ title := Title
+ },
+ internal_state := InternalState,
+ token := Token,
+ total := Total,
+ progress_enabled := ProgressEnabled
+}) ->
+ case Reason of
+ shutdown ->
+ ?LOG_DEBUG("Background job terminated.", []);
+ _ ->
+ ?LOG_ERROR(
+ "Background job aborted. [reason=~p] [title=~p",
+ [Reason, Title]
+ )
+ end,
+ notify_end(Token, Total, ProgressEnabled),
+ OnError(InternalState),
+ ok.
%%==============================================================================
%% Internal functions
@@ -215,50 +237,65 @@ step(N) -> 100 / N.
-spec progress_msg(non_neg_integer(), pos_integer()) -> binary().
progress_msg(Current, Total) ->
- list_to_binary(io_lib:format("~p / ~p", [Current, Total])).
+ list_to_binary(io_lib:format("~p / ~p", [Current, Total])).
-spec noop(any()) -> ok.
noop(_) ->
- ok.
+ ok.
--spec notify_begin( els_progress:token()
- , binary()
- , pos_integer()
- , boolean()
- , boolean()) ->
- ok.
+-spec notify_begin(
+ els_progress:token(),
+ binary(),
+ pos_integer(),
+ boolean(),
+ boolean()
+) ->
+ ok.
notify_begin(Token, Title, Total, true, ShowPercentages) ->
- BeginMsg = progress_msg(0, Total),
- Begin = case ShowPercentages of
+ BeginMsg = progress_msg(0, Total),
+ Begin =
+ case ShowPercentages of
true -> els_work_done_progress:value_begin(Title, BeginMsg, 0);
false -> els_work_done_progress:value_begin(Title, BeginMsg)
- end,
- els_progress:send_notification(Token, Begin);
+ end,
+ els_progress:send_notification(Token, Begin);
notify_begin(_Token, _Title, _Total, false, _ShowPercentages) ->
- ok.
+ ok.
--spec notify_report( els_progress:token(), pos_integer(), pos_integer()
- , pos_integer(), boolean(), boolean()) -> ok.
+-spec notify_report(
+ els_progress:token(),
+ pos_integer(),
+ pos_integer(),
+ pos_integer(),
+ boolean(),
+ boolean()
+) -> ok.
notify_report(Token, Current, Step, Total, true, true) ->
- Percentage = floor(Current * Step),
- ReportMsg = progress_msg(Current, Total),
- Report = els_work_done_progress:value_report(ReportMsg, Percentage),
- els_progress:send_notification(Token, Report);
-notify_report( _Token, _Current, _Step
- , _Total, _ProgressEnabled, _ShowPercentages) ->
- ok.
+ Percentage = floor(Current * Step),
+ ReportMsg = progress_msg(Current, Total),
+ Report = els_work_done_progress:value_report(ReportMsg, Percentage),
+ els_progress:send_notification(Token, Report);
+notify_report(
+ _Token,
+ _Current,
+ _Step,
+ _Total,
+ _ProgressEnabled,
+ _ShowPercentages
+) ->
+ ok.
-spec notify_end(els_progress:token(), pos_integer(), boolean()) -> ok.
notify_end(Token, Total, true) ->
- EndMsg = progress_msg(Total, Total),
- End = els_work_done_progress:value_end(EndMsg),
- els_progress:send_notification(Token, End);
+ EndMsg = progress_msg(Total, Total),
+ End = els_work_done_progress:value_end(EndMsg),
+ els_progress:send_notification(Token, End);
notify_end(_Token, _Total, false) ->
- ok.
+ ok.
-spec spinning_wheel(els_progress:token()) -> no_return().
spinning_wheel(Token) ->
- Report = els_work_done_progress:value_report(<<>>),
- els_progress:send_notification(Token, Report),
- timer:sleep(?SPINNING_WHEEL_INTERVAL),
- spinning_wheel(Token).
+ Report = els_work_done_progress:value_report(<<>>),
+ els_progress:send_notification(Token, Report),
+ timer:sleep(?SPINNING_WHEEL_INTERVAL),
+ spinning_wheel(Token).
diff --git a/apps/els_lsp/src/els_background_job_sup.erl b/apps/els_lsp/src/els_background_job_sup.erl
index 30024608f..4b8258e9c 100644
--- a/apps/els_lsp/src/els_background_job_sup.erl
+++ b/apps/els_lsp/src/els_background_job_sup.erl
@@ -13,10 +13,10 @@
%%==============================================================================
%% API
--export([ start_link/0 ]).
+-export([start_link/0]).
%% Supervisor Callbacks
--export([ init/1 ]).
+-export([init/1]).
%%==============================================================================
%% Defines
@@ -28,20 +28,24 @@
%%==============================================================================
-spec start_link() -> {ok, pid()}.
start_link() ->
- supervisor:start_link({local, ?SERVER}, ?MODULE, []).
+ supervisor:start_link({local, ?SERVER}, ?MODULE, []).
%%==============================================================================
%% Supervisor callbacks
%%==============================================================================
-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init([]) ->
- SupFlags = #{ strategy => simple_one_for_one
- , intensity => 5
- , period => 60
- },
- ChildSpecs = [#{ id => els_background_job
- , start => {els_background_job, start_link, []}
- , restart => temporary
- , shutdown => 5000
- }],
- {ok, {SupFlags, ChildSpecs}}.
+ SupFlags = #{
+ strategy => simple_one_for_one,
+ intensity => 5,
+ period => 60
+ },
+ ChildSpecs = [
+ #{
+ id => els_background_job,
+ start => {els_background_job, start_link, []},
+ restart => temporary,
+ shutdown => 5000
+ }
+ ],
+ {ok, {SupFlags, ChildSpecs}}.
diff --git a/apps/els_lsp/src/els_bound_var_in_pattern_diagnostics.erl b/apps/els_lsp/src/els_bound_var_in_pattern_diagnostics.erl
index eff1ee1d6..38535ae80 100644
--- a/apps/els_lsp/src/els_bound_var_in_pattern_diagnostics.erl
+++ b/apps/els_lsp/src/els_bound_var_in_pattern_diagnostics.erl
@@ -8,10 +8,11 @@
%%==============================================================================
-behaviour(els_diagnostics).
--export([ is_default/0
- , run/1
- , source/0
- ]).
+-export([
+ is_default/0,
+ run/1,
+ source/0
+]).
%%==============================================================================
%% Includes
@@ -29,21 +30,21 @@
-spec is_default() -> boolean().
is_default() ->
- true.
+ true.
-spec run(uri()) -> [els_diagnostics:diagnostic()].
run(Uri) ->
- case filename:extension(Uri) of
- <<".erl">> ->
- BoundVarsInPatterns = find_vars(Uri),
- [make_diagnostic(POI) || POI <- BoundVarsInPatterns];
- _ ->
- []
- end.
+ case filename:extension(Uri) of
+ <<".erl">> ->
+ BoundVarsInPatterns = find_vars(Uri),
+ [make_diagnostic(POI) || POI <- BoundVarsInPatterns];
+ _ ->
+ []
+ end.
-spec source() -> binary().
source() ->
- <<"BoundVarInPattern">>.
+ <<"BoundVarInPattern">>.
%%==============================================================================
%% Internal Functions
@@ -51,103 +52,109 @@ source() ->
-spec find_vars(uri()) -> [poi()].
find_vars(Uri) ->
- {ok, #{text := Text}} = els_utils:lookup_document(Uri),
- {ok, Forms} = els_parser:parse_text(Text),
- lists:flatmap(fun find_vars_in_form/1, Forms).
+ {ok, #{text := Text}} = els_utils:lookup_document(Uri),
+ {ok, Forms} = els_parser:parse_text(Text),
+ lists:flatmap(fun find_vars_in_form/1, Forms).
-spec find_vars_in_form(erl_syntax:forms()) -> [poi()].
find_vars_in_form(Form) ->
- case erl_syntax:type(Form) of
- function ->
- %% #1288: The try catch should allow us to understand the root cause
- %% of the occasional crashes, which could be due to an incorrect mapping
- %% between the erlfmt AST and erl_syntax AST
- try
- AnnotatedForm = erl_syntax_lib:annotate_bindings(Form, []),
- %% There are no bound variables in function heads or guards
- %% so lets descend straight into the bodies
- Clauses = erl_syntax:function_clauses(AnnotatedForm),
- ClauseBodies = lists:map(fun erl_syntax:clause_body/1, Clauses),
- fold_subtrees(ClauseBodies, [])
- catch C:E:St ->
- ?LOG_ERROR("Error annotating bindings "
- "[form=~p] [class=~p] [error=~p] [stacktrace=~p]",
- [Form, C, E, St]),
- []
- end;
- _ ->
- []
- end.
+ case erl_syntax:type(Form) of
+ function ->
+ %% #1288: The try catch should allow us to understand the root cause
+ %% of the occasional crashes, which could be due to an incorrect mapping
+ %% between the erlfmt AST and erl_syntax AST
+ try
+ AnnotatedForm = erl_syntax_lib:annotate_bindings(Form, []),
+ %% There are no bound variables in function heads or guards
+ %% so lets descend straight into the bodies
+ Clauses = erl_syntax:function_clauses(AnnotatedForm),
+ ClauseBodies = lists:map(fun erl_syntax:clause_body/1, Clauses),
+ fold_subtrees(ClauseBodies, [])
+ catch
+ C:E:St ->
+ ?LOG_ERROR(
+ "Error annotating bindings "
+ "[form=~p] [class=~p] [error=~p] [stacktrace=~p]",
+ [Form, C, E, St]
+ ),
+ []
+ end;
+ _ ->
+ []
+ end.
-spec fold_subtrees([[tree()]], [poi()]) -> [poi()].
fold_subtrees(Subtrees, Acc) ->
- erl_syntax_lib:foldl_listlist(fun find_vars_in_tree/2, Acc, Subtrees).
+ erl_syntax_lib:foldl_listlist(fun find_vars_in_tree/2, Acc, Subtrees).
-spec find_vars_in_tree(tree(), [poi()]) -> [poi()].
find_vars_in_tree(Tree, Acc) ->
- case erl_syntax:type(Tree) of
- Type when Type =:= fun_expr;
- Type =:= named_fun_expr ->
- %% There is no bound variables in fun expression heads,
- %% because they shadow whatever is in the input env
- %% so lets descend straight into the bodies
- %% (This is a workaround for erl_syntax_lib not considering
- %% shadowing in fun expressions)
- Clauses = case Type of
- fun_expr -> erl_syntax:fun_expr_clauses(Tree);
- named_fun_expr -> erl_syntax:named_fun_expr_clauses(Tree)
+ case erl_syntax:type(Tree) of
+ Type when
+ Type =:= fun_expr;
+ Type =:= named_fun_expr
+ ->
+ %% There is no bound variables in fun expression heads,
+ %% because they shadow whatever is in the input env
+ %% so lets descend straight into the bodies
+ %% (This is a workaround for erl_syntax_lib not considering
+ %% shadowing in fun expressions)
+ Clauses =
+ case Type of
+ fun_expr -> erl_syntax:fun_expr_clauses(Tree);
+ named_fun_expr -> erl_syntax:named_fun_expr_clauses(Tree)
end,
- ClauseBodies = lists:map(fun erl_syntax:clause_body/1, Clauses),
- fold_subtrees(ClauseBodies, Acc);
- match_expr ->
- Pattern = erl_syntax:match_expr_pattern(Tree),
- NewAcc = fold_pattern(Pattern, Acc),
- find_vars_in_tree(erl_syntax:match_expr_body(Tree), NewAcc);
- clause ->
- Patterns = erl_syntax:clause_patterns(Tree),
- NewAcc = fold_pattern_list(Patterns, Acc),
- fold_subtrees([erl_syntax:clause_body(Tree)], NewAcc);
- _ ->
- fold_subtrees(erl_syntax:subtrees(Tree), Acc)
- end.
+ ClauseBodies = lists:map(fun erl_syntax:clause_body/1, Clauses),
+ fold_subtrees(ClauseBodies, Acc);
+ match_expr ->
+ Pattern = erl_syntax:match_expr_pattern(Tree),
+ NewAcc = fold_pattern(Pattern, Acc),
+ find_vars_in_tree(erl_syntax:match_expr_body(Tree), NewAcc);
+ clause ->
+ Patterns = erl_syntax:clause_patterns(Tree),
+ NewAcc = fold_pattern_list(Patterns, Acc),
+ fold_subtrees([erl_syntax:clause_body(Tree)], NewAcc);
+ _ ->
+ fold_subtrees(erl_syntax:subtrees(Tree), Acc)
+ end.
-spec fold_pattern(tree(), [poi()]) -> [poi()].
fold_pattern(Pattern, Acc) ->
- erl_syntax_lib:fold(fun find_vars_in_pattern/2, Acc, Pattern).
+ erl_syntax_lib:fold(fun find_vars_in_pattern/2, Acc, Pattern).
-spec fold_pattern_list([tree()], [poi()]) -> [poi()].
fold_pattern_list(Patterns, Acc) ->
- lists:foldl(fun fold_pattern/2, Acc, Patterns).
+ lists:foldl(fun fold_pattern/2, Acc, Patterns).
-spec find_vars_in_pattern(tree(), [poi()]) -> [poi()].
find_vars_in_pattern(Tree, Acc) ->
- case erl_syntax:type(Tree) of
- variable ->
- Var = erl_syntax:variable_name(Tree),
- Anno = erl_syntax:get_ann(Tree),
- case lists:keyfind(free, 1, Anno) of
- {free, Free} when Free =:= [Var] ->
- %% Using already bound variable in pattern
- [variable(Tree) | Acc];
+ case erl_syntax:type(Tree) of
+ variable ->
+ Var = erl_syntax:variable_name(Tree),
+ Anno = erl_syntax:get_ann(Tree),
+ case lists:keyfind(free, 1, Anno) of
+ {free, Free} when Free =:= [Var] ->
+ %% Using already bound variable in pattern
+ [variable(Tree) | Acc];
+ _ ->
+ Acc
+ end;
_ ->
- Acc
- end;
- _ ->
- Acc
- end.
+ Acc
+ end.
-spec variable(tree()) -> poi().
variable(Tree) ->
- Id = erl_syntax:variable_name(Tree),
- Pos = erl_syntax:get_pos(Tree),
- Range = els_range:range(Pos, variable, Id, undefined),
- els_poi:new(Range, variable, Id, undefined).
+ Id = erl_syntax:variable_name(Tree),
+ Pos = erl_syntax:get_pos(Tree),
+ Range = els_range:range(Pos, variable, Id, undefined),
+ els_poi:new(Range, variable, Id, undefined).
-spec make_diagnostic(poi()) -> els_diagnostics:diagnostic().
make_diagnostic(#{id := Id, range := POIRange}) ->
- Range = els_protocol:range(POIRange),
- VariableName = atom_to_binary(Id, utf8),
- Message = <<"Bound variable in pattern: ", VariableName/binary>>,
- Severity = ?DIAGNOSTIC_HINT,
- Source = source(),
- els_diagnostics:make_diagnostic(Range, Message, Severity, Source).
+ Range = els_protocol:range(POIRange),
+ VariableName = atom_to_binary(Id, utf8),
+ Message = <<"Bound variable in pattern: ", VariableName/binary>>,
+ Severity = ?DIAGNOSTIC_HINT,
+ Source = source(),
+ els_diagnostics:make_diagnostic(Range, Message, Severity, Source).
diff --git a/apps/els_lsp/src/els_call_hierarchy_item.erl b/apps/els_lsp/src/els_call_hierarchy_item.erl
index e5b9abdcb..920e16358 100644
--- a/apps/els_lsp/src/els_call_hierarchy_item.erl
+++ b/apps/els_lsp/src/els_call_hierarchy_item.erl
@@ -4,49 +4,56 @@
-include_lib("kernel/include/logger.hrl").
--export([ new/5
- , poi/1
- ]).
+-export([
+ new/5,
+ poi/1
+]).
-type data() :: any().
--type item() :: #{ name := binary()
- , kind := symbol_kind()
- , tags => [symbol_tag()]
- , detail => binary()
- , uri := uri()
- , range := range()
- , selectionRange := range()
- , data => data()
- }.
--type incoming_call() :: #{ from := item()
- , fromRanges := [range()]
- }.
--type outgoing_call() :: #{ to := item()
- , fromRanges := [range()]
- }.
--export_type([ item/0
- , incoming_call/0
- , outgoing_call/0
- ]).
+-type item() :: #{
+ name := binary(),
+ kind := symbol_kind(),
+ tags => [symbol_tag()],
+ detail => binary(),
+ uri := uri(),
+ range := range(),
+ selectionRange := range(),
+ data => data()
+}.
+-type incoming_call() :: #{
+ from := item(),
+ fromRanges := [range()]
+}.
+-type outgoing_call() :: #{
+ to := item(),
+ fromRanges := [range()]
+}.
+-export_type([
+ item/0,
+ incoming_call/0,
+ outgoing_call/0
+]).
%% @doc Extract and decode the POI from the data
-spec poi(item()) -> poi().
poi(#{<<"data">> := Data}) ->
- maps:get(poi, els_utils:base64_decode_term(Data)).
+ maps:get(poi, els_utils:base64_decode_term(Data)).
-spec new(binary(), uri(), poi_range(), poi_range(), data()) -> item().
new(Name, Uri, Range, SelectionRange, Data) ->
- #{from := {StartLine, _}} = Range,
- Detail = <<(atom_to_binary(els_uri:module(Uri), utf8))/binary,
- " [L",
- (integer_to_binary(StartLine))/binary,
- "]"
- >>,
- #{ name => Name
- , kind => ?SYMBOLKIND_FUNCTION
- , detail => Detail
- , uri => Uri
- , range => els_protocol:range(Range)
- , selectionRange => els_protocol:range(SelectionRange)
- , data => els_utils:base64_encode_term(Data)
- }.
+ #{from := {StartLine, _}} = Range,
+ Detail = <<
+ (atom_to_binary(els_uri:module(Uri), utf8))/binary,
+ " [L",
+ (integer_to_binary(StartLine))/binary,
+ "]"
+ >>,
+ #{
+ name => Name,
+ kind => ?SYMBOLKIND_FUNCTION,
+ detail => Detail,
+ uri => Uri,
+ range => els_protocol:range(Range),
+ selectionRange => els_protocol:range(SelectionRange),
+ data => els_utils:base64_encode_term(Data)
+ }.
diff --git a/apps/els_lsp/src/els_call_hierarchy_provider.erl b/apps/els_lsp/src/els_call_hierarchy_provider.erl
index 9328cd031..145c9c6e5 100644
--- a/apps/els_lsp/src/els_call_hierarchy_provider.erl
+++ b/apps/els_lsp/src/els_call_hierarchy_provider.erl
@@ -2,9 +2,10 @@
-behaviour(els_provider).
--export([ is_enabled/0
- , handle_request/2
- ]).
+-export([
+ is_enabled/0,
+ handle_request/2
+]).
%%==============================================================================
%% Includes
@@ -29,84 +30,91 @@ is_enabled() -> true.
-spec handle_request(any(), state()) -> {response, any()}.
handle_request({prepare, Params}, _State) ->
- {Uri, Line, Char} =
- els_text_document_position_params:uri_line_character(Params),
- {ok, Document} = els_utils:lookup_document(Uri),
- Functions = els_dt_document:wrapping_functions(Document, Line + 1, Char + 1),
- Items = [function_to_item(Uri, F) || F <- Functions],
- {response, Items};
+ {Uri, Line, Char} =
+ els_text_document_position_params:uri_line_character(Params),
+ {ok, Document} = els_utils:lookup_document(Uri),
+ Functions = els_dt_document:wrapping_functions(Document, Line + 1, Char + 1),
+ Items = [function_to_item(Uri, F) || F <- Functions],
+ {response, Items};
handle_request({incoming_calls, Params}, _State) ->
- #{ <<"item">> := #{<<"uri">> := Uri} = Item } = Params,
- POI = els_call_hierarchy_item:poi(Item),
- References = els_references_provider:find_references(Uri, POI),
- Items = [reference_to_item(Reference) || Reference <- References],
- {response, incoming_calls(Items)};
+ #{<<"item">> := #{<<"uri">> := Uri} = Item} = Params,
+ POI = els_call_hierarchy_item:poi(Item),
+ References = els_references_provider:find_references(Uri, POI),
+ Items = [reference_to_item(Reference) || Reference <- References],
+ {response, incoming_calls(Items)};
handle_request({outgoing_calls, Params}, _State) ->
- #{ <<"item">> := Item } = Params,
- #{ <<"uri">> := Uri } = Item,
- POI = els_call_hierarchy_item:poi(Item),
- Applications = applications_in_function_range(Uri, POI),
- Items = lists:foldl(fun(Application, Acc) ->
- case application_to_item(Uri, Application) of
- {error, not_found} ->
- %% The function may contain a reference
- %% to a not yet implemented function
- Acc;
- {ok, I} ->
- [I|Acc]
- end
- end, [], Applications),
- {response, outgoing_calls(lists:reverse(Items))}.
+ #{<<"item">> := Item} = Params,
+ #{<<"uri">> := Uri} = Item,
+ POI = els_call_hierarchy_item:poi(Item),
+ Applications = applications_in_function_range(Uri, POI),
+ Items = lists:foldl(
+ fun(Application, Acc) ->
+ case application_to_item(Uri, Application) of
+ {error, not_found} ->
+ %% The function may contain a reference
+ %% to a not yet implemented function
+ Acc;
+ {ok, I} ->
+ [I | Acc]
+ end
+ end,
+ [],
+ Applications
+ ),
+ {response, outgoing_calls(lists:reverse(Items))}.
%%==============================================================================
%% Internal functions
%%==============================================================================
-spec incoming_calls([els_call_hierarchy_item:item()]) ->
- [els_call_hierarchy_item:incoming_call()].
+ [els_call_hierarchy_item:incoming_call()].
incoming_calls(Items) ->
- [#{from => Item, fromRanges => [Range]} || #{range := Range} = Item <- Items].
+ [#{from => Item, fromRanges => [Range]} || #{range := Range} = Item <- Items].
-spec outgoing_calls([els_call_hierarchy_item:item()]) ->
- [els_call_hierarchy_item:outgoing_call()].
+ [els_call_hierarchy_item:outgoing_call()].
outgoing_calls(Items) ->
- [#{to => Item, fromRanges => [Range]} || #{range := Range} = Item <- Items].
+ [#{to => Item, fromRanges => [Range]} || #{range := Range} = Item <- Items].
-spec function_to_item(uri(), poi()) -> els_call_hierarchy_item:item().
function_to_item(Uri, Function) ->
- #{id := Id, range := Range} = Function,
- Name = els_utils:function_signature(Id),
- Data = #{poi => Function},
- els_call_hierarchy_item:new(Name, Uri, Range, Range, Data).
+ #{id := Id, range := Range} = Function,
+ Name = els_utils:function_signature(Id),
+ Data = #{poi => Function},
+ els_call_hierarchy_item:new(Name, Uri, Range, Range, Data).
-spec reference_to_item(location()) -> els_call_hierarchy_item:item().
reference_to_item(Reference) ->
- #{uri := RefUri, range := RefRange} = Reference,
- {ok, RefDoc} = els_utils:lookup_document(RefUri),
- [WrappingPOI] = els_dt_document:wrapping_functions(RefDoc, RefRange),
- Name = els_utils:function_signature(maps:get(id, WrappingPOI)),
- POIRange = els_range:to_poi_range(RefRange),
- Data = #{poi => WrappingPOI},
- els_call_hierarchy_item:new(Name, RefUri, POIRange, POIRange, Data).
+ #{uri := RefUri, range := RefRange} = Reference,
+ {ok, RefDoc} = els_utils:lookup_document(RefUri),
+ [WrappingPOI] = els_dt_document:wrapping_functions(RefDoc, RefRange),
+ Name = els_utils:function_signature(maps:get(id, WrappingPOI)),
+ POIRange = els_range:to_poi_range(RefRange),
+ Data = #{poi => WrappingPOI},
+ els_call_hierarchy_item:new(Name, RefUri, POIRange, POIRange, Data).
-spec application_to_item(uri(), poi()) ->
- {ok, els_call_hierarchy_item:item()} | {error, not_found}.
+ {ok, els_call_hierarchy_item:item()} | {error, not_found}.
application_to_item(Uri, Application) ->
- #{id := Id} = Application,
- Name = els_utils:function_signature(Id),
- case els_code_navigation:goto_definition(Uri, Application) of
- {ok, DefUri, DefPOI} ->
- DefRange = maps:get(range, DefPOI),
- Data = #{poi => DefPOI},
- {ok, els_call_hierarchy_item:new(Name, DefUri, DefRange, DefRange, Data)};
- {error, Reason} ->
- {error, Reason}
- end.
+ #{id := Id} = Application,
+ Name = els_utils:function_signature(Id),
+ case els_code_navigation:goto_definition(Uri, Application) of
+ {ok, DefUri, DefPOI} ->
+ DefRange = maps:get(range, DefPOI),
+ Data = #{poi => DefPOI},
+ {ok, els_call_hierarchy_item:new(Name, DefUri, DefRange, DefRange, Data)};
+ {error, Reason} ->
+ {error, Reason}
+ end.
-spec applications_in_function_range(uri(), poi()) ->
- [poi()].
+ [poi()].
applications_in_function_range(Uri, Function) ->
- {ok, Document} = els_utils:lookup_document(Uri),
- #{data := #{wrapping_range := WrappingRange}} = Function,
- AllApplications = els_dt_document:pois(Document, ['application']),
- [A || #{range := AppRange} = A <- AllApplications
- , els_range:in(AppRange, WrappingRange)].
+ {ok, Document} = els_utils:lookup_document(Uri),
+ #{data := #{wrapping_range := WrappingRange}} = Function,
+ AllApplications = els_dt_document:pois(Document, ['application']),
+ [
+ A
+ || #{range := AppRange} = A <- AllApplications,
+ els_range:in(AppRange, WrappingRange)
+ ].
diff --git a/apps/els_lsp/src/els_code_action_provider.erl b/apps/els_lsp/src/els_code_action_provider.erl
index 7217543ba..3f4bbfdc7 100644
--- a/apps/els_lsp/src/els_code_action_provider.erl
+++ b/apps/els_lsp/src/els_code_action_provider.erl
@@ -2,9 +2,10 @@
-behaviour(els_provider).
--export([ is_enabled/0
- , handle_request/2
- ]).
+-export([
+ is_enabled/0,
+ handle_request/2
+]).
-include("els_lsp.hrl").
@@ -18,11 +19,13 @@ is_enabled() -> true.
-spec handle_request(any(), state()) -> {response, any()}.
handle_request({document_codeaction, Params}, _State) ->
- #{ <<"textDocument">> := #{ <<"uri">> := Uri}
- , <<"range">> := RangeLSP
- , <<"context">> := Context } = Params,
- Result = code_actions(Uri, RangeLSP, Context),
- {response, Result}.
+ #{
+ <<"textDocument">> := #{<<"uri">> := Uri},
+ <<"range">> := RangeLSP,
+ <<"context">> := Context
+ } = Params,
+ Result = code_actions(Uri, RangeLSP, Context),
+ {response, Result}.
%%==============================================================================
%% Internal Functions
@@ -31,39 +34,43 @@ handle_request({document_codeaction, Params}, _State) ->
%% @doc Result: `(Command | CodeAction)[] | null'
-spec code_actions(uri(), range(), code_action_context()) -> [map()].
code_actions(Uri, _Range, #{<<"diagnostics">> := Diagnostics}) ->
- lists:flatten([make_code_actions(Uri, D) || D <- Diagnostics]).
+ lists:flatten([make_code_actions(Uri, D) || D <- Diagnostics]).
-spec make_code_actions(uri(), map()) -> [map()].
-make_code_actions(Uri,
- #{<<"message">> := Message, <<"range">> := Range} = Diagnostic) ->
+make_code_actions(
+ Uri,
+ #{<<"message">> := Message, <<"range">> := Range} = Diagnostic
+) ->
Data = maps:get(<<"data">>, Diagnostic, <<>>),
- make_code_actions(
- [ {"function (.*) is unused",
- fun els_code_actions:export_function/4}
- , {"variable '(.*)' is unused",
- fun els_code_actions:ignore_variable/4}
- , {"variable '(.*)' is unbound",
- fun els_code_actions:suggest_variable/4}
- , {"Module name '(.*)' does not match file name '(.*)'",
- fun els_code_actions:fix_module_name/4}
- , {"Unused macro: (.*)",
- fun els_code_actions:remove_macro/4}
- , {"function (.*) undefined",
- fun els_code_actions:create_function/4}
- , {"Unused file: (.*)",
- fun els_code_actions:remove_unused/4}
- ], Uri, Range, Data, Message).
+ make_code_actions(
+ [
+ {"function (.*) is unused", fun els_code_actions:export_function/4},
+ {"variable '(.*)' is unused", fun els_code_actions:ignore_variable/4},
+ {"variable '(.*)' is unbound", fun els_code_actions:suggest_variable/4},
+ {"Module name '(.*)' does not match file name '(.*)'",
+ fun els_code_actions:fix_module_name/4},
+ {"Unused macro: (.*)", fun els_code_actions:remove_macro/4},
+ {"function (.*) undefined", fun els_code_actions:create_function/4},
+ {"Unused file: (.*)", fun els_code_actions:remove_unused/4}
+ ],
+ Uri,
+ Range,
+ Data,
+ Message
+ ).
--spec make_code_actions([{string(), Fun}], uri(), range(), binary(), binary())
- -> [map()]
- when Fun :: fun((uri(), range(), binary(), [binary()]) -> [map()]).
+-spec make_code_actions([{string(), Fun}], uri(), range(), binary(), binary()) ->
+ [map()]
+when
+ Fun :: fun((uri(), range(), binary(), [binary()]) -> [map()]).
make_code_actions([], _Uri, _Range, _Data, _Message) ->
- [];
-make_code_actions([{RE, Fun}|Rest], Uri, Range, Data, Message) ->
- Actions = case re:run(Message, RE, [{capture, all_but_first, binary}]) of
- {match, Matches} ->
+ [];
+make_code_actions([{RE, Fun} | Rest], Uri, Range, Data, Message) ->
+ Actions =
+ case re:run(Message, RE, [{capture, all_but_first, binary}]) of
+ {match, Matches} ->
Fun(Uri, Range, Data, Matches);
- nomatch ->
+ nomatch ->
[]
- end,
- Actions ++ make_code_actions(Rest, Uri, Range, Data, Message).
+ end,
+ Actions ++ make_code_actions(Rest, Uri, Range, Data, Message).
diff --git a/apps/els_lsp/src/els_code_actions.erl b/apps/els_lsp/src/els_code_actions.erl
index 3a7ae1f62..7ca2e1a9d 100644
--- a/apps/els_lsp/src/els_code_actions.erl
+++ b/apps/els_lsp/src/els_code_actions.erl
@@ -1,159 +1,195 @@
-module(els_code_actions).
--export([ create_function/4
- , export_function/4
- , fix_module_name/4
- , ignore_variable/4
- , remove_macro/4
- , remove_unused/4
- , suggest_variable/4
- ]).
+-export([
+ create_function/4,
+ export_function/4,
+ fix_module_name/4,
+ ignore_variable/4,
+ remove_macro/4,
+ remove_unused/4,
+ suggest_variable/4
+]).
-include("els_lsp.hrl").
-spec create_function(uri(), range(), binary(), [binary()]) -> [map()].
create_function(Uri, _Range, _Data, [UndefinedFun]) ->
- {ok, Document} = els_utils:lookup_document(Uri),
- case els_poi:sort(els_dt_document:pois(Document)) of
- [] ->
- [];
- POIs ->
- #{range := #{to := {Line, _Col}}} = lists:last(POIs),
- [FunctionName, _Arity] = string:split(UndefinedFun, "/"),
- [ make_edit_action( Uri
- , <<"Add the undefined function ",
- UndefinedFun/binary>>
- , ?CODE_ACTION_KIND_QUICKFIX
- , <<"-spec ", FunctionName/binary, "() -> ok. \n ",
- FunctionName/binary, "() -> \n \t ok.">>
- , els_protocol:range(#{from => {Line+1, 1},
- to => {Line+2, 1}}))]
- end.
+ {ok, Document} = els_utils:lookup_document(Uri),
+ case els_poi:sort(els_dt_document:pois(Document)) of
+ [] ->
+ [];
+ POIs ->
+ #{range := #{to := {Line, _Col}}} = lists:last(POIs),
+ [FunctionName, _Arity] = string:split(UndefinedFun, "/"),
+ [
+ make_edit_action(
+ Uri,
+ <<"Add the undefined function ", UndefinedFun/binary>>,
+ ?CODE_ACTION_KIND_QUICKFIX,
+ <<"-spec ", FunctionName/binary, "() -> ok. \n ", FunctionName/binary,
+ "() -> \n \t ok.">>,
+ els_protocol:range(#{
+ from => {Line + 1, 1},
+ to => {Line + 2, 1}
+ })
+ )
+ ]
+ end.
-spec export_function(uri(), range(), binary(), [binary()]) -> [map()].
export_function(Uri, _Range, _Data, [UnusedFun]) ->
- {ok, Document} = els_utils:lookup_document(Uri),
- case els_poi:sort(els_dt_document:pois(Document, [module, export])) of
- [] ->
- [];
- POIs ->
- #{range := #{to := {Line, _Col}}} = lists:last(POIs),
- Pos = {Line + 1, 1},
- [ make_edit_action( Uri
- , <<"Export ", UnusedFun/binary>>
- , ?CODE_ACTION_KIND_QUICKFIX
- , <<"-export([", UnusedFun/binary, "]).\n">>
- , els_protocol:range(#{from => Pos, to => Pos})) ]
- end.
+ {ok, Document} = els_utils:lookup_document(Uri),
+ case els_poi:sort(els_dt_document:pois(Document, [module, export])) of
+ [] ->
+ [];
+ POIs ->
+ #{range := #{to := {Line, _Col}}} = lists:last(POIs),
+ Pos = {Line + 1, 1},
+ [
+ make_edit_action(
+ Uri,
+ <<"Export ", UnusedFun/binary>>,
+ ?CODE_ACTION_KIND_QUICKFIX,
+ <<"-export([", UnusedFun/binary, "]).\n">>,
+ els_protocol:range(#{from => Pos, to => Pos})
+ )
+ ]
+ end.
-spec ignore_variable(uri(), range(), binary(), [binary()]) -> [map()].
ignore_variable(Uri, Range, _Data, [UnusedVariable]) ->
- {ok, Document} = els_utils:lookup_document(Uri),
- POIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
- case ensure_range(els_range:to_poi_range(Range), UnusedVariable, POIs) of
- {ok, VarRange} ->
- [ make_edit_action( Uri
- , <<"Add '_' to '", UnusedVariable/binary, "'">>
- , ?CODE_ACTION_KIND_QUICKFIX
- , <<"_", UnusedVariable/binary>>
- , els_protocol:range(VarRange)) ];
- error ->
- []
- end.
+ {ok, Document} = els_utils:lookup_document(Uri),
+ POIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
+ case ensure_range(els_range:to_poi_range(Range), UnusedVariable, POIs) of
+ {ok, VarRange} ->
+ [
+ make_edit_action(
+ Uri,
+ <<"Add '_' to '", UnusedVariable/binary, "'">>,
+ ?CODE_ACTION_KIND_QUICKFIX,
+ <<"_", UnusedVariable/binary>>,
+ els_protocol:range(VarRange)
+ )
+ ];
+ error ->
+ []
+ end.
-spec suggest_variable(uri(), range(), binary(), [binary()]) -> [map()].
suggest_variable(Uri, Range, _Data, [Var]) ->
- %% Supply a quickfix to replace an unbound variable with the most similar
- %% variable name in scope.
- {ok, Document} = els_utils:lookup_document(Uri),
- POIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
- case ensure_range(els_range:to_poi_range(Range), Var, POIs) of
- {ok, VarRange} ->
- ScopeRange = els_scope:variable_scope_range(VarRange, Document),
- VarsInScope = [atom_to_binary(Id, utf8) ||
- #{range := R, id := Id} <- POIs,
- els_range:in(R, ScopeRange),
- els_range:compare(R, VarRange)],
- VariableDistances =
- [{els_utils:jaro_distance(V, Var), V} || V <- VarsInScope, V =/= Var],
- [ make_edit_action( Uri
- , <<"Did you mean '", V/binary, "'?">>
- , ?CODE_ACTION_KIND_QUICKFIX
- , V
- , els_protocol:range(VarRange))
- || {Distance, V} <- lists:reverse(lists:usort(VariableDistances)),
- Distance > 0.8];
- error ->
- []
- end.
+ %% Supply a quickfix to replace an unbound variable with the most similar
+ %% variable name in scope.
+ {ok, Document} = els_utils:lookup_document(Uri),
+ POIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
+ case ensure_range(els_range:to_poi_range(Range), Var, POIs) of
+ {ok, VarRange} ->
+ ScopeRange = els_scope:variable_scope_range(VarRange, Document),
+ VarsInScope = [
+ atom_to_binary(Id, utf8)
+ || #{range := R, id := Id} <- POIs,
+ els_range:in(R, ScopeRange),
+ els_range:compare(R, VarRange)
+ ],
+ VariableDistances =
+ [{els_utils:jaro_distance(V, Var), V} || V <- VarsInScope, V =/= Var],
+ [
+ make_edit_action(
+ Uri,
+ <<"Did you mean '", V/binary, "'?">>,
+ ?CODE_ACTION_KIND_QUICKFIX,
+ V,
+ els_protocol:range(VarRange)
+ )
+ || {Distance, V} <- lists:reverse(lists:usort(VariableDistances)),
+ Distance > 0.8
+ ];
+ error ->
+ []
+ end.
-spec fix_module_name(uri(), range(), binary(), [binary()]) -> [map()].
fix_module_name(Uri, Range0, _Data, [ModName, FileName]) ->
- {ok, Document} = els_utils:lookup_document(Uri),
- POIs = els_poi:sort(els_dt_document:pois(Document, [module])),
- case ensure_range(els_range:to_poi_range(Range0), ModName, POIs) of
- {ok, Range} ->
- [ make_edit_action( Uri
- , <<"Change to -module(", FileName/binary, ").">>
- , ?CODE_ACTION_KIND_QUICKFIX
- , FileName
- , els_protocol:range(Range)) ];
- error ->
- []
- end.
+ {ok, Document} = els_utils:lookup_document(Uri),
+ POIs = els_poi:sort(els_dt_document:pois(Document, [module])),
+ case ensure_range(els_range:to_poi_range(Range0), ModName, POIs) of
+ {ok, Range} ->
+ [
+ make_edit_action(
+ Uri,
+ <<"Change to -module(", FileName/binary, ").">>,
+ ?CODE_ACTION_KIND_QUICKFIX,
+ FileName,
+ els_protocol:range(Range)
+ )
+ ];
+ error ->
+ []
+ end.
-spec remove_macro(uri(), range(), binary(), [binary()]) -> [map()].
remove_macro(Uri, Range, _Data, [Macro]) ->
- %% Supply a quickfix to remove the unused Macro
- {ok, Document} = els_utils:lookup_document(Uri),
- POIs = els_poi:sort(els_dt_document:pois(Document, [define])),
- case ensure_range(els_range:to_poi_range(Range), Macro, POIs) of
- {ok, MacroRange} ->
- LineRange = els_range:line(MacroRange),
- [ make_edit_action( Uri
- , <<"Remove unused macro ", Macro/binary, ".">>
- , ?CODE_ACTION_KIND_QUICKFIX
- , <<"">>
- , els_protocol:range(LineRange)) ];
- error ->
- []
- end.
+ %% Supply a quickfix to remove the unused Macro
+ {ok, Document} = els_utils:lookup_document(Uri),
+ POIs = els_poi:sort(els_dt_document:pois(Document, [define])),
+ case ensure_range(els_range:to_poi_range(Range), Macro, POIs) of
+ {ok, MacroRange} ->
+ LineRange = els_range:line(MacroRange),
+ [
+ make_edit_action(
+ Uri,
+ <<"Remove unused macro ", Macro/binary, ".">>,
+ ?CODE_ACTION_KIND_QUICKFIX,
+ <<"">>,
+ els_protocol:range(LineRange)
+ )
+ ];
+ error ->
+ []
+ end.
-spec remove_unused(uri(), range(), binary(), [binary()]) -> [map()].
remove_unused(Uri, _Range0, Data, [Import]) ->
- {ok, Document} = els_utils:lookup_document(Uri),
- case els_range:inclusion_range(Data, Document) of
- {ok, UnusedRange} ->
- LineRange = els_range:line(UnusedRange),
- [ make_edit_action( Uri
- , <<"Remove unused -include_lib(", Import/binary, ").">>
- , ?CODE_ACTION_KIND_QUICKFIX
- , <<>>
- , els_protocol:range(LineRange)) ];
- error ->
- []
- end.
+ {ok, Document} = els_utils:lookup_document(Uri),
+ case els_range:inclusion_range(Data, Document) of
+ {ok, UnusedRange} ->
+ LineRange = els_range:line(UnusedRange),
+ [
+ make_edit_action(
+ Uri,
+ <<"Remove unused -include_lib(", Import/binary, ").">>,
+ ?CODE_ACTION_KIND_QUICKFIX,
+ <<>>,
+ els_protocol:range(LineRange)
+ )
+ ];
+ error ->
+ []
+ end.
-spec ensure_range(poi_range(), binary(), [poi()]) -> {ok, poi_range()} | error.
ensure_range(#{from := {Line, _}}, SubjectId, POIs) ->
- SubjectAtom = binary_to_atom(SubjectId, utf8),
- Ranges = [R || #{range := R, id := Id} <- POIs,
- els_range:in(R, #{from => {Line, 1}, to => {Line + 1, 1}}),
- Id =:= SubjectAtom],
- case Ranges of
- [] ->
- error;
- [Range|_] ->
- {ok, Range}
- end.
+ SubjectAtom = binary_to_atom(SubjectId, utf8),
+ Ranges = [
+ R
+ || #{range := R, id := Id} <- POIs,
+ els_range:in(R, #{from => {Line, 1}, to => {Line + 1, 1}}),
+ Id =:= SubjectAtom
+ ],
+ case Ranges of
+ [] ->
+ error;
+ [Range | _] ->
+ {ok, Range}
+ end.
--spec make_edit_action(uri(), binary(), binary(), binary(), range())
- -> map().
+-spec make_edit_action(uri(), binary(), binary(), binary(), range()) ->
+ map().
make_edit_action(Uri, Title, Kind, Text, Range) ->
- #{ title => Title
- , kind => Kind
- , edit => edit(Uri, Text, Range)
- }.
+ #{
+ title => Title,
+ kind => Kind,
+ edit => edit(Uri, Text, Range)
+ }.
-spec edit(uri(), binary(), range()) -> workspace_edit().
edit(Uri, Text, Range) ->
- #{changes => #{Uri => [#{newText => Text, range => Range}]}}.
+ #{changes => #{Uri => [#{newText => Text, range => Range}]}}.
diff --git a/apps/els_lsp/src/els_code_lens.erl b/apps/els_lsp/src/els_code_lens.erl
index 9468b6976..87f59dd51 100644
--- a/apps/els_lsp/src/els_code_lens.erl
+++ b/apps/els_lsp/src/els_code_lens.erl
@@ -10,23 +10,25 @@
-callback init(els_dt_document:item()) -> state().
-callback command(els_dt_document:item(), poi(), state()) ->
- els_command:command().
+ els_command:command().
-callback is_default() -> boolean().
-callback pois(els_dt_document:item()) -> [poi()].
-callback precondition(els_dt_document:item()) -> boolean().
--optional_callbacks([ init/1
- , precondition/1
- ]).
+-optional_callbacks([
+ init/1,
+ precondition/1
+]).
%%==============================================================================
%% API
%%==============================================================================
--export([ available_lenses/0
- , default_lenses/0
- , enabled_lenses/0
- , lenses/2
- ]).
+-export([
+ available_lenses/0,
+ default_lenses/0,
+ enabled_lenses/0,
+ lenses/2
+]).
%%==============================================================================
%% Includes
@@ -39,16 +41,18 @@
%% Type Definitions
%%==============================================================================
--type lens() :: #{ range := range()
- , command => els_command:command()
- , data => any()
- }.
+-type lens() :: #{
+ range := range(),
+ command => els_command:command(),
+ data => any()
+}.
-type lens_id() :: binary().
-type state() :: any().
--export_type([ lens/0
- , lens_id/0
- , state/0
- ]).
+-export_type([
+ lens/0,
+ lens_id/0,
+ state/0
+]).
%%==============================================================================
%% API
@@ -56,41 +60,45 @@
-spec available_lenses() -> [lens_id()].
available_lenses() ->
- [ <<"ct-run-test">>
- , <<"server-info">>
- , <<"show-behaviour-usages">>
- , <<"suggest-spec">>
- , <<"function-references">>
- ].
+ [
+ <<"ct-run-test">>,
+ <<"server-info">>,
+ <<"show-behaviour-usages">>,
+ <<"suggest-spec">>,
+ <<"function-references">>
+ ].
-spec default_lenses() -> [lens_id()].
default_lenses() ->
- [Id || Id <- available_lenses(), (cb_module(Id)):is_default()].
+ [Id || Id <- available_lenses(), (cb_module(Id)):is_default()].
-spec enabled_lenses() -> [lens_id()].
enabled_lenses() ->
- Config = els_config:get(lenses),
- Default = default_lenses(),
- Enabled = maps:get("enabled", Config, []),
- Disabled = maps:get("disabled", Config, []),
- lists:usort((Default ++ valid(Enabled)) -- valid(Disabled)).
+ Config = els_config:get(lenses),
+ Default = default_lenses(),
+ Enabled = maps:get("enabled", Config, []),
+ Disabled = maps:get("disabled", Config, []),
+ lists:usort((Default ++ valid(Enabled)) -- valid(Disabled)).
-spec lenses(lens_id(), els_dt_document:item()) -> [lens()].
lenses(Id, Document) ->
- CbModule = cb_module(Id),
- case precondition(CbModule, Document) of
- true ->
- State = case erlang:function_exported(CbModule, init, 1) of
- true ->
- CbModule:init(Document);
- false ->
- 'state_not_initialized'
- end,
- [make_lens(CbModule, Document, POI, State) ||
- POI <- CbModule:pois(Document)];
- false ->
- []
- end.
+ CbModule = cb_module(Id),
+ case precondition(CbModule, Document) of
+ true ->
+ State =
+ case erlang:function_exported(CbModule, init, 1) of
+ true ->
+ CbModule:init(Document);
+ false ->
+ 'state_not_initialized'
+ end,
+ [
+ make_lens(CbModule, Document, POI, State)
+ || POI <- CbModule:pois(Document)
+ ];
+ false ->
+ []
+ end.
%%==============================================================================
%% Constructors
@@ -98,16 +106,17 @@ lenses(Id, Document) ->
-spec make_lens(atom(), els_dt_document:item(), poi(), state()) -> lens().
make_lens(CbModule, Document, #{range := Range} = POI, State) ->
- #{ range => els_protocol:range(Range)
- , command => CbModule:command(Document, POI, State)
- , data => []
- }.
+ #{
+ range => els_protocol:range(Range),
+ command => CbModule:command(Document, POI, State),
+ data => []
+ }.
%% @doc Return the callback module for a given Code Lens Identifier
-spec cb_module(lens_id()) -> module().
cb_module(Id0) ->
- Id = re:replace(Id0, "-", "_", [global, {return, binary}]),
- binary_to_atom(<<"els_code_lens_", Id/binary>>, utf8).
+ Id = re:replace(Id0, "-", "_", [global, {return, binary}]),
+ binary_to_atom(<<"els_code_lens_", Id/binary>>, utf8).
%%==============================================================================
%% Internal Functions
@@ -115,32 +124,35 @@ cb_module(Id0) ->
-spec is_valid(lens_id()) -> boolean().
is_valid(Id) ->
- lists:member(Id, available_lenses()).
+ lists:member(Id, available_lenses()).
-spec valid([string()]) -> [lens_id()].
valid(Ids0) ->
- Ids = [els_utils:to_binary(Id) || Id <- Ids0],
- {Valid, Invalid} = lists:partition(fun is_valid/1, Ids),
- case Invalid of
- [] ->
- ok;
- _ ->
- Fmt = "Skipping invalid lenses in config file: ~p",
- Args = [Invalid],
- Msg = lists:flatten(io_lib:format(Fmt, Args)),
- ?LOG_WARNING(Msg),
- els_server:send_notification(<<"window/showMessage">>,
- #{ type => ?MESSAGE_TYPE_WARNING,
- message => els_utils:to_binary(Msg)
- })
- end,
- Valid.
+ Ids = [els_utils:to_binary(Id) || Id <- Ids0],
+ {Valid, Invalid} = lists:partition(fun is_valid/1, Ids),
+ case Invalid of
+ [] ->
+ ok;
+ _ ->
+ Fmt = "Skipping invalid lenses in config file: ~p",
+ Args = [Invalid],
+ Msg = lists:flatten(io_lib:format(Fmt, Args)),
+ ?LOG_WARNING(Msg),
+ els_server:send_notification(
+ <<"window/showMessage">>,
+ #{
+ type => ?MESSAGE_TYPE_WARNING,
+ message => els_utils:to_binary(Msg)
+ }
+ )
+ end,
+ Valid.
-spec precondition(atom(), els_dt_document:item()) -> boolean().
precondition(CbModule, Document) ->
- case erlang:function_exported(CbModule, precondition, 1) of
- true ->
- CbModule:precondition(Document);
- false ->
- true
- end.
+ case erlang:function_exported(CbModule, precondition, 1) of
+ true ->
+ CbModule:precondition(Document);
+ false ->
+ true
+ end.
diff --git a/apps/els_lsp/src/els_code_lens_ct_run_test.erl b/apps/els_lsp/src/els_code_lens_ct_run_test.erl
index 75b9a395e..e9c5d62e7 100644
--- a/apps/els_lsp/src/els_code_lens_ct_run_test.erl
+++ b/apps/els_lsp/src/els_code_lens_ct_run_test.erl
@@ -5,51 +5,54 @@
-module(els_code_lens_ct_run_test).
-behaviour(els_code_lens).
--export([ command/3
- , is_default/0
- , pois/1
- , precondition/1
- ]).
+-export([
+ command/3,
+ is_default/0,
+ pois/1,
+ precondition/1
+]).
-include("els_lsp.hrl").
-spec command(els_dt_document:item(), poi(), els_code_lens:state()) ->
- els_command:command().
+ els_command:command().
command(#{uri := Uri} = _Document, POI, _State) ->
- #{id := {F, A}, range := #{from := {Line, _}}} = POI,
- Title = <<"Run test">>,
- CommandId = <<"ct-run-test">>,
- CommandArgs = [ #{ module => els_uri:module(Uri)
- , function => F
- , arity => A
- , uri => Uri
- , line => Line
- }
- ],
- els_command:make_command(Title, CommandId, CommandArgs).
+ #{id := {F, A}, range := #{from := {Line, _}}} = POI,
+ Title = <<"Run test">>,
+ CommandId = <<"ct-run-test">>,
+ CommandArgs = [
+ #{
+ module => els_uri:module(Uri),
+ function => F,
+ arity => A,
+ uri => Uri,
+ line => Line
+ }
+ ],
+ els_command:make_command(Title, CommandId, CommandArgs).
-spec is_default() -> boolean().
is_default() ->
- false.
+ false.
-spec pois(els_dt_document:item()) -> [poi()].
pois(Document) ->
- Functions = els_dt_document:pois(Document, [function]),
- [POI || #{id := {F, 1}} = POI <- Functions, not is_blacklisted(F)].
+ Functions = els_dt_document:pois(Document, [function]),
+ [POI || #{id := {F, 1}} = POI <- Functions, not is_blacklisted(F)].
-spec precondition(els_dt_document:item()) -> boolean().
precondition(Document) ->
- Includes = els_dt_document:pois(Document, [include_lib]),
- case [POI || #{id := "common_test/include/ct.hrl"} = POI <- Includes] of
- [] ->
- false;
- _ ->
- true
- end.
+ Includes = els_dt_document:pois(Document, [include_lib]),
+ case [POI || #{id := "common_test/include/ct.hrl"} = POI <- Includes] of
+ [] ->
+ false;
+ _ ->
+ true
+ end.
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec is_blacklisted(atom()) -> boolean().
is_blacklisted(Function) ->
- lists:member(Function, [init_per_suite, end_per_suite, group]).
+ lists:member(Function, [init_per_suite, end_per_suite, group]).
diff --git a/apps/els_lsp/src/els_code_lens_function_references.erl b/apps/els_lsp/src/els_code_lens_function_references.erl
index a2b6f01c9..a49e1561b 100644
--- a/apps/els_lsp/src/els_code_lens_function_references.erl
+++ b/apps/els_lsp/src/els_code_lens_function_references.erl
@@ -1,34 +1,35 @@
-module(els_code_lens_function_references).
-behaviour(els_code_lens).
--export([ is_default/0
- , pois/1
- , command/3
- ]).
+-export([
+ is_default/0,
+ pois/1,
+ command/3
+]).
-include("els_lsp.hrl").
-spec is_default() -> boolean().
is_default() ->
- true.
+ true.
-spec pois(els_dt_document:item()) -> [poi()].
pois(Document) ->
- els_dt_document:pois(Document, [function]).
+ els_dt_document:pois(Document, [function]).
-spec command(els_dt_document:item(), poi(), els_code_lens:state()) ->
- els_command:command().
+ els_command:command().
command(Document, POI, _State) ->
- Title = title(Document, POI),
- CommandId = <<"function-references">>,
- CommandArgs = [],
- els_command:make_command(Title, CommandId, CommandArgs).
+ Title = title(Document, POI),
+ CommandId = <<"function-references">>,
+ CommandArgs = [],
+ els_command:make_command(Title, CommandId, CommandArgs).
-spec title(els_dt_document:item(), poi()) -> binary().
title(Document, POI) ->
- #{uri := Uri} = Document,
- M = els_uri:module(Uri),
- #{id := {F, A}} = POI,
- {ok, References} = els_dt_references:find_by_id(function, {M, F, A}),
- N = length(References),
- unicode:characters_to_binary(io_lib:format("Used ~p times", [N])).
+ #{uri := Uri} = Document,
+ M = els_uri:module(Uri),
+ #{id := {F, A}} = POI,
+ {ok, References} = els_dt_references:find_by_id(function, {M, F, A}),
+ N = length(References),
+ unicode:characters_to_binary(io_lib:format("Used ~p times", [N])).
diff --git a/apps/els_lsp/src/els_code_lens_provider.erl b/apps/els_lsp/src/els_code_lens_provider.erl
index 1493281f5..08feb56de 100644
--- a/apps/els_lsp/src/els_code_lens_provider.erl
+++ b/apps/els_lsp/src/els_code_lens_provider.erl
@@ -1,10 +1,11 @@
-module(els_code_lens_provider).
-behaviour(els_provider).
--export([ is_enabled/0
- , options/0
- , handle_request/2
- ]).
+-export([
+ is_enabled/0,
+ options/0,
+ handle_request/2
+]).
-include("els_lsp.hrl").
-include_lib("kernel/include/logger.hrl").
@@ -17,34 +18,38 @@ is_enabled() -> true.
-spec options() -> map().
options() ->
- #{ resolveProvider => false }.
+ #{resolveProvider => false}.
-spec handle_request(any(), any()) -> {async, uri(), pid()}.
handle_request({document_codelens, Params}, _State) ->
- #{ <<"textDocument">> := #{ <<"uri">> := Uri}} = Params,
- ?LOG_DEBUG("Starting lenses job [uri=~p]", [Uri]),
- Job = run_lenses_job(Uri),
- {async, Uri, Job}.
+ #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
+ ?LOG_DEBUG("Starting lenses job [uri=~p]", [Uri]),
+ Job = run_lenses_job(Uri),
+ {async, Uri, Job}.
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec run_lenses_job(uri()) -> pid().
run_lenses_job(Uri) ->
- {ok, Document} = els_utils:lookup_document(Uri),
- Config = #{ task =>
- fun(Doc, _) ->
- lists:flatten(
- [els_code_lens:lenses(Id, Doc) ||
- Id <- els_code_lens:enabled_lenses()])
- end
- , entries => [Document]
- , title => <<"Lenses">>
- , on_complete =>
- fun(Lenses) ->
- els_provider ! {result, Lenses, self()},
- ok
- end
- },
- {ok, Pid} = els_background_job:new(Config),
- Pid.
+ {ok, Document} = els_utils:lookup_document(Uri),
+ Config = #{
+ task =>
+ fun(Doc, _) ->
+ lists:flatten(
+ [
+ els_code_lens:lenses(Id, Doc)
+ || Id <- els_code_lens:enabled_lenses()
+ ]
+ )
+ end,
+ entries => [Document],
+ title => <<"Lenses">>,
+ on_complete =>
+ fun(Lenses) ->
+ els_provider ! {result, Lenses, self()},
+ ok
+ end
+ },
+ {ok, Pid} = els_background_job:new(Config),
+ Pid.
diff --git a/apps/els_lsp/src/els_code_lens_server_info.erl b/apps/els_lsp/src/els_code_lens_server_info.erl
index a500c865d..1d7f90aff 100644
--- a/apps/els_lsp/src/els_code_lens_server_info.erl
+++ b/apps/els_lsp/src/els_code_lens_server_info.erl
@@ -5,31 +5,32 @@
-module(els_code_lens_server_info).
-behaviour(els_code_lens).
--export([ command/3
- , is_default/0
- , pois/1
- ]).
+-export([
+ command/3,
+ is_default/0,
+ pois/1
+]).
-include("els_lsp.hrl").
-spec command(els_dt_document:item(), poi(), els_code_lens:state()) ->
- els_command:command().
+ els_command:command().
command(_Document, _POI, _State) ->
- Title = title(),
- CommandId = <<"server-info">>,
- CommandArgs = [],
- els_command:make_command(Title, CommandId, CommandArgs).
+ Title = title(),
+ CommandId = <<"server-info">>,
+ CommandArgs = [],
+ els_command:make_command(Title, CommandId, CommandArgs).
-spec is_default() -> boolean().
is_default() ->
- false.
+ false.
-spec pois(els_dt_document:item()) -> [poi()].
pois(_Document) ->
- %% Return a dummy POI on the first line
- [els_poi:new(#{from => {1, 1}, to => {2, 1}}, dummy, dummy)].
+ %% Return a dummy POI on the first line
+ [els_poi:new(#{from => {1, 1}, to => {2, 1}}, dummy, dummy)].
-spec title() -> binary().
title() ->
- Root = filename:basename(els_uri:path(els_config:get(root_uri))),
- <<"Erlang LS (in ", Root/binary, ") info">>.
+ Root = filename:basename(els_uri:path(els_config:get(root_uri))),
+ <<"Erlang LS (in ", Root/binary, ") info">>.
diff --git a/apps/els_lsp/src/els_code_lens_show_behaviour_usages.erl b/apps/els_lsp/src/els_code_lens_show_behaviour_usages.erl
index d76c06e7d..47cd6dee8 100644
--- a/apps/els_lsp/src/els_code_lens_show_behaviour_usages.erl
+++ b/apps/els_lsp/src/els_code_lens_show_behaviour_usages.erl
@@ -5,40 +5,41 @@
-module(els_code_lens_show_behaviour_usages).
-behaviour(els_code_lens).
--export([ command/3
- , is_default/0
- , pois/1
- , precondition/1
- ]).
+-export([
+ command/3,
+ is_default/0,
+ pois/1,
+ precondition/1
+]).
-include("els_lsp.hrl").
-spec command(els_dt_document:item(), poi(), els_code_lens:state()) ->
- els_command:command().
+ els_command:command().
command(_Document, POI, _State) ->
- Title = title(POI),
- CommandId = <<"show-behaviour-usages">>,
- CommandArgs = [],
- els_command:make_command(Title, CommandId, CommandArgs).
+ Title = title(POI),
+ CommandId = <<"show-behaviour-usages">>,
+ CommandArgs = [],
+ els_command:make_command(Title, CommandId, CommandArgs).
-spec is_default() -> boolean().
is_default() ->
- true.
+ true.
-spec precondition(els_dt_document:item()) -> boolean().
precondition(Document) ->
- %% A behaviour is defined by the presence of one more callback
- %% attributes.
- Callbacks = els_dt_document:pois(Document, [callback]),
- length(Callbacks) > 0.
+ %% A behaviour is defined by the presence of one more callback
+ %% attributes.
+ Callbacks = els_dt_document:pois(Document, [callback]),
+ length(Callbacks) > 0.
-spec pois(els_dt_document:item()) -> [poi()].
pois(Document) ->
- els_dt_document:pois(Document, [module]).
+ els_dt_document:pois(Document, [module]).
-spec title(poi()) -> binary().
title(#{id := Id} = _POI) ->
- {ok, Refs} = els_dt_references:find_by_id(behaviour, Id),
- Count = length(Refs),
- Msg = io_lib:format("Behaviour used in ~p place(s)", [Count]),
- els_utils:to_binary(Msg).
+ {ok, Refs} = els_dt_references:find_by_id(behaviour, Id),
+ Count = length(Refs),
+ Msg = io_lib:format("Behaviour used in ~p place(s)", [Count]),
+ els_utils:to_binary(Msg).
diff --git a/apps/els_lsp/src/els_code_lens_suggest_spec.erl b/apps/els_lsp/src/els_code_lens_suggest_spec.erl
index 483c71ee4..29df08130 100644
--- a/apps/els_lsp/src/els_code_lens_suggest_spec.erl
+++ b/apps/els_lsp/src/els_code_lens_suggest_spec.erl
@@ -4,11 +4,12 @@
-module(els_code_lens_suggest_spec).
-behaviour(els_code_lens).
--export([ init/1
- , command/3
- , is_default/0
- , pois/1
- ]).
+-export([
+ init/1,
+ command/3,
+ is_default/0,
+ pois/1
+]).
%%==============================================================================
%% Includes
@@ -26,46 +27,49 @@
%%==============================================================================
-spec init(els_dt_document:item()) -> state().
init(#{uri := Uri} = _Document) ->
- try els_typer:get_info(Uri) of
- Info ->
- Info
- catch C:E:S ->
- Fmt =
- "Cannot extract typer info.~n"
- "Class: ~p~n"
- "Exception: ~p~n"
- "Stacktrace: ~p~n",
- ?LOG_WARNING(Fmt, [C, E, S]),
- 'no_info'
- end.
+ try els_typer:get_info(Uri) of
+ Info ->
+ Info
+ catch
+ C:E:S ->
+ Fmt =
+ "Cannot extract typer info.~n"
+ "Class: ~p~n"
+ "Exception: ~p~n"
+ "Stacktrace: ~p~n",
+ ?LOG_WARNING(Fmt, [C, E, S]),
+ 'no_info'
+ end.
-spec command(els_dt_document:item(), poi(), state()) -> els_command:command().
command(_Document, _POI, 'no_info') ->
- CommandId = <<"suggest-spec">>,
- Title = <<"Cannot extract specs (check logs for details)">>,
- els_command:make_command(Title, CommandId, []);
+ CommandId = <<"suggest-spec">>,
+ Title = <<"Cannot extract specs (check logs for details)">>,
+ els_command:make_command(Title, CommandId, []);
command(Document, #{range := #{from := {Line, _}}} = POI, Info) ->
- #{uri := Uri} = Document,
- CommandId = <<"suggest-spec">>,
- Spec = get_type_spec(POI, Info),
- Title = truncate_spec_title(Spec, spec_title_max_length()),
- CommandArgs = [ #{ uri => Uri
- , line => Line
- , spec => Spec
- }
- ],
- els_command:make_command(Title, CommandId, CommandArgs).
+ #{uri := Uri} = Document,
+ CommandId = <<"suggest-spec">>,
+ Spec = get_type_spec(POI, Info),
+ Title = truncate_spec_title(Spec, spec_title_max_length()),
+ CommandArgs = [
+ #{
+ uri => Uri,
+ line => Line,
+ spec => Spec
+ }
+ ],
+ els_command:make_command(Title, CommandId, CommandArgs).
-spec is_default() -> boolean().
is_default() ->
- true.
+ true.
-spec pois(els_dt_document:item()) -> [poi()].
pois(Document) ->
- Functions = els_dt_document:pois(Document, [function]),
- Specs = els_dt_document:pois(Document, [spec]),
- SpecsIds = [Id || #{id := Id} <- Specs],
- [POI || #{id := Id} = POI <- Functions, not lists:member(Id, SpecsIds)].
+ Functions = els_dt_document:pois(Document, [function]),
+ Specs = els_dt_document:pois(Document, [spec]),
+ SpecsIds = [Id || #{id := Id} <- Specs],
+ [POI || #{id := Id} = POI <- Functions, not lists:member(Id, SpecsIds)].
%%==============================================================================
%% Internal functions
@@ -78,17 +82,17 @@ get_type_spec(POI, Info) ->
-spec truncate_spec_title(binary(), integer()) -> binary().
truncate_spec_title(Spec, MaxLength) ->
- Length = string:length(Spec),
- case Length > MaxLength of
- true ->
- Title = unicode:characters_to_binary(
- string:slice(Spec, 0, MaxLength - 3)
- ),
- <>;
- false ->
- Spec
- end.
+ Length = string:length(Spec),
+ case Length > MaxLength of
+ true ->
+ Title = unicode:characters_to_binary(
+ string:slice(Spec, 0, MaxLength - 3)
+ ),
+ <>;
+ false ->
+ Spec
+ end.
-spec spec_title_max_length() -> integer().
spec_title_max_length() ->
- application:get_env(els_core, suggest_spec_title_max_length, 100).
+ application:get_env(els_core, suggest_spec_title_max_length, 100).
diff --git a/apps/els_lsp/src/els_code_navigation.erl b/apps/els_lsp/src/els_code_navigation.erl
index 967001ef9..c0b7a487d 100644
--- a/apps/els_lsp/src/els_code_navigation.erl
+++ b/apps/els_lsp/src/els_code_navigation.erl
@@ -8,9 +8,10 @@
%%==============================================================================
%% API
--export([ goto_definition/2
- , find_in_scope/2
- ]).
+-export([
+ goto_definition/2,
+ find_in_scope/2
+]).
%%==============================================================================
%% Includes
@@ -23,187 +24,217 @@
%%==============================================================================
-spec goto_definition(uri(), poi()) ->
- {ok, uri(), poi()} | {error, any()}.
-goto_definition( Uri
- , Var = #{kind := variable}
- ) ->
- %% This will naively try to find the definition of a variable by finding the
- %% first occurrence of the variable in variable scope.
- case find_in_scope(Uri, Var) of
- [Var|_] -> {error, already_at_definition};
- [POI|_] -> {ok, Uri, POI};
- [] -> {error, nothing_in_scope} % Probably due to parse error
- end;
-goto_definition( _Uri
- , #{ kind := Kind, id := {M, F, A} }
- ) when Kind =:= application;
- Kind =:= implicit_fun;
- Kind =:= import_entry ->
- case els_utils:find_module(M) of
- {ok, Uri} -> find(Uri, function, {F, A});
- {error, Error} -> {error, Error}
- end;
-goto_definition( Uri
- , #{ kind := Kind, id := {F, A}} = POI
- ) when Kind =:= application;
- Kind =:= implicit_fun;
- Kind =:= export_entry ->
- %% try to find local function first
- %% fall back to bif search if unsuccessful
- case find(Uri, function, {F, A}) of
- {error, Error} ->
- case is_imported_bif(Uri, F, A) of
- true ->
- goto_definition(Uri, POI#{id := {erlang, F, A}});
- false ->
- {error, Error}
- end;
- Result ->
- Result
- end;
-goto_definition( _Uri
- , #{ kind := Kind, id := Module }
- ) when Kind =:= atom;
- Kind =:= behaviour;
- Kind =:= module ->
- case els_utils:find_module(Module) of
- {ok, Uri} -> find(Uri, module, Module);
- {error, Error} -> {error, Error}
- end;
-goto_definition(Uri
- , #{ kind := macro
- , id := {MacroName, _Arity} = Define } = POI
- ) ->
- case find(Uri, define, Define) of
- {error, not_found} ->
- goto_definition(Uri, POI#{id => MacroName});
- Else ->
- Else
- end;
-goto_definition(Uri, #{ kind := macro, id := Define }) ->
- find(Uri, define, Define);
-goto_definition(Uri, #{ kind := record_expr, id := Record }) ->
- find(Uri, record, Record);
-goto_definition(Uri, #{ kind := record_field, id := {Record, Field} }) ->
- find(Uri, record_def_field, {Record, Field});
-goto_definition(_Uri, #{ kind := Kind, id := Id }
- ) when Kind =:= include;
- Kind =:= include_lib ->
- case els_utils:find_header(els_utils:filename_to_atom(Id)) of
- {ok, Uri} -> {ok, Uri, beginning()};
- {error, Error} -> {error, Error}
- end;
-goto_definition(_Uri, #{ kind := type_application, id := {M, T, A} }) ->
- case els_utils:find_module(M) of
- {ok, Uri} -> find(Uri, type_definition, {T, A});
- {error, Error} -> {error, Error}
- end;
-goto_definition(Uri, #{ kind := Kind, id := {T, A} })
- when Kind =:= type_application; Kind =:= export_type_entry ->
- find(Uri, type_definition, {T, A});
-goto_definition(_Uri, #{ kind := parse_transform, id := Module }) ->
- case els_utils:find_module(Module) of
- {ok, Uri} -> find(Uri, module, Module);
- {error, Error} -> {error, Error}
- end;
+ {ok, uri(), poi()} | {error, any()}.
+goto_definition(
+ Uri,
+ Var = #{kind := variable}
+) ->
+ %% This will naively try to find the definition of a variable by finding the
+ %% first occurrence of the variable in variable scope.
+ case find_in_scope(Uri, Var) of
+ [Var | _] -> {error, already_at_definition};
+ [POI | _] -> {ok, Uri, POI};
+ % Probably due to parse error
+ [] -> {error, nothing_in_scope}
+ end;
+goto_definition(
+ _Uri,
+ #{kind := Kind, id := {M, F, A}}
+) when
+ Kind =:= application;
+ Kind =:= implicit_fun;
+ Kind =:= import_entry
+->
+ case els_utils:find_module(M) of
+ {ok, Uri} -> find(Uri, function, {F, A});
+ {error, Error} -> {error, Error}
+ end;
+goto_definition(
+ Uri,
+ #{kind := Kind, id := {F, A}} = POI
+) when
+ Kind =:= application;
+ Kind =:= implicit_fun;
+ Kind =:= export_entry
+->
+ %% try to find local function first
+ %% fall back to bif search if unsuccessful
+ case find(Uri, function, {F, A}) of
+ {error, Error} ->
+ case is_imported_bif(Uri, F, A) of
+ true ->
+ goto_definition(Uri, POI#{id := {erlang, F, A}});
+ false ->
+ {error, Error}
+ end;
+ Result ->
+ Result
+ end;
+goto_definition(
+ _Uri,
+ #{kind := Kind, id := Module}
+) when
+ Kind =:= atom;
+ Kind =:= behaviour;
+ Kind =:= module
+->
+ case els_utils:find_module(Module) of
+ {ok, Uri} -> find(Uri, module, Module);
+ {error, Error} -> {error, Error}
+ end;
+goto_definition(
+ Uri,
+ #{
+ kind := macro,
+ id := {MacroName, _Arity} = Define
+ } = POI
+) ->
+ case find(Uri, define, Define) of
+ {error, not_found} ->
+ goto_definition(Uri, POI#{id => MacroName});
+ Else ->
+ Else
+ end;
+goto_definition(Uri, #{kind := macro, id := Define}) ->
+ find(Uri, define, Define);
+goto_definition(Uri, #{kind := record_expr, id := Record}) ->
+ find(Uri, record, Record);
+goto_definition(Uri, #{kind := record_field, id := {Record, Field}}) ->
+ find(Uri, record_def_field, {Record, Field});
+goto_definition(_Uri, #{kind := Kind, id := Id}) when
+ Kind =:= include;
+ Kind =:= include_lib
+->
+ case els_utils:find_header(els_utils:filename_to_atom(Id)) of
+ {ok, Uri} -> {ok, Uri, beginning()};
+ {error, Error} -> {error, Error}
+ end;
+goto_definition(_Uri, #{kind := type_application, id := {M, T, A}}) ->
+ case els_utils:find_module(M) of
+ {ok, Uri} -> find(Uri, type_definition, {T, A});
+ {error, Error} -> {error, Error}
+ end;
+goto_definition(Uri, #{kind := Kind, id := {T, A}}) when
+ Kind =:= type_application; Kind =:= export_type_entry
+->
+ find(Uri, type_definition, {T, A});
+goto_definition(_Uri, #{kind := parse_transform, id := Module}) ->
+ case els_utils:find_module(Module) of
+ {ok, Uri} -> find(Uri, module, Module);
+ {error, Error} -> {error, Error}
+ end;
goto_definition(_Filename, _) ->
- {error, not_found}.
+ {error, not_found}.
-spec is_imported_bif(uri(), atom(), non_neg_integer()) -> boolean().
is_imported_bif(_Uri, F, A) ->
- OldBif = erl_internal:old_bif(F, A),
- Bif = erl_internal:bif(F, A),
- case {OldBif, Bif} of
- %% Cannot be shadowed, always imported
- {true, true} ->
- true;
- %% It's not a BIF at all
- {false, false} ->
- false;
- %% The hard case, just jump to the bif for now
- {_, _} ->
- true
- end.
+ OldBif = erl_internal:old_bif(F, A),
+ Bif = erl_internal:bif(F, A),
+ case {OldBif, Bif} of
+ %% Cannot be shadowed, always imported
+ {true, true} ->
+ true;
+ %% It's not a BIF at all
+ {false, false} ->
+ false;
+ %% The hard case, just jump to the bif for now
+ {_, _} ->
+ true
+ end.
-spec find(uri() | [uri()], poi_kind(), any()) ->
- {ok, uri(), poi()} | {error, not_found}.
+ {ok, uri(), poi()} | {error, not_found}.
find(UriOrUris, Kind, Data) ->
- find(UriOrUris, Kind, Data, sets:new()).
+ find(UriOrUris, Kind, Data, sets:new()).
-spec find(uri() | [uri()], poi_kind(), any(), sets:set(binary())) ->
- {ok, uri(), poi()} | {error, not_found}.
+ {ok, uri(), poi()} | {error, not_found}.
find([], _Kind, _Data, _AlreadyVisited) ->
- {error, not_found};
-find([Uri|Uris0], Kind, Data, AlreadyVisited) ->
- case sets:is_element(Uri, AlreadyVisited) of
- true ->
- find(Uris0, Kind, Data, AlreadyVisited);
- false ->
- AlreadyVisited2 = sets:add_element(Uri, AlreadyVisited),
- case els_utils:lookup_document(Uri) of
- {ok, Document} ->
- find_in_document([Uri|Uris0], Document, Kind, Data, AlreadyVisited2);
- {error, _Error} ->
- find(Uris0, Kind, Data, AlreadyVisited2)
- end
- end;
+ {error, not_found};
+find([Uri | Uris0], Kind, Data, AlreadyVisited) ->
+ case sets:is_element(Uri, AlreadyVisited) of
+ true ->
+ find(Uris0, Kind, Data, AlreadyVisited);
+ false ->
+ AlreadyVisited2 = sets:add_element(Uri, AlreadyVisited),
+ case els_utils:lookup_document(Uri) of
+ {ok, Document} ->
+ find_in_document([Uri | Uris0], Document, Kind, Data, AlreadyVisited2);
+ {error, _Error} ->
+ find(Uris0, Kind, Data, AlreadyVisited2)
+ end
+ end;
find(Uri, Kind, Data, AlreadyVisited) ->
- find([Uri], Kind, Data, AlreadyVisited).
+ find([Uri], Kind, Data, AlreadyVisited).
--spec find_in_document(uri() | [uri()], els_dt_document:item(), poi_kind()
- , any(), sets:set(binary())) ->
- {ok, uri(), poi()} | {error, any()}.
-find_in_document([Uri|Uris0], Document, Kind, Data, AlreadyVisited) ->
- POIs = els_dt_document:pois(Document, [Kind]),
- case [POI || #{id := Id} = POI <- POIs, Id =:= Data] of
- [] ->
- case maybe_imported(Document, Kind, Data) of
- {ok, U, P} -> {ok, U, P};
- {error, not_found} ->
- find(lists:usort(include_uris(Document) ++ Uris0), Kind, Data,
- AlreadyVisited)
- end;
- Definitions ->
- {ok, Uri, hd(els_poi:sort(Definitions))}
- end.
+-spec find_in_document(
+ uri() | [uri()],
+ els_dt_document:item(),
+ poi_kind(),
+ any(),
+ sets:set(binary())
+) ->
+ {ok, uri(), poi()} | {error, any()}.
+find_in_document([Uri | Uris0], Document, Kind, Data, AlreadyVisited) ->
+ POIs = els_dt_document:pois(Document, [Kind]),
+ case [POI || #{id := Id} = POI <- POIs, Id =:= Data] of
+ [] ->
+ case maybe_imported(Document, Kind, Data) of
+ {ok, U, P} ->
+ {ok, U, P};
+ {error, not_found} ->
+ find(
+ lists:usort(include_uris(Document) ++ Uris0),
+ Kind,
+ Data,
+ AlreadyVisited
+ )
+ end;
+ Definitions ->
+ {ok, Uri, hd(els_poi:sort(Definitions))}
+ end.
-spec include_uris(els_dt_document:item()) -> [uri()].
include_uris(Document) ->
- POIs = els_dt_document:pois(Document, [include, include_lib]),
- lists:foldl(fun add_include_uri/2, [], POIs).
+ POIs = els_dt_document:pois(Document, [include, include_lib]),
+ lists:foldl(fun add_include_uri/2, [], POIs).
-spec add_include_uri(poi(), [uri()]) -> [uri()].
-add_include_uri(#{ id := Id }, Acc) ->
- case els_utils:find_header(els_utils:filename_to_atom(Id)) of
- {ok, Uri} -> [Uri | Acc];
- {error, _Error} -> Acc
- end.
+add_include_uri(#{id := Id}, Acc) ->
+ case els_utils:find_header(els_utils:filename_to_atom(Id)) of
+ {ok, Uri} -> [Uri | Acc];
+ {error, _Error} -> Acc
+ end.
-spec beginning() -> #{range => #{from => {1, 1}, to => {1, 1}}}.
beginning() ->
- #{range => #{from => {1, 1}, to => {1, 1}}}.
+ #{range => #{from => {1, 1}, to => {1, 1}}}.
%% @doc check for a match in any of the module imported functions.
-spec maybe_imported(els_dt_document:item(), poi_kind(), any()) ->
- {ok, uri(), poi()} | {error, not_found}.
+ {ok, uri(), poi()} | {error, not_found}.
maybe_imported(Document, function, {F, A}) ->
- POIs = els_dt_document:pois(Document, [import_entry]),
- case [{M, F, A} || #{id := {M, FP, AP}} <- POIs, FP =:= F, AP =:= A] of
- [] -> {error, not_found};
- [{M, F, A}|_] ->
- case els_utils:find_module(M) of
- {ok, Uri0} -> find(Uri0, function, {F, A});
- {error, not_found} -> {error, not_found}
- end
- end;
+ POIs = els_dt_document:pois(Document, [import_entry]),
+ case [{M, F, A} || #{id := {M, FP, AP}} <- POIs, FP =:= F, AP =:= A] of
+ [] ->
+ {error, not_found};
+ [{M, F, A} | _] ->
+ case els_utils:find_module(M) of
+ {ok, Uri0} -> find(Uri0, function, {F, A});
+ {error, not_found} -> {error, not_found}
+ end
+ end;
maybe_imported(_Document, _Kind, _Data) ->
- {error, not_found}.
+ {error, not_found}.
-spec find_in_scope(uri(), poi()) -> [poi()].
find_in_scope(Uri, #{kind := variable, id := VarId, range := VarRange}) ->
- {ok, Document} = els_utils:lookup_document(Uri),
- VarPOIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
- ScopeRange = els_scope:variable_scope_range(VarRange, Document),
- [POI || #{range := Range, id := Id} = POI <- VarPOIs,
- els_range:in(Range, ScopeRange),
- Id =:= VarId].
+ {ok, Document} = els_utils:lookup_document(Uri),
+ VarPOIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
+ ScopeRange = els_scope:variable_scope_range(VarRange, Document),
+ [
+ POI
+ || #{range := Range, id := Id} = POI <- VarPOIs,
+ els_range:in(Range, ScopeRange),
+ Id =:= VarId
+ ].
diff --git a/apps/els_lsp/src/els_command_ct_run_test.erl b/apps/els_lsp/src/els_command_ct_run_test.erl
index 7541f29bf..7a0fb39cb 100644
--- a/apps/els_lsp/src/els_command_ct_run_test.erl
+++ b/apps/els_lsp/src/els_command_ct_run_test.erl
@@ -1,6 +1,6 @@
-module(els_command_ct_run_test).
--export([ execute/1, task/2 ]).
+-export([execute/1, task/2]).
%%==============================================================================
%% Includes
@@ -9,50 +9,54 @@
-include_lib("kernel/include/logger.hrl").
-spec execute(map()) -> ok.
-execute(#{ <<"module">> := M
- , <<"function">> := F
- , <<"arity">> := A
- , <<"line">> := Line
- , <<"uri">> := Uri
- }) ->
- Msg = io_lib:format("Running Common Test [mfa=~s:~s/~p]", [M, F, A]),
- ?LOG_INFO(Msg, []),
- Title = unicode:characters_to_binary(Msg),
- Suite = els_uri:module(Uri),
- Case = binary_to_atom(F, utf8),
- Config = #{ task => fun ?MODULE:task/2
- , entries => [{Uri, Line, Suite, Case}]
- , title => Title
- , show_percentages => false
- },
- {ok, _Pid} = els_background_job:new(Config),
- ok.
+execute(#{
+ <<"module">> := M,
+ <<"function">> := F,
+ <<"arity">> := A,
+ <<"line">> := Line,
+ <<"uri">> := Uri
+}) ->
+ Msg = io_lib:format("Running Common Test [mfa=~s:~s/~p]", [M, F, A]),
+ ?LOG_INFO(Msg, []),
+ Title = unicode:characters_to_binary(Msg),
+ Suite = els_uri:module(Uri),
+ Case = binary_to_atom(F, utf8),
+ Config = #{
+ task => fun ?MODULE:task/2,
+ entries => [{Uri, Line, Suite, Case}],
+ title => Title,
+ show_percentages => false
+ },
+ {ok, _Pid} = els_background_job:new(Config),
+ ok.
-spec task({uri(), pos_integer(), atom(), atom()}, any()) -> ok.
task({Uri, Line, Suite, Case}, _State) ->
- case run_test(Suite, Case) of
- {ok, IO} ->
- ?LOG_INFO("CT Test passed", []),
- publish_result(Uri, Line, ?DIAGNOSTIC_INFO, IO);
- {Error, IO} ->
- Message = els_utils:to_binary(io_lib:format("~p", [Error])),
- ?LOG_INFO("CT Test failed [error=~p] [io=~p]", [Error, IO]),
- publish_result(Uri, Line, ?DIAGNOSTIC_ERROR, Message)
- end.
+ case run_test(Suite, Case) of
+ {ok, IO} ->
+ ?LOG_INFO("CT Test passed", []),
+ publish_result(Uri, Line, ?DIAGNOSTIC_INFO, IO);
+ {Error, IO} ->
+ Message = els_utils:to_binary(io_lib:format("~p", [Error])),
+ ?LOG_INFO("CT Test failed [error=~p] [io=~p]", [Error, IO]),
+ publish_result(Uri, Line, ?DIAGNOSTIC_ERROR, Message)
+ end.
--spec publish_result( uri()
- , pos_integer()
- , els_diagnostics:severity()
- , binary()) -> ok.
+-spec publish_result(
+ uri(),
+ pos_integer(),
+ els_diagnostics:severity(),
+ binary()
+) -> ok.
publish_result(Uri, Line, Severity, Message) ->
- Range = els_protocol:range(#{from => {Line, 1}, to => {Line + 1, 1}}),
- Source = <<"Common Test">>,
- D = els_diagnostics:make_diagnostic(Range, Message, Severity, Source),
- els_diagnostics_provider:publish(Uri, [D]),
- ok.
+ Range = els_protocol:range(#{from => {Line, 1}, to => {Line + 1, 1}}),
+ Source = <<"Common Test">>,
+ D = els_diagnostics:make_diagnostic(Range, Message, Severity, Source),
+ els_diagnostics_provider:publish(Uri, [D]),
+ ok.
-spec run_test(atom(), atom()) -> {ok, binary()} | {error, any()}.
run_test(Suite, Case) ->
- Module = els_config_ct_run_test:get_module(),
- Function = els_config_ct_run_test:get_function(),
- els_distribution_server:rpc_call(Module, Function, [Suite, Case], infinity).
+ Module = els_config_ct_run_test:get_module(),
+ Function = els_config_ct_run_test:get_function(),
+ els_distribution_server:rpc_call(Module, Function, [Suite, Case], infinity).
diff --git a/apps/els_lsp/src/els_compiler_diagnostics.erl b/apps/els_lsp/src/els_compiler_diagnostics.erl
index ca79adfa0..5e2f3af37 100644
--- a/apps/els_lsp/src/els_compiler_diagnostics.erl
+++ b/apps/els_lsp/src/els_compiler_diagnostics.erl
@@ -7,23 +7,26 @@
%% Exports
%%==============================================================================
-behaviour(els_diagnostics).
--export([ is_default/0
- , run/1
- , source/0
- , on_complete/2
- ]).
+-export([
+ is_default/0,
+ run/1,
+ source/0,
+ on_complete/2
+]).
%% identity function for our own diagnostics
--export([ format_error/1 ]).
+-export([format_error/1]).
--export([ inclusion_range/2
- , inclusion_range/3
- ]).
+-export([
+ inclusion_range/2,
+ inclusion_range/3
+]).
--export([ include_options/0
- , macro_options/0
- , telemetry/2
- ]).
+-export([
+ include_options/0,
+ macro_options/0,
+ telemetry/2
+]).
%%==============================================================================
%% Includes
@@ -34,10 +37,10 @@
%%==============================================================================
%% Type Definitions
%%==============================================================================
--type compiler_info() :: {erl_anno:anno() | 'none', module(), any()}.
--type compiler_msg() :: {file:filename(), [compiler_info()]}.
--type macro_config() :: #{string() => string()}.
--type macro_option() :: {'d', atom()} | {'d', atom(), any()}.
+-type compiler_info() :: {erl_anno:anno() | 'none', module(), any()}.
+-type compiler_msg() :: {file:filename(), [compiler_info()]}.
+-type macro_config() :: #{string() => string()}.
+-type macro_option() :: {'d', atom()} | {'d', atom(), any()}.
-type include_option() :: {'i', string()}.
%%==============================================================================
@@ -46,82 +49,90 @@
-spec is_default() -> boolean().
is_default() ->
- true.
+ true.
-spec run(uri()) -> [els_diagnostics:diagnostic()].
run(Uri) ->
- case filename:extension(Uri) of
- <<".erl">> ->
- compile(Uri);
- <<".hrl">> ->
- %% It does not make sense to 'compile' header files in isolation
- %% (e.g. using the compile:forms/1 function). That would in fact
- %% produce a big number of false positive errors and warnings,
- %% including 'record not used' or 'module attribute not
- %% specified'. An alternative could be to use a 'fake' module
- %% that simply includes the file, but that feels a bit too
- %% hackish. As a compromise, we decided to parse the include
- %% file, since that allows us to identify most of the common
- %% errors in header files.
- parse(Uri);
- <<".escript">> ->
- parse_escript(Uri);
- _Ext ->
- ?LOG_DEBUG("Skipping diagnostics due to extension [uri=~p]", [Uri]),
- []
- end.
+ case filename:extension(Uri) of
+ <<".erl">> ->
+ compile(Uri);
+ <<".hrl">> ->
+ %% It does not make sense to 'compile' header files in isolation
+ %% (e.g. using the compile:forms/1 function). That would in fact
+ %% produce a big number of false positive errors and warnings,
+ %% including 'record not used' or 'module attribute not
+ %% specified'. An alternative could be to use a 'fake' module
+ %% that simply includes the file, but that feels a bit too
+ %% hackish. As a compromise, we decided to parse the include
+ %% file, since that allows us to identify most of the common
+ %% errors in header files.
+ parse(Uri);
+ <<".escript">> ->
+ parse_escript(Uri);
+ _Ext ->
+ ?LOG_DEBUG("Skipping diagnostics due to extension [uri=~p]", [Uri]),
+ []
+ end.
-spec source() -> binary().
source() ->
- <<"Compiler">>.
+ <<"Compiler">>.
-spec on_complete(uri(), [els_diagnostics:diagnostic()]) -> ok.
on_complete(Uri, Diagnostics) ->
- ?MODULE:telemetry(Uri, Diagnostics),
- maybe_compile_and_load(Uri, Diagnostics).
+ ?MODULE:telemetry(Uri, Diagnostics),
+ maybe_compile_and_load(Uri, Diagnostics).
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec compile(uri()) -> [els_diagnostics:diagnostic()].
compile(Uri) ->
- Dependencies = els_diagnostics_utils:dependencies(Uri),
- Path = els_utils:to_list(els_uri:path(Uri)),
- case compile_file(Path, Dependencies) of
- {{ok, _, WS}, Diagnostics} ->
- Diagnostics ++
- diagnostics(Path, WS, ?DIAGNOSTIC_WARNING);
- {{error, ES, WS}, Diagnostics} ->
- Diagnostics ++
- diagnostics(Path, WS, ?DIAGNOSTIC_WARNING) ++
- diagnostics(Path, ES, ?DIAGNOSTIC_ERROR)
- end.
+ Dependencies = els_diagnostics_utils:dependencies(Uri),
+ Path = els_utils:to_list(els_uri:path(Uri)),
+ case compile_file(Path, Dependencies) of
+ {{ok, _, WS}, Diagnostics} ->
+ Diagnostics ++
+ diagnostics(Path, WS, ?DIAGNOSTIC_WARNING);
+ {{error, ES, WS}, Diagnostics} ->
+ Diagnostics ++
+ diagnostics(Path, WS, ?DIAGNOSTIC_WARNING) ++
+ diagnostics(Path, ES, ?DIAGNOSTIC_ERROR)
+ end.
-spec parse(uri()) -> [els_diagnostics:diagnostic()].
parse(Uri) ->
- FileName = els_utils:to_list(els_uri:path(Uri)),
- Document = case els_dt_document:lookup(Uri) of
- {ok, [DocItem]} ->
- DocItem;
- _ ->
- undefined
- end,
- {ok, Epp} = epp:open([ {name, FileName}
- , {includes, els_config:get(include_paths)}
- ]),
- Res = [epp_diagnostic(Document, Anno, Module, Desc)
- || {error, {Anno, Module, Desc}} <- epp:parse_file(Epp)],
- epp:close(Epp),
- Res.
+ FileName = els_utils:to_list(els_uri:path(Uri)),
+ Document =
+ case els_dt_document:lookup(Uri) of
+ {ok, [DocItem]} ->
+ DocItem;
+ _ ->
+ undefined
+ end,
+ {ok, Epp} = epp:open([
+ {name, FileName},
+ {includes, els_config:get(include_paths)}
+ ]),
+ Res = [
+ epp_diagnostic(Document, Anno, Module, Desc)
+ || {error, {Anno, Module, Desc}} <- epp:parse_file(Epp)
+ ],
+ epp:close(Epp),
+ Res.
%% Possible cases to handle
%% ,{error,{19,erl_parse,["syntax error before: ","'-'"]}}
%% ,{error,{1,epp,{error,1,{undefined,'MODULE',none}}}}
%% ,{error,{3,epp,{error,"including nonexistent_macro.hrl is not allowed"}}}
%% ,{error,{3,epp,{include,file,"yaws.hrl"}}}
--spec epp_diagnostic(els_dt_document:item(),
- erl_anno:anno(), module(), any()) ->
- els_diagnostics:diagnostic().
+-spec epp_diagnostic(
+ els_dt_document:item(),
+ erl_anno:anno(),
+ module(),
+ any()
+) ->
+ els_diagnostics:diagnostic().
epp_diagnostic(Document, Anno, epp, {error, Anno, Reason}) ->
%% Workaround for https://bugs.erlang.org/browse/ERL-1310
epp_diagnostic(Document, Anno, epp, Reason);
@@ -130,14 +141,14 @@ epp_diagnostic(Document, Anno, Module, Desc) ->
-spec parse_escript(uri()) -> [els_diagnostics:diagnostic()].
parse_escript(Uri) ->
- FileName = els_utils:to_list(els_uri:path(Uri)),
- case els_escript:extract(FileName) of
- {ok, WS} ->
- diagnostics(FileName, WS, ?DIAGNOSTIC_WARNING);
- {error, ES, WS} ->
- diagnostics(FileName, WS, ?DIAGNOSTIC_WARNING) ++
- diagnostics(FileName, ES, ?DIAGNOSTIC_ERROR)
- end.
+ FileName = els_utils:to_list(els_uri:path(Uri)),
+ case els_escript:extract(FileName) of
+ {ok, WS} ->
+ diagnostics(FileName, WS, ?DIAGNOSTIC_WARNING);
+ {error, ES, WS} ->
+ diagnostics(FileName, WS, ?DIAGNOSTIC_WARNING) ++
+ diagnostics(FileName, ES, ?DIAGNOSTIC_ERROR)
+ end.
%% @doc Convert compiler messages into diagnostics
%%
@@ -148,64 +159,72 @@ parse_escript(Uri) ->
%% and they are presented to the user by highlighting the line where
%% the file inclusion happens.
-spec diagnostics(list(), [compiler_msg()], els_diagnostics:severity()) ->
- [els_diagnostics:diagnostic()].
+ [els_diagnostics:diagnostic()].
diagnostics(Path, List, Severity) ->
- Uri = els_uri:uri(els_utils:to_binary(Path)),
- case els_utils:lookup_document(Uri) of
- {ok, Document} ->
- lists:flatten([[ diagnostic( Path
- , MessagePath
- , range(Document, Anno)
- , Document
- , Module
- , Desc
- , Severity)
- || {Anno, Module, Desc} <- Info]
- || {MessagePath, Info} <- List]);
- {error, _Error} ->
- []
- end.
-
--spec diagnostic( string()
- , string()
- , poi_range()
- , els_dt_document:item()
- , module()
- , string()
- , integer()) -> els_diagnostics:diagnostic().
+ Uri = els_uri:uri(els_utils:to_binary(Path)),
+ case els_utils:lookup_document(Uri) of
+ {ok, Document} ->
+ lists:flatten([
+ [
+ diagnostic(
+ Path,
+ MessagePath,
+ range(Document, Anno),
+ Document,
+ Module,
+ Desc,
+ Severity
+ )
+ || {Anno, Module, Desc} <- Info
+ ]
+ || {MessagePath, Info} <- List
+ ]);
+ {error, _Error} ->
+ []
+ end.
+
+-spec diagnostic(
+ string(),
+ string(),
+ poi_range(),
+ els_dt_document:item(),
+ module(),
+ string(),
+ integer()
+) -> els_diagnostics:diagnostic().
diagnostic(Path, Path, Range, _Document, Module, Desc, Severity) ->
- %% The compiler message is related to the same .erl file, so
- %% preserve the location information.
- diagnostic(Range, Module, Desc, Severity);
+ %% The compiler message is related to the same .erl file, so
+ %% preserve the location information.
+ diagnostic(Range, Module, Desc, Severity);
diagnostic(_Path, MessagePath, Range, Document, Module, Desc0, Severity) ->
- #{from := {Line, _}} = Range,
- InclusionRange = inclusion_range(MessagePath, Document),
- %% The compiler message is related to an included file. Replace the
- %% original location with the location of the file inclusion.
- %% And re-route the format_error call to this module as a no-op
- Desc1 = Module:format_error(Desc0),
- Desc = io_lib:format("Issue in included file (~p): ~s", [Line, Desc1]),
- diagnostic(InclusionRange, ?MODULE, Desc, Severity).
+ #{from := {Line, _}} = Range,
+ InclusionRange = inclusion_range(MessagePath, Document),
+ %% The compiler message is related to an included file. Replace the
+ %% original location with the location of the file inclusion.
+ %% And re-route the format_error call to this module as a no-op
+ Desc1 = Module:format_error(Desc0),
+ Desc = io_lib:format("Issue in included file (~p): ~s", [Line, Desc1]),
+ diagnostic(InclusionRange, ?MODULE, Desc, Severity).
-spec diagnostic(poi_range(), module(), string(), integer()) ->
- els_diagnostics:diagnostic().
+ els_diagnostics:diagnostic().
diagnostic(Range, Module, Desc, Severity) ->
- Message0 = lists:flatten(Module:format_error(Desc)),
- Message = els_utils:to_binary(Message0),
- Code = make_code(Module, Desc),
- #{ range => els_protocol:range(Range)
- , message => Message
- , severity => Severity
- , source => source()
- , code => Code
- }.
+ Message0 = lists:flatten(Module:format_error(Desc)),
+ Message = els_utils:to_binary(Message0),
+ Code = make_code(Module, Desc),
+ #{
+ range => els_protocol:range(Range),
+ message => Message,
+ severity => Severity,
+ source => source(),
+ code => Code
+ }.
%% @doc NOP function for the call to 'Module:format_error/1' in diagnostic/4
%% above.
-spec format_error(string()) -> [string()].
format_error(Str) ->
- Str.
-
+ Str.
%-----------------------------------------------------------------------
@@ -216,429 +235,422 @@ format_error(Str) ->
%% This file
make_code(els_compiler_diagnostics, _) ->
- <<"L0000">>;
-
+ <<"L0000">>;
%% compiler-8.0.2/src/compile.erl
make_code(compile, no_crypto) ->
- <<"C1000">>;
+ <<"C1000">>;
make_code(compile, bad_crypto_key) ->
- <<"C1001">>;
+ <<"C1001">>;
make_code(compile, no_crypto_key) ->
- <<"C1002">>;
+ <<"C1002">>;
make_code(compile, {open, _E}) ->
- <<"C1003">>;
+ <<"C1003">>;
make_code(compile, {epp, _E}) ->
- make_code(epp, compile);
+ make_code(epp, compile);
make_code(compile, write_error) ->
- <<"C1004">>;
+ <<"C1004">>;
make_code(compile, {write_error, _Error}) ->
- <<"C1005">>;
+ <<"C1005">>;
make_code(compile, {rename, _From, _To, _Error}) ->
- <<"C1006">>;
+ <<"C1006">>;
make_code(compile, {parse_transform, _M, _R}) ->
- <<"C1007">>;
+ <<"C1007">>;
make_code(compile, {undef_parse_transform, _M}) ->
- <<"C1008">>;
+ <<"C1008">>;
make_code(compile, {core_transform, _M, _R}) ->
- <<"C1009">>;
+ <<"C1009">>;
make_code(compile, {crash, _Pass, _Reason, _Stk}) ->
- <<"C1010">>;
+ <<"C1010">>;
make_code(compile, {bad_return, _Pass, _Reason}) ->
- <<"C1011">>;
+ <<"C1011">>;
make_code(compile, {module_name, _Mod, _Filename}) ->
- <<"C1012">>;
+ <<"C1012">>;
make_code(compile, _Other) ->
- <<"C1099">>;
-
+ <<"C1099">>;
%% syntax_tools-2.6/src/epp_dodger.erl
make_code(epp_dodger, macro_args) ->
- <<"D1100">>;
+ <<"D1100">>;
make_code(epp_dodger, {error, _Error}) ->
- <<"D1101">>;
+ <<"D1101">>;
make_code(epp_dodger, {warning, _Error}) ->
- <<"D1102">>;
+ <<"D1102">>;
make_code(epp_dodger, {unknown, _Reason}) ->
- <<"D1103">>;
+ <<"D1103">>;
make_code(epp_dodger, _Other) ->
- <<"D1199">>;
-
+ <<"D1199">>;
%% stdlib-3.15.2/src/erl_lint.erl
make_code(erl_lint, undefined_module) ->
- <<"L1201">>;
+ <<"L1201">>;
make_code(erl_lint, redefine_module) ->
- <<"L1202">>;
+ <<"L1202">>;
make_code(erl_lint, pmod_unsupported) ->
- <<"L1203">>;
+ <<"L1203">>;
make_code(erl_lint, non_latin1_module_unsupported) ->
- <<"L1204">>;
+ <<"L1204">>;
make_code(erl_lint, invalid_call) ->
- <<"L1205">>;
+ <<"L1205">>;
make_code(erl_lint, invalid_record) ->
- <<"L1206">>;
+ <<"L1206">>;
make_code(erl_lint, {attribute, _A}) ->
- <<"L1207">>;
+ <<"L1207">>;
make_code(erl_lint, {missing_qlc_hrl, _A}) ->
- <<"L1208">>;
+ <<"L1208">>;
make_code(erl_lint, {redefine_import, {{_F, _A}, _M}}) ->
- <<"L1209">>;
+ <<"L1209">>;
make_code(erl_lint, {bad_inline, {_F, _A}}) ->
- <<"L1210">>;
+ <<"L1210">>;
make_code(erl_lint, {invalid_deprecated, _D}) ->
- <<"L1211">>;
+ <<"L1211">>;
make_code(erl_lint, {bad_deprecated, {_F, _A}}) ->
- <<"L1212">>;
+ <<"L1212">>;
make_code(erl_lint, {invalid_removed, _D}) ->
- <<"L1213">>;
+ <<"L1213">>;
make_code(erl_lint, {bad_removed, {F, A}}) when F =:= '_'; A =:= '_' ->
- <<"L1214">>;
+ <<"L1214">>;
make_code(erl_lint, {bad_removed, {_F, _A}}) ->
- <<"L1215">>;
+ <<"L1215">>;
make_code(erl_lint, {bad_nowarn_unused_function, {_F, _A}}) ->
- <<"L1216">>;
+ <<"L1216">>;
make_code(erl_lint, {bad_nowarn_bif_clash, {_F, _A}}) ->
- <<"L1217">>;
+ <<"L1217">>;
make_code(erl_lint, disallowed_nowarn_bif_clash) ->
- <<"L1218">>;
+ <<"L1218">>;
make_code(erl_lint, {bad_on_load, _Term}) ->
- <<"L1219">>;
+ <<"L1219">>;
make_code(erl_lint, multiple_on_loads) ->
- <<"L1220">>;
+ <<"L1220">>;
make_code(erl_lint, {bad_on_load_arity, {_F, _A}}) ->
- <<"L1221">>;
+ <<"L1221">>;
make_code(erl_lint, {undefined_on_load, {_F, _A}}) ->
- <<"L1222">>;
+ <<"L1222">>;
make_code(erl_lint, nif_inline) ->
- <<"L1223">>;
+ <<"L1223">>;
make_code(erl_lint, export_all) ->
- <<"L1224">>;
+ <<"L1224">>;
make_code(erl_lint, {duplicated_export, {_F, _A}}) ->
- <<"L1225">>;
+ <<"L1225">>;
make_code(erl_lint, {unused_import, {{_F, _A}, _M}}) ->
- <<"L1226">>;
+ <<"L1226">>;
make_code(erl_lint, {undefined_function, {_F, _A}}) ->
- <<"L1227">>;
+ <<"L1227">>;
make_code(erl_lint, {redefine_function, {_F, _A}}) ->
- <<"L1228">>;
+ <<"L1228">>;
make_code(erl_lint, {define_import, {_F, _A}}) ->
- <<"L1229">>;
+ <<"L1229">>;
make_code(erl_lint, {unused_function, {_F, _A}}) ->
- <<"L1230">>;
+ <<"L1230">>;
make_code(erl_lint, {call_to_redefined_bif, {_F, _A}}) ->
- <<"L1231">>;
+ <<"L1231">>;
make_code(erl_lint, {call_to_redefined_old_bif, {_F, _A}}) ->
- <<"L1232">>;
+ <<"L1232">>;
make_code(erl_lint, {redefine_old_bif_import, {_F, _A}}) ->
- <<"L1233">>;
+ <<"L1233">>;
make_code(erl_lint, {redefine_bif_import, {_F, _A}}) ->
- <<"L1234">>;
+ <<"L1234">>;
make_code(erl_lint, {deprecated, _MFA, _String, _Rel}) ->
- <<"L1235">>;
+ <<"L1235">>;
make_code(erl_lint, {deprecated, _MFA, String}) when is_list(String) ->
- <<"L1236">>;
+ <<"L1236">>;
make_code(erl_lint, {deprecated_type, {_M1, _F1, _A1}, _String, _Rel}) ->
- <<"L1237">>;
-make_code(erl_lint, {deprecated_type, {_M1, _F1, _A1}, String})
- when is_list(String) ->
- <<"L1238">>;
+ <<"L1237">>;
+make_code(erl_lint, {deprecated_type, {_M1, _F1, _A1}, String}) when
+ is_list(String)
+->
+ <<"L1238">>;
make_code(erl_lint, {removed, _MFA, _ReplacementMFA, _Rel}) ->
- <<"L1239">>;
+ <<"L1239">>;
make_code(erl_lint, {removed, _MFA, String}) when is_list(String) ->
- <<"L1240">>;
+ <<"L1240">>;
make_code(erl_lint, {removed_type, _MNA, _String}) ->
- <<"L1241">>;
+ <<"L1241">>;
make_code(erl_lint, {obsolete_guard, {_F, _A}}) ->
- <<"L1242">>;
+ <<"L1242">>;
make_code(erl_lint, {obsolete_guard_overridden, _Test}) ->
- <<"L1243">>;
+ <<"L1243">>;
make_code(erl_lint, {too_many_arguments, _Arity}) ->
- <<"L1244">>;
+ <<"L1244">>;
make_code(erl_lint, illegal_pattern) ->
- <<"L1245">>;
+ <<"L1245">>;
make_code(erl_lint, illegal_map_key) ->
- <<"L1246">>;
+ <<"L1246">>;
make_code(erl_lint, illegal_bin_pattern) ->
- <<"L1247">>;
+ <<"L1247">>;
make_code(erl_lint, illegal_expr) ->
- <<"L1248">>;
+ <<"L1248">>;
make_code(erl_lint, {illegal_guard_local_call, {_F, _A}}) ->
- <<"L1249">>;
+ <<"L1249">>;
make_code(erl_lint, illegal_guard_expr) ->
- <<"L1250">>;
+ <<"L1250">>;
make_code(erl_lint, illegal_map_construction) ->
- <<"L1251">>;
+ <<"L1251">>;
make_code(erl_lint, {undefined_record, _T}) ->
- <<"L1252">>;
+ <<"L1252">>;
make_code(erl_lint, {redefine_record, _T}) ->
- <<"L1253">>;
+ <<"L1253">>;
make_code(erl_lint, {redefine_field, _T, _F}) ->
- <<"L1254">>;
+ <<"L1254">>;
make_code(erl_lint, bad_multi_field_init) ->
- <<"L1255">>;
+ <<"L1255">>;
make_code(erl_lint, {undefined_field, _T, _F}) ->
- <<"L1256">>;
+ <<"L1256">>;
make_code(erl_lint, illegal_record_info) ->
- <<"L1257">>;
+ <<"L1257">>;
make_code(erl_lint, {field_name_is_variable, _T, _F}) ->
- <<"L1258">>;
+ <<"L1258">>;
make_code(erl_lint, {wildcard_in_update, _T}) ->
- <<"L1259">>;
+ <<"L1259">>;
make_code(erl_lint, {unused_record, _T}) ->
- <<"L1260">>;
+ <<"L1260">>;
make_code(erl_lint, {untyped_record, _T}) ->
- <<"L1261">>;
+ <<"L1261">>;
make_code(erl_lint, {unbound_var, _V}) ->
- <<"L1262">>;
+ <<"L1262">>;
make_code(erl_lint, {unsafe_var, _V, {_What, _Where}}) ->
- <<"L1263">>;
+ <<"L1263">>;
make_code(erl_lint, {exported_var, _V, {_What, _Where}}) ->
- <<"L1264">>;
+ <<"L1264">>;
make_code(erl_lint, {match_underscore_var, _V}) ->
- <<"L1265">>;
+ <<"L1265">>;
make_code(erl_lint, {match_underscore_var_pat, _V}) ->
- <<"L1266">>;
+ <<"L1266">>;
make_code(erl_lint, {shadowed_var, _V, _In}) ->
- <<"L1267">>;
+ <<"L1267">>;
make_code(erl_lint, {unused_var, _V}) ->
- <<"L1268">>;
+ <<"L1268">>;
make_code(erl_lint, {variable_in_record_def, _V}) ->
- <<"L1269">>;
+ <<"L1269">>;
make_code(erl_lint, {stacktrace_guard, _V}) ->
- <<"L1270">>;
+ <<"L1270">>;
make_code(erl_lint, {stacktrace_bound, _V}) ->
- <<"L1271">>;
+ <<"L1271">>;
make_code(erl_lint, {undefined_bittype, _Type}) ->
- <<"L1272">>;
+ <<"L1272">>;
make_code(erl_lint, {bittype_mismatch, _Val1, _Val2, _What}) ->
- <<"L1273">>;
+ <<"L1273">>;
make_code(erl_lint, bittype_unit) ->
- <<"L1274">>;
+ <<"L1274">>;
make_code(erl_lint, illegal_bitsize) ->
- <<"L1275">>;
+ <<"L1275">>;
make_code(erl_lint, {illegal_bitsize_local_call, {_F, _A}}) ->
- <<"L1276">>;
+ <<"L1276">>;
make_code(erl_lint, non_integer_bitsize) ->
- <<"L1277">>;
+ <<"L1277">>;
make_code(erl_lint, unsized_binary_not_at_end) ->
- <<"L1278">>;
+ <<"L1278">>;
make_code(erl_lint, typed_literal_string) ->
- <<"L1279">>;
+ <<"L1279">>;
make_code(erl_lint, utf_bittype_size_or_unit) ->
- <<"L1280">>;
+ <<"L1280">>;
make_code(erl_lint, {bad_bitsize, _Type}) ->
- <<"L1281">>;
+ <<"L1281">>;
make_code(erl_lint, unsized_binary_in_bin_gen_pattern) ->
- <<"L1282">>;
-make_code(erl_lint, {conflicting_behaviours,
- {_Name, _Arity}, _B, _FirstL, _FirstB}) ->
- <<"L1283">>;
+ <<"L1282">>;
+make_code(erl_lint, {conflicting_behaviours, {_Name, _Arity}, _B, _FirstL, _FirstB}) ->
+ <<"L1283">>;
make_code(erl_lint, {undefined_behaviour_func, {_Func, _Arity}, _Behaviour}) ->
- <<"L1284">>;
+ <<"L1284">>;
make_code(erl_lint, {undefined_behaviour, _Behaviour}) ->
- <<"L1285">>;
+ <<"L1285">>;
make_code(erl_lint, {undefined_behaviour_callbacks, _Behaviour}) ->
- <<"L1286">>;
+ <<"L1286">>;
make_code(erl_lint, {ill_defined_behaviour_callbacks, _Behaviour}) ->
- <<"L1287">>;
+ <<"L1287">>;
make_code(erl_lint, {ill_defined_optional_callbacks, _Behaviour}) ->
- <<"L1288">>;
+ <<"L1288">>;
make_code(erl_lint, {behaviour_info, {_M, _F, _A}}) ->
- <<"L1289">>;
+ <<"L1289">>;
make_code(erl_lint, {redefine_optional_callback, {_F, _A}}) ->
- <<"L1290">>;
+ <<"L1290">>;
make_code(erl_lint, {undefined_callback, {_M, _F, _A}}) ->
- <<"L1291">>;
+ <<"L1291">>;
make_code(erl_lint, {singleton_typevar, _Name}) ->
- <<"L1292">>;
+ <<"L1292">>;
make_code(erl_lint, {bad_export_type, _ETs}) ->
- <<"L1293">>;
+ <<"L1293">>;
make_code(erl_lint, {duplicated_export_type, {_T, _A}}) ->
- <<"L1294">>;
+ <<"L1294">>;
make_code(erl_lint, {undefined_type, {_TypeName, _Arity}}) ->
- <<"L1295">>;
+ <<"L1295">>;
make_code(erl_lint, {unused_type, {_TypeName, _Arity}}) ->
- <<"L1296">>;
+ <<"L1296">>;
make_code(erl_lint, {new_builtin_type, {_TypeName, _Arity}}) ->
- <<"L1297">>;
+ <<"L1297">>;
make_code(erl_lint, {builtin_type, {_TypeName, _Arity}}) ->
- <<"L1298">>;
+ <<"L1298">>;
make_code(erl_lint, {renamed_type, _OldName, _NewName}) ->
- <<"L1299">>;
+ <<"L1299">>;
make_code(erl_lint, {redefine_type, {_TypeName, _Arity}}) ->
- <<"L1300">>;
+ <<"L1300">>;
make_code(erl_lint, {type_syntax, _Constr}) ->
- <<"L1301">>;
+ <<"L1301">>;
make_code(erl_lint, old_abstract_code) ->
- <<"L1302">>;
+ <<"L1302">>;
make_code(erl_lint, {redefine_spec, {_M, _F, _A}}) ->
- <<"L1303">>;
+ <<"L1303">>;
make_code(erl_lint, {redefine_spec, {_F, _A}}) ->
- <<"L1304">>;
+ <<"L1304">>;
make_code(erl_lint, {redefine_callback, {_F, _A}}) ->
- <<"L1305">>;
+ <<"L1305">>;
make_code(erl_lint, {bad_callback, {_M, _F, _A}}) ->
- <<"L1306">>;
+ <<"L1306">>;
make_code(erl_lint, {bad_module, {_M, _F, _A}}) ->
- <<"L1307">>;
+ <<"L1307">>;
make_code(erl_lint, {spec_fun_undefined, {_F, _A}}) ->
- <<"L1308">>;
+ <<"L1308">>;
make_code(erl_lint, {missing_spec, {_F, _A}}) ->
- <<"L1309">>;
+ <<"L1309">>;
make_code(erl_lint, spec_wrong_arity) ->
- <<"L1310">>;
+ <<"L1310">>;
make_code(erl_lint, callback_wrong_arity) ->
- <<"L1311">>;
-make_code(erl_lint, {deprecated_builtin_type, {_Name, _Arity},
- _Replacement, _Rel}) ->
- <<"L1312">>;
+ <<"L1311">>;
+make_code(erl_lint, {deprecated_builtin_type, {_Name, _Arity}, _Replacement, _Rel}) ->
+ <<"L1312">>;
make_code(erl_lint, {not_exported_opaque, {_TypeName, _Arity}}) ->
- <<"L1313">>;
+ <<"L1313">>;
make_code(erl_lint, {underspecified_opaque, {_TypeName, _Arity}}) ->
- <<"L1314">>;
+ <<"L1314">>;
make_code(erl_lint, {bad_dialyzer_attribute, _Term}) ->
- <<"L1315">>;
+ <<"L1315">>;
make_code(erl_lint, {bad_dialyzer_option, _Term}) ->
- <<"L1316">>;
+ <<"L1316">>;
make_code(erl_lint, {format_error, {_Fmt, _Args}}) ->
- <<"L1317">>;
+ <<"L1317">>;
make_code(erl_lint, _Other) ->
- <<"L1399">>;
-
+ <<"L1399">>;
%% stdlib-3.15.2/src/erl_scan.erl
make_code(erl_scan, {string, _Quote, _Head}) ->
- <<"S1400">>;
+ <<"S1400">>;
make_code(erl_scan, {illegal, _Type}) ->
- <<"S1401">>;
+ <<"S1401">>;
make_code(erl_scan, char) ->
- <<"S1402">>;
+ <<"S1402">>;
make_code(erl_scan, {base, _Base}) ->
- <<"S1403">>;
-make_code(erl_scan, _Other) ->
- <<"S1499">>;
-
+ <<"S1403">>;
+make_code(erl_scan, _Other) ->
+ <<"S1499">>;
%% stdlib-3.15.2/src/epp.erl
make_code(epp, cannot_parse) ->
- <<"E1500">>;
+ <<"E1500">>;
make_code(epp, {bad, _W}) ->
- <<"E1501">>;
+ <<"E1501">>;
make_code(epp, {duplicated_argument, _Arg}) ->
- <<"E1502">>;
+ <<"E1502">>;
make_code(epp, missing_parenthesis) ->
- <<"E1503">>;
+ <<"E1503">>;
make_code(epp, missing_comma) ->
- <<"E1504">>;
+ <<"E1504">>;
make_code(epp, premature_end) ->
- <<"E1505">>;
+ <<"E1505">>;
make_code(epp, {call, _What}) ->
- <<"E1506">>;
+ <<"E1506">>;
make_code(epp, {undefined, _M, none}) ->
- <<"E1507">>;
+ <<"E1507">>;
make_code(epp, {undefined, _M, _A}) ->
- <<"E1508">>;
+ <<"E1508">>;
make_code(epp, {depth, _What}) ->
- <<"E1509">>;
+ <<"E1509">>;
make_code(epp, {mismatch, _M}) ->
- <<"E1510">>;
+ <<"E1510">>;
make_code(epp, {arg_error, _M}) ->
- <<"E1511">>;
+ <<"E1511">>;
make_code(epp, {redefine, _M}) ->
- <<"E1512">>;
+ <<"E1512">>;
make_code(epp, {redefine_predef, _M}) ->
- <<"E1513">>;
+ <<"E1513">>;
make_code(epp, {circular, _M, none}) ->
- <<"E1514">>;
+ <<"E1514">>;
make_code(epp, {circular, _M, _A}) ->
- <<"E1515">>;
+ <<"E1515">>;
make_code(epp, {include, _W, _F}) ->
- <<"E1516">>;
+ <<"E1516">>;
make_code(epp, {illegal, _How, _What}) ->
- <<"E1517">>;
+ <<"E1517">>;
make_code(epp, {illegal_function, _Macro}) ->
- <<"E1518">>;
+ <<"E1518">>;
make_code(epp, {illegal_function_usage, _Macro}) ->
- <<"E1519">>;
+ <<"E1519">>;
make_code(epp, elif_after_else) ->
- <<"E1520">>;
+ <<"E1520">>;
make_code(epp, {'NYI', _What}) ->
- <<"E1521">>;
+ <<"E1521">>;
make_code(epp, {error, _Term}) ->
- <<"E1522">>;
+ <<"E1522">>;
make_code(epp, {warning, _Term}) ->
- <<"E1523">>;
+ <<"E1523">>;
make_code(epp, _E) ->
- <<"E1599">>;
-
+ <<"E1599">>;
%% stdlib-3.15.2/src/qlc.erl
make_code(qlc, not_a_query_list_comprehension) ->
- <<"Q1600">>;
+ <<"Q1600">>;
make_code(qlc, {used_generator_variable, _V}) ->
- <<"Q1601">>;
+ <<"Q1601">>;
make_code(qlc, binary_generator) ->
- <<"Q1602">>;
+ <<"Q1602">>;
make_code(qlc, too_complex_join) ->
- <<"Q1603">>;
+ <<"Q1603">>;
make_code(qlc, too_many_joins) ->
- <<"Q1604">>;
+ <<"Q1604">>;
make_code(qlc, nomatch_pattern) ->
- <<"Q1605">>;
+ <<"Q1605">>;
make_code(qlc, nomatch_filter) ->
- <<"Q1606">>;
+ <<"Q1606">>;
make_code(qlc, {Location, _Mod, _Reason}) when is_integer(Location) ->
- <<"Q1607">>;
+ <<"Q1607">>;
make_code(qlc, {bad_object, _FileName}) ->
- <<"Q1608">>;
+ <<"Q1608">>;
make_code(qlc, bad_object) ->
- <<"Q1609">>;
+ <<"Q1609">>;
make_code(qlc, {file_error, _FileName, _Reason}) ->
- <<"Q1610">>;
+ <<"Q1610">>;
make_code(qlc, {premature_eof, _FileName}) ->
- <<"Q1611">>;
+ <<"Q1611">>;
make_code(qlc, {tmpdir_usage, _Why}) ->
- <<"Q1612">>;
+ <<"Q1612">>;
make_code(qlc, {error, _Module, _Reason}) ->
- <<"Q1613">>;
+ <<"Q1613">>;
make_code(qlc, _E) ->
- <<"Q1699">>;
-
+ <<"Q1699">>;
%% stdlib-3.15.2/src/erl_parse.yrl
make_code(erl_parse, "head mismatch") ->
- <<"P1700">>;
+ <<"P1700">>;
make_code(erl_parse, "bad type variable") ->
- <<"P1701">>;
+ <<"P1701">>;
make_code(erl_parse, "bad attribute") ->
- <<"P1702">>;
+ <<"P1702">>;
make_code(erl_parse, "unsupported constraint" ++ _) ->
- <<"P1703">>;
+ <<"P1703">>;
make_code(erl_parse, "bad binary type") ->
- <<"P1704">>;
+ <<"P1704">>;
make_code(erl_parse, "bad variable list") ->
- <<"P1705">>;
+ <<"P1705">>;
make_code(erl_parse, "bad function arity") ->
- <<"P1706">>;
+ <<"P1706">>;
make_code(erl_parse, "bad function name") ->
- <<"P1707">>;
+ <<"P1707">>;
make_code(erl_parse, "bad Name/Arity") ->
- <<"P1708">>;
+ <<"P1708">>;
make_code(erl_parse, "bad record declaration") ->
- <<"P1709">>;
+ <<"P1709">>;
make_code(erl_parse, "bad record field") ->
- <<"P1710">>;
+ <<"P1710">>;
make_code(erl_parse, ["syntax error before: ", _]) ->
- <<"P1711">>;
+ <<"P1711">>;
%% Matching 'io_lib:format("bad ~tw declaration", [S])).', must come last
make_code(erl_parse, "bad " ++ _Str) ->
- <<"P1798">>;
+ <<"P1798">>;
make_code(erl_parse, _Other) ->
- <<"P1799">>;
-
+ <<"P1799">>;
make_code(Module, _Reason) ->
- unicode:characters_to_binary(io_lib:format("~p", [Module])).
+ unicode:characters_to_binary(io_lib:format("~p", [Module])).
%-----------------------------------------------------------------------
--spec range(els_dt_document:item() | undefined,
- erl_anno:anno() | none) -> poi_range().
+-spec range(
+ els_dt_document:item() | undefined,
+ erl_anno:anno() | none
+) -> poi_range().
range(Document, Anno) ->
- els_diagnostics_utils:range(Document, Anno).
+ els_diagnostics_utils:range(Document, Anno).
%% @doc Find the inclusion range for a header file.
%%
@@ -646,84 +658,92 @@ range(Document, Anno) ->
%% a given document.
-spec inclusion_range(string(), els_dt_document:item()) -> poi_range().
inclusion_range(IncludePath, Document) ->
- case
- inclusion_range(IncludePath, Document, include) ++
- inclusion_range(IncludePath, Document, include_lib) ++
- inclusion_range(IncludePath, Document, behaviour) ++
- inclusion_range(IncludePath, Document, parse_transform)
- of
- [Range|_] -> Range;
- _ -> range(undefined, none)
- end.
-
--spec inclusion_range( string()
- , els_dt_document:item()
- , include | include_lib | behaviour | parse_transform)
- -> [poi_range()].
+ case
+ inclusion_range(IncludePath, Document, include) ++
+ inclusion_range(IncludePath, Document, include_lib) ++
+ inclusion_range(IncludePath, Document, behaviour) ++
+ inclusion_range(IncludePath, Document, parse_transform)
+ of
+ [Range | _] -> Range;
+ _ -> range(undefined, none)
+ end.
+
+-spec inclusion_range(
+ string(),
+ els_dt_document:item(),
+ include | include_lib | behaviour | parse_transform
+) ->
+ [poi_range()].
inclusion_range(IncludePath, Document, include) ->
- POIs = els_dt_document:pois(Document, [include]),
- IncludeId = els_utils:include_id(IncludePath),
- [Range || #{id := Id, range := Range} <- POIs, Id =:= IncludeId];
+ POIs = els_dt_document:pois(Document, [include]),
+ IncludeId = els_utils:include_id(IncludePath),
+ [Range || #{id := Id, range := Range} <- POIs, Id =:= IncludeId];
inclusion_range(IncludePath, Document, include_lib) ->
- POIs = els_dt_document:pois(Document, [include_lib]),
- IncludeId = els_utils:include_lib_id(IncludePath),
- [Range || #{id := Id, range := Range} <- POIs, Id =:= IncludeId];
+ POIs = els_dt_document:pois(Document, [include_lib]),
+ IncludeId = els_utils:include_lib_id(IncludePath),
+ [Range || #{id := Id, range := Range} <- POIs, Id =:= IncludeId];
inclusion_range(IncludePath, Document, behaviour) ->
- POIs = els_dt_document:pois(Document, [behaviour]),
- BehaviourId = els_uri:module(els_uri:uri(els_utils:to_binary(IncludePath))),
- [Range || #{id := Id, range := Range} <- POIs, Id =:= BehaviourId];
+ POIs = els_dt_document:pois(Document, [behaviour]),
+ BehaviourId = els_uri:module(els_uri:uri(els_utils:to_binary(IncludePath))),
+ [Range || #{id := Id, range := Range} <- POIs, Id =:= BehaviourId];
inclusion_range(IncludePath, Document, parse_transform) ->
- POIs = els_dt_document:pois(Document, [parse_transform]),
- ParseTransformId
- = els_uri:module(els_uri:uri(els_utils:to_binary(IncludePath))),
- [Range || #{id := Id, range := Range} <- POIs, Id =:= ParseTransformId].
+ POIs = els_dt_document:pois(Document, [parse_transform]),
+ ParseTransformId =
+ els_uri:module(els_uri:uri(els_utils:to_binary(IncludePath))),
+ [Range || #{id := Id, range := Range} <- POIs, Id =:= ParseTransformId].
-spec macro_options() -> [macro_option()].
macro_options() ->
- Macros = els_config:get(macros),
- [macro_option(M) || M <- Macros].
+ Macros = els_config:get(macros),
+ [macro_option(M) || M <- Macros].
-spec macro_option(macro_config()) -> macro_option().
macro_option(#{"name" := Name, "value" := Value}) ->
- {'d', list_to_atom(Name), els_utils:macro_string_to_term(Value)};
+ {'d', list_to_atom(Name), els_utils:macro_string_to_term(Value)};
macro_option(#{"name" := Name}) ->
- {'d', list_to_atom(Name), true}.
+ {'d', list_to_atom(Name), true}.
-spec include_options() -> [include_option()].
include_options() ->
- Paths = els_config:get(include_paths),
- [ {i, Path} || Path <- Paths ].
+ Paths = els_config:get(include_paths),
+ [{i, Path} || Path <- Paths].
-spec diagnostics_options() -> [any()].
diagnostics_options() ->
- [basic_validation|diagnostics_options_bare()].
+ [basic_validation | diagnostics_options_bare()].
-spec diagnostics_options_load_code() -> [any()].
diagnostics_options_load_code() ->
- [binary|diagnostics_options_bare()].
+ [binary | diagnostics_options_bare()].
-spec diagnostics_options_bare() -> [any()].
diagnostics_options_bare() ->
- lists:append([ macro_options()
- , include_options()
- , [ return_warnings
- , return_errors
- ]]).
+ lists:append([
+ macro_options(),
+ include_options(),
+ [
+ return_warnings,
+ return_errors
+ ]
+ ]).
-spec compile_file(string(), [atom()]) ->
- {{ok | error, [compiler_msg()], [compiler_msg()]}
- , [els_diagnostics:diagnostic()]}.
+ {{ok | error, [compiler_msg()], [compiler_msg()]}, [els_diagnostics:diagnostic()]}.
compile_file(Path, Dependencies) ->
- %% Load dependencies required for the compilation
- Olds = [load_dependency(Dependency, Path)
- || Dependency <- Dependencies
- , not code:is_sticky(Dependency) ],
- Res = compile:file(Path, diagnostics_options()),
- %% Restore things after compilation
- [code:load_binary(Dependency, Filename, Binary)
- || {{Dependency, Binary, Filename}, _} <- Olds],
- Diagnostics = lists:flatten([ Diags || {_, Diags} <- Olds ]),
- {Res, Diagnostics ++ module_name_check(Path)}.
+ %% Load dependencies required for the compilation
+ Olds = [
+ load_dependency(Dependency, Path)
+ || Dependency <- Dependencies,
+ not code:is_sticky(Dependency)
+ ],
+ Res = compile:file(Path, diagnostics_options()),
+ %% Restore things after compilation
+ [
+ code:load_binary(Dependency, Filename, Binary)
+ || {{Dependency, Binary, Filename}, _} <- Olds
+ ],
+ Diagnostics = lists:flatten([Diags || {_, Diags} <- Olds]),
+ {Res, Diagnostics ++ module_name_check(Path)}.
%% The module_name error is only emitted by the Erlang compiler during
%% the "save binary" phase. This phase does not occur when code
@@ -734,130 +754,150 @@ compile_file(Path, Dependencies) ->
%% See issue #1152.
-spec module_name_check(string()) -> [els_diagnostics:diagnostic()].
module_name_check(Path) ->
- Basename = filename:basename(Path, ".erl"),
- Uri = els_uri:uri(els_utils:to_binary(Path)),
- case els_dt_document:lookup(Uri) of
- {ok, [Document]} ->
- case els_dt_document:pois(Document, [module]) of
- [#{id := Module, range := Range}] ->
- case atom_to_list(Module) =:= Basename of
- true ->
- [];
- false ->
- Message =
- io_lib:format("Module name '~s' does not match file name '~ts'",
- [Module, Basename]),
- Diagnostic = els_diagnostics:make_diagnostic(
- els_protocol:range(Range),
- els_utils:to_binary(Message),
- ?DIAGNOSTIC_ERROR,
- <<"Compiler (via Erlang LS)">>),
- [Diagnostic]
- end;
+ Basename = filename:basename(Path, ".erl"),
+ Uri = els_uri:uri(els_utils:to_binary(Path)),
+ case els_dt_document:lookup(Uri) of
+ {ok, [Document]} ->
+ case els_dt_document:pois(Document, [module]) of
+ [#{id := Module, range := Range}] ->
+ case atom_to_list(Module) =:= Basename of
+ true ->
+ [];
+ false ->
+ Message =
+ io_lib:format(
+ "Module name '~s' does not match file name '~ts'",
+ [Module, Basename]
+ ),
+ Diagnostic = els_diagnostics:make_diagnostic(
+ els_protocol:range(Range),
+ els_utils:to_binary(Message),
+ ?DIAGNOSTIC_ERROR,
+ <<"Compiler (via Erlang LS)">>
+ ),
+ [Diagnostic]
+ end;
+ _ ->
+ []
+ end;
_ ->
- []
- end;
- _ ->
- []
- end.
+ []
+ end.
%% @doc Load a dependency, return the old version of the code (if any),
%% so it can be restored.
-spec load_dependency(atom(), string()) ->
- {{atom(), binary(), file:filename()}, [els_diagnostics:diagnostic()]}
- | error.
+ {{atom(), binary(), file:filename()}, [els_diagnostics:diagnostic()]}
+ | error.
load_dependency(Module, IncludingPath) ->
- Old = code:get_object_code(Module),
- Diagnostics =
- case els_utils:find_module(Module) of
- {ok, Uri} ->
- Path = els_utils:to_list(els_uri:path(Uri)),
- Opts = compile_options(Module),
- case compile:file(Path, diagnostics_options_load_code() ++ Opts) of
- {ok, [], []} ->
- [];
- {ok, Module, Binary} ->
- code:load_binary(Module, atom_to_list(Module), Binary),
- [];
- {ok, Module, Binary, WS} ->
- code:load_binary(Module, atom_to_list(Module), Binary),
- diagnostics(IncludingPath, WS, ?DIAGNOSTIC_WARNING);
- {error, ES, WS} ->
- diagnostics(IncludingPath, WS, ?DIAGNOSTIC_WARNING) ++
- diagnostics(IncludingPath, ES, ?DIAGNOSTIC_ERROR)
- end;
- {error, Error} ->
- ?LOG_WARNING( "Error finding dependency [module=~p] [error=~p]"
- , [Module, Error]),
- []
- end,
- {Old, Diagnostics}.
+ Old = code:get_object_code(Module),
+ Diagnostics =
+ case els_utils:find_module(Module) of
+ {ok, Uri} ->
+ Path = els_utils:to_list(els_uri:path(Uri)),
+ Opts = compile_options(Module),
+ case compile:file(Path, diagnostics_options_load_code() ++ Opts) of
+ {ok, [], []} ->
+ [];
+ {ok, Module, Binary} ->
+ code:load_binary(Module, atom_to_list(Module), Binary),
+ [];
+ {ok, Module, Binary, WS} ->
+ code:load_binary(Module, atom_to_list(Module), Binary),
+ diagnostics(IncludingPath, WS, ?DIAGNOSTIC_WARNING);
+ {error, ES, WS} ->
+ diagnostics(IncludingPath, WS, ?DIAGNOSTIC_WARNING) ++
+ diagnostics(IncludingPath, ES, ?DIAGNOSTIC_ERROR)
+ end;
+ {error, Error} ->
+ ?LOG_WARNING(
+ "Error finding dependency [module=~p] [error=~p]",
+ [Module, Error]
+ ),
+ []
+ end,
+ {Old, Diagnostics}.
-spec maybe_compile_and_load(uri(), [els_diagnostics:diagnostic()]) -> ok.
maybe_compile_and_load(Uri, [] = _CDiagnostics) ->
- case els_config:get(code_reload) of
- #{"node" := NodeStr} ->
- Node = els_utils:compose_node_name(NodeStr,
- els_config_runtime:get_name_type()),
- Module = els_uri:module(Uri),
- case rpc:call(Node, code, is_sticky, [Module]) of
- true -> ok;
- _ -> handle_rpc_result(rpc:call(Node, c, c, [Module]), Module)
- end;
- disabled ->
- ok
- end;
+ case els_config:get(code_reload) of
+ #{"node" := NodeStr} ->
+ Node = els_utils:compose_node_name(
+ NodeStr,
+ els_config_runtime:get_name_type()
+ ),
+ Module = els_uri:module(Uri),
+ case rpc:call(Node, code, is_sticky, [Module]) of
+ true -> ok;
+ _ -> handle_rpc_result(rpc:call(Node, c, c, [Module]), Module)
+ end;
+ disabled ->
+ ok
+ end;
maybe_compile_and_load(_Uri, _CDiagnostics) ->
- ok.
+ ok.
-spec handle_rpc_result(term() | {badrpc, term()}, atom()) -> ok.
handle_rpc_result({ok, Module}, _) ->
- Msg = io_lib:format("code_reload success for: ~s", [Module]),
- els_server:send_notification(<<"window/showMessage">>,
- #{ type => ?MESSAGE_TYPE_INFO,
- message => els_utils:to_binary(Msg)
- });
+ Msg = io_lib:format("code_reload success for: ~s", [Module]),
+ els_server:send_notification(
+ <<"window/showMessage">>,
+ #{
+ type => ?MESSAGE_TYPE_INFO,
+ message => els_utils:to_binary(Msg)
+ }
+ );
handle_rpc_result(Err, Module) ->
- ?LOG_INFO("[code_reload] code_reload using c:c/1 crashed with: ~p",
- [Err]),
- Msg = io_lib:format("code_reload swap crashed for: ~s with: ~w",
- [Module, Err]),
- els_server:send_notification(<<"window/showMessage">>,
- #{ type => ?MESSAGE_TYPE_ERROR,
- message => els_utils:to_binary(Msg)
- }).
+ ?LOG_INFO(
+ "[code_reload] code_reload using c:c/1 crashed with: ~p",
+ [Err]
+ ),
+ Msg = io_lib:format(
+ "code_reload swap crashed for: ~s with: ~w",
+ [Module, Err]
+ ),
+ els_server:send_notification(
+ <<"window/showMessage">>,
+ #{
+ type => ?MESSAGE_TYPE_ERROR,
+ message => els_utils:to_binary(Msg)
+ }
+ ).
%% @doc Return the compile options from the compile_info chunk
-spec compile_options(atom()) -> [any()].
compile_options(Module) ->
- case code:which(Module) of
- non_existing ->
- ?LOG_DEBUG("Could not find compile options. [module=~p]", [Module]),
- [];
- Beam ->
- case beam_lib:chunks(Beam, [compile_info]) of
- {ok, {_, Chunks}} ->
- Info = proplists:get_value(compile_info, Chunks),
- proplists:get_value(options, Info, []);
- Error ->
- ?LOG_DEBUG( "Error extracting compile_info. [module=~p] [error=~p]"
- , [Module, Error]),
- []
- end
- end.
+ case code:which(Module) of
+ non_existing ->
+ ?LOG_DEBUG("Could not find compile options. [module=~p]", [Module]),
+ [];
+ Beam ->
+ case beam_lib:chunks(Beam, [compile_info]) of
+ {ok, {_, Chunks}} ->
+ Info = proplists:get_value(compile_info, Chunks),
+ proplists:get_value(options, Info, []);
+ Error ->
+ ?LOG_DEBUG(
+ "Error extracting compile_info. [module=~p] [error=~p]",
+ [Module, Error]
+ ),
+ []
+ end
+ end.
%% @doc Send a telemetry/event LSP message, for logging in the client
-spec telemetry(uri(), [els_diagnostics:diagnostic()]) -> ok.
telemetry(Uri, Diagnostics) ->
- case els_config:get(compiler_telemetry_enabled) of
- true ->
- Codes = [Code || #{ code := Code } <- Diagnostics ],
- Method = <<"telemetry/event">>,
- Params = #{ uri => Uri
- , diagnostics => Codes
- , type => <<"erlang-diagnostic-codes">>
- },
- els_server:send_notification(Method, Params);
- _ ->
- ok
- end.
+ case els_config:get(compiler_telemetry_enabled) of
+ true ->
+ Codes = [Code || #{code := Code} <- Diagnostics],
+ Method = <<"telemetry/event">>,
+ Params = #{
+ uri => Uri,
+ diagnostics => Codes,
+ type => <<"erlang-diagnostic-codes">>
+ },
+ els_server:send_notification(Method, Params);
+ _ ->
+ ok
+ end.
diff --git a/apps/els_lsp/src/els_completion_provider.erl b/apps/els_lsp/src/els_completion_provider.erl
index 7dbb82368..cda5fe921 100644
--- a/apps/els_lsp/src/els_completion_provider.erl
+++ b/apps/els_lsp/src/els_completion_provider.erl
@@ -5,20 +5,23 @@
-include("els_lsp.hrl").
-include_lib("kernel/include/logger.hrl").
--export([ handle_request/2
- , trigger_characters/0
- ]).
+-export([
+ handle_request/2,
+ trigger_characters/0
+]).
%% Exported to ease testing.
--export([ bifs/2
- , keywords/0
- ]).
-
--type options() :: #{ trigger := binary()
- , document := els_dt_document:item()
- , line := line()
- , column := column()
- }.
+-export([
+ bifs/2,
+ keywords/0
+]).
+
+-type options() :: #{
+ trigger := binary(),
+ document := els_dt_document:item(),
+ line := line(),
+ column := column()
+}.
-type items() :: [item()].
-type item() :: completion_item().
@@ -28,377 +31,442 @@
%%==============================================================================
-spec trigger_characters() -> [binary()].
trigger_characters() ->
- [<<":">>, <<"#">>, <<"?">>, <<".">>, <<"-">>, <<"\"">>].
+ [<<":">>, <<"#">>, <<"?">>, <<".">>, <<"-">>, <<"\"">>].
-spec handle_request(els_provider:request(), any()) -> {response, any()}.
handle_request({completion, Params}, _State) ->
- #{ <<"position">> := #{ <<"line">> := Line
- , <<"character">> := Character
- }
- , <<"textDocument">> := #{<<"uri">> := Uri}
- } = Params,
- {ok, #{text := Text} = Document} = els_utils:lookup_document(Uri),
- Context = maps:get( <<"context">>
- , Params
- , #{ <<"triggerKind">> => ?COMPLETION_TRIGGER_KIND_INVOKED }
- ),
- TriggerKind = maps:get(<<"triggerKind">>, Context),
- TriggerCharacter = maps:get(<<"triggerCharacter">>, Context, <<>>),
- %% We subtract 1 to strip the character that triggered the
- %% completion from the string.
- Length = case Character > 0 of true -> 1; false -> 0 end,
- Prefix = case TriggerKind of
- ?COMPLETION_TRIGGER_KIND_CHARACTER ->
- els_text:line(Text, Line, Character - Length);
- ?COMPLETION_TRIGGER_KIND_INVOKED ->
- els_text:line(Text, Line, Character);
- ?COMPLETION_TRIGGER_KIND_FOR_INCOMPLETE_COMPLETIONS ->
- els_text:line(Text, Line, Character)
- end,
- Opts = #{ trigger => TriggerCharacter
- , document => Document
- , line => Line + 1
- , column => Character
- },
- Completions = find_completions(Prefix, TriggerKind, Opts),
- {response, Completions};
+ #{
+ <<"position">> := #{
+ <<"line">> := Line,
+ <<"character">> := Character
+ },
+ <<"textDocument">> := #{<<"uri">> := Uri}
+ } = Params,
+ {ok, #{text := Text} = Document} = els_utils:lookup_document(Uri),
+ Context = maps:get(
+ <<"context">>,
+ Params,
+ #{<<"triggerKind">> => ?COMPLETION_TRIGGER_KIND_INVOKED}
+ ),
+ TriggerKind = maps:get(<<"triggerKind">>, Context),
+ TriggerCharacter = maps:get(<<"triggerCharacter">>, Context, <<>>),
+ %% We subtract 1 to strip the character that triggered the
+ %% completion from the string.
+ Length =
+ case Character > 0 of
+ true -> 1;
+ false -> 0
+ end,
+ Prefix =
+ case TriggerKind of
+ ?COMPLETION_TRIGGER_KIND_CHARACTER ->
+ els_text:line(Text, Line, Character - Length);
+ ?COMPLETION_TRIGGER_KIND_INVOKED ->
+ els_text:line(Text, Line, Character);
+ ?COMPLETION_TRIGGER_KIND_FOR_INCOMPLETE_COMPLETIONS ->
+ els_text:line(Text, Line, Character)
+ end,
+ Opts = #{
+ trigger => TriggerCharacter,
+ document => Document,
+ line => Line + 1,
+ column => Character
+ },
+ Completions = find_completions(Prefix, TriggerKind, Opts),
+ {response, Completions};
handle_request({resolve, CompletionItem}, _State) ->
- {response, resolve(CompletionItem)}.
+ {response, resolve(CompletionItem)}.
%%==============================================================================
%% Internal functions
%%==============================================================================
-spec resolve(map()) -> map().
-resolve(#{ <<"kind">> := ?COMPLETION_ITEM_KIND_FUNCTION
- , <<"data">> := #{ <<"module">> := Module
- , <<"function">> := Function
- , <<"arity">> := Arity
- }
- } = CompletionItem) ->
- Entries = els_docs:function_docs ( 'remote'
- , binary_to_atom(Module, utf8)
- , binary_to_atom(Function, utf8)
- , Arity),
- CompletionItem#{documentation => els_markup_content:new(Entries)};
-resolve(#{ <<"kind">> := ?COMPLETION_ITEM_KIND_TYPE_PARAM
- , <<"data">> := #{ <<"module">> := Module
- , <<"type">> := Type
- , <<"arity">> := Arity
- }
- } = CompletionItem) ->
- Entries = els_docs:type_docs('remote'
- , binary_to_atom(Module, utf8)
- , binary_to_atom(Type, utf8)
- , Arity),
- CompletionItem#{ documentation => els_markup_content:new(Entries) };
+resolve(
+ #{
+ <<"kind">> := ?COMPLETION_ITEM_KIND_FUNCTION,
+ <<"data">> := #{
+ <<"module">> := Module,
+ <<"function">> := Function,
+ <<"arity">> := Arity
+ }
+ } = CompletionItem
+) ->
+ Entries = els_docs:function_docs(
+ 'remote',
+ binary_to_atom(Module, utf8),
+ binary_to_atom(Function, utf8),
+ Arity
+ ),
+ CompletionItem#{documentation => els_markup_content:new(Entries)};
+resolve(
+ #{
+ <<"kind">> := ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ <<"data">> := #{
+ <<"module">> := Module,
+ <<"type">> := Type,
+ <<"arity">> := Arity
+ }
+ } = CompletionItem
+) ->
+ Entries = els_docs:type_docs(
+ 'remote',
+ binary_to_atom(Module, utf8),
+ binary_to_atom(Type, utf8),
+ Arity
+ ),
+ CompletionItem#{documentation => els_markup_content:new(Entries)};
resolve(CompletionItem) ->
- CompletionItem.
+ CompletionItem.
-spec find_completions(binary(), integer(), options()) -> items().
-find_completions( Prefix
- , ?COMPLETION_TRIGGER_KIND_CHARACTER
- , #{ trigger := <<":">>
- , document := Document
- , line := Line
- , column := Column
- }
- ) ->
- case lists:reverse(els_text:tokens(Prefix)) of
- [{atom, _, Module}, {'fun', _}| _] ->
- exported_definitions(Module, 'function', true);
- [{atom, _, Module}|_] ->
- {ExportFormat, TypeOrFun} = completion_context(Document, Line, Column),
- exported_definitions(Module, TypeOrFun, ExportFormat);
- _ ->
- []
- end;
-find_completions( _Prefix
- , ?COMPLETION_TRIGGER_KIND_CHARACTER
- , #{trigger := <<"?">>, document := Document}
- ) ->
- definitions(Document, define);
-find_completions( _Prefix
- , ?COMPLETION_TRIGGER_KIND_CHARACTER
- , #{trigger := <<"-">>, document := _Document, column := 1}
- ) ->
- attributes();
-find_completions( _Prefix
- , ?COMPLETION_TRIGGER_KIND_CHARACTER
- , #{trigger := <<"#">>, document := Document}
- ) ->
- definitions(Document, record);
-find_completions( <<"-include_lib(">>
- , ?COMPLETION_TRIGGER_KIND_CHARACTER
- , #{trigger := <<"\"">>}
- ) ->
- [item_kind_file(Path) || Path <- paths_include_lib()];
-find_completions( <<"-include(">>
- , ?COMPLETION_TRIGGER_KIND_CHARACTER
- , #{trigger := <<"\"">>, document := Document}
- ) ->
- [item_kind_file(Path) || Path <- paths_include(Document)];
-find_completions( Prefix
- , ?COMPLETION_TRIGGER_KIND_CHARACTER
- , #{trigger := <<".">>, document := Document}
- ) ->
- case lists:reverse(els_text:tokens(Prefix)) of
- [{atom, _, RecordName}, {'#', _} | _] ->
- record_fields(Document, RecordName);
- _ ->
- []
+find_completions(
+ Prefix,
+ ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ #{
+ trigger := <<":">>,
+ document := Document,
+ line := Line,
+ column := Column
+ }
+) ->
+ case lists:reverse(els_text:tokens(Prefix)) of
+ [{atom, _, Module}, {'fun', _} | _] ->
+ exported_definitions(Module, 'function', true);
+ [{atom, _, Module} | _] ->
+ {ExportFormat, TypeOrFun} = completion_context(Document, Line, Column),
+ exported_definitions(Module, TypeOrFun, ExportFormat);
+ _ ->
+ []
+ end;
+find_completions(
+ _Prefix,
+ ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ #{trigger := <<"?">>, document := Document}
+) ->
+ definitions(Document, define);
+find_completions(
+ _Prefix,
+ ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ #{trigger := <<"-">>, document := _Document, column := 1}
+) ->
+ attributes();
+find_completions(
+ _Prefix,
+ ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ #{trigger := <<"#">>, document := Document}
+) ->
+ definitions(Document, record);
+find_completions(
+ <<"-include_lib(">>,
+ ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ #{trigger := <<"\"">>}
+) ->
+ [item_kind_file(Path) || Path <- paths_include_lib()];
+find_completions(
+ <<"-include(">>,
+ ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ #{trigger := <<"\"">>, document := Document}
+) ->
+ [item_kind_file(Path) || Path <- paths_include(Document)];
+find_completions(
+ Prefix,
+ ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ #{trigger := <<".">>, document := Document}
+) ->
+ case lists:reverse(els_text:tokens(Prefix)) of
+ [{atom, _, RecordName}, {'#', _} | _] ->
+ record_fields(Document, RecordName);
+ _ ->
+ []
+ end;
+find_completions(
+ Prefix,
+ ?COMPLETION_TRIGGER_KIND_INVOKED,
+ #{
+ document := Document,
+ line := Line,
+ column := Column
+ }
+) ->
+ case lists:reverse(els_text:tokens(Prefix)) of
+ %% Check for "[...] fun atom:"
+ [{':', _}, {atom, _, Module}, {'fun', _} | _] ->
+ exported_definitions(Module, function, _ExportFormat = true);
+ %% Check for "[...] fun atom:atom"
+ [{atom, _, _}, {':', _}, {atom, _, Module}, {'fun', _} | _] ->
+ exported_definitions(Module, function, _ExportFormat = true);
+ %% Check for "[...] atom:"
+ [{':', _}, {atom, _, Module} | _] ->
+ {ExportFormat, TypeOrFun} = completion_context(Document, Line, Column),
+ exported_definitions(Module, TypeOrFun, ExportFormat);
+ %% Check for "[...] atom:atom"
+ [{atom, _, _}, {':', _}, {atom, _, Module} | _] ->
+ {ExportFormat, TypeOrFun} = completion_context(Document, Line, Column),
+ exported_definitions(Module, TypeOrFun, ExportFormat);
+ %% Check for "[...] ?"
+ [{'?', _} | _] ->
+ definitions(Document, define);
+ %% Check for "[...] ?anything"
+ [_, {'?', _} | _] ->
+ definitions(Document, define);
+ %% Check for "[...] #anything."
+ [{'.', _}, {atom, _, RecordName}, {'#', _} | _] ->
+ record_fields(Document, RecordName);
+ %% Check for "[...] #anything.something"
+ [_, {'.', _}, {atom, _, RecordName}, {'#', _} | _] ->
+ record_fields(Document, RecordName);
+ %% Check for "[...] #"
+ [{'#', _} | _] ->
+ definitions(Document, record);
+ %% Check for "[...] #anything"
+ [_, {'#', _} | _] ->
+ definitions(Document, record);
+ %% Check for "[...] Variable"
+ [{var, _, _} | _] ->
+ variables(Document);
+ %% Check for "-anything"
+ [{atom, _, _}, {'-', _}] ->
+ attributes();
+ %% Check for "-export(["
+ [{'[', _}, {'(', _}, {atom, _, export}, {'-', _}] ->
+ unexported_definitions(Document, function);
+ %% Check for "-export_type(["
+ [{'[', _}, {'(', _}, {atom, _, export_type}, {'-', _}] ->
+ unexported_definitions(Document, type_definition);
+ %% Check for "-behaviour(anything"
+ [{atom, _, _}, {'(', _}, {atom, _, Attribute}, {'-', _}] when
+ Attribute =:= behaviour; Attribute =:= behavior
+ ->
+ [item_kind_module(Module) || Module <- behaviour_modules()];
+ %% Check for "-behaviour("
+ [{'(', _}, {atom, _, Attribute}, {'-', _}] when
+ Attribute =:= behaviour; Attribute =:= behavior
+ ->
+ [item_kind_module(Module) || Module <- behaviour_modules()];
+ %% Check for "[...] fun atom"
+ [{atom, _, _}, {'fun', _} | _] ->
+ bifs(function, ExportFormat = true) ++
+ definitions(Document, function, ExportFormat = true);
+ %% Check for "[...] atom"
+ [{atom, _, Name} | _] ->
+ NameBinary = atom_to_binary(Name, utf8),
+ {ExportFormat, POIKind} = completion_context(Document, Line, Column),
+ case ExportFormat of
+ true ->
+ %% Only complete unexported definitions when in export
+ unexported_definitions(Document, POIKind);
+ false ->
+ keywords() ++
+ bifs(POIKind, ExportFormat) ++
+ atoms(Document, NameBinary) ++
+ all_record_fields(Document, NameBinary) ++
+ modules(NameBinary) ++
+ definitions(Document, POIKind, ExportFormat) ++
+ els_snippets_server:snippets()
+ end;
+ Tokens ->
+ ?LOG_DEBUG(
+ "No completion found. [prefix=~p] [tokens=~p]",
+ [Prefix, Tokens]
+ ),
+ []
end;
-find_completions( Prefix
- , ?COMPLETION_TRIGGER_KIND_INVOKED
- , #{ document := Document
- , line := Line
- , column := Column
- }
- ) ->
- case lists:reverse(els_text:tokens(Prefix)) of
- %% Check for "[...] fun atom:"
- [{':', _}, {atom, _, Module}, {'fun', _} | _] ->
- exported_definitions(Module, function, _ExportFormat = true);
- %% Check for "[...] fun atom:atom"
- [{atom, _, _}, {':', _}, {atom, _, Module}, {'fun', _} | _] ->
- exported_definitions(Module, function, _ExportFormat = true);
- %% Check for "[...] atom:"
- [{':', _}, {atom, _, Module} | _] ->
- {ExportFormat, TypeOrFun} = completion_context(Document, Line, Column),
- exported_definitions(Module, TypeOrFun, ExportFormat);
- %% Check for "[...] atom:atom"
- [{atom, _, _}, {':', _}, {atom, _, Module} | _] ->
- {ExportFormat, TypeOrFun} = completion_context(Document, Line, Column),
- exported_definitions(Module, TypeOrFun, ExportFormat);
- %% Check for "[...] ?"
- [{'?', _} | _] ->
- definitions(Document, define);
- %% Check for "[...] ?anything"
- [_, {'?', _} | _] ->
- definitions(Document, define);
- %% Check for "[...] #anything."
- [{'.', _}, {atom, _, RecordName}, {'#', _} | _] ->
- record_fields(Document, RecordName);
- %% Check for "[...] #anything.something"
- [_, {'.', _}, {atom, _, RecordName}, {'#', _} | _] ->
- record_fields(Document, RecordName);
- %% Check for "[...] #"
- [{'#', _} | _] ->
- definitions(Document, record);
- %% Check for "[...] #anything"
- [_, {'#', _} | _] ->
- definitions(Document, record);
- %% Check for "[...] Variable"
- [{var, _, _} | _] ->
- variables(Document);
- %% Check for "-anything"
- [{atom, _, _}, {'-', _}] ->
- attributes();
- %% Check for "-export(["
- [{'[', _}, {'(', _}, {atom, _, export}, {'-', _}] ->
- unexported_definitions(Document, function);
- %% Check for "-export_type(["
- [{'[', _}, {'(', _}, {atom, _, export_type}, {'-', _}] ->
- unexported_definitions(Document, type_definition);
- %% Check for "-behaviour(anything"
- [{atom, _, _}, {'(', _}, {atom, _, Attribute}, {'-', _}]
- when Attribute =:= behaviour; Attribute =:= behavior ->
- [item_kind_module(Module) || Module <- behaviour_modules()];
- %% Check for "-behaviour("
- [{'(', _}, {atom, _, Attribute}, {'-', _}]
- when Attribute =:= behaviour; Attribute =:= behavior ->
- [item_kind_module(Module) || Module <- behaviour_modules()];
- %% Check for "[...] fun atom"
- [{atom, _, _}, {'fun', _} | _] ->
- bifs(function, ExportFormat = true)
- ++ definitions(Document, function, ExportFormat = true);
- %% Check for "[...] atom"
- [{atom, _, Name} | _] ->
- NameBinary = atom_to_binary(Name, utf8),
- {ExportFormat, POIKind} = completion_context(Document, Line, Column),
- case ExportFormat of
- true ->
- %% Only complete unexported definitions when in export
- unexported_definitions(Document, POIKind);
- false ->
- keywords()
- ++ bifs(POIKind, ExportFormat)
- ++ atoms(Document, NameBinary)
- ++ all_record_fields(Document, NameBinary)
- ++ modules(NameBinary)
- ++ definitions(Document, POIKind, ExportFormat)
- ++ els_snippets_server:snippets()
- end;
- Tokens ->
- ?LOG_DEBUG("No completion found. [prefix=~p] [tokens=~p]",
- [Prefix, Tokens]),
- []
- end;
find_completions(_Prefix, _TriggerKind, _Opts) ->
- [].
+ [].
%%=============================================================================
%% Attributes
%%=============================================================================
-spec attributes() -> items().
attributes() ->
- [ snippet(attribute_behaviour)
- , snippet(attribute_callback)
- , snippet(attribute_compile)
- , snippet(attribute_define)
- , snippet(attribute_dialyzer)
- , snippet(attribute_export)
- , snippet(attribute_export_type)
- , snippet(attribute_if)
- , snippet(attribute_ifdef)
- , snippet(attribute_ifndef)
- , snippet(attribute_import)
- , snippet(attribute_include)
- , snippet(attribute_include_lib)
- , snippet(attribute_on_load)
- , snippet(attribute_opaque)
- , snippet(attribute_record)
- , snippet(attribute_type)
- , snippet(attribute_vsn)
- ].
+ [
+ snippet(attribute_behaviour),
+ snippet(attribute_callback),
+ snippet(attribute_compile),
+ snippet(attribute_define),
+ snippet(attribute_dialyzer),
+ snippet(attribute_export),
+ snippet(attribute_export_type),
+ snippet(attribute_if),
+ snippet(attribute_ifdef),
+ snippet(attribute_ifndef),
+ snippet(attribute_import),
+ snippet(attribute_include),
+ snippet(attribute_include_lib),
+ snippet(attribute_on_load),
+ snippet(attribute_opaque),
+ snippet(attribute_record),
+ snippet(attribute_type),
+ snippet(attribute_vsn)
+ ].
%%=============================================================================
%% Include paths
%%=============================================================================
-spec paths_include(els_dt_document:item()) -> [binary()].
paths_include(#{uri := Uri}) ->
- case match_in_path(els_uri:path(Uri), els_config:get(apps_paths)) of
- [] ->
- [];
- [Path|_] ->
- AppPath = filename:join(lists:droplast(filename:split(Path))),
- {ok, Headers} = els_dt_document_index:find_by_kind(header),
- lists:flatmap(
- fun(#{uri := HeaderUri}) ->
- case string:prefix(els_uri:path(HeaderUri), AppPath) of
- nomatch ->
- [];
- IncludePath ->
- [relative_include_path(IncludePath)]
- end
- end, Headers)
- end.
+ case match_in_path(els_uri:path(Uri), els_config:get(apps_paths)) of
+ [] ->
+ [];
+ [Path | _] ->
+ AppPath = filename:join(lists:droplast(filename:split(Path))),
+ {ok, Headers} = els_dt_document_index:find_by_kind(header),
+ lists:flatmap(
+ fun(#{uri := HeaderUri}) ->
+ case string:prefix(els_uri:path(HeaderUri), AppPath) of
+ nomatch ->
+ [];
+ IncludePath ->
+ [relative_include_path(IncludePath)]
+ end
+ end,
+ Headers
+ )
+ end.
-spec paths_include_lib() -> [binary()].
paths_include_lib() ->
- Paths = els_config:get(otp_paths)
- ++ els_config:get(deps_paths)
- ++ els_config:get(apps_paths)
- ++ els_config:get(include_paths),
- {ok, Headers} = els_dt_document_index:find_by_kind(header),
- lists:flatmap(
- fun(#{uri := Uri}) ->
- HeaderPath = els_uri:path(Uri),
- case match_in_path(HeaderPath, Paths) of
- [] ->
- [];
- [Path|_] ->
- <<"/", PathSuffix/binary>> = string:prefix(HeaderPath, Path),
- PathBin = unicode:characters_to_binary(Path),
- case lists:reverse(filename:split(PathBin)) of
- [<<"include">>, App | _] ->
- [filename:join([ strip_app_version(App)
- , <<"include">>
- , PathSuffix])];
- _ ->
- []
+ Paths =
+ els_config:get(otp_paths) ++
+ els_config:get(deps_paths) ++
+ els_config:get(apps_paths) ++
+ els_config:get(include_paths),
+ {ok, Headers} = els_dt_document_index:find_by_kind(header),
+ lists:flatmap(
+ fun(#{uri := Uri}) ->
+ HeaderPath = els_uri:path(Uri),
+ case match_in_path(HeaderPath, Paths) of
+ [] ->
+ [];
+ [Path | _] ->
+ <<"/", PathSuffix/binary>> = string:prefix(HeaderPath, Path),
+ PathBin = unicode:characters_to_binary(Path),
+ case lists:reverse(filename:split(PathBin)) of
+ [<<"include">>, App | _] ->
+ [
+ filename:join([
+ strip_app_version(App),
+ <<"include">>,
+ PathSuffix
+ ])
+ ];
+ _ ->
+ []
+ end
end
- end
- end, Headers).
+ end,
+ Headers
+ ).
-spec match_in_path(binary(), [binary()]) -> [binary()].
match_in_path(DocumentPath, Paths) ->
- [P || P <- Paths, string:prefix(DocumentPath, P) =/= nomatch].
+ [P || P <- Paths, string:prefix(DocumentPath, P) =/= nomatch].
-spec relative_include_path(binary()) -> binary().
relative_include_path(Path) ->
- case filename:split(Path) of
- [_App, <<"include">> | Rest] -> filename:join(Rest);
- [_App, <<"src">> | Rest] -> filename:join(Rest);
- [_App, SubDir | Rest] -> filename:join([<<"..">>, SubDir|Rest])
- end.
+ case filename:split(Path) of
+ [_App, <<"include">> | Rest] -> filename:join(Rest);
+ [_App, <<"src">> | Rest] -> filename:join(Rest);
+ [_App, SubDir | Rest] -> filename:join([<<"..">>, SubDir | Rest])
+ end.
-spec strip_app_version(binary()) -> binary().
strip_app_version(App0) ->
- %% Transform "foo-1.0" into "foo"
- case string:lexemes(App0, "-") of
- [] -> App0;
- [_] -> App0;
- Lexemes ->
- Vsn = lists:last(Lexemes),
- case re:run(Vsn, "^[0-9.]+$", [global, {capture, none}]) of
- match -> list_to_binary(lists:join("-", lists:droplast(Lexemes)));
- nomatch -> App0
- end
- end.
+ %% Transform "foo-1.0" into "foo"
+ case string:lexemes(App0, "-") of
+ [] ->
+ App0;
+ [_] ->
+ App0;
+ Lexemes ->
+ Vsn = lists:last(Lexemes),
+ case re:run(Vsn, "^[0-9.]+$", [global, {capture, none}]) of
+ match -> list_to_binary(lists:join("-", lists:droplast(Lexemes)));
+ nomatch -> App0
+ end
+ end.
-spec item_kind_file(binary()) -> item().
item_kind_file(Path) ->
- #{ label => Path
- , kind => ?COMPLETION_ITEM_KIND_FILE
- , insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- }.
+ #{
+ label => Path,
+ kind => ?COMPLETION_ITEM_KIND_FILE,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
+ }.
%%==============================================================================
%% Snippets
%%==============================================================================
-spec snippet(atom()) -> item().
snippet(attribute_behaviour) ->
- snippet(<<"-behaviour().">>, <<"behaviour(${1:Behaviour}).">>);
+ snippet(<<"-behaviour().">>, <<"behaviour(${1:Behaviour}).">>);
snippet(attribute_export) ->
- snippet(<<"-export().">>, <<"export([${1:}]).">>);
+ snippet(<<"-export().">>, <<"export([${1:}]).">>);
snippet(attribute_vsn) ->
- snippet(<<"-vsn(Version).">>, <<"vsn(${1:Version}).">>);
+ snippet(<<"-vsn(Version).">>, <<"vsn(${1:Version}).">>);
snippet(attribute_callback) ->
- snippet(<<"-callback name(Args) -> return().">>,
- <<"callback ${1:name}(${2:Args}) -> ${3:return()}.">>);
+ snippet(
+ <<"-callback name(Args) -> return().">>,
+ <<"callback ${1:name}(${2:Args}) -> ${3:return()}.">>
+ );
snippet(attribute_on_load) ->
- snippet(<<"-on_load().">>,
- <<"on_load(${1:Function}).">>);
+ snippet(
+ <<"-on_load().">>,
+ <<"on_load(${1:Function}).">>
+ );
snippet(attribute_export_type) ->
- snippet(<<"-export_type().">>, <<"export_type([${1:}]).">>);
+ snippet(<<"-export_type().">>, <<"export_type([${1:}]).">>);
snippet(attribute_include) ->
- snippet(<<"-include().">>, <<"include(${1:}).">>);
+ snippet(<<"-include().">>, <<"include(${1:}).">>);
snippet(attribute_include_lib) ->
- snippet(<<"-include_lib().">>, <<"include_lib(${1:}).">>);
+ snippet(<<"-include_lib().">>, <<"include_lib(${1:}).">>);
snippet(attribute_type) ->
- snippet(<<"-type name() :: definition.">>,
- <<"type ${1:name}() :: ${2:definition}.">>);
+ snippet(
+ <<"-type name() :: definition.">>,
+ <<"type ${1:name}() :: ${2:definition}.">>
+ );
snippet(attribute_opaque) ->
- snippet(<<"-opaque name() :: definition.">>,
- <<"opaque ${1:name}() :: ${2:definition}.">>);
+ snippet(
+ <<"-opaque name() :: definition.">>,
+ <<"opaque ${1:name}() :: ${2:definition}.">>
+ );
snippet(attribute_ifdef) ->
- snippet(<<"-ifdef().">>, <<"ifdef(${1:VAR}).\n${2:}\n-endif.">>);
+ snippet(<<"-ifdef().">>, <<"ifdef(${1:VAR}).\n${2:}\n-endif.">>);
snippet(attribute_ifndef) ->
- snippet(<<"-ifndef().">>, <<"ifndef(${1:VAR}).\n${2:}\n-endif.">>);
+ snippet(<<"-ifndef().">>, <<"ifndef(${1:VAR}).\n${2:}\n-endif.">>);
snippet(attribute_if) ->
- snippet(<<"-if().">>, <<"if(${1:Pred}).\n${2:}\n-endif.">>);
+ snippet(<<"-if().">>, <<"if(${1:Pred}).\n${2:}\n-endif.">>);
snippet(attribute_define) ->
- snippet(<<"-define().">>, <<"define(${1:MACRO}, ${2:Value}).">>);
+ snippet(<<"-define().">>, <<"define(${1:MACRO}, ${2:Value}).">>);
snippet(attribute_record) ->
- snippet(<<"-record().">>,
- <<"record(${1:name}, {${2:field} = ${3:Value} :: ${4:Type}()}).">>);
+ snippet(
+ <<"-record().">>,
+ <<"record(${1:name}, {${2:field} = ${3:Value} :: ${4:Type}()}).">>
+ );
snippet(attribute_import) ->
- snippet(<<"-import().">>,
- <<"import(${1:Module}, [${2:}]).">>);
+ snippet(
+ <<"-import().">>,
+ <<"import(${1:Module}, [${2:}]).">>
+ );
snippet(attribute_dialyzer) ->
- snippet(<<"-dialyzer().">>,
- <<"dialyzer(${1:}).">>);
+ snippet(
+ <<"-dialyzer().">>,
+ <<"dialyzer(${1:}).">>
+ );
snippet(attribute_compile) ->
- snippet(<<"-compile().">>,
- <<"compile(${1:}).">>).
+ snippet(
+ <<"-compile().">>,
+ <<"compile(${1:}).">>
+ ).
-spec snippet(binary(), binary()) -> item().
snippet(Label, InsertText) ->
- #{ label => Label
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , insertText => InsertText
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- }.
+ #{
+ label => Label,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ insertText => InsertText,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
+ }.
%%==============================================================================
%% Atoms
@@ -406,17 +474,18 @@ snippet(Label, InsertText) ->
-spec atoms(els_dt_document:item(), binary()) -> [map()].
atoms(Document, Prefix) ->
- POIs = els_scope:local_and_included_pois(Document, atom),
- Atoms = [Id || #{id := Id} <- POIs],
- Unique = lists:usort(Atoms),
- filter_by_prefix(Prefix, Unique, fun atom_to_label/1, fun item_kind_atom/1).
+ POIs = els_scope:local_and_included_pois(Document, atom),
+ Atoms = [Id || #{id := Id} <- POIs],
+ Unique = lists:usort(Atoms),
+ filter_by_prefix(Prefix, Unique, fun atom_to_label/1, fun item_kind_atom/1).
-spec item_kind_atom(binary()) -> map().
item_kind_atom(Atom) ->
- #{ label => Atom
- , kind => ?COMPLETION_ITEM_KIND_CONSTANT
- , insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- }.
+ #{
+ label => Atom,
+ kind => ?COMPLETION_ITEM_KIND_CONSTANT,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
+ }.
%%==============================================================================
%% Modules
@@ -424,119 +493,139 @@ item_kind_atom(Atom) ->
-spec modules(binary()) -> [map()].
modules(Prefix) ->
- {ok, Items} = els_dt_document_index:find_by_kind(module),
- Modules = [Id || #{id := Id} <- Items],
- filter_by_prefix(Prefix, Modules,
- fun atom_to_label/1, fun item_kind_module/1).
+ {ok, Items} = els_dt_document_index:find_by_kind(module),
+ Modules = [Id || #{id := Id} <- Items],
+ filter_by_prefix(
+ Prefix,
+ Modules,
+ fun atom_to_label/1,
+ fun item_kind_module/1
+ ).
-spec item_kind_module(atom()) -> item().
item_kind_module(Module) ->
- #{ label => Module
- , kind => ?COMPLETION_ITEM_KIND_MODULE
- , insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- }.
+ #{
+ label => Module,
+ kind => ?COMPLETION_ITEM_KIND_MODULE,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
+ }.
-spec behaviour_modules() -> [atom()].
behaviour_modules() ->
- {ok, Modules} = els_dt_document_index:find_by_kind(module),
- OtpBehaviours = [ gen_event
- , gen_server
- , gen_statem
- , supervisor
- ],
- Behaviours = [Id || #{id := Id, uri := Uri} <- Modules, is_behaviour(Uri)],
- OtpBehaviours ++ Behaviours.
+ {ok, Modules} = els_dt_document_index:find_by_kind(module),
+ OtpBehaviours = [
+ gen_event,
+ gen_server,
+ gen_statem,
+ supervisor
+ ],
+ Behaviours = [Id || #{id := Id, uri := Uri} <- Modules, is_behaviour(Uri)],
+ OtpBehaviours ++ Behaviours.
-spec is_behaviour(uri()) -> boolean().
is_behaviour(Uri) ->
- case els_dt_document:lookup(Uri) of
- {ok, [Document]} ->
- [] =/= els_dt_document:pois(Document, [callback]);
- _ ->
- false
- end.
+ case els_dt_document:lookup(Uri) of
+ {ok, [Document]} ->
+ [] =/= els_dt_document:pois(Document, [callback]);
+ _ ->
+ false
+ end.
%%==============================================================================
%% Functions, Types, Macros and Records
%%==============================================================================
-spec unexported_definitions(els_dt_document:item(), poi_kind()) -> items().
unexported_definitions(Document, POIKind) ->
- AllDefs = definitions(Document, POIKind, true, false),
- ExportedDefs = definitions(Document, POIKind, true, true),
- AllDefs -- ExportedDefs.
+ AllDefs = definitions(Document, POIKind, true, false),
+ ExportedDefs = definitions(Document, POIKind, true, true),
+ AllDefs -- ExportedDefs.
-spec definitions(els_dt_document:item(), poi_kind()) -> [map()].
definitions(Document, POIKind) ->
- definitions(Document, POIKind, _ExportFormat = false, _ExportedOnly = false).
+ definitions(Document, POIKind, _ExportFormat = false, _ExportedOnly = false).
-spec definitions(els_dt_document:item(), poi_kind(), boolean()) -> [map()].
definitions(Document, POIKind, ExportFormat) ->
- definitions(Document, POIKind, ExportFormat, _ExportedOnly = false).
+ definitions(Document, POIKind, ExportFormat, _ExportedOnly = false).
-spec definitions(els_dt_document:item(), poi_kind(), boolean(), boolean()) ->
- [map()].
+ [map()].
definitions(Document, POIKind, ExportFormat, ExportedOnly) ->
- POIs = els_scope:local_and_included_pois(Document, POIKind),
- #{uri := Uri} = Document,
- %% Find exported entries when there is an export_entry kind available
- FAs = case export_entry_kind(POIKind) of
- {error, no_export_entry_kind} -> [];
- ExportKind ->
- Exports = els_scope:local_and_included_pois(Document, ExportKind),
- [FA || #{id := FA} <- Exports]
+ POIs = els_scope:local_and_included_pois(Document, POIKind),
+ #{uri := Uri} = Document,
+ %% Find exported entries when there is an export_entry kind available
+ FAs =
+ case export_entry_kind(POIKind) of
+ {error, no_export_entry_kind} ->
+ [];
+ ExportKind ->
+ Exports = els_scope:local_and_included_pois(Document, ExportKind),
+ [FA || #{id := FA} <- Exports]
end,
- Items = resolve_definitions(Uri, POIs, FAs, ExportedOnly, ExportFormat),
- lists:usort(Items).
+ Items = resolve_definitions(Uri, POIs, FAs, ExportedOnly, ExportFormat),
+ lists:usort(Items).
-spec completion_context(els_dt_document:item(), line(), column()) ->
- {boolean(), poi_kind()}.
+ {boolean(), poi_kind()}.
completion_context(Document, Line, Column) ->
- ExportFormat = is_in(Document, Line, Column, [export, export_type]),
- POIKind = case is_in(Document, Line, Column, [spec, export_type]) of
- true -> type_definition;
- false -> function
- end,
- {ExportFormat, POIKind}.
-
--spec resolve_definitions(uri(), [poi()], [{atom(), arity()}],
- boolean(), boolean()) ->
- [map()].
+ ExportFormat = is_in(Document, Line, Column, [export, export_type]),
+ POIKind =
+ case is_in(Document, Line, Column, [spec, export_type]) of
+ true -> type_definition;
+ false -> function
+ end,
+ {ExportFormat, POIKind}.
+
+-spec resolve_definitions(
+ uri(),
+ [poi()],
+ [{atom(), arity()}],
+ boolean(),
+ boolean()
+) ->
+ [map()].
resolve_definitions(Uri, Functions, ExportsFA, ExportedOnly, ArityOnly) ->
- [ resolve_definition(Uri, POI, ArityOnly)
- || #{id := FA} = POI <- Functions,
- not ExportedOnly orelse lists:member(FA, ExportsFA)
- ].
+ [
+ resolve_definition(Uri, POI, ArityOnly)
+ || #{id := FA} = POI <- Functions,
+ not ExportedOnly orelse lists:member(FA, ExportsFA)
+ ].
-spec resolve_definition(uri(), poi(), boolean()) -> map().
resolve_definition(Uri, #{kind := 'function', id := {F, A}} = POI, ArityOnly) ->
- Data = #{ <<"module">> => els_uri:module(Uri)
- , <<"function">> => F
- , <<"arity">> => A
- },
- completion_item(POI, Data, ArityOnly);
-resolve_definition(Uri, #{kind := 'type_definition', id := {T, A}} = POI,
- ArityOnly) ->
- Data = #{ <<"module">> => els_uri:module(Uri)
- , <<"type">> => T
- , <<"arity">> => A
- },
- completion_item(POI, Data, ArityOnly);
+ Data = #{
+ <<"module">> => els_uri:module(Uri),
+ <<"function">> => F,
+ <<"arity">> => A
+ },
+ completion_item(POI, Data, ArityOnly);
+resolve_definition(
+ Uri,
+ #{kind := 'type_definition', id := {T, A}} = POI,
+ ArityOnly
+) ->
+ Data = #{
+ <<"module">> => els_uri:module(Uri),
+ <<"type">> => T,
+ <<"arity">> => A
+ },
+ completion_item(POI, Data, ArityOnly);
resolve_definition(_Uri, POI, ArityOnly) ->
- completion_item(POI, ArityOnly).
+ completion_item(POI, ArityOnly).
-spec exported_definitions(module(), poi_kind(), boolean()) -> [map()].
exported_definitions(Module, POIKind, ExportFormat) ->
- case els_utils:find_module(Module) of
- {ok, Uri} ->
- case els_utils:lookup_document(Uri) of
- {ok, Document} ->
- definitions(Document, POIKind, ExportFormat, true);
- {error, _} ->
- []
- end;
- {error, _Error} ->
- []
- end.
+ case els_utils:find_module(Module) of
+ {ok, Uri} ->
+ case els_utils:lookup_document(Uri) of
+ {ok, Document} ->
+ definitions(Document, POIKind, ExportFormat, true);
+ {error, _} ->
+ []
+ end;
+ {error, _Error} ->
+ []
+ end.
%%==============================================================================
%% Variables
@@ -544,13 +633,15 @@ exported_definitions(Module, POIKind, ExportFormat) ->
-spec variables(els_dt_document:item()) -> [map()].
variables(Document) ->
- POIs = els_dt_document:pois(Document, [variable]),
- Vars = [ #{ label => atom_to_binary(Name, utf8)
- , kind => ?COMPLETION_ITEM_KIND_VARIABLE
- }
- || #{id := Name} <- POIs
- ],
- lists:usort(Vars).
+ POIs = els_dt_document:pois(Document, [variable]),
+ Vars = [
+ #{
+ label => atom_to_binary(Name, utf8),
+ kind => ?COMPLETION_ITEM_KIND_VARIABLE
+ }
+ || #{id := Name} <- POIs
+ ],
+ lists:usort(Vars).
%%==============================================================================
%% Record Fields
@@ -558,33 +649,38 @@ variables(Document) ->
-spec all_record_fields(els_dt_document:item(), binary()) -> [map()].
all_record_fields(Document, Prefix) ->
- POIs = els_scope:local_and_included_pois(Document, [ record_def_field
- , record_field]),
- Fields = [Id || #{id := {_Record, Id}} <- POIs],
- Unique = lists:usort(Fields),
- filter_by_prefix(Prefix, Unique, fun atom_to_label/1, fun item_kind_field/1).
+ POIs = els_scope:local_and_included_pois(Document, [
+ record_def_field,
+ record_field
+ ]),
+ Fields = [Id || #{id := {_Record, Id}} <- POIs],
+ Unique = lists:usort(Fields),
+ filter_by_prefix(Prefix, Unique, fun atom_to_label/1, fun item_kind_field/1).
-spec record_fields(els_dt_document:item(), atom()) -> [map()].
record_fields(Document, RecordName) ->
- case find_record_definition(Document, RecordName) of
- [] -> [];
- POIs ->
- [#{data := #{field_list := Fields}} | _] = els_poi:sort(POIs),
- [ item_kind_field(atom_to_label(Name))
- || Name <- Fields
- ]
- end.
+ case find_record_definition(Document, RecordName) of
+ [] ->
+ [];
+ POIs ->
+ [#{data := #{field_list := Fields}} | _] = els_poi:sort(POIs),
+ [
+ item_kind_field(atom_to_label(Name))
+ || Name <- Fields
+ ]
+ end.
-spec find_record_definition(els_dt_document:item(), atom()) -> [poi()].
find_record_definition(Document, RecordName) ->
- POIs = els_scope:local_and_included_pois(Document, record),
- [X || X = #{id := Name} <- POIs, Name =:= RecordName].
+ POIs = els_scope:local_and_included_pois(Document, record),
+ [X || X = #{id := Name} <- POIs, Name =:= RecordName].
-spec item_kind_field(binary()) -> map().
item_kind_field(Name) ->
- #{ label => Name
- , kind => ?COMPLETION_ITEM_KIND_FIELD
- }.
+ #{
+ label => Name,
+ kind => ?COMPLETION_ITEM_KIND_FIELD
+ }.
%%==============================================================================
%% Keywords
@@ -592,13 +688,42 @@ item_kind_field(Name) ->
-spec keywords() -> [map()].
keywords() ->
- Keywords = [ 'after', 'and', 'andalso', 'band', 'begin', 'bnot', 'bor', 'bsl'
- , 'bsr', 'bxor', 'case', 'catch', 'cond', 'div', 'end', 'fun'
- , 'if', 'let', 'not', 'of', 'or', 'orelse', 'receive', 'rem'
- , 'try', 'when', 'xor'],
- [ #{ label => atom_to_binary(K, utf8)
- , kind => ?COMPLETION_ITEM_KIND_KEYWORD
- } || K <- Keywords ].
+ Keywords = [
+ 'after',
+ 'and',
+ 'andalso',
+ 'band',
+ 'begin',
+ 'bnot',
+ 'bor',
+ 'bsl',
+ 'bsr',
+ 'bxor',
+ 'case',
+ 'catch',
+ 'cond',
+ 'div',
+ 'end',
+ 'fun',
+ 'if',
+ 'let',
+ 'not',
+ 'of',
+ 'or',
+ 'orelse',
+ 'receive',
+ 'rem',
+ 'try',
+ 'when',
+ 'xor'
+ ],
+ [
+ #{
+ label => atom_to_binary(K, utf8),
+ kind => ?COMPLETION_ITEM_KIND_KEYWORD
+ }
+ || K <- Keywords
+ ].
%%==============================================================================
%% Built-in functions
@@ -606,47 +731,81 @@ keywords() ->
-spec bifs(poi_kind(), boolean()) -> [map()].
bifs(function, ExportFormat) ->
- Range = #{from => {0, 0}, to => {0, 0}},
- Exports = erlang:module_info(exports),
- BIFs = [ #{ kind => function
- , id => X
- , range => Range
- , data => #{args => generate_arguments("Arg", A)}
- }
- || {F, A} = X <- Exports, erl_internal:bif(F, A)
- ],
- [completion_item(X, ExportFormat) || X <- BIFs];
+ Range = #{from => {0, 0}, to => {0, 0}},
+ Exports = erlang:module_info(exports),
+ BIFs = [
+ #{
+ kind => function,
+ id => X,
+ range => Range,
+ data => #{args => generate_arguments("Arg", A)}
+ }
+ || {F, A} = X <- Exports, erl_internal:bif(F, A)
+ ],
+ [completion_item(X, ExportFormat) || X <- BIFs];
bifs(type_definition, true = _ExportFormat) ->
- %% We don't want to include the built-in types when we are in
- %% a -export_types(). context.
- [];
+ %% We don't want to include the built-in types when we are in
+ %% a -export_types(). context.
+ [];
bifs(type_definition, false = ExportFormat) ->
- Types = [ {'any', 0}, {'arity', 0}, {'atom', 0}, {'binary', 0}
- , {'bitstring', 0}, {'boolean', 0}, {'byte', 0}, {'char', 0}
- , {'float', 0}, {'fun', 0}, {'fun', 1}, {'function', 0}
- , {'identifier', 0}, {'integer', 0}, {'iodata', 0}, {'iolist', 0}
- , {'list', 0}, {'list', 1}, {'map', 0}, {'maybe_improper_list', 0}
- , {'maybe_improper_list', 2}, {'mfa', 0}, {'module', 0}
- , {'neg_integer', 0}, {'nil', 0}, {'no_return', 0}, {'node', 0}
- , {'nonempty_improper_list', 2}, {'nonempty_list', 1}
- , {'non_neg_integer', 0}, {'none', 0}, {'nonempty_list', 0}
- , {'nonempty_string', 0}, {'number', 0}, {'pid', 0}, {'port', 0}
- , {'pos_integer', 0}, {'reference', 0}, {'string', 0}, {'term', 0}
- , {'timeout', 0}
- ],
- Range = #{from => {0, 0}, to => {0, 0}},
- POIs = [ #{ kind => type_definition
- , id => X
- , range => Range
- , data => #{args => generate_arguments("Type", A)}
- }
- || {_, A} = X <- Types
- ],
- [completion_item(X, ExportFormat) || X <- POIs].
+ Types = [
+ {'any', 0},
+ {'arity', 0},
+ {'atom', 0},
+ {'binary', 0},
+ {'bitstring', 0},
+ {'boolean', 0},
+ {'byte', 0},
+ {'char', 0},
+ {'float', 0},
+ {'fun', 0},
+ {'fun', 1},
+ {'function', 0},
+ {'identifier', 0},
+ {'integer', 0},
+ {'iodata', 0},
+ {'iolist', 0},
+ {'list', 0},
+ {'list', 1},
+ {'map', 0},
+ {'maybe_improper_list', 0},
+ {'maybe_improper_list', 2},
+ {'mfa', 0},
+ {'module', 0},
+ {'neg_integer', 0},
+ {'nil', 0},
+ {'no_return', 0},
+ {'node', 0},
+ {'nonempty_improper_list', 2},
+ {'nonempty_list', 1},
+ {'non_neg_integer', 0},
+ {'none', 0},
+ {'nonempty_list', 0},
+ {'nonempty_string', 0},
+ {'number', 0},
+ {'pid', 0},
+ {'port', 0},
+ {'pos_integer', 0},
+ {'reference', 0},
+ {'string', 0},
+ {'term', 0},
+ {'timeout', 0}
+ ],
+ Range = #{from => {0, 0}, to => {0, 0}},
+ POIs = [
+ #{
+ kind => type_definition,
+ id => X,
+ range => Range,
+ data => #{args => generate_arguments("Type", A)}
+ }
+ || {_, A} = X <- Types
+ ],
+ [completion_item(X, ExportFormat) || X <- POIs].
-spec generate_arguments(string(), integer()) -> [{integer(), string()}].
generate_arguments(Prefix, Arity) ->
- [{N, Prefix ++ integer_to_list(N)} || N <- lists:seq(1, Arity)].
+ [{N, Prefix ++ integer_to_list(N)} || N <- lists:seq(1, Arity)].
%%==============================================================================
%% Filter by prefix
@@ -655,140 +814,154 @@ generate_arguments(Prefix, Arity) ->
%% TODO: Implement as select
-spec filter_by_prefix(binary(), [binary()], function(), function()) -> [map()].
filter_by_prefix(Prefix, List, ToBinary, ItemFun) ->
- FilterMapFun = fun(X) ->
- Str = ToBinary(X),
- case string:prefix(Str, Prefix) of
- nomatch -> false;
- _ -> {true, ItemFun(Str)}
- end
- end,
- lists:filtermap(FilterMapFun, List).
+ FilterMapFun = fun(X) ->
+ Str = ToBinary(X),
+ case string:prefix(Str, Prefix) of
+ nomatch -> false;
+ _ -> {true, ItemFun(Str)}
+ end
+ end,
+ lists:filtermap(FilterMapFun, List).
%%==============================================================================
%% Helper functions
%%==============================================================================
-spec completion_item(poi(), boolean()) -> map().
completion_item(POI, ExportFormat) ->
- completion_item(POI, #{}, ExportFormat).
+ completion_item(POI, #{}, ExportFormat).
-spec completion_item(poi(), map(), ExportFormat :: boolean()) -> map().
-completion_item(#{kind := Kind, id := {F, A}, data := POIData}, Data, false)
- when Kind =:= function;
- Kind =:= type_definition ->
- ArgsNames = maps:get(args, POIData),
- Label = io_lib:format("~p/~p", [F, A]),
- SnippetSupport = snippet_support(),
- Format =
- case SnippetSupport of
- true -> ?INSERT_TEXT_FORMAT_SNIPPET;
- false -> ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- end,
- #{ label => els_utils:to_binary(Label)
- , kind => completion_item_kind(Kind)
- , insertText => format_function(F, ArgsNames, SnippetSupport)
- , insertTextFormat => Format
- , data => Data
- };
-completion_item(#{kind := Kind, id := {F, A}}, Data, true)
- when Kind =:= function;
- Kind =:= type_definition ->
- Label = io_lib:format("~p/~p", [F, A]),
- #{ label => els_utils:to_binary(Label)
- , kind => completion_item_kind(Kind)
- , insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , data => Data
- };
+completion_item(#{kind := Kind, id := {F, A}, data := POIData}, Data, false) when
+ Kind =:= function;
+ Kind =:= type_definition
+->
+ ArgsNames = maps:get(args, POIData),
+ Label = io_lib:format("~p/~p", [F, A]),
+ SnippetSupport = snippet_support(),
+ Format =
+ case SnippetSupport of
+ true -> ?INSERT_TEXT_FORMAT_SNIPPET;
+ false -> ?INSERT_TEXT_FORMAT_PLAIN_TEXT
+ end,
+ #{
+ label => els_utils:to_binary(Label),
+ kind => completion_item_kind(Kind),
+ insertText => format_function(F, ArgsNames, SnippetSupport),
+ insertTextFormat => Format,
+ data => Data
+ };
+completion_item(#{kind := Kind, id := {F, A}}, Data, true) when
+ Kind =:= function;
+ Kind =:= type_definition
+->
+ Label = io_lib:format("~p/~p", [F, A]),
+ #{
+ label => els_utils:to_binary(Label),
+ kind => completion_item_kind(Kind),
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ data => Data
+ };
completion_item(#{kind := Kind = record, id := Name}, Data, _) ->
- #{ label => atom_to_label(Name)
- , kind => completion_item_kind(Kind)
- , data => Data
- };
+ #{
+ label => atom_to_label(Name),
+ kind => completion_item_kind(Kind),
+ data => Data
+ };
completion_item(#{kind := Kind = define, id := Name, data := Info}, Data, _) ->
- #{args := ArgNames} = Info,
- SnippetSupport = snippet_support(),
- Format =
- case SnippetSupport of
- true -> ?INSERT_TEXT_FORMAT_SNIPPET;
- false -> ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- end,
- #{ label => macro_label(Name)
- , kind => completion_item_kind(Kind)
- , insertText => format_macro(Name, ArgNames, SnippetSupport)
- , insertTextFormat => Format
- , data => Data
- }.
+ #{args := ArgNames} = Info,
+ SnippetSupport = snippet_support(),
+ Format =
+ case SnippetSupport of
+ true -> ?INSERT_TEXT_FORMAT_SNIPPET;
+ false -> ?INSERT_TEXT_FORMAT_PLAIN_TEXT
+ end,
+ #{
+ label => macro_label(Name),
+ kind => completion_item_kind(Kind),
+ insertText => format_macro(Name, ArgNames, SnippetSupport),
+ insertTextFormat => Format,
+ data => Data
+ }.
-spec macro_label(atom() | {atom(), non_neg_integer()}) -> binary().
macro_label({Name, Arity}) ->
- els_utils:to_binary(io_lib:format("~ts/~p", [Name, Arity]));
+ els_utils:to_binary(io_lib:format("~ts/~p", [Name, Arity]));
macro_label(Name) ->
- atom_to_binary(Name, utf8).
+ atom_to_binary(Name, utf8).
-spec format_function(atom(), [{integer(), string()}], boolean()) -> binary().
format_function(Name, Args, SnippetSupport) ->
- format_args(atom_to_label(Name), Args, SnippetSupport).
+ format_args(atom_to_label(Name), Args, SnippetSupport).
--spec format_macro( atom() | {atom(), non_neg_integer()}
- , [{integer(), string()}]
- , boolean()) -> binary().
+-spec format_macro(
+ atom() | {atom(), non_neg_integer()},
+ [{integer(), string()}],
+ boolean()
+) -> binary().
format_macro({Name0, _Arity}, Args, SnippetSupport) ->
- Name = atom_to_binary(Name0, utf8),
- format_args(Name, Args, SnippetSupport);
+ Name = atom_to_binary(Name0, utf8),
+ format_args(Name, Args, SnippetSupport);
format_macro(Name, none, _SnippetSupport) ->
- atom_to_binary(Name, utf8).
+ atom_to_binary(Name, utf8).
-spec format_args(binary(), [{integer(), string()}], boolean()) -> binary().
format_args(Name, Args0, SnippetSupport) ->
- Args =
- case SnippetSupport of
- false ->
- [];
- true ->
- ArgList = [["${", integer_to_list(N), ":", A, "}"] || {N, A} <- Args0],
- ["(", string:join(ArgList, ", "), ")"]
- end,
- els_utils:to_binary([Name | Args]).
+ Args =
+ case SnippetSupport of
+ false ->
+ [];
+ true ->
+ ArgList = [["${", integer_to_list(N), ":", A, "}"] || {N, A} <- Args0],
+ ["(", string:join(ArgList, ", "), ")"]
+ end,
+ els_utils:to_binary([Name | Args]).
-spec snippet_support() -> boolean().
snippet_support() ->
- case els_config:get(capabilities) of
- #{<<"textDocument">> :=
- #{<<"completion">> :=
- #{<<"completionItem">> :=
- #{<<"snippetSupport">> := SnippetSupport}}}} ->
- SnippetSupport;
- _ ->
- false
- end.
+ case els_config:get(capabilities) of
+ #{
+ <<"textDocument">> :=
+ #{
+ <<"completion">> :=
+ #{
+ <<"completionItem">> :=
+ #{<<"snippetSupport">> := SnippetSupport}
+ }
+ }
+ } ->
+ SnippetSupport;
+ _ ->
+ false
+ end.
-spec is_in(els_dt_document:item(), line(), column(), [poi_kind()]) ->
- boolean().
+ boolean().
is_in(Document, Line, Column, POIKinds) ->
- POIs = els_dt_document:get_element_at_pos(Document, Line, Column),
- IsKind = fun(#{kind := Kind}) -> lists:member(Kind, POIKinds) end,
- lists:any(IsKind, POIs).
+ POIs = els_dt_document:get_element_at_pos(Document, Line, Column),
+ IsKind = fun(#{kind := Kind}) -> lists:member(Kind, POIKinds) end,
+ lists:any(IsKind, POIs).
%% @doc Maps a POI kind to its completion item kind
-spec completion_item_kind(poi_kind()) -> completion_item_kind().
completion_item_kind(define) ->
- ?COMPLETION_ITEM_KIND_CONSTANT;
+ ?COMPLETION_ITEM_KIND_CONSTANT;
completion_item_kind(record) ->
- ?COMPLETION_ITEM_KIND_STRUCT;
+ ?COMPLETION_ITEM_KIND_STRUCT;
completion_item_kind(type_definition) ->
- ?COMPLETION_ITEM_KIND_TYPE_PARAM;
+ ?COMPLETION_ITEM_KIND_TYPE_PARAM;
completion_item_kind(function) ->
- ?COMPLETION_ITEM_KIND_FUNCTION.
+ ?COMPLETION_ITEM_KIND_FUNCTION.
%% @doc Maps a POI kind to its export entry POI kind
-spec export_entry_kind(poi_kind()) ->
- poi_kind() | {error, no_export_entry_kind}.
+ poi_kind() | {error, no_export_entry_kind}.
export_entry_kind(type_definition) -> export_type_entry;
export_entry_kind(function) -> export_entry;
export_entry_kind(_) -> {error, no_export_entry_kind}.
-spec atom_to_label(atom()) -> binary().
atom_to_label(Atom) when is_atom(Atom) ->
- unicode:characters_to_binary(io_lib:write(Atom)).
+ unicode:characters_to_binary(io_lib:write(Atom)).
%%==============================================================================
%% Tests
@@ -797,12 +970,12 @@ atom_to_label(Atom) when is_atom(Atom) ->
-include_lib("eunit/include/eunit.hrl").
strip_app_version_test() ->
- ?assertEqual(<<"foo">>, strip_app_version(<<"foo">>)),
- ?assertEqual(<<"foo">>, strip_app_version(<<"foo-1.2.3">>)),
- ?assertEqual(<<"">>, strip_app_version(<<"">>)),
- ?assertEqual(<<"foo-bar">>, strip_app_version(<<"foo-bar">>)),
- ?assertEqual(<<"foo-bar">>, strip_app_version(<<"foo-bar-1.2.3">>)),
- ?assertEqual(<<"foo-bar-baz">>, strip_app_version(<<"foo-bar-baz">>)),
- ?assertEqual(<<"foo-bar-baz">>, strip_app_version(<<"foo-bar-baz-1.2.3">>)).
+ ?assertEqual(<<"foo">>, strip_app_version(<<"foo">>)),
+ ?assertEqual(<<"foo">>, strip_app_version(<<"foo-1.2.3">>)),
+ ?assertEqual(<<"">>, strip_app_version(<<"">>)),
+ ?assertEqual(<<"foo-bar">>, strip_app_version(<<"foo-bar">>)),
+ ?assertEqual(<<"foo-bar">>, strip_app_version(<<"foo-bar-1.2.3">>)),
+ ?assertEqual(<<"foo-bar-baz">>, strip_app_version(<<"foo-bar-baz">>)),
+ ?assertEqual(<<"foo-bar-baz">>, strip_app_version(<<"foo-bar-baz-1.2.3">>)).
-endif.
diff --git a/apps/els_lsp/src/els_crossref_diagnostics.erl b/apps/els_lsp/src/els_crossref_diagnostics.erl
index 6a8ac0ca9..2633401a8 100644
--- a/apps/els_lsp/src/els_crossref_diagnostics.erl
+++ b/apps/els_lsp/src/els_crossref_diagnostics.erl
@@ -12,10 +12,11 @@
%%==============================================================================
%% Exports
%%==============================================================================
--export([ is_default/0
- , run/1
- , source/0
- ]).
+-export([
+ is_default/0,
+ run/1,
+ source/0
+]).
%%==============================================================================
%% Includes
@@ -29,73 +30,114 @@
-spec is_default() -> boolean().
is_default() ->
- false.
+ false.
-spec run(uri()) -> [els_diagnostics:diagnostic()].
run(Uri) ->
- case els_utils:lookup_document(Uri) of
- {error, _Error} ->
- [];
- {ok, Document} ->
- POIs = els_dt_document:pois(Document, [ application
- , implicit_fun
- , import_entry
- , export_entry
- ]),
- [make_diagnostic(POI) || POI <- POIs, not has_definition(POI, Document)]
- end.
+ case els_utils:lookup_document(Uri) of
+ {error, _Error} ->
+ [];
+ {ok, Document} ->
+ POIs = els_dt_document:pois(Document, [
+ application,
+ implicit_fun,
+ import_entry,
+ export_entry
+ ]),
+ [make_diagnostic(POI) || POI <- POIs, not has_definition(POI, Document)]
+ end.
-spec source() -> binary().
source() ->
- <<"CrossRef">>.
+ <<"CrossRef">>.
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec make_diagnostic(poi()) -> els_diagnostics:diagnostic().
make_diagnostic(#{range := Range, id := Id}) ->
- Function = case Id of
- {F, A} -> lists:flatten(io_lib:format("~p/~p", [F, A]));
- {M, F, A} -> lists:flatten(io_lib:format("~p:~p/~p", [M, F, A]))
- end,
- Message = els_utils:to_binary(
- io_lib:format( "Cannot find definition for function ~s"
- , [Function])),
- Severity = ?DIAGNOSTIC_ERROR,
- els_diagnostics:make_diagnostic( els_protocol:range(Range)
- , Message
- , Severity
- , source()).
+ Function =
+ case Id of
+ {F, A} -> lists:flatten(io_lib:format("~p/~p", [F, A]));
+ {M, F, A} -> lists:flatten(io_lib:format("~p:~p/~p", [M, F, A]))
+ end,
+ Message = els_utils:to_binary(
+ io_lib:format(
+ "Cannot find definition for function ~s",
+ [Function]
+ )
+ ),
+ Severity = ?DIAGNOSTIC_ERROR,
+ els_diagnostics:make_diagnostic(
+ els_protocol:range(Range),
+ Message,
+ Severity,
+ source()
+ ).
-spec has_definition(poi(), els_dt_document:item()) -> boolean().
-has_definition(#{ kind := application
- , id := {module_info, 0} }, _) -> true;
-has_definition(#{ kind := application
- , id := {module_info, 1} }, _) -> true;
-has_definition(#{ kind := application
- , id := {Module, module_info, Arity}
- }, _) when Arity =:= 0; Arity =:= 1 ->
- {ok, []} =/= els_dt_document_index:lookup(Module);
-has_definition(#{ kind := application
- , id := {record_info, 2} }, _) -> true;
-has_definition(#{ kind := application
- , id := {behaviour_info, 1} }, _) -> true;
-has_definition(#{ kind := application
- , id := {lager, Level, Arity} }, _) ->
- lager_definition(Level, Arity);
+has_definition(
+ #{
+ kind := application,
+ id := {module_info, 0}
+ },
+ _
+) ->
+ true;
+has_definition(
+ #{
+ kind := application,
+ id := {module_info, 1}
+ },
+ _
+) ->
+ true;
+has_definition(
+ #{
+ kind := application,
+ id := {Module, module_info, Arity}
+ },
+ _
+) when Arity =:= 0; Arity =:= 1 ->
+ {ok, []} =/= els_dt_document_index:lookup(Module);
+has_definition(
+ #{
+ kind := application,
+ id := {record_info, 2}
+ },
+ _
+) ->
+ true;
+has_definition(
+ #{
+ kind := application,
+ id := {behaviour_info, 1}
+ },
+ _
+) ->
+ true;
+has_definition(
+ #{
+ kind := application,
+ id := {lager, Level, Arity}
+ },
+ _
+) ->
+ lager_definition(Level, Arity);
has_definition(POI, #{uri := Uri}) ->
- case els_code_navigation:goto_definition(Uri, POI) of
- {ok, _Uri, _POI} ->
- true;
- {error, _Error} ->
- false
- end.
+ case els_code_navigation:goto_definition(Uri, POI) of
+ {ok, _Uri, _POI} ->
+ true;
+ {error, _Error} ->
+ false
+ end.
-spec lager_definition(atom(), integer()) -> boolean().
lager_definition(Level, Arity) when Arity =:= 1 orelse Arity =:= 2 ->
- lists:member(Level, lager_levels());
-lager_definition(_, _) -> false.
+ lists:member(Level, lager_levels());
+lager_definition(_, _) ->
+ false.
-spec lager_levels() -> [atom()].
lager_levels() ->
- [debug, info, notice, warning, error, critical, alert, emergency].
+ [debug, info, notice, warning, error, critical, alert, emergency].
diff --git a/apps/els_lsp/src/els_db.erl b/apps/els_lsp/src/els_db.erl
index 07731d98f..a613ea6bb 100644
--- a/apps/els_lsp/src/els_db.erl
+++ b/apps/els_lsp/src/els_db.erl
@@ -1,24 +1,25 @@
-module(els_db).
%% API
--export([ clear_table/1
- , clear_tables/0
- , delete/2
- , delete_object/2
- , lookup/2
- , match/2
- , match_delete/2
- , select_delete/2
- , tables/0
- , write/2
- , conditional_write/4
- ]).
+-export([
+ clear_table/1,
+ clear_tables/0,
+ delete/2,
+ delete_object/2,
+ lookup/2,
+ match/2,
+ match_delete/2,
+ select_delete/2,
+ tables/0,
+ write/2,
+ conditional_write/4
+]).
%%==============================================================================
%% Type Definitions
%%==============================================================================
-type condition() :: fun((tuple()) -> boolean()).
--export_type([ condition/0 ]).
+-export_type([condition/0]).
%%==============================================================================
%% Exported functions
@@ -26,50 +27,51 @@
-spec tables() -> [atom()].
tables() ->
- [ els_dt_document
- , els_dt_document_index
- , els_dt_references
- , els_dt_signatures
- ].
+ [
+ els_dt_document,
+ els_dt_document_index,
+ els_dt_references,
+ els_dt_signatures
+ ].
-spec delete(atom(), any()) -> ok.
delete(Table, Key) ->
- els_db_server:delete(Table, Key).
+ els_db_server:delete(Table, Key).
-spec delete_object(atom(), any()) -> ok.
delete_object(Table, Object) ->
- els_db_server:delete_object(Table, Object).
+ els_db_server:delete_object(Table, Object).
-spec lookup(atom(), any()) -> {ok, [tuple()]}.
lookup(Table, Key) ->
- {ok, ets:lookup(Table, Key)}.
+ {ok, ets:lookup(Table, Key)}.
-spec match(atom(), tuple()) -> {ok, [tuple()]}.
match(Table, Pattern) when is_tuple(Pattern) ->
- {ok, ets:match_object(Table, Pattern)}.
+ {ok, ets:match_object(Table, Pattern)}.
-spec match_delete(atom(), tuple()) -> ok.
match_delete(Table, Pattern) when is_tuple(Pattern) ->
- els_db_server:match_delete(Table, Pattern).
+ els_db_server:match_delete(Table, Pattern).
-spec select_delete(atom(), any()) -> ok.
select_delete(Table, MS) ->
- els_db_server:select_delete(Table, MS).
+ els_db_server:select_delete(Table, MS).
-spec write(atom(), tuple()) -> ok.
write(Table, Object) when is_tuple(Object) ->
- els_db_server:write(Table, Object).
+ els_db_server:write(Table, Object).
-spec conditional_write(atom(), any(), tuple(), condition()) ->
- ok | {error, any()}.
+ ok | {error, any()}.
conditional_write(Table, Key, Object, Condition) when is_tuple(Object) ->
- els_db_server:conditional_write(Table, Key, Object, Condition).
+ els_db_server:conditional_write(Table, Key, Object, Condition).
-spec clear_table(atom()) -> ok.
clear_table(Table) ->
- els_db_server:clear_table(Table).
+ els_db_server:clear_table(Table).
-spec clear_tables() -> ok.
clear_tables() ->
- [ok = clear_table(T) || T <- tables()],
- ok.
+ [ok = clear_table(T) || T <- tables()],
+ ok.
diff --git a/apps/els_lsp/src/els_db_server.erl b/apps/els_lsp/src/els_db_server.erl
index d5ac8b953..f08e5fa99 100644
--- a/apps/els_lsp/src/els_db_server.erl
+++ b/apps/els_lsp/src/els_db_server.erl
@@ -7,15 +7,16 @@
%%==============================================================================
%% API
%%==============================================================================
--export([ start_link/0
- , clear_table/1
- , delete/2
- , delete_object/2
- , match_delete/2
- , select_delete/2
- , write/2
- , conditional_write/4
- ]).
+-export([
+ start_link/0,
+ clear_table/1,
+ delete/2,
+ delete_object/2,
+ match_delete/2,
+ select_delete/2,
+ write/2,
+ conditional_write/4
+]).
%%==============================================================================
%% Includes
@@ -26,11 +27,12 @@
%% Callbacks for the gen_server behaviour
%%==============================================================================
-behaviour(gen_server).
--export([ init/1
- , handle_call/3
- , handle_cast/2
- , handle_info/2
- ]).
+-export([
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2
+]).
-type state() :: #{}.
%%==============================================================================
@@ -43,87 +45,89 @@
%%==============================================================================
-spec start_link() -> {ok, pid()}.
start_link() ->
- gen_server:start_link({local, ?SERVER}, ?MODULE, unused, []).
+ gen_server:start_link({local, ?SERVER}, ?MODULE, unused, []).
-spec clear_table(atom()) -> ok.
clear_table(Table) ->
- gen_server:call(?SERVER, {clear_table, Table}).
+ gen_server:call(?SERVER, {clear_table, Table}).
-spec delete(atom(), any()) -> ok.
delete(Table, Key) ->
- gen_server:call(?SERVER, {delete, Table, Key}).
+ gen_server:call(?SERVER, {delete, Table, Key}).
-spec delete_object(atom(), any()) -> ok.
delete_object(Table, Key) ->
- gen_server:call(?SERVER, {delete_object, Table, Key}).
+ gen_server:call(?SERVER, {delete_object, Table, Key}).
-spec match_delete(atom(), tuple()) -> ok.
match_delete(Table, Pattern) ->
- gen_server:call(?SERVER, {match_delete, Table, Pattern}).
+ gen_server:call(?SERVER, {match_delete, Table, Pattern}).
-spec select_delete(atom(), any()) -> ok.
select_delete(Table, MS) ->
- gen_server:call(?SERVER, {select_delete, Table, MS}).
+ gen_server:call(?SERVER, {select_delete, Table, MS}).
-spec write(atom(), tuple()) -> ok.
write(Table, Object) ->
- gen_server:call(?SERVER, {write, Table, Object}).
+ gen_server:call(?SERVER, {write, Table, Object}).
-spec conditional_write(atom(), any(), tuple(), els_db:condition()) ->
- ok | {error, any()}.
+ ok | {error, any()}.
conditional_write(Table, Key, Object, Condition) ->
- gen_server:call(?SERVER, {conditional_write, Table, Key, Object, Condition}).
+ gen_server:call(?SERVER, {conditional_write, Table, Key, Object, Condition}).
%%==============================================================================
%% Callbacks for the gen_server behaviour
%%==============================================================================
-spec init(unused) -> {ok, state()}.
init(unused) ->
- [ok = els_db_table:init(Table) || Table <- els_db:tables()],
- {ok, #{}}.
+ [ok = els_db_table:init(Table) || Table <- els_db:tables()],
+ {ok, #{}}.
-spec handle_call(any(), {pid(), any()}, state()) ->
- {reply, any(), state()} | {noreply, state()}.
+ {reply, any(), state()} | {noreply, state()}.
handle_call({clear_table, Table}, _From, State) ->
- true = ets:delete_all_objects(Table),
- {reply, ok, State};
+ true = ets:delete_all_objects(Table),
+ {reply, ok, State};
handle_call({delete, Table, Key}, _From, State) ->
- true = ets:delete(Table, Key),
- {reply, ok, State};
+ true = ets:delete(Table, Key),
+ {reply, ok, State};
handle_call({delete_object, Table, Key}, _From, State) ->
- true = ets:delete_object(Table, Key),
- {reply, ok, State};
+ true = ets:delete_object(Table, Key),
+ {reply, ok, State};
handle_call({match_delete, Table, Pattern}, _From, State) ->
- true = ets:match_delete(Table, Pattern),
- {reply, ok, State};
+ true = ets:match_delete(Table, Pattern),
+ {reply, ok, State};
handle_call({select_delete, Table, MS}, _From, State) ->
- ets:select_delete(Table, MS),
- {reply, ok, State};
+ ets:select_delete(Table, MS),
+ {reply, ok, State};
handle_call({write, Table, Object}, _From, State) ->
- true = ets:insert(Table, Object),
- {reply, ok, State};
+ true = ets:insert(Table, Object),
+ {reply, ok, State};
handle_call({conditional_write, Table, Key, Object, Condition}, _From, State) ->
- case ets:lookup(Table, Key) of
- [Entry] ->
- case Condition(Entry) of
- true ->
- true = ets:insert(Table, Object),
- {reply, ok, State};
- false ->
- ?LOG_DEBUG("Skip insertion due to invalid condition "
- "[table=~p] [key=~p]",
- [Table, Key]),
- {reply, {error, condition_not_satisfied}, State}
- end;
- [] ->
- true = ets:insert(Table, Object),
- {reply, ok, State}
- end.
+ case ets:lookup(Table, Key) of
+ [Entry] ->
+ case Condition(Entry) of
+ true ->
+ true = ets:insert(Table, Object),
+ {reply, ok, State};
+ false ->
+ ?LOG_DEBUG(
+ "Skip insertion due to invalid condition "
+ "[table=~p] [key=~p]",
+ [Table, Key]
+ ),
+ {reply, {error, condition_not_satisfied}, State}
+ end;
+ [] ->
+ true = ets:insert(Table, Object),
+ {reply, ok, State}
+ end.
-spec handle_cast(any(), state()) -> {noreply, state()}.
handle_cast(_Request, State) ->
- {noreply, State}.
+ {noreply, State}.
-spec handle_info(any(), state()) -> {noreply, state()}.
handle_info(_Request, State) ->
- {noreply, State}.
+ {noreply, State}.
diff --git a/apps/els_lsp/src/els_db_table.erl b/apps/els_lsp/src/els_db_table.erl
index 3b6cc328c..2c6afb486 100644
--- a/apps/els_lsp/src/els_db_table.erl
+++ b/apps/els_lsp/src/els_db_table.erl
@@ -15,11 +15,12 @@
%% Exports
%%==============================================================================
--export([ default_opts/0
- , init/1
- , name/1
- , opts/1
- ]).
+-export([
+ default_opts/0,
+ init/1,
+ name/1,
+ opts/1
+]).
%%==============================================================================
%% Includes
@@ -30,7 +31,7 @@
%% Type Definitions
%%==============================================================================
-type table() :: atom().
--export_type([ table/0 ]).
+-export_type([table/0]).
%%==============================================================================
%% API
@@ -38,19 +39,19 @@
-spec default_opts() -> [any()].
default_opts() ->
- [public, named_table, {keypos, 2}, {read_concurrency, true}].
+ [public, named_table, {keypos, 2}, {read_concurrency, true}].
-spec init(table()) -> ok.
init(Table) ->
- TableName = name(Table),
- ?LOG_INFO("Creating table [name=~p]", [TableName]),
- ets:new(TableName, opts(Table)),
- ok.
+ TableName = name(Table),
+ ?LOG_INFO("Creating table [name=~p]", [TableName]),
+ ets:new(TableName, opts(Table)),
+ ok.
-spec name(table()) -> atom().
name(Table) ->
- Table:name().
+ Table:name().
-spec opts(table()) -> proplists:proplist().
opts(Table) ->
- default_opts() ++ Table:opts().
+ default_opts() ++ Table:opts().
diff --git a/apps/els_lsp/src/els_definition_provider.erl b/apps/els_lsp/src/els_definition_provider.erl
index 631026220..2f692fbaa 100644
--- a/apps/els_lsp/src/els_definition_provider.erl
+++ b/apps/els_lsp/src/els_definition_provider.erl
@@ -2,9 +2,10 @@
-behaviour(els_provider).
--export([ is_enabled/0
- , handle_request/2
- ]).
+-export([
+ is_enabled/0,
+ handle_request/2
+]).
-include("els_lsp.hrl").
@@ -18,78 +19,92 @@ is_enabled() -> true.
-spec handle_request(any(), state()) -> {response, any()}.
handle_request({definition, Params}, State) ->
- #{ <<"position">> := #{ <<"line">> := Line
- , <<"character">> := Character
- }
- , <<"textDocument">> := #{<<"uri">> := Uri}
- } = Params,
- {ok, Document} = els_utils:lookup_document(Uri),
- POIs = els_dt_document:get_element_at_pos(Document, Line + 1, Character + 1),
- case goto_definition(Uri, POIs) of
- null ->
- #{text := Text} = Document,
- IncompletePOIs = match_incomplete(Text, {Line, Character}),
- case goto_definition(Uri, IncompletePOIs) of
+ #{
+ <<"position">> := #{
+ <<"line">> := Line,
+ <<"character">> := Character
+ },
+ <<"textDocument">> := #{<<"uri">> := Uri}
+ } = Params,
+ {ok, Document} = els_utils:lookup_document(Uri),
+ POIs = els_dt_document:get_element_at_pos(Document, Line + 1, Character + 1),
+ case goto_definition(Uri, POIs) of
null ->
- els_references_provider:handle_request({references, Params}, State);
+ #{text := Text} = Document,
+ IncompletePOIs = match_incomplete(Text, {Line, Character}),
+ case goto_definition(Uri, IncompletePOIs) of
+ null ->
+ els_references_provider:handle_request({references, Params}, State);
+ GoTo ->
+ {response, GoTo}
+ end;
GoTo ->
- {response, GoTo}
- end;
- GoTo ->
- {response, GoTo}
- end.
+ {response, GoTo}
+ end.
-spec goto_definition(uri(), [poi()]) -> map() | null.
goto_definition(_Uri, []) ->
- null;
-goto_definition(Uri, [POI|Rest]) ->
- case els_code_navigation:goto_definition(Uri, POI) of
- {ok, DefUri, #{range := Range}} ->
- #{uri => DefUri, range => els_protocol:range(Range)};
- _ ->
- goto_definition(Uri, Rest)
- end.
+ null;
+goto_definition(Uri, [POI | Rest]) ->
+ case els_code_navigation:goto_definition(Uri, POI) of
+ {ok, DefUri, #{range := Range}} ->
+ #{uri => DefUri, range => els_protocol:range(Range)};
+ _ ->
+ goto_definition(Uri, Rest)
+ end.
-spec match_incomplete(binary(), pos()) -> [poi()].
match_incomplete(Text, Pos) ->
- %% Try parsing subsets of text to find a matching POI at Pos
- match_after(Text, Pos) ++ match_line(Text, Pos).
+ %% Try parsing subsets of text to find a matching POI at Pos
+ match_after(Text, Pos) ++ match_line(Text, Pos).
-spec match_after(binary(), pos()) -> [poi()].
match_after(Text, {Line, Character}) ->
- %% Try to parse current line and the lines after it
- POIs = els_incomplete_parser:parse_after(Text, Line),
- MatchingPOIs = match_pois(POIs, {1, Character + 1}),
- fix_line_offsets(MatchingPOIs, Line).
+ %% Try to parse current line and the lines after it
+ POIs = els_incomplete_parser:parse_after(Text, Line),
+ MatchingPOIs = match_pois(POIs, {1, Character + 1}),
+ fix_line_offsets(MatchingPOIs, Line).
-spec match_line(binary(), pos()) -> [poi()].
match_line(Text, {Line, Character}) ->
- %% Try to parse only current line
- POIs = els_incomplete_parser:parse_line(Text, Line),
- MatchingPOIs = match_pois(POIs, {1, Character + 1}),
- fix_line_offsets(MatchingPOIs, Line).
+ %% Try to parse only current line
+ POIs = els_incomplete_parser:parse_line(Text, Line),
+ MatchingPOIs = match_pois(POIs, {1, Character + 1}),
+ fix_line_offsets(MatchingPOIs, Line).
-spec match_pois([poi()], pos()) -> [poi()].
match_pois(POIs, Pos) ->
- els_poi:sort(els_poi:match_pos(POIs, Pos)).
+ els_poi:sort(els_poi:match_pos(POIs, Pos)).
-spec fix_line_offsets([poi()], integer()) -> [poi()].
fix_line_offsets(POIs, Offset) ->
- [fix_line_offset(POI, Offset) || POI <- POIs].
+ [fix_line_offset(POI, Offset) || POI <- POIs].
-spec fix_line_offset(poi(), integer()) -> poi().
-fix_line_offset(#{range := #{from := {FromL, FromC},
- to := {ToL, ToC}}} = POI, Offset) ->
- %% TODO: Fix other ranges too
- POI#{range => #{from => {FromL + Offset, FromC},
- to => {ToL + Offset, ToC}
- }}.
+fix_line_offset(
+ #{
+ range := #{
+ from := {FromL, FromC},
+ to := {ToL, ToC}
+ }
+ } = POI,
+ Offset
+) ->
+ %% TODO: Fix other ranges too
+ POI#{
+ range => #{
+ from => {FromL + Offset, FromC},
+ to => {ToL + Offset, ToC}
+ }
+ }.
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
fix_line_offset_test() ->
- In = #{range => #{from => {1, 16}, to => {1, 32}}},
- ?assertMatch( #{range := #{from := {66, 16}, to := {66, 32}}}
- , fix_line_offset(In, 65)).
+ In = #{range => #{from => {1, 16}, to => {1, 32}}},
+ ?assertMatch(
+ #{range := #{from := {66, 16}, to := {66, 32}}},
+ fix_line_offset(In, 65)
+ ).
-endif.
diff --git a/apps/els_lsp/src/els_diagnostics.erl b/apps/els_lsp/src/els_diagnostics.erl
index 6f76fe639..1ce399c5f 100644
--- a/apps/els_lsp/src/els_diagnostics.erl
+++ b/apps/els_lsp/src/els_diagnostics.erl
@@ -12,46 +12,51 @@
%%==============================================================================
%% Types
%%==============================================================================
--type diagnostic() :: #{ range := range()
- , severity => severity()
- , code => number() | binary()
- , source => binary()
- , message := binary()
- , relatedInformation => [related_info()]
- , data => binary()
- }.
+-type diagnostic() :: #{
+ range := range(),
+ severity => severity(),
+ code => number() | binary(),
+ source => binary(),
+ message := binary(),
+ relatedInformation => [related_info()],
+ data => binary()
+}.
-type diagnostic_id() :: binary().
--type related_info() :: #{ location := location()
- , message := binary()
- }.
--type severity() :: ?DIAGNOSTIC_ERROR
- | ?DIAGNOSTIC_WARNING
- | ?DIAGNOSTIC_INFO
- | ?DIAGNOSTIC_HINT.
--export_type([ diagnostic/0
- , diagnostic_id/0
- , severity/0
- ]).
+-type related_info() :: #{
+ location := location(),
+ message := binary()
+}.
+-type severity() ::
+ ?DIAGNOSTIC_ERROR
+ | ?DIAGNOSTIC_WARNING
+ | ?DIAGNOSTIC_INFO
+ | ?DIAGNOSTIC_HINT.
+-export_type([
+ diagnostic/0,
+ diagnostic_id/0,
+ severity/0
+]).
%%==============================================================================
%% Callback Functions Definitions
%%==============================================================================
--callback is_default() -> boolean().
--callback run(uri()) -> [diagnostic()].
--callback source() -> binary().
+-callback is_default() -> boolean().
+-callback run(uri()) -> [diagnostic()].
+-callback source() -> binary().
-callback on_complete(uri(), [diagnostic()]) -> ok.
--optional_callbacks([ on_complete/2 ]).
+-optional_callbacks([on_complete/2]).
%%==============================================================================
%% API
%%==============================================================================
--export([ available_diagnostics/0
- , default_diagnostics/0
- , enabled_diagnostics/0
- , make_diagnostic/4
- , make_diagnostic/5
- , run_diagnostics/1
- ]).
+-export([
+ available_diagnostics/0,
+ default_diagnostics/0,
+ enabled_diagnostics/0,
+ make_diagnostic/4,
+ make_diagnostic/5,
+ run_diagnostics/1
+]).
%%==============================================================================
%% API
@@ -59,53 +64,56 @@
-spec available_diagnostics() -> [diagnostic_id()].
available_diagnostics() ->
- [ <<"bound_var_in_pattern">>
- , <<"compiler">>
- , <<"crossref">>
- , <<"dialyzer">>
- , <<"edoc">>
- , <<"gradualizer">>
- , <<"elvis">>
- , <<"unused_includes">>
- , <<"unused_macros">>
- , <<"unused_record_fields">>
- , <<"refactorerl">>
- ].
+ [
+ <<"bound_var_in_pattern">>,
+ <<"compiler">>,
+ <<"crossref">>,
+ <<"dialyzer">>,
+ <<"edoc">>,
+ <<"gradualizer">>,
+ <<"elvis">>,
+ <<"unused_includes">>,
+ <<"unused_macros">>,
+ <<"unused_record_fields">>,
+ <<"refactorerl">>
+ ].
-spec default_diagnostics() -> [diagnostic_id()].
default_diagnostics() ->
- [Id || Id <- available_diagnostics(), (cb_module(Id)):is_default()].
+ [Id || Id <- available_diagnostics(), (cb_module(Id)):is_default()].
-spec enabled_diagnostics() -> [diagnostic_id()].
enabled_diagnostics() ->
- Config = els_config:get(diagnostics),
- Default = default_diagnostics(),
- Enabled = maps:get("enabled", Config, []),
- Disabled = maps:get("disabled", Config, []),
- lists:usort((Default ++ valid(Enabled)) -- valid(Disabled)).
+ Config = els_config:get(diagnostics),
+ Default = default_diagnostics(),
+ Enabled = maps:get("enabled", Config, []),
+ Disabled = maps:get("disabled", Config, []),
+ lists:usort((Default ++ valid(Enabled)) -- valid(Disabled)).
-spec make_diagnostic(range(), binary(), severity(), binary()) ->
- diagnostic().
+ diagnostic().
make_diagnostic(Range, Message, Severity, Source) ->
- #{ range => Range
- , message => Message
- , severity => Severity
- , source => Source
- }.
-
--spec make_diagnostic(range(), binary(), severity(), binary(), binary())
- -> diagnostic().
+ #{
+ range => Range,
+ message => Message,
+ severity => Severity,
+ source => Source
+ }.
+
+-spec make_diagnostic(range(), binary(), severity(), binary(), binary()) ->
+ diagnostic().
make_diagnostic(Range, Message, Severity, Source, Data) ->
- #{ range => Range
- , message => Message
- , severity => Severity
- , source => Source
- , data => Data
- }.
+ #{
+ range => Range,
+ message => Message,
+ severity => Severity,
+ source => Source,
+ data => Data
+ }.
-spec run_diagnostics(uri()) -> [pid()].
run_diagnostics(Uri) ->
- [run_diagnostic(Uri, Id) || Id <- enabled_diagnostics()].
+ [run_diagnostic(Uri, Id) || Id <- enabled_diagnostics()].
%%==============================================================================
%% Internal Functions
@@ -113,51 +121,55 @@ run_diagnostics(Uri) ->
-spec run_diagnostic(uri(), diagnostic_id()) -> pid().
run_diagnostic(Uri, Id) ->
- CbModule = cb_module(Id),
- Source = CbModule:source(),
- Module = atom_to_binary(els_uri:module(Uri), utf8),
- Title = <>,
- Config = #{ task => fun(U, _) -> CbModule:run(U) end
- , entries => [Uri]
- , title => Title
- , on_complete =>
- fun(Diagnostics) ->
- case erlang:function_exported(CbModule, on_complete, 2) of
- true ->
+ CbModule = cb_module(Id),
+ Source = CbModule:source(),
+ Module = atom_to_binary(els_uri:module(Uri), utf8),
+ Title = <>,
+ Config = #{
+ task => fun(U, _) -> CbModule:run(U) end,
+ entries => [Uri],
+ title => Title,
+ on_complete =>
+ fun(Diagnostics) ->
+ case erlang:function_exported(CbModule, on_complete, 2) of
+ true ->
CbModule:on_complete(Uri, Diagnostics);
- false ->
+ false ->
ok
- end,
- els_diagnostics_provider:notify(Diagnostics, self())
- end
- },
- {ok, Pid} = els_background_job:new(Config),
- Pid.
+ end,
+ els_diagnostics_provider:notify(Diagnostics, self())
+ end
+ },
+ {ok, Pid} = els_background_job:new(Config),
+ Pid.
%% @doc Return the callback module for a given Diagnostic Identifier
-spec cb_module(diagnostic_id()) -> module().
cb_module(Id) ->
- binary_to_existing_atom(<<"els_", Id/binary, "_diagnostics">>, utf8).
+ binary_to_existing_atom(<<"els_", Id/binary, "_diagnostics">>, utf8).
-spec is_valid(diagnostic_id()) -> boolean().
is_valid(Id) ->
- lists:member(Id, available_diagnostics()).
+ lists:member(Id, available_diagnostics()).
-spec valid([string()]) -> [diagnostic_id()].
valid(Ids0) ->
- Ids = [els_utils:to_binary(Id) || Id <- Ids0],
- {Valid, Invalid} = lists:partition(fun is_valid/1, Ids),
- case Invalid of
- [] ->
- ok;
- _ ->
- Fmt = "Skipping invalid diagnostics in config file: ~p",
- Args = [Invalid],
- Msg = lists:flatten(io_lib:format(Fmt, Args)),
- ?LOG_WARNING(Msg),
- els_server:send_notification(<<"window/showMessage">>,
- #{ type => ?MESSAGE_TYPE_WARNING,
- message => els_utils:to_binary(Msg)
- })
- end,
- Valid.
+ Ids = [els_utils:to_binary(Id) || Id <- Ids0],
+ {Valid, Invalid} = lists:partition(fun is_valid/1, Ids),
+ case Invalid of
+ [] ->
+ ok;
+ _ ->
+ Fmt = "Skipping invalid diagnostics in config file: ~p",
+ Args = [Invalid],
+ Msg = lists:flatten(io_lib:format(Fmt, Args)),
+ ?LOG_WARNING(Msg),
+ els_server:send_notification(
+ <<"window/showMessage">>,
+ #{
+ type => ?MESSAGE_TYPE_WARNING,
+ message => els_utils:to_binary(Msg)
+ }
+ )
+ end,
+ Valid.
diff --git a/apps/els_lsp/src/els_diagnostics_provider.erl b/apps/els_lsp/src/els_diagnostics_provider.erl
index 65c2ce14b..90902b1a4 100644
--- a/apps/els_lsp/src/els_diagnostics_provider.erl
+++ b/apps/els_lsp/src/els_diagnostics_provider.erl
@@ -2,14 +2,16 @@
-behaviour(els_provider).
--export([ is_enabled/0
- , options/0
- , handle_request/2
- ]).
+-export([
+ is_enabled/0,
+ options/0,
+ handle_request/2
+]).
--export([ notify/2
- , publish/2
- ]).
+-export([
+ notify/2,
+ publish/2
+]).
%%==============================================================================
%% Includes
@@ -25,27 +27,28 @@ is_enabled() -> true.
-spec options() -> map().
options() ->
- #{}.
+ #{}.
-spec handle_request(any(), any()) -> {diagnostics, uri(), [pid()]}.
handle_request({run_diagnostics, Params}, _State) ->
- #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
- ?LOG_DEBUG("Starting diagnostics jobs [uri=~p]", [Uri]),
- Jobs = els_diagnostics:run_diagnostics(Uri),
- {diagnostics, Uri, Jobs}.
+ #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
+ ?LOG_DEBUG("Starting diagnostics jobs [uri=~p]", [Uri]),
+ Jobs = els_diagnostics:run_diagnostics(Uri),
+ {diagnostics, Uri, Jobs}.
%%==============================================================================
%% API
%%==============================================================================
-spec notify([els_diagnostics:diagnostic()], pid()) -> ok.
notify(Diagnostics, Job) ->
- els_provider ! {diagnostics, Diagnostics, Job},
- ok.
+ els_provider ! {diagnostics, Diagnostics, Job},
+ ok.
-spec publish(uri(), [els_diagnostics:diagnostic()]) -> ok.
publish(Uri, Diagnostics) ->
- Method = <<"textDocument/publishDiagnostics">>,
- Params = #{ uri => Uri
- , diagnostics => Diagnostics
- },
- els_server:send_notification(Method, Params).
+ Method = <<"textDocument/publishDiagnostics">>,
+ Params = #{
+ uri => Uri,
+ diagnostics => Diagnostics
+ },
+ els_server:send_notification(Method, Params).
diff --git a/apps/els_lsp/src/els_diagnostics_utils.erl b/apps/els_lsp/src/els_diagnostics_utils.erl
index 01d442249..85c3b1b3c 100644
--- a/apps/els_lsp/src/els_diagnostics_utils.erl
+++ b/apps/els_lsp/src/els_diagnostics_utils.erl
@@ -6,12 +6,13 @@
%%==============================================================================
%% Exports
%%==============================================================================
--export([ dependencies/1
- , included_uris/1
- , included_documents/1
- , range/2
- , traverse_include_graph/3
- ]).
+-export([
+ dependencies/1,
+ included_uris/1,
+ included_documents/1,
+ range/2,
+ traverse_include_graph/3
+]).
%%==============================================================================
%% Includes
%%==============================================================================
@@ -20,61 +21,67 @@
-spec dependencies(uri()) -> [atom()].
dependencies(Uri) ->
- dependencies([Uri], [], sets:new()).
+ dependencies([Uri], [], sets:new()).
-spec included_uris(els_dt_document:item()) -> [uri()].
included_uris(Document) ->
- POIs = els_dt_document:pois(Document, [include, include_lib]),
- included_uris([Id || #{id := Id} <- POIs], []).
+ POIs = els_dt_document:pois(Document, [include, include_lib]),
+ included_uris([Id || #{id := Id} <- POIs], []).
-spec included_documents(els_dt_document:item()) -> [els_dt_document:item()].
included_documents(Document) ->
- lists:filtermap(fun find_included_document/1, included_uris(Document)).
+ lists:filtermap(fun find_included_document/1, included_uris(Document)).
-spec find_included_document(uri()) -> {true, els_dt_document:item()} | false.
find_included_document(Uri) ->
- case els_utils:lookup_document(Uri) of
- {ok, IncludeDocument} ->
- {true, IncludeDocument};
- {error, _} ->
- ?LOG_WARNING("Failed included document lookup [uri=~p]", [Uri]),
- false
- end.
+ case els_utils:lookup_document(Uri) of
+ {ok, IncludeDocument} ->
+ {true, IncludeDocument};
+ {error, _} ->
+ ?LOG_WARNING("Failed included document lookup [uri=~p]", [Uri]),
+ false
+ end.
--spec range(els_dt_document:item() | undefined,
- erl_anno:anno() | none) -> poi_range().
+-spec range(
+ els_dt_document:item() | undefined,
+ erl_anno:anno() | none
+) -> poi_range().
range(Document, none) ->
- range(Document, erl_anno:new(1));
+ range(Document, erl_anno:new(1));
range(Document, Anno) ->
- true = erl_anno:is_anno(Anno),
- Line = erl_anno:line(Anno),
- case erl_anno:column(Anno) of
- Col when Document =:= undefined; Col =:= undefined ->
- #{from => {Line, 1}, to => {Line + 1, 1}};
- Col ->
- POIs0 = els_dt_document:get_element_at_pos(Document, Line, Col),
+ true = erl_anno:is_anno(Anno),
+ Line = erl_anno:line(Anno),
+ case erl_anno:column(Anno) of
+ Col when Document =:= undefined; Col =:= undefined ->
+ #{from => {Line, 1}, to => {Line + 1, 1}};
+ Col ->
+ POIs0 = els_dt_document:get_element_at_pos(Document, Line, Col),
- %% Exclude folding range since line is more exact anyway
- POIs = [POI || #{kind := Kind} = POI <- POIs0, Kind =/= folding_range],
+ %% Exclude folding range since line is more exact anyway
+ POIs = [POI || #{kind := Kind} = POI <- POIs0, Kind =/= folding_range],
- %% * If we find no pois that we just return the original line
- %% * If we find a poi that start on the line and col as the anno
- %% we are looking for we that that one.
- %% * We take the "first" poi if we find some, but none come from
- %% the correct line and number.
+ %% * If we find no pois that we just return the original line
+ %% * If we find a poi that start on the line and col as the anno
+ %% we are looking for we that that one.
+ %% * We take the "first" poi if we find some, but none come from
+ %% the correct line and number.
- case lists:search(
- fun(#{ range := #{ from := {FromLine, FromCol} } }) ->
- FromLine =:= Line andalso FromCol =:= Col
- end, POIs) of
- {value, #{ range := Range } } ->
- Range;
- false when POIs =:= [] ->
- #{ from => {Line, 1}, to => {Line + 1, 1} };
- false ->
- maps:get(range, hd(POIs))
- end
- end.
+ case
+ lists:search(
+ fun(#{range := #{from := {FromLine, FromCol}}}) ->
+ FromLine =:= Line andalso FromCol =:= Col
+ end,
+ POIs
+ )
+ of
+ {value, #{range := Range}} ->
+ Range;
+ false when POIs =:= [] ->
+ #{from => {Line, 1}, to => {Line + 1, 1}};
+ false ->
+ maps:get(range, hd(POIs))
+ end
+ end.
-spec traverse_include_graph(AccFun, AccT, From) -> AccT when
AccFun :: fun((Included, Includer, AccT) -> AccT),
@@ -83,8 +90,9 @@ range(Document, Anno) ->
Includer :: els_dt_document:item().
traverse_include_graph(AccFun, Acc, From) ->
Graph = els_fungraph:new(
- fun els_dt_document:uri/1,
- fun included_documents/1),
+ fun els_dt_document:uri/1,
+ fun included_documents/1
+ ),
els_fungraph:traverse(AccFun, Acc, From, Graph).
%%==============================================================================
@@ -92,73 +100,81 @@ traverse_include_graph(AccFun, Acc, From) ->
%%==============================================================================
-spec dependencies([uri()], [atom()], sets:set(binary())) -> [atom()].
dependencies([], Acc, _AlreadyProcessed) ->
- Acc;
-dependencies([Uri|Uris], Acc, AlreadyProcessed) ->
- case els_utils:lookup_document(Uri) of
- {ok, Document} ->
- Behaviours = els_dt_document:pois(Document, [behaviour]),
- ParseTransforms = els_dt_document:pois(Document, [parse_transform]),
- IncludedUris = included_uris(Document),
- FilteredIncludedUris = exclude_already_processed( IncludedUris
- , AlreadyProcessed
- ),
- PTUris = lists:usort(
- lists:flatten(
- [pt_deps(Id) || #{id := Id} <- ParseTransforms])),
- FilteredPTUris = exclude_already_processed( PTUris
- , AlreadyProcessed
- ),
- dependencies( Uris ++ FilteredIncludedUris ++ FilteredPTUris
- , Acc ++ [Id || #{id := Id} <- Behaviours ++ ParseTransforms]
- ++ [els_uri:module(FPTUri) || FPTUri <- FilteredPTUris]
- , sets:add_element(Uri, AlreadyProcessed));
- {error, _Error} ->
- []
- end.
+ Acc;
+dependencies([Uri | Uris], Acc, AlreadyProcessed) ->
+ case els_utils:lookup_document(Uri) of
+ {ok, Document} ->
+ Behaviours = els_dt_document:pois(Document, [behaviour]),
+ ParseTransforms = els_dt_document:pois(Document, [parse_transform]),
+ IncludedUris = included_uris(Document),
+ FilteredIncludedUris = exclude_already_processed(
+ IncludedUris,
+ AlreadyProcessed
+ ),
+ PTUris = lists:usort(
+ lists:flatten(
+ [pt_deps(Id) || #{id := Id} <- ParseTransforms]
+ )
+ ),
+ FilteredPTUris = exclude_already_processed(
+ PTUris,
+ AlreadyProcessed
+ ),
+ dependencies(
+ Uris ++ FilteredIncludedUris ++ FilteredPTUris,
+ Acc ++ [Id || #{id := Id} <- Behaviours ++ ParseTransforms] ++
+ [els_uri:module(FPTUri) || FPTUri <- FilteredPTUris],
+ sets:add_element(Uri, AlreadyProcessed)
+ );
+ {error, _Error} ->
+ []
+ end.
-spec exclude_already_processed([uri()], sets:set()) -> [uri()].
exclude_already_processed(Uris, AlreadyProcessed) ->
- [Uri || Uri <- Uris, not sets:is_element(Uri, AlreadyProcessed)].
+ [Uri || Uri <- Uris, not sets:is_element(Uri, AlreadyProcessed)].
-spec pt_deps(atom()) -> [uri()].
pt_deps(Module) ->
- case els_utils:find_module(Module) of
- {ok, Uri} ->
- case els_utils:lookup_document(Uri) of
- {ok, Document} ->
- Applications = els_dt_document:pois(Document, [ application
- , implicit_fun
- ]),
- applications_to_uris(Applications);
- {error, _Error} ->
- []
- end;
- {error, Error} ->
- ?LOG_INFO("Find module failed [module=~p] [error=~p]", [Module, Error]),
- []
- end.
+ case els_utils:find_module(Module) of
+ {ok, Uri} ->
+ case els_utils:lookup_document(Uri) of
+ {ok, Document} ->
+ Applications = els_dt_document:pois(Document, [
+ application,
+ implicit_fun
+ ]),
+ applications_to_uris(Applications);
+ {error, _Error} ->
+ []
+ end;
+ {error, Error} ->
+ ?LOG_INFO("Find module failed [module=~p] [error=~p]", [Module, Error]),
+ []
+ end.
-spec applications_to_uris([poi()]) -> [uri()].
applications_to_uris(Applications) ->
- Modules = [M || #{id := {M, _F, _A}} <- Applications],
- Fun = fun(M, Acc) ->
- case els_utils:find_module(M) of
- {ok, Uri} ->
- [Uri|Acc];
- {error, Error} ->
- ?LOG_INFO( "Could not find module [module=~p] [error=~p]"
- , [M, Error]
- ),
+ Modules = [M || #{id := {M, _F, _A}} <- Applications],
+ Fun = fun(M, Acc) ->
+ case els_utils:find_module(M) of
+ {ok, Uri} ->
+ [Uri | Acc];
+ {error, Error} ->
+ ?LOG_INFO(
+ "Could not find module [module=~p] [error=~p]",
+ [M, Error]
+ ),
Acc
- end
- end,
- lists:foldl(Fun, [], Modules).
+ end
+ end,
+ lists:foldl(Fun, [], Modules).
-spec included_uris([atom()], [uri()]) -> [uri()].
included_uris([], Acc) ->
- lists:usort(Acc);
-included_uris([Id|Ids], Acc) ->
- case els_utils:find_header(els_utils:filename_to_atom(Id)) of
- {ok, Uri} -> included_uris(Ids, [Uri | Acc]);
- {error, _Error} -> included_uris(Ids, Acc)
- end.
+ lists:usort(Acc);
+included_uris([Id | Ids], Acc) ->
+ case els_utils:find_header(els_utils:filename_to_atom(Id)) of
+ {ok, Uri} -> included_uris(Ids, [Uri | Acc]);
+ {error, _Error} -> included_uris(Ids, Acc)
+ end.
diff --git a/apps/els_lsp/src/els_dialyzer_diagnostics.erl b/apps/els_lsp/src/els_dialyzer_diagnostics.erl
index 47c3c9d90..2a82fe1ff 100644
--- a/apps/els_lsp/src/els_dialyzer_diagnostics.erl
+++ b/apps/els_lsp/src/els_dialyzer_diagnostics.erl
@@ -11,10 +11,11 @@
%%==============================================================================
%% Exports
%%==============================================================================
--export([ is_default/0
- , run/1
- , source/0
- ]).
+-export([
+ is_default/0,
+ run/1,
+ source/0
+]).
%%==============================================================================
%% Includes
@@ -34,63 +35,72 @@
-spec is_default() -> boolean().
is_default() ->
- true.
+ true.
-spec run(uri()) -> [els_diagnostics:diagnostic()].
run(Uri) ->
- Path = els_uri:path(Uri),
- case els_config:get(plt_path) of
- undefined -> [];
- DialyzerPltPath ->
- {ok, Document} = els_utils:lookup_document(Uri),
- Deps = [dep_path(X) || X <- els_diagnostics_utils:dependencies(Uri)],
- Files = [els_utils:to_list(Path) | Deps],
- WS = try dialyzer:run([ {files, Files}
- , {from, src_code}
- , {include_dirs, els_config:get(include_paths)}
- , {plts, [DialyzerPltPath]}
- , {defines, defines()}
- ])
- catch Type:Error ->
- ?LOG_ERROR( "Error while running dialyzer [type=~p] [error=~p]"
- , [Type, Error]
- ),
- []
- end,
- [diagnostic(Document, W) || W <- WS]
- end.
+ Path = els_uri:path(Uri),
+ case els_config:get(plt_path) of
+ undefined ->
+ [];
+ DialyzerPltPath ->
+ {ok, Document} = els_utils:lookup_document(Uri),
+ Deps = [dep_path(X) || X <- els_diagnostics_utils:dependencies(Uri)],
+ Files = [els_utils:to_list(Path) | Deps],
+ WS =
+ try
+ dialyzer:run([
+ {files, Files},
+ {from, src_code},
+ {include_dirs, els_config:get(include_paths)},
+ {plts, [DialyzerPltPath]},
+ {defines, defines()}
+ ])
+ catch
+ Type:Error ->
+ ?LOG_ERROR(
+ "Error while running dialyzer [type=~p] [error=~p]",
+ [Type, Error]
+ ),
+ []
+ end,
+ [diagnostic(Document, W) || W <- WS]
+ end.
-spec source() -> binary().
source() ->
- <<"Dialyzer">>.
+ <<"Dialyzer">>.
%%==============================================================================
%% Internal Functions
%%==============================================================================
--spec diagnostic(els_dt_document:item(),
- {any(), {any(), erl_anno:anno()}, any()}) ->
- els_diagnostics:diagnostic().
+-spec diagnostic(
+ els_dt_document:item(),
+ {any(), {any(), erl_anno:anno()}, any()}
+) ->
+ els_diagnostics:diagnostic().
diagnostic(Document, {_, {_, Anno}, _} = Warning) ->
- Range = els_diagnostics_utils:range(Document, Anno),
- Message = lists:flatten(dialyzer:format_warning(Warning)),
- #{ range => els_protocol:range(Range)
- , message => els_utils:to_binary(Message)
- , severity => ?DIAGNOSTIC_WARNING
- , source => source()
- }.
+ Range = els_diagnostics_utils:range(Document, Anno),
+ Message = lists:flatten(dialyzer:format_warning(Warning)),
+ #{
+ range => els_protocol:range(Range),
+ message => els_utils:to_binary(Message),
+ severity => ?DIAGNOSTIC_WARNING,
+ source => source()
+ }.
-spec dep_path(module()) -> string().
dep_path(Module) ->
- {ok, Uri} = els_utils:find_module(Module),
- els_utils:to_list(els_uri:path(Uri)).
+ {ok, Uri} = els_utils:find_module(Module),
+ els_utils:to_list(els_uri:path(Uri)).
-spec defines() -> [macro_option()].
defines() ->
- Macros = els_config:get(macros),
- [define(M) || M <- Macros].
+ Macros = els_config:get(macros),
+ [define(M) || M <- Macros].
-spec define(macro_config()) -> macro_option().
define(#{"name" := Name, "value" := Value}) ->
- {list_to_atom(Name), els_utils:macro_string_to_term(Value)};
+ {list_to_atom(Name), els_utils:macro_string_to_term(Value)};
define(#{"name" := Name}) ->
- {list_to_atom(Name), true}.
+ {list_to_atom(Name), true}.
diff --git a/apps/els_lsp/src/els_docs.erl b/apps/els_lsp/src/els_docs.erl
index 8aa40b994..e13230a2f 100644
--- a/apps/els_lsp/src/els_docs.erl
+++ b/apps/els_lsp/src/els_docs.erl
@@ -6,10 +6,11 @@
%%==============================================================================
%% Exports
%%==============================================================================
--export([ docs/2
- , function_docs/4
- , type_docs/4
- ]).
+-export([
+ docs/2,
+ function_docs/4,
+ type_docs/4
+]).
%%==============================================================================
%% Includes
@@ -44,50 +45,53 @@
%% API
%%==============================================================================
-spec docs(uri(), poi()) -> [els_markup_content:doc_entry()].
-docs(_Uri, #{kind := Kind, id := {M, F, A}})
- when Kind =:= application;
- Kind =:= implicit_fun ->
- function_docs('remote', M, F, A);
-docs(Uri, #{kind := Kind, id := {F, A}})
- when Kind =:= application;
- Kind =:= implicit_fun;
- Kind =:= export_entry ->
- M = els_uri:module(Uri),
- function_docs('local', M, F, A);
+docs(_Uri, #{kind := Kind, id := {M, F, A}}) when
+ Kind =:= application;
+ Kind =:= implicit_fun
+->
+ function_docs('remote', M, F, A);
+docs(Uri, #{kind := Kind, id := {F, A}}) when
+ Kind =:= application;
+ Kind =:= implicit_fun;
+ Kind =:= export_entry
+->
+ M = els_uri:module(Uri),
+ function_docs('local', M, F, A);
docs(Uri, #{kind := macro, id := Name} = POI) ->
- case els_code_navigation:goto_definition(Uri, POI) of
- {ok, DefUri, #{data := #{args := Args, value_range := ValueRange}}}
- when is_list(Args); is_atom(Name) ->
- NameStr = macro_signature(Name, Args),
-
- ValueText = get_valuetext(DefUri, ValueRange),
-
- Line = lists:flatten(["?", NameStr, " = ", ValueText]),
- [{code_line, Line}];
- _ ->
- []
- end;
+ case els_code_navigation:goto_definition(Uri, POI) of
+ {ok, DefUri, #{data := #{args := Args, value_range := ValueRange}}} when
+ is_list(Args); is_atom(Name)
+ ->
+ NameStr = macro_signature(Name, Args),
+
+ ValueText = get_valuetext(DefUri, ValueRange),
+
+ Line = lists:flatten(["?", NameStr, " = ", ValueText]),
+ [{code_line, Line}];
+ _ ->
+ []
+ end;
docs(Uri, #{kind := record_expr} = POI) ->
- case els_code_navigation:goto_definition(Uri, POI) of
- {ok, DefUri, #{data := #{value_range := ValueRange}}} ->
- ValueText = get_valuetext(DefUri, ValueRange),
-
- [{code_line, ValueText}];
- _ ->
- []
- end;
+ case els_code_navigation:goto_definition(Uri, POI) of
+ {ok, DefUri, #{data := #{value_range := ValueRange}}} ->
+ ValueText = get_valuetext(DefUri, ValueRange),
+
+ [{code_line, ValueText}];
+ _ ->
+ []
+ end;
docs(_M, #{kind := type_application, id := {M, F, A}}) ->
- type_docs('remote', M, F, A);
+ type_docs('remote', M, F, A);
docs(Uri, #{kind := type_application, id := {F, A}}) ->
- type_docs('local', els_uri:module(Uri), F, A);
+ type_docs('local', els_uri:module(Uri), F, A);
docs(_M, _POI) ->
- [].
+ [].
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec function_docs(application_type(), atom(), atom(), non_neg_integer()) ->
- [els_markup_content:doc_entry()].
+ [els_markup_content:doc_entry()].
function_docs(Type, M, F, A) ->
case eep48_docs(function, M, F, A) of
{ok, Docs} ->
@@ -96,10 +100,11 @@ function_docs(Type, M, F, A) ->
%% We cannot fetch the EEP-48 style docs, so instead we create
%% something similar using the tools we have.
Sig = {h2, signature(Type, M, F, A)},
- L = [ function_clauses(M, F, A)
- , specs(M, F, A)
- , edoc(M, F, A)
- ],
+ L = [
+ function_clauses(M, F, A),
+ specs(M, F, A),
+ edoc(M, F, A)
+ ],
case lists:append(L) of
[] ->
[Sig];
@@ -109,7 +114,7 @@ function_docs(Type, M, F, A) ->
end.
-spec type_docs(application_type(), atom(), atom(), non_neg_integer()) ->
- [els_markup_content:doc_entry()].
+ [els_markup_content:doc_entry()].
type_docs(_Type, M, F, A) ->
case eep48_docs(type, M, F, A) of
{ok, Docs} ->
@@ -120,16 +125,15 @@ type_docs(_Type, M, F, A) ->
-spec get_valuetext(uri(), map()) -> list().
get_valuetext(DefUri, #{from := From, to := To}) ->
- {ok, #{text := Text}} = els_utils:lookup_document(DefUri),
- els_utils:to_list(els_text:range(Text, From, To)).
-
+ {ok, #{text := Text}} = els_utils:lookup_document(DefUri),
+ els_utils:to_list(els_text:range(Text, From, To)).
-spec signature(application_type(), atom(), atom(), non_neg_integer()) ->
- string().
+ string().
signature('local', _M, F, A) ->
- io_lib:format("~p/~p", [F, A]);
+ io_lib:format("~p/~p", [F, A]);
signature('remote', M, F, A) ->
- io_lib:format("~p:~p/~p", [M, F, A]).
+ io_lib:format("~p:~p/~p", [M, F, A]).
%% @doc Fetch EEP-48 style Docs
%%
@@ -141,47 +145,52 @@ signature('remote', M, F, A) ->
%% using edoc.
-ifdef(NATIVE_FORMAT).
-spec eep48_docs(function | type, atom(), atom(), non_neg_integer()) ->
- {ok, string()} | {error, not_available}.
+ {ok, string()} | {error, not_available}.
eep48_docs(Type, M, F, A) ->
- Render = case Type of
- function ->
- render;
- type ->
- render_type
- end,
- GL = setup_group_leader_proxy(),
- try get_doc_chunk(M) of
- {ok, #docs_v1{ format = ?NATIVE_FORMAT
- , module_doc = MDoc
- } = DocChunk} when MDoc =/= hidden ->
-
- flush_group_leader_proxy(GL),
-
- case els_eep48_docs:Render(M, F, A, DocChunk) of
- {error, _R0} ->
- case els_eep48_docs:Render(M, F, DocChunk) of
- {error, _R1} ->
- {error, not_available};
- Docs ->
- {ok, els_utils:to_list(Docs)}
- end;
- Docs ->
- {ok, els_utils:to_list(Docs)}
- end;
- _R1 ->
- ?LOG_DEBUG(#{ error => _R1 }),
- {error, not_available}
- catch C:E:ST ->
- %% code:get_doc/1 fails for escriptized modules, so fall back
- %% reading docs from source. See #751 for details
- IO = flush_group_leader_proxy(GL),
- ?LOG_DEBUG(#{ slogan => "Error fetching docs, falling back to src.",
- module => M,
- error => {C, E},
- st => ST,
- io => IO }),
- {error, not_available}
- end.
+ Render =
+ case Type of
+ function ->
+ render;
+ type ->
+ render_type
+ end,
+ GL = setup_group_leader_proxy(),
+ try get_doc_chunk(M) of
+ {ok,
+ #docs_v1{
+ format = ?NATIVE_FORMAT,
+ module_doc = MDoc
+ } = DocChunk} when MDoc =/= hidden ->
+ flush_group_leader_proxy(GL),
+
+ case els_eep48_docs:Render(M, F, A, DocChunk) of
+ {error, _R0} ->
+ case els_eep48_docs:Render(M, F, DocChunk) of
+ {error, _R1} ->
+ {error, not_available};
+ Docs ->
+ {ok, els_utils:to_list(Docs)}
+ end;
+ Docs ->
+ {ok, els_utils:to_list(Docs)}
+ end;
+ _R1 ->
+ ?LOG_DEBUG(#{error => _R1}),
+ {error, not_available}
+ catch
+ C:E:ST ->
+ %% code:get_doc/1 fails for escriptized modules, so fall back
+ %% reading docs from source. See #751 for details
+ IO = flush_group_leader_proxy(GL),
+ ?LOG_DEBUG(#{
+ slogan => "Error fetching docs, falling back to src.",
+ module => M,
+ error => {C, E},
+ st => ST,
+ io => IO
+ }),
+ {error, not_available}
+ end.
%% This function first tries to read the doc chunk from the .beam file
%% and if that fails it attempts to find the .chunk file.
@@ -195,12 +204,18 @@ get_doc_chunk(M) ->
%% Erlang/OTP there will be two "lists" modules, and we want to
%% fetch the docs from any version that has docs built.
- case lists:foldl(
- fun(Uri, undefined) ->
- get_doc_chunk(M, Uri);
- (_Uri, Chunk) ->
- Chunk
- end, undefined, Uris) of
+ case
+ lists:foldl(
+ fun
+ (Uri, undefined) ->
+ get_doc_chunk(M, Uri);
+ (_Uri, Chunk) ->
+ Chunk
+ end,
+ undefined,
+ Uris
+ )
+ of
undefined ->
get_edoc_chunk(M, hd(Uris));
Chunk ->
@@ -209,11 +224,20 @@ get_doc_chunk(M) ->
-spec get_doc_chunk(module(), uri()) -> docs_v1() | undefined.
get_doc_chunk(M, Uri) ->
- SrcDir = filename:dirname(els_utils:to_list(els_uri:path(Uri))),
- BeamFile = filename:join([SrcDir, "..", "ebin",
- lists:concat([M, ".beam"])]),
- ChunkFile = filename:join([SrcDir, "..", "doc", "chunks",
- lists:concat([M, ".chunk"])]),
+ SrcDir = filename:dirname(els_utils:to_list(els_uri:path(Uri))),
+ BeamFile = filename:join([
+ SrcDir,
+ "..",
+ "ebin",
+ lists:concat([M, ".beam"])
+ ]),
+ ChunkFile = filename:join([
+ SrcDir,
+ "..",
+ "doc",
+ "chunks",
+ lists:concat([M, ".chunk"])
+ ]),
case beam_lib:chunks(BeamFile, ["Docs"]) of
{ok, {_Mod, [{"Docs", Bin}]}} ->
binary_to_term(Bin);
@@ -229,15 +253,19 @@ get_doc_chunk(M, Uri) ->
-spec get_edoc_chunk(M :: module(), Uri :: uri()) -> {ok, term()} | error.
get_edoc_chunk(M, Uri) ->
%% edoc in Erlang/OTP 24 and later can create doc chunks for edoc
- case {code:ensure_loaded(edoc_doclet_chunks),
- code:ensure_loaded(edoc_layout_chunks)} of
+ case {code:ensure_loaded(edoc_doclet_chunks), code:ensure_loaded(edoc_layout_chunks)} of
{{module, _}, {module, _}} ->
Path = els_uri:path(Uri),
Dir = erlang_ls:cache_root(),
- ok = edoc:run([els_utils:to_list(Path)],
- [{doclet, edoc_doclet_chunks},
- {layout, edoc_layout_chunks},
- {dir, Dir} | edoc_options()]),
+ ok = edoc:run(
+ [els_utils:to_list(Path)],
+ [
+ {doclet, edoc_doclet_chunks},
+ {layout, edoc_layout_chunks},
+ {dir, Dir}
+ | edoc_options()
+ ]
+ ),
Chunk = filename:join([Dir, "chunks", atom_to_list(M) ++ ".chunk"]),
{ok, Bin} = file:read_file(Chunk),
{ok, binary_to_term(Bin)};
@@ -249,42 +277,49 @@ get_edoc_chunk(M, Uri) ->
-dialyzer({no_match, function_docs/4}).
-dialyzer({no_match, type_docs/4}).
-spec eep48_docs(function | type, atom(), atom(), non_neg_integer()) ->
- {error, not_available}.
+ {error, not_available}.
eep48_docs(_Type, _M, _F, _A) ->
{error, not_available}.
-endif.
--spec edoc_options() -> [{'includes' | 'macros', [any()]} |
- {'preprocess', 'true'}].
+-spec edoc_options() ->
+ [
+ {'includes' | 'macros', [any()]}
+ | {'preprocess', 'true'}
+ ].
edoc_options() ->
- [{preprocess, true},
- {macros,
- [{N, V} || {'d', N, V} <- els_compiler_diagnostics:macro_options()]},
- {includes,
- [I || {i, I} <- els_compiler_diagnostics:include_options()]}].
+ [
+ {preprocess, true},
+ {macros, [{N, V} || {'d', N, V} <- els_compiler_diagnostics:macro_options()]},
+ {includes, [I || {i, I} <- els_compiler_diagnostics:include_options()]}
+ ].
-spec specs(atom(), atom(), non_neg_integer()) ->
- [els_markup_content:doc_entry()].
+ [els_markup_content:doc_entry()].
specs(M, F, A) ->
- case els_dt_signatures:lookup({M, F, A}) of
- {ok, [#{spec := Spec}]} ->
- [ {code_line, els_utils:to_list(Spec)} ];
- {ok, []} ->
- []
- end.
+ case els_dt_signatures:lookup({M, F, A}) of
+ {ok, [#{spec := Spec}]} ->
+ [{code_line, els_utils:to_list(Spec)}];
+ {ok, []} ->
+ []
+ end.
-spec type(module(), atom(), arity()) ->
- [els_markup_content:doc_entry()].
+ [els_markup_content:doc_entry()].
type(M, T, A) ->
case els_utils:find_module(M) of
{ok, Uri} ->
{ok, Document} = els_utils:lookup_document(Uri),
ExportedTypes = els_dt_document:pois(Document, [type_definition]),
- case lists:search(
- fun(#{ id := Id }) ->
- Id =:= {T, A}
- end, ExportedTypes) of
- {value, #{ range := Range }} ->
+ case
+ lists:search(
+ fun(#{id := Id}) ->
+ Id =:= {T, A}
+ end,
+ ExportedTypes
+ )
+ of
+ {value, #{range := Range}} ->
[{code_line, get_valuetext(Uri, Range)}];
false ->
[]
@@ -294,142 +329,157 @@ type(M, T, A) ->
end.
-spec function_clauses(atom(), atom(), non_neg_integer()) ->
- [els_markup_content:doc_entry()].
+ [els_markup_content:doc_entry()].
function_clauses(_Module, _Function, 0) ->
- [];
+ [];
function_clauses(Module, Function, Arity) ->
- case els_utils:find_module(Module) of
- {ok, Uri} ->
- {ok, Doc} = els_utils:lookup_document(Uri),
- ClausesPOIs = els_dt_document:pois(Doc, [function_clause]),
- Lines = [{code_block_line, atom_to_list(F) ++ els_utils:to_list(Data)}
- || #{id := {F, A, _}, data := Data} <- ClausesPOIs,
- F =:= Function, A =:= Arity],
- lists:append([ [{code_block_begin, "erlang"}]
- , truncate_lines(Lines)
- , [{code_block_end, "erlang"}]
- ]);
- {error, _Reason} ->
- []
- end.
+ case els_utils:find_module(Module) of
+ {ok, Uri} ->
+ {ok, Doc} = els_utils:lookup_document(Uri),
+ ClausesPOIs = els_dt_document:pois(Doc, [function_clause]),
+ Lines = [
+ {code_block_line, atom_to_list(F) ++ els_utils:to_list(Data)}
+ || #{id := {F, A, _}, data := Data} <- ClausesPOIs,
+ F =:= Function,
+ A =:= Arity
+ ],
+ lists:append([
+ [{code_block_begin, "erlang"}],
+ truncate_lines(Lines),
+ [{code_block_end, "erlang"}]
+ ]);
+ {error, _Reason} ->
+ []
+ end.
-spec truncate_lines([els_markup_content:doc_entry()]) ->
- [els_markup_content:doc_entry()].
+ [els_markup_content:doc_entry()].
truncate_lines(Lines) when length(Lines) =< ?MAX_CLAUSES ->
- Lines;
+ Lines;
truncate_lines(Lines0) ->
- Lines = lists:sublist(Lines0, ?MAX_CLAUSES),
- lists:append(Lines, [{code_block_line, "[...]"}]).
+ Lines = lists:sublist(Lines0, ?MAX_CLAUSES),
+ lists:append(Lines, [{code_block_line, "[...]"}]).
-spec edoc(atom(), atom(), non_neg_integer()) ->
- [els_markup_content:doc_entry()].
+ [els_markup_content:doc_entry()].
edoc(M, F, A) ->
- case els_utils:find_module(M) of
- {ok, Uri} ->
- GL = setup_group_leader_proxy(),
- try
- Path = els_uri:path(Uri),
- {M, EDoc} = edoc:get_doc(
- els_utils:to_list(Path)
- , [{private, true}
- , edoc_options()] ),
- Internal = xmerl:export_simple([EDoc], docsh_edoc_xmerl),
- %% TODO: Something is weird with the docsh specs.
- %% For now, let's avoid the Dialyzer warnings.
- Docs = erlang:apply(docsh_docs_v1, from_internal, [Internal]),
- Res = erlang:apply(docsh_docs_v1, lookup, [ Docs
- , {M, F, A}
- , [doc, spec]]),
- flush_group_leader_proxy(GL),
-
- case Res of
- {ok, [{{function, F, A}, _Anno,
- _Signature, Desc, _Metadata}|_]} ->
- format_edoc(Desc);
- {not_found, _} ->
+ case els_utils:find_module(M) of
+ {ok, Uri} ->
+ GL = setup_group_leader_proxy(),
+ try
+ Path = els_uri:path(Uri),
+ {M, EDoc} = edoc:get_doc(
+ els_utils:to_list(Path),
+ [
+ {private, true},
+ edoc_options()
+ ]
+ ),
+ Internal = xmerl:export_simple([EDoc], docsh_edoc_xmerl),
+ %% TODO: Something is weird with the docsh specs.
+ %% For now, let's avoid the Dialyzer warnings.
+ Docs = erlang:apply(docsh_docs_v1, from_internal, [Internal]),
+ Res = erlang:apply(docsh_docs_v1, lookup, [
+ Docs,
+ {M, F, A},
+ [doc, spec]
+ ]),
+ flush_group_leader_proxy(GL),
+
+ case Res of
+ {ok, [{{function, F, A}, _Anno, _Signature, Desc, _Metadata} | _]} ->
+ format_edoc(Desc);
+ {not_found, _} ->
+ []
+ end
+ catch
+ C:E:ST ->
+ IO = flush_group_leader_proxy(GL),
+ ?LOG_DEBUG(
+ "[hover] Error fetching edoc [error=~p]",
+ [{M, F, A, C, E, ST, IO}]
+ ),
+ case IO of
+ timeout ->
+ [];
+ noproc ->
+ [];
+ IO ->
+ [{text, IO}]
+ end
+ end;
+ _ ->
[]
- end
- catch C:E:ST ->
- IO = flush_group_leader_proxy(GL),
- ?LOG_DEBUG("[hover] Error fetching edoc [error=~p]",
- [{M, F, A, C, E, ST, IO}]),
- case IO of
- timeout ->
- [];
- noproc ->
- [];
- IO ->
- [{text, IO}]
- end
- end;
- _ ->
- []
- end.
+ end.
-spec format_edoc(none | map()) -> [els_markup_content:doc_entry()].
format_edoc(none) ->
- [];
+ [];
format_edoc(Desc) when is_map(Desc) ->
- Lang = <<"en">>,
- Doc = maps:get(Lang, Desc, <<>>),
- FormattedDoc = els_utils:to_list(docsh_edoc:format_edoc(Doc, #{})),
- [{text, FormattedDoc}].
+ Lang = <<"en">>,
+ Doc = maps:get(Lang, Desc, <<>>),
+ FormattedDoc = els_utils:to_list(docsh_edoc:format_edoc(Doc, #{})),
+ [{text, FormattedDoc}].
-spec macro_signature(poi_id(), [{integer(), string()}]) -> unicode:charlist().
macro_signature({Name, _Arity}, Args) ->
- [atom_to_list(Name), "(", lists:join(", ", [A || {_N, A} <- Args]), ")"];
+ [atom_to_list(Name), "(", lists:join(", ", [A || {_N, A} <- Args]), ")"];
macro_signature(Name, none) ->
- atom_to_list(Name).
+ atom_to_list(Name).
-spec setup_group_leader_proxy() -> pid().
setup_group_leader_proxy() ->
OrigGL = group_leader(),
group_leader(
- spawn_link(
- fun() ->
+ spawn_link(
+ fun() ->
spawn_group_proxy([])
- end),
- self()),
+ end
+ ),
+ self()
+ ),
OrigGL.
-spec flush_group_leader_proxy(pid()) -> [term()] | term().
flush_group_leader_proxy(OrigGL) ->
GL = group_leader(),
case GL of
- OrigGL ->
- % This is the effect of setting a monitor on nonexisting process.
- noproc;
- _ ->
- Ref = monitor(process, GL),
- group_leader(OrigGL, self()),
- GL ! {get, Ref, self()},
- receive
- {Ref, Msg} ->
- demonitor(Ref, [flush]),
- Msg;
- {'DOWN', process, Ref, Reason} ->
- Reason
- after 5000 ->
- demonitor(Ref, [flush]),
- timeout
- end
+ OrigGL ->
+ % This is the effect of setting a monitor on nonexisting process.
+ noproc;
+ _ ->
+ Ref = monitor(process, GL),
+ group_leader(OrigGL, self()),
+ GL ! {get, Ref, self()},
+ receive
+ {Ref, Msg} ->
+ demonitor(Ref, [flush]),
+ Msg;
+ {'DOWN', process, Ref, Reason} ->
+ Reason
+ after 5000 ->
+ demonitor(Ref, [flush]),
+ timeout
+ end
end.
-spec spawn_group_proxy([any()]) -> ok.
spawn_group_proxy(Acc) ->
receive
{get, Ref, Pid} ->
- Pid ! {Ref, lists:reverse(Acc)}, ok;
+ Pid ! {Ref, lists:reverse(Acc)},
+ ok;
{io_request, From, ReplyAs, {put_chars, unicode, Chars}} ->
From ! {io_reply, ReplyAs, ok},
- spawn_group_proxy([catch unicode:characters_to_binary(Chars)|Acc]);
+ spawn_group_proxy([catch unicode:characters_to_binary(Chars) | Acc]);
{io_request, From, ReplyAs, {put_chars, unicode, M, F, As}} ->
From ! {io_reply, ReplyAs, ok},
spawn_group_proxy(
- [catch unicode:characters_to_binary(apply(M, F, As))|Acc]);
+ [catch unicode:characters_to_binary(apply(M, F, As)) | Acc]
+ );
{io_request, From, ReplyAs, _Request} = M ->
From ! {io_reply, ReplyAs, ok},
- spawn_group_proxy([M|Acc]);
+ spawn_group_proxy([M | Acc]);
M ->
- spawn_group_proxy([M|Acc])
+ spawn_group_proxy([M | Acc])
end.
diff --git a/apps/els_lsp/src/els_document_highlight_provider.erl b/apps/els_lsp/src/els_document_highlight_provider.erl
index 09d5cc8e0..09dd58861 100644
--- a/apps/els_lsp/src/els_document_highlight_provider.erl
+++ b/apps/els_lsp/src/els_document_highlight_provider.erl
@@ -2,9 +2,10 @@
-behaviour(els_provider).
--export([ is_enabled/0
- , handle_request/2
- ]).
+-export([
+ is_enabled/0,
+ handle_request/2
+]).
%%==============================================================================
%% Includes
@@ -24,95 +25,110 @@ is_enabled() -> true.
-spec handle_request(any(), state()) -> {response, any()}.
handle_request({document_highlight, Params}, _State) ->
- #{ <<"position">> := #{ <<"line">> := Line
- , <<"character">> := Character
- }
- , <<"textDocument">> := #{<<"uri">> := Uri}
- } = Params,
- {ok, Document} = els_utils:lookup_document(Uri),
- case
- els_dt_document:get_element_at_pos(Document, Line + 1, Character + 1)
- of
- [POI | _] -> {response, find_highlights(Document, POI)};
- [] -> {response, null}
- end.
+ #{
+ <<"position">> := #{
+ <<"line">> := Line,
+ <<"character">> := Character
+ },
+ <<"textDocument">> := #{<<"uri">> := Uri}
+ } = Params,
+ {ok, Document} = els_utils:lookup_document(Uri),
+ case els_dt_document:get_element_at_pos(Document, Line + 1, Character + 1) of
+ [POI | _] -> {response, find_highlights(Document, POI)};
+ [] -> {response, null}
+ end.
%%==============================================================================
%% Internal functions
%%==============================================================================
-spec find_highlights(els_dt_document:item(), poi()) -> any().
-find_highlights(Document, #{ id := Id, kind := atom }) ->
- AtomHighlights = do_find_highlights(Document, Id, [ atom ]),
- FieldPOIs = els_dt_document:pois(Document, [ record_def_field
- , record_field]),
- FieldHighlights = [document_highlight(R) ||
- #{id := I, range := R} <- FieldPOIs,
- element(2, I) =:= Id
- ],
- normalize_result(AtomHighlights ++ FieldHighlights);
-find_highlights(Document, #{ id := Id, kind := Kind }) ->
- POIs = els_dt_document:pois(Document, find_similar_kinds(Kind)),
- Highlights = [document_highlight(R) ||
- #{id := I, kind := K, range := R} <- POIs,
- I =:= Id,
- K =/= 'folding_range'
- ],
- normalize_result(Highlights).
+find_highlights(Document, #{id := Id, kind := atom}) ->
+ AtomHighlights = do_find_highlights(Document, Id, [atom]),
+ FieldPOIs = els_dt_document:pois(Document, [
+ record_def_field,
+ record_field
+ ]),
+ FieldHighlights = [
+ document_highlight(R)
+ || #{id := I, range := R} <- FieldPOIs,
+ element(2, I) =:= Id
+ ],
+ normalize_result(AtomHighlights ++ FieldHighlights);
+find_highlights(Document, #{id := Id, kind := Kind}) ->
+ POIs = els_dt_document:pois(Document, find_similar_kinds(Kind)),
+ Highlights = [
+ document_highlight(R)
+ || #{id := I, kind := K, range := R} <- POIs,
+ I =:= Id,
+ K =/= 'folding_range'
+ ],
+ normalize_result(Highlights).
--spec do_find_highlights(els_dt_document:item() , poi_id() , [poi_kind()])
- -> any().
+-spec do_find_highlights(els_dt_document:item(), poi_id(), [poi_kind()]) ->
+ any().
do_find_highlights(Document, Id, Kinds) ->
- POIs = els_dt_document:pois(Document, Kinds),
- _Highlights = [document_highlight(R) ||
- #{id := I, range := R} <- POIs,
- I =:= Id
- ].
+ POIs = els_dt_document:pois(Document, Kinds),
+ _Highlights = [
+ document_highlight(R)
+ || #{id := I, range := R} <- POIs,
+ I =:= Id
+ ].
-spec document_highlight(poi_range()) -> map().
document_highlight(Range) ->
- #{ range => els_protocol:range(Range)
- , kind => ?DOCUMENT_HIGHLIGHT_KIND_TEXT
- }.
+ #{
+ range => els_protocol:range(Range),
+ kind => ?DOCUMENT_HIGHLIGHT_KIND_TEXT
+ }.
-spec normalize_result([map()]) -> [map()] | null.
normalize_result([]) ->
- null;
+ null;
normalize_result(L) when is_list(L) ->
- L.
+ L.
-spec find_similar_kinds(poi_kind()) -> [poi_kind()].
find_similar_kinds(Kind) ->
- find_similar_kinds(Kind, kind_groups()).
+ find_similar_kinds(Kind, kind_groups()).
-spec find_similar_kinds(poi_kind(), [[poi_kind()]]) -> [poi_kind()].
find_similar_kinds(Kind, []) ->
- [Kind];
+ [Kind];
find_similar_kinds(Kind, [Group | Groups]) ->
- case lists:member(Kind, Group) of
- true ->
- Group;
- false ->
- find_similar_kinds(Kind, Groups)
- end.
+ case lists:member(Kind, Group) of
+ true ->
+ Group;
+ false ->
+ find_similar_kinds(Kind, Groups)
+ end.
%% Each group represents a list of POI kinds which represent the same or similar
%% objects (usually the definition and the usages of an object). Each POI kind
%% in one group must have the same id format.
-spec kind_groups() -> [[poi_kind()]].
kind_groups() ->
- [ %% function
- [ application
- , implicit_fun
- , function
- , export_entry]
- %% record
- , [ record
- , record_expr]
- %% record_field
- , [ record_def_field
- , record_field]
- %% macro
- , [ define
- , macro]
- ].
+ %% function
+ [
+ [
+ application,
+ implicit_fun,
+ function,
+ export_entry
+ ],
+ %% record
+ [
+ record,
+ record_expr
+ ],
+ %% record_field
+ [
+ record_def_field,
+ record_field
+ ],
+ %% macro
+ [
+ define,
+ macro
+ ]
+ ].
diff --git a/apps/els_lsp/src/els_document_symbol_provider.erl b/apps/els_lsp/src/els_document_symbol_provider.erl
index 44f88e75b..4fb37036d 100644
--- a/apps/els_lsp/src/els_document_symbol_provider.erl
+++ b/apps/els_lsp/src/els_document_symbol_provider.erl
@@ -2,9 +2,10 @@
-behaviour(els_provider).
--export([ is_enabled/0
- , handle_request/2
- ]).
+-export([
+ is_enabled/0,
+ handle_request/2
+]).
-include("els_lsp.hrl").
@@ -18,35 +19,38 @@ is_enabled() -> true.
-spec handle_request(any(), state()) -> {response, any()}.
handle_request({document_symbol, Params}, _State) ->
- #{ <<"textDocument">> := #{ <<"uri">> := Uri}} = Params,
- Symbols = symbols(Uri),
- case Symbols of
- [] -> {response, null};
- _ -> {response, Symbols}
- end.
+ #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
+ Symbols = symbols(Uri),
+ case Symbols of
+ [] -> {response, null};
+ _ -> {response, Symbols}
+ end.
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec symbols(uri()) -> [map()].
symbols(Uri) ->
- {ok, Document} = els_utils:lookup_document(Uri),
- POIs = els_dt_document:pois(Document, [ function
- , define
- , record
- , type_definition
- ]),
- lists:reverse([poi_to_symbol(Uri, POI) || POI <- POIs ]).
+ {ok, Document} = els_utils:lookup_document(Uri),
+ POIs = els_dt_document:pois(Document, [
+ function,
+ define,
+ record,
+ type_definition
+ ]),
+ lists:reverse([poi_to_symbol(Uri, POI) || POI <- POIs]).
-spec poi_to_symbol(uri(), poi()) -> symbol_information().
poi_to_symbol(Uri, POI) ->
- #{range := Range, kind := Kind, id := Id} = POI,
- #{ name => symbol_name(Kind, Id)
- , kind => symbol_kind(Kind)
- , location => #{ uri => Uri
- , range => els_protocol:range(Range)
- }
- }.
+ #{range := Range, kind := Kind, id := Id} = POI,
+ #{
+ name => symbol_name(Kind, Id),
+ kind => symbol_kind(Kind),
+ location => #{
+ uri => Uri,
+ range => els_protocol:range(Range)
+ }
+ }.
-spec symbol_kind(poi_kind()) -> symbol_kind().
symbol_kind(function) -> ?SYMBOLKIND_FUNCTION;
@@ -56,12 +60,12 @@ symbol_kind(type_definition) -> ?SYMBOLKIND_TYPE_PARAMETER.
-spec symbol_name(poi_kind(), any()) -> binary().
symbol_name(function, {F, A}) ->
- els_utils:to_binary(io_lib:format("~s/~p", [F, A]));
+ els_utils:to_binary(io_lib:format("~s/~p", [F, A]));
symbol_name(define, {Name, Arity}) ->
- els_utils:to_binary(io_lib:format("~s/~p", [Name, Arity]));
+ els_utils:to_binary(io_lib:format("~s/~p", [Name, Arity]));
symbol_name(define, Name) when is_atom(Name) ->
- atom_to_binary(Name, utf8);
+ atom_to_binary(Name, utf8);
symbol_name(record, Name) when is_atom(Name) ->
- atom_to_binary(Name, utf8);
+ atom_to_binary(Name, utf8);
symbol_name(type_definition, {Name, Arity}) ->
- els_utils:to_binary(io_lib:format("~s/~p", [Name, Arity])).
+ els_utils:to_binary(io_lib:format("~s/~p", [Name, Arity])).
diff --git a/apps/els_lsp/src/els_dt_document.erl b/apps/els_lsp/src/els_dt_document.erl
index 6667caef0..42d7844db 100644
--- a/apps/els_lsp/src/els_dt_document.erl
+++ b/apps/els_lsp/src/els_dt_document.erl
@@ -9,32 +9,35 @@
%%==============================================================================
-behaviour(els_db_table).
--export([ name/0
- , opts/0
- ]).
+-export([
+ name/0,
+ opts/0
+]).
%%==============================================================================
%% API
%%==============================================================================
--export([ insert/1
- , versioned_insert/1
- , lookup/1
- , delete/1
- ]).
-
--export([ new/3
- , pois/1
- , pois/2
- , get_element_at_pos/3
- , uri/1
- , functions_at_pos/3
- , applications_at_pos/3
- , wrapping_functions/2
- , wrapping_functions/3
- , find_candidates/1
- , get_words/1
- ]).
+-export([
+ insert/1,
+ versioned_insert/1,
+ lookup/1,
+ delete/1
+]).
+
+-export([
+ new/3,
+ pois/1,
+ pois/2,
+ get_element_at_pos/3,
+ uri/1,
+ functions_at_pos/3,
+ applications_at_pos/3,
+ wrapping_functions/2,
+ wrapping_functions/3,
+ find_candidates/1,
+ get_words/1
+]).
%%==============================================================================
%% Includes
@@ -45,7 +48,7 @@
%%==============================================================================
%% Type Definitions
%%==============================================================================
--type id() :: atom().
+-type id() :: atom().
-type kind() :: module | header | other.
-type source() :: otp | app | dep.
-type version() :: null | integer().
@@ -54,30 +57,33 @@
%%==============================================================================
%% Item Definition
%%==============================================================================
--record(els_dt_document, { uri :: uri() | '_' | '$1'
- , id :: id() | '_'
- , kind :: kind() | '_'
- , text :: binary() | '_'
- , pois :: [poi()] | '_' | ondemand
- , source :: source() | '$2'
- , words :: sets:set() | '_' | '$3'
- , version :: version() | '_'
- }).
+-record(els_dt_document, {
+ uri :: uri() | '_' | '$1',
+ id :: id() | '_',
+ kind :: kind() | '_',
+ text :: binary() | '_',
+ pois :: [poi()] | '_' | ondemand,
+ source :: source() | '$2',
+ words :: sets:set() | '_' | '$3',
+ version :: version() | '_'
+}).
-type els_dt_document() :: #els_dt_document{}.
--type item() :: #{ uri := uri()
- , id := id()
- , kind := kind()
- , text := binary()
- , pois => [poi()] | ondemand
- , source => source()
- , words => sets:set()
- , version => version()
- }.
--export_type([ id/0
- , item/0
- , kind/0
- ]).
+-type item() :: #{
+ uri := uri(),
+ id := id(),
+ kind := kind(),
+ text := binary(),
+ pois => [poi()] | ondemand,
+ source => source(),
+ words => sets:set(),
+ version => version()
+}.
+-export_type([
+ id/0,
+ item/0,
+ kind/0
+]).
%%==============================================================================
%% Callbacks for the els_db_table Behaviour
@@ -88,192 +94,206 @@ name() -> ?MODULE.
-spec opts() -> proplists:proplist().
opts() ->
- [set, compressed].
+ [set, compressed].
%%==============================================================================
%% API
%%==============================================================================
-spec from_item(item()) -> els_dt_document().
-from_item(#{ uri := Uri
- , id := Id
- , kind := Kind
- , text := Text
- , pois := POIs
- , source := Source
- , words := Words
- , version := Version
- }) ->
- #els_dt_document{ uri = Uri
- , id = Id
- , kind = Kind
- , text = Text
- , pois = POIs
- , source = Source
- , words = Words
- , version = Version
- }.
+from_item(#{
+ uri := Uri,
+ id := Id,
+ kind := Kind,
+ text := Text,
+ pois := POIs,
+ source := Source,
+ words := Words,
+ version := Version
+}) ->
+ #els_dt_document{
+ uri = Uri,
+ id = Id,
+ kind = Kind,
+ text = Text,
+ pois = POIs,
+ source = Source,
+ words = Words,
+ version = Version
+ }.
-spec to_item(els_dt_document()) -> item().
-to_item(#els_dt_document{ uri = Uri
- , id = Id
- , kind = Kind
- , text = Text
- , pois = POIs
- , source = Source
- , words = Words
- , version = Version
- }) ->
- #{ uri => Uri
- , id => Id
- , kind => Kind
- , text => Text
- , pois => POIs
- , source => Source
- , words => Words
- , version => Version
- }.
+to_item(#els_dt_document{
+ uri = Uri,
+ id = Id,
+ kind = Kind,
+ text = Text,
+ pois = POIs,
+ source = Source,
+ words = Words,
+ version = Version
+}) ->
+ #{
+ uri => Uri,
+ id => Id,
+ kind => Kind,
+ text => Text,
+ pois => POIs,
+ source => Source,
+ words => Words,
+ version => Version
+ }.
-spec insert(item()) -> ok | {error, any()}.
insert(Map) when is_map(Map) ->
- Record = from_item(Map),
- els_db:write(name(), Record).
+ Record = from_item(Map),
+ els_db:write(name(), Record).
-spec versioned_insert(item()) -> ok | {error, any()}.
versioned_insert(#{uri := Uri, version := Version} = Map) ->
- Record = from_item(Map),
- Condition = fun(#els_dt_document{version = CurrentVersion}) ->
- CurrentVersion =:= null orelse Version >= CurrentVersion
- end,
- els_db:conditional_write(name(), Uri, Record, Condition).
+ Record = from_item(Map),
+ Condition = fun(#els_dt_document{version = CurrentVersion}) ->
+ CurrentVersion =:= null orelse Version >= CurrentVersion
+ end,
+ els_db:conditional_write(name(), Uri, Record, Condition).
-spec lookup(uri()) -> {ok, [item()]}.
lookup(Uri) ->
- {ok, Items} = els_db:lookup(name(), Uri),
- {ok, [to_item(Item) || Item <- Items]}.
+ {ok, Items} = els_db:lookup(name(), Uri),
+ {ok, [to_item(Item) || Item <- Items]}.
-spec delete(uri()) -> ok.
delete(Uri) ->
- els_db:delete(name(), Uri).
+ els_db:delete(name(), Uri).
-spec new(uri(), binary(), source()) -> item().
new(Uri, Text, Source) ->
- Extension = filename:extension(Uri),
- Id = binary_to_atom(filename:basename(Uri, Extension), utf8),
- Version = null,
- case Extension of
- <<".erl">> ->
- new(Uri, Text, Id, module, Source, Version);
- <<".hrl">> ->
- new(Uri, Text, Id, header, Source, Version);
- _ ->
- new(Uri, Text, Id, other, Source, Version)
- end.
+ Extension = filename:extension(Uri),
+ Id = binary_to_atom(filename:basename(Uri, Extension), utf8),
+ Version = null,
+ case Extension of
+ <<".erl">> ->
+ new(Uri, Text, Id, module, Source, Version);
+ <<".hrl">> ->
+ new(Uri, Text, Id, header, Source, Version);
+ _ ->
+ new(Uri, Text, Id, other, Source, Version)
+ end.
-spec new(uri(), binary(), atom(), kind(), source(), version()) -> item().
new(Uri, Text, Id, Kind, Source, Version) ->
- #{ uri => Uri
- , id => Id
- , kind => Kind
- , text => Text
- , pois => ondemand
- , source => Source
- , words => get_words(Text)
- , version => Version
- }.
+ #{
+ uri => Uri,
+ id => Id,
+ kind => Kind,
+ text => Text,
+ pois => ondemand,
+ source => Source,
+ words => get_words(Text),
+ version => Version
+ }.
%% @doc Returns the list of POIs for the current document
-spec pois(item()) -> [poi()].
-pois(#{ uri := Uri, pois := ondemand }) ->
- #{pois := POIs} = els_indexing:ensure_deeply_indexed(Uri),
- POIs;
-pois(#{ pois := POIs }) ->
- POIs.
+pois(#{uri := Uri, pois := ondemand}) ->
+ #{pois := POIs} = els_indexing:ensure_deeply_indexed(Uri),
+ POIs;
+pois(#{pois := POIs}) ->
+ POIs.
%% @doc Returns the list of POIs of the given types for the current
%% document
-spec pois(item(), [poi_kind()]) -> [poi()].
pois(Item, Kinds) ->
- [POI || #{kind := K} = POI <- pois(Item), lists:member(K, Kinds)].
+ [POI || #{kind := K} = POI <- pois(Item), lists:member(K, Kinds)].
-spec get_element_at_pos(item(), non_neg_integer(), non_neg_integer()) ->
- [poi()].
+ [poi()].
get_element_at_pos(Item, Line, Column) ->
- POIs = pois(Item),
- MatchedPOIs = els_poi:match_pos(POIs, {Line, Column}),
- els_poi:sort(MatchedPOIs).
+ POIs = pois(Item),
+ MatchedPOIs = els_poi:match_pos(POIs, {Line, Column}),
+ els_poi:sort(MatchedPOIs).
%% @doc Returns the URI of the current document
-spec uri(item()) -> uri().
-uri(#{ uri := Uri }) ->
- Uri.
+uri(#{uri := Uri}) ->
+ Uri.
-spec functions_at_pos(item(), non_neg_integer(), non_neg_integer()) -> [poi()].
functions_at_pos(Item, Line, Column) ->
- POIs = get_element_at_pos(Item, Line, Column),
- [POI || #{kind := 'function'} = POI <- POIs].
+ POIs = get_element_at_pos(Item, Line, Column),
+ [POI || #{kind := 'function'} = POI <- POIs].
-spec applications_at_pos(item(), non_neg_integer(), non_neg_integer()) ->
- [poi()].
+ [poi()].
applications_at_pos(Item, Line, Column) ->
- POIs = get_element_at_pos(Item, Line, Column),
- [POI || #{kind := 'application'} = POI <- POIs].
+ POIs = get_element_at_pos(Item, Line, Column),
+ [POI || #{kind := 'application'} = POI <- POIs].
-spec wrapping_functions(item(), non_neg_integer(), non_neg_integer()) ->
- [poi()].
+ [poi()].
wrapping_functions(Document, Line, Column) ->
- Range = #{from => {Line, Column}, to => {Line, Column}},
- Functions = pois(Document, ['function']),
- [F || #{data := #{ wrapping_range := WR}} = F <- Functions,
- els_range:in(Range, WR)].
+ Range = #{from => {Line, Column}, to => {Line, Column}},
+ Functions = pois(Document, ['function']),
+ [
+ F
+ || #{data := #{wrapping_range := WR}} = F <- Functions,
+ els_range:in(Range, WR)
+ ].
-spec wrapping_functions(item(), range()) -> [poi()].
wrapping_functions(Document, Range) ->
- #{start := #{character := Character, line := Line}} = Range,
- wrapping_functions(Document, Line, Character).
+ #{start := #{character := Character, line := Line}} = Range,
+ wrapping_functions(Document, Line, Character).
-spec find_candidates(atom() | string()) -> [uri()].
find_candidates(Pattern) ->
- %% ets:fun2ms(fun(#els_dt_document{source = Source, uri = Uri, words = Words})
- %% when Source =/= otp -> {Uri, Words} end).
- MS = [{#els_dt_document{ uri = '$1'
- , id = '_'
- , kind = '_'
- , text = '_'
- , pois = '_'
- , source = '$2'
- , words = '$3'
- , version = '_'
- },
- [{'=/=', '$2', otp}],
- [{{'$1', '$3'}}]}],
- All = ets:select(name(), MS),
- Fun = fun({Uri, Words}) ->
- case sets:is_element(Pattern, Words) of
- true -> {true, Uri};
- false -> false
- end
- end,
- lists:filtermap(Fun, All).
+ %% ets:fun2ms(fun(#els_dt_document{source = Source, uri = Uri, words = Words})
+ %% when Source =/= otp -> {Uri, Words} end).
+ MS = [
+ {
+ #els_dt_document{
+ uri = '$1',
+ id = '_',
+ kind = '_',
+ text = '_',
+ pois = '_',
+ source = '$2',
+ words = '$3',
+ version = '_'
+ },
+ [{'=/=', '$2', otp}],
+ [{{'$1', '$3'}}]
+ }
+ ],
+ All = ets:select(name(), MS),
+ Fun = fun({Uri, Words}) ->
+ case sets:is_element(Pattern, Words) of
+ true -> {true, Uri};
+ false -> false
+ end
+ end,
+ lists:filtermap(Fun, All).
-spec get_words(binary()) -> sets:set().
get_words(Text) ->
- case erl_scan:string(els_utils:to_list(Text)) of
- {ok, Tokens, _EndLocation} ->
- Fun = fun({atom, _Location, Atom}, Words) ->
- sets:add_element(Atom, Words);
- ({string, _Location, String}, Words) ->
- case filename:extension(String) of
- ".hrl" ->
- Id = filename:rootname(filename:basename(String)),
- sets:add_element(Id, Words);
- _ ->
+ case erl_scan:string(els_utils:to_list(Text)) of
+ {ok, Tokens, _EndLocation} ->
+ Fun = fun
+ ({atom, _Location, Atom}, Words) ->
+ sets:add_element(Atom, Words);
+ ({string, _Location, String}, Words) ->
+ case filename:extension(String) of
+ ".hrl" ->
+ Id = filename:rootname(filename:basename(String)),
+ sets:add_element(Id, Words);
+ _ ->
+ Words
+ end;
+ (_, Words) ->
Words
- end;
- (_, Words) ->
- Words
end,
- lists:foldl(Fun, sets:new(), Tokens);
- {error, ErrorInfo, _ErrorLocation} ->
- ?LOG_DEBUG("Errors while get_words ~p", [ErrorInfo])
- end.
+ lists:foldl(Fun, sets:new(), Tokens);
+ {error, ErrorInfo, _ErrorLocation} ->
+ ?LOG_DEBUG("Errors while get_words ~p", [ErrorInfo])
+ end.
diff --git a/apps/els_lsp/src/els_dt_document_index.erl b/apps/els_lsp/src/els_dt_document_index.erl
index 5cb1a9b95..790b4f359 100644
--- a/apps/els_lsp/src/els_dt_document_index.erl
+++ b/apps/els_lsp/src/els_dt_document_index.erl
@@ -9,21 +9,23 @@
%%==============================================================================
-behaviour(els_db_table).
--export([ name/0
- , opts/0
- ]).
+-export([
+ name/0,
+ opts/0
+]).
%%==============================================================================
%% API
%%==============================================================================
--export([ new/3 ]).
+-export([new/3]).
--export([ find_by_kind/1
- , insert/1
- , lookup/1
- , delete_by_uri/1
- ]).
+-export([
+ find_by_kind/1,
+ insert/1,
+ lookup/1,
+ delete_by_uri/1
+]).
%%==============================================================================
%% Includes
@@ -34,17 +36,19 @@
%% Item Definition
%%==============================================================================
--record(els_dt_document_index, { id :: els_dt_document:id() | '_'
- , uri :: uri() | '_'
- , kind :: els_dt_document:kind() | '_'
- }).
+-record(els_dt_document_index, {
+ id :: els_dt_document:id() | '_',
+ uri :: uri() | '_',
+ kind :: els_dt_document:kind() | '_'
+}).
-type els_dt_document_index() :: #els_dt_document_index{}.
--type item() :: #{ id := els_dt_document:id()
- , uri := uri()
- , kind := els_dt_document:kind()
- }.
--export_type([ item/0 ]).
+-type item() :: #{
+ id := els_dt_document:id(),
+ uri := uri(),
+ kind := els_dt_document:kind()
+}.
+-export_type([item/0]).
%%==============================================================================
%% Callbacks for the els_db_table Behaviour
@@ -55,7 +59,7 @@ name() -> ?MODULE.
-spec opts() -> proplists:proplist().
opts() ->
- [bag].
+ [bag].
%%==============================================================================
%% API
@@ -63,36 +67,37 @@ opts() ->
-spec from_item(item()) -> els_dt_document_index().
from_item(#{id := Id, uri := Uri, kind := Kind}) ->
- #els_dt_document_index{id = Id, uri = Uri, kind = Kind}.
+ #els_dt_document_index{id = Id, uri = Uri, kind = Kind}.
-spec to_item(els_dt_document_index()) -> item().
to_item(#els_dt_document_index{id = Id, uri = Uri, kind = Kind}) ->
- #{id => Id, uri => Uri, kind => Kind}.
+ #{id => Id, uri => Uri, kind => Kind}.
-spec insert(item()) -> ok | {error, any()}.
insert(Map) when is_map(Map) ->
- Record = from_item(Map),
- els_db:write(name(), Record).
+ Record = from_item(Map),
+ els_db:write(name(), Record).
-spec lookup(atom()) -> {ok, [item()]}.
lookup(Id) ->
- {ok, Items} = els_db:lookup(name(), Id),
- {ok, [to_item(Item) || Item <- Items]}.
+ {ok, Items} = els_db:lookup(name(), Id),
+ {ok, [to_item(Item) || Item <- Items]}.
-spec delete_by_uri(uri()) -> ok | {error, any()}.
delete_by_uri(Uri) ->
- Pattern = #els_dt_document_index{uri = Uri, _ = '_'},
- ok = els_db:match_delete(name(), Pattern).
+ Pattern = #els_dt_document_index{uri = Uri, _ = '_'},
+ ok = els_db:match_delete(name(), Pattern).
-spec find_by_kind(els_dt_document:kind()) -> {ok, [item()]}.
find_by_kind(Kind) ->
- Pattern = #els_dt_document_index{kind = Kind, _ = '_'},
- {ok, Items} = els_db:match(name(), Pattern),
- {ok, [to_item(Item) || Item <- Items]}.
+ Pattern = #els_dt_document_index{kind = Kind, _ = '_'},
+ {ok, Items} = els_db:match(name(), Pattern),
+ {ok, [to_item(Item) || Item <- Items]}.
-spec new(atom(), uri(), els_dt_document:kind()) -> item().
new(Id, Uri, Kind) ->
- #{ id => Id
- , uri => Uri
- , kind => Kind
- }.
+ #{
+ id => Id,
+ uri => Uri,
+ kind => Kind
+ }.
diff --git a/apps/els_lsp/src/els_dt_references.erl b/apps/els_lsp/src/els_dt_references.erl
index 3635cdc7d..1f6f35d59 100644
--- a/apps/els_lsp/src/els_dt_references.erl
+++ b/apps/els_lsp/src/els_dt_references.erl
@@ -9,21 +9,23 @@
%%==============================================================================
-behaviour(els_db_table).
--export([ name/0
- , opts/0
- ]).
+-export([
+ name/0,
+ opts/0
+]).
%%==============================================================================
%% API
%%==============================================================================
--export([ delete_by_uri/1
- , versioned_delete_by_uri/2
- , find_by/1
- , find_by_id/2
- , insert/2
- , versioned_insert/2
- ]).
+-export([
+ delete_by_uri/1,
+ versioned_delete_by_uri/2,
+ find_by/1,
+ find_by_id/2,
+ insert/2,
+ versioned_insert/2
+]).
%%==============================================================================
%% Includes
@@ -36,28 +38,31 @@
%% Item Definition
%%==============================================================================
--record(els_dt_references, { id :: any() | '_'
- , uri :: uri() | '_'
- , range :: poi_range() | '_'
- , version :: version() | '_'
- }).
+-record(els_dt_references, {
+ id :: any() | '_',
+ uri :: uri() | '_',
+ range :: poi_range() | '_',
+ version :: version() | '_'
+}).
-type els_dt_references() :: #els_dt_references{}.
-type version() :: null | integer().
--type item() :: #{ id := any()
- , uri := uri()
- , range := poi_range()
- , version := version()
- }.
--export_type([ item/0 ]).
-
--type poi_category() :: function
- | type
- | macro
- | record
- | include
- | include_lib
- | behaviour.
--export_type([ poi_category/0 ]).
+-type item() :: #{
+ id := any(),
+ uri := uri(),
+ range := poi_range(),
+ version := version()
+}.
+-export_type([item/0]).
+
+-type poi_category() ::
+ function
+ | type
+ | macro
+ | record
+ | include
+ | include_lib
+ | behaviour.
+-export_type([poi_category/0]).
%%==============================================================================
%% Callbacks for the els_db_table Behaviour
@@ -68,104 +73,116 @@ name() -> ?MODULE.
-spec opts() -> proplists:proplist().
opts() ->
- [bag].
+ [bag].
%%==============================================================================
%% API
%%==============================================================================
-spec from_item(poi_kind(), item()) -> els_dt_references().
-from_item(Kind, #{ id := Id
- , uri := Uri
- , range := Range
- , version := Version
- }) ->
- InternalId = {kind_to_category(Kind), Id},
- #els_dt_references{ id = InternalId
- , uri = Uri
- , range = Range
- , version = Version
- }.
+from_item(Kind, #{
+ id := Id,
+ uri := Uri,
+ range := Range,
+ version := Version
+}) ->
+ InternalId = {kind_to_category(Kind), Id},
+ #els_dt_references{
+ id = InternalId,
+ uri = Uri,
+ range = Range,
+ version = Version
+ }.
-spec to_item(els_dt_references()) -> item().
-to_item(#els_dt_references{ id = {_Category, Id}
- , uri = Uri
- , range = Range
- , version = Version
- }) ->
- #{ id => Id
- , uri => Uri
- , range => Range
- , version => Version
- }.
+to_item(#els_dt_references{
+ id = {_Category, Id},
+ uri = Uri,
+ range = Range,
+ version = Version
+}) ->
+ #{
+ id => Id,
+ uri => Uri,
+ range => Range,
+ version => Version
+ }.
-spec delete_by_uri(uri()) -> ok | {error, any()}.
delete_by_uri(Uri) ->
- Pattern = #els_dt_references{uri = Uri, _ = '_'},
- ok = els_db:match_delete(name(), Pattern).
+ Pattern = #els_dt_references{uri = Uri, _ = '_'},
+ ok = els_db:match_delete(name(), Pattern).
-spec versioned_delete_by_uri(uri(), version()) -> ok.
versioned_delete_by_uri(Uri, Version) ->
- MS = ets:fun2ms(fun(#els_dt_references{uri = U, version = CurrentVersion})
- when U =:= Uri,
- CurrentVersion =:= null orelse
- CurrentVersion =< Version
- ->
- true;
-
- (_) ->
- false
- end),
- ok = els_db:select_delete(name(), MS).
+ MS = ets:fun2ms(fun
+ (#els_dt_references{uri = U, version = CurrentVersion}) when
+ U =:= Uri,
+ CurrentVersion =:= null orelse
+ CurrentVersion =< Version
+ ->
+ true;
+ (_) ->
+ false
+ end),
+ ok = els_db:select_delete(name(), MS).
-spec insert(poi_kind(), item()) -> ok | {error, any()}.
insert(Kind, Map) when is_map(Map) ->
- Record = from_item(Kind, Map),
- els_db:write(name(), Record).
+ Record = from_item(Kind, Map),
+ els_db:write(name(), Record).
-spec versioned_insert(poi_kind(), item()) -> ok | {error, any()}.
versioned_insert(Kind, #{id := Id, version := Version} = Map) ->
- Record = from_item(Kind, Map),
- Condition = fun(#els_dt_references{version = CurrentVersion}) ->
- CurrentVersion =:= null orelse Version >= CurrentVersion
- end,
- els_db:conditional_write(name(), Id, Record, Condition).
+ Record = from_item(Kind, Map),
+ Condition = fun(#els_dt_references{version = CurrentVersion}) ->
+ CurrentVersion =:= null orelse Version >= CurrentVersion
+ end,
+ els_db:conditional_write(name(), Id, Record, Condition).
%% @doc Find by id
-spec find_by_id(poi_kind(), any()) -> {ok, [item()]} | {error, any()}.
find_by_id(Kind, Id) ->
- InternalId = {kind_to_category(Kind), Id},
- Pattern = #els_dt_references{id = InternalId, _ = '_'},
- find_by(Pattern).
+ InternalId = {kind_to_category(Kind), Id},
+ Pattern = #els_dt_references{id = InternalId, _ = '_'},
+ find_by(Pattern).
-spec find_by(tuple()) -> {ok, [item()]}.
find_by(#els_dt_references{id = Id} = Pattern) ->
- Uris = els_text_search:find_candidate_uris(Id),
- [els_indexing:ensure_deeply_indexed(Uri) || Uri <- Uris],
- {ok, Items} = els_db:match(name(), Pattern),
- {ok, [to_item(Item) || Item <- Items]}.
+ Uris = els_text_search:find_candidate_uris(Id),
+ [els_indexing:ensure_deeply_indexed(Uri) || Uri <- Uris],
+ {ok, Items} = els_db:match(name(), Pattern),
+ {ok, [to_item(Item) || Item <- Items]}.
-spec kind_to_category(poi_kind()) -> poi_category().
-kind_to_category(Kind) when Kind =:= application;
- Kind =:= export_entry;
- Kind =:= function;
- Kind =:= function_clause;
- Kind =:= import_entry;
- Kind =:= implicit_fun ->
- function;
-kind_to_category(Kind) when Kind =:= export_type_entry;
- Kind =:= type_application;
- Kind =:= type_definition ->
- type;
-kind_to_category(Kind) when Kind =:= macro;
- Kind =:= define ->
- macro;
-kind_to_category(Kind) when Kind =:= record_expr;
- Kind =:= record ->
- record;
+kind_to_category(Kind) when
+ Kind =:= application;
+ Kind =:= export_entry;
+ Kind =:= function;
+ Kind =:= function_clause;
+ Kind =:= import_entry;
+ Kind =:= implicit_fun
+->
+ function;
+kind_to_category(Kind) when
+ Kind =:= export_type_entry;
+ Kind =:= type_application;
+ Kind =:= type_definition
+->
+ type;
+kind_to_category(Kind) when
+ Kind =:= macro;
+ Kind =:= define
+->
+ macro;
+kind_to_category(Kind) when
+ Kind =:= record_expr;
+ Kind =:= record
+->
+ record;
kind_to_category(Kind) when Kind =:= include ->
- include;
+ include;
kind_to_category(Kind) when Kind =:= include_lib ->
- include_lib;
+ include_lib;
kind_to_category(Kind) when Kind =:= behaviour ->
- behaviour.
+ behaviour.
diff --git a/apps/els_lsp/src/els_dt_signatures.erl b/apps/els_lsp/src/els_dt_signatures.erl
index 1afaa6a92..3981576fa 100644
--- a/apps/els_lsp/src/els_dt_signatures.erl
+++ b/apps/els_lsp/src/els_dt_signatures.erl
@@ -9,20 +9,22 @@
%%==============================================================================
-behaviour(els_db_table).
--export([ name/0
- , opts/0
- ]).
+-export([
+ name/0,
+ opts/0
+]).
%%==============================================================================
%% API
%%==============================================================================
--export([ insert/1
- , versioned_insert/1
- , lookup/1
- , delete_by_uri/1
- , versioned_delete_by_uri/2
- ]).
+-export([
+ insert/1,
+ versioned_insert/1,
+ lookup/1,
+ delete_by_uri/1,
+ versioned_delete_by_uri/2
+]).
%%==============================================================================
%% Includes
@@ -35,17 +37,19 @@
%% Item Definition
%%==============================================================================
--record(els_dt_signatures, { mfa :: mfa() | '_' | {atom(), '_', '_'}
- , spec :: binary() | '_'
- , version :: version() | '_'
- }).
+-record(els_dt_signatures, {
+ mfa :: mfa() | '_' | {atom(), '_', '_'},
+ spec :: binary() | '_',
+ version :: version() | '_'
+}).
-type els_dt_signatures() :: #els_dt_signatures{}.
-type version() :: null | integer().
--type item() :: #{ mfa := mfa()
- , spec := binary()
- , version := version()
- }.
--export_type([ item/0 ]).
+-type item() :: #{
+ mfa := mfa(),
+ spec := binary(),
+ version := version()
+}.
+-export_type([item/0]).
%%==============================================================================
%% Callbacks for the els_db_table Behaviour
@@ -56,77 +60,83 @@ name() -> ?MODULE.
-spec opts() -> proplists:proplist().
opts() ->
- [set].
+ [set].
%%==============================================================================
%% API
%%==============================================================================
-spec from_item(item()) -> els_dt_signatures().
-from_item(#{ mfa := MFA
- , spec := Spec
- , version := Version
- }) ->
- #els_dt_signatures{ mfa = MFA
- , spec = Spec
- , version = Version
- }.
+from_item(#{
+ mfa := MFA,
+ spec := Spec,
+ version := Version
+}) ->
+ #els_dt_signatures{
+ mfa = MFA,
+ spec = Spec,
+ version = Version
+ }.
-spec to_item(els_dt_signatures()) -> item().
-to_item(#els_dt_signatures{ mfa = MFA
- , spec = Spec
- , version = Version
- }) ->
- #{ mfa => MFA
- , spec => Spec
- , version => Version
- }.
+to_item(#els_dt_signatures{
+ mfa = MFA,
+ spec = Spec,
+ version = Version
+}) ->
+ #{
+ mfa => MFA,
+ spec => Spec,
+ version => Version
+ }.
-spec insert(item()) -> ok | {error, any()}.
insert(Map) when is_map(Map) ->
- Record = from_item(Map),
- els_db:write(name(), Record).
+ Record = from_item(Map),
+ els_db:write(name(), Record).
-spec versioned_insert(item()) -> ok | {error, any()}.
versioned_insert(#{mfa := MFA, version := Version} = Map) ->
- Record = from_item(Map),
- Condition = fun(#els_dt_signatures{version = CurrentVersion}) ->
- CurrentVersion =:= null orelse Version >= CurrentVersion
- end,
- els_db:conditional_write(name(), MFA, Record, Condition).
+ Record = from_item(Map),
+ Condition = fun(#els_dt_signatures{version = CurrentVersion}) ->
+ CurrentVersion =:= null orelse Version >= CurrentVersion
+ end,
+ els_db:conditional_write(name(), MFA, Record, Condition).
-spec lookup(mfa()) -> {ok, [item()]}.
lookup({M, _F, _A} = MFA) ->
- {ok, _Uris} = els_utils:find_modules(M),
- {ok, Items} = els_db:lookup(name(), MFA),
- {ok, [to_item(Item) || Item <- Items]}.
+ {ok, _Uris} = els_utils:find_modules(M),
+ {ok, Items} = els_db:lookup(name(), MFA),
+ {ok, [to_item(Item) || Item <- Items]}.
-spec delete_by_uri(uri()) -> ok.
delete_by_uri(Uri) ->
- case filename:extension(Uri) of
- <<".erl">> ->
- Module = els_uri:module(Uri),
- Pattern = #els_dt_signatures{mfa = {Module, '_', '_'}, _ = '_'},
- ok = els_db:match_delete(name(), Pattern);
- _ ->
- ok
- end.
+ case filename:extension(Uri) of
+ <<".erl">> ->
+ Module = els_uri:module(Uri),
+ Pattern = #els_dt_signatures{mfa = {Module, '_', '_'}, _ = '_'},
+ ok = els_db:match_delete(name(), Pattern);
+ _ ->
+ ok
+ end.
-spec versioned_delete_by_uri(uri(), version()) -> ok.
versioned_delete_by_uri(Uri, Version) ->
- case filename:extension(Uri) of
- <<".erl">> ->
- Module = els_uri:module(Uri),
- MS = ets:fun2ms(
- fun(#els_dt_signatures{mfa = {M, _, _}, version = CurrentVersion})
- when M =:= Module,
- CurrentVersion =:= null orelse CurrentVersion < Version
- ->
- true;
- (_) ->
- false
- end),
- ok = els_db:select_delete(name(), MS);
- _ ->
- ok
- end.
+ case filename:extension(Uri) of
+ <<".erl">> ->
+ Module = els_uri:module(Uri),
+ MS = ets:fun2ms(
+ fun
+ (#els_dt_signatures{mfa = {M, _, _}, version = CurrentVersion}) when
+ M =:= Module,
+ CurrentVersion =:= null orelse CurrentVersion < Version
+ ->
+ true;
+ (_) ->
+ false
+ end
+ ),
+ ok = els_db:select_delete(name(), MS);
+ _ ->
+ ok
+ end.
diff --git a/apps/els_lsp/src/els_edoc_diagnostics.erl b/apps/els_lsp/src/els_edoc_diagnostics.erl
index deea482ad..8b8bad6a0 100644
--- a/apps/els_lsp/src/els_edoc_diagnostics.erl
+++ b/apps/els_lsp/src/els_edoc_diagnostics.erl
@@ -11,10 +11,11 @@
%%==============================================================================
%% Exports
%%==============================================================================
--export([ is_default/0
- , run/1
- , source/0
- ]).
+-export([
+ is_default/0,
+ run/1,
+ source/0
+]).
%%==============================================================================
%% Includes
@@ -27,7 +28,7 @@
-spec is_default() -> boolean().
is_default() ->
- false.
+ false.
%% The edoc application currently does not offer an API to
%% programmatically return a list of warnings and errors. Instead,
@@ -41,62 +42,69 @@ is_default() ->
%% warnings and errors.
-spec run(uri()) -> [els_diagnostics:diagnostic()].
run(Uri) ->
- case filename:extension(Uri) of
- <<".erl">> ->
- do_run(Uri);
- _ ->
- []
- end.
+ case filename:extension(Uri) of
+ <<".erl">> ->
+ do_run(Uri);
+ _ ->
+ []
+ end.
-spec do_run(uri()) -> [els_diagnostics:diagnostic()].
do_run(Uri) ->
- Paths = [els_utils:to_list(els_uri:path(Uri))],
- Fun = fun(Dir) ->
- Options = edoc_options(Dir),
- put(edoc_diagnostics, []),
- try
- edoc:run(Paths, Options)
- catch
- _:_:_ ->
+ Paths = [els_utils:to_list(els_uri:path(Uri))],
+ Fun = fun(Dir) ->
+ Options = edoc_options(Dir),
+ put(edoc_diagnostics, []),
+ try
+ edoc:run(Paths, Options)
+ catch
+ _:_:_ ->
ok
- end,
- [make_diagnostic(L, Format, Args, Severity) ||
- {L, _Where, Format, Args, Severity}
- <- get(edoc_diagnostics), L =/= 0]
end,
- tempdir:mktmp(Fun).
+ [
+ make_diagnostic(L, Format, Args, Severity)
+ || {L, _Where, Format, Args, Severity} <-
+ get(edoc_diagnostics),
+ L =/= 0
+ ]
+ end,
+ tempdir:mktmp(Fun).
-spec source() -> binary().
source() ->
- <<"Edoc">>.
+ <<"Edoc">>.
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec edoc_options(string()) -> proplists:proplist().
edoc_options(Dir) ->
- Macros = [{N, V} || {'d', N, V} <- els_compiler_diagnostics:macro_options()],
- Includes = [I || {i, I} <- els_compiler_diagnostics:include_options()],
- [ {preprocess, true}
- , {macros, Macros}
- , {includes, Includes}
- , {dir, Dir}
- ].
+ Macros = [{N, V} || {'d', N, V} <- els_compiler_diagnostics:macro_options()],
+ Includes = [I || {i, I} <- els_compiler_diagnostics:include_options()],
+ [
+ {preprocess, true},
+ {macros, Macros},
+ {includes, Includes},
+ {dir, Dir}
+ ].
-spec make_diagnostic(pos_integer(), string(), [any()], warning | error) ->
- els_diagnostics:diagnostic().
+ els_diagnostics:diagnostic().
make_diagnostic(Line, Format, Args, Severity0) ->
- Severity = severity(Severity0),
- Message = els_utils:to_binary(io_lib:format(Format, Args)),
- els_diagnostics:make_diagnostic( els_protocol:range(#{ from => {Line, 1}
- , to => {Line + 1, 1}
- })
- , Message
- , Severity
- , source()).
+ Severity = severity(Severity0),
+ Message = els_utils:to_binary(io_lib:format(Format, Args)),
+ els_diagnostics:make_diagnostic(
+ els_protocol:range(#{
+ from => {Line, 1},
+ to => {Line + 1, 1}
+ }),
+ Message,
+ Severity,
+ source()
+ ).
-spec severity(warning | error) -> els_diagnostics:severity().
severity(warning) ->
- ?DIAGNOSTIC_WARNING;
+ ?DIAGNOSTIC_WARNING;
severity(error) ->
- ?DIAGNOSTIC_ERROR.
+ ?DIAGNOSTIC_ERROR.
diff --git a/apps/els_lsp/src/els_eep48_docs.erl b/apps/els_lsp/src/els_eep48_docs.erl
index 21b8661a6..2805c2423 100644
--- a/apps/els_lsp/src/els_eep48_docs.erl
+++ b/apps/els_lsp/src/els_eep48_docs.erl
@@ -36,43 +36,87 @@
-export_type([chunk_elements/0, chunk_element_attr/0]).
--record(config, { docs :: docs_v1() }).
-
--define(ALL_ELEMENTS,[a,p,'div',br,h1,h2,h3,h4,h5,h6,hr,
- i,b,em,strong,pre,code,ul,ol,li,dl,dt,dd]).
+-record(config, {docs :: docs_v1()}).
+
+-define(ALL_ELEMENTS, [
+ a,
+ p,
+ 'div',
+ br,
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6,
+ hr,
+ i,
+ b,
+ em,
+ strong,
+ pre,
+ code,
+ ul,
+ ol,
+ li,
+ dl,
+ dt,
+ dd
+]).
%% inline elements are:
--define(INLINE,[i,b,em,strong,code,a]).
--define(IS_INLINE(ELEM),(((ELEM) =:= a) orelse ((ELEM) =:= code)
- orelse ((ELEM) =:= i) orelse ((ELEM) =:= em)
- orelse ((ELEM) =:= b) orelse ((ELEM) =:= strong))).
+-define(INLINE, [i, b, em, strong, code, a]).
+-define(IS_INLINE(ELEM),
+ (((ELEM) =:= a) orelse ((ELEM) =:= code) orelse
+ ((ELEM) =:= i) orelse ((ELEM) =:= em) orelse
+ ((ELEM) =:= b) orelse ((ELEM) =:= strong))
+).
%% non-inline elements are:
--define(BLOCK,[p,'div',pre,br,ul,ol,li,dl,dt,dd,h1,h2,h3,h4,h5,h6,hr]).
--define(IS_BLOCK(ELEM),not ?IS_INLINE(ELEM)).
--define(IS_PRE(ELEM),(((ELEM) =:= pre))).
+-define(BLOCK, [p, 'div', pre, br, ul, ol, li, dl, dt, dd, h1, h2, h3, h4, h5, h6, hr]).
+-define(IS_BLOCK(ELEM), not ?IS_INLINE(ELEM)).
+-define(IS_PRE(ELEM), ((ELEM) =:= pre)).
%% If you update the below types, make sure to update the documentation in
%% erl_docgen/doc/src/doc_storage.xml as well!!!
--type docs_v1() :: #docs_v1{ docs :: [chunk_entry()], format :: binary(), module_doc :: chunk_elements() }.
--type chunk_entry() :: {{Type :: function | type | callback, Name :: atom(), Arity :: non_neg_integer()},
- Anno :: erl_anno:anno(),
- Sigs :: [binary()],
- Docs :: none | hidden | #{ binary() => chunk_elements() },
- Meta :: #{ signature := term() }}.
--type config() :: #{ }.
+-type docs_v1() :: #docs_v1{
+ docs :: [chunk_entry()], format :: binary(), module_doc :: chunk_elements()
+}.
+-type chunk_entry() :: {
+ {Type :: function | type | callback, Name :: atom(), Arity :: non_neg_integer()},
+ Anno :: erl_anno:anno(),
+ Sigs :: [binary()],
+ Docs :: none | hidden | #{binary() => chunk_elements()},
+ Meta :: #{signature := term()}
+}.
+-type config() :: #{}.
-type chunk_elements() :: [chunk_element()].
--type chunk_element() :: {chunk_element_type(),chunk_element_attrs(),
- chunk_elements()} | binary().
+-type chunk_element() ::
+ {chunk_element_type(), chunk_element_attrs(), chunk_elements()}
+ | binary().
-type chunk_element_attrs() :: [chunk_element_attr()].
--type chunk_element_attr() :: {atom(),unicode:chardata()}.
+-type chunk_element_attr() :: {atom(), unicode:chardata()}.
-type chunk_element_type() :: chunk_element_inline_type() | chunk_element_block_type().
-type chunk_element_inline_type() :: a | code | em | strong | i | b.
--type chunk_element_block_type() :: p | 'div' | br | pre | ul |
- ol | li | dl | dt | dd |
- h1 | h2 | h3 | h4 | h5 | h6.
+-type chunk_element_block_type() ::
+ p
+ | 'div'
+ | br
+ | pre
+ | ul
+ | ol
+ | li
+ | dl
+ | dt
+ | dd
+ | h1
+ | h2
+ | h3
+ | h4
+ | h5
+ | h6.
-spec normalize(Docs) -> NormalizedDocs when
- Docs :: chunk_elements(),
- NormalizedDocs :: chunk_elements().
+ Docs :: chunk_elements(),
+ NormalizedDocs :: chunk_elements().
normalize(Docs) ->
shell_docs:normalize(Docs).
@@ -80,242 +124,335 @@ normalize(Docs) ->
%% API function for dealing with the function documentation
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec render(Module, Function, Docs) -> Res when
- Module :: module(),
- Function :: atom(),
- Docs :: docs_v1(),
- Res :: unicode:chardata() | {error,function_missing}.
-render(_Module, Function, #docs_v1{ } = D) ->
+ Module :: module(),
+ Function :: atom(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error, function_missing}.
+render(_Module, Function, #docs_v1{} = D) ->
render(_Module, Function, D, #{}).
--spec render(Module, Function, Docs, Config) -> Res when
- Module :: module(),
- Function :: atom(),
- Docs :: docs_v1(),
- Config :: config(),
- Res :: unicode:chardata() | {error,function_missing};
-
- (Module, Function, Arity, Docs) -> Res when
- Module :: module(),
- Function :: atom(),
- Arity :: arity(),
- Docs :: docs_v1(),
- Res :: unicode:chardata() | {error,function_missing}.
-render(Module, Function, #docs_v1{ docs = Docs } = D, Config)
- when is_atom(Module), is_atom(Function), is_map(Config) ->
+-spec render
+ (Module, Function, Docs, Config) -> Res when
+ Module :: module(),
+ Function :: atom(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error, function_missing};
+ (Module, Function, Arity, Docs) -> Res when
+ Module :: module(),
+ Function :: atom(),
+ Arity :: arity(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error, function_missing}.
+render(Module, Function, #docs_v1{docs = Docs} = D, Config) when
+ is_atom(Module), is_atom(Function), is_map(Config)
+->
render_function(
- lists:filter(fun({{function, F, _},_Anno,_Sig,Doc,_Meta}) when Doc =/= none ->
- F =:= Function;
- (_) ->
- false
- end, Docs), D, Config);
-render(_Module, Function, Arity, #docs_v1{ } = D) ->
+ lists:filter(
+ fun
+ ({{function, F, _}, _Anno, _Sig, Doc, _Meta}) when Doc =/= none ->
+ F =:= Function;
+ (_) ->
+ false
+ end,
+ Docs
+ ),
+ D,
+ Config
+ );
+render(_Module, Function, Arity, #docs_v1{} = D) ->
render(_Module, Function, Arity, D, #{}).
-spec render(Module, Function, Arity, Docs, Config) -> Res when
- Module :: module(),
- Function :: atom(),
- Arity :: arity(),
- Docs :: docs_v1(),
- Config :: config(),
- Res :: unicode:chardata() | {error,function_missing}.
-render(Module, Function, Arity, #docs_v1{ docs = Docs } = D, Config)
- when is_atom(Module), is_atom(Function), is_integer(Arity), is_map(Config) ->
+ Module :: module(),
+ Function :: atom(),
+ Arity :: arity(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error, function_missing}.
+render(Module, Function, Arity, #docs_v1{docs = Docs} = D, Config) when
+ is_atom(Module), is_atom(Function), is_integer(Arity), is_map(Config)
+->
render_function(
- lists:filter(fun({{function, F, A},_Anno,_Sig,Doc,_Meta}) when Doc =/= none->
- F =:= Function andalso A =:= Arity;
- (_) ->
- false
- end, Docs), D, Config).
+ lists:filter(
+ fun
+ ({{function, F, A}, _Anno, _Sig, Doc, _Meta}) when Doc =/= none ->
+ F =:= Function andalso A =:= Arity;
+ (_) ->
+ false
+ end,
+ Docs
+ ),
+ D,
+ Config
+ ).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% API function for dealing with the type documentation
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec render_type(Module, Type, Docs) -> Res when
- Module :: module(), Type :: atom(),
- Docs :: docs_v1(),
- Res :: unicode:chardata() | {error, type_missing}.
+ Module :: module(),
+ Type :: atom(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error, type_missing}.
render_type(Module, Type, D = #docs_v1{}) ->
render_type(Module, Type, D, #{}).
--spec render_type(Module, Type, Docs, Config) -> Res when
- Module :: module(), Type :: atom(),
- Docs :: docs_v1(),
- Config :: config(),
- Res :: unicode:chardata() | {error, type_missing};
- (Module, Type, Arity, Docs) -> Res when
- Module :: module(), Type :: atom(), Arity :: arity(),
- Docs :: docs_v1(),
- Res :: unicode:chardata() | {error, type_missing}.
-render_type(_Module, Type, #docs_v1{ docs = Docs } = D, Config) ->
+-spec render_type
+ (Module, Type, Docs, Config) -> Res when
+ Module :: module(),
+ Type :: atom(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error, type_missing};
+ (Module, Type, Arity, Docs) -> Res when
+ Module :: module(),
+ Type :: atom(),
+ Arity :: arity(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error, type_missing}.
+render_type(_Module, Type, #docs_v1{docs = Docs} = D, Config) ->
render_typecb_docs(
- lists:filter(fun({{type, T, _},_Anno,_Sig,_Doc,_Meta}) ->
- T =:= Type;
- (_) ->
- false
- end, Docs), D, Config);
-render_type(_Module, Type, Arity, #docs_v1{ } = D) ->
+ lists:filter(
+ fun
+ ({{type, T, _}, _Anno, _Sig, _Doc, _Meta}) ->
+ T =:= Type;
+ (_) ->
+ false
+ end,
+ Docs
+ ),
+ D,
+ Config
+ );
+render_type(_Module, Type, Arity, #docs_v1{} = D) ->
render_type(_Module, Type, Arity, D, #{}).
-spec render_type(Module, Type, Arity, Docs, Config) -> Res when
- Module :: module(), Type :: atom(), Arity :: arity(),
- Docs :: docs_v1(),
- Config :: config(),
- Res :: unicode:chardata() | {error, type_missing}.
-render_type(_Module, Type, Arity, #docs_v1{ docs = Docs } = D, Config) ->
+ Module :: module(),
+ Type :: atom(),
+ Arity :: arity(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error, type_missing}.
+render_type(_Module, Type, Arity, #docs_v1{docs = Docs} = D, Config) ->
render_typecb_docs(
- lists:filter(fun({{type, T, A},_Anno,_Sig,_Doc,_Meta}) ->
- T =:= Type andalso A =:= Arity;
- (_) ->
- false
- end, Docs), D, Config).
+ lists:filter(
+ fun
+ ({{type, T, A}, _Anno, _Sig, _Doc, _Meta}) ->
+ T =:= Type andalso A =:= Arity;
+ (_) ->
+ false
+ end,
+ Docs
+ ),
+ D,
+ Config
+ ).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% API function for dealing with the callback documentation
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec render_callback(Module, Callback, Docs) -> Res when
- Module :: module(), Callback :: atom(),
- Docs :: docs_v1(),
- Res :: unicode:chardata() | {error, callback_missing}.
-render_callback(_Module, Callback, #docs_v1{ } = D) ->
+ Module :: module(),
+ Callback :: atom(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error, callback_missing}.
+render_callback(_Module, Callback, #docs_v1{} = D) ->
render_callback(_Module, Callback, D, #{}).
--spec render_callback(Module, Callback, Docs, Config) -> Res when
- Module :: module(), Callback :: atom(),
- Docs :: docs_v1(),
- Config :: config(),
- Res :: unicode:chardata() | {error, callback_missing};
- (Module, Callback, Arity, Docs) -> Res when
- Module :: module(), Callback :: atom(), Arity :: arity(),
- Docs :: docs_v1(),
- Res :: unicode:chardata() | {error, callback_missing}.
-render_callback(_Module, Callback, Arity, #docs_v1{ } = D) ->
+-spec render_callback
+ (Module, Callback, Docs, Config) -> Res when
+ Module :: module(),
+ Callback :: atom(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error, callback_missing};
+ (Module, Callback, Arity, Docs) -> Res when
+ Module :: module(),
+ Callback :: atom(),
+ Arity :: arity(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error, callback_missing}.
+render_callback(_Module, Callback, Arity, #docs_v1{} = D) ->
render_callback(_Module, Callback, Arity, D, #{});
-render_callback(_Module, Callback, #docs_v1{ docs = Docs } = D, Config) ->
+render_callback(_Module, Callback, #docs_v1{docs = Docs} = D, Config) ->
render_typecb_docs(
- lists:filter(fun({{callback, T, _},_Anno,_Sig,_Doc,_Meta}) ->
- T =:= Callback;
- (_) ->
- false
- end, Docs), D, Config).
+ lists:filter(
+ fun
+ ({{callback, T, _}, _Anno, _Sig, _Doc, _Meta}) ->
+ T =:= Callback;
+ (_) ->
+ false
+ end,
+ Docs
+ ),
+ D,
+ Config
+ ).
-spec render_callback(Module, Callback, Arity, Docs, Config) -> Res when
- Module :: module(), Callback :: atom(), Arity :: arity(),
- Docs :: docs_v1(),
- Config :: config(),
- Res :: unicode:chardata() | {error, callback_missing}.
-render_callback(_Module, Callback, Arity, #docs_v1{ docs = Docs } = D, Config) ->
+ Module :: module(),
+ Callback :: atom(),
+ Arity :: arity(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error, callback_missing}.
+render_callback(_Module, Callback, Arity, #docs_v1{docs = Docs} = D, Config) ->
render_typecb_docs(
- lists:filter(fun({{callback, T, A},_Anno,_Sig,_Doc,_Meta}) ->
- T =:= Callback andalso A =:= Arity;
- (_) ->
- false
- end, Docs), D, Config).
+ lists:filter(
+ fun
+ ({{callback, T, A}, _Anno, _Sig, _Doc, _Meta}) ->
+ T =:= Callback andalso A =:= Arity;
+ (_) ->
+ false
+ end,
+ Docs
+ ),
+ D,
+ Config
+ ).
%% Get the docs in the correct locale if it exists.
-spec get_local_doc(_, 'hidden' | 'none' | map(), #docs_v1{}) -> chunk_elements().
get_local_doc(MissingMod, Docs, D) when is_atom(MissingMod) ->
get_local_doc(atom_to_binary(MissingMod), Docs, D);
-get_local_doc({F,A}, Docs, D) ->
- get_local_doc(unicode:characters_to_binary(
- io_lib:format("~tp/~p",[F,A])), Docs, D);
-get_local_doc({_Type,F,A}, Docs, D) ->
- get_local_doc({F,A}, Docs, D);
-get_local_doc(_Missing, #{ <<"en">> := Docs }, D) ->
+get_local_doc({F, A}, Docs, D) ->
+ get_local_doc(
+ unicode:characters_to_binary(
+ io_lib:format("~tp/~p", [F, A])
+ ),
+ Docs,
+ D
+ );
+get_local_doc({_Type, F, A}, Docs, D) ->
+ get_local_doc({F, A}, Docs, D);
+get_local_doc(_Missing, #{<<"en">> := Docs}, D) ->
%% English if it exists
normalize_format(Docs, D);
get_local_doc(_Missing, ModuleDoc, D) when map_size(ModuleDoc) > 0 ->
%% Otherwise take first alternative found
normalize_format(maps:get(hd(maps:keys(ModuleDoc)), ModuleDoc), D);
get_local_doc(Missing, hidden, _D) ->
- [{p,[],[<<"The documentation for ">>,Missing,
- <<" is hidden. This probably means that it is internal "
- "and not to be used by other applications.">>]}];
+ [
+ {p, [], [
+ <<"The documentation for ">>,
+ Missing,
+ <<
+ " is hidden. This probably means that it is internal "
+ "and not to be used by other applications."
+ >>
+ ]}
+ ];
get_local_doc(_Missing, None, _D) when None =:= none; None =:= #{} ->
[].
-spec normalize_format(chunk_elements(), #docs_v1{}) -> chunk_elements().
-normalize_format(Docs, #docs_v1{ format = ?NATIVE_FORMAT }) ->
+normalize_format(Docs, #docs_v1{format = ?NATIVE_FORMAT}) ->
normalize(Docs);
-normalize_format(Docs, #docs_v1{ format = <<"text/", _/binary>> }) when is_binary(Docs) ->
+normalize_format(Docs, #docs_v1{format = <<"text/", _/binary>>}) when is_binary(Docs) ->
[{pre, [], [Docs]}].
%%% Functions for rendering reference documentation
--spec render_function([chunk_entry()], #docs_v1{}, map()) -> unicode:chardata() | {'error', 'function_missing'}.
+-spec render_function([chunk_entry()], #docs_v1{}, map()) ->
+ unicode:chardata() | {'error', 'function_missing'}.
render_function([], _D, _Config) ->
- {error,function_missing};
-render_function(FDocs, #docs_v1{ docs = Docs } = D, Config) ->
+ {error, function_missing};
+render_function(FDocs, #docs_v1{docs = Docs} = D, Config) ->
Grouping =
lists:foldl(
- fun({_Group,_Anno,_Sig,_Doc,#{ equiv := Group }} = Func,Acc) ->
- Members = maps:get(Group, Acc, []),
- Acc#{ Group => [Func|Members] };
- ({Group, _Anno, _Sig, _Doc, _Meta} = Func, Acc) ->
- Members = maps:get(Group, Acc, []),
- Acc#{ Group => [Func|Members] }
- end, #{}, lists:sort(FDocs)),
+ fun
+ ({_Group, _Anno, _Sig, _Doc, #{equiv := Group}} = Func, Acc) ->
+ Members = maps:get(Group, Acc, []),
+ Acc#{Group => [Func | Members]};
+ ({Group, _Anno, _Sig, _Doc, _Meta} = Func, Acc) ->
+ Members = maps:get(Group, Acc, []),
+ Acc#{Group => [Func | Members]}
+ end,
+ #{},
+ lists:sort(FDocs)
+ ),
lists:map(
- fun({Group,Members}) ->
- lists:map(
- fun(Member = {_,_,_,Doc,_}) ->
- Sig = render_signature(Member),
- LocalDoc =
- if Doc =:= #{} ->
- case lists:keyfind(Group, 1, Docs) of
- false ->
- get_local_doc(Group, none, D);
- {_,_,_,GroupDoc,_} ->
- get_local_doc(Group, GroupDoc, D)
- end;
- true ->
- get_local_doc(Group, Doc, D)
+ fun({Group, Members}) ->
+ lists:map(
+ fun(Member = {_, _, _, Doc, _}) ->
+ Sig = render_signature(Member),
+ LocalDoc =
+ if
+ Doc =:= #{} ->
+ case lists:keyfind(Group, 1, Docs) of
+ false ->
+ get_local_doc(Group, none, D);
+ {_, _, _, GroupDoc, _} ->
+ get_local_doc(Group, GroupDoc, D)
+ end;
+ true ->
+ get_local_doc(Group, Doc, D)
end,
- render_headers_and_docs(
- [Sig], LocalDoc, D, Config)
- end, Members)
- end, maps:to_list(Grouping)).
+ render_headers_and_docs(
+ [Sig], LocalDoc, D, Config
+ )
+ end,
+ Members
+ )
+ end,
+ maps:to_list(Grouping)
+ ).
%% Render the signature of either function, type, or anything else really.
-spec render_signature(chunk_entry()) -> chunk_elements().
-render_signature({{_Type,_F,_A},_Anno,_Sigs,_Docs,#{ signature := Specs } = Meta}) ->
+render_signature({{_Type, _F, _A}, _Anno, _Sigs, _Docs, #{signature := Specs} = Meta}) ->
lists:flatmap(
- fun(ASTSpec) ->
- PPSpec = erl_pp:attribute(ASTSpec,[{encoding,utf8}]),
- Spec =
- case ASTSpec of
- {_Attribute, _Line, opaque, _} ->
- %% We do not want show the internals of the opaque type
- hd(string:split(PPSpec,"::"));
- _ ->
- trim_spec(PPSpec)
- end,
- BinSpec =
- unicode:characters_to_binary(
- string:trim(Spec, trailing, "\n")),
- [{pre,[],BinSpec},
- {hr,[],[]}|render_meta(Meta)]
- end, Specs);
-render_signature({{_Type,_F,_A},_Anno,Sigs,_Docs,Meta}) ->
- [{pre,[],Sigs},{hr,[],[]} | render_meta(Meta)].
+ fun(ASTSpec) ->
+ PPSpec = erl_pp:attribute(ASTSpec, [{encoding, utf8}]),
+ Spec =
+ case ASTSpec of
+ {_Attribute, _Line, opaque, _} ->
+ %% We do not want show the internals of the opaque type
+ hd(string:split(PPSpec, "::"));
+ _ ->
+ trim_spec(PPSpec)
+ end,
+ BinSpec =
+ unicode:characters_to_binary(
+ string:trim(Spec, trailing, "\n")
+ ),
+ [
+ {pre, [], BinSpec},
+ {hr, [], []}
+ | render_meta(Meta)
+ ]
+ end,
+ Specs
+ );
+render_signature({{_Type, _F, _A}, _Anno, Sigs, _Docs, Meta}) ->
+ [{pre, [], Sigs}, {hr, [], []} | render_meta(Meta)].
-spec trim_spec(unicode:chardata()) -> unicode:chardata().
trim_spec(Spec) ->
unicode:characters_to_binary(
- string:trim(
- lists:join($\n,trim_spec(string:split(Spec, "\n", all),0)),
- trailing, "\n")).
+ string:trim(
+ lists:join($\n, trim_spec(string:split(Spec, "\n", all), 0)),
+ trailing,
+ "\n"
+ )
+ ).
-spec trim_spec([unicode:chardata()], non_neg_integer()) -> unicode:chardata().
-trim_spec(["-spec " ++ Spec|T], 0) ->
+trim_spec(["-spec " ++ Spec | T], 0) ->
[Spec | trim_spec(T, 6)];
-trim_spec([H|T], N) ->
- case re:run(H,io_lib:format("(\\s{~p}\\s+)when",[N]),[{capture,all_but_first}]) of
- {match,[{0,Indent}]} ->
- trim_spec([H|T],Indent);
+trim_spec([H | T], N) ->
+ case re:run(H, io_lib:format("(\\s{~p}\\s+)when", [N]), [{capture, all_but_first}]) of
+ {match, [{0, Indent}]} ->
+ trim_spec([H | T], Indent);
nomatch ->
- case string:trim(string:slice(H, 0, N),both) of
+ case string:trim(string:slice(H, 0, N), both) of
"" ->
- [re:replace(string:slice(H, N, infinity)," "," ",[global])|trim_spec(T, N)];
+ [
+ re:replace(string:slice(H, N, infinity), " ", " ", [global])
+ | trim_spec(T, N)
+ ];
_ ->
- [re:replace(H," "," ",[global])|trim_spec(T, N)]
+ [re:replace(H, " ", " ", [global]) | trim_spec(T, N)]
end
end;
trim_spec([], _N) ->
@@ -323,42 +460,63 @@ trim_spec([], _N) ->
-spec render_meta(map()) -> chunk_elements().
render_meta(Meta) ->
- case lists:flatmap(
- fun({since,Vsn}) ->
- [{em,[],<<"Since:">>}, <<" ">>, Vsn];
- ({ deprecated, Depr }) ->
- [{em,[],<<"Deprecated: ">>}, <<" ">>, Depr];
- (_) -> []
- end, maps:to_list(Meta)) of
+ case
+ lists:flatmap(
+ fun
+ ({since, Vsn}) ->
+ [{em, [], <<"Since:">>}, <<" ">>, Vsn];
+ ({deprecated, Depr}) ->
+ [{em, [], <<"Deprecated: ">>}, <<" ">>, Depr];
+ (_) ->
+ []
+ end,
+ maps:to_list(Meta)
+ )
+ of
[] ->
[];
Docs ->
Docs
end.
--spec render_headers_and_docs([chunk_elements()], chunk_elements(), #docs_v1{}, map()) -> unicode:chardata().
+-spec render_headers_and_docs([chunk_elements()], chunk_elements(), #docs_v1{}, map()) ->
+ unicode:chardata().
render_headers_and_docs(Headers, DocContents, D, Config) ->
render_headers_and_docs(Headers, DocContents, init_config(D, Config)).
--spec render_headers_and_docs([chunk_elements()], chunk_elements(), #config{}) -> unicode:chardata().
+-spec render_headers_and_docs([chunk_elements()], chunk_elements(), #config{}) ->
+ unicode:chardata().
render_headers_and_docs(Headers, DocContents, #config{} = Config) ->
- [render_docs(
- lists:flatmap(
- fun(Header) ->
- [{br,[],[]},Header]
- end, Headers), Config),
- "\n",
- render_docs(DocContents, 0, Config)].
-
--spec render_typecb_docs([TypeCB] | TypeCB, #config{}) -> unicode:chardata() | {'error', 'type_missing'} when
- TypeCB :: {{type | callback, Name :: atom(), Arity :: non_neg_integer()},
- Encoding :: binary(), Sig :: [binary()], none | hidden | #{ binary() => chunk_elements()}}.
+ [
+ render_docs(
+ lists:flatmap(
+ fun(Header) ->
+ [{br, [], []}, Header]
+ end,
+ Headers
+ ),
+ Config
+ ),
+ "\n",
+ render_docs(DocContents, 0, Config)
+ ].
+
+-spec render_typecb_docs([TypeCB] | TypeCB, #config{}) ->
+ unicode:chardata() | {'error', 'type_missing'}
+when
+ TypeCB :: {
+ {type | callback, Name :: atom(), Arity :: non_neg_integer()},
+ Encoding :: binary(),
+ Sig :: [binary()],
+ none | hidden | #{binary() => chunk_elements()}
+ }.
render_typecb_docs([], _C) ->
- {error,type_missing};
+ {error, type_missing};
render_typecb_docs(TypeCBs, #config{} = C) when is_list(TypeCBs) ->
[render_typecb_docs(TypeCB, C) || TypeCB <- TypeCBs];
-render_typecb_docs({F,_,_Sig,Docs,_Meta} = TypeCB, #config{docs = D} = C) ->
- render_headers_and_docs(render_signature(TypeCB), get_local_doc(F,Docs,D), C).
--spec render_typecb_docs(chunk_elements(), #docs_v1{}, _) -> unicode:chardata() | {'error', 'type_missing'}.
+render_typecb_docs({F, _, _Sig, Docs, _Meta} = TypeCB, #config{docs = D} = C) ->
+ render_headers_and_docs(render_signature(TypeCB), get_local_doc(F, Docs, D), C).
+-spec render_typecb_docs(chunk_elements(), #docs_v1{}, _) ->
+ unicode:chardata() | {'error', 'type_missing'}.
render_typecb_docs(Docs, D, Config) ->
render_typecb_docs(Docs, init_config(D, Config)).
@@ -368,28 +526,32 @@ render_docs(DocContents, #config{} = Config) ->
render_docs(DocContents, 0, Config).
-spec render_docs([chunk_element()], 0, #config{}) -> unicode:chardata().
render_docs(DocContents, Ind, D = #config{}) when is_integer(Ind) ->
- {Doc,_} = trimnl(render_docs(DocContents, [], 0, Ind, D)),
+ {Doc, _} = trimnl(render_docs(DocContents, [], 0, Ind, D)),
Doc.
-spec init_config(#docs_v1{}, _) -> #config{}.
init_config(D, _Config) ->
- #config{ docs = D }.
-
--spec render_docs(Elems :: [chunk_element()],
- Stack :: [chunk_element_type()],
- non_neg_integer(),
- non_neg_integer(),
- #config{}) ->
+ #config{docs = D}.
+
+-spec render_docs(
+ Elems :: [chunk_element()],
+ Stack :: [chunk_element_type()],
+ non_neg_integer(),
+ non_neg_integer(),
+ #config{}
+) ->
{unicode:chardata(), non_neg_integer()}.
-render_docs(Elems,State,Pos,Ind,D) when is_list(Elems) ->
+render_docs(Elems, State, Pos, Ind, D) when is_list(Elems) ->
lists:mapfoldl(
- fun(Elem,P) ->
- render_docs(Elem,State,P,Ind,D)
- end,Pos,Elems);
-render_docs(Elem,State,Pos,Ind,D) ->
-% io:format("Elem: ~p (~p) (~p,~p)~n",[Elem,State,Pos,Ind]),
- render_element(Elem,State,Pos,Ind,D).
-
+ fun(Elem, P) ->
+ render_docs(Elem, State, P, Ind, D)
+ end,
+ Pos,
+ Elems
+ );
+render_docs(Elem, State, Pos, Ind, D) ->
+ % io:format("Elem: ~p (~p) (~p,~p)~n",[Elem,State,Pos,Ind]),
+ render_element(Elem, State, Pos, Ind, D).
%%% The function is the main element rendering function
%%%
@@ -406,215 +568,244 @@ render_docs(Elem,State,Pos,Ind,D) ->
%%% Any block elements (i.e. p, ul, li etc) are responsible for trimming
%%% extra new lines. eg. should only
%%% have two newlines at the end.
--spec render_element(Elem :: chunk_element(),
- Stack :: [chunk_element_type()],
- Pos :: non_neg_integer(),
- Indent :: non_neg_integer(),
- Config :: #config{}) ->
- {unicode:chardata(), Pos :: non_neg_integer()}.
+-spec render_element(
+ Elem :: chunk_element(),
+ Stack :: [chunk_element_type()],
+ Pos :: non_neg_integer(),
+ Indent :: non_neg_integer(),
+ Config :: #config{}
+) ->
+ {unicode:chardata(), Pos :: non_neg_integer()}.
%% render_element({IgnoreMe,_,Content}, State, Pos, Ind,D)
%% when IgnoreMe =:= a ->
%% render_docs(Content, State, Pos, Ind,D);
%% Catch h* before the padding is done as they reset padding
-render_element({h1,_,Content},State,0 = Pos,_Ind,D) ->
- {Docs, NewPos} = render_docs(Content,State,Pos,0,D),
+render_element({h1, _, Content}, State, 0 = Pos, _Ind, D) ->
+ {Docs, NewPos} = render_docs(Content, State, Pos, 0, D),
trimnlnl({["# ", Docs], NewPos});
-render_element({h2,_,Content},State,0 = Pos,_Ind,D) ->
- {Docs, NewPos} = render_docs(Content,State,Pos,0,D),
+render_element({h2, _, Content}, State, 0 = Pos, _Ind, D) ->
+ {Docs, NewPos} = render_docs(Content, State, Pos, 0, D),
trimnlnl({["## ", Docs], NewPos});
-render_element({h3,_,Content},State,Pos,_Ind,D) when Pos =< 2 ->
- {Docs, NewPos} = render_docs(Content,State,Pos,0,D),
+render_element({h3, _, Content}, State, Pos, _Ind, D) when Pos =< 2 ->
+ {Docs, NewPos} = render_docs(Content, State, Pos, 0, D),
trimnlnl({["### ", Docs], NewPos});
-render_element({h4,_,Content},State,Pos,_Ind,D) when Pos =< 2 ->
- {Docs, NewPos} = render_docs(Content,State,Pos,0,D),
+render_element({h4, _, Content}, State, Pos, _Ind, D) when Pos =< 2 ->
+ {Docs, NewPos} = render_docs(Content, State, Pos, 0, D),
trimnlnl({["#### ", Docs], NewPos});
-render_element({h5,_,Content},State,Pos,_Ind,D) when Pos =< 2 ->
- {Docs, NewPos} = render_docs(Content,State,Pos,0,D),
+render_element({h5, _, Content}, State, Pos, _Ind, D) when Pos =< 2 ->
+ {Docs, NewPos} = render_docs(Content, State, Pos, 0, D),
trimnlnl({["##### ", Docs], NewPos});
-render_element({h6,_,Content},State,Pos,_Ind,D) when Pos =< 2 ->
- {Docs, NewPos} = render_docs(Content,State,Pos,0,D),
+render_element({h6, _, Content}, State, Pos, _Ind, D) when Pos =< 2 ->
+ {Docs, NewPos} = render_docs(Content, State, Pos, 0, D),
trimnlnl({["###### ", Docs], NewPos});
-
-render_element({pre,_Attr,_Content} = E,State,Pos,Ind,D) when Pos > Ind ->
+render_element({pre, _Attr, _Content} = E, State, Pos, Ind, D) when Pos > Ind ->
%% We pad `pre` with two newlines if the previous section did not indent the region.
- {Docs,NewPos} = render_element(E,State,0,Ind,D),
- {["\n\n",Docs],NewPos};
-render_element({Elem,_Attr,_Content} = E,State,Pos,Ind,D) when Pos > Ind, ?IS_BLOCK(Elem) ->
- {Docs,NewPos} = render_element(E,State,0,Ind,D),
- {["\n",Docs],NewPos};
-render_element({'div',[{class,What}],Content},State,Pos,Ind,D) ->
- Title = unicode:characters_to_binary([string:titlecase(What),":"]),
- {Header, 0} = render_element({h3,[],[Title]},State,Pos,Ind,D),
- {Docs, 0} = render_element({'div',[],Content},['div'|State], 0, Ind+2, D),
- {[Header,Docs],0};
-render_element({Tag,_,Content},State,Pos,Ind,D) when Tag =:= p; Tag =:= 'div' ->
- trimnlnl(render_docs(Content, [Tag|State], Pos, Ind, D));
-
-render_element(Elem,State,Pos,Ind,D) when Pos < Ind ->
- {Docs,NewPos} = render_element(Elem,State,Ind,Ind,D),
- {[pad(Ind - Pos), Docs],NewPos};
-
-render_element({a,Attr,Content}, State, Pos, Ind,D) ->
+ {Docs, NewPos} = render_element(E, State, 0, Ind, D),
+ {["\n\n", Docs], NewPos};
+render_element({Elem, _Attr, _Content} = E, State, Pos, Ind, D) when Pos > Ind, ?IS_BLOCK(Elem) ->
+ {Docs, NewPos} = render_element(E, State, 0, Ind, D),
+ {["\n", Docs], NewPos};
+render_element({'div', [{class, What}], Content}, State, Pos, Ind, D) ->
+ Title = unicode:characters_to_binary([string:titlecase(What), ":"]),
+ {Header, 0} = render_element({h3, [], [Title]}, State, Pos, Ind, D),
+ {Docs, 0} = render_element({'div', [], Content}, ['div' | State], 0, Ind + 2, D),
+ {[Header, Docs], 0};
+render_element({Tag, _, Content}, State, Pos, Ind, D) when Tag =:= p; Tag =:= 'div' ->
+ trimnlnl(render_docs(Content, [Tag | State], Pos, Ind, D));
+render_element(Elem, State, Pos, Ind, D) when Pos < Ind ->
+ {Docs, NewPos} = render_element(Elem, State, Ind, Ind, D),
+ {[pad(Ind - Pos), Docs], NewPos};
+render_element({a, Attr, Content}, State, Pos, Ind, D) ->
{Docs, NewPos} = render_docs(Content, State, Pos, Ind, D),
Href = proplists:get_value(href, Attr),
IsOTPLink = Href =/= undefined andalso string:find(Href, ":") =/= nomatch,
- case proplists:get_value(rel,Attr) of
+ case proplists:get_value(rel, Attr) of
_ when not IsOTPLink ->
- {Docs, NewPos};
+ {Docs, NewPos};
<<"https://erlang.org/doc/link/seemfa">> ->
- [_App, MFA] = string:split(Href,":"),
- [Mod, FA] = string:split(MFA,"#"),
- [Func, Arity] = string:split(FA,"/"),
- {["[",Docs,"](https://erlang.org/doc/man/",Mod,".html#",Func,"-",Arity,")"],NewPos};
+ [_App, MFA] = string:split(Href, ":"),
+ [Mod, FA] = string:split(MFA, "#"),
+ [Func, Arity] = string:split(FA, "/"),
+ {
+ ["[", Docs, "](https://erlang.org/doc/man/", Mod, ".html#", Func, "-", Arity, ")"],
+ NewPos
+ };
<<"https://erlang.org/doc/link/seetype">> ->
- case string:lexemes(Href,":#/") of
+ case string:lexemes(Href, ":#/") of
[_App, Mod, Type, Arity] ->
- {["[",Docs,"](https://erlang.org/doc/man/",Mod,".html#","type-",Type,"-",Arity,")"],NewPos};
+ {
+ [
+ "[",
+ Docs,
+ "](https://erlang.org/doc/man/",
+ Mod,
+ ".html#",
+ "type-",
+ Type,
+ "-",
+ Arity,
+ ")"
+ ],
+ NewPos
+ };
[_App, Mod, Type] ->
- {["[",Docs,"](https://erlang.org/doc/man/",Mod,".html#","type-",Type,")"],NewPos}
+ {
+ [
+ "[",
+ Docs,
+ "](https://erlang.org/doc/man/",
+ Mod,
+ ".html#",
+ "type-",
+ Type,
+ ")"
+ ],
+ NewPos
+ }
end;
<<"https://erlang.org/doc/link/seeerl">> ->
- [_App, Mod|Anchor] = string:lexemes(Href,":#"),
- {["[",Docs,"](https://erlang.org/doc/man/",Mod,".html#",Anchor,")"],NewPos};
+ [_App, Mod | Anchor] = string:lexemes(Href, ":#"),
+ {["[", Docs, "](https://erlang.org/doc/man/", Mod, ".html#", Anchor, ")"], NewPos};
_ ->
- {Docs,NewPos}
+ {Docs, NewPos}
end;
-
-render_element({code,_,Content},[pre|_] = State,Pos,Ind,D) ->
+render_element({code, _, Content}, [pre | _] = State, Pos, Ind, D) ->
%% When code is within a pre we don't emit any underline
- render_docs(Content, [code|State], Pos, Ind,D);
-render_element({code,_,Content},State,Pos,Ind,D) ->
- {Docs, NewPos} = render_docs(Content, [code|State], Pos, Ind,D),
- {["`",Docs,"`"], NewPos};
-
-render_element({em,Attr,Content},State,Pos,Ind,D) ->
- render_element({i,Attr,Content},State,Pos,Ind,D);
-render_element({i,_,Content},State,Pos,Ind,D) ->
- {Docs, NewPos} = render_docs(Content, [i|State], Pos, Ind,D),
- case lists:member(pre,State) of
+ render_docs(Content, [code | State], Pos, Ind, D);
+render_element({code, _, Content}, State, Pos, Ind, D) ->
+ {Docs, NewPos} = render_docs(Content, [code | State], Pos, Ind, D),
+ {["`", Docs, "`"], NewPos};
+render_element({em, Attr, Content}, State, Pos, Ind, D) ->
+ render_element({i, Attr, Content}, State, Pos, Ind, D);
+render_element({i, _, Content}, State, Pos, Ind, D) ->
+ {Docs, NewPos} = render_docs(Content, [i | State], Pos, Ind, D),
+ case lists:member(pre, State) of
true ->
{[Docs], NewPos};
false ->
- {["*",Docs,"*"], NewPos}
+ {["*", Docs, "*"], NewPos}
end;
-
-render_element({hr,[],[]},_State,Pos,_Ind,_D) ->
- {"---\n",Pos};
-
-render_element({br,[],[]},_State,Pos,_Ind,_D) ->
- {"",Pos};
-
-render_element({strong,Attr,Content},State,Pos,Ind,D) ->
- render_element({b,Attr,Content},State,Pos,Ind,D);
-render_element({b,_,Content},State,Pos,Ind,D) ->
- {Docs, NewPos} = render_docs(Content, State, Pos, Ind,D),
- case lists:member(pre,State) of
+render_element({hr, [], []}, _State, Pos, _Ind, _D) ->
+ {"---\n", Pos};
+render_element({br, [], []}, _State, Pos, _Ind, _D) ->
+ {"", Pos};
+render_element({strong, Attr, Content}, State, Pos, Ind, D) ->
+ render_element({b, Attr, Content}, State, Pos, Ind, D);
+render_element({b, _, Content}, State, Pos, Ind, D) ->
+ {Docs, NewPos} = render_docs(Content, State, Pos, Ind, D),
+ case lists:member(pre, State) of
true ->
{[Docs], NewPos};
false ->
- {["**",Docs,"**"], NewPos}
+ {["**", Docs, "**"], NewPos}
end;
-
-render_element({pre,_,Content},State,Pos,Ind,D) ->
+render_element({pre, _, Content}, State, Pos, Ind, D) ->
%% For pre we make sure to respect the newlines in pre
- {Docs, _} = trimnl(render_docs(Content, [pre|State], Pos, Ind, D)),
- trimnlnl(["```erlang\n",pad(Ind),Docs,pad(Ind),"```"]);
-
-render_element({ul,[{class,<<"types">>}],Content},State,_Pos,Ind,D) ->
- {Docs, _} = render_docs(Content, [types|State], 0, Ind, D),
+ {Docs, _} = trimnl(render_docs(Content, [pre | State], Pos, Ind, D)),
+ trimnlnl(["```erlang\n", pad(Ind), Docs, pad(Ind), "```"]);
+render_element({ul, [{class, <<"types">>}], Content}, State, _Pos, Ind, D) ->
+ {Docs, _} = render_docs(Content, [types | State], 0, Ind, D),
trimnlnl(Docs);
-render_element({li,Attr,Content},[types|_] = State,Pos,Ind,C) ->
+render_element({li, Attr, Content}, [types | _] = State, Pos, Ind, C) ->
Doc =
- case {proplists:get_value(name, Attr),proplists:get_value(class, Attr)} of
- {undefined,Class} when Class =:= undefined; Class =:= <<"type">> ->
+ case {proplists:get_value(name, Attr), proplists:get_value(class, Attr)} of
+ {undefined, Class} when Class =:= undefined; Class =:= <<"type">> ->
%% Inline html for types
- render_docs(Content ++ [<<" ">>],[type|State],Pos,Ind,C);
- {_,<<"description">>} ->
+ render_docs(Content ++ [<<" ">>], [type | State], Pos, Ind, C);
+ {_, <<"description">>} ->
%% Inline html for type descriptions
- render_docs(Content ++ [<<" ">>],[type|State],Pos,Ind+2,C);
- {Name,_} ->
+ render_docs(Content ++ [<<" ">>], [type | State], Pos, Ind + 2, C);
+ {Name, _} ->
%% Try to render from type metadata
- case render_type_signature(binary_to_atom(Name),C) of
+ case render_type_signature(binary_to_atom(Name), C) of
undefined when Content =:= [] ->
%% Failed and no content, emit place-holder
- {["```erlang\n-type ",Name,"() :: term().```"],0};
+ {["```erlang\n-type ", Name, "() :: term().```"], 0};
undefined ->
%% Failed with metadata, render the content
- render_docs(Content ++ [<<" ">>],[type|State],Pos,Ind,C);
+ render_docs(Content ++ [<<" ">>], [type | State], Pos, Ind, C);
Type ->
%% Emit the erl_pp typespec
- {["```erlang\n",Type,"```"],0}
+ {["```erlang\n", Type, "```"], 0}
end
end,
trimnl(Doc);
-render_element({ul,[],Content},State,Pos,Ind,D) ->
- trimnlnl(render_docs(Content, [ul|State], Pos, Ind,D));
-render_element({ol,[],Content},State,Pos,Ind,D) ->
- trimnlnl(render_docs(Content, [ol|State], Pos, Ind,D));
-render_element({li,[],Content},[ul | _] = State, Pos, Ind,D) ->
- {Docs, _NewPos} = render_docs(Content, [li | State], Pos + 2,Ind + 2, D),
- trimnl(["* ",Docs]);
-render_element({li,[],Content},[ol | _] = State, Pos, Ind,D) ->
- {Docs, _NewPos} = render_docs(Content, [li | State], Pos + 2,Ind + 2, D),
+render_element({ul, [], Content}, State, Pos, Ind, D) ->
+ trimnlnl(render_docs(Content, [ul | State], Pos, Ind, D));
+render_element({ol, [], Content}, State, Pos, Ind, D) ->
+ trimnlnl(render_docs(Content, [ol | State], Pos, Ind, D));
+render_element({li, [], Content}, [ul | _] = State, Pos, Ind, D) ->
+ {Docs, _NewPos} = render_docs(Content, [li | State], Pos + 2, Ind + 2, D),
+ trimnl(["* ", Docs]);
+render_element({li, [], Content}, [ol | _] = State, Pos, Ind, D) ->
+ {Docs, _NewPos} = render_docs(Content, [li | State], Pos + 2, Ind + 2, D),
trimnl(["1. ", Docs]);
-
-render_element({dl,_,Content},State,Pos,Ind,D) ->
- trimnlnl(render_docs(Content, [dl|State], Pos, Ind,D));
-render_element({dt,_,Content},[dl | _] = State,Pos,Ind,D) ->
- {Docs, NewPos} = render_docs([{b,[],Content}],
- [li | State], Pos + 2, Ind + 2, D),
+render_element({dl, _, Content}, State, Pos, Ind, D) ->
+ trimnlnl(render_docs(Content, [dl | State], Pos, Ind, D));
+render_element({dt, _, Content}, [dl | _] = State, Pos, Ind, D) ->
+ {Docs, NewPos} = render_docs(
+ [{b, [], Content}],
+ [li | State],
+ Pos + 2,
+ Ind + 2,
+ D
+ ),
trimnl({["* ", string:trim(Docs, trailing, "\n"), " "], NewPos});
-render_element({dd,_,Content},[dl | _] = State,Pos,Ind,D) ->
- {Docs, _NewPos} = render_docs(Content, [li | State], Pos+2, Ind + 2, D),
+render_element({dd, _, Content}, [dl | _] = State, Pos, Ind, D) ->
+ {Docs, _NewPos} = render_docs(Content, [li | State], Pos + 2, Ind + 2, D),
trimnlnl([pad(2 + Ind - Pos), Docs]);
-
-render_element(B, State, Pos, Ind,_D) when is_binary(B) ->
- Pre = string:replace(B,"\n",[nlpad(Ind)],all),
+render_element(B, State, Pos, Ind, _D) when is_binary(B) ->
+ Pre = string:replace(B, "\n", [nlpad(Ind)], all),
EscapeChars = [
- "\\",
- "`",
- "*",
- "_",
- "{",
- "}",
- "[",
- "]",
- "<",
- ">",
- "(",
- ")",
- "#",
- "+",
- "-",
- ".",
- "!",
- "|"
- ],
+ "\\",
+ "`",
+ "*",
+ "_",
+ "{",
+ "}",
+ "[",
+ "]",
+ "<",
+ ">",
+ "(",
+ ")",
+ "#",
+ "+",
+ "-",
+ ".",
+ "!",
+ "|"
+ ],
Str =
case State of
- [pre|_] -> Pre;
- [code|_] -> Pre;
+ [pre | _] ->
+ Pre;
+ [code | _] ->
+ Pre;
_ ->
- re:replace(Pre, ["(",lists:join($|,[["\\",C] || C <- EscapeChars]),")"],
- "\\\\\\1", [global])
+ re:replace(
+ Pre,
+ ["(", lists:join($|, [["\\", C] || C <- EscapeChars]), ")"],
+ "\\\\\\1",
+ [global]
+ )
end,
{Str, Pos + lastline(Str)};
-
-render_element({Tag,Attr,Content}, State, Pos, Ind,D) ->
- case lists:member(Tag,?ALL_ELEMENTS) of
+render_element({Tag, Attr, Content}, State, Pos, Ind, D) ->
+ case lists:member(Tag, ?ALL_ELEMENTS) of
true ->
- throw({unhandled_element,Tag,Attr,Content});
+ throw({unhandled_element, Tag, Attr, Content});
false ->
%% We ignore tags that we do not care about
ok
end,
- render_docs(Content, State, Pos, Ind,D).
+ render_docs(Content, State, Pos, Ind, D).
-spec render_type_signature(atom(), #config{}) -> 'undefined' | unicode:chardata().
-render_type_signature(Name, #config{ docs = #docs_v1{ metadata = #{ types := AllTypes }}}) ->
- case [Type || Type = {TName,_} <- maps:keys(AllTypes), TName =:= Name] of
+render_type_signature(Name, #config{docs = #docs_v1{metadata = #{types := AllTypes}}}) ->
+ case [Type || Type = {TName, _} <- maps:keys(AllTypes), TName =:= Name] of
[] ->
undefined;
Types ->
@@ -624,36 +815,39 @@ render_type_signature(Name, #config{ docs = #docs_v1{ metadata = #{ types := All
%% Pad N spaces (and possibly pre-prend newline), disabling any ansi formatting while doing so.
-spec pad(non_neg_integer()) -> unicode:chardata().
pad(N) ->
- pad(N,"").
+ pad(N, "").
-spec nlpad(non_neg_integer()) -> unicode:chardata().
nlpad(N) ->
- pad(N,"\n").
+ pad(N, "\n").
-spec pad(non_neg_integer(), unicode:chardata()) -> unicode:chardata().
pad(N, Extra) ->
- Pad = lists:duplicate(N,[$ ]),
+ Pad = lists:duplicate(N, [$\s]),
[Extra, Pad].
-spec lastline(unicode:chardata()) -> non_neg_integer().
%% Look for the length of the last line of a string
lastline(Str) ->
- LastStr = case string:find(Str,"\n",trailing) of
- nomatch ->
- Str;
- Match ->
- tl(string:next_codepoint(Match))
- end,
+ LastStr =
+ case string:find(Str, "\n", trailing) of
+ nomatch ->
+ Str;
+ Match ->
+ tl(string:next_codepoint(Match))
+ end,
string:length(LastStr).
%% These functions make sure that we trim extra newlines added
%% by the renderer. For example if we do
%% that would add 4 \n at after the last . This is trimmed
%% here to only be 2 \n
--spec trimnlnl(unicode:chardata() | {unicode:chardata(), non_neg_integer()}) -> {unicode:chardata(), 0}.
+-spec trimnlnl(unicode:chardata() | {unicode:chardata(), non_neg_integer()}) ->
+ {unicode:chardata(), 0}.
trimnlnl({Chars, _Pos}) ->
nl(nl(string:trim(Chars, trailing, "\n")));
trimnlnl(Chars) ->
nl(nl(string:trim(Chars, trailing, "\n"))).
--spec trimnl(unicode:chardata() | {unicode:chardata(), non_neg_integer()}) -> {unicode:chardata(), 0}.
+-spec trimnl(unicode:chardata() | {unicode:chardata(), non_neg_integer()}) ->
+ {unicode:chardata(), 0}.
trimnl({Chars, _Pos}) ->
nl(string:trim(Chars, trailing, "\n"));
trimnl(Chars) ->
@@ -662,7 +856,7 @@ trimnl(Chars) ->
nl({Chars, _Pos}) ->
nl(Chars);
nl(Chars) ->
- {[Chars,"\n"],0}.
+ {[Chars, "\n"], 0}.
-endif.
-endif.
diff --git a/apps/els_lsp/src/els_elvis_diagnostics.erl b/apps/els_lsp/src/els_elvis_diagnostics.erl
index 8737fc14f..fcfd0165e 100644
--- a/apps/els_lsp/src/els_elvis_diagnostics.erl
+++ b/apps/els_lsp/src/els_elvis_diagnostics.erl
@@ -11,10 +11,11 @@
%%==============================================================================
%% Exports
%%==============================================================================
--export([ is_default/0
- , run/1
- , source/0
- ]).
+-export([
+ is_default/0,
+ run/1,
+ source/0
+]).
%%==============================================================================
%% Includes
@@ -28,55 +29,61 @@
-spec is_default() -> boolean().
is_default() ->
- true.
+ true.
-spec run(uri()) -> [els_diagnostics:diagnostic()].
run(Uri) ->
- case els_utils:project_relative(Uri) of
- {error, not_relative} -> [];
- RelFile ->
- %% Note: elvis_core:rock_this requires a file path relative to the
- %% project root, formatted as a string.
- try
- Filename = get_elvis_config_path(),
- Config = elvis_config:from_file(Filename),
- elvis_core:rock_this(RelFile, Config)
- of
- ok -> [];
- {fail, Problems} -> lists:flatmap(fun format_diagnostics/1, Problems)
- catch Err ->
- ?LOG_WARNING("Elvis error.[Err=~p] ", [Err]),
- []
- end
+ case els_utils:project_relative(Uri) of
+ {error, not_relative} ->
+ [];
+ RelFile ->
+ %% Note: elvis_core:rock_this requires a file path relative to the
+ %% project root, formatted as a string.
+ try
+ Filename = get_elvis_config_path(),
+ Config = elvis_config:from_file(Filename),
+ elvis_core:rock_this(RelFile, Config)
+ of
+ ok -> [];
+ {fail, Problems} -> lists:flatmap(fun format_diagnostics/1, Problems)
+ catch
+ Err ->
+ ?LOG_WARNING("Elvis error.[Err=~p] ", [Err]),
+ []
+ end
end.
-spec source() -> binary().
source() ->
- <<"Elvis">>.
+ <<"Elvis">>.
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec format_diagnostics(any()) -> [map()].
format_diagnostics(#{file := _File, rules := Rules}) ->
- R = format_rules(Rules),
- lists:flatten(R).
+ R = format_rules(Rules),
+ lists:flatten(R).
%%% This section is based directly on elvis_result:print_rules
-spec format_rules([any()]) -> [[map()]].
format_rules([]) ->
- [];
+ [];
format_rules([#{error_msg := Msg, info := Info} | Items]) ->
- [diagnostic(<<"Config Error">>, Msg, 1, Info, ?DIAGNOSTIC_ERROR) |
- format_rules(Items)];
+ [
+ diagnostic(<<"Config Error">>, Msg, 1, Info, ?DIAGNOSTIC_ERROR)
+ | format_rules(Items)
+ ];
format_rules([#{warn_msg := Msg, info := Info} | Items]) ->
- [diagnostic(<<"Config Warning">>, Msg, 1, Info, ?DIAGNOSTIC_WARNING) |
- format_rules(Items)];
+ [
+ diagnostic(<<"Config Warning">>, Msg, 1, Info, ?DIAGNOSTIC_WARNING)
+ | format_rules(Items)
+ ];
format_rules([#{items := []} | Items]) ->
- format_rules(Items);
+ format_rules(Items);
format_rules([#{items := Items, name := Name} | EItems]) ->
- ItemDiags = format_item(Name, Items),
- [lists:flatten(ItemDiags) | format_rules(EItems)].
+ ItemDiags = format_item(Name, Items),
+ [lists:flatten(ItemDiags) | format_rules(EItems)].
%% Item
-spec format_item(any(), [any()]) -> [[map()]].
@@ -88,35 +95,45 @@ format_item(_Name, []) ->
%%% End of section based directly on elvis_result:print_rules
--spec diagnostic(any(), any(), integer(), [any()],
- els_diagnostics:severity()) -> [map()].
+-spec diagnostic(
+ any(),
+ any(),
+ integer(),
+ [any()],
+ els_diagnostics:severity()
+) -> [map()].
diagnostic(Name, Msg, Ln, Info, Severity) ->
- %% Avoid negative line numbers
- DiagLine = make_protocol_line(Ln),
- FMsg = io_lib:format(Msg, Info),
- Range = els_protocol:range(#{ from => {DiagLine, 1}
- , to => {DiagLine + 1, 1}}),
- Message = els_utils:to_binary(FMsg),
- [#{ range => Range
- , severity => Severity
- , code => Name
- , source => source()
- , message => Message
- , relatedInformation => []
- }].
+ %% Avoid negative line numbers
+ DiagLine = make_protocol_line(Ln),
+ FMsg = io_lib:format(Msg, Info),
+ Range = els_protocol:range(#{
+ from => {DiagLine, 1},
+ to => {DiagLine + 1, 1}
+ }),
+ Message = els_utils:to_binary(FMsg),
+ [
+ #{
+ range => Range,
+ severity => Severity,
+ code => Name,
+ source => source(),
+ message => Message,
+ relatedInformation => []
+ }
+ ].
-spec make_protocol_line(Line :: number()) -> number().
make_protocol_line(Line) when Line =< 0 ->
- 1;
+ 1;
make_protocol_line(Line) ->
- Line.
+ Line.
-spec get_elvis_config_path() -> file:filename_all().
get_elvis_config_path() ->
- case els_config:get(elvis_config_path) of
- undefined ->
- RootPath = els_uri:path(els_config:get(root_uri)),
- filename:join([RootPath, "elvis.config"]);
- FilePath ->
- FilePath
- end.
+ case els_config:get(elvis_config_path) of
+ undefined ->
+ RootPath = els_uri:path(els_config:get(root_uri)),
+ filename:join([RootPath, "elvis.config"]);
+ FilePath ->
+ FilePath
+ end.
diff --git a/apps/els_lsp/src/els_erlfmt_ast.erl b/apps/els_lsp/src/els_erlfmt_ast.erl
index 59418e130..130f3b404 100644
--- a/apps/els_lsp/src/els_erlfmt_ast.erl
+++ b/apps/els_lsp/src/els_erlfmt_ast.erl
@@ -38,7 +38,7 @@
%% Hence, the erl_syntax:set_pos() function is used for all annotations.
erlfmt_to_st(Node) ->
- Context = get('$erlfmt_ast_context$'),
+ Context = get('$erlfmt_ast_context$'),
case Node of
%% ---------------------------------------------------------------------
%% The following cases can be easily rewritten without losing information
@@ -94,7 +94,7 @@ erlfmt_to_st(Node) ->
_ ->
erlfmt_to_st(F)
end
- || F <- Fields
+ || F <- Fields
],
Tuple1 = update_tree_with_meta(erl_syntax:tuple(Fields1), TPos),
update_tree_with_meta(
@@ -110,158 +110,206 @@ erlfmt_to_st(Node) ->
%% Representation for types is in general the same as for
%% corresponding values. The `type` node is not used at all. This
%% means new binary operators `|`, `::`, and `..` inside types.
- {attribute, Pos, {atom, _, Tag} = Name, [{op, OPos, '::', Type, Definition}]}
- when Tag =:= type; Tag =:= opaque ->
- put('$erlfmt_ast_context$', type),
- {TypeName, Args} =
- case Type of
- {call, _CPos, TypeName0, Args0} ->
- {TypeName0, Args0};
- {macro_call, _, _, _} ->
- %% Note: in the following example the arguments belong to the macro
- %% so we set empty type args.
- %% `-type ?M(A) :: A.'
- %% The erlang preprocessor also prefers the M/1 macro if both M/0
- %% and M/1 are defined, but it also allows only M/0. Unfortunately
- %% erlang_ls doesn't know what macros are defined.
- {Type, []};
- _ ->
- %% whatever stands at the left side of '::', let's keep it.
- %% erlfmt_parser allows atoms and vars too
- {Type, []}
- end,
- Tree =
- update_tree_with_meta(
- erl_syntax:attribute(erlfmt_to_st(Name),
- [update_tree_with_meta(
- erl_syntax:tuple([erlfmt_to_st(TypeName),
- erlfmt_to_st(Definition),
- erl_syntax:list([erlfmt_to_st(A) || A <- Args])]),
- OPos)]),
- Pos),
- erase('$erlfmt_ast_context$'),
- Tree;
- {attribute, Pos, {atom, _, Tag} = Name, [Def]} when Tag =:= type; Tag =:= opaque ->
- %% an incomplete attribute, where `::` operator and the definition missing
- %% eg "-type t()."
- put('$erlfmt_ast_context$', type),
- OPos = element(2, Def),
- {TypeName, Args} =
- case Def of
- {call, _CPos, TypeName0, Args0} ->
- {TypeName0, Args0};
- _ ->
- {Def, []}
- end,
- %% Set definition as an empty tuple for which els_parser generates no POIs
- EmptyDef = erl_syntax:tuple([]),
- Tree =
- update_tree_with_meta(
- erl_syntax:attribute(erlfmt_to_st(Name),
- [update_tree_with_meta(
- erl_syntax:tuple([erlfmt_to_st(TypeName),
- EmptyDef,
- erl_syntax:list([erlfmt_to_st(A) || A <- Args])]),
- OPos)]),
- Pos),
- erase('$erlfmt_ast_context$'),
- Tree;
- {attribute, Pos, {atom, _, RawName} = Name, Args} when RawName =:= callback;
- RawName =:= spec ->
- put('$erlfmt_ast_context$', type),
- [{spec, SPos, FName, Clauses}] = Args,
- {spec_clause, _, {args, _, ClauseArgs}, _, _} = hd(Clauses),
- Arity = length(ClauseArgs),
- Tree =
- update_tree_with_meta(
- erl_syntax:attribute(erlfmt_to_st(Name),
- [update_tree_with_meta(
- erl_syntax:tuple([erl_syntax:tuple([erlfmt_to_st(FName), erl_syntax:integer(Arity)]),
- erl_syntax:list([erlfmt_to_st(C) || C <- Clauses])]),
- SPos)]),
- Pos),
- erase('$erlfmt_ast_context$'),
- Tree;
- {spec_clause, Pos, {args, _HeadMeta, Args}, ReturnType, empty} ->
- update_tree_with_meta(
- erl_syntax_function_type([erlfmt_to_st(A) || A <- Args],
- erlfmt_to_st(ReturnType)),
- Pos);
- {spec_clause, Pos, {args, _HeadMeta, Args}, ReturnType, GuardOr} ->
- FunctionType =
- update_tree_with_meta(
- erl_syntax_function_type([erlfmt_to_st(A) || A <- Args],
- erlfmt_to_st(ReturnType)),
- Pos),
- FunctionConstraint = erlfmt_guard_to_st(GuardOr),
-
- update_tree_with_meta(
- erl_syntax:constrained_function_type(FunctionType, [FunctionConstraint]),
- Pos);
- {op, Pos, '|', A, B} when Context =:= type ->
- update_tree_with_meta(
- erl_syntax:type_union([erlfmt_to_st(A),
- erlfmt_to_st(B)]),
- Pos);
- {op, Pos, '..', A, B} when Context =:= type ->
- %% erlfmt_to_st_1({type, Pos, range, [A, B]}),
- update_tree_with_meta(
- erl_syntax:integer_range_type(erlfmt_to_st(A),
- erlfmt_to_st(B)),
- Pos);
- %%{op, Pos, '::', A, B} when Context =:= type ->
- %% update_tree_with_meta(
- %% erl_syntax:annotated_type(erlfmt_to_st(A),
- %% erlfmt_to_st(B)),
- %% Pos);
- {record, Pos, Name, Fields} when Context =:= type ->
- %% The record name is represented as node instead of a raw atom
- %% and typed record fields are represented as '::' ops
- Fields1 = [
- case F of
- {op, FPos, '::', B, T} ->
- B1 = erlfmt_to_st(B),
- T1 = erlfmt_to_st(T),
- update_tree_with_meta(
- erl_syntax:record_type_field(B1, T1),
- FPos
+ {attribute, Pos, {atom, _, Tag} = Name, [{op, OPos, '::', Type, Definition}]} when
+ Tag =:= type; Tag =:= opaque
+ ->
+ put('$erlfmt_ast_context$', type),
+ {TypeName, Args} =
+ case Type of
+ {call, _CPos, TypeName0, Args0} ->
+ {TypeName0, Args0};
+ {macro_call, _, _, _} ->
+ %% Note: in the following example the arguments belong to the macro
+ %% so we set empty type args.
+ %% `-type ?M(A) :: A.'
+ %% The erlang preprocessor also prefers the M/1 macro if both M/0
+ %% and M/1 are defined, but it also allows only M/0. Unfortunately
+ %% erlang_ls doesn't know what macros are defined.
+ {Type, []};
+ _ ->
+ %% whatever stands at the left side of '::', let's keep it.
+ %% erlfmt_parser allows atoms and vars too
+ {Type, []}
+ end,
+ Tree =
+ update_tree_with_meta(
+ erl_syntax:attribute(
+ erlfmt_to_st(Name),
+ [
+ update_tree_with_meta(
+ erl_syntax:tuple([
+ erlfmt_to_st(TypeName),
+ erlfmt_to_st(Definition),
+ erl_syntax:list([erlfmt_to_st(A) || A <- Args])
+ ]),
+ OPos
+ )
+ ]
+ ),
+ Pos
+ ),
+ erase('$erlfmt_ast_context$'),
+ Tree;
+ {attribute, Pos, {atom, _, Tag} = Name, [Def]} when Tag =:= type; Tag =:= opaque ->
+ %% an incomplete attribute, where `::` operator and the definition missing
+ %% eg "-type t()."
+ put('$erlfmt_ast_context$', type),
+ OPos = element(2, Def),
+ {TypeName, Args} =
+ case Def of
+ {call, _CPos, TypeName0, Args0} ->
+ {TypeName0, Args0};
+ _ ->
+ {Def, []}
+ end,
+ %% Set definition as an empty tuple for which els_parser generates no POIs
+ EmptyDef = erl_syntax:tuple([]),
+ Tree =
+ update_tree_with_meta(
+ erl_syntax:attribute(
+ erlfmt_to_st(Name),
+ [
+ update_tree_with_meta(
+ erl_syntax:tuple([
+ erlfmt_to_st(TypeName),
+ EmptyDef,
+ erl_syntax:list([erlfmt_to_st(A) || A <- Args])
+ ]),
+ OPos
+ )
+ ]
+ ),
+ Pos
+ ),
+ erase('$erlfmt_ast_context$'),
+ Tree;
+ {attribute, Pos, {atom, _, RawName} = Name, Args} when
+ RawName =:= callback;
+ RawName =:= spec
+ ->
+ put('$erlfmt_ast_context$', type),
+ [{spec, SPos, FName, Clauses}] = Args,
+ {spec_clause, _, {args, _, ClauseArgs}, _, _} = hd(Clauses),
+ Arity = length(ClauseArgs),
+ Tree =
+ update_tree_with_meta(
+ erl_syntax:attribute(
+ erlfmt_to_st(Name),
+ [
+ update_tree_with_meta(
+ erl_syntax:tuple([
+ erl_syntax:tuple([
+ erlfmt_to_st(FName), erl_syntax:integer(Arity)
+ ]),
+ erl_syntax:list([erlfmt_to_st(C) || C <- Clauses])
+ ]),
+ SPos
+ )
+ ]
+ ),
+ Pos
+ ),
+ erase('$erlfmt_ast_context$'),
+ Tree;
+ {spec_clause, Pos, {args, _HeadMeta, Args}, ReturnType, empty} ->
+ update_tree_with_meta(
+ erl_syntax_function_type(
+ [erlfmt_to_st(A) || A <- Args],
+ erlfmt_to_st(ReturnType)
+ ),
+ Pos
+ );
+ {spec_clause, Pos, {args, _HeadMeta, Args}, ReturnType, GuardOr} ->
+ FunctionType =
+ update_tree_with_meta(
+ erl_syntax_function_type(
+ [erlfmt_to_st(A) || A <- Args],
+ erlfmt_to_st(ReturnType)
+ ),
+ Pos
+ ),
+ FunctionConstraint = erlfmt_guard_to_st(GuardOr),
+
+ update_tree_with_meta(
+ erl_syntax:constrained_function_type(FunctionType, [FunctionConstraint]),
+ Pos
+ );
+ {op, Pos, '|', A, B} when Context =:= type ->
+ update_tree_with_meta(
+ erl_syntax:type_union([
+ erlfmt_to_st(A),
+ erlfmt_to_st(B)
+ ]),
+ Pos
+ );
+ {op, Pos, '..', A, B} when Context =:= type ->
+ %% erlfmt_to_st_1({type, Pos, range, [A, B]}),
+ update_tree_with_meta(
+ erl_syntax:integer_range_type(
+ erlfmt_to_st(A),
+ erlfmt_to_st(B)
+ ),
+ Pos
+ );
+ %%{op, Pos, '::', A, B} when Context =:= type ->
+ %% update_tree_with_meta(
+ %% erl_syntax:annotated_type(erlfmt_to_st(A),
+ %% erlfmt_to_st(B)),
+ %% Pos);
+ {record, Pos, Name, Fields} when Context =:= type ->
+ %% The record name is represented as node instead of a raw atom
+ %% and typed record fields are represented as '::' ops
+ Fields1 = [
+ case F of
+ {op, FPos, '::', B, T} ->
+ B1 = erlfmt_to_st(B),
+ T1 = erlfmt_to_st(T),
+ update_tree_with_meta(
+ erl_syntax:record_type_field(B1, T1),
+ FPos
);
- _ ->
- erlfmt_to_st(F)
- end
- || F <- Fields
- ],
-
- update_tree_with_meta(
- erl_syntax:record_type(
- erlfmt_to_st(Name),
- Fields1
- ),
- Pos
- );
- {call, Pos, {remote, _, _, _} = Name, Args} when Context =:= type ->
- update_tree_with_meta(
- erl_syntax:type_application(erlfmt_to_st(Name),
- [erlfmt_to_st(A) || A <- Args]),
- Pos);
- {call, Pos, Name, Args} when Context =:= type ->
- TypeTag =
- case Name of
- {atom, _, NameAtom} ->
- Arity = length(Args),
- case erl_internal:is_type(NameAtom, Arity) of
- true ->
- type_application;
- false ->
- user_type_application
- end;
- _ ->
- user_type_application
- end,
- update_tree_with_meta(
- erl_syntax:TypeTag(erlfmt_to_st(Name),
- [erlfmt_to_st(A) || A <- Args]),
- Pos);
+ _ ->
+ erlfmt_to_st(F)
+ end
+ || F <- Fields
+ ],
+
+ update_tree_with_meta(
+ erl_syntax:record_type(
+ erlfmt_to_st(Name),
+ Fields1
+ ),
+ Pos
+ );
+ {call, Pos, {remote, _, _, _} = Name, Args} when Context =:= type ->
+ update_tree_with_meta(
+ erl_syntax:type_application(
+ erlfmt_to_st(Name),
+ [erlfmt_to_st(A) || A <- Args]
+ ),
+ Pos
+ );
+ {call, Pos, Name, Args} when Context =:= type ->
+ TypeTag =
+ case Name of
+ {atom, _, NameAtom} ->
+ Arity = length(Args),
+ case erl_internal:is_type(NameAtom, Arity) of
+ true ->
+ type_application;
+ false ->
+ user_type_application
+ end;
+ _ ->
+ user_type_application
+ end,
+ update_tree_with_meta(
+ erl_syntax:TypeTag(
+ erlfmt_to_st(Name),
+ [erlfmt_to_st(A) || A <- Args]
+ ),
+ Pos
+ );
{attribute, Pos, {atom, _, define} = Tag, [Name, empty]} ->
%% the erlfmt parser allows defines with empty bodies (with the
%% closing parens following after the comma); we must turn the
@@ -322,18 +370,20 @@ erlfmt_to_st(Node) ->
{'try', Pos, {body, _, _} = Body, Clauses, Handlers, After} ->
%% TODO: preserving annotations on bodies and clause groups
Body1 = [erlfmt_to_st(Body)],
- Clauses1 = case Clauses of
- {clauses, _, CList} ->
- [erlfmt_clause_to_st(C) || C <- CList];
- none ->
- []
- end,
- Handlers1 = case Handlers of
- {clauses, _, HList} ->
- [erlfmt_clause_to_st(C) || C <- HList];
- none ->
- []
- end,
+ Clauses1 =
+ case Clauses of
+ {clauses, _, CList} ->
+ [erlfmt_clause_to_st(C) || C <- CList];
+ none ->
+ []
+ end,
+ Handlers1 =
+ case Handlers of
+ {clauses, _, HList} ->
+ [erlfmt_clause_to_st(C) || C <- HList];
+ none ->
+ []
+ end,
After1 = [erlfmt_to_st(E) || E <- After],
update_tree_with_meta(
erl_syntax:try_expr(
@@ -476,38 +526,45 @@ erlfmt_to_st(Node) ->
FPos
),
update_tree_with_meta(erl_syntax:implicit_fun(FName), Pos);
- {'fun', Pos, type} ->
- update_tree_with_meta(erl_syntax:fun_type(), Pos);
- {'fun', Pos, {type, _, {args, _, Args}, Res}} ->
- update_tree_with_meta(
- erl_syntax_function_type(
- [erlfmt_to_st(A) || A <- Args],
- erlfmt_to_st(Res)),
- Pos);
- {'bin', Pos, Elements} when Context =:= type ->
- %% Note: we loose a lot of Annotation info here
- %% Note2: erl_parse assigns the line number (with no column) to the dummy zeros
- {M, N} =
- case Elements of
- [{bin_element, _, {var, _, '_'}, {bin_size, _, {var, _, '_'}, NNode}, default}] ->
- {{integer, dummy_anno(), 0}, NNode};
- [{bin_element, _, {var, _, '_'}, MNode, default}] ->
- {MNode, {integer, dummy_anno(), 0}};
- [{bin_element, _, {var, _, '_'}, MNode, default},
- {bin_element, _, {var, _, '_'}, {bin_size, _, {var, _, '_'}, NNode}, default}] ->
- {MNode, NNode};
- [] ->
- {{integer, dummy_anno(), 0}, {integer, dummy_anno(), 0}};
- _ ->
- %% No idea what this is - what ST should we create?
- %% maybe just a binary(), or an empty text node
- {{integer, dummy_anno(), 0}, {integer, dummy_anno(), 1}}
- end,
- update_tree_with_meta(
- erl_syntax:bitstring_type(
- erlfmt_to_st(M),
- erlfmt_to_st(N)),
- Pos);
+ {'fun', Pos, type} ->
+ update_tree_with_meta(erl_syntax:fun_type(), Pos);
+ {'fun', Pos, {type, _, {args, _, Args}, Res}} ->
+ update_tree_with_meta(
+ erl_syntax_function_type(
+ [erlfmt_to_st(A) || A <- Args],
+ erlfmt_to_st(Res)
+ ),
+ Pos
+ );
+ {'bin', Pos, Elements} when Context =:= type ->
+ %% Note: we loose a lot of Annotation info here
+ %% Note2: erl_parse assigns the line number (with no column) to the dummy zeros
+ {M, N} =
+ case Elements of
+ [{bin_element, _, {var, _, '_'}, {bin_size, _, {var, _, '_'}, NNode}, default}] ->
+ {{integer, dummy_anno(), 0}, NNode};
+ [{bin_element, _, {var, _, '_'}, MNode, default}] ->
+ {MNode, {integer, dummy_anno(), 0}};
+ [
+ {bin_element, _, {var, _, '_'}, MNode, default},
+ {bin_element, _, {var, _, '_'}, {bin_size, _, {var, _, '_'}, NNode},
+ default}
+ ] ->
+ {MNode, NNode};
+ [] ->
+ {{integer, dummy_anno(), 0}, {integer, dummy_anno(), 0}};
+ _ ->
+ %% No idea what this is - what ST should we create?
+ %% maybe just a binary(), or an empty text node
+ {{integer, dummy_anno(), 0}, {integer, dummy_anno(), 1}}
+ end,
+ update_tree_with_meta(
+ erl_syntax:bitstring_type(
+ erlfmt_to_st(M),
+ erlfmt_to_st(N)
+ ),
+ Pos
+ );
%% Bit type definitions inside binaries are represented as full nodes
%% instead of raw atoms and integers. The unit notation `unit:Int` is
%% represented with a `{remote, Anno, {atom, Anno, unit}, Int}` node.
@@ -540,24 +597,27 @@ erlfmt_to_st(Node) ->
),
Pos
);
- {'receive', Pos, {clauses, _PosClauses, ClauseList}} ->
- update_tree_with_meta(
- erl_syntax:receive_expr([erlfmt_to_st(C) || C <- ClauseList]),
- Pos);
- {'receive', Pos, Clauses, {after_clause, _PosAfter, Timeout, Action}} ->
- Clauses1 = case Clauses of
- empty ->
- [];
- {clauses, _PosClauses, ClauseList} ->
- [erlfmt_to_st(C) || C <- ClauseList]
- end,
- update_tree_with_meta(
- erl_syntax:receive_expr(
- Clauses1,
- erlfmt_to_st(Timeout),
- [erlfmt_to_st(A) || A <- Action]),
- Pos);
-
+ {'receive', Pos, {clauses, _PosClauses, ClauseList}} ->
+ update_tree_with_meta(
+ erl_syntax:receive_expr([erlfmt_to_st(C) || C <- ClauseList]),
+ Pos
+ );
+ {'receive', Pos, Clauses, {after_clause, _PosAfter, Timeout, Action}} ->
+ Clauses1 =
+ case Clauses of
+ empty ->
+ [];
+ {clauses, _PosClauses, ClauseList} ->
+ [erlfmt_to_st(C) || C <- ClauseList]
+ end,
+ update_tree_with_meta(
+ erl_syntax:receive_expr(
+ Clauses1,
+ erlfmt_to_st(Timeout),
+ [erlfmt_to_st(A) || A <- Action]
+ ),
+ Pos
+ );
%% ---------------------------------------------------------------------
%% The remaining cases have been added by erlfmt and need special handling
%% (many are represented as magically-tagged tuples for now)
@@ -633,11 +693,11 @@ erlfmt_to_st(Node) ->
%% So first replace the Meta from Node with proper erl_syntax pos+annotation to
%% make dialyzer happy.
-spec erlfmt_to_st_1(erlfmt() | syntax_tools()) -> syntax_tools().
-erlfmt_to_st_1(Node) when is_map(element(2, Node))->
- Node2 = convert_meta_to_anno(Node),
- erlfmt_to_st_2(Node2);
+erlfmt_to_st_1(Node) when is_map(element(2, Node)) ->
+ Node2 = convert_meta_to_anno(Node),
+ erlfmt_to_st_2(Node2);
erlfmt_to_st_1(Node) ->
- erlfmt_to_st_2(Node).
+ erlfmt_to_st_2(Node).
-spec erlfmt_to_st_2(syntax_tools()) -> syntax_tools().
erlfmt_to_st_2(Node) ->
@@ -656,9 +716,9 @@ erlfmt_subtrees_to_st(Groups) ->
[
[
erlfmt_to_st(Subtree)
- || Subtree <- Group
+ || Subtree <- Group
]
- || Group <- Groups
+ || Group <- Groups
].
-spec get_function_name(maybe_improper_list()) -> any().
@@ -707,7 +767,7 @@ erlfmt_clause_to_st(Other) ->
%% might be a macro call
erlfmt_to_st(Other).
--spec erlfmt_clause_to_st(_,[any()],_,[any()]) -> any().
+-spec erlfmt_clause_to_st(_, [any()], _, [any()]) -> any().
erlfmt_clause_to_st(Pos, Patterns, Guard, Body) ->
Groups = [
Patterns,
@@ -727,7 +787,7 @@ erlfmt_guard_to_st({guard_or, Pos, List}) ->
update_tree_with_meta(
erl_syntax:disjunction([
erlfmt_guard_to_st(E)
- || E <- List
+ || E <- List
]),
Pos
);
@@ -735,7 +795,7 @@ erlfmt_guard_to_st({guard_and, Pos, List}) ->
update_tree_with_meta(
erl_syntax:conjunction([
erlfmt_guard_to_st(E)
- || E <- List
+ || E <- List
]),
Pos
);
@@ -779,7 +839,7 @@ fold_arity_qualifier(Node) ->
-spec dummy_anno() -> erl_anno:anno().
dummy_anno() ->
- erl_anno:set_generated(true, erl_anno:new({0, 1})).
+ erl_anno:set_generated(true, erl_anno:new({0, 1})).
%% erlfmt ast utilities
@@ -795,43 +855,43 @@ set_anno(Node, Loc) ->
%% erl_syntax:function_type/2 has wrong spec before OTP 24
-spec erl_syntax_function_type('any_arity' | [syntax_tools()], syntax_tools()) -> syntax_tools().
erl_syntax_function_type(Arguments, Return) ->
- apply(erl_syntax, function_type, [Arguments, Return]).
+ apply(erl_syntax, function_type, [Arguments, Return]).
%% Convert erlfmt_scan:anno to erl_syntax pos+annotation
%%
%% Note: nothing from meta is stored in annotation
%% as erlang_ls only needs start and end locations.
--spec update_tree_with_meta(syntax_tools(), erlfmt_scan:anno())
- -> syntax_tools().
+-spec update_tree_with_meta(syntax_tools(), erlfmt_scan:anno()) ->
+ syntax_tools().
update_tree_with_meta(Tree, Meta) ->
- Anno = meta_to_anno(Meta),
- Tree2 = erl_syntax:set_pos(Tree, Anno),
- %% erl_syntax:set_ann(Tree2, [{meta, Meta}]).
- Tree2.
+ Anno = meta_to_anno(Meta),
+ Tree2 = erl_syntax:set_pos(Tree, Anno),
+ %% erl_syntax:set_ann(Tree2, [{meta, Meta}]).
+ Tree2.
-spec convert_meta_to_anno(erlfmt()) -> syntax_tools().
convert_meta_to_anno(Node) ->
- Meta = get_anno(Node),
- Node2 = set_anno(Node, meta_to_anno(Meta)),
- %% erl_syntax:set_ann(Node2, [{meta, Meta}]).
- Node2.
+ Meta = get_anno(Node),
+ Node2 = set_anno(Node, meta_to_anno(Meta)),
+ %% erl_syntax:set_ann(Node2, [{meta, Meta}]).
+ Node2.
-spec meta_to_anno(erlfmt_scan:anno()) -> erl_anno:anno().
meta_to_anno(Meta) ->
- %% Recommenting can modify the start and end locations of certain trees
- %% see erlfmt_recomment:put_(pre|post)_comments/1
- From =
- case maps:is_key(pre_comments, Meta) of
- true ->
- maps:get(inner_location, Meta);
- false ->
- maps:get(location, Meta)
- end,
- To =
- case maps:is_key(post_comments, Meta) of
- true ->
- maps:get(inner_end_location, Meta);
- false ->
- maps:get(end_location, Meta)
- end,
- erl_anno:from_term([{location, From}, {end_location, To}]).
+ %% Recommenting can modify the start and end locations of certain trees
+ %% see erlfmt_recomment:put_(pre|post)_comments/1
+ From =
+ case maps:is_key(pre_comments, Meta) of
+ true ->
+ maps:get(inner_location, Meta);
+ false ->
+ maps:get(location, Meta)
+ end,
+ To =
+ case maps:is_key(post_comments, Meta) of
+ true ->
+ maps:get(inner_end_location, Meta);
+ false ->
+ maps:get(end_location, Meta)
+ end,
+ erl_anno:from_term([{location, From}, {end_location, To}]).
diff --git a/apps/els_lsp/src/els_execute_command_provider.erl b/apps/els_lsp/src/els_execute_command_provider.erl
index 15cad428c..79d801f2a 100644
--- a/apps/els_lsp/src/els_execute_command_provider.erl
+++ b/apps/els_lsp/src/els_execute_command_provider.erl
@@ -2,10 +2,11 @@
-behaviour(els_provider).
--export([ is_enabled/0
- , options/0
- , handle_request/2
- ]).
+-export([
+ is_enabled/0,
+ options/0,
+ handle_request/2
+]).
%%==============================================================================
%% Includes
@@ -23,20 +24,25 @@ is_enabled() -> true.
-spec options() -> map().
options() ->
- #{ commands => [ els_command:with_prefix(<<"server-info">>)
- , els_command:with_prefix(<<"ct-run-test">>)
- , els_command:with_prefix(<<"show-behaviour-usages">>)
- , els_command:with_prefix(<<"suggest-spec">>)
- , els_command:with_prefix(<<"function-references">>)
- ] }.
+ #{
+ commands => [
+ els_command:with_prefix(<<"server-info">>),
+ els_command:with_prefix(<<"ct-run-test">>),
+ els_command:with_prefix(<<"show-behaviour-usages">>),
+ els_command:with_prefix(<<"suggest-spec">>),
+ els_command:with_prefix(<<"function-references">>)
+ ]
+ }.
-spec handle_request(any(), state()) -> {response, any()}.
handle_request({workspace_executecommand, Params}, _State) ->
- #{ <<"command">> := PrefixedCommand } = Params,
- Arguments = maps:get(<<"arguments">>, Params, []),
- Result = execute_command( els_command:without_prefix(PrefixedCommand)
- , Arguments),
- {response, Result}.
+ #{<<"command">> := PrefixedCommand} = Params,
+ Arguments = maps:get(<<"arguments">>, Params, []),
+ Result = execute_command(
+ els_command:without_prefix(PrefixedCommand),
+ Arguments
+ ),
+ {response, Result}.
%%==============================================================================
%% Internal Functions
@@ -44,60 +50,66 @@ handle_request({workspace_executecommand, Params}, _State) ->
-spec execute_command(els_command:command_id(), [any()]) -> [map()].
execute_command(<<"server-info">>, _Arguments) ->
- {ok, Version} = application:get_key(?APP, vsn),
- BinVersion = list_to_binary(Version),
- Root = filename:basename(els_uri:path(els_config:get(root_uri))),
- ConfigPath = case els_config:get(config_path) of
- undefined -> <<"undefined">>;
- Path -> list_to_binary(Path)
- end,
+ {ok, Version} = application:get_key(?APP, vsn),
+ BinVersion = list_to_binary(Version),
+ Root = filename:basename(els_uri:path(els_config:get(root_uri))),
+ ConfigPath =
+ case els_config:get(config_path) of
+ undefined -> <<"undefined">>;
+ Path -> list_to_binary(Path)
+ end,
- OtpPathConfig = list_to_binary(els_config:get(otp_path)),
- OtpRootDir = list_to_binary(code:root_dir()),
- OtpMessage = case OtpRootDir == OtpPathConfig of
- true ->
- <<", OTP root ", OtpRootDir/binary>>;
- false ->
- <<", OTP root(code):"
- , OtpRootDir/binary
- , ", OTP root(config):"
- , OtpPathConfig/binary>>
- end,
- Message = <<"Erlang LS (in ", Root/binary, "), version: "
- , BinVersion/binary
- , ", config from "
- , ConfigPath/binary
- , OtpMessage/binary
- >>,
- els_server:send_notification(<<"window/showMessage">>,
- #{ type => ?MESSAGE_TYPE_INFO,
- message => Message
- }),
- [];
+ OtpPathConfig = list_to_binary(els_config:get(otp_path)),
+ OtpRootDir = list_to_binary(code:root_dir()),
+ OtpMessage =
+ case OtpRootDir == OtpPathConfig of
+ true ->
+ <<", OTP root ", OtpRootDir/binary>>;
+ false ->
+ <<", OTP root(code):", OtpRootDir/binary, ", OTP root(config):",
+ OtpPathConfig/binary>>
+ end,
+ Message =
+ <<"Erlang LS (in ", Root/binary, "), version: ", BinVersion/binary, ", config from ",
+ ConfigPath/binary, OtpMessage/binary>>,
+ els_server:send_notification(
+ <<"window/showMessage">>,
+ #{
+ type => ?MESSAGE_TYPE_INFO,
+ message => Message
+ }
+ ),
+ [];
execute_command(<<"ct-run-test">>, [Params]) ->
- els_command_ct_run_test:execute(Params),
- [];
+ els_command_ct_run_test:execute(Params),
+ [];
execute_command(<<"function-references">>, [_Params]) ->
- [];
+ [];
execute_command(<<"show-behaviour-usages">>, [_Params]) ->
- [];
+ [];
execute_command(<<"suggest-spec">>, []) ->
- [];
-execute_command(<<"suggest-spec">>, [#{ <<"uri">> := Uri
- , <<"line">> := Line
- , <<"spec">> := Spec
- }]) ->
- Method = <<"workspace/applyEdit">>,
- {ok, #{text := Text}} = els_utils:lookup_document(Uri),
- LineText = els_text:line(Text, Line - 1),
- NewText = <>,
- Params =
- #{ edit =>
- els_text_edit:edit_replace_text(Uri, NewText, Line - 1, Line)
- },
- els_server:send_request(Method, Params),
- [];
+ [];
+execute_command(<<"suggest-spec">>, [
+ #{
+ <<"uri">> := Uri,
+ <<"line">> := Line,
+ <<"spec">> := Spec
+ }
+]) ->
+ Method = <<"workspace/applyEdit">>,
+ {ok, #{text := Text}} = els_utils:lookup_document(Uri),
+ LineText = els_text:line(Text, Line - 1),
+ NewText = <>,
+ Params =
+ #{
+ edit =>
+ els_text_edit:edit_replace_text(Uri, NewText, Line - 1, Line)
+ },
+ els_server:send_request(Method, Params),
+ [];
execute_command(Command, Arguments) ->
- ?LOG_INFO("Unsupported command: [Command=~p] [Arguments=~p]"
- , [Command, Arguments]),
- [].
+ ?LOG_INFO(
+ "Unsupported command: [Command=~p] [Arguments=~p]",
+ [Command, Arguments]
+ ),
+ [].
diff --git a/apps/els_lsp/src/els_folding_range_provider.erl b/apps/els_lsp/src/els_folding_range_provider.erl
index 22540b955..1e4189b26 100644
--- a/apps/els_lsp/src/els_folding_range_provider.erl
+++ b/apps/els_lsp/src/els_folding_range_provider.erl
@@ -4,9 +4,10 @@
-include("els_lsp.hrl").
--export([ is_enabled/0
- , handle_request/2
- ]).
+-export([
+ is_enabled/0,
+ handle_request/2
+]).
%%==============================================================================
%% Type Definitions
@@ -21,15 +22,20 @@ is_enabled() -> true.
-spec handle_request(tuple(), any()) -> {response, folding_range_result()}.
handle_request({document_foldingrange, Params}, _State) ->
- #{ <<"textDocument">> := #{<<"uri">> := Uri} } = Params,
- {ok, Document} = els_utils:lookup_document(Uri),
- POIs = els_dt_document:pois(Document, [function, record]),
- Response = case [folding_range(Range)
- || #{data := #{folding_range := Range = #{}}} <- POIs] of
- [] -> null;
- Ranges -> Ranges
- end,
- {response, Response}.
+ #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
+ {ok, Document} = els_utils:lookup_document(Uri),
+ POIs = els_dt_document:pois(Document, [function, record]),
+ Response =
+ case
+ [
+ folding_range(Range)
+ || #{data := #{folding_range := Range = #{}}} <- POIs
+ ]
+ of
+ [] -> null;
+ Ranges -> Ranges
+ end,
+ {response, Response}.
%%==============================================================================
%% Internal functions
@@ -37,8 +43,9 @@ handle_request({document_foldingrange, Params}, _State) ->
-spec folding_range(poi_range()) -> folding_range().
folding_range(#{from := {FromLine, FromCol}, to := {ToLine, ToCol}}) ->
- #{ startLine => FromLine - 1
- , startCharacter => FromCol
- , endLine => ToLine - 1
- , endCharacter => ToCol
- }.
+ #{
+ startLine => FromLine - 1,
+ startCharacter => FromCol,
+ endLine => ToLine - 1,
+ endCharacter => ToCol
+ }.
diff --git a/apps/els_lsp/src/els_formatting_provider.erl b/apps/els_lsp/src/els_formatting_provider.erl
index b3adfe601..8901c1680 100644
--- a/apps/els_lsp/src/els_formatting_provider.erl
+++ b/apps/els_lsp/src/els_formatting_provider.erl
@@ -2,13 +2,14 @@
-behaviour(els_provider).
--export([ init/0
- , handle_request/2
- , is_enabled/0
- , is_enabled_document/0
- , is_enabled_range/0
- , is_enabled_on_type/0
- ]).
+-export([
+ init/0,
+ handle_request/2,
+ is_enabled/0,
+ is_enabled_document/0,
+ is_enabled_range/0,
+ is_enabled_on_type/0
+]).
%%==============================================================================
%% Includes
@@ -19,8 +20,7 @@
%%==============================================================================
%% Types
%%==============================================================================
--type formatter() :: fun((string(), string(), formatting_options()) ->
- boolean()).
+-type formatter() :: fun((string(), string(), formatting_options()) -> boolean()).
-type state() :: [formatter()].
%%==============================================================================
@@ -33,7 +33,7 @@
%%==============================================================================
-spec init() -> state().
init() ->
- [ fun format_document_local/3 ].
+ [fun format_document_local/3].
%% Keep the behaviour happy
-spec is_enabled() -> boolean().
@@ -44,7 +44,7 @@ is_enabled_document() -> true.
-spec is_enabled_range() -> boolean().
is_enabled_range() ->
- false.
+ false.
%% NOTE: because erlang_ls does not send incremental document changes
%% via `textDocument/didChange`, this kind of formatting does not
@@ -54,75 +54,98 @@ is_enabled_on_type() -> false.
-spec handle_request(any(), state()) -> {response, any()}.
handle_request({document_formatting, Params}, _State) ->
- #{ <<"options">> := Options
- , <<"textDocument">> := #{<<"uri">> := Uri}
- } = Params,
- Path = els_uri:path(Uri),
- case els_utils:project_relative(Uri) of
- {error, not_relative} ->
- {response, []};
- RelativePath ->
- format_document(Path, RelativePath, Options)
- end;
+ #{
+ <<"options">> := Options,
+ <<"textDocument">> := #{<<"uri">> := Uri}
+ } = Params,
+ Path = els_uri:path(Uri),
+ case els_utils:project_relative(Uri) of
+ {error, not_relative} ->
+ {response, []};
+ RelativePath ->
+ format_document(Path, RelativePath, Options)
+ end;
handle_request({document_rangeformatting, Params}, _State) ->
- #{ <<"range">> := #{ <<"start">> := StartPos
- , <<"end">> := EndPos
- }
- , <<"options">> := Options
- , <<"textDocument">> := #{<<"uri">> := Uri}
- } = Params,
- Range = #{ start => StartPos, 'end' => EndPos },
- {ok, Document} = els_utils:lookup_document(Uri),
- {ok, TextEdit} = rangeformat_document(Uri, Document, Range, Options),
- {response, TextEdit};
+ #{
+ <<"range">> := #{
+ <<"start">> := StartPos,
+ <<"end">> := EndPos
+ },
+ <<"options">> := Options,
+ <<"textDocument">> := #{<<"uri">> := Uri}
+ } = Params,
+ Range = #{start => StartPos, 'end' => EndPos},
+ {ok, Document} = els_utils:lookup_document(Uri),
+ {ok, TextEdit} = rangeformat_document(Uri, Document, Range, Options),
+ {response, TextEdit};
handle_request({document_ontypeformatting, Params}, _State) ->
- #{ <<"position">> := #{ <<"line">> := Line
- , <<"character">> := Character
- }
- , <<"ch">> := Char
- , <<"options">> := Options
- , <<"textDocument">> := #{<<"uri">> := Uri}
- } = Params,
- {ok, Document} = els_utils:lookup_document(Uri),
- {ok, TextEdit} =
- ontypeformat_document(Uri, Document, Line + 1, Character + 1,
- Char, Options),
- {response, TextEdit}.
+ #{
+ <<"position">> := #{
+ <<"line">> := Line,
+ <<"character">> := Character
+ },
+ <<"ch">> := Char,
+ <<"options">> := Options,
+ <<"textDocument">> := #{<<"uri">> := Uri}
+ } = Params,
+ {ok, Document} = els_utils:lookup_document(Uri),
+ {ok, TextEdit} =
+ ontypeformat_document(
+ Uri,
+ Document,
+ Line + 1,
+ Character + 1,
+ Char,
+ Options
+ ),
+ {response, TextEdit}.
%%==============================================================================
%% Internal functions
%%==============================================================================
-spec format_document(binary(), string(), formatting_options()) ->
- {[text_edit()]}.
+ {[text_edit()]}.
format_document(Path, RelativePath, Options) ->
- Fun = fun(Dir) ->
- format_document_local(Dir, RelativePath, Options),
- Outfile = filename:join(Dir, RelativePath),
- {response, els_text_edit:diff_files(Path, Outfile)}
- end,
- tempdir:mktmp(Fun).
+ Fun = fun(Dir) ->
+ format_document_local(Dir, RelativePath, Options),
+ Outfile = filename:join(Dir, RelativePath),
+ {response, els_text_edit:diff_files(Path, Outfile)}
+ end,
+ tempdir:mktmp(Fun).
-spec format_document_local(string(), string(), formatting_options()) -> ok.
-format_document_local(Dir, RelativePath,
- #{ <<"insertSpaces">> := InsertSpaces
- , <<"tabSize">> := TabSize } = Options) ->
- SubIndent = maps:get(<<"subIndent">>, Options, ?DEFAULT_SUB_INDENT),
- Opts = #{ remove_tabs => InsertSpaces
- , break_indent => TabSize
- , sub_indent => SubIndent
- , output_dir => Dir
- },
- Formatter = rebar3_formatter:new(default_formatter, Opts, unused),
- rebar3_formatter:format_file(RelativePath, Formatter),
- ok.
+format_document_local(
+ Dir,
+ RelativePath,
+ #{
+ <<"insertSpaces">> := InsertSpaces,
+ <<"tabSize">> := TabSize
+ } = Options
+) ->
+ SubIndent = maps:get(<<"subIndent">>, Options, ?DEFAULT_SUB_INDENT),
+ Opts = #{
+ remove_tabs => InsertSpaces,
+ break_indent => TabSize,
+ sub_indent => SubIndent,
+ output_dir => Dir
+ },
+ Formatter = rebar3_formatter:new(default_formatter, Opts, unused),
+ rebar3_formatter:format_file(RelativePath, Formatter),
+ ok.
--spec rangeformat_document(uri(), map(), range(), formatting_options())
- -> {ok, [text_edit()]}.
+-spec rangeformat_document(uri(), map(), range(), formatting_options()) ->
+ {ok, [text_edit()]}.
rangeformat_document(_Uri, _Document, _Range, _Options) ->
{ok, []}.
--spec ontypeformat_document(binary(), map()
- , number(), number(), string(), formatting_options())
- -> {ok, [text_edit()]}.
+-spec ontypeformat_document(
+ binary(),
+ map(),
+ number(),
+ number(),
+ string(),
+ formatting_options()
+) ->
+ {ok, [text_edit()]}.
ontypeformat_document(_Uri, _Document, _Line, _Col, _Char, _Options) ->
{ok, []}.
diff --git a/apps/els_lsp/src/els_fungraph.erl b/apps/els_lsp/src/els_fungraph.erl
index 19da5d1ec..adc2d935d 100644
--- a/apps/els_lsp/src/els_fungraph.erl
+++ b/apps/els_lsp/src/els_fungraph.erl
@@ -12,34 +12,34 @@
-spec new(id_fun(NodeT), edges_fun(NodeT)) -> graph(NodeT).
new(IdFun, EdgesFun) ->
- {?MODULE, IdFun, EdgesFun}.
+ {?MODULE, IdFun, EdgesFun}.
-type acc_fun(NodeT, AccT) ::
- fun((_Node :: NodeT, _From :: NodeT, AccT) -> AccT).
+ fun((_Node :: NodeT, _From :: NodeT, AccT) -> AccT).
-spec traverse(acc_fun(NodeT, AccT), AccT, NodeT, graph(NodeT)) -> AccT.
traverse(AccFun, Acc, From, G) ->
- traverse(AccFun, Acc, [From], sets:new(), G).
+ traverse(AccFun, Acc, [From], sets:new(), G).
-spec traverse(acc_fun(NodeT, AccT), AccT, [NodeT], sets:set(), graph(NodeT)) ->
- AccT.
+ AccT.
traverse(AccFun, Acc, [Node | Rest], Visited, G = {?MODULE, IdFun, EdgesFun}) ->
- {AdjacentNodes, VisitedNext} = lists:foldr(
- fun(Adjacent, {NodesAcc, VisitedAcc}) ->
- ID = IdFun(Adjacent),
- case sets:is_element(ID, VisitedAcc) of
- false -> {[Adjacent | NodesAcc], sets:add_element(ID, VisitedAcc)};
- true -> {NodesAcc, VisitedAcc}
- end
- end,
- {[], Visited},
- EdgesFun(Node)
- ),
- AccNext = lists:foldl(
- fun(Adjacent, AccIn) -> AccFun(Adjacent, Node, AccIn) end,
- Acc,
- AdjacentNodes
- ),
- traverse(AccFun, AccNext, AdjacentNodes ++ Rest, VisitedNext, G);
+ {AdjacentNodes, VisitedNext} = lists:foldr(
+ fun(Adjacent, {NodesAcc, VisitedAcc}) ->
+ ID = IdFun(Adjacent),
+ case sets:is_element(ID, VisitedAcc) of
+ false -> {[Adjacent | NodesAcc], sets:add_element(ID, VisitedAcc)};
+ true -> {NodesAcc, VisitedAcc}
+ end
+ end,
+ {[], Visited},
+ EdgesFun(Node)
+ ),
+ AccNext = lists:foldl(
+ fun(Adjacent, AccIn) -> AccFun(Adjacent, Node, AccIn) end,
+ Acc,
+ AdjacentNodes
+ ),
+ traverse(AccFun, AccNext, AdjacentNodes ++ Rest, VisitedNext, G);
traverse(_AccFun, Acc, [], _Visited, _G) ->
- Acc.
+ Acc.
diff --git a/apps/els_lsp/src/els_general_provider.erl b/apps/els_lsp/src/els_general_provider.erl
index aebe37684..06ab4b65d 100644
--- a/apps/els_lsp/src/els_general_provider.erl
+++ b/apps/els_lsp/src/els_general_provider.erl
@@ -1,12 +1,12 @@
-module(els_general_provider).
-behaviour(els_provider).
--export([ is_enabled/0
- , handle_request/2
- ]).
+-export([
+ is_enabled/0,
+ handle_request/2
+]).
--export([ server_capabilities/0
- ]).
+-export([server_capabilities/0]).
%%==============================================================================
%% Includes
@@ -20,18 +20,21 @@
-type server_capabilities() :: map().
-type initialize_request() :: {initialize, initialize_params()}.
--type initialize_params() :: #{ processId := number() | null
- , rootPath => binary() | null
- , rootUri := uri() | null
- , initializationOptions => any()
- , capabilities := client_capabilities()
- , trace => off
- | messages
- | verbose
- , workspaceFolders => [workspace_folder()]
- | null
- }.
--type initialize_result() :: #{ capabilities => server_capabilities() }.
+-type initialize_params() :: #{
+ processId := number() | null,
+ rootPath => binary() | null,
+ rootUri := uri() | null,
+ initializationOptions => any(),
+ capabilities := client_capabilities(),
+ trace =>
+ off
+ | messages
+ | verbose,
+ workspaceFolders =>
+ [workspace_folder()]
+ | null
+}.
+-type initialize_result() :: #{capabilities => server_capabilities()}.
-type initialized_request() :: {initialized, initialized_params()}.
-type initialized_params() :: #{}.
-type initialized_result() :: null.
@@ -49,52 +52,61 @@
-spec is_enabled() -> boolean().
is_enabled() -> true.
--spec handle_request( initialize_request()
- | initialized_request()
- | shutdown_request()
- | exit_request()
- , state()) ->
- { response,
- initialize_result()
+-spec handle_request(
+ initialize_request()
+ | initialized_request()
+ | shutdown_request()
+ | exit_request(),
+ state()
+) ->
+ {response,
+ initialize_result()
| initialized_result()
| shutdown_result()
- | exit_result()
- }.
+ | exit_result()}.
handle_request({initialize, Params}, _State) ->
- #{ <<"rootUri">> := RootUri0
- , <<"capabilities">> := Capabilities
- } = Params,
- RootUri = case RootUri0 of
- null ->
+ #{
+ <<"rootUri">> := RootUri0,
+ <<"capabilities">> := Capabilities
+ } = Params,
+ RootUri =
+ case RootUri0 of
+ null ->
{ok, Cwd} = file:get_cwd(),
els_uri:uri(els_utils:to_binary(Cwd));
- _ -> RootUri0
- end,
- InitOptions = case maps:get(<<"initializationOptions">>, Params, #{}) of
- InitOptions0 when is_map(InitOptions0) ->
- InitOptions0;
- _ -> #{}
- end,
- ok = els_config:initialize(RootUri, Capabilities, InitOptions, true),
- {response, server_capabilities()};
+ _ ->
+ RootUri0
+ end,
+ InitOptions =
+ case maps:get(<<"initializationOptions">>, Params, #{}) of
+ InitOptions0 when is_map(InitOptions0) ->
+ InitOptions0;
+ _ ->
+ #{}
+ end,
+ ok = els_config:initialize(RootUri, Capabilities, InitOptions, true),
+ {response, server_capabilities()};
handle_request({initialized, _Params}, _State) ->
- RootUri = els_config:get(root_uri),
- NodeName = els_distribution_server:node_name( <<"erlang_ls">>
- , filename:basename(RootUri)),
- els_distribution_server:start_distribution(NodeName),
- ?LOG_INFO("Started distribution for: [~p]", [NodeName]),
- els_indexing:maybe_start(),
- {response, null};
+ RootUri = els_config:get(root_uri),
+ NodeName = els_distribution_server:node_name(
+ <<"erlang_ls">>,
+ filename:basename(RootUri)
+ ),
+ els_distribution_server:start_distribution(NodeName),
+ ?LOG_INFO("Started distribution for: [~p]", [NodeName]),
+ els_indexing:maybe_start(),
+ {response, null};
handle_request({shutdown, _Params}, _State) ->
- {response, null};
+ {response, null};
handle_request({exit, #{status := Status}}, _State) ->
- ?LOG_INFO("Language server stopping..."),
- ExitCode = case Status of
- shutdown -> 0;
- _ -> 1
- end,
- els_utils:halt(ExitCode),
- {response, null}.
+ ?LOG_INFO("Language server stopping..."),
+ ExitCode =
+ case Status of
+ shutdown -> 0;
+ _ -> 1
+ end,
+ els_utils:halt(ExitCode),
+ {response, null}.
%%==============================================================================
%% API
@@ -102,47 +114,51 @@ handle_request({exit, #{status := Status}}, _State) ->
-spec server_capabilities() -> server_capabilities().
server_capabilities() ->
- {ok, Version} = application:get_key(?APP, vsn),
- #{ capabilities =>
- #{ textDocumentSync =>
- els_text_synchronization_provider:options()
- , hoverProvider => true
- , completionProvider =>
- #{ resolveProvider => true
- , triggerCharacters =>
- els_completion_provider:trigger_characters()
- }
- , definitionProvider =>
- els_definition_provider:is_enabled()
- , referencesProvider =>
- els_references_provider:is_enabled()
- , documentHighlightProvider =>
- els_document_highlight_provider:is_enabled()
- , documentSymbolProvider =>
- els_document_symbol_provider:is_enabled()
- , workspaceSymbolProvider =>
- els_workspace_symbol_provider:is_enabled()
- , codeActionProvider =>
- els_code_action_provider:is_enabled()
- , documentFormattingProvider =>
- els_formatting_provider:is_enabled_document()
- , documentRangeFormattingProvider =>
- els_formatting_provider:is_enabled_range()
- , foldingRangeProvider =>
- els_folding_range_provider:is_enabled()
- , implementationProvider =>
- els_implementation_provider:is_enabled()
- , executeCommandProvider =>
- els_execute_command_provider:options()
- , codeLensProvider =>
- els_code_lens_provider:options()
- , renameProvider =>
- els_rename_provider:is_enabled()
- , callHierarchyProvider =>
- els_call_hierarchy_provider:is_enabled()
- },
- serverInfo =>
- #{ name => <<"Erlang LS">>
- , version => els_utils:to_binary(Version)
- }
- }.
+ {ok, Version} = application:get_key(?APP, vsn),
+ #{
+ capabilities =>
+ #{
+ textDocumentSync =>
+ els_text_synchronization_provider:options(),
+ hoverProvider => true,
+ completionProvider =>
+ #{
+ resolveProvider => true,
+ triggerCharacters =>
+ els_completion_provider:trigger_characters()
+ },
+ definitionProvider =>
+ els_definition_provider:is_enabled(),
+ referencesProvider =>
+ els_references_provider:is_enabled(),
+ documentHighlightProvider =>
+ els_document_highlight_provider:is_enabled(),
+ documentSymbolProvider =>
+ els_document_symbol_provider:is_enabled(),
+ workspaceSymbolProvider =>
+ els_workspace_symbol_provider:is_enabled(),
+ codeActionProvider =>
+ els_code_action_provider:is_enabled(),
+ documentFormattingProvider =>
+ els_formatting_provider:is_enabled_document(),
+ documentRangeFormattingProvider =>
+ els_formatting_provider:is_enabled_range(),
+ foldingRangeProvider =>
+ els_folding_range_provider:is_enabled(),
+ implementationProvider =>
+ els_implementation_provider:is_enabled(),
+ executeCommandProvider =>
+ els_execute_command_provider:options(),
+ codeLensProvider =>
+ els_code_lens_provider:options(),
+ renameProvider =>
+ els_rename_provider:is_enabled(),
+ callHierarchyProvider =>
+ els_call_hierarchy_provider:is_enabled()
+ },
+ serverInfo =>
+ #{
+ name => <<"Erlang LS">>,
+ version => els_utils:to_binary(Version)
+ }
+ }.
diff --git a/apps/els_lsp/src/els_gradualizer_diagnostics.erl b/apps/els_lsp/src/els_gradualizer_diagnostics.erl
index e2ad075f9..f515f32a9 100644
--- a/apps/els_lsp/src/els_gradualizer_diagnostics.erl
+++ b/apps/els_lsp/src/els_gradualizer_diagnostics.erl
@@ -11,10 +11,11 @@
%%==============================================================================
%% Exports
%%==============================================================================
--export([ is_default/0
- , run/1
- , source/0
- ]).
+-export([
+ is_default/0,
+ run/1,
+ source/0
+]).
%%==============================================================================
%% Includes
@@ -28,28 +29,31 @@
-spec is_default() -> boolean().
is_default() ->
- false.
+ false.
-spec run(uri()) -> [els_diagnostics:diagnostic()].
run(Uri) ->
- Files = lists:flatmap(
- fun(Dir) ->
- filelib:wildcard(filename:join(Dir, "*.erl"))
- end,
- els_config:get(apps_paths) ++ els_config:get(deps_paths)),
- ?LOG_INFO("Importing files into gradualizer_db: ~p", [Files]),
- ok = gradualizer_db:import_erl_files(Files,
- els_config:get(include_paths)),
- Path = unicode:characters_to_list(els_uri:path(Uri)),
- Includes = [{i, I} || I <- els_config:get(include_paths)],
- Opts = [return_errors] ++ Includes,
- Errors = gradualizer:type_check_files([Path], Opts),
- ?LOG_INFO("Gradualizer diagnostics: ~p", [Errors]),
- lists:flatmap(fun analyzer_error/1, Errors).
+ Files = lists:flatmap(
+ fun(Dir) ->
+ filelib:wildcard(filename:join(Dir, "*.erl"))
+ end,
+ els_config:get(apps_paths) ++ els_config:get(deps_paths)
+ ),
+ ?LOG_INFO("Importing files into gradualizer_db: ~p", [Files]),
+ ok = gradualizer_db:import_erl_files(
+ Files,
+ els_config:get(include_paths)
+ ),
+ Path = unicode:characters_to_list(els_uri:path(Uri)),
+ Includes = [{i, I} || I <- els_config:get(include_paths)],
+ Opts = [return_errors] ++ Includes,
+ Errors = gradualizer:type_check_files([Path], Opts),
+ ?LOG_INFO("Gradualizer diagnostics: ~p", [Errors]),
+ lists:flatmap(fun analyzer_error/1, Errors).
-spec source() -> binary().
source() ->
- <<"Gradualizer">>.
+ <<"Gradualizer">>.
%%==============================================================================
%% Internal Functions
@@ -57,19 +61,33 @@ source() ->
-spec analyzer_error(any()) -> any().
analyzer_error({_Path, Error}) ->
- FmtOpts = [{fmt_location, brief}, {color, never}],
- FmtError = gradualizer_fmt:format_type_error(Error, FmtOpts),
- case re:run(FmtError, "([0-9]+):([0-9]+:)? (.*)",
- [{capture, all_but_first, binary}, dotall]) of
- {match, [BinLine, _BinCol, Msg]} ->
- Line = case binary_to_integer(BinLine) of
- 0 -> 1;
- L -> L
- end,
- Range = els_protocol:range(#{ from => {Line, 1},
- to => {Line + 1, 1} }),
- [els_diagnostics:make_diagnostic(Range, Msg, ?DIAGNOSTIC_WARNING,
- source())];
- _ ->
- []
- end.
+ FmtOpts = [{fmt_location, brief}, {color, never}],
+ FmtError = gradualizer_fmt:format_type_error(Error, FmtOpts),
+ case
+ re:run(
+ FmtError,
+ "([0-9]+):([0-9]+:)? (.*)",
+ [{capture, all_but_first, binary}, dotall]
+ )
+ of
+ {match, [BinLine, _BinCol, Msg]} ->
+ Line =
+ case binary_to_integer(BinLine) of
+ 0 -> 1;
+ L -> L
+ end,
+ Range = els_protocol:range(#{
+ from => {Line, 1},
+ to => {Line + 1, 1}
+ }),
+ [
+ els_diagnostics:make_diagnostic(
+ Range,
+ Msg,
+ ?DIAGNOSTIC_WARNING,
+ source()
+ )
+ ];
+ _ ->
+ []
+ end.
diff --git a/apps/els_lsp/src/els_group_leader_server.erl b/apps/els_lsp/src/els_group_leader_server.erl
index feb309c92..77d2e7575 100644
--- a/apps/els_lsp/src/els_group_leader_server.erl
+++ b/apps/els_lsp/src/els_group_leader_server.erl
@@ -7,26 +7,28 @@
%%==============================================================================
%% API
%%==============================================================================
--export([ new/0
- , flush/1
- , stop/1
- ]).
+-export([
+ new/0,
+ flush/1,
+ stop/1
+]).
%%==============================================================================
%% Supervisor API
%%==============================================================================
--export([ start_link/1 ]).
+-export([start_link/1]).
%%==============================================================================
%% Callbacks for gen_server
%%==============================================================================
-behaviour(gen_server).
--export([ init/1
- , handle_call/3
- , handle_cast/2
- , handle_info/2
- , terminate/2
- ]).
+-export([
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2
+]).
%%==============================================================================
%% Includes
@@ -41,13 +43,15 @@
%%==============================================================================
%% Type Definitions
%%==============================================================================
--type config() :: #{ caller := pid()
- , gl := pid()
- }.
--type state() :: #{ acc := [any()]
- , caller := pid()
- , gl := pid()
- }.
+-type config() :: #{
+ caller := pid(),
+ gl := pid()
+}.
+-type state() :: #{
+ acc := [any()],
+ caller := pid(),
+ gl := pid()
+}.
%%==============================================================================
%% API
@@ -56,24 +60,24 @@
%% @doc Create a new server
-spec new() -> {ok, pid()}.
new() ->
- Caller = self(),
- GL = group_leader(),
- supervisor:start_child(els_group_leader_sup, [#{caller => Caller, gl => GL}]).
+ Caller = self(),
+ GL = group_leader(),
+ supervisor:start_child(els_group_leader_sup, [#{caller => Caller, gl => GL}]).
-spec flush(pid()) -> binary().
flush(Server) ->
- gen_server:call(Server, {flush}).
+ gen_server:call(Server, {flush}).
-spec stop(pid()) -> ok.
stop(Server) ->
- gen_server:call(Server, {stop}).
+ gen_server:call(Server, {stop}).
%%==============================================================================
%% Supervisor API
%%==============================================================================
-spec start_link(config()) -> {ok, pid()}.
start_link(Config) ->
- gen_server:start_link(?MODULE, Config, []).
+ gen_server:start_link(?MODULE, Config, []).
%%==============================================================================
%% gen_server callbacks
@@ -81,51 +85,53 @@ start_link(Config) ->
-spec init(config()) -> {ok, state()}.
init(#{caller := Caller, gl := GL}) ->
- process_flag(trap_exit, true),
- ?LOG_INFO("Starting group leader server [caller=~p] [gl=~p]", [Caller, GL]),
- group_leader(self(), Caller),
- {ok, #{acc => [], caller => Caller, gl => GL}}.
+ process_flag(trap_exit, true),
+ ?LOG_INFO("Starting group leader server [caller=~p] [gl=~p]", [Caller, GL]),
+ group_leader(self(), Caller),
+ {ok, #{acc => [], caller => Caller, gl => GL}}.
-spec handle_call(any(), {pid(), any()}, state()) -> {noreply, state()}.
handle_call({flush}, _From, #{acc := Acc} = State) ->
- Res = els_utils:to_binary(lists:flatten(lists:reverse(Acc))),
- {reply, Res, State};
+ Res = els_utils:to_binary(lists:flatten(lists:reverse(Acc))),
+ {reply, Res, State};
handle_call({stop}, _From, State) ->
- {stop, normal, ok, State};
+ {stop, normal, ok, State};
handle_call(_Request, _From, State) ->
- {noreply, State}.
+ {noreply, State}.
-spec handle_cast(any(), state()) -> {noreply, state()}.
handle_cast(_Request, State) ->
- {noreply, State}.
+ {noreply, State}.
-spec handle_info(any(), state()) -> {noreply, state()}.
handle_info({io_request, From, ReplyAs, Request}, State) ->
- #{acc := Acc} = State,
- case Request of
- {put_chars, Encoding, Characters} ->
- List = unicode:characters_to_list(Characters, Encoding),
- From ! {io_reply, ReplyAs, ok},
- {noreply, State#{acc => [List|Acc]}};
- {put_chars, Encoding, M, F, A} ->
- String = try erlang:apply(M, F, A)
- catch
- C:E:S ->
- io_lib:format("~p", [{C, E, S}])
- end,
- List = unicode:characters_to_list(String, Encoding),
- From ! {io_reply, ReplyAs, ok},
- {noreply, State#{acc => [List|Acc]}};
- Else ->
- ?LOG_WARNING("[Group Leader] Request not implemented", [Else]),
- From ! {io_reply, ReplyAs, {error, not_implemented}},
- {noreply, State}
- end;
+ #{acc := Acc} = State,
+ case Request of
+ {put_chars, Encoding, Characters} ->
+ List = unicode:characters_to_list(Characters, Encoding),
+ From ! {io_reply, ReplyAs, ok},
+ {noreply, State#{acc => [List | Acc]}};
+ {put_chars, Encoding, M, F, A} ->
+ String =
+ try
+ erlang:apply(M, F, A)
+ catch
+ C:E:S ->
+ io_lib:format("~p", [{C, E, S}])
+ end,
+ List = unicode:characters_to_list(String, Encoding),
+ From ! {io_reply, ReplyAs, ok},
+ {noreply, State#{acc => [List | Acc]}};
+ Else ->
+ ?LOG_WARNING("[Group Leader] Request not implemented", [Else]),
+ From ! {io_reply, ReplyAs, {error, not_implemented}},
+ {noreply, State}
+ end;
handle_info(Request, State) ->
- ?LOG_WARNING("[Group Leader] Unexpected request", [Request]),
- {noreply, State}.
+ ?LOG_WARNING("[Group Leader] Unexpected request", [Request]),
+ {noreply, State}.
-spec terminate(any(), state()) -> ok.
terminate(_Reason, #{caller := Caller, gl := GL} = _State) ->
- group_leader(GL, Caller),
- ok.
+ group_leader(GL, Caller),
+ ok.
diff --git a/apps/els_lsp/src/els_group_leader_sup.erl b/apps/els_lsp/src/els_group_leader_sup.erl
index 852ef463f..f5a8564c6 100644
--- a/apps/els_lsp/src/els_group_leader_sup.erl
+++ b/apps/els_lsp/src/els_group_leader_sup.erl
@@ -13,10 +13,10 @@
%%==============================================================================
%% API
--export([ start_link/0 ]).
+-export([start_link/0]).
%% Supervisor Callbacks
--export([ init/1 ]).
+-export([init/1]).
%%==============================================================================
%% Defines
@@ -28,20 +28,24 @@
%%==============================================================================
-spec start_link() -> {ok, pid()}.
start_link() ->
- supervisor:start_link({local, ?SERVER}, ?MODULE, []).
+ supervisor:start_link({local, ?SERVER}, ?MODULE, []).
%%==============================================================================
%% Supervisor callbacks
%%==============================================================================
-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init([]) ->
- SupFlags = #{ strategy => simple_one_for_one
- , intensity => 5
- , period => 60
- },
- ChildSpecs = [#{ id => els_group_leader_server
- , start => {els_group_leader_server, start_link, []}
- , restart => temporary
- , shutdown => 5000
- }],
- {ok, {SupFlags, ChildSpecs}}.
+ SupFlags = #{
+ strategy => simple_one_for_one,
+ intensity => 5,
+ period => 60
+ },
+ ChildSpecs = [
+ #{
+ id => els_group_leader_server,
+ start => {els_group_leader_server, start_link, []},
+ restart => temporary,
+ shutdown => 5000
+ }
+ ],
+ {ok, {SupFlags, ChildSpecs}}.
diff --git a/apps/els_lsp/src/els_hover_provider.erl b/apps/els_lsp/src/els_hover_provider.erl
index 5d3ebe30c..d097ef390 100644
--- a/apps/els_lsp/src/els_hover_provider.erl
+++ b/apps/els_lsp/src/els_hover_provider.erl
@@ -5,9 +5,10 @@
-behaviour(els_provider).
--export([ is_enabled/0
- , handle_request/2
- ]).
+-export([
+ is_enabled/0,
+ handle_request/2
+]).
-include("els_lsp.hrl").
-include_lib("kernel/include/logger.hrl").
@@ -21,20 +22,23 @@
%%==============================================================================
-spec is_enabled() -> boolean().
is_enabled() ->
- true.
+ true.
-spec handle_request(any(), any()) -> {async, uri(), pid()}.
handle_request({hover, Params}, _State) ->
- #{ <<"position">> := #{ <<"line">> := Line
- , <<"character">> := Character
- }
- , <<"textDocument">> := #{<<"uri">> := Uri}
- } = Params,
- ?LOG_DEBUG("Starting hover job ""[uri=~p, line=~p, character=~p]"
- , [Uri, Line, Character]
- ),
- Job = run_hover_job(Uri, Line, Character),
- {async, Uri, Job}.
+ #{
+ <<"position">> := #{
+ <<"line">> := Line,
+ <<"character">> := Character
+ },
+ <<"textDocument">> := #{<<"uri">> := Uri}
+ } = Params,
+ ?LOG_DEBUG(
+ "Starting hover job " "[uri=~p, line=~p, character=~p]",
+ [Uri, Line, Character]
+ ),
+ Job = run_hover_job(Uri, Line, Character),
+ {async, Uri, Job}.
%%==============================================================================
%% Internal Functions
@@ -42,32 +46,32 @@ handle_request({hover, Params}, _State) ->
-spec run_hover_job(uri(), line(), column()) -> pid().
run_hover_job(Uri, Line, Character) ->
- Config = #{ task => fun get_docs/2
- , entries => [{Uri, Line, Character}]
- , title => <<"Hover">>
- , on_complete =>
- fun(HoverResp) ->
- els_provider ! {result, HoverResp, self()},
- ok
- end
- },
- {ok, Pid} = els_background_job:new(Config),
- Pid.
+ Config = #{
+ task => fun get_docs/2,
+ entries => [{Uri, Line, Character}],
+ title => <<"Hover">>,
+ on_complete =>
+ fun(HoverResp) ->
+ els_provider ! {result, HoverResp, self()},
+ ok
+ end
+ },
+ {ok, Pid} = els_background_job:new(Config),
+ Pid.
-spec get_docs({uri(), integer(), integer()}, undefined) -> map() | null.
get_docs({Uri, Line, Character}, _) ->
- {ok, Doc} = els_utils:lookup_document(Uri),
- POIs = els_dt_document:get_element_at_pos(Doc, Line + 1, Character + 1),
- do_get_docs(Uri, POIs).
-
+ {ok, Doc} = els_utils:lookup_document(Uri),
+ POIs = els_dt_document:get_element_at_pos(Doc, Line + 1, Character + 1),
+ do_get_docs(Uri, POIs).
-spec do_get_docs(uri(), [poi()]) -> map() | null.
do_get_docs(_Uri, []) ->
- null;
-do_get_docs(Uri, [POI|Rest]) ->
- case els_docs:docs(Uri, POI) of
- [] ->
- do_get_docs(Uri, Rest);
- Entries ->
- #{contents => els_markup_content:new(Entries)}
- end.
+ null;
+do_get_docs(Uri, [POI | Rest]) ->
+ case els_docs:docs(Uri, POI) of
+ [] ->
+ do_get_docs(Uri, Rest);
+ Entries ->
+ #{contents => els_markup_content:new(Entries)}
+ end.
diff --git a/apps/els_lsp/src/els_implementation_provider.erl b/apps/els_lsp/src/els_implementation_provider.erl
index 58ff0e944..145fb3e8b 100644
--- a/apps/els_lsp/src/els_implementation_provider.erl
+++ b/apps/els_lsp/src/els_implementation_provider.erl
@@ -4,9 +4,10 @@
-include("els_lsp.hrl").
--export([ is_enabled/0
- , handle_request/2
- ]).
+-export([
+ is_enabled/0,
+ handle_request/2
+]).
%%==============================================================================
%% els_provider functions
@@ -16,95 +17,106 @@ is_enabled() -> true.
-spec handle_request(tuple(), els_provider:state()) -> {response, [location()]}.
handle_request({implementation, Params}, _State) ->
- #{ <<"position">> := #{ <<"line">> := Line
- , <<"character">> := Character
- }
- , <<"textDocument">> := #{<<"uri">> := Uri}
- } = Params,
- {ok, Document} = els_utils:lookup_document(Uri),
- Implementations = find_implementation(Document, Line, Character),
- Locations = [#{uri => U, range => els_protocol:range(Range)} ||
- {U, #{range := Range}} <- Implementations],
- {response, Locations}.
+ #{
+ <<"position">> := #{
+ <<"line">> := Line,
+ <<"character">> := Character
+ },
+ <<"textDocument">> := #{<<"uri">> := Uri}
+ } = Params,
+ {ok, Document} = els_utils:lookup_document(Uri),
+ Implementations = find_implementation(Document, Line, Character),
+ Locations = [
+ #{uri => U, range => els_protocol:range(Range)}
+ || {U, #{range := Range}} <- Implementations
+ ],
+ {response, Locations}.
%%==============================================================================
%% Internal functions
%%==============================================================================
--spec find_implementation( els_dt_document:item()
- , non_neg_integer()
- , non_neg_integer()
- ) -> [{uri(), poi()}].
+-spec find_implementation(
+ els_dt_document:item(),
+ non_neg_integer(),
+ non_neg_integer()
+) -> [{uri(), poi()}].
find_implementation(Document, Line, Character) ->
- case els_dt_document:get_element_at_pos(Document, Line + 1, Character + 1) of
- [POI|_] -> implementation(Document, POI);
- [] -> []
- end.
+ case els_dt_document:get_element_at_pos(Document, Line + 1, Character + 1) of
+ [POI | _] -> implementation(Document, POI);
+ [] -> []
+ end.
-spec implementation(els_dt_document:item(), poi()) -> [{uri(), poi()}].
implementation(Document, #{kind := application, id := MFA}) ->
- #{uri := Uri} = Document,
- case callback(MFA) of
- {CF, CA} ->
- POIs = els_dt_document:pois(Document, [function]),
- [{Uri, POI} || #{id := {F, A}} = POI <- POIs, F =:= CF, A =:= CA];
- undefined ->
- []
- end;
-implementation(Document, #{kind := callback, id := {CF, CA}}) ->
- #{uri := Uri} = Document,
- {ok, Refs} = els_dt_references:find_by_id(behaviour, els_uri:module(Uri)),
- lists:flatmap(
- fun(#{uri := U}) ->
- case els_utils:lookup_document(U) of
- {ok, D} ->
- [{U, POI} || #{id := {F, A}} = POI
- <- els_dt_document:pois(D, [function]),
- F =:= CF, A =:= CA];
- {error, _Reason} ->
+ #{uri := Uri} = Document,
+ case callback(MFA) of
+ {CF, CA} ->
+ POIs = els_dt_document:pois(Document, [function]),
+ [{Uri, POI} || #{id := {F, A}} = POI <- POIs, F =:= CF, A =:= CA];
+ undefined ->
[]
- end
- end, Refs);
+ end;
+implementation(Document, #{kind := callback, id := {CF, CA}}) ->
+ #{uri := Uri} = Document,
+ {ok, Refs} = els_dt_references:find_by_id(behaviour, els_uri:module(Uri)),
+ lists:flatmap(
+ fun(#{uri := U}) ->
+ case els_utils:lookup_document(U) of
+ {ok, D} ->
+ [
+ {U, POI}
+ || #{id := {F, A}} = POI <-
+ els_dt_document:pois(D, [function]),
+ F =:= CF,
+ A =:= CA
+ ];
+ {error, _Reason} ->
+ []
+ end
+ end,
+ Refs
+ );
implementation(_Document, _POI) ->
- [].
+ [].
-spec callback(mfa()) -> {atom(), non_neg_integer()} | undefined.
%% gen_event
-callback({gen_event, add_handler, 3}) -> {init, 1};
+callback({gen_event, add_handler, 3}) -> {init, 1};
callback({gen_event, add_sup_handler, 3}) -> {init, 1};
-callback({gen_event, call, 3}) -> {handle_call, 2};
-callback({gen_event, call, 4}) -> {handle_call, 2};
-callback({gen_event, delete_handler, 1}) -> {terminate, 2};
-callback({gen_event, notify, 2}) -> {handle_event, 2};
-callback({gen_event, sync_notify, 2}) -> {handle_event, 2};
-callback({gen_event, stop, 1}) -> {terminate, 2};
-callback({gen_event, stop, 3}) -> {terminate, 2};
+callback({gen_event, call, 3}) -> {handle_call, 2};
+callback({gen_event, call, 4}) -> {handle_call, 2};
+callback({gen_event, delete_handler, 1}) -> {terminate, 2};
+callback({gen_event, notify, 2}) -> {handle_event, 2};
+callback({gen_event, sync_notify, 2}) -> {handle_event, 2};
+callback({gen_event, stop, 1}) -> {terminate, 2};
+callback({gen_event, stop, 3}) -> {terminate, 2};
%% gen_server
-callback({gen_server, abcast, 2}) -> {handle_cast, 2};
-callback({gen_server, abcast, 3}) -> {handle_cast, 2};
-callback({gen_server, call, 2}) -> {handle_call, 3};
-callback({gen_server, call, 3}) -> {handle_call, 3};
-callback({gen_server, cast, 2}) -> {handle_cast, 2};
-callback({gen_server, multi_call, 2}) -> {handle_call, 3};
-callback({gen_server, multi_call, 3}) -> {handle_call, 3};
-callback({gen_server, multi_call, 4}) -> {handle_call, 3};
-callback({gen_server, start, 3}) -> {init, 1};
-callback({gen_server, start, 4}) -> {init, 1};
-callback({gen_server, start_link, 3}) -> {init, 1};
-callback({gen_server, start_link, 4}) -> {init, 1};
-callback({gen_server, stop, 1}) -> {terminate, 2};
-callback({gen_server, stop, 3}) -> {terminate, 2};
+callback({gen_server, abcast, 2}) -> {handle_cast, 2};
+callback({gen_server, abcast, 3}) -> {handle_cast, 2};
+callback({gen_server, call, 2}) -> {handle_call, 3};
+callback({gen_server, call, 3}) -> {handle_call, 3};
+callback({gen_server, cast, 2}) -> {handle_cast, 2};
+callback({gen_server, multi_call, 2}) -> {handle_call, 3};
+callback({gen_server, multi_call, 3}) -> {handle_call, 3};
+callback({gen_server, multi_call, 4}) -> {handle_call, 3};
+callback({gen_server, start, 3}) -> {init, 1};
+callback({gen_server, start, 4}) -> {init, 1};
+callback({gen_server, start_link, 3}) -> {init, 1};
+callback({gen_server, start_link, 4}) -> {init, 1};
+callback({gen_server, stop, 1}) -> {terminate, 2};
+callback({gen_server, stop, 3}) -> {terminate, 2};
%% gen_statem
-callback({gen_statem, call, 2}) -> {handle_event, 4};
-callback({gen_statem, call, 3}) -> {handle_event, 4};
-callback({gen_statem, cast, 2}) -> {handle_event, 4};
-callback({gen_statem, start, 3}) -> {init, 1};
-callback({gen_statem, start, 4}) -> {init, 1};
-callback({gen_statem, start_link, 3}) -> {init, 1};
-callback({gen_statem, start_link, 4}) -> {init, 1};
-callback({gen_statem, stop, 1}) -> {terminate, 3};
-callback({gen_statem, stop, 3}) -> {terminate, 3};
+callback({gen_statem, call, 2}) -> {handle_event, 4};
+callback({gen_statem, call, 3}) -> {handle_event, 4};
+callback({gen_statem, cast, 2}) -> {handle_event, 4};
+callback({gen_statem, start, 3}) -> {init, 1};
+callback({gen_statem, start, 4}) -> {init, 1};
+callback({gen_statem, start_link, 3}) -> {init, 1};
+callback({gen_statem, start_link, 4}) -> {init, 1};
+callback({gen_statem, stop, 1}) -> {terminate, 3};
+callback({gen_statem, stop, 3}) -> {terminate, 3};
%% supervisor
-callback({supervisor, start_link, 2}) -> {init, 1};
-callback({supervisor, start_link, 3}) -> {init, 1};
+callback({supervisor, start_link, 2}) -> {init, 1};
+callback({supervisor, start_link, 3}) -> {init, 1};
%% Everything else
-callback(_) -> undefined.
+callback(_) -> undefined.
diff --git a/apps/els_lsp/src/els_incomplete_parser.erl b/apps/els_lsp/src/els_incomplete_parser.erl
index 27ef12805..8d59ac2cb 100644
--- a/apps/els_lsp/src/els_incomplete_parser.erl
+++ b/apps/els_lsp/src/els_incomplete_parser.erl
@@ -7,24 +7,25 @@
-spec parse_after(binary(), integer()) -> [poi()].
parse_after(Text, Line) ->
- {_, AfterText} = els_text:split_at_line(Text, Line),
- {ok, POIs} = els_parser:parse(AfterText),
- POIs.
+ {_, AfterText} = els_text:split_at_line(Text, Line),
+ {ok, POIs} = els_parser:parse(AfterText),
+ POIs.
-spec parse_line(binary(), integer()) -> [poi()].
parse_line(Text, Line) ->
- LineText0 = string:trim(els_text:line(Text, Line), trailing, ",;"),
- case els_parser:parse(LineText0) of
- {ok, []} ->
- LineStr = els_utils:to_list(LineText0),
- case lists:reverse(LineStr) of
- "fo " ++ _ -> %% Kludge to parse "case foo() of"
- LineText1 = < _ end">>,
- {ok, POIs} = els_parser:parse(LineText1),
- POIs;
- _ ->
- []
- end;
- {ok, POIs} ->
- POIs
- end.
+ LineText0 = string:trim(els_text:line(Text, Line), trailing, ",;"),
+ case els_parser:parse(LineText0) of
+ {ok, []} ->
+ LineStr = els_utils:to_list(LineText0),
+ case lists:reverse(LineStr) of
+ %% Kludge to parse "case foo() of"
+ "fo " ++ _ ->
+ LineText1 = < _ end">>,
+ {ok, POIs} = els_parser:parse(LineText1),
+ POIs;
+ _ ->
+ []
+ end;
+ {ok, POIs} ->
+ POIs
+ end.
diff --git a/apps/els_lsp/src/els_indexing.erl b/apps/els_lsp/src/els_indexing.erl
index 8b2c7a616..b017eec35 100644
--- a/apps/els_lsp/src/els_indexing.erl
+++ b/apps/els_lsp/src/els_indexing.erl
@@ -3,15 +3,16 @@
-callback index(els_dt_document:item()) -> ok.
%% API
--export([ find_and_deeply_index_file/1
- , index_dir/2
- , start/0
- , maybe_start/0
- , ensure_deeply_indexed/1
- , shallow_index/2
- , deep_index/1
- , remove/1
- ]).
+-export([
+ find_and_deeply_index_file/1,
+ index_dir/2,
+ start/0,
+ maybe_start/0,
+ ensure_deeply_indexed/1,
+ shallow_index/2,
+ deep_index/1,
+ remove/1
+]).
%%==============================================================================
%% Includes
@@ -29,236 +30,258 @@
%%==============================================================================
-spec find_and_deeply_index_file(string()) ->
- {ok, uri()} | {error, any()}.
+ {ok, uri()} | {error, any()}.
find_and_deeply_index_file(FileName) ->
- SearchPaths = els_config:get(search_paths),
- case file:path_open( SearchPaths
- , els_utils:to_binary(FileName)
- , [read]
- )
- of
- {ok, IoDevice, Path} ->
- %% TODO: Avoid opening file twice
- file:close(IoDevice),
- Uri = els_uri:uri(Path),
- ensure_deeply_indexed(Uri),
- {ok, Uri};
- {error, Error} ->
- {error, Error}
- end.
+ SearchPaths = els_config:get(search_paths),
+ case
+ file:path_open(
+ SearchPaths,
+ els_utils:to_binary(FileName),
+ [read]
+ )
+ of
+ {ok, IoDevice, Path} ->
+ %% TODO: Avoid opening file twice
+ file:close(IoDevice),
+ Uri = els_uri:uri(Path),
+ ensure_deeply_indexed(Uri),
+ {ok, Uri};
+ {error, Error} ->
+ {error, Error}
+ end.
-spec is_generated_file(binary(), string()) -> boolean().
is_generated_file(Text, Tag) ->
- [Line|_] = string:split(Text, "\n", leading),
- case re:run(Line, Tag) of
- {match, _} ->
- true;
- nomatch ->
- false
- end.
+ [Line | _] = string:split(Text, "\n", leading),
+ case re:run(Line, Tag) of
+ {match, _} ->
+ true;
+ nomatch ->
+ false
+ end.
-spec ensure_deeply_indexed(uri()) -> els_dt_document:item().
ensure_deeply_indexed(Uri) ->
- {ok, #{pois := POIs} = Document} = els_utils:lookup_document(Uri),
- case POIs of
- ondemand ->
- deep_index(Document);
- _ ->
- Document
- end.
+ {ok, #{pois := POIs} = Document} = els_utils:lookup_document(Uri),
+ case POIs of
+ ondemand ->
+ deep_index(Document);
+ _ ->
+ Document
+ end.
-spec deep_index(els_dt_document:item()) -> els_dt_document:item().
deep_index(Document0) ->
- #{ id := Id
- , uri := Uri
- , text := Text
- , source := Source
- , version := Version
- } = Document0,
- {ok, POIs} = els_parser:parse(Text),
- Words = els_dt_document:get_words(Text),
- Document = Document0#{pois => POIs, words => Words},
- case els_dt_document:versioned_insert(Document) of
- ok ->
- index_signatures(Id, Uri, Text, POIs, Version),
- case Source of
- otp ->
- ok;
- S when S =:= app orelse S =:= dep ->
- index_references(Id, Uri, POIs, Version)
- end;
- {error, condition_not_satisfied} ->
- ?LOG_DEBUG("Skip indexing old version [uri=~p]", [Uri]),
- ok
- end,
- Document.
+ #{
+ id := Id,
+ uri := Uri,
+ text := Text,
+ source := Source,
+ version := Version
+ } = Document0,
+ {ok, POIs} = els_parser:parse(Text),
+ Words = els_dt_document:get_words(Text),
+ Document = Document0#{pois => POIs, words => Words},
+ case els_dt_document:versioned_insert(Document) of
+ ok ->
+ index_signatures(Id, Uri, Text, POIs, Version),
+ case Source of
+ otp ->
+ ok;
+ S when S =:= app orelse S =:= dep ->
+ index_references(Id, Uri, POIs, Version)
+ end;
+ {error, condition_not_satisfied} ->
+ ?LOG_DEBUG("Skip indexing old version [uri=~p]", [Uri]),
+ ok
+ end,
+ Document.
-spec index_signatures(atom(), uri(), binary(), [poi()], version()) -> ok.
index_signatures(Id, Uri, Text, POIs, Version) ->
- ok = els_dt_signatures:versioned_delete_by_uri(Uri, Version),
- [index_signature(Id, Text, POI, Version) || #{kind := spec} = POI <- POIs],
- ok.
+ ok = els_dt_signatures:versioned_delete_by_uri(Uri, Version),
+ [index_signature(Id, Text, POI, Version) || #{kind := spec} = POI <- POIs],
+ ok.
-spec index_signature(atom(), binary(), poi(), version()) -> ok.
index_signature(_M, _Text, #{id := undefined}, _Version) ->
- ok;
+ ok;
index_signature(M, Text, #{id := {F, A}, range := Range}, Version) ->
- #{from := From, to := To} = Range,
- Spec = els_text:range(Text, From, To),
- els_dt_signatures:versioned_insert(#{ mfa => {M, F, A}
- , spec => Spec
- , version => Version
- }).
+ #{from := From, to := To} = Range,
+ Spec = els_text:range(Text, From, To),
+ els_dt_signatures:versioned_insert(#{
+ mfa => {M, F, A},
+ spec => Spec,
+ version => Version
+ }).
-spec index_references(atom(), uri(), [poi()], version()) -> ok.
index_references(Id, Uri, POIs, Version) ->
- ok = els_dt_references:versioned_delete_by_uri(Uri, Version),
- ReferenceKinds = [ %% Function
- application
- , implicit_fun
- , import_entry
- %% Include
- , include
- , include_lib
- %% Behaviour
- , behaviour
- %% Type
- , type_application
- ],
- [index_reference(Id, Uri, POI, Version)
- || #{kind := Kind} = POI <- POIs,
- lists:member(Kind, ReferenceKinds)],
- ok.
+ ok = els_dt_references:versioned_delete_by_uri(Uri, Version),
+ %% Function
+ ReferenceKinds = [
+ application,
+ implicit_fun,
+ import_entry,
+ %% Include
+ include,
+ include_lib,
+ %% Behaviour
+ behaviour,
+ %% Type
+ type_application
+ ],
+ [
+ index_reference(Id, Uri, POI, Version)
+ || #{kind := Kind} = POI <- POIs,
+ lists:member(Kind, ReferenceKinds)
+ ],
+ ok.
-spec index_reference(atom(), uri(), poi(), version()) -> ok.
index_reference(M, Uri, #{id := {F, A}} = POI, Version) ->
- index_reference(M, Uri, POI#{id => {M, F, A}}, Version);
+ index_reference(M, Uri, POI#{id => {M, F, A}}, Version);
index_reference(_M, Uri, #{kind := Kind, id := Id, range := Range}, Version) ->
- els_dt_references:versioned_insert(Kind, #{ id => Id
- , uri => Uri
- , range => Range
- , version => Version
- }).
+ els_dt_references:versioned_insert(Kind, #{
+ id => Id,
+ uri => Uri,
+ range => Range,
+ version => Version
+ }).
-spec shallow_index(binary(), els_dt_document:source()) -> {ok, uri()}.
shallow_index(Path, Source) ->
- Uri = els_uri:uri(Path),
- {ok, Text} = file:read_file(Path),
- shallow_index(Uri, Text, Source),
- {ok, Uri}.
+ Uri = els_uri:uri(Path),
+ {ok, Text} = file:read_file(Path),
+ shallow_index(Uri, Text, Source),
+ {ok, Uri}.
-spec shallow_index(uri(), binary(), els_dt_document:source()) -> ok.
shallow_index(Uri, Text, Source) ->
- Document = els_dt_document:new(Uri, Text, Source),
- case els_dt_document:versioned_insert(Document) of
- ok ->
- #{id := Id, kind := Kind} = Document,
- ModuleItem = els_dt_document_index:new(Id, Uri, Kind),
- ok = els_dt_document_index:insert(ModuleItem);
- {error, condition_not_satisfied} ->
- ?LOG_DEBUG("Skip indexing old version [uri=~p]", [Uri]),
- ok
- end.
+ Document = els_dt_document:new(Uri, Text, Source),
+ case els_dt_document:versioned_insert(Document) of
+ ok ->
+ #{id := Id, kind := Kind} = Document,
+ ModuleItem = els_dt_document_index:new(Id, Uri, Kind),
+ ok = els_dt_document_index:insert(ModuleItem);
+ {error, condition_not_satisfied} ->
+ ?LOG_DEBUG("Skip indexing old version [uri=~p]", [Uri]),
+ ok
+ end.
-spec maybe_start() -> true | false.
maybe_start() ->
- IndexingEnabled = els_config:get(indexing_enabled),
- case IndexingEnabled of
- true ->
- start();
- false ->
- ?LOG_INFO("Skipping Indexing (disabled via InitOptions)")
- end,
- IndexingEnabled.
+ IndexingEnabled = els_config:get(indexing_enabled),
+ case IndexingEnabled of
+ true ->
+ start();
+ false ->
+ ?LOG_INFO("Skipping Indexing (disabled via InitOptions)")
+ end,
+ IndexingEnabled.
-spec start() -> ok.
start() ->
- Skip = els_config_indexing:get_skip_generated_files(),
- SkipTag = els_config_indexing:get_generated_files_tag(),
- ?LOG_INFO("Start indexing. [skip=~p] [skip_tag=~p]", [Skip, SkipTag]),
- start(<<"OTP">>, Skip, SkipTag, els_config:get(otp_paths), otp),
- start(<<"Applications">>, Skip, SkipTag, els_config:get(apps_paths), app),
- start(<<"Dependencies">>, Skip, SkipTag, els_config:get(deps_paths), dep).
+ Skip = els_config_indexing:get_skip_generated_files(),
+ SkipTag = els_config_indexing:get_generated_files_tag(),
+ ?LOG_INFO("Start indexing. [skip=~p] [skip_tag=~p]", [Skip, SkipTag]),
+ start(<<"OTP">>, Skip, SkipTag, els_config:get(otp_paths), otp),
+ start(<<"Applications">>, Skip, SkipTag, els_config:get(apps_paths), app),
+ start(<<"Dependencies">>, Skip, SkipTag, els_config:get(deps_paths), dep).
--spec start(binary(), boolean(), string(), [string()],
- els_dt_document:source()) -> ok.
+-spec start(
+ binary(),
+ boolean(),
+ string(),
+ [string()],
+ els_dt_document:source()
+) -> ok.
start(Group, Skip, SkipTag, Entries, Source) ->
- Task = fun(Dir, {Succeeded0, Skipped0, Failed0}) ->
- {Su, Sk, Fa} = index_dir(Dir, Skip, SkipTag, Source),
- {Succeeded0 + Su, Skipped0 + Sk, Failed0 + Fa}
- end,
- Config = #{ task => Task
- , entries => Entries
- , title => <<"Indexing ", Group/binary>>
- , initial_state => {0, 0, 0}
- , on_complete =>
- fun({Succeeded, Skipped, Failed}) ->
- ?LOG_INFO("Completed indexing for ~s "
- "(succeeded: ~p, skipped: ~p, failed: ~p)",
- [Group, Succeeded, Skipped, Failed])
- end
- },
- {ok, _Pid} = els_background_job:new(Config),
- ok.
+ Task = fun(Dir, {Succeeded0, Skipped0, Failed0}) ->
+ {Su, Sk, Fa} = index_dir(Dir, Skip, SkipTag, Source),
+ {Succeeded0 + Su, Skipped0 + Sk, Failed0 + Fa}
+ end,
+ Config = #{
+ task => Task,
+ entries => Entries,
+ title => <<"Indexing ", Group/binary>>,
+ initial_state => {0, 0, 0},
+ on_complete =>
+ fun({Succeeded, Skipped, Failed}) ->
+ ?LOG_INFO(
+ "Completed indexing for ~s "
+ "(succeeded: ~p, skipped: ~p, failed: ~p)",
+ [Group, Succeeded, Skipped, Failed]
+ )
+ end
+ },
+ {ok, _Pid} = els_background_job:new(Config),
+ ok.
-spec remove(uri()) -> ok.
remove(Uri) ->
- ok = els_dt_document:delete(Uri),
- ok = els_dt_document_index:delete_by_uri(Uri),
- ok = els_dt_references:delete_by_uri(Uri),
- ok = els_dt_signatures:delete_by_uri(Uri).
+ ok = els_dt_document:delete(Uri),
+ ok = els_dt_document_index:delete_by_uri(Uri),
+ ok = els_dt_references:delete_by_uri(Uri),
+ ok = els_dt_signatures:delete_by_uri(Uri).
%%==============================================================================
%% Internal functions
%%==============================================================================
-spec shallow_index(binary(), boolean(), string(), els_dt_document:source()) ->
- ok | skipped.
+ ok | skipped.
shallow_index(FullName, SkipGeneratedFiles, GeneratedFilesTag, Source) ->
- Uri = els_uri:uri(FullName),
- ?LOG_DEBUG("Shallow indexing file. [filename=~s] [uri=~s]",
- [FullName, Uri]),
- {ok, Text} = file:read_file(FullName),
- case SkipGeneratedFiles andalso is_generated_file(Text, GeneratedFilesTag) of
- true ->
- ?LOG_DEBUG("Skip indexing for generated file ~p", [Uri]),
- skipped;
- false ->
- shallow_index(Uri, Text, Source)
- end.
+ Uri = els_uri:uri(FullName),
+ ?LOG_DEBUG(
+ "Shallow indexing file. [filename=~s] [uri=~s]",
+ [FullName, Uri]
+ ),
+ {ok, Text} = file:read_file(FullName),
+ case SkipGeneratedFiles andalso is_generated_file(Text, GeneratedFilesTag) of
+ true ->
+ ?LOG_DEBUG("Skip indexing for generated file ~p", [Uri]),
+ skipped;
+ false ->
+ shallow_index(Uri, Text, Source)
+ end.
-spec index_dir(string(), els_dt_document:source()) ->
- {non_neg_integer(), non_neg_integer(), non_neg_integer()}.
+ {non_neg_integer(), non_neg_integer(), non_neg_integer()}.
index_dir(Dir, Source) ->
- Skip = els_config_indexing:get_skip_generated_files(),
- SkipTag = els_config_indexing:get_generated_files_tag(),
- index_dir(Dir, Skip, SkipTag, Source).
+ Skip = els_config_indexing:get_skip_generated_files(),
+ SkipTag = els_config_indexing:get_generated_files_tag(),
+ index_dir(Dir, Skip, SkipTag, Source).
-spec index_dir(string(), boolean(), string(), els_dt_document:source()) ->
- {non_neg_integer(), non_neg_integer(), non_neg_integer()}.
+ {non_neg_integer(), non_neg_integer(), non_neg_integer()}.
index_dir(Dir, Skip, SkipTag, Source) ->
- ?LOG_DEBUG("Indexing directory. [dir=~s]", [Dir]),
- F = fun(FileName, {Succeeded, Skipped, Failed}) ->
- BinaryName = els_utils:to_binary(FileName),
- case shallow_index(BinaryName, Skip, SkipTag, Source) of
+ ?LOG_DEBUG("Indexing directory. [dir=~s]", [Dir]),
+ F = fun(FileName, {Succeeded, Skipped, Failed}) ->
+ BinaryName = els_utils:to_binary(FileName),
+ case shallow_index(BinaryName, Skip, SkipTag, Source) of
ok -> {Succeeded + 1, Skipped, Failed};
skipped -> {Succeeded, Skipped + 1, Failed}
- end
- end,
- Filter = fun(Path) ->
- Ext = filename:extension(Path),
- lists:member(Ext, [".erl", ".hrl", ".escript"])
- end,
+ end
+ end,
+ Filter = fun(Path) ->
+ Ext = filename:extension(Path),
+ lists:member(Ext, [".erl", ".hrl", ".escript"])
+ end,
- {Time, {Succeeded, Skipped, Failed}} = timer:tc( els_utils
- , fold_files
- , [ F
- , Filter
- , Dir
- , {0, 0, 0}
- ]
- ),
- ?LOG_DEBUG("Finished indexing directory. [dir=~s] [time=~p] "
- "[succeeded=~p] [skipped=~p] [failed=~p]",
- [Dir, Time/1000/1000, Succeeded, Skipped, Failed]),
- {Succeeded, Skipped, Failed}.
+ {Time, {Succeeded, Skipped, Failed}} = timer:tc(
+ els_utils,
+ fold_files,
+ [
+ F,
+ Filter,
+ Dir,
+ {0, 0, 0}
+ ]
+ ),
+ ?LOG_DEBUG(
+ "Finished indexing directory. [dir=~s] [time=~p] "
+ "[succeeded=~p] [skipped=~p] [failed=~p]",
+ [Dir, Time / 1000 / 1000, Succeeded, Skipped, Failed]
+ ),
+ {Succeeded, Skipped, Failed}.
diff --git a/apps/els_lsp/src/els_log_notification.erl b/apps/els_lsp/src/els_log_notification.erl
index 1a4572b9e..8bdf08fe8 100644
--- a/apps/els_lsp/src/els_log_notification.erl
+++ b/apps/els_lsp/src/els_log_notification.erl
@@ -9,16 +9,19 @@
-define(LSP_MESSAGE_TYPE_INFO, 3).
-define(LSP_MESSAGE_TYPE_LOG, 4).
--type lsp_message_type() :: ?LSP_MESSAGE_TYPE_ERROR |
- ?LSP_MESSAGE_TYPE_WARNING |
- ?LSP_MESSAGE_TYPE_INFO |
- ?LSP_MESSAGE_TYPE_LOG.
+-type lsp_message_type() ::
+ ?LSP_MESSAGE_TYPE_ERROR
+ | ?LSP_MESSAGE_TYPE_WARNING
+ | ?LSP_MESSAGE_TYPE_INFO
+ | ?LSP_MESSAGE_TYPE_LOG.
-spec log(logger:log_event(), logger:config_handler()) -> ok.
log(#{level := Level} = LogEvent, _Config) ->
try
- Msg = logger_formatter:format( LogEvent
- , #{ template => ?LSP_LOG_FORMAT}),
+ Msg = logger_formatter:format(
+ LogEvent,
+ #{template => ?LSP_LOG_FORMAT}
+ ),
els_server:send_notification(<<"window/logMessage">>, #{
<<"message">> => unicode:characters_to_binary(Msg),
<<"type">> => otp_log_level_to_lsp(Level)
@@ -26,8 +29,10 @@ log(#{level := Level} = LogEvent, _Config) ->
catch
E:R:ST ->
ErrMsg =
- io_lib:format( "Logger Exception ({~w, ~w}): ~n~p"
- , [E, R, ST]),
+ io_lib:format(
+ "Logger Exception ({~w, ~w}): ~n~p",
+ [E, R, ST]
+ ),
els_server:send_notification(<<"window/logMessage">>, #{
<<"message">> => unicode:characters_to_binary(ErrMsg),
<<"type">> => 1
diff --git a/apps/els_lsp/src/els_markup_content.erl b/apps/els_lsp/src/els_markup_content.erl
index 4498f942f..453e35cb2 100644
--- a/apps/els_lsp/src/els_markup_content.erl
+++ b/apps/els_lsp/src/els_markup_content.erl
@@ -1,23 +1,32 @@
-module(els_markup_content).
--export([ new/1 ]).
+-export([new/1]).
-include_lib("els_core/include/els_core.hrl").
--type doc_entry() :: { 'text' | 'h1' | 'h2' | 'h3' | 'h4'
- | 'code_block_line' | 'code_block_begin' | 'code_block_end'
- | 'code_line' | 'code_inline'
- , string()
- }.
--export_type([ doc_entry/0 ]).
+-type doc_entry() :: {
+ 'text'
+ | 'h1'
+ | 'h2'
+ | 'h3'
+ | 'h4'
+ | 'code_block_line'
+ | 'code_block_begin'
+ | 'code_block_end'
+ | 'code_line'
+ | 'code_inline',
+ string()
+}.
+-export_type([doc_entry/0]).
-spec new([doc_entry()]) -> markup_content().
new(Entries) ->
- MarkupKind = markup_kind(),
- Value = value(MarkupKind, Entries),
- #{ kind => MarkupKind
- , value => Value
- }.
+ MarkupKind = markup_kind(),
+ Value = value(MarkupKind, Entries),
+ #{
+ kind => MarkupKind,
+ value => Value
+ }.
%%------------------------------------------------------------------------------
%% @doc markup_kind/0
@@ -27,52 +36,52 @@ new(Entries) ->
%%------------------------------------------------------------------------------
-spec markup_kind() -> markup_kind().
markup_kind() ->
- ContentFormat =
- case els_config:get(capabilities) of
- #{<<"textDocument">> := #{<<"hover">> := #{<<"contentFormat">> := X}}} ->
- X;
- _ ->
- []
- end,
- case lists:member(atom_to_binary(?MARKDOWN, utf8), ContentFormat) of
- true -> ?MARKDOWN;
- false -> ?PLAINTEXT
- end.
+ ContentFormat =
+ case els_config:get(capabilities) of
+ #{<<"textDocument">> := #{<<"hover">> := #{<<"contentFormat">> := X}}} ->
+ X;
+ _ ->
+ []
+ end,
+ case lists:member(atom_to_binary(?MARKDOWN, utf8), ContentFormat) of
+ true -> ?MARKDOWN;
+ false -> ?PLAINTEXT
+ end.
-spec value(markup_kind(), [doc_entry()]) -> binary().
value(MarkupKind, Entries) ->
- Separator = separator(MarkupKind),
- FormattedEntries = [format_entry(Entry, MarkupKind) || Entry <- Entries],
- unicode:characters_to_binary(string:join(FormattedEntries, Separator)).
+ Separator = separator(MarkupKind),
+ FormattedEntries = [format_entry(Entry, MarkupKind) || Entry <- Entries],
+ unicode:characters_to_binary(string:join(FormattedEntries, Separator)).
-spec separator(markup_kind()) -> string().
separator(?MARKDOWN) ->
- "\n\n";
+ "\n\n";
separator(?PLAINTEXT) ->
- "\n".
+ "\n".
-spec format_entry(doc_entry(), markup_kind()) -> string().
format_entry({h1, String}, _MarkupKind) ->
- "# " ++ String;
+ "# " ++ String;
format_entry({h2, String}, _MarkupKind) ->
- "## " ++ String;
+ "## " ++ String;
format_entry({h3, String}, _MarkupKind) ->
- "### " ++ String;
+ "### " ++ String;
format_entry({h4, String}, _MarkupKind) ->
- "#### " ++ String;
+ "#### " ++ String;
format_entry({code_block_line, String}, _MarkupKind) ->
- " " ++ String;
+ " " ++ String;
format_entry({code_block_begin, _Language}, ?PLAINTEXT) ->
- "";
+ "";
format_entry({code_block_begin, Language}, ?MARKDOWN) ->
- "```" ++ Language;
+ "```" ++ Language;
format_entry({code_block_end, _Language}, ?PLAINTEXT) ->
- "";
+ "";
format_entry({code_block_end, _Language}, ?MARKDOWN) ->
- "```";
+ "```";
format_entry({code_inline, String}, ?PLAINTEXT) ->
- String;
+ String;
format_entry({code_line, String}, ?MARKDOWN) ->
- "```erlang\n" ++ String ++ "\n```";
+ "```erlang\n" ++ String ++ "\n```";
format_entry({_Kind, String}, _MarkupKind) ->
- String.
+ String.
diff --git a/apps/els_lsp/src/els_methods.erl b/apps/els_lsp/src/els_methods.erl
index a0549f78d..bd4ed2217 100644
--- a/apps/els_lsp/src/els_methods.erl
+++ b/apps/els_lsp/src/els_methods.erl
@@ -1,39 +1,39 @@
-module(els_methods).
--export([ dispatch/4
- ]).
-
--export([ exit/2
- , initialize/2
- , initialized/2
- , shutdown/2
- , textdocument_completion/2
- , completionitem_resolve/2
- , textdocument_didopen/2
- , textdocument_didchange/2
- , textdocument_didsave/2
- , textdocument_didclose/2
- , textdocument_documentsymbol/2
- , textdocument_hover/2
- , textdocument_definition/2
- , textdocument_implementation/2
- , textdocument_references/2
- , textdocument_documenthighlight/2
- , textdocument_formatting/2
- , textdocument_rangeformatting/2
- , textdocument_ontypeformatting/2
- , textdocument_foldingrange/2
- , workspace_didchangeconfiguration/2
- , textdocument_codeaction/2
- , textdocument_codelens/2
- , textdocument_rename/2
- , textdocument_preparecallhierarchy/2
- , callhierarchy_incomingcalls/2
- , callhierarchy_outgoingcalls/2
- , workspace_executecommand/2
- , workspace_didchangewatchedfiles/2
- , workspace_symbol/2
- ]).
+-export([dispatch/4]).
+
+-export([
+ exit/2,
+ initialize/2,
+ initialized/2,
+ shutdown/2,
+ textdocument_completion/2,
+ completionitem_resolve/2,
+ textdocument_didopen/2,
+ textdocument_didchange/2,
+ textdocument_didsave/2,
+ textdocument_didclose/2,
+ textdocument_documentsymbol/2,
+ textdocument_hover/2,
+ textdocument_definition/2,
+ textdocument_implementation/2,
+ textdocument_references/2,
+ textdocument_documenthighlight/2,
+ textdocument_formatting/2,
+ textdocument_rangeformatting/2,
+ textdocument_ontypeformatting/2,
+ textdocument_foldingrange/2,
+ workspace_didchangeconfiguration/2,
+ textdocument_codeaction/2,
+ textdocument_codelens/2,
+ textdocument_rename/2,
+ textdocument_preparecallhierarchy/2,
+ callhierarchy_incomingcalls/2,
+ callhierarchy_outgoingcalls/2,
+ workspace_executecommand/2,
+ workspace_didchangewatchedfiles/2,
+ workspace_symbol/2
+]).
%%==============================================================================
%% Includes
@@ -41,14 +41,15 @@
-include("els_lsp.hrl").
-include_lib("kernel/include/logger.hrl").
--type method_name() :: binary().
--type state() :: map().
--type params() :: map().
--type result() :: {response, params() | null, state()}
- | {error, params(), state()}
- | {noresponse, state()}
- | {noresponse, pid(), state()}
- | {notification, binary(), params(), state()}.
+-type method_name() :: binary().
+-type state() :: map().
+-type params() :: map().
+-type result() ::
+ {response, params() | null, state()}
+ | {error, params(), state()}
+ | {noresponse, state()}
+ | {noresponse, pid(), state()}
+ | {notification, binary(), params(), state()}.
-type request_type() :: notification | request.
%%==============================================================================
@@ -56,72 +57,80 @@
%%==============================================================================
-spec dispatch(method_name(), params(), request_type(), state()) -> result().
dispatch(<<"$/", Method/binary>>, Params, notification, State) ->
- Msg = "Ignoring $/ notification [method=~p] [params=~p]",
- Fmt = [Method, Params],
- ?LOG_DEBUG(Msg, Fmt),
- {noresponse, State};
+ Msg = "Ignoring $/ notification [method=~p] [params=~p]",
+ Fmt = [Method, Params],
+ ?LOG_DEBUG(Msg, Fmt),
+ {noresponse, State};
dispatch(<<"$/", Method/binary>>, Params, request, State) ->
- Msg = "Ignoring $/ request [method=~p] [params=~p]",
- Fmt = [Method, Params],
- ?LOG_DEBUG(Msg, Fmt),
- Error = #{ code => ?ERR_METHOD_NOT_FOUND
- , message => <<"Method not found: ", Method/binary>>
- },
- {error, Error, State};
+ Msg = "Ignoring $/ request [method=~p] [params=~p]",
+ Fmt = [Method, Params],
+ ?LOG_DEBUG(Msg, Fmt),
+ Error = #{
+ code => ?ERR_METHOD_NOT_FOUND,
+ message => <<"Method not found: ", Method/binary>>
+ },
+ {error, Error, State};
dispatch(Method, Params, _Type, State) ->
- Function = method_to_function_name(Method),
- ?LOG_DEBUG("Dispatching request [method=~p] [params=~p]", [Method, Params]),
- try do_dispatch(Function, Params, State)
- catch
- error:undef ->
- not_implemented_method(Method, State);
- Type:Reason:Stack ->
- ?LOG_ERROR( "Unexpected error [type=~p] [error=~p] [stack=~p]"
- , [Type, Reason, Stack]),
- Error = #{ code => ?ERR_UNKNOWN_ERROR_CODE
- , message => <<"Unexpected error while ", Method/binary>>
- },
- {error, Error, State}
- end.
+ Function = method_to_function_name(Method),
+ ?LOG_DEBUG("Dispatching request [method=~p] [params=~p]", [Method, Params]),
+ try
+ do_dispatch(Function, Params, State)
+ catch
+ error:undef ->
+ not_implemented_method(Method, State);
+ Type:Reason:Stack ->
+ ?LOG_ERROR(
+ "Unexpected error [type=~p] [error=~p] [stack=~p]",
+ [Type, Reason, Stack]
+ ),
+ Error = #{
+ code => ?ERR_UNKNOWN_ERROR_CODE,
+ message => <<"Unexpected error while ", Method/binary>>
+ },
+ {error, Error, State}
+ end.
-spec do_dispatch(atom(), params(), state()) -> result().
do_dispatch(exit, Params, State) ->
- els_methods:exit(Params, State);
+ els_methods:exit(Params, State);
do_dispatch(_Function, _Params, #{status := shutdown} = State) ->
- Message = <<"Server is shutting down">>,
- Result = #{ code => ?ERR_INVALID_REQUEST
- , message => Message
- },
- {error, Result, State};
+ Message = <<"Server is shutting down">>,
+ Result = #{
+ code => ?ERR_INVALID_REQUEST,
+ message => Message
+ },
+ {error, Result, State};
do_dispatch(initialize, Params, State) ->
- els_methods:initialize(Params, State);
+ els_methods:initialize(Params, State);
do_dispatch(Function, Params, #{status := initialized} = State) ->
- els_methods:Function(Params, State);
+ els_methods:Function(Params, State);
do_dispatch(_Function, _Params, State) ->
- Message = <<"The server is not fully initialized yet, please wait.">>,
- Result = #{ code => ?ERR_SERVER_NOT_INITIALIZED
- , message => Message
- },
- {error, Result, State}.
+ Message = <<"The server is not fully initialized yet, please wait.">>,
+ Result = #{
+ code => ?ERR_SERVER_NOT_INITIALIZED,
+ message => Message
+ },
+ {error, Result, State}.
-spec not_implemented_method(method_name(), state()) -> result().
not_implemented_method(Method, State) ->
- ?LOG_WARNING("[Method not implemented] [method=~s]", [Method]),
- Message = <<"Method not implemented: ", Method/binary>>,
- Method1 = <<"window/showMessage">>,
- Params = #{ type => ?MESSAGE_TYPE_INFO
- , message => Message
- },
- {notification, Method1, Params, State}.
+ ?LOG_WARNING("[Method not implemented] [method=~s]", [Method]),
+ Message = <<"Method not implemented: ", Method/binary>>,
+ Method1 = <<"window/showMessage">>,
+ Params = #{
+ type => ?MESSAGE_TYPE_INFO,
+ message => Message
+ },
+ {notification, Method1, Params, State}.
-spec method_to_function_name(method_name()) -> atom().
method_to_function_name(<<"$/", Method/binary>>) ->
- method_to_function_name(<<"$_", Method/binary>>);
+ method_to_function_name(<<"$_", Method/binary>>);
method_to_function_name(Method) ->
- Replaced = string:replace(Method, <<"/">>, <<"_">>),
- Lower = string:lowercase(Replaced),
- Binary = els_utils:to_binary(Lower),
- binary_to_atom(Binary, utf8).
+ Replaced = string:replace(Method, <<"/">>, <<"_">>),
+ Lower = string:lowercase(Replaced),
+ Binary = els_utils:to_binary(Lower),
+ binary_to_atom(Binary, utf8).
%%==============================================================================
%% Initialize
@@ -129,10 +138,10 @@ method_to_function_name(Method) ->
-spec initialize(params(), state()) -> result().
initialize(Params, State) ->
- Provider = els_general_provider,
- Request = {initialize, Params},
- {response, Response} = els_provider:handle_request(Provider, Request),
- {response, Response, State#{status => initialized}}.
+ Provider = els_general_provider,
+ Request = {initialize, Params},
+ {response, Response} = els_provider:handle_request(Provider, Request),
+ {response, Response, State#{status => initialized}}.
%%==============================================================================
%% Initialized
@@ -140,23 +149,24 @@ initialize(Params, State) ->
-spec initialized(params(), state()) -> result().
initialized(Params, State) ->
- Provider = els_general_provider,
- Request = {initialized, Params},
- {response, _Response} = els_provider:handle_request(Provider, Request),
- %% Report to the user the server version
- {ok, Version} = application:get_key(?APP, vsn),
- ?LOG_INFO("initialized: [App=~p] [Version=~p]", [?APP, Version]),
- BinVersion = els_utils:to_binary(Version),
- Root = filename:basename(els_uri:path(els_config:get(root_uri))),
- OTPVersion = els_utils:to_binary(erlang:system_info(otp_release)),
- Message = <<"Erlang LS (in ", Root/binary, "), version: "
- , BinVersion/binary, ", OTP version: "
- , OTPVersion/binary>>,
- NMethod = <<"window/showMessage">>,
- NParams = #{ type => ?MESSAGE_TYPE_INFO
- , message => Message
- },
- {notification, NMethod, NParams, State}.
+ Provider = els_general_provider,
+ Request = {initialized, Params},
+ {response, _Response} = els_provider:handle_request(Provider, Request),
+ %% Report to the user the server version
+ {ok, Version} = application:get_key(?APP, vsn),
+ ?LOG_INFO("initialized: [App=~p] [Version=~p]", [?APP, Version]),
+ BinVersion = els_utils:to_binary(Version),
+ Root = filename:basename(els_uri:path(els_config:get(root_uri))),
+ OTPVersion = els_utils:to_binary(erlang:system_info(otp_release)),
+ Message =
+ <<"Erlang LS (in ", Root/binary, "), version: ", BinVersion/binary, ", OTP version: ",
+ OTPVersion/binary>>,
+ NMethod = <<"window/showMessage">>,
+ NParams = #{
+ type => ?MESSAGE_TYPE_INFO,
+ message => Message
+ },
+ {notification, NMethod, NParams, State}.
%%==============================================================================
%% shutdown
@@ -164,10 +174,10 @@ initialized(Params, State) ->
-spec shutdown(params(), state()) -> result().
shutdown(Params, State) ->
- Provider = els_general_provider,
- Request = {shutdown, Params},
- {response, Response} = els_provider:handle_request(Provider, Request),
- {response, Response, State#{status => shutdown}}.
+ Provider = els_general_provider,
+ Request = {shutdown, Params},
+ {response, Response} = els_provider:handle_request(Provider, Request),
+ {response, Response, State#{status => shutdown}}.
%%==============================================================================
%% exit
@@ -175,10 +185,10 @@ shutdown(Params, State) ->
-spec exit(params(), state()) -> no_return().
exit(_Params, State) ->
- Provider = els_general_provider,
- Request = {exit, #{status => maps:get(status, State, undefined)}},
- {response, _Response} = els_provider:handle_request(Provider, Request),
- {noresponse, #{}}.
+ Provider = els_general_provider,
+ Request = {exit, #{status => maps:get(status, State, undefined)}},
+ {response, _Response} = els_provider:handle_request(Provider, Request),
+ {noresponse, #{}}.
%%==============================================================================
%% textDocument/didopen
@@ -186,11 +196,11 @@ exit(_Params, State) ->
-spec textdocument_didopen(params(), state()) -> result().
textdocument_didopen(Params, #{open_buffers := OpenBuffers} = State) ->
- #{<<"textDocument">> := #{ <<"uri">> := Uri}} = Params,
- Provider = els_text_synchronization_provider,
- Request = {did_open, Params},
- noresponse = els_provider:handle_request(Provider, Request),
- {noresponse, State#{open_buffers => sets:add_element(Uri, OpenBuffers)}}.
+ #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
+ Provider = els_text_synchronization_provider,
+ Request = {did_open, Params},
+ noresponse = els_provider:handle_request(Provider, Request),
+ {noresponse, State#{open_buffers => sets:add_element(Uri, OpenBuffers)}}.
%%==============================================================================
%% textDocument/didchange
@@ -198,12 +208,12 @@ textdocument_didopen(Params, #{open_buffers := OpenBuffers} = State) ->
-spec textdocument_didchange(params(), state()) -> result().
textdocument_didchange(Params, State) ->
- #{<<"textDocument">> := #{ <<"uri">> := Uri}} = Params,
- els_provider:cancel_request_by_uri(Uri),
- Provider = els_text_synchronization_provider,
- Request = {did_change, Params},
- els_provider:handle_request(Provider, Request),
- {noresponse, State}.
+ #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
+ els_provider:cancel_request_by_uri(Uri),
+ Provider = els_text_synchronization_provider,
+ Request = {did_change, Params},
+ els_provider:handle_request(Provider, Request),
+ {noresponse, State}.
%%==============================================================================
%% textDocument/didsave
@@ -211,10 +221,10 @@ textdocument_didchange(Params, State) ->
-spec textdocument_didsave(params(), state()) -> result().
textdocument_didsave(Params, State) ->
- Provider = els_text_synchronization_provider,
- Request = {did_save, Params},
- noresponse = els_provider:handle_request(Provider, Request),
- {noresponse, State}.
+ Provider = els_text_synchronization_provider,
+ Request = {did_save, Params},
+ noresponse = els_provider:handle_request(Provider, Request),
+ {noresponse, State}.
%%==============================================================================
%% textDocument/didclose
@@ -222,11 +232,11 @@ textdocument_didsave(Params, State) ->
-spec textdocument_didclose(params(), state()) -> result().
textdocument_didclose(Params, #{open_buffers := OpenBuffers} = State) ->
- #{<<"textDocument">> := #{ <<"uri">> := Uri}} = Params,
- Provider = els_text_synchronization_provider,
- Request = {did_close, Params},
- noresponse = els_provider:handle_request(Provider, Request),
- {noresponse, State#{open_buffers => sets:del_element(Uri, OpenBuffers)}}.
+ #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
+ Provider = els_text_synchronization_provider,
+ Request = {did_close, Params},
+ noresponse = els_provider:handle_request(Provider, Request),
+ {noresponse, State#{open_buffers => sets:del_element(Uri, OpenBuffers)}}.
%%==============================================================================
%% textdocument/documentSymbol
@@ -234,10 +244,10 @@ textdocument_didclose(Params, #{open_buffers := OpenBuffers} = State) ->
-spec textdocument_documentsymbol(params(), state()) -> result().
textdocument_documentsymbol(Params, State) ->
- Provider = els_document_symbol_provider,
- Request = {document_symbol, Params},
- {response, Response} = els_provider:handle_request(Provider, Request),
- {response, Response, State}.
+ Provider = els_document_symbol_provider,
+ Request = {document_symbol, Params},
+ {response, Response} = els_provider:handle_request(Provider, Request),
+ {response, Response, State}.
%%==============================================================================
%% textDocument/hover
@@ -245,9 +255,9 @@ textdocument_documentsymbol(Params, State) ->
-spec textdocument_hover(params(), state()) -> result().
textdocument_hover(Params, State) ->
- Provider = els_hover_provider,
- {async, Job} = els_provider:handle_request(Provider, {hover, Params}),
- {noresponse, Job, State}.
+ Provider = els_hover_provider,
+ {async, Job} = els_provider:handle_request(Provider, {hover, Params}),
+ {noresponse, Job, State}.
%%==============================================================================
%% textDocument/completion
@@ -255,10 +265,10 @@ textdocument_hover(Params, State) ->
-spec textdocument_completion(params(), state()) -> result().
textdocument_completion(Params, State) ->
- Provider = els_completion_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {completion, Params}),
- {response, Response, State}.
+ Provider = els_completion_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {completion, Params}),
+ {response, Response, State}.
%%==============================================================================
%% completionItem/resolve
@@ -266,10 +276,10 @@ textdocument_completion(Params, State) ->
-spec completionitem_resolve(params(), state()) -> result().
completionitem_resolve(Params, State) ->
- Provider = els_completion_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {resolve, Params}),
- {response, Response, State}.
+ Provider = els_completion_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {resolve, Params}),
+ {response, Response, State}.
%%==============================================================================
%% textDocument/definition
@@ -277,10 +287,10 @@ completionitem_resolve(Params, State) ->
-spec textdocument_definition(params(), state()) -> result().
textdocument_definition(Params, State) ->
- Provider = els_definition_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {definition, Params}),
- {response, Response, State}.
+ Provider = els_definition_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {definition, Params}),
+ {response, Response, State}.
%%==============================================================================
%% textDocument/references
@@ -288,10 +298,10 @@ textdocument_definition(Params, State) ->
-spec textdocument_references(params(), state()) -> result().
textdocument_references(Params, State) ->
- Provider = els_references_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {references, Params}),
- {response, Response, State}.
+ Provider = els_references_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {references, Params}),
+ {response, Response, State}.
%%==============================================================================
%% textDocument/documentHightlight
@@ -299,10 +309,10 @@ textdocument_references(Params, State) ->
-spec textdocument_documenthighlight(params(), state()) -> result().
textdocument_documenthighlight(Params, State) ->
- Provider = els_document_highlight_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {document_highlight, Params}),
- {response, Response, State}.
+ Provider = els_document_highlight_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {document_highlight, Params}),
+ {response, Response, State}.
%%==============================================================================
%% textDocument/formatting
@@ -310,10 +320,10 @@ textdocument_documenthighlight(Params, State) ->
-spec textdocument_formatting(params(), state()) -> result().
textdocument_formatting(Params, State) ->
- Provider = els_formatting_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {document_formatting, Params}),
- {response, Response, State}.
+ Provider = els_formatting_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {document_formatting, Params}),
+ {response, Response, State}.
%%==============================================================================
%% textDocument/rangeFormatting
@@ -321,10 +331,10 @@ textdocument_formatting(Params, State) ->
-spec textdocument_rangeformatting(params(), state()) -> result().
textdocument_rangeformatting(Params, State) ->
- Provider = els_formatting_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {document_rangeformatting, Params}),
- {response, Response, State}.
+ Provider = els_formatting_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {document_rangeformatting, Params}),
+ {response, Response, State}.
%%==============================================================================
%% textDocument/onTypeFormatting
@@ -332,10 +342,10 @@ textdocument_rangeformatting(Params, State) ->
-spec textdocument_ontypeformatting(params(), state()) -> result().
textdocument_ontypeformatting(Params, State) ->
- Provider = els_formatting_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {document_ontypeformatting, Params}),
- {response, Response, State}.
+ Provider = els_formatting_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {document_ontypeformatting, Params}),
+ {response, Response, State}.
%%==============================================================================
%% textDocument/foldingRange
@@ -343,10 +353,10 @@ textdocument_ontypeformatting(Params, State) ->
-spec textdocument_foldingrange(params(), state()) -> result().
textdocument_foldingrange(Params, State) ->
- Provider = els_folding_range_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {document_foldingrange, Params}),
- {response, Response, State}.
+ Provider = els_folding_range_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {document_foldingrange, Params}),
+ {response, Response, State}.
%%==============================================================================
%% textDocument/implementation
@@ -354,10 +364,10 @@ textdocument_foldingrange(Params, State) ->
-spec textdocument_implementation(params(), state()) -> result().
textdocument_implementation(Params, State) ->
- Provider = els_implementation_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {implementation, Params}),
- {response, Response, State}.
+ Provider = els_implementation_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {implementation, Params}),
+ {response, Response, State}.
%%==============================================================================
%% workspace/didChangeConfiguration
@@ -365,9 +375,9 @@ textdocument_implementation(Params, State) ->
-spec workspace_didchangeconfiguration(params(), state()) -> result().
workspace_didchangeconfiguration(_Params, State) ->
- %% Some clients send this notification on startup, even though we
- %% have no server-side config. So swallow it without complaining.
- {noresponse, State}.
+ %% Some clients send this notification on startup, even though we
+ %% have no server-side config. So swallow it without complaining.
+ {noresponse, State}.
%%==============================================================================
%% textDocument/codeAction
@@ -375,10 +385,10 @@ workspace_didchangeconfiguration(_Params, State) ->
-spec textdocument_codeaction(params(), state()) -> result().
textdocument_codeaction(Params, State) ->
- Provider = els_code_action_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {document_codeaction, Params}),
- {response, Response, State}.
+ Provider = els_code_action_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {document_codeaction, Params}),
+ {response, Response, State}.
%%==============================================================================
%% textDocument/codeLens
@@ -386,10 +396,10 @@ textdocument_codeaction(Params, State) ->
-spec textdocument_codelens(params(), state()) -> result().
textdocument_codelens(Params, State) ->
- Provider = els_code_lens_provider,
- {async, Job} =
- els_provider:handle_request(Provider, {document_codelens, Params}),
- {noresponse, Job, State}.
+ Provider = els_code_lens_provider,
+ {async, Job} =
+ els_provider:handle_request(Provider, {document_codelens, Params}),
+ {noresponse, Job, State}.
%%==============================================================================
%% textDocument/rename
@@ -397,10 +407,10 @@ textdocument_codelens(Params, State) ->
-spec textdocument_rename(params(), state()) -> result().
textdocument_rename(Params, State) ->
- Provider = els_rename_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {rename, Params}),
- {response, Response, State}.
+ Provider = els_rename_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {rename, Params}),
+ {response, Response, State}.
%%==============================================================================
%% textDocument/preparePreparecallhierarchy
@@ -408,10 +418,10 @@ textdocument_rename(Params, State) ->
-spec textdocument_preparecallhierarchy(params(), state()) -> result().
textdocument_preparecallhierarchy(Params, State) ->
- Provider = els_call_hierarchy_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {prepare, Params}),
- {response, Response, State}.
+ Provider = els_call_hierarchy_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {prepare, Params}),
+ {response, Response, State}.
%%==============================================================================
%% callHierarchy/incomingCalls
@@ -419,10 +429,10 @@ textdocument_preparecallhierarchy(Params, State) ->
-spec callhierarchy_incomingcalls(params(), state()) -> result().
callhierarchy_incomingcalls(Params, State) ->
- Provider = els_call_hierarchy_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {incoming_calls, Params}),
- {response, Response, State}.
+ Provider = els_call_hierarchy_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {incoming_calls, Params}),
+ {response, Response, State}.
%%==============================================================================
%% callHierarchy/outgoingCalls
@@ -430,10 +440,10 @@ callhierarchy_incomingcalls(Params, State) ->
-spec callhierarchy_outgoingcalls(params(), state()) -> result().
callhierarchy_outgoingcalls(Params, State) ->
- Provider = els_call_hierarchy_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {outgoing_calls, Params}),
- {response, Response, State}.
+ Provider = els_call_hierarchy_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {outgoing_calls, Params}),
+ {response, Response, State}.
%%==============================================================================
%% workspace/executeCommand
@@ -441,10 +451,10 @@ callhierarchy_outgoingcalls(Params, State) ->
-spec workspace_executecommand(params(), state()) -> result().
workspace_executecommand(Params, State) ->
- Provider = els_execute_command_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {workspace_executecommand, Params}),
- {response, Response, State}.
+ Provider = els_execute_command_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {workspace_executecommand, Params}),
+ {response, Response, State}.
%%==============================================================================
%% workspace/didChangeWatchedFiles
@@ -452,15 +462,18 @@ workspace_executecommand(Params, State) ->
-spec workspace_didchangewatchedfiles(map(), state()) -> result().
workspace_didchangewatchedfiles(Params0, State) ->
- #{open_buffers := OpenBuffers} = State,
- #{<<"changes">> := Changes0} = Params0,
- Changes = [C || #{<<"uri">> := Uri} = C <- Changes0,
- not sets:is_element(Uri, OpenBuffers)],
- Params = Params0#{<<"changes">> => Changes},
- Provider = els_text_synchronization_provider,
- Request = {did_change_watched_files, Params},
- noresponse = els_provider:handle_request(Provider, Request),
- {noresponse, State}.
+ #{open_buffers := OpenBuffers} = State,
+ #{<<"changes">> := Changes0} = Params0,
+ Changes = [
+ C
+ || #{<<"uri">> := Uri} = C <- Changes0,
+ not sets:is_element(Uri, OpenBuffers)
+ ],
+ Params = Params0#{<<"changes">> => Changes},
+ Provider = els_text_synchronization_provider,
+ Request = {did_change_watched_files, Params},
+ noresponse = els_provider:handle_request(Provider, Request),
+ {noresponse, State}.
%%==============================================================================
%% workspace/symbol
@@ -468,7 +481,7 @@ workspace_didchangewatchedfiles(Params0, State) ->
-spec workspace_symbol(map(), state()) -> result().
workspace_symbol(Params, State) ->
- Provider = els_workspace_symbol_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {symbol, Params}),
- {response, Response, State}.
+ Provider = els_workspace_symbol_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {symbol, Params}),
+ {response, Response, State}.
diff --git a/apps/els_lsp/src/els_parser.erl b/apps/els_lsp/src/els_parser.erl
index de6ff3d3c..f860ee837 100644
--- a/apps/els_lsp/src/els_parser.erl
+++ b/apps/els_lsp/src/els_parser.erl
@@ -6,15 +6,17 @@
%%==============================================================================
%% Exports
%%==============================================================================
--export([ parse/1
- , parse_incomplete_text/2
- , points_of_interest/1
- ]).
+-export([
+ parse/1,
+ parse_incomplete_text/2,
+ points_of_interest/1
+]).
%% For manual use only, to test the parser
--export([ parse_file/1
- , parse_text/1
- ]).
+-export([
+ parse_file/1,
+ parse_text/1
+]).
%%==============================================================================
%% Includes
@@ -29,42 +31,42 @@
%%==============================================================================
-spec parse(binary()) -> {ok, [poi()]}.
parse(Text) ->
- String = els_utils:to_list(Text),
- case erlfmt:read_nodes_string("nofile", String) of
- {ok, Forms, _ErrorInfo} ->
- {ok, lists:flatten(parse_forms(Forms))};
- {error, _ErrorInfo} ->
- {ok, []}
- end.
+ String = els_utils:to_list(Text),
+ case erlfmt:read_nodes_string("nofile", String) of
+ {ok, Forms, _ErrorInfo} ->
+ {ok, lists:flatten(parse_forms(Forms))};
+ {error, _ErrorInfo} ->
+ {ok, []}
+ end.
-spec parse_file(file:name_all()) -> {ok, [tree()]} | {error, term()}.
parse_file(FileName) ->
- forms_to_ast(erlfmt:read_nodes(FileName)).
+ forms_to_ast(erlfmt:read_nodes(FileName)).
-spec parse_text(binary()) -> {ok, [tree()]} | {error, term()}.
parse_text(Text) ->
- String = els_utils:to_list(Text),
- forms_to_ast(erlfmt:read_nodes_string("nofile", String)).
+ String = els_utils:to_list(Text),
+ forms_to_ast(erlfmt:read_nodes_string("nofile", String)).
-spec forms_to_ast(tuple()) -> {ok, [tree()]} | {error, term()}.
forms_to_ast({ok, Forms, _ErrorInfo}) ->
- TreeList =
- [els_erlfmt_ast:erlfmt_to_st(Form) || Form <- Forms],
- {ok, TreeList};
+ TreeList =
+ [els_erlfmt_ast:erlfmt_to_st(Form) || Form <- Forms],
+ {ok, TreeList};
forms_to_ast({error, _ErrorInfo} = Error) ->
- Error.
+ Error.
--spec parse_incomplete_text(string(), {erl_anno:line(), erl_anno:column()})
- -> {ok, tree()} | error.
+-spec parse_incomplete_text(string(), {erl_anno:line(), erl_anno:column()}) ->
+ {ok, tree()} | error.
parse_incomplete_text(Text, {_Line, _Col} = StartLoc) ->
- Tokens = scan_text(Text, StartLoc),
- case parse_incomplete_tokens(Tokens) of
- {ok, Form} ->
- Tree = els_erlfmt_ast:erlfmt_to_st(Form),
- {ok, Tree};
- error ->
- error
- end.
+ Tokens = scan_text(Text, StartLoc),
+ case parse_incomplete_tokens(Tokens) of
+ {ok, Form} ->
+ Tree = els_erlfmt_ast:erlfmt_to_st(Form),
+ {ok, Tree};
+ error ->
+ error
+ end.
%%==============================================================================
%% Internal Functions
@@ -72,91 +74,96 @@ parse_incomplete_text(Text, {_Line, _Col} = StartLoc) ->
-spec parse_forms([erlfmt_parse:abstract_node()]) -> deep_list(poi()).
parse_forms(Forms) ->
- [try
- parse_form(Form)
- catch Type:Reason:St ->
- ?LOG_WARNING("Please report error parsing form ~p:~p:~p~n~p~n",
- [Type, Reason, St, Form]),
- []
- end
- || Form <- Forms].
+ [
+ try
+ parse_form(Form)
+ catch
+ Type:Reason:St ->
+ ?LOG_WARNING(
+ "Please report error parsing form ~p:~p:~p~n~p~n",
+ [Type, Reason, St, Form]
+ ),
+ []
+ end
+ || Form <- Forms
+ ].
-spec parse_form(erlfmt_parse:abstract_node()) -> deep_list(poi()).
parse_form({raw_string, Anno, Text}) ->
- StartLoc = erlfmt_scan:get_anno(location, Anno),
- RangeTokens = scan_text(Text, StartLoc),
- case parse_incomplete_tokens(RangeTokens) of
- {ok, Form} ->
- parse_form(Form);
- error ->
- find_attribute_tokens(RangeTokens)
- end;
+ StartLoc = erlfmt_scan:get_anno(location, Anno),
+ RangeTokens = scan_text(Text, StartLoc),
+ case parse_incomplete_tokens(RangeTokens) of
+ {ok, Form} ->
+ parse_form(Form);
+ error ->
+ find_attribute_tokens(RangeTokens)
+ end;
parse_form(Form) ->
- Tree = els_erlfmt_ast:erlfmt_to_st(Form),
- POIs = points_of_interest(Tree),
- POIs.
+ Tree = els_erlfmt_ast:erlfmt_to_st(Form),
+ POIs = points_of_interest(Tree),
+ POIs.
--spec scan_text(string(), {erl_anno:line(), erl_anno:column()})
- -> [erlfmt_scan:token()].
+-spec scan_text(string(), {erl_anno:line(), erl_anno:column()}) ->
+ [erlfmt_scan:token()].
scan_text(Text, StartLoc) ->
- PaddedText = pad_text(Text, StartLoc),
- {ok, Tokens, _Comments, _Cont} = erlfmt_scan:string_node(PaddedText),
- case Tokens of
- [] -> [];
- _ -> ensure_dot(Tokens)
- end.
-
--spec parse_incomplete_tokens([erlfmt_scan:token()])
- -> {ok, erlfmt_parse:abstract_node()} | error.
+ PaddedText = pad_text(Text, StartLoc),
+ {ok, Tokens, _Comments, _Cont} = erlfmt_scan:string_node(PaddedText),
+ case Tokens of
+ [] -> [];
+ _ -> ensure_dot(Tokens)
+ end.
+
+-spec parse_incomplete_tokens([erlfmt_scan:token()]) ->
+ {ok, erlfmt_parse:abstract_node()} | error.
parse_incomplete_tokens([{dot, _}]) ->
- error;
+ error;
parse_incomplete_tokens([]) ->
- error;
+ error;
parse_incomplete_tokens(Tokens) ->
- case erlfmt_parse:parse_node(Tokens) of
- {ok, Form} ->
- {ok, Form};
- {error, {ErrorLoc, erlfmt_parse, _Reason}} ->
- TrimmedTokens = tokens_until(Tokens, ErrorLoc),
- parse_incomplete_tokens(TrimmedTokens)
- end.
+ case erlfmt_parse:parse_node(Tokens) of
+ {ok, Form} ->
+ {ok, Form};
+ {error, {ErrorLoc, erlfmt_parse, _Reason}} ->
+ TrimmedTokens = tokens_until(Tokens, ErrorLoc),
+ parse_incomplete_tokens(TrimmedTokens)
+ end.
%% @doc Drop tokens after given location but keep final dot, to preserve its
%% location
--spec tokens_until([erlfmt_scan:token()], erl_anno:location())
- -> [erlfmt_scan:token()].
+-spec tokens_until([erlfmt_scan:token()], erl_anno:location()) ->
+ [erlfmt_scan:token()].
tokens_until([_Hd, {dot, _} = Dot], _Loc) ->
- %% We need to drop at least one token before the dot.
- %% Otherwise if error location is at the dot, we cannot just drop the dot and
- %% add a dot again, because it would result in an infinite loop.
- [Dot];
+ %% We need to drop at least one token before the dot.
+ %% Otherwise if error location is at the dot, we cannot just drop the dot and
+ %% add a dot again, because it would result in an infinite loop.
+ [Dot];
tokens_until([Hd | Tail], Loc) ->
- case erlfmt_scan:get_anno(location, Hd) < Loc of
- true ->
- [Hd | tokens_until(Tail, Loc)];
- false ->
- tokens_until(Tail, Loc)
- end.
+ case erlfmt_scan:get_anno(location, Hd) < Loc of
+ true ->
+ [Hd | tokens_until(Tail, Loc)];
+ false ->
+ tokens_until(Tail, Loc)
+ end.
%% `erlfmt_scan' does not support start location other than {1,1}
%% so we have to shift the text with newlines and spaces
-spec pad_text(string(), {erl_anno:line(), erl_anno:column()}) -> string().
pad_text(Text, {StartLine, StartColumn}) ->
- lists:duplicate(StartLine - 1, $\n)
- ++ lists:duplicate(StartColumn - 1, $\s)
- ++ Text.
+ lists:duplicate(StartLine - 1, $\n) ++
+ lists:duplicate(StartColumn - 1, $\s) ++
+ Text.
-spec ensure_dot([erlfmt_scan:token(), ...]) -> [erlfmt_scan:token(), ...].
ensure_dot(Tokens) ->
- case lists:last(Tokens) of
- {dot, _} ->
- Tokens;
- T ->
- EndLocation = erlfmt_scan:get_anno(end_location, T),
- %% Add a dot which has zero length (invisible) so it does not modify the
- %% end location of the whole form
- Tokens ++ [{dot, #{location => EndLocation, end_location => EndLocation}}]
- end.
+ case lists:last(Tokens) of
+ {dot, _} ->
+ Tokens;
+ T ->
+ EndLocation = erlfmt_scan:get_anno(end_location, T),
+ %% Add a dot which has zero length (invisible) so it does not modify the
+ %% end location of the whole form
+ Tokens ++ [{dot, #{location => EndLocation, end_location => EndLocation}}]
+ end.
%% @doc Resolve POI for specific sections
%%
@@ -166,695 +173,793 @@ ensure_dot(Tokens) ->
%% beginning and end for this sections, and can also handle the situations when
%% the code is not parsable.
-spec find_attribute_tokens([erlfmt_scan:token()]) -> [poi()].
-find_attribute_tokens([ {'-', Anno}, {atom, _, Name} | [_|_] = Rest])
- when Name =:= export;
- Name =:= export_type ->
- From = erlfmt_scan:get_anno(location, Anno),
- To = erlfmt_scan:get_anno(end_location, lists:last(Rest)),
- [poi({From, To}, Name, From)];
-find_attribute_tokens([ {'-', Anno}, {atom, _, spec} | [_|_] = Rest]) ->
- From = erlfmt_scan:get_anno(location, Anno),
- To = erlfmt_scan:get_anno(end_location, lists:last(Rest)),
- [poi({From, To}, spec, undefined)];
+find_attribute_tokens([{'-', Anno}, {atom, _, Name} | [_ | _] = Rest]) when
+ Name =:= export;
+ Name =:= export_type
+->
+ From = erlfmt_scan:get_anno(location, Anno),
+ To = erlfmt_scan:get_anno(end_location, lists:last(Rest)),
+ [poi({From, To}, Name, From)];
+find_attribute_tokens([{'-', Anno}, {atom, _, spec} | [_ | _] = Rest]) ->
+ From = erlfmt_scan:get_anno(location, Anno),
+ To = erlfmt_scan:get_anno(end_location, lists:last(Rest)),
+ [poi({From, To}, spec, undefined)];
find_attribute_tokens(_) ->
- [].
+ [].
-spec points_of_interest(tree()) -> [[poi()]].
points_of_interest(Tree) ->
- FoldFun = fun(T, Acc) -> [do_points_of_interest(T) | Acc] end,
- fold(FoldFun, [], Tree).
+ FoldFun = fun(T, Acc) -> [do_points_of_interest(T) | Acc] end,
+ fold(FoldFun, [], Tree).
%% @doc Return the list of points of interest for a given `Tree'.
-spec do_points_of_interest(tree()) -> [poi()].
do_points_of_interest(Tree) ->
- try
- case erl_syntax:type(Tree) of
- application -> application(Tree);
- attribute -> attribute(Tree);
- function -> function(Tree);
- implicit_fun -> implicit_fun(Tree);
- macro -> macro(Tree);
- record_access -> record_access(Tree);
- record_expr -> record_expr(Tree);
- variable -> variable(Tree);
- atom -> atom(Tree);
- Type when Type =:= type_application;
- Type =:= user_type_application ->
- type_application(Tree);
- record_type -> record_type(Tree);
- _ -> []
- end
- catch throw:syntax_error -> []
- end.
+ try
+ case erl_syntax:type(Tree) of
+ application ->
+ application(Tree);
+ attribute ->
+ attribute(Tree);
+ function ->
+ function(Tree);
+ implicit_fun ->
+ implicit_fun(Tree);
+ macro ->
+ macro(Tree);
+ record_access ->
+ record_access(Tree);
+ record_expr ->
+ record_expr(Tree);
+ variable ->
+ variable(Tree);
+ atom ->
+ atom(Tree);
+ Type when
+ Type =:= type_application;
+ Type =:= user_type_application
+ ->
+ type_application(Tree);
+ record_type ->
+ record_type(Tree);
+ _ ->
+ []
+ end
+ catch
+ throw:syntax_error -> []
+ end.
-spec application(tree()) -> [poi()].
application(Tree) ->
- case application_mfa(Tree) of
- undefined -> [];
- {F, A} ->
- Pos = erl_syntax:get_pos(erl_syntax:application_operator(Tree)),
- case erl_internal:bif(F, A) of
- %% Call to a function from the `erlang` module
- true -> [poi(Pos, application, {erlang, F, A}, #{imported => true})];
- %% Local call
- false -> [poi(Pos, application, {F, A})]
- end;
- MFA ->
- ModFunTree = erl_syntax:application_operator(Tree),
- Pos = erl_syntax:get_pos(ModFunTree),
- FunTree = erl_syntax:module_qualifier_body(ModFunTree),
- ModTree = erl_syntax:module_qualifier_argument(ModFunTree),
- Data = #{ name_range => els_range:range(erl_syntax:get_pos(FunTree))
- , mod_range => els_range:range(erl_syntax:get_pos(ModTree))
- },
- [poi(Pos, application, MFA, Data)]
- end.
+ case application_mfa(Tree) of
+ undefined ->
+ [];
+ {F, A} ->
+ Pos = erl_syntax:get_pos(erl_syntax:application_operator(Tree)),
+ case erl_internal:bif(F, A) of
+ %% Call to a function from the `erlang` module
+ true -> [poi(Pos, application, {erlang, F, A}, #{imported => true})];
+ %% Local call
+ false -> [poi(Pos, application, {F, A})]
+ end;
+ MFA ->
+ ModFunTree = erl_syntax:application_operator(Tree),
+ Pos = erl_syntax:get_pos(ModFunTree),
+ FunTree = erl_syntax:module_qualifier_body(ModFunTree),
+ ModTree = erl_syntax:module_qualifier_argument(ModFunTree),
+ Data = #{
+ name_range => els_range:range(erl_syntax:get_pos(FunTree)),
+ mod_range => els_range:range(erl_syntax:get_pos(ModTree))
+ },
+ [poi(Pos, application, MFA, Data)]
+ end.
-spec application_mfa(tree()) ->
- {module(), atom(), arity()} | {atom(), arity()} | undefined.
+ {module(), atom(), arity()} | {atom(), arity()} | undefined.
application_mfa(Tree) ->
- case erl_syntax_lib:analyze_application(Tree) of
- %% Remote call
- {M, {F, A}} ->
- {M, F, A};
- {F, A} ->
- {F, A};
- A when is_integer(A) ->
- %% If the function is not explicitly named (e.g. a variable is
- %% used as the module qualifier or the function name), only the
- %% arity A is returned.
- %% In the special case where the macro `?MODULE` is used as the
- %% module qualifier, we can consider it as a local call.
- Operator = erl_syntax:application_operator(Tree),
- case erl_syntax:type(Operator) of
- module_qualifier -> application_with_variable(Operator, A);
- _ -> undefined
- end
- end.
+ case erl_syntax_lib:analyze_application(Tree) of
+ %% Remote call
+ {M, {F, A}} ->
+ {M, F, A};
+ {F, A} ->
+ {F, A};
+ A when is_integer(A) ->
+ %% If the function is not explicitly named (e.g. a variable is
+ %% used as the module qualifier or the function name), only the
+ %% arity A is returned.
+ %% In the special case where the macro `?MODULE` is used as the
+ %% module qualifier, we can consider it as a local call.
+ Operator = erl_syntax:application_operator(Tree),
+ case erl_syntax:type(Operator) of
+ module_qualifier -> application_with_variable(Operator, A);
+ _ -> undefined
+ end
+ end.
-spec application_with_variable(tree(), arity()) ->
- {atom(), arity()} | undefined.
+ {atom(), arity()} | undefined.
application_with_variable(Operator, A) ->
- Module = erl_syntax:module_qualifier_argument(Operator),
- Function = erl_syntax:module_qualifier_body(Operator),
- case {erl_syntax:type(Module), erl_syntax:type(Function)} of
- %% The usage of the ?MODULE macro as the module name for
- %% fully qualified calls is so common that it is worth a
- %% specific clause.
- {macro, atom} ->
- ModuleName = macro_name(Module),
- FunctionName = node_name(Function),
- case {ModuleName, FunctionName} of
- {'MODULE', F} -> {F, A};
- _ -> undefined
- end;
- _ -> undefined
- end.
+ Module = erl_syntax:module_qualifier_argument(Operator),
+ Function = erl_syntax:module_qualifier_body(Operator),
+ case {erl_syntax:type(Module), erl_syntax:type(Function)} of
+ %% The usage of the ?MODULE macro as the module name for
+ %% fully qualified calls is so common that it is worth a
+ %% specific clause.
+ {macro, atom} ->
+ ModuleName = macro_name(Module),
+ FunctionName = node_name(Function),
+ case {ModuleName, FunctionName} of
+ {'MODULE', F} -> {F, A};
+ _ -> undefined
+ end;
+ _ ->
+ undefined
+ end.
-spec attribute(tree()) -> [poi()].
attribute(Tree) ->
- Pos = erl_syntax:get_pos(Tree),
- try {attribute_name_atom(Tree), erl_syntax:attribute_arguments(Tree)} of
- %% Yes, Erlang allows both British and American spellings for
- %% keywords.
- {AttrName, [Arg]} when AttrName =:= behaviour;
- AttrName =:= behavior ->
- case is_atom_node(Arg) of
- {true, Behaviour} ->
- Data = #{mod_range => els_range:range(erl_syntax:get_pos(Arg))},
- [poi(Pos, behaviour, Behaviour, Data)];
- false ->
- []
- end;
- {module, [Module, _Args]} ->
- case is_atom_node(Module) of
- {true, ModuleName} ->
- [poi(erl_syntax:get_pos(Module), module, ModuleName)];
- _ ->
- []
- end;
- {module, [Module]} ->
- case is_atom_node(Module) of
- {true, ModuleName} ->
- [poi(erl_syntax:get_pos(Module), module, ModuleName)];
- _ ->
- []
- end;
- {compile, [Arg]} ->
- %% When we encounter a compiler attribute, we include a POI
- %% indicating the fact. This is useful, for example, to avoid
- %% marking header files including parse transforms or other
- %% compiler attributes as unused. See #1047
- Marker = poi(erl_syntax:get_pos(Arg), compile, unused),
- [Marker|find_compile_options_pois(Arg)];
- {AttrName, [Arg]} when AttrName =:= export;
- AttrName =:= export_type ->
- find_export_pois(Tree, AttrName, Arg);
- {import, [ModTree, ImportList]} ->
- case is_atom_node(ModTree) of
- {true, _} ->
- Imports = erl_syntax:list_elements(ImportList),
- find_import_entry_pois(ModTree, Imports);
- _ ->
- []
- end;
- {define, [Define|Value]} ->
- DefinePos = case erl_syntax:type(Define) of
+ Pos = erl_syntax:get_pos(Tree),
+ try {attribute_name_atom(Tree), erl_syntax:attribute_arguments(Tree)} of
+ %% Yes, Erlang allows both British and American spellings for
+ %% keywords.
+ {AttrName, [Arg]} when
+ AttrName =:= behaviour;
+ AttrName =:= behavior
+ ->
+ case is_atom_node(Arg) of
+ {true, Behaviour} ->
+ Data = #{mod_range => els_range:range(erl_syntax:get_pos(Arg))},
+ [poi(Pos, behaviour, Behaviour, Data)];
+ false ->
+ []
+ end;
+ {module, [Module, _Args]} ->
+ case is_atom_node(Module) of
+ {true, ModuleName} ->
+ [poi(erl_syntax:get_pos(Module), module, ModuleName)];
+ _ ->
+ []
+ end;
+ {module, [Module]} ->
+ case is_atom_node(Module) of
+ {true, ModuleName} ->
+ [poi(erl_syntax:get_pos(Module), module, ModuleName)];
+ _ ->
+ []
+ end;
+ {compile, [Arg]} ->
+ %% When we encounter a compiler attribute, we include a POI
+ %% indicating the fact. This is useful, for example, to avoid
+ %% marking header files including parse transforms or other
+ %% compiler attributes as unused. See #1047
+ Marker = poi(erl_syntax:get_pos(Arg), compile, unused),
+ [Marker | find_compile_options_pois(Arg)];
+ {AttrName, [Arg]} when
+ AttrName =:= export;
+ AttrName =:= export_type
+ ->
+ find_export_pois(Tree, AttrName, Arg);
+ {import, [ModTree, ImportList]} ->
+ case is_atom_node(ModTree) of
+ {true, _} ->
+ Imports = erl_syntax:list_elements(ImportList),
+ find_import_entry_pois(ModTree, Imports);
+ _ ->
+ []
+ end;
+ {define, [Define | Value]} ->
+ DefinePos =
+ case erl_syntax:type(Define) of
application ->
- Operator = erl_syntax:application_operator(Define),
- erl_syntax:get_pos(Operator);
+ Operator = erl_syntax:application_operator(Define),
+ erl_syntax:get_pos(Operator);
_ ->
- erl_syntax:get_pos(Define)
- end,
- ValueRange = #{ from => get_start_location(hd(Value))
- , to => get_end_location(lists:last(Value))
- },
- Args = define_args(Define),
- Data = #{value_range => ValueRange, args => Args},
- [poi(DefinePos, define, define_name(Define), Data)];
- {include, [String]} ->
- [poi(Pos, include, erl_syntax:string_value(String))];
- {include_lib, [String]} ->
- [poi(Pos, include_lib, erl_syntax:string_value(String))];
- {record, [Record, Fields]} ->
- case is_record_name(Record) of
- {true, RecordName} ->
- record_attribute_pois(Tree, Record, RecordName, Fields);
- false ->
- []
- end;
- {AttrName, [ArgTuple]} when AttrName =:= type;
- AttrName =:= opaque ->
- [Type, _, ArgsListTree] = erl_syntax:tuple_elements(ArgTuple),
- TypeArgs = erl_syntax:list_elements(ArgsListTree),
- case is_atom_node(Type) of
- {true, TypeName} ->
- Id = {TypeName, length(TypeArgs)},
- [poi(Pos, type_definition, Id,
- #{ name_range => els_range:range(erl_syntax:get_pos(Type)),
- args => type_args(TypeArgs)})];
+ erl_syntax:get_pos(Define)
+ end,
+ ValueRange = #{
+ from => get_start_location(hd(Value)),
+ to => get_end_location(lists:last(Value))
+ },
+ Args = define_args(Define),
+ Data = #{value_range => ValueRange, args => Args},
+ [poi(DefinePos, define, define_name(Define), Data)];
+ {include, [String]} ->
+ [poi(Pos, include, erl_syntax:string_value(String))];
+ {include_lib, [String]} ->
+ [poi(Pos, include_lib, erl_syntax:string_value(String))];
+ {record, [Record, Fields]} ->
+ case is_record_name(Record) of
+ {true, RecordName} ->
+ record_attribute_pois(Tree, Record, RecordName, Fields);
+ false ->
+ []
+ end;
+ {AttrName, [ArgTuple]} when
+ AttrName =:= type;
+ AttrName =:= opaque
+ ->
+ [Type, _, ArgsListTree] = erl_syntax:tuple_elements(ArgTuple),
+ TypeArgs = erl_syntax:list_elements(ArgsListTree),
+ case is_atom_node(Type) of
+ {true, TypeName} ->
+ Id = {TypeName, length(TypeArgs)},
+ [
+ poi(
+ Pos,
+ type_definition,
+ Id,
+ #{
+ name_range => els_range:range(erl_syntax:get_pos(Type)),
+ args => type_args(TypeArgs)
+ }
+ )
+ ];
+ _ ->
+ []
+ end;
+ {callback, [ArgTuple]} ->
+ [FATree | _] = erl_syntax:tuple_elements(ArgTuple),
+ case spec_function_name(FATree) of
+ {F, A} ->
+ [FTree, _] = erl_syntax:tuple_elements(FATree),
+ [
+ poi(
+ Pos,
+ callback,
+ {F, A},
+ #{name_range => els_range:range(erl_syntax:get_pos(FTree))}
+ )
+ ];
+ undefined ->
+ []
+ end;
+ {spec, [ArgTuple]} ->
+ [FATree | _] = erl_syntax:tuple_elements(ArgTuple),
+ case spec_function_name(FATree) of
+ {F, A} ->
+ [FTree, _] = erl_syntax:tuple_elements(FATree),
+ [
+ poi(
+ Pos,
+ spec,
+ {F, A},
+ #{name_range => els_range:range(erl_syntax:get_pos(FTree))}
+ )
+ ];
+ undefined ->
+ [poi(Pos, spec, undefined)]
+ end;
_ ->
- []
- end;
- {callback, [ArgTuple]} ->
- [FATree | _] = erl_syntax:tuple_elements(ArgTuple),
- case spec_function_name(FATree) of
- {F, A} ->
- [FTree, _] = erl_syntax:tuple_elements(FATree),
- [poi(Pos, callback, {F, A},
- #{name_range => els_range:range(erl_syntax:get_pos(FTree))})];
- undefined ->
- []
- end;
- {spec, [ArgTuple]} ->
- [FATree | _] = erl_syntax:tuple_elements(ArgTuple),
- case spec_function_name(FATree) of
- {F, A} ->
- [FTree, _] = erl_syntax:tuple_elements(FATree),
- [poi(Pos, spec, {F, A},
- #{name_range => els_range:range(erl_syntax:get_pos(FTree))})];
- undefined ->
- [poi(Pos, spec, undefined)]
- end;
- _ ->
- []
- catch throw:syntax_error ->
- []
- end.
+ []
+ catch
+ throw:syntax_error ->
+ []
+ end.
-spec record_attribute_pois(tree(), tree(), atom(), tree()) -> [poi()].
record_attribute_pois(Tree, Record, RecordName, Fields) ->
- FieldList = record_def_field_name_list(Fields),
- {StartLine, StartColumn} = get_start_location(Tree),
- {EndLine, EndColumn} = get_end_location(Tree),
- ValueRange = #{ from => {StartLine, StartColumn}
- , to => {EndLine, EndColumn}
- },
- FoldingRange = exceeds_one_line(StartLine, EndLine),
- Data = #{ field_list => FieldList
- , value_range => ValueRange
- , folding_range => FoldingRange
- },
- [poi(erl_syntax:get_pos(Record), record, RecordName, Data)
- | record_def_fields(Fields, RecordName)].
+ FieldList = record_def_field_name_list(Fields),
+ {StartLine, StartColumn} = get_start_location(Tree),
+ {EndLine, EndColumn} = get_end_location(Tree),
+ ValueRange = #{
+ from => {StartLine, StartColumn},
+ to => {EndLine, EndColumn}
+ },
+ FoldingRange = exceeds_one_line(StartLine, EndLine),
+ Data = #{
+ field_list => FieldList,
+ value_range => ValueRange,
+ folding_range => FoldingRange
+ },
+ [
+ poi(erl_syntax:get_pos(Record), record, RecordName, Data)
+ | record_def_fields(Fields, RecordName)
+ ].
-spec find_compile_options_pois(tree()) -> [poi()].
find_compile_options_pois(Arg) ->
- case erl_syntax:type(Arg) of
- list ->
- L = erl_syntax:list_elements(Arg),
- lists:flatmap(fun find_compile_options_pois/1, L);
- tuple ->
- case erl_syntax:tuple_elements(Arg) of
- [K, V] ->
- case {is_atom_node(K), is_atom_node(V)} of
- {{true, parse_transform}, {true, PT}} ->
- [poi(erl_syntax:get_pos(V), parse_transform, PT)];
- _ ->
- []
- end;
+ case erl_syntax:type(Arg) of
+ list ->
+ L = erl_syntax:list_elements(Arg),
+ lists:flatmap(fun find_compile_options_pois/1, L);
+ tuple ->
+ case erl_syntax:tuple_elements(Arg) of
+ [K, V] ->
+ case {is_atom_node(K), is_atom_node(V)} of
+ {{true, parse_transform}, {true, PT}} ->
+ [poi(erl_syntax:get_pos(V), parse_transform, PT)];
+ _ ->
+ []
+ end;
+ _ ->
+ []
+ end;
+ atom ->
+ %% currently there is no atom compile option that we are interested in
+ [];
_ ->
- []
- end;
- atom ->
- %% currently there is no atom compile option that we are interested in
- [];
- _ ->
- []
- end.
+ []
+ end.
-spec find_export_pois(tree(), export | export_type, tree()) -> [poi()].
find_export_pois(Tree, AttrName, Arg) ->
- Exports = erl_syntax:list_elements(Arg),
- EntryPoiKind = case AttrName of
- export -> export_entry;
- export_type -> export_type_entry
- end,
- ExportEntries = find_export_entry_pois(EntryPoiKind, Exports),
- [ poi(erl_syntax:get_pos(Tree), AttrName, get_start_location(Tree))
- | ExportEntries ].
-
--spec find_export_entry_pois(export_entry | export_type_entry, [tree()])
- -> [poi()].
+ Exports = erl_syntax:list_elements(Arg),
+ EntryPoiKind =
+ case AttrName of
+ export -> export_entry;
+ export_type -> export_type_entry
+ end,
+ ExportEntries = find_export_entry_pois(EntryPoiKind, Exports),
+ [
+ poi(erl_syntax:get_pos(Tree), AttrName, get_start_location(Tree))
+ | ExportEntries
+ ].
+
+-spec find_export_entry_pois(export_entry | export_type_entry, [tree()]) ->
+ [poi()].
find_export_entry_pois(EntryPoiKind, Exports) ->
- lists:flatten(
- [ case get_name_arity(FATree) of
- {F, A} ->
- FTree = erl_syntax:arity_qualifier_body(FATree),
- poi(erl_syntax:get_pos(FATree), EntryPoiKind, {F, A},
- #{name_range => els_range:range(erl_syntax:get_pos(FTree))});
- false ->
- []
- end
- || FATree <- Exports
- ]).
+ lists:flatten(
+ [
+ case get_name_arity(FATree) of
+ {F, A} ->
+ FTree = erl_syntax:arity_qualifier_body(FATree),
+ poi(
+ erl_syntax:get_pos(FATree),
+ EntryPoiKind,
+ {F, A},
+ #{name_range => els_range:range(erl_syntax:get_pos(FTree))}
+ );
+ false ->
+ []
+ end
+ || FATree <- Exports
+ ]
+ ).
-spec find_import_entry_pois(tree(), [tree()]) -> [poi()].
find_import_entry_pois(ModTree, Imports) ->
- M = erl_syntax:atom_value(ModTree),
- lists:flatten(
- [ case get_name_arity(FATree) of
- {F, A} ->
- FTree = erl_syntax:arity_qualifier_body(FATree),
- Data = #{ name_range => els_range:range(erl_syntax:get_pos(FTree))
- , mod_range => els_range:range(erl_syntax:get_pos(ModTree))
- },
- poi(erl_syntax:get_pos(FATree), import_entry, {M, F, A}, Data);
- false ->
- []
- end
- || FATree <- Imports
- ]).
+ M = erl_syntax:atom_value(ModTree),
+ lists:flatten(
+ [
+ case get_name_arity(FATree) of
+ {F, A} ->
+ FTree = erl_syntax:arity_qualifier_body(FATree),
+ Data = #{
+ name_range => els_range:range(erl_syntax:get_pos(FTree)),
+ mod_range => els_range:range(erl_syntax:get_pos(ModTree))
+ },
+ poi(erl_syntax:get_pos(FATree), import_entry, {M, F, A}, Data);
+ false ->
+ []
+ end
+ || FATree <- Imports
+ ]
+ ).
-spec spec_function_name(tree()) -> {atom(), arity()} | undefined.
spec_function_name(FATree) ->
- %% concrete will throw an error if `FATree' contains any macro
- try erl_syntax:concrete(FATree) of
- {F, A} -> {F, A};
- _ -> undefined
- catch _:_ ->
- undefined
- end.
+ %% concrete will throw an error if `FATree' contains any macro
+ try erl_syntax:concrete(FATree) of
+ {F, A} -> {F, A};
+ _ -> undefined
+ catch
+ _:_ ->
+ undefined
+ end.
-spec type_args([tree()]) -> [{integer(), string()}].
type_args(Args) ->
- [ case erl_syntax:type(T) of
- variable -> {N, erl_syntax:variable_literal(T)};
- _ -> {N, "Type" ++ integer_to_list(N)}
- end
- || {N, T} <- lists:zip(lists:seq(1, length(Args)), Args)
- ].
+ [
+ case erl_syntax:type(T) of
+ variable -> {N, erl_syntax:variable_literal(T)};
+ _ -> {N, "Type" ++ integer_to_list(N)}
+ end
+ || {N, T} <- lists:zip(lists:seq(1, length(Args)), Args)
+ ].
-spec function(tree()) -> [poi()].
function(Tree) ->
- FunName = erl_syntax:function_name(Tree),
- Clauses = erl_syntax:function_clauses(Tree),
- {F, A, Args} = analyze_function(FunName, Clauses),
-
- IndexedClauses = lists:zip(lists:seq(1, length(Clauses)), Clauses),
- %% FIXME function_clause range should be the range of the name atom however
- %% that is not present in the clause Tree (it is in the erlfmt_parse node)
- ClausesPOIs = [ poi( get_start_location(Clause)
- , function_clause
- , {F, A, I}
- , pretty_print_clause(Clause)
- )
- || {I, Clause} <- IndexedClauses,
- erl_syntax:type(Clause) =:= clause],
- {StartLine, StartColumn} = get_start_location(Tree),
- {EndLine, _EndColumn} = get_end_location(Tree),
- FoldingRange = exceeds_one_line(StartLine, EndLine),
- FunctionPOI = poi(erl_syntax:get_pos(FunName), function, {F, A},
- #{ args => Args
- , wrapping_range => #{ from => {StartLine, StartColumn}
- , to => {EndLine + 1, 0}
- }
- , folding_range => FoldingRange
- }),
- lists:append([ [ FunctionPOI ]
- , ClausesPOIs
- ]).
+ FunName = erl_syntax:function_name(Tree),
+ Clauses = erl_syntax:function_clauses(Tree),
+ {F, A, Args} = analyze_function(FunName, Clauses),
+
+ IndexedClauses = lists:zip(lists:seq(1, length(Clauses)), Clauses),
+ %% FIXME function_clause range should be the range of the name atom however
+ %% that is not present in the clause Tree (it is in the erlfmt_parse node)
+ ClausesPOIs = [
+ poi(
+ get_start_location(Clause),
+ function_clause,
+ {F, A, I},
+ pretty_print_clause(Clause)
+ )
+ || {I, Clause} <- IndexedClauses,
+ erl_syntax:type(Clause) =:= clause
+ ],
+ {StartLine, StartColumn} = get_start_location(Tree),
+ {EndLine, _EndColumn} = get_end_location(Tree),
+ FoldingRange = exceeds_one_line(StartLine, EndLine),
+ FunctionPOI = poi(
+ erl_syntax:get_pos(FunName),
+ function,
+ {F, A},
+ #{
+ args => Args,
+ wrapping_range => #{
+ from => {StartLine, StartColumn},
+ to => {EndLine + 1, 0}
+ },
+ folding_range => FoldingRange
+ }
+ ),
+ lists:append([
+ [FunctionPOI],
+ ClausesPOIs
+ ]).
-spec analyze_function(tree(), [tree()]) ->
- {atom(), arity(), [{integer(), string()}]}.
+ {atom(), arity(), [{integer(), string()}]}.
analyze_function(FunName, Clauses) ->
- F = case is_atom_node(FunName) of
- {true, FAtom} -> FAtom;
- false -> throw(syntax_error)
- end,
-
- case lists:dropwhile(fun(T) -> erl_syntax:type(T) =/= clause end, Clauses) of
- [Clause | _] ->
- {Arity, Args} = function_args(Clause),
- {F, Arity, Args};
- [] ->
- throw(syntax_error)
- end.
+ F =
+ case is_atom_node(FunName) of
+ {true, FAtom} -> FAtom;
+ false -> throw(syntax_error)
+ end,
+
+ case lists:dropwhile(fun(T) -> erl_syntax:type(T) =/= clause end, Clauses) of
+ [Clause | _] ->
+ {Arity, Args} = function_args(Clause),
+ {F, Arity, Args};
+ [] ->
+ throw(syntax_error)
+ end.
-spec function_args(tree()) -> {arity(), [{integer(), string()}]}.
function_args(Clause) ->
- Patterns = erl_syntax:clause_patterns(Clause),
- Arity = length(Patterns),
- Args = args_from_subtrees(Patterns),
- {Arity, Args}.
-
+ Patterns = erl_syntax:clause_patterns(Clause),
+ Arity = length(Patterns),
+ Args = args_from_subtrees(Patterns),
+ {Arity, Args}.
-spec args_from_subtrees([tree()]) -> [{integer(), string()}].
args_from_subtrees(Trees) ->
- Arity = length(Trees),
- [ case erl_syntax:type(T) of
- %% TODO: Handle literals
- variable -> {N, erl_syntax:variable_literal(T)};
- _ -> {N, "Arg" ++ integer_to_list(N)}
- end
- || {N, T} <- lists:zip(lists:seq(1, Arity), Trees)
- ].
+ Arity = length(Trees),
+ [
+ case erl_syntax:type(T) of
+ %% TODO: Handle literals
+ variable -> {N, erl_syntax:variable_literal(T)};
+ _ -> {N, "Arg" ++ integer_to_list(N)}
+ end
+ || {N, T} <- lists:zip(lists:seq(1, Arity), Trees)
+ ].
-spec implicit_fun(tree()) -> [poi()].
implicit_fun(Tree) ->
- FunSpec = try erl_syntax_lib:analyze_implicit_fun(Tree) of
- {M, {F, A}} -> {M, F, A};
- {F, A} -> {F, A}
- catch throw:syntax_error ->
+ FunSpec =
+ try erl_syntax_lib:analyze_implicit_fun(Tree) of
+ {M, {F, A}} -> {M, F, A};
+ {F, A} -> {F, A}
+ catch
+ throw:syntax_error ->
undefined
- end,
- case FunSpec of
- undefined -> [];
- _ ->
- NameTree = erl_syntax:implicit_fun_name(Tree),
- Data =
- case FunSpec of
- {_, _, _} ->
- ModTree = erl_syntax:module_qualifier_argument(NameTree),
- FunTree = erl_syntax:arity_qualifier_body(
- erl_syntax:module_qualifier_body(NameTree)),
- #{ name_range => els_range:range(erl_syntax:get_pos(FunTree))
- , mod_range => els_range:range(erl_syntax:get_pos(ModTree))
- };
- {_, _} ->
- FunTree = erl_syntax:arity_qualifier_body(NameTree),
- #{name_range => els_range:range(erl_syntax:get_pos(FunTree))}
end,
- [poi(erl_syntax:get_pos(Tree), implicit_fun, FunSpec, Data)]
- end.
+ case FunSpec of
+ undefined ->
+ [];
+ _ ->
+ NameTree = erl_syntax:implicit_fun_name(Tree),
+ Data =
+ case FunSpec of
+ {_, _, _} ->
+ ModTree = erl_syntax:module_qualifier_argument(NameTree),
+ FunTree = erl_syntax:arity_qualifier_body(
+ erl_syntax:module_qualifier_body(NameTree)
+ ),
+ #{
+ name_range => els_range:range(erl_syntax:get_pos(FunTree)),
+ mod_range => els_range:range(erl_syntax:get_pos(ModTree))
+ };
+ {_, _} ->
+ FunTree = erl_syntax:arity_qualifier_body(NameTree),
+ #{name_range => els_range:range(erl_syntax:get_pos(FunTree))}
+ end,
+ [poi(erl_syntax:get_pos(Tree), implicit_fun, FunSpec, Data)]
+ end.
-spec macro(tree()) -> [poi()].
macro(Tree) ->
- Anno = macro_location(Tree),
- [poi(Anno, macro, macro_name(Tree))].
+ Anno = macro_location(Tree),
+ [poi(Anno, macro, macro_name(Tree))].
--spec map_record_def_fields(Fun, tree(), atom()) -> [Result]
- when Fun :: fun((tree(), atom()) -> Result).
+-spec map_record_def_fields(Fun, tree(), atom()) -> [Result] when
+ Fun :: fun((tree(), atom()) -> Result).
map_record_def_fields(Fun, Fields, RecordName) ->
- case erl_syntax:type(Fields) of
- tuple ->
- lists:append(
- [ case erl_syntax:type(FieldTree) of
- record_field ->
- FieldNode = FieldTree,
- Fun(FieldNode, RecordName);
- typed_record_field ->
- FieldNode = erl_syntax:typed_record_field_body(FieldTree),
- Fun(FieldNode, RecordName);
- _ ->
- []
- end
- || FieldTree <- erl_syntax:tuple_elements(Fields)
- ]);
- _ ->
- []
- end.
+ case erl_syntax:type(Fields) of
+ tuple ->
+ lists:append(
+ [
+ case erl_syntax:type(FieldTree) of
+ record_field ->
+ FieldNode = FieldTree,
+ Fun(FieldNode, RecordName);
+ typed_record_field ->
+ FieldNode = erl_syntax:typed_record_field_body(FieldTree),
+ Fun(FieldNode, RecordName);
+ _ ->
+ []
+ end
+ || FieldTree <- erl_syntax:tuple_elements(Fields)
+ ]
+ );
+ _ ->
+ []
+ end.
%% Fields with macro name are skipped
-spec record_def_field_name_list(tree()) -> [atom()].
record_def_field_name_list(Fields) ->
- map_record_def_fields(
- fun(FieldNode, _) ->
- FieldName = erl_syntax:record_field_name(FieldNode),
- case is_atom_node(FieldName) of
- {true, NameAtom} ->
- [NameAtom];
- false ->
- []
- end
- end,
- Fields,
- undefined).
+ map_record_def_fields(
+ fun(FieldNode, _) ->
+ FieldName = erl_syntax:record_field_name(FieldNode),
+ case is_atom_node(FieldName) of
+ {true, NameAtom} ->
+ [NameAtom];
+ false ->
+ []
+ end
+ end,
+ Fields,
+ undefined
+ ).
-spec record_def_fields(tree(), atom()) -> [poi()].
record_def_fields(Fields, RecordName) ->
- map_record_def_fields(
- fun(F, R) ->
- record_field_name(F, R, record_def_field)
- end,
- Fields,
- RecordName).
+ map_record_def_fields(
+ fun(F, R) ->
+ record_field_name(F, R, record_def_field)
+ end,
+ Fields,
+ RecordName
+ ).
-spec record_access(tree()) -> [poi()].
record_access(Tree) ->
- RecordNode = erl_syntax:record_access_type(Tree),
- case is_record_name(RecordNode) of
- {true, Record} ->
- record_access_pois(Tree, Record);
- false ->
- []
- end.
+ RecordNode = erl_syntax:record_access_type(Tree),
+ case is_record_name(RecordNode) of
+ {true, Record} ->
+ record_access_pois(Tree, Record);
+ false ->
+ []
+ end.
-spec record_access_pois(tree(), atom()) -> [poi()].
record_access_pois(Tree, Record) ->
- FieldNode = erl_syntax:record_access_field(Tree),
- FieldPoi =
- case is_atom_node(FieldNode) of
- {true, FieldName} ->
- [poi(erl_syntax:get_pos(FieldNode), record_field, {Record, FieldName})];
- _ ->
- []
- end,
- Anno = record_access_location(Tree),
- [ poi(Anno, record_expr, Record)
- | FieldPoi ].
+ FieldNode = erl_syntax:record_access_field(Tree),
+ FieldPoi =
+ case is_atom_node(FieldNode) of
+ {true, FieldName} ->
+ [poi(erl_syntax:get_pos(FieldNode), record_field, {Record, FieldName})];
+ _ ->
+ []
+ end,
+ Anno = record_access_location(Tree),
+ [
+ poi(Anno, record_expr, Record)
+ | FieldPoi
+ ].
-spec record_expr(tree()) -> [poi()].
record_expr(Tree) ->
- RecordNode = erl_syntax:record_expr_type(Tree),
- case is_record_name(RecordNode) of
- {true, Record} ->
- record_expr_pois(Tree, RecordNode, Record);
- false ->
- []
- end.
+ RecordNode = erl_syntax:record_expr_type(Tree),
+ case is_record_name(RecordNode) of
+ {true, Record} ->
+ record_expr_pois(Tree, RecordNode, Record);
+ false ->
+ []
+ end.
-spec record_expr_pois(tree(), tree(), atom()) -> [poi()].
record_expr_pois(Tree, RecordNode, Record) ->
- FieldPois = lists:append(
- [record_field_name(F, Record, record_field)
- || F <- erl_syntax:record_expr_fields(Tree)]),
- Anno = record_expr_location(Tree, RecordNode),
- [ poi(Anno, record_expr, Record)
- | FieldPois ].
+ FieldPois = lists:append(
+ [
+ record_field_name(F, Record, record_field)
+ || F <- erl_syntax:record_expr_fields(Tree)
+ ]
+ ),
+ Anno = record_expr_location(Tree, RecordNode),
+ [
+ poi(Anno, record_expr, Record)
+ | FieldPois
+ ].
-spec record_type(tree()) -> [poi()].
record_type(Tree) ->
- RecordNode = erl_syntax:record_type_name(Tree),
- case is_record_name(RecordNode) of
- {true, Record} ->
- record_type_pois(Tree, RecordNode, Record);
- false ->
- []
- end.
+ RecordNode = erl_syntax:record_type_name(Tree),
+ case is_record_name(RecordNode) of
+ {true, Record} ->
+ record_type_pois(Tree, RecordNode, Record);
+ false ->
+ []
+ end.
-spec record_type_pois(tree(), tree(), atom()) -> [poi()].
record_type_pois(Tree, RecordNode, Record) ->
- FieldPois = lists:append(
- [record_field_name(F, Record, record_field)
- || F <- erl_syntax:record_type_fields(Tree)]),
- Anno = record_expr_location(Tree, RecordNode),
- [ poi(Anno, record_expr, Record)
- | FieldPois ].
+ FieldPois = lists:append(
+ [
+ record_field_name(F, Record, record_field)
+ || F <- erl_syntax:record_type_fields(Tree)
+ ]
+ ),
+ Anno = record_expr_location(Tree, RecordNode),
+ [
+ poi(Anno, record_expr, Record)
+ | FieldPois
+ ].
-spec record_field_name(tree(), atom(), poi_kind()) -> [poi()].
record_field_name(FieldNode, Record, Kind) ->
- NameNode =
- case erl_syntax:type(FieldNode) of
- record_field ->
- erl_syntax:record_field_name(FieldNode);
- record_type_field ->
- erl_syntax:record_type_field_name(FieldNode)
- end,
- case is_atom_node(NameNode) of
- {true, NameAtom} ->
- Pos = erl_syntax:get_pos(NameNode),
- [poi(Pos, Kind, {Record, NameAtom})];
- _ ->
- []
- end.
+ NameNode =
+ case erl_syntax:type(FieldNode) of
+ record_field ->
+ erl_syntax:record_field_name(FieldNode);
+ record_type_field ->
+ erl_syntax:record_type_field_name(FieldNode)
+ end,
+ case is_atom_node(NameNode) of
+ {true, NameAtom} ->
+ Pos = erl_syntax:get_pos(NameNode),
+ [poi(Pos, Kind, {Record, NameAtom})];
+ _ ->
+ []
+ end.
-spec is_record_name(tree()) -> {true, atom()} | false.
is_record_name(RecordNameNode) ->
- case erl_syntax:type(RecordNameNode) of
- atom ->
- NameAtom = erl_syntax:atom_value(RecordNameNode),
- {true, NameAtom};
- macro ->
- case macro_name(RecordNameNode) of
- 'MODULE' ->
- %% [#1052] Let's handle the common ?MODULE macro case explicitly
- {true, '?MODULE'};
+ case erl_syntax:type(RecordNameNode) of
+ atom ->
+ NameAtom = erl_syntax:atom_value(RecordNameNode),
+ {true, NameAtom};
+ macro ->
+ case macro_name(RecordNameNode) of
+ 'MODULE' ->
+ %% [#1052] Let's handle the common ?MODULE macro case explicitly
+ {true, '?MODULE'};
+ _ ->
+ false
+ end;
_ ->
- false
- end;
- _ ->
- false
- end.
+ false
+ end.
-spec type_application(tree()) -> [poi()].
type_application(Tree) ->
- Type = erl_syntax:type(Tree),
- case erl_syntax_lib:analyze_type_application(Tree) of
- {Module, {Name, Arity}} ->
- %% remote type
- Id = {Module, Name, Arity},
- ModTypeTree = erl_syntax:type_application_name(Tree),
- Pos = erl_syntax:get_pos(ModTypeTree),
- TypeTree = erl_syntax:module_qualifier_body(ModTypeTree),
- ModTree = erl_syntax:module_qualifier_argument(ModTypeTree),
- Data = #{ name_range => els_range:range(erl_syntax:get_pos(TypeTree))
- , mod_range => els_range:range(erl_syntax:get_pos(ModTree))
- },
- [poi(Pos, type_application, Id, Data)];
- {Name, Arity} when Type =:= user_type_application ->
- %% user-defined local type
- Id = {Name, Arity},
- Pos = erl_syntax:get_pos(erl_syntax:user_type_application_name(Tree)),
- [poi(Pos, type_application, Id)];
- {Name, Arity} when Type =:= type_application ->
- %% Built-in types
- Id = {erlang, Name, Arity},
- Pos = erl_syntax:get_pos(erl_syntax:type_application_name(Tree)),
- [poi(Pos, type_application, Id)]
- end.
+ Type = erl_syntax:type(Tree),
+ case erl_syntax_lib:analyze_type_application(Tree) of
+ {Module, {Name, Arity}} ->
+ %% remote type
+ Id = {Module, Name, Arity},
+ ModTypeTree = erl_syntax:type_application_name(Tree),
+ Pos = erl_syntax:get_pos(ModTypeTree),
+ TypeTree = erl_syntax:module_qualifier_body(ModTypeTree),
+ ModTree = erl_syntax:module_qualifier_argument(ModTypeTree),
+ Data = #{
+ name_range => els_range:range(erl_syntax:get_pos(TypeTree)),
+ mod_range => els_range:range(erl_syntax:get_pos(ModTree))
+ },
+ [poi(Pos, type_application, Id, Data)];
+ {Name, Arity} when Type =:= user_type_application ->
+ %% user-defined local type
+ Id = {Name, Arity},
+ Pos = erl_syntax:get_pos(erl_syntax:user_type_application_name(Tree)),
+ [poi(Pos, type_application, Id)];
+ {Name, Arity} when Type =:= type_application ->
+ %% Built-in types
+ Id = {erlang, Name, Arity},
+ Pos = erl_syntax:get_pos(erl_syntax:type_application_name(Tree)),
+ [poi(Pos, type_application, Id)]
+ end.
-spec variable(tree()) -> [poi()].
variable(Tree) ->
- Pos = erl_syntax:get_pos(Tree),
- case Pos of
- 0 -> [];
- _ -> [poi(Pos, variable, node_name(Tree))]
- end.
+ Pos = erl_syntax:get_pos(Tree),
+ case Pos of
+ 0 -> [];
+ _ -> [poi(Pos, variable, node_name(Tree))]
+ end.
-spec atom(tree()) -> [poi()].
atom(Tree) ->
- Pos = erl_syntax:get_pos(Tree),
- case Pos of
- 0 -> [];
- _ -> [poi(Pos, atom, node_name(Tree))]
- end.
+ Pos = erl_syntax:get_pos(Tree),
+ case Pos of
+ 0 -> [];
+ _ -> [poi(Pos, atom, node_name(Tree))]
+ end.
-spec define_name(tree()) -> atom().
define_name(Tree) ->
- case erl_syntax:type(Tree) of
- application ->
- Operator = erl_syntax:application_operator(Tree),
- Args = erl_syntax:application_arguments(Tree),
- macro_name(Operator, Args);
- variable ->
- erl_syntax:variable_name(Tree);
- atom ->
- erl_syntax:atom_value(Tree);
- underscore ->
- '_'
- end.
+ case erl_syntax:type(Tree) of
+ application ->
+ Operator = erl_syntax:application_operator(Tree),
+ Args = erl_syntax:application_arguments(Tree),
+ macro_name(Operator, Args);
+ variable ->
+ erl_syntax:variable_name(Tree);
+ atom ->
+ erl_syntax:atom_value(Tree);
+ underscore ->
+ '_'
+ end.
-spec define_args(tree()) -> none | [{integer(), string()}].
define_args(Define) ->
- case erl_syntax:type(Define) of
- application ->
- Args = erl_syntax:application_arguments(Define),
- args_from_subtrees(Args);
- _ ->
- none
- end.
+ case erl_syntax:type(Define) of
+ application ->
+ Args = erl_syntax:application_arguments(Define),
+ args_from_subtrees(Args);
+ _ ->
+ none
+ end.
-spec node_name(tree()) -> atom().
node_name(Tree) ->
- case erl_syntax:type(Tree) of
- atom ->
- erl_syntax:atom_value(Tree);
- variable ->
- erl_syntax:variable_name(Tree);
- underscore ->
- '_'
- end.
+ case erl_syntax:type(Tree) of
+ atom ->
+ erl_syntax:atom_value(Tree);
+ variable ->
+ erl_syntax:variable_name(Tree);
+ underscore ->
+ '_'
+ end.
-spec macro_name(tree()) -> atom() | {atom(), non_neg_integer()}.
macro_name(Tree) ->
- macro_name(erl_syntax:macro_name(Tree), erl_syntax:macro_arguments(Tree)).
+ macro_name(erl_syntax:macro_name(Tree), erl_syntax:macro_arguments(Tree)).
-spec macro_name(tree(), [tree()] | none) ->
- atom() | {atom(), non_neg_integer()}.
+ atom() | {atom(), non_neg_integer()}.
macro_name(Name, none) -> node_name(Name);
macro_name(Name, Args) -> {node_name(Name), length(Args)}.
-spec is_atom_node(tree()) -> {true, atom()} | false.
is_atom_node(Tree) ->
- case erl_syntax:type(Tree) of
- atom ->
- {true, erl_syntax:atom_value(Tree)};
- _ ->
- false
- end.
+ case erl_syntax:type(Tree) of
+ atom ->
+ {true, erl_syntax:atom_value(Tree)};
+ _ ->
+ false
+ end.
-spec get_name_arity(tree()) -> {atom(), integer()} | false.
get_name_arity(Tree) ->
- case erl_syntax:type(Tree) of
- arity_qualifier ->
- A = erl_syntax:arity_qualifier_argument(Tree),
- case erl_syntax:type(A) of
- integer ->
- F = erl_syntax:arity_qualifier_body(Tree),
- case is_atom_node(F) of
- {true, Name} ->
- Arity = erl_syntax:integer_value(A),
- {Name, Arity};
- false ->
- false
- end;
+ case erl_syntax:type(Tree) of
+ arity_qualifier ->
+ A = erl_syntax:arity_qualifier_argument(Tree),
+ case erl_syntax:type(A) of
+ integer ->
+ F = erl_syntax:arity_qualifier_body(Tree),
+ case is_atom_node(F) of
+ {true, Name} ->
+ Arity = erl_syntax:integer_value(A),
+ {Name, Arity};
+ false ->
+ false
+ end;
+ _ ->
+ false
+ end;
_ ->
- false
- end;
- _ ->
- false
- end.
+ false
+ end.
-spec poi(pos() | {pos(), pos()} | erl_anno:anno(), poi_kind(), any()) -> poi().
poi(Pos, Kind, Id) ->
- poi(Pos, Kind, Id, undefined).
+ poi(Pos, Kind, Id, undefined).
-spec poi(pos() | {pos(), pos()} | erl_anno:anno(), poi_kind(), any(), any()) ->
- poi().
+ poi().
poi(Pos, Kind, Id, Data) ->
- Range = els_range:range(Pos, Kind, Id, Data),
- els_poi:new(Range, Kind, Id, Data).
+ Range = els_range:range(Pos, Kind, Id, Data),
+ els_poi:new(Range, Kind, Id, Data).
%% @doc Fold over nodes in the tree
%%
@@ -862,297 +967,327 @@ poi(Pos, Kind, Id, Data) ->
%% what subtrees should be folded over for certain types of nodes.
-spec fold(fun((tree(), term()) -> term()), term(), tree()) -> term().
fold(F, S, Tree) ->
- case subtrees(Tree, erl_syntax:type(Tree)) of
- [] -> F(Tree, S);
- Gs -> F(Tree, fold1(F, S, Gs))
- end.
+ case subtrees(Tree, erl_syntax:type(Tree)) of
+ [] -> F(Tree, S);
+ Gs -> F(Tree, fold1(F, S, Gs))
+ end.
-spec fold1(fun((tree(), term()) -> term()), term(), [[tree()]]) ->
- term().
+ term().
fold1(F, S, [L | Ls]) ->
- fold1(F, fold2(F, S, L), Ls);
+ fold1(F, fold2(F, S, L), Ls);
fold1(_, S, []) ->
- S.
+ S.
-spec fold2(fun((tree(), term()) -> term()), term(), [tree()]) ->
- term().
+ term().
fold2(F, S, [T | Ts]) ->
- fold2(F, fold(F, S, T), Ts);
+ fold2(F, fold(F, S, T), Ts);
fold2(_, S, []) ->
- S.
+ S.
-spec subtrees(tree(), atom()) -> [[tree()]].
subtrees(Tree, application) ->
- [ case application_mfa(Tree) of
- undefined ->
- [erl_syntax:application_operator(Tree)];
- _ ->
- []
- end
- , erl_syntax:application_arguments(Tree)];
+ [
+ case application_mfa(Tree) of
+ undefined ->
+ [erl_syntax:application_operator(Tree)];
+ _ ->
+ []
+ end,
+ erl_syntax:application_arguments(Tree)
+ ];
subtrees(Tree, function) ->
- [erl_syntax:function_clauses(Tree)];
+ [erl_syntax:function_clauses(Tree)];
subtrees(_Tree, implicit_fun) ->
- [];
+ [];
subtrees(Tree, macro) ->
- case erl_syntax:macro_arguments(Tree) of
- none -> [];
- Args -> [Args]
- end;
+ case erl_syntax:macro_arguments(Tree) of
+ none -> [];
+ Args -> [Args]
+ end;
subtrees(Tree, record_access) ->
- NameNode = erl_syntax:record_access_type(Tree),
- FieldNode = erl_syntax:record_access_field(Tree),
- [ [erl_syntax:record_access_argument(Tree)]
- , skip_record_name_atom(NameNode)
- , skip_name_atom(FieldNode)
- ];
+ NameNode = erl_syntax:record_access_type(Tree),
+ FieldNode = erl_syntax:record_access_field(Tree),
+ [
+ [erl_syntax:record_access_argument(Tree)],
+ skip_record_name_atom(NameNode),
+ skip_name_atom(FieldNode)
+ ];
subtrees(Tree, record_expr) ->
- NameNode = erl_syntax:record_expr_type(Tree),
- Fields = erl_syntax:record_expr_fields(Tree),
- [ case erl_syntax:record_expr_argument(Tree) of
- none -> [];
- Arg -> [Arg]
- end
- , skip_record_name_atom(NameNode)
- , Fields
- ];
+ NameNode = erl_syntax:record_expr_type(Tree),
+ Fields = erl_syntax:record_expr_fields(Tree),
+ [
+ case erl_syntax:record_expr_argument(Tree) of
+ none -> [];
+ Arg -> [Arg]
+ end,
+ skip_record_name_atom(NameNode),
+ Fields
+ ];
subtrees(Tree, record_field) ->
- NameNode = erl_syntax:record_field_name(Tree),
- [ skip_name_atom(NameNode)
- , case erl_syntax:record_field_value(Tree) of
- none ->
- [];
- V ->
- [V]
- end];
+ NameNode = erl_syntax:record_field_name(Tree),
+ [
+ skip_name_atom(NameNode),
+ case erl_syntax:record_field_value(Tree) of
+ none ->
+ [];
+ V ->
+ [V]
+ end
+ ];
subtrees(Tree, record_type) ->
- NameNode = erl_syntax:record_type_name(Tree),
- [ skip_record_name_atom(NameNode)
- , erl_syntax:record_type_fields(Tree)
- ];
+ NameNode = erl_syntax:record_type_name(Tree),
+ [
+ skip_record_name_atom(NameNode),
+ erl_syntax:record_type_fields(Tree)
+ ];
subtrees(Tree, record_type_field) ->
- NameNode = erl_syntax:record_type_field_name(Tree),
- [ skip_name_atom(NameNode)
- , [erl_syntax:record_type_field_type(Tree)]
- ];
+ NameNode = erl_syntax:record_type_field_name(Tree),
+ [
+ skip_name_atom(NameNode),
+ [erl_syntax:record_type_field_type(Tree)]
+ ];
subtrees(Tree, user_type_application) ->
- NameNode = erl_syntax:user_type_application_name(Tree),
- [ skip_name_atom(NameNode)
- , erl_syntax:user_type_application_arguments(Tree)
- ];
+ NameNode = erl_syntax:user_type_application_name(Tree),
+ [
+ skip_name_atom(NameNode),
+ erl_syntax:user_type_application_arguments(Tree)
+ ];
subtrees(Tree, type_application) ->
- NameNode = erl_syntax:type_application_name(Tree),
- [ skip_type_name_atom(NameNode)
- , erl_syntax:type_application_arguments(Tree)
- ];
+ NameNode = erl_syntax:type_application_name(Tree),
+ [
+ skip_type_name_atom(NameNode),
+ erl_syntax:type_application_arguments(Tree)
+ ];
subtrees(Tree, attribute) ->
- AttrName = attribute_name_atom(Tree),
- Args = case erl_syntax:attribute_arguments(Tree) of
- none -> [];
- Args0 -> Args0
- end,
- attribute_subtrees(AttrName, Args);
+ AttrName = attribute_name_atom(Tree),
+ Args =
+ case erl_syntax:attribute_arguments(Tree) of
+ none -> [];
+ Args0 -> Args0
+ end,
+ attribute_subtrees(AttrName, Args);
subtrees(Tree, _) ->
- erl_syntax:subtrees(Tree).
+ erl_syntax:subtrees(Tree).
-spec attribute_name_atom(tree()) -> atom() | tree().
attribute_name_atom(Tree) ->
- NameNode = erl_syntax:attribute_name(Tree),
- case erl_syntax:type(NameNode) of
- atom ->
- erl_syntax:atom_value(NameNode);
- _ ->
- NameNode
- end.
+ NameNode = erl_syntax:attribute_name(Tree),
+ case erl_syntax:type(NameNode) of
+ atom ->
+ erl_syntax:atom_value(NameNode);
+ _ ->
+ NameNode
+ end.
-spec attribute_subtrees(atom() | tree(), [tree()]) -> [[tree()]].
-attribute_subtrees(AttrName, [Mod])
- when AttrName =:= module;
- AttrName =:= behavior;
- AttrName =:= behaviour ->
- [skip_name_atom(Mod)];
+attribute_subtrees(AttrName, [Mod]) when
+ AttrName =:= module;
+ AttrName =:= behavior;
+ AttrName =:= behaviour
+->
+ [skip_name_atom(Mod)];
attribute_subtrees(record, [RecordName, FieldsTuple]) ->
- [ skip_record_name_atom(RecordName)
- , [FieldsTuple]
- ];
+ [
+ skip_record_name_atom(RecordName),
+ [FieldsTuple]
+ ];
attribute_subtrees(import, [Mod, Imports]) ->
- [ skip_name_atom(Mod)
- , skip_function_entries(Imports) ];
-attribute_subtrees(AttrName, [Exports])
- when AttrName =:= export;
- AttrName =:= export_type ->
- [ skip_function_entries(Exports) ];
+ [
+ skip_name_atom(Mod),
+ skip_function_entries(Imports)
+ ];
+attribute_subtrees(AttrName, [Exports]) when
+ AttrName =:= export;
+ AttrName =:= export_type
+->
+ [skip_function_entries(Exports)];
attribute_subtrees(define, [Name | Definition]) ->
- %% The definition can contain commas, in which case it will look like as if
- %% the attribute would have more than two arguments. Eg.: `-define(M, a, b).'
- Args = case erl_syntax:type(Name) of
- application -> erl_syntax:application_arguments(Name);
- _ -> []
- end,
- [Args, Definition];
-attribute_subtrees(AttrName, _)
- when AttrName =:= include;
- AttrName =:= include_lib ->
- [];
-attribute_subtrees(AttrName, [ArgTuple])
- when AttrName =:= callback;
- AttrName =:= spec ->
- case erl_syntax:type(ArgTuple) of
- tuple ->
- [FATree | Rest] = erl_syntax:tuple_elements(ArgTuple),
- [ case spec_function_name(FATree) of
- {_, _} -> [];
- undefined -> [FATree]
- end
- , Rest ];
- _ ->
- [[ArgTuple]]
- end;
-attribute_subtrees(AttrName, [ArgTuple])
- when AttrName =:= type;
- AttrName =:= opaque ->
- case erl_syntax:type(ArgTuple) of
- tuple ->
- [Type | Rest] = erl_syntax:tuple_elements(ArgTuple),
- [skip_name_atom(Type), Rest];
- _ ->
- [ArgTuple]
- end;
-attribute_subtrees(AttrName, Args)
- when is_atom(AttrName) ->
- [Args];
+ %% The definition can contain commas, in which case it will look like as if
+ %% the attribute would have more than two arguments. Eg.: `-define(M, a, b).'
+ Args =
+ case erl_syntax:type(Name) of
+ application -> erl_syntax:application_arguments(Name);
+ _ -> []
+ end,
+ [Args, Definition];
+attribute_subtrees(AttrName, _) when
+ AttrName =:= include;
+ AttrName =:= include_lib
+->
+ [];
+attribute_subtrees(AttrName, [ArgTuple]) when
+ AttrName =:= callback;
+ AttrName =:= spec
+->
+ case erl_syntax:type(ArgTuple) of
+ tuple ->
+ [FATree | Rest] = erl_syntax:tuple_elements(ArgTuple),
+ [
+ case spec_function_name(FATree) of
+ {_, _} -> [];
+ undefined -> [FATree]
+ end,
+ Rest
+ ];
+ _ ->
+ [[ArgTuple]]
+ end;
+attribute_subtrees(AttrName, [ArgTuple]) when
+ AttrName =:= type;
+ AttrName =:= opaque
+->
+ case erl_syntax:type(ArgTuple) of
+ tuple ->
+ [Type | Rest] = erl_syntax:tuple_elements(ArgTuple),
+ [skip_name_atom(Type), Rest];
+ _ ->
+ [ArgTuple]
+ end;
+attribute_subtrees(AttrName, Args) when
+ is_atom(AttrName)
+->
+ [Args];
attribute_subtrees(AttrName, Args) ->
- %% Attribute name not an atom, probably a macro
- [[AttrName], Args].
+ %% Attribute name not an atom, probably a macro
+ [[AttrName], Args].
%% Skip visiting atoms of import/export entries
-spec skip_function_entries(tree()) -> [tree()].
skip_function_entries(FunList) ->
- case erl_syntax:type(FunList) of
- list ->
- lists:filter(
- fun(FATree) ->
- case get_name_arity(FATree) of
- {_, _} -> false;
- false -> true
- end
- end, erl_syntax:list_elements(FunList));
- _ ->
- [FunList]
- end.
+ case erl_syntax:type(FunList) of
+ list ->
+ lists:filter(
+ fun(FATree) ->
+ case get_name_arity(FATree) of
+ {_, _} -> false;
+ false -> true
+ end
+ end,
+ erl_syntax:list_elements(FunList)
+ );
+ _ ->
+ [FunList]
+ end.
%% Helpers for determining valid Folding Ranges
-spec exceeds_one_line(erl_anno:line(), erl_anno:line()) ->
- poi_range() | oneliner.
+ poi_range() | oneliner.
exceeds_one_line(StartLine, EndLine) when EndLine > StartLine ->
- #{ from => {StartLine, ?END_OF_LINE}
- , to => {EndLine, ?END_OF_LINE}
- };
-exceeds_one_line(_, _) -> oneliner.
+ #{
+ from => {StartLine, ?END_OF_LINE},
+ to => {EndLine, ?END_OF_LINE}
+ };
+exceeds_one_line(_, _) ->
+ oneliner.
%% Skip visiting atoms of record names as they are already
%% represented as `record_expr' pois
-spec skip_record_name_atom(tree()) -> [tree()].
skip_record_name_atom(NameNode) ->
- case is_record_name(NameNode) of
- {true, _} ->
- [];
- _ ->
- [NameNode]
- end.
+ case is_record_name(NameNode) of
+ {true, _} ->
+ [];
+ _ ->
+ [NameNode]
+ end.
%% Skip visiting atoms as they are already represented as other pois
-spec skip_name_atom(tree()) -> [tree()].
skip_name_atom(NameNode) ->
- case erl_syntax:type(NameNode) of
- atom ->
- [];
- _ ->
- [NameNode]
- end.
+ case erl_syntax:type(NameNode) of
+ atom ->
+ [];
+ _ ->
+ [NameNode]
+ end.
-spec skip_type_name_atom(tree()) -> [tree()].
skip_type_name_atom(NameNode) ->
- case erl_syntax:type(NameNode) of
- atom ->
- [];
- module_qualifier ->
- skip_name_atom(erl_syntax:module_qualifier_body(NameNode))
- ++
- skip_name_atom(erl_syntax:module_qualifier_argument(NameNode));
- _ ->
- [NameNode]
- end.
+ case erl_syntax:type(NameNode) of
+ atom ->
+ [];
+ module_qualifier ->
+ skip_name_atom(erl_syntax:module_qualifier_body(NameNode)) ++
+ skip_name_atom(erl_syntax:module_qualifier_argument(NameNode));
+ _ ->
+ [NameNode]
+ end.
-spec pretty_print_clause(tree()) -> binary().
pretty_print_clause(Tree) ->
- Patterns = erl_syntax:clause_patterns(Tree),
- PrettyPatterns = [ erl_prettypr:format(P) || P <- Patterns],
- Guard = erl_syntax:clause_guard(Tree),
- PrettyGuard = case Guard of
- none ->
- "";
- _ ->
- "when " ++ erl_prettypr:format(Guard)
- end,
- PrettyClause = io_lib:format( "(~ts) ~ts"
- , [ string:join(PrettyPatterns, ", ")
- , PrettyGuard
- ]),
- els_utils:to_binary(PrettyClause).
+ Patterns = erl_syntax:clause_patterns(Tree),
+ PrettyPatterns = [erl_prettypr:format(P) || P <- Patterns],
+ Guard = erl_syntax:clause_guard(Tree),
+ PrettyGuard =
+ case Guard of
+ none ->
+ "";
+ _ ->
+ "when " ++ erl_prettypr:format(Guard)
+ end,
+ PrettyClause = io_lib:format(
+ "(~ts) ~ts",
+ [
+ string:join(PrettyPatterns, ", "),
+ PrettyGuard
+ ]
+ ),
+ els_utils:to_binary(PrettyClause).
-spec record_access_location(tree()) -> erl_anno:anno().
record_access_location(Tree) ->
- %% erlfmt_parser sets start at the start of the argument expression
- %% we don't have an exact location of '#'
- %% best approximation is the end of the argument
- Start = get_end_location(erl_syntax:record_access_argument(Tree)),
- Anno = erl_syntax:get_pos(erl_syntax:record_access_type(Tree)),
- erl_anno:set_location(Start, Anno).
+ %% erlfmt_parser sets start at the start of the argument expression
+ %% we don't have an exact location of '#'
+ %% best approximation is the end of the argument
+ Start = get_end_location(erl_syntax:record_access_argument(Tree)),
+ Anno = erl_syntax:get_pos(erl_syntax:record_access_type(Tree)),
+ erl_anno:set_location(Start, Anno).
-spec record_expr_location(tree(), tree()) -> erl_anno:anno().
record_expr_location(Tree, RecordName) ->
- %% set start location at '#'
- %% and end location at the end of record name
- Start = record_expr_start_location(Tree),
- Anno = erl_syntax:get_pos(RecordName),
- erl_anno:set_location(Start, Anno).
+ %% set start location at '#'
+ %% and end location at the end of record name
+ Start = record_expr_start_location(Tree),
+ Anno = erl_syntax:get_pos(RecordName),
+ erl_anno:set_location(Start, Anno).
-spec record_expr_start_location(tree()) -> erl_anno:location().
record_expr_start_location(Tree) ->
- %% If this is a new record creation or record type
- %% the tree start location is at '#'.
- %% However if this is a record update, then
- %% we don't have an exact location of '#',
- %% best approximation is the end of the argument.
- case erl_syntax:type(Tree) of
- record_expr ->
- case erl_syntax:record_expr_argument(Tree) of
- none ->
- get_start_location(Tree);
- RecordArg ->
- get_end_location(RecordArg)
- end;
- record_type ->
- get_start_location(Tree)
- end.
+ %% If this is a new record creation or record type
+ %% the tree start location is at '#'.
+ %% However if this is a record update, then
+ %% we don't have an exact location of '#',
+ %% best approximation is the end of the argument.
+ case erl_syntax:type(Tree) of
+ record_expr ->
+ case erl_syntax:record_expr_argument(Tree) of
+ none ->
+ get_start_location(Tree);
+ RecordArg ->
+ get_end_location(RecordArg)
+ end;
+ record_type ->
+ get_start_location(Tree)
+ end.
-spec macro_location(tree()) -> erl_anno:anno().
macro_location(Tree) ->
- %% set start location at '?'
- %% and end location at the end of macro name
- %% (exclude arguments)
- Start = get_start_location(Tree),
- MacroName = erl_syntax:macro_name(Tree),
- Anno = erl_syntax:get_pos(MacroName),
- erl_anno:set_location(Start, Anno).
+ %% set start location at '?'
+ %% and end location at the end of macro name
+ %% (exclude arguments)
+ Start = get_start_location(Tree),
+ MacroName = erl_syntax:macro_name(Tree),
+ Anno = erl_syntax:get_pos(MacroName),
+ erl_anno:set_location(Start, Anno).
-spec get_start_location(tree()) -> erl_anno:location().
get_start_location(Tree) ->
- erl_anno:location(erl_syntax:get_pos(Tree)).
+ erl_anno:location(erl_syntax:get_pos(Tree)).
-spec get_end_location(tree()) -> erl_anno:location().
get_end_location(Tree) ->
- %% erl_anno:end_location(erl_syntax:get_pos(Tree)).
- Anno = erl_syntax:get_pos(Tree),
- proplists:get_value(end_location, erl_anno:to_term(Anno)).
+ %% erl_anno:end_location(erl_syntax:get_pos(Tree)).
+ Anno = erl_syntax:get_pos(Tree),
+ proplists:get_value(end_location, erl_anno:to_term(Anno)).
diff --git a/apps/els_lsp/src/els_poi.erl b/apps/els_lsp/src/els_poi.erl
index 18018d9f6..53cc5abf9 100644
--- a/apps/els_lsp/src/els_poi.erl
+++ b/apps/els_lsp/src/els_poi.erl
@@ -4,13 +4,15 @@
-module(els_poi).
%% Constructor
--export([ new/3
- , new/4
- ]).
+-export([
+ new/3,
+ new/4
+]).
--export([ match_pos/2
- , sort/1
- ]).
+-export([
+ match_pos/2,
+ sort/1
+]).
%%==============================================================================
%% Includes
@@ -24,34 +26,42 @@
%% @doc Constructor for a Point of Interest.
-spec new(poi_range(), poi_kind(), any()) -> poi().
new(Range, Kind, Id) ->
- new(Range, Kind, Id, undefined).
+ new(Range, Kind, Id, undefined).
%% @doc Constructor for a Point of Interest.
-spec new(poi_range(), poi_kind(), any(), any()) -> poi().
new(Range, Kind, Id, Data) ->
- #{ kind => Kind
- , id => Id
- , data => Data
- , range => Range
- }.
+ #{
+ kind => Kind,
+ id => Id,
+ data => Data,
+ range => Range
+ }.
-spec match_pos([poi()], pos()) -> [poi()].
match_pos(POIs, Pos) ->
- [POI || #{range := #{ from := From
- , to := To
- }} = POI <- POIs, (From =< Pos) andalso (Pos =< To)].
+ [
+ POI
+ || #{
+ range := #{
+ from := From,
+ to := To
+ }
+ } = POI <- POIs,
+ (From =< Pos) andalso (Pos =< To)
+ ].
%% @doc Sorts pois based on their range
%%
%% Order is defined using els_range:compare/2.
-spec sort([poi()]) -> [poi()].
sort(POIs) ->
- lists:sort(fun compare/2, POIs).
+ lists:sort(fun compare/2, POIs).
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec compare(poi(), poi()) -> boolean().
-compare(#{range := A}, #{range := B}) ->
- els_range:compare(A, B).
+compare(#{range := A}, #{range := B}) ->
+ els_range:compare(A, B).
diff --git a/apps/els_lsp/src/els_progress.erl b/apps/els_lsp/src/els_progress.erl
index b756aa427..936abf8fd 100644
--- a/apps/els_lsp/src/els_progress.erl
+++ b/apps/els_lsp/src/els_progress.erl
@@ -11,32 +11,36 @@
%%==============================================================================
%% Exports
%%==============================================================================
--export([ send_notification/2
- , token/0
- ]).
+-export([
+ send_notification/2,
+ token/0
+]).
%%==============================================================================
%% Types
%%==============================================================================
-type token() :: binary().
-type value() :: els_work_done_progress:value().
--type params() :: #{ token := token()
- , value := value()
- }.
--export_type([ token/0
- , params/0
- ]).
+-type params() :: #{
+ token := token(),
+ value := value()
+}.
+-export_type([
+ token/0,
+ params/0
+]).
%%==============================================================================
%% API
%%==============================================================================
-spec send_notification(token(), value()) -> ok.
send_notification(Token, Value) ->
- Params = #{ token => Token
- , value => Value
- },
- els_server:send_notification(?METHOD, Params).
+ Params = #{
+ token => Token,
+ value => Value
+ },
+ els_server:send_notification(?METHOD, Params).
-spec token() -> token().
token() ->
- list_to_binary(uuid:uuid_to_string(uuid:get_v4())).
+ list_to_binary(uuid:uuid_to_string(uuid:get_v4())).
diff --git a/apps/els_lsp/src/els_range.erl b/apps/els_lsp/src/els_range.erl
index 1cea9e8c9..5e3c2f339 100644
--- a/apps/els_lsp/src/els_range.erl
+++ b/apps/els_lsp/src/els_range.erl
@@ -2,88 +2,98 @@
-include("els_lsp.hrl").
--export([ compare/2
- , in/2
- , range/4
- , range/1
- , line/1
- , to_poi_range/1
- , inclusion_range/2
- ]).
+-export([
+ compare/2,
+ in/2,
+ range/4,
+ range/1,
+ line/1,
+ to_poi_range/1,
+ inclusion_range/2
+]).
-spec compare(poi_range(), poi_range()) -> boolean().
-compare( #{from := FromA, to := ToA}
- , #{from := FromB, to := ToB}
- ) when FromB =< FromA, ToA =< ToB; %% Nested
- ToA =< FromB; %% Sequential
- FromA =< FromB, ToA =< ToB %% Sequential & Overlapped
- ->
- true;
+compare(
+ #{from := FromA, to := ToA},
+ #{from := FromB, to := ToB}
+ %% Nested
+) when
+ FromB =< FromA, ToA =< ToB;
+ %% Sequential
+ ToA =< FromB;
+ %% Sequential & Overlapped
+ FromA =< FromB, ToA =< ToB
+->
+ true;
compare(_, _) ->
- false.
+ false.
-spec in(poi_range(), poi_range()) -> boolean().
in(#{from := FromA, to := ToA}, #{from := FromB, to := ToB}) ->
- FromA >= FromB andalso ToA =< ToB.
+ FromA >= FromB andalso ToA =< ToB.
--spec range(pos() | {pos(), pos()} | erl_anno:anno(), poi_kind(), any(), any())
- -> poi_range().
-range({{_Line, _Column} = From, {_ToLine, _ToColumn} = To}, Name, _, _Data)
- when Name =:= export;
- Name =:= export_type;
- Name =:= spec ->
- %% range from unparsable tokens
- #{ from => From, to => To };
+-spec range(pos() | {pos(), pos()} | erl_anno:anno(), poi_kind(), any(), any()) ->
+ poi_range().
+range({{_Line, _Column} = From, {_ToLine, _ToColumn} = To}, Name, _, _Data) when
+ Name =:= export;
+ Name =:= export_type;
+ Name =:= spec
+->
+ %% range from unparsable tokens
+ #{from => From, to => To};
range({Line, Column}, function_clause, {F, _A, _Index}, _Data) ->
- From = {Line, Column},
- To = plus(From, atom_to_string(F)),
- #{ from => From, to => To };
+ From = {Line, Column},
+ To = plus(From, atom_to_string(F)),
+ #{from => From, to => To};
range(Anno, _Type, _Id, _Data) ->
- range(Anno).
+ range(Anno).
-spec range(erl_anno:anno()) -> poi_range().
range(Anno) ->
- From = erl_anno:location(Anno),
- %% To = erl_anno:end_location(Anno),
- To = proplists:get_value(end_location, erl_anno:to_term(Anno)),
- #{ from => From, to => To }.
+ From = erl_anno:location(Anno),
+ %% To = erl_anno:end_location(Anno),
+ To = proplists:get_value(end_location, erl_anno:to_term(Anno)),
+ #{from => From, to => To}.
-spec line(poi_range()) -> poi_range().
-line(#{ from := {FromL, _}, to := {ToL, _} }) ->
- #{ from => {FromL, 1}, to => {ToL+1, 1} }.
+line(#{from := {FromL, _}, to := {ToL, _}}) ->
+ #{from => {FromL, 1}, to => {ToL + 1, 1}}.
%% @doc Converts a LSP range into a POI range
-spec to_poi_range(range()) -> poi_range().
to_poi_range(#{'start' := Start, 'end' := End}) ->
- #{'line' := LineStart, 'character' := CharStart} = Start,
- #{'line' := LineEnd, 'character' := CharEnd} = End,
- #{ from => {LineStart + 1, CharStart + 1}
- , to => {LineEnd + 1, CharEnd + 1}
- };
+ #{'line' := LineStart, 'character' := CharStart} = Start,
+ #{'line' := LineEnd, 'character' := CharEnd} = End,
+ #{
+ from => {LineStart + 1, CharStart + 1},
+ to => {LineEnd + 1, CharEnd + 1}
+ };
to_poi_range(#{<<"start">> := Start, <<"end">> := End}) ->
- #{<<"line">> := LineStart, <<"character">> := CharStart} = Start,
- #{<<"line">> := LineEnd, <<"character">> := CharEnd} = End,
- #{ from => {LineStart + 1, CharStart + 1}
- , to => {LineEnd + 1, CharEnd + 1}
- }.
+ #{<<"line">> := LineStart, <<"character">> := CharStart} = Start,
+ #{<<"line">> := LineEnd, <<"character">> := CharEnd} = End,
+ #{
+ from => {LineStart + 1, CharStart + 1},
+ to => {LineEnd + 1, CharEnd + 1}
+ }.
-spec inclusion_range(uri(), els_dt_document:item()) ->
- {ok, poi_range()} | error.
+ {ok, poi_range()} | error.
inclusion_range(Uri, Document) ->
- Path = binary_to_list(els_uri:path(Uri)),
- case
- els_compiler_diagnostics:inclusion_range(Path, Document, include) ++
- els_compiler_diagnostics:inclusion_range(Path, Document, include_lib) of
- [Range|_] ->
- {ok, Range};
- [] ->
- error
- end.
+ Path = binary_to_list(els_uri:path(Uri)),
+ case
+ els_compiler_diagnostics:inclusion_range(Path, Document, include) ++
+ els_compiler_diagnostics:inclusion_range(Path, Document, include_lib)
+ of
+ [Range | _] ->
+ {ok, Range};
+ [] ->
+ error
+ end.
-spec plus(pos(), string()) -> pos().
plus({Line, Column}, String) ->
- {Line, Column + string:length(String)}.
+ {Line, Column + string:length(String)}.
-spec atom_to_string(atom()) -> string().
atom_to_string(Atom) ->
- io_lib:write(Atom).
+ io_lib:write(Atom).
diff --git a/apps/els_lsp/src/els_refactorerl_diagnostics.erl b/apps/els_lsp/src/els_refactorerl_diagnostics.erl
index 8e837d79e..8d1cee5da 100644
--- a/apps/els_lsp/src/els_refactorerl_diagnostics.erl
+++ b/apps/els_lsp/src/els_refactorerl_diagnostics.erl
@@ -11,10 +11,11 @@
%%==============================================================================
%% Exports
%%==============================================================================
--export([ is_default/0
- , run/1
- , source/0
- ]).
+-export([
+ is_default/0,
+ run/1,
+ source/0
+]).
%%==============================================================================
%% Includes & Defines
@@ -34,33 +35,33 @@
-spec is_default() -> boolean().
is_default() ->
- false.
+ false.
-spec run(uri()) -> [els_diagnostics:diagnostic()].
run(Uri) ->
- case filename:extension(Uri) of
- <<".erl">> ->
- case els_refactorerl_utils:referl_node() of
- {error, _} ->
- [];
- {ok, _} ->
- case els_refactorerl_utils:add(Uri) of
- error ->
- [];
- ok ->
- Module = els_uri:module(Uri),
- Diags = enabled_diagnostics(),
- Results = els_refactorerl_utils:run_diagnostics(Diags, Module),
- make_diagnostics(Results)
- end
- end;
- _ ->
- []
- end.
+ case filename:extension(Uri) of
+ <<".erl">> ->
+ case els_refactorerl_utils:referl_node() of
+ {error, _} ->
+ [];
+ {ok, _} ->
+ case els_refactorerl_utils:add(Uri) of
+ error ->
+ [];
+ ok ->
+ Module = els_uri:module(Uri),
+ Diags = enabled_diagnostics(),
+ Results = els_refactorerl_utils:run_diagnostics(Diags, Module),
+ make_diagnostics(Results)
+ end
+ end;
+ _ ->
+ []
+ end.
-spec source() -> binary().
source() ->
- els_refactorerl_utils:source_name().
+ els_refactorerl_utils:source_name().
%%==============================================================================
%% Internal Functions
@@ -69,22 +70,20 @@ source() ->
% Returns the enabled diagnostics by merging default and configed
-spec enabled_diagnostics() -> [refactorerl_diagnostic_alias()].
enabled_diagnostics() ->
- case els_config:get(refactorerl) of
- #{"diagnostics" := List} ->
- [list_to_atom(Element) || Element <- List];
- _ ->
- []
- end.
-
+ case els_config:get(refactorerl) of
+ #{"diagnostics" := List} ->
+ [list_to_atom(Element) || Element <- List];
+ _ ->
+ []
+ end.
% @doc
% Constructs the ELS diagnostic from RefactorErl result
-spec make_diagnostics([refactorerl_diagnostic_result()]) -> any().
make_diagnostics([{Range, Message} | Tail]) ->
- Severity = ?DIAGNOSTIC_WARNING,
- Source = source(),
- Diag = els_diagnostics:make_diagnostic(Range, Message, Severity, Source),
- [ Diag | make_diagnostics(Tail) ];
-
+ Severity = ?DIAGNOSTIC_WARNING,
+ Source = source(),
+ Diag = els_diagnostics:make_diagnostic(Range, Message, Severity, Source),
+ [Diag | make_diagnostics(Tail)];
make_diagnostics([]) ->
- [].
+ [].
diff --git a/apps/els_lsp/src/els_refactorerl_utils.erl b/apps/els_lsp/src/els_refactorerl_utils.erl
index 3a666e168..9bb78ebc0 100644
--- a/apps/els_lsp/src/els_refactorerl_utils.erl
+++ b/apps/els_lsp/src/els_refactorerl_utils.erl
@@ -6,13 +6,14 @@
%%==============================================================================
%% API
%%==============================================================================
--export([ referl_node/0
- , notification/1
- , notification/2
- , run_diagnostics/2
- , source_name/0
- , add/1
- ]).
+-export([
+ referl_node/0,
+ notification/1,
+ notification/2,
+ run_diagnostics/2,
+ source_name/0,
+ add/1
+]).
%%==============================================================================
%% Includes & Defines
@@ -47,33 +48,28 @@
%% - disconnected: node is not running
%% - disabled: RefactorErl is turned off for this session. T
%% his can happen after an unsuccessfull query attempt.
--spec referl_node() -> {ok, atom()}
- | {error, disconnected}
- | {error, disabled}
- | {error, other}.
+-spec referl_node() ->
+ {ok, atom()}
+ | {error, disconnected}
+ | {error, disabled}
+ | {error, other}.
referl_node() ->
- case els_config:get(refactorerl) of
- #{"node" := {Node, validated}} ->
- {ok, Node};
-
- #{"node" := {Node, disconnected}} ->
- connect_node({retry, Node});
-
- #{"node" := {_Node, disabled}} ->
- {error, disabled};
-
- #{"node" := NodeStr} ->
- RT = els_config_runtime:get_name_type(),
- Node = els_utils:compose_node_name(NodeStr, RT),
- connect_node({validate, Node});
-
- notconfigured ->
- {error, disabled};
-
- _ ->
- {error, other}
- end.
-
+ case els_config:get(refactorerl) of
+ #{"node" := {Node, validated}} ->
+ {ok, Node};
+ #{"node" := {Node, disconnected}} ->
+ connect_node({retry, Node});
+ #{"node" := {_Node, disabled}} ->
+ {error, disabled};
+ #{"node" := NodeStr} ->
+ RT = els_config_runtime:get_name_type(),
+ Node = els_utils:compose_node_name(NodeStr, RT),
+ connect_node({validate, Node});
+ notconfigured ->
+ {error, disabled};
+ _ ->
+ {error, other}
+ end.
%%@doc
%% Adds a module to the RefactorErl node. Using the UI router
@@ -81,37 +77,41 @@ referl_node() ->
%% Returns 'error' if it fails
-spec add(uri()) -> error | ok.
add(Uri) ->
- case els_refactorerl_utils:referl_node() of
- {ok, Node} ->
- Path = [binary_to_list(els_uri:path(Uri))],
- rpc:call(Node, referl_els, add, [Path]); %% returns error | ok
- _ ->
- error
- end.
+ case els_refactorerl_utils:referl_node() of
+ {ok, Node} ->
+ Path = [binary_to_list(els_uri:path(Uri))],
+ %% returns error | ok
+ rpc:call(Node, referl_els, add, [Path]);
+ _ ->
+ error
+ end.
%%@doc
%% Runs list of diagnostic aliases on refactorerl
-spec run_diagnostics(list(), atom()) -> list().
run_diagnostics(DiagnosticAliases, Module) ->
- case els_refactorerl_utils:referl_node() of
- {ok, Node} ->
- %% returns error | ok
- rpc:call(Node, referl_els, run_diagnostics, [DiagnosticAliases, Module]);
- _ -> % In this case there was probably error.
- []
- end.
+ case els_refactorerl_utils:referl_node() of
+ {ok, Node} ->
+ %% returns error | ok
+ rpc:call(Node, referl_els, run_diagnostics, [DiagnosticAliases, Module]);
+ % In this case there was probably error.
+ _ ->
+ []
+ end.
%%@doc
%% Util for popping up notifications
-spec notification(string(), number()) -> atom().
notification(Msg, Severity) ->
- Param = #{ type => Severity,
- message => list_to_binary(Msg) },
- els_server:send_notification(<<"window/showMessage">>, Param).
+ Param = #{
+ type => Severity,
+ message => list_to_binary(Msg)
+ },
+ els_server:send_notification(<<"window/showMessage">>, Param).
-spec notification(string()) -> atom().
notification(Msg) ->
- notification(Msg, ?MESSAGE_TYPE_INFO).
+ notification(Msg, ?MESSAGE_TYPE_INFO).
%%==============================================================================
%% Internal Functions
@@ -121,10 +121,10 @@ notification(Msg) ->
%% Checks if the given node is running RefactorErl with ELS interface
-spec is_refactorerl(atom()) -> boolean().
is_refactorerl(Node) ->
- case rpc:call(Node, referl_els, ping, [], 500) of
- {refactorerl_els, pong} -> true;
- _ -> false
- end.
+ case rpc:call(Node, referl_els, ping, [], 500) of
+ {refactorerl_els, pong} -> true;
+ _ -> false
+ end.
%%@doc
%% Tries to connect to a node.
@@ -132,22 +132,23 @@ is_refactorerl(Node) ->
%% so it will reports the success, and failure as well,
%%
%% when retry, it won't report.
--spec connect_node({validate | retry, atom()}) -> {error, disconnected}
- | atom().
+-spec connect_node({validate | retry, atom()}) ->
+ {error, disconnected}
+ | atom().
connect_node({Status, Node}) ->
- Config = els_config:get(refactorerl),
- case {Status, is_refactorerl(Node)} of
- {validate, false} ->
- els_config:set(refactorerl, Config#{"node" => {Node, disconnected}}),
- {error, disconnected};
- {retry, false} ->
- els_config:set(refactorerl, Config#{"node" => {Node, disconnected}}),
- {error, disconnected};
- {_, true} ->
- notification("RefactorErl is connected!", ?MESSAGE_TYPE_INFO),
- els_config:set(refactorerl, Config#{"node" => {Node, validated}}),
- {ok, Node}
- end.
+ Config = els_config:get(refactorerl),
+ case {Status, is_refactorerl(Node)} of
+ {validate, false} ->
+ els_config:set(refactorerl, Config#{"node" => {Node, disconnected}}),
+ {error, disconnected};
+ {retry, false} ->
+ els_config:set(refactorerl, Config#{"node" => {Node, disconnected}}),
+ {error, disconnected};
+ {_, true} ->
+ notification("RefactorErl is connected!", ?MESSAGE_TYPE_INFO),
+ els_config:set(refactorerl, Config#{"node" => {Node, validated}}),
+ {ok, Node}
+ end.
%%==============================================================================
%% Values
@@ -157,4 +158,4 @@ connect_node({Status, Node}) ->
%% Common soruce name for all RefactorErl based backend(s)
-spec source_name() -> binary().
source_name() ->
- <<"RefactorErl">>.
\ No newline at end of file
+ <<"RefactorErl">>.
diff --git a/apps/els_lsp/src/els_references_provider.erl b/apps/els_lsp/src/els_references_provider.erl
index a6467e8b0..ad595bfdd 100644
--- a/apps/els_lsp/src/els_references_provider.erl
+++ b/apps/els_lsp/src/els_references_provider.erl
@@ -2,15 +2,17 @@
-behaviour(els_provider).
--export([ is_enabled/0
- , handle_request/2
- ]).
+-export([
+ is_enabled/0,
+ handle_request/2
+]).
%% For use in other providers
--export([ find_references/2
- , find_scoped_references_for_def/2
- , find_references_to_module/1
- ]).
+-export([
+ find_references/2,
+ find_scoped_references_for_def/2,
+ find_references_to_module/1
+]).
%%==============================================================================
%% Includes
@@ -29,141 +31,163 @@ is_enabled() -> true.
-spec handle_request(any(), any()) -> {response, [location()] | null}.
handle_request({references, Params}, _State) ->
- #{ <<"position">> := #{ <<"line">> := Line
- , <<"character">> := Character
- }
- , <<"textDocument">> := #{<<"uri">> := Uri}
- } = Params,
- {ok, Document} = els_utils:lookup_document(Uri),
- Refs =
- case
- els_dt_document:get_element_at_pos(Document, Line + 1, Character + 1)
- of
- [POI | _] -> find_references(Uri, POI);
- [] -> []
- end,
- case Refs of
- [] -> {response, null};
- Rs -> {response, Rs}
- end.
+ #{
+ <<"position">> := #{
+ <<"line">> := Line,
+ <<"character">> := Character
+ },
+ <<"textDocument">> := #{<<"uri">> := Uri}
+ } = Params,
+ {ok, Document} = els_utils:lookup_document(Uri),
+ Refs =
+ case els_dt_document:get_element_at_pos(Document, Line + 1, Character + 1) of
+ [POI | _] -> find_references(Uri, POI);
+ [] -> []
+ end,
+ case Refs of
+ [] -> {response, null};
+ Rs -> {response, Rs}
+ end.
%%==============================================================================
%% Internal functions
%%==============================================================================
-spec find_references(uri(), poi()) -> [location()].
-find_references(Uri, #{ kind := Kind
- , id := Id
- }) when Kind =:= application;
- Kind =:= implicit_fun;
- Kind =:= function;
- Kind =:= export_entry;
- Kind =:= export_type_entry
- ->
- Key = case Id of
- {F, A} -> {els_uri:module(Uri), F, A};
- {M, F, A} -> {M, F, A}
+find_references(Uri, #{
+ kind := Kind,
+ id := Id
+}) when
+ Kind =:= application;
+ Kind =:= implicit_fun;
+ Kind =:= function;
+ Kind =:= export_entry;
+ Kind =:= export_type_entry
+->
+ Key =
+ case Id of
+ {F, A} -> {els_uri:module(Uri), F, A};
+ {M, F, A} -> {M, F, A}
end,
- find_references_for_id(Kind, Key);
+ find_references_for_id(Kind, Key);
find_references(Uri, #{kind := variable} = Var) ->
- POIs = els_code_navigation:find_in_scope(Uri, Var),
- [location(Uri, Range) || #{range := Range} = POI <- POIs, POI =/= Var];
-find_references(Uri, #{ kind := Kind
- , id := Id
- }) when Kind =:= function_clause ->
- {F, A, _Index} = Id,
- Key = {els_uri:module(Uri), F, A},
- find_references_for_id(Kind, Key);
-find_references(Uri, Poi = #{kind := Kind})
- when Kind =:= record;
- Kind =:= record_def_field;
- Kind =:= define ->
- uri_pois_to_locations(
- find_scoped_references_for_def(Uri, Poi));
-find_references(Uri, Poi = #{kind := Kind, id := Id})
- when Kind =:= type_definition ->
- Key = case Id of
- {F, A} -> {els_uri:module(Uri), F, A};
- {M, F, A} -> {M, F, A}
+ POIs = els_code_navigation:find_in_scope(Uri, Var),
+ [location(Uri, Range) || #{range := Range} = POI <- POIs, POI =/= Var];
+find_references(Uri, #{
+ kind := Kind,
+ id := Id
+}) when Kind =:= function_clause ->
+ {F, A, _Index} = Id,
+ Key = {els_uri:module(Uri), F, A},
+ find_references_for_id(Kind, Key);
+find_references(Uri, Poi = #{kind := Kind}) when
+ Kind =:= record;
+ Kind =:= record_def_field;
+ Kind =:= define
+->
+ uri_pois_to_locations(
+ find_scoped_references_for_def(Uri, Poi)
+ );
+find_references(Uri, Poi = #{kind := Kind, id := Id}) when
+ Kind =:= type_definition
+->
+ Key =
+ case Id of
+ {F, A} -> {els_uri:module(Uri), F, A};
+ {M, F, A} -> {M, F, A}
end,
- lists:usort(find_references_for_id(Kind, Key) ++
- uri_pois_to_locations(
- find_scoped_references_for_def(Uri, Poi)));
-find_references(Uri, Poi = #{kind := Kind})
- when Kind =:= record_expr;
- Kind =:= record_field;
- Kind =:= macro;
- Kind =:= type_application ->
- case els_code_navigation:goto_definition(Uri, Poi) of
- {ok, DefUri, DefPoi} ->
- find_references(DefUri, DefPoi);
- {error, _} ->
- %% look for references only in the current document
- uri_pois_to_locations(
- find_scoped_references_for_def(Uri, Poi))
- end;
+ lists:usort(
+ find_references_for_id(Kind, Key) ++
+ uri_pois_to_locations(
+ find_scoped_references_for_def(Uri, Poi)
+ )
+ );
+find_references(Uri, Poi = #{kind := Kind}) when
+ Kind =:= record_expr;
+ Kind =:= record_field;
+ Kind =:= macro;
+ Kind =:= type_application
+->
+ case els_code_navigation:goto_definition(Uri, Poi) of
+ {ok, DefUri, DefPoi} ->
+ find_references(DefUri, DefPoi);
+ {error, _} ->
+ %% look for references only in the current document
+ uri_pois_to_locations(
+ find_scoped_references_for_def(Uri, Poi)
+ )
+ end;
find_references(Uri, #{kind := module}) ->
- Refs = find_references_to_module(Uri),
- [location(U, R) || #{uri := U, range := R} <- Refs];
-find_references(_Uri, #{kind := Kind, id := Name})
- when Kind =:= behaviour ->
- find_references_for_id(Kind, Name);
+ Refs = find_references_to_module(Uri),
+ [location(U, R) || #{uri := U, range := R} <- Refs];
+find_references(_Uri, #{kind := Kind, id := Name}) when
+ Kind =:= behaviour
+->
+ find_references_for_id(Kind, Name);
find_references(_Uri, _POI) ->
- [].
+ [].
-spec find_scoped_references_for_def(uri(), poi()) -> [{uri(), poi()}].
find_scoped_references_for_def(Uri, #{kind := Kind, id := Name}) ->
- Kinds = kind_to_ref_kinds(Kind),
- Refs = els_scope:local_and_includer_pois(Uri, Kinds),
- [{U, Poi} || {U, Pois} <- Refs,
- #{id := N} = Poi <- Pois,
- N =:= Name].
+ Kinds = kind_to_ref_kinds(Kind),
+ Refs = els_scope:local_and_includer_pois(Uri, Kinds),
+ [
+ {U, Poi}
+ || {U, Pois} <- Refs,
+ #{id := N} = Poi <- Pois,
+ N =:= Name
+ ].
-spec kind_to_ref_kinds(poi_kind()) -> [poi_kind()].
kind_to_ref_kinds(define) ->
- [macro];
+ [macro];
kind_to_ref_kinds(record) ->
- [record_expr];
+ [record_expr];
kind_to_ref_kinds(record_def_field) ->
- [record_field];
+ [record_field];
kind_to_ref_kinds(type_definition) ->
- [type_application];
+ [type_application];
kind_to_ref_kinds(Kind) ->
- [Kind].
+ [Kind].
-spec find_references_to_module(uri()) -> [els_dt_references:item()].
find_references_to_module(Uri) ->
- M = els_uri:module(Uri),
- {ok, Doc} = els_utils:lookup_document(Uri),
- ExportRefs =
- lists:flatmap(
- fun(#{id := {F, A}}) ->
- {ok, Rs} =
- els_dt_references:find_by_id(export_entry, {M, F, A}),
- Rs
- end, els_dt_document:pois(Doc, [export_entry])),
- ExportTypeRefs =
- lists:flatmap(
- fun(#{id := {F, A}}) ->
- {ok, Rs} =
- els_dt_references:find_by_id(export_type_entry, {M, F, A}),
- Rs
- end, els_dt_document:pois(Doc, [export_type_entry])),
- {ok, BehaviourRefs} = els_dt_references:find_by_id(behaviour, M),
- ExcludeLocalRefs = fun(Loc) -> maps:get(uri, Loc) =/= Uri end,
- lists:filter(ExcludeLocalRefs, ExportRefs ++ ExportTypeRefs ++ BehaviourRefs).
+ M = els_uri:module(Uri),
+ {ok, Doc} = els_utils:lookup_document(Uri),
+ ExportRefs =
+ lists:flatmap(
+ fun(#{id := {F, A}}) ->
+ {ok, Rs} =
+ els_dt_references:find_by_id(export_entry, {M, F, A}),
+ Rs
+ end,
+ els_dt_document:pois(Doc, [export_entry])
+ ),
+ ExportTypeRefs =
+ lists:flatmap(
+ fun(#{id := {F, A}}) ->
+ {ok, Rs} =
+ els_dt_references:find_by_id(export_type_entry, {M, F, A}),
+ Rs
+ end,
+ els_dt_document:pois(Doc, [export_type_entry])
+ ),
+ {ok, BehaviourRefs} = els_dt_references:find_by_id(behaviour, M),
+ ExcludeLocalRefs = fun(Loc) -> maps:get(uri, Loc) =/= Uri end,
+ lists:filter(ExcludeLocalRefs, ExportRefs ++ ExportTypeRefs ++ BehaviourRefs).
-spec find_references_for_id(poi_kind(), any()) -> [location()].
find_references_for_id(Kind, Id) ->
- {ok, Refs} = els_dt_references:find_by_id(Kind, Id),
- [location(U, R) || #{uri := U, range := R} <- Refs].
+ {ok, Refs} = els_dt_references:find_by_id(Kind, Id),
+ [location(U, R) || #{uri := U, range := R} <- Refs].
-spec uri_pois_to_locations([{uri(), poi()}]) -> [location()].
uri_pois_to_locations(Refs) ->
- [location(U, R) || {U, #{range := R}} <- Refs].
+ [location(U, R) || {U, #{range := R}} <- Refs].
-spec location(uri(), poi_range()) -> location().
location(Uri, Range) ->
- #{ uri => Uri
- , range => els_protocol:range(Range)
- }.
+ #{
+ uri => Uri,
+ range => els_protocol:range(Range)
+ }.
diff --git a/apps/els_lsp/src/els_rename_provider.erl b/apps/els_lsp/src/els_rename_provider.erl
index e647db24d..9685a51b3 100644
--- a/apps/els_lsp/src/els_rename_provider.erl
+++ b/apps/els_lsp/src/els_rename_provider.erl
@@ -2,9 +2,10 @@
-behaviour(els_provider).
--export([ handle_request/2
- , is_enabled/0
- ]).
+-export([
+ handle_request/2,
+ is_enabled/0
+]).
%%==============================================================================
%% Includes
@@ -28,264 +29,341 @@ is_enabled() -> true.
-spec handle_request(any(), any()) -> {response, any()}.
handle_request({rename, Params}, _State) ->
- #{ <<"textDocument">> := #{<<"uri">> := Uri}
- , <<"position">> := #{ <<"line">> := Line
- , <<"character">> := Character
- }
- , <<"newName">> := NewName
- } = Params,
- {ok, Document} = els_utils:lookup_document(Uri),
- Elem = els_dt_document:get_element_at_pos(Document, Line + 1, Character + 1),
- WorkspaceEdits = workspace_edits(Uri, Elem, NewName),
- {response, WorkspaceEdits}.
+ #{
+ <<"textDocument">> := #{<<"uri">> := Uri},
+ <<"position">> := #{
+ <<"line">> := Line,
+ <<"character">> := Character
+ },
+ <<"newName">> := NewName
+ } = Params,
+ {ok, Document} = els_utils:lookup_document(Uri),
+ Elem = els_dt_document:get_element_at_pos(Document, Line + 1, Character + 1),
+ WorkspaceEdits = workspace_edits(Uri, Elem, NewName),
+ {response, WorkspaceEdits}.
%%==============================================================================
%% Internal functions
%%==============================================================================
-spec workspace_edits(uri(), [poi()], binary()) -> null | [any()].
workspace_edits(_Uri, [], _NewName) ->
- null;
-workspace_edits(OldUri, [#{kind := module} = POI| _], NewName) ->
- %% Generate new Uri
- Path = els_uri:path(OldUri),
- Dir = filename:dirname(Path),
- NewPath = filename:join(Dir, <>),
- NewUri = els_uri:uri(NewPath),
- %% Find references that needs to be changed
- Refs = els_references_provider:find_references_to_module(OldUri),
- RefPOIs = convert_references_to_pois(Refs, [ application
- , implicit_fun
- , import_entry
- , type_application
- , behaviour
- ]),
- Changes = [#{ textDocument => #{uri => RefUri, version => null}
- , edits => [#{ range => editable_range(RefPOI, module)
- , newText => NewName
- }]
- } || {RefUri, RefPOI} <- RefPOIs],
- #{documentChanges =>
- [ %% Update -module attribute
- #{textDocument => #{uri => OldUri, version => null},
- edits => [change(POI, NewName)]
- }
- %% Rename file
- , #{kind => rename, oldUri => OldUri, newUri => NewUri}
- | Changes]
- };
-workspace_edits(Uri, [#{kind := function_clause} = POI| _], NewName) ->
- #{id := {F, A, _}} = POI,
- #{changes => changes(Uri, POI#{kind => function, id => {F, A}}, NewName)};
-workspace_edits(Uri, [#{kind := spec} = POI| _], NewName) ->
- #{changes => changes(Uri, POI#{kind => function}, NewName)};
-workspace_edits(Uri, [#{kind := Kind} = POI| _], NewName)
- when Kind =:= define;
- Kind =:= record;
- Kind =:= record_def_field;
- Kind =:= function;
- Kind =:= type_definition;
- Kind =:= variable ->
- #{changes => changes(Uri, POI, NewName)};
-workspace_edits(Uri, [#{kind := Kind} = POI| _], NewName)
- when Kind =:= macro;
- Kind =:= record_expr;
- Kind =:= record_field;
- Kind =:= application;
- Kind =:= implicit_fun;
- Kind =:= export_entry;
- Kind =:= import_entry;
- Kind =:= export_type_entry;
- Kind =:= type_application ->
- case els_code_navigation:goto_definition(Uri, POI) of
- {ok, DefUri, DefPOI} ->
- #{changes => changes(DefUri, DefPOI, NewName)};
- _ ->
- null
- end;
+ null;
+workspace_edits(OldUri, [#{kind := module} = POI | _], NewName) ->
+ %% Generate new Uri
+ Path = els_uri:path(OldUri),
+ Dir = filename:dirname(Path),
+ NewPath = filename:join(Dir, <>),
+ NewUri = els_uri:uri(NewPath),
+ %% Find references that needs to be changed
+ Refs = els_references_provider:find_references_to_module(OldUri),
+ RefPOIs = convert_references_to_pois(Refs, [
+ application,
+ implicit_fun,
+ import_entry,
+ type_application,
+ behaviour
+ ]),
+ Changes = [
+ #{
+ textDocument => #{uri => RefUri, version => null},
+ edits => [
+ #{
+ range => editable_range(RefPOI, module),
+ newText => NewName
+ }
+ ]
+ }
+ || {RefUri, RefPOI} <- RefPOIs
+ ],
+ #{
+ documentChanges =>
+ %% Update -module attribute
+ [
+ #{
+ textDocument => #{uri => OldUri, version => null},
+ edits => [change(POI, NewName)]
+ },
+ %% Rename file
+ #{kind => rename, oldUri => OldUri, newUri => NewUri}
+ | Changes
+ ]
+ };
+workspace_edits(Uri, [#{kind := function_clause} = POI | _], NewName) ->
+ #{id := {F, A, _}} = POI,
+ #{changes => changes(Uri, POI#{kind => function, id => {F, A}}, NewName)};
+workspace_edits(Uri, [#{kind := spec} = POI | _], NewName) ->
+ #{changes => changes(Uri, POI#{kind => function}, NewName)};
+workspace_edits(Uri, [#{kind := Kind} = POI | _], NewName) when
+ Kind =:= define;
+ Kind =:= record;
+ Kind =:= record_def_field;
+ Kind =:= function;
+ Kind =:= type_definition;
+ Kind =:= variable
+->
+ #{changes => changes(Uri, POI, NewName)};
+workspace_edits(Uri, [#{kind := Kind} = POI | _], NewName) when
+ Kind =:= macro;
+ Kind =:= record_expr;
+ Kind =:= record_field;
+ Kind =:= application;
+ Kind =:= implicit_fun;
+ Kind =:= export_entry;
+ Kind =:= import_entry;
+ Kind =:= export_type_entry;
+ Kind =:= type_application
+->
+ case els_code_navigation:goto_definition(Uri, POI) of
+ {ok, DefUri, DefPOI} ->
+ #{changes => changes(DefUri, DefPOI, NewName)};
+ _ ->
+ null
+ end;
workspace_edits(Uri, [#{kind := 'callback'} = POI | _], NewName) ->
- #{id := {Name, Arity} = Id} = POI,
- Module = els_uri:module(Uri),
- {ok, Refs} = els_dt_references:find_by_id(behaviour, Module),
- Changes =
- lists:foldl(
- fun(#{uri := U}, Acc) ->
- {ok, Doc} = els_utils:lookup_document(U),
- ExportEntries = els_dt_document:pois(Doc, [export_entry]),
- FunctionClauses = els_dt_document:pois(Doc, [function_clause]),
- Specs = els_dt_document:pois(Doc, [spec]),
- Acc#{ U =>
- [ #{ range => editable_range(P)
- , newText => NewName
- } || #{id := I} = P <- ExportEntries, I =:= Id
- ] ++
- [ #{ range => editable_range(P)
- , newText => NewName
- } || #{id := {N, A, _I}} = P <- FunctionClauses
- , N =:= Name
- , A =:= Arity
- ] ++
- [ #{ range => editable_range(P)
- , newText => NewName
- } || #{id := I} = P <- Specs, I =:= Id
- ]
- }
- end, #{ Uri =>
- [#{ range => editable_range(POI)
- , newText => NewName
- }]
- }, Refs),
- #{changes => Changes};
+ #{id := {Name, Arity} = Id} = POI,
+ Module = els_uri:module(Uri),
+ {ok, Refs} = els_dt_references:find_by_id(behaviour, Module),
+ Changes =
+ lists:foldl(
+ fun(#{uri := U}, Acc) ->
+ {ok, Doc} = els_utils:lookup_document(U),
+ ExportEntries = els_dt_document:pois(Doc, [export_entry]),
+ FunctionClauses = els_dt_document:pois(Doc, [function_clause]),
+ Specs = els_dt_document:pois(Doc, [spec]),
+ Acc#{
+ U =>
+ [
+ #{
+ range => editable_range(P),
+ newText => NewName
+ }
+ || #{id := I} = P <- ExportEntries, I =:= Id
+ ] ++
+ [
+ #{
+ range => editable_range(P),
+ newText => NewName
+ }
+ || #{id := {N, A, _I}} = P <- FunctionClauses,
+ N =:= Name,
+ A =:= Arity
+ ] ++
+ [
+ #{
+ range => editable_range(P),
+ newText => NewName
+ }
+ || #{id := I} = P <- Specs, I =:= Id
+ ]
+ }
+ end,
+ #{
+ Uri =>
+ [
+ #{
+ range => editable_range(POI),
+ newText => NewName
+ }
+ ]
+ },
+ Refs
+ ),
+ #{changes => Changes};
workspace_edits(_Uri, _POIs, _NewName) ->
- null.
+ null.
-spec editable_range(poi()) -> range().
editable_range(POI) ->
- editable_range(POI, function).
+ editable_range(POI, function).
-spec editable_range(poi(), function | module) -> range().
-editable_range(#{kind := Kind, data := #{mod_range := Range}}, module)
- when Kind =:= application;
- Kind =:= implicit_fun;
- Kind =:= import_entry;
- Kind =:= type_application;
- Kind =:= behaviour ->
- els_protocol:range(Range);
-editable_range(#{kind := Kind, data := #{name_range := Range}}, function)
- when Kind =:= application;
- Kind =:= implicit_fun;
- Kind =:= callback;
- Kind =:= spec;
- Kind =:= export_entry;
- Kind =:= export_type_entry;
- Kind =:= import_entry;
- Kind =:= type_application;
- Kind =:= type_definition ->
- %% application POI of a local call and
- %% type_application POI of a built-in type don't have name_range data
- %% they are handled by the next clause
- els_protocol:range(Range);
+editable_range(#{kind := Kind, data := #{mod_range := Range}}, module) when
+ Kind =:= application;
+ Kind =:= implicit_fun;
+ Kind =:= import_entry;
+ Kind =:= type_application;
+ Kind =:= behaviour
+->
+ els_protocol:range(Range);
+editable_range(#{kind := Kind, data := #{name_range := Range}}, function) when
+ Kind =:= application;
+ Kind =:= implicit_fun;
+ Kind =:= callback;
+ Kind =:= spec;
+ Kind =:= export_entry;
+ Kind =:= export_type_entry;
+ Kind =:= import_entry;
+ Kind =:= type_application;
+ Kind =:= type_definition
+->
+ %% application POI of a local call and
+ %% type_application POI of a built-in type don't have name_range data
+ %% they are handled by the next clause
+ els_protocol:range(Range);
editable_range(#{kind := _Kind, range := Range}, _) ->
- els_protocol:range(Range).
-
+ els_protocol:range(Range).
-spec changes(uri(), poi(), binary()) -> #{uri() => [text_edit()]} | null.
changes(Uri, #{kind := module} = Mod, NewName) ->
- #{Uri => [#{range => editable_range(Mod), newText => NewName}]};
+ #{Uri => [#{range => editable_range(Mod), newText => NewName}]};
changes(Uri, #{kind := variable} = Var, NewName) ->
- POIs = els_code_navigation:find_in_scope(Uri, Var),
- #{Uri => [#{range => editable_range(P), newText => NewName} || P <- POIs]};
+ POIs = els_code_navigation:find_in_scope(Uri, Var),
+ #{Uri => [#{range => editable_range(P), newText => NewName} || P <- POIs]};
changes(Uri, #{kind := type_definition, id := {Name, A}}, NewName) ->
- ?LOG_INFO("Renaming type ~p/~p to ~s", [Name, A, NewName]),
- {ok, Doc} = els_utils:lookup_document(Uri),
- SelfChanges = [change(P, NewName) ||
- P <- els_dt_document:pois(Doc, [ type_definition
- , export_type_entry
- ]),
- maps:get(id, P) =:= {Name, A}
- ],
- Key = {els_uri:module(Uri), Name, A},
- {ok, Refs} = els_dt_references:find_by_id(type_application, Key),
- RefPOIs = convert_references_to_pois(Refs, [ type_application
- ]),
- Changes =
- lists:foldl(
- fun({RefUri, RefPOI}, Acc) ->
- Change = change(RefPOI, NewName),
- maps:update_with(RefUri, fun(V) -> [Change|V] end, [Change], Acc)
- end, #{Uri => SelfChanges}, RefPOIs),
- ?LOG_INFO("Done renaming type ~p/~p to ~s. ~p changes in ~p files.",
- [Name, A, NewName, length(lists:flatten(maps:values(Changes))),
- length(maps:keys(Changes))]),
- Changes;
+ ?LOG_INFO("Renaming type ~p/~p to ~s", [Name, A, NewName]),
+ {ok, Doc} = els_utils:lookup_document(Uri),
+ SelfChanges = [
+ change(P, NewName)
+ || P <- els_dt_document:pois(Doc, [
+ type_definition,
+ export_type_entry
+ ]),
+ maps:get(id, P) =:= {Name, A}
+ ],
+ Key = {els_uri:module(Uri), Name, A},
+ {ok, Refs} = els_dt_references:find_by_id(type_application, Key),
+ RefPOIs = convert_references_to_pois(Refs, [type_application]),
+ Changes =
+ lists:foldl(
+ fun({RefUri, RefPOI}, Acc) ->
+ Change = change(RefPOI, NewName),
+ maps:update_with(RefUri, fun(V) -> [Change | V] end, [Change], Acc)
+ end,
+ #{Uri => SelfChanges},
+ RefPOIs
+ ),
+ ?LOG_INFO(
+ "Done renaming type ~p/~p to ~s. ~p changes in ~p files.",
+ [
+ Name,
+ A,
+ NewName,
+ length(lists:flatten(maps:values(Changes))),
+ length(maps:keys(Changes))
+ ]
+ ),
+ Changes;
changes(Uri, #{kind := function, id := {F, A}}, NewName) ->
- ?LOG_INFO("Renaming function ~p/~p to ~s", [F, A, NewName]),
- {ok, Doc} = els_utils:lookup_document(Uri),
- IsMatch = fun (#{id := {Fun, Arity}}) -> {Fun, Arity} =:= {F, A};
- (#{id := {Fun, Arity, _}}) -> {Fun, Arity} =:= {F, A};
- (_) -> false
+ ?LOG_INFO("Renaming function ~p/~p to ~s", [F, A, NewName]),
+ {ok, Doc} = els_utils:lookup_document(Uri),
+ IsMatch = fun
+ (#{id := {Fun, Arity}}) -> {Fun, Arity} =:= {F, A};
+ (#{id := {Fun, Arity, _}}) -> {Fun, Arity} =:= {F, A};
+ (_) -> false
+ end,
+ SelfChanges = [
+ change(P, NewName)
+ || P <- els_dt_document:pois(Doc, [
+ export_entry,
+ function_clause,
+ spec
+ ]),
+ IsMatch(P)
+ ],
+ Key = {els_uri:module(Uri), F, A},
+ {ok, Refs} = els_dt_references:find_by_id(function, Key),
+ RefPOIs = convert_references_to_pois(Refs, [
+ application,
+ implicit_fun,
+ import_entry
+ ]),
+ Changes =
+ lists:foldl(
+ fun({RefUri, RefPOI}, Acc) ->
+ ImportChanges = import_changes(RefUri, RefPOI, NewName),
+ Changes = [change(RefPOI, NewName)] ++ ImportChanges,
+ maps:update_with(RefUri, fun(V) -> Changes ++ V end, Changes, Acc)
end,
- SelfChanges = [change(P, NewName) ||
- P <- els_dt_document:pois(Doc, [ export_entry
- , function_clause
- , spec
- ]),
- IsMatch(P)
- ],
- Key = {els_uri:module(Uri), F, A},
- {ok, Refs} = els_dt_references:find_by_id(function, Key),
- RefPOIs = convert_references_to_pois(Refs, [ application
- , implicit_fun
- , import_entry
- ]),
- Changes =
+ #{Uri => SelfChanges},
+ RefPOIs
+ ),
+ ?LOG_INFO(
+ "Done renaming function ~p/~p to ~s. ~p changes in ~p files.",
+ [
+ F,
+ A,
+ NewName,
+ length(lists:flatten(maps:values(Changes))),
+ length(maps:keys(Changes))
+ ]
+ ),
+ Changes;
+changes(Uri, #{kind := DefKind} = DefPoi, NewName) when
+ DefKind =:= define;
+ DefKind =:= record;
+ DefKind =:= record_def_field
+->
+ Self = #{range => editable_range(DefPoi), newText => NewName},
+ Refs = els_references_provider:find_scoped_references_for_def(Uri, DefPoi),
lists:foldl(
- fun({RefUri, RefPOI}, Acc) ->
- ImportChanges = import_changes(RefUri, RefPOI, NewName),
- Changes = [change(RefPOI, NewName)] ++ ImportChanges,
- maps:update_with(RefUri, fun(V) -> Changes ++ V end, Changes, Acc)
- end, #{Uri => SelfChanges}, RefPOIs),
- ?LOG_INFO("Done renaming function ~p/~p to ~s. ~p changes in ~p files.",
- [F, A, NewName, length(lists:flatten(maps:values(Changes))),
- length(maps:keys(Changes))]),
- Changes;
-changes(Uri, #{kind := DefKind} = DefPoi, NewName)
- when DefKind =:= define;
- DefKind =:= record;
- DefKind =:= record_def_field ->
- Self = #{range => editable_range(DefPoi), newText => NewName},
- Refs = els_references_provider:find_scoped_references_for_def(Uri, DefPoi),
- lists:foldl(
- fun({U, Poi}, Acc) ->
- Change = #{ range => editable_range(Poi)
- , newText => new_name(Poi, NewName)
- },
- maps:update_with(U, fun(V) -> [Change|V] end, [Change], Acc)
- end, #{Uri => [Self]}, Refs);
+ fun({U, Poi}, Acc) ->
+ Change = #{
+ range => editable_range(Poi),
+ newText => new_name(Poi, NewName)
+ },
+ maps:update_with(U, fun(V) -> [Change | V] end, [Change], Acc)
+ end,
+ #{Uri => [Self]},
+ Refs
+ );
changes(_Uri, _POI, _NewName) ->
- null.
+ null.
-spec new_name(poi(), binary()) -> binary().
new_name(#{kind := macro}, NewName) ->
- <<"?", NewName/binary>>;
+ <<"?", NewName/binary>>;
new_name(#{kind := record_expr}, NewName) ->
- <<"#", NewName/binary>>;
+ <<"#", NewName/binary>>;
new_name(_, NewName) ->
- NewName.
+ NewName.
-spec convert_references_to_pois([els_dt_references:item()], [poi_kind()]) ->
- [{uri(), poi()}].
+ [{uri(), poi()}].
convert_references_to_pois(Refs, Kinds) ->
- UriPOIs = lists:foldl(fun(#{uri := Uri}, Acc) when is_map_key(Uri, Acc) ->
- Acc;
- (#{uri := Uri}, Acc) ->
- POIs = case els_utils:lookup_document(Uri) of
- {ok, Doc} ->
- els_dt_document:pois(Doc, Kinds);
- {error, _} ->
- []
- end,
- maps:put(Uri, POIs, Acc)
- end, #{}, Refs),
- lists:map(fun(#{uri := Uri, range := #{from := Pos}}) ->
- POIs = maps:get(Uri, UriPOIs),
- {Uri, hd(els_poi:match_pos(POIs, Pos))}
- end, Refs).
+ UriPOIs = lists:foldl(
+ fun
+ (#{uri := Uri}, Acc) when is_map_key(Uri, Acc) ->
+ Acc;
+ (#{uri := Uri}, Acc) ->
+ POIs =
+ case els_utils:lookup_document(Uri) of
+ {ok, Doc} ->
+ els_dt_document:pois(Doc, Kinds);
+ {error, _} ->
+ []
+ end,
+ maps:put(Uri, POIs, Acc)
+ end,
+ #{},
+ Refs
+ ),
+ lists:map(
+ fun(#{uri := Uri, range := #{from := Pos}}) ->
+ POIs = maps:get(Uri, UriPOIs),
+ {Uri, hd(els_poi:match_pos(POIs, Pos))}
+ end,
+ Refs
+ ).
%% @doc Find all uses of imported function in Uri
-spec import_changes(uri(), poi(), binary()) -> [text_edit()].
import_changes(Uri, #{kind := import_entry, id := {_M, F, A}}, NewName) ->
- case els_utils:lookup_document(Uri) of
- {ok, Doc} ->
- [change(P, NewName) || P <- els_dt_document:pois(Doc, [ application
- , implicit_fun
- ]),
- maps:get(id, P) =:= {F, A}];
- {error, _} ->
- []
- end;
+ case els_utils:lookup_document(Uri) of
+ {ok, Doc} ->
+ [
+ change(P, NewName)
+ || P <- els_dt_document:pois(Doc, [
+ application,
+ implicit_fun
+ ]),
+ maps:get(id, P) =:= {F, A}
+ ];
+ {error, _} ->
+ []
+ end;
import_changes(_Uri, _POI, _NewName) ->
- [].
+ [].
-spec change(poi(), binary()) -> text_edit().
change(POI, NewName) ->
- #{range => editable_range(POI), newText => NewName}.
+ #{range => editable_range(POI), newText => NewName}.
diff --git a/apps/els_lsp/src/els_scope.erl b/apps/els_lsp/src/els_scope.erl
index ab63e748b..66c1ae35a 100644
--- a/apps/els_lsp/src/els_scope.erl
+++ b/apps/els_lsp/src/els_scope.erl
@@ -1,154 +1,167 @@
%%% @doc Library module to calculate various scoping rules
-module(els_scope).
--export([ local_and_included_pois/2
- , local_and_includer_pois/2
- , variable_scope_range/2
- ]).
+-export([
+ local_and_included_pois/2,
+ local_and_includer_pois/2,
+ variable_scope_range/2
+]).
-include("els_lsp.hrl").
%% @doc Return POIs of the provided `Kinds' in the document and included files
--spec local_and_included_pois(els_dt_document:item(), poi_kind() | [poi_kind()])
- -> [poi()].
+-spec local_and_included_pois(els_dt_document:item(), poi_kind() | [poi_kind()]) ->
+ [poi()].
local_and_included_pois(Document, Kind) when is_atom(Kind) ->
- local_and_included_pois(Document, [Kind]);
+ local_and_included_pois(Document, [Kind]);
local_and_included_pois(Document, Kinds) ->
- lists:flatten([ els_dt_document:pois(Document, Kinds)
- , included_pois(Document, Kinds)
- ]).
+ lists:flatten([
+ els_dt_document:pois(Document, Kinds),
+ included_pois(Document, Kinds)
+ ]).
%% @doc Return POIs of the provided `Kinds' in included files from `Document'
-spec included_pois(els_dt_document:item(), [poi_kind()]) -> [poi()].
included_pois(Document, Kinds) ->
- els_diagnostics_utils:traverse_include_graph(
- fun(IncludedDocument, _Includer, Acc) ->
- els_dt_document:pois(IncludedDocument, Kinds) ++ Acc
- end,
- [],
- Document).
+ els_diagnostics_utils:traverse_include_graph(
+ fun(IncludedDocument, _Includer, Acc) ->
+ els_dt_document:pois(IncludedDocument, Kinds) ++ Acc
+ end,
+ [],
+ Document
+ ).
%% @doc Return POIs of the provided `Kinds' in the local document and files that
%% (maybe recursively) include it
-spec local_and_includer_pois(uri(), [poi_kind()]) ->
- [{uri(), [poi()]}].
+ [{uri(), [poi()]}].
local_and_includer_pois(LocalUri, Kinds) ->
- [{Uri, find_pois_by_uri(Uri, Kinds)}
- || Uri <- local_and_includers(LocalUri)].
+ [
+ {Uri, find_pois_by_uri(Uri, Kinds)}
+ || Uri <- local_and_includers(LocalUri)
+ ].
-spec find_pois_by_uri(uri(), [poi_kind()]) -> [poi()].
find_pois_by_uri(Uri, Kinds) ->
- {ok, Document} = els_utils:lookup_document(Uri),
- els_dt_document:pois(Document, Kinds).
+ {ok, Document} = els_utils:lookup_document(Uri),
+ els_dt_document:pois(Document, Kinds).
-spec local_and_includers(uri()) -> [uri()].
local_and_includers(Uri) ->
- find_includers_loop(Uri, [Uri]).
+ find_includers_loop(Uri, [Uri]).
-spec find_includers_loop(uri(), ordsets:ordset(uri())) ->
- ordsets:ordset(uri()).
+ ordsets:ordset(uri()).
find_includers_loop(Uri, Acc0) ->
- Includers = find_includers(Uri),
- case ordsets:subtract(Includers, Acc0) of
- [] ->
- %% no new uris
- Acc0;
- New ->
- Acc1 = ordsets:union(New, Acc0),
- lists:foldl(fun find_includers_loop/2, Acc1, New)
- end.
+ Includers = find_includers(Uri),
+ case ordsets:subtract(Includers, Acc0) of
+ [] ->
+ %% no new uris
+ Acc0;
+ New ->
+ Acc1 = ordsets:union(New, Acc0),
+ lists:foldl(fun find_includers_loop/2, Acc1, New)
+ end.
-spec find_includers(uri()) -> [uri()].
find_includers(Uri) ->
- IncludeId = els_utils:include_id(els_uri:path(Uri)),
- IncludeLibId = els_utils:include_lib_id(els_uri:path(Uri)),
- lists:usort(find_includers(include, IncludeId) ++
- find_includers(include_lib, IncludeLibId)).
+ IncludeId = els_utils:include_id(els_uri:path(Uri)),
+ IncludeLibId = els_utils:include_lib_id(els_uri:path(Uri)),
+ lists:usort(
+ find_includers(include, IncludeId) ++
+ find_includers(include_lib, IncludeLibId)
+ ).
-spec find_includers(poi_kind(), string()) -> [uri()].
find_includers(Kind, Id) ->
- {ok, Items} = els_dt_references:find_by_id(Kind, Id),
- [Uri || #{uri := Uri} <- Items].
+ {ok, Items} = els_dt_references:find_by_id(Kind, Id),
+ [Uri || #{uri := Uri} <- Items].
%% @doc Find the rough scope of a variable, this is based on heuristics and
%% won't always be correct.
%% `VarRange' is expected to be the range of the variable.
-spec variable_scope_range(poi_range(), els_dt_document:item()) -> poi_range().
variable_scope_range(VarRange, Document) ->
- Attributes = [spec, callback, define, record, type_definition],
- AttrPOIs = els_dt_document:pois(Document, Attributes),
- case pois_match(AttrPOIs, VarRange) of
- [#{range := Range}] ->
- %% Inside attribute, simple.
- Range;
- [] ->
- %% If variable is not inside an attribute we need to figure out where the
- %% scope of the variable begins and ends.
- %% The scope of variables inside functions are limited by function clauses
- %% The scope of variables outside of function are limited by top-level
- %% POIs (attributes and functions) before and after.
- FunPOIs = els_poi:sort(els_dt_document:pois(Document, [function])),
- POIs = els_poi:sort(els_dt_document:pois(Document, [ function_clause
- | Attributes
- ])),
- CurrentFunRange = case pois_match(FunPOIs, VarRange) of
- [] -> undefined;
- [POI] -> range(POI)
- end,
- IsInsideFunction = CurrentFunRange /= undefined,
- BeforeFunRanges = [range(POI) || POI <- pois_before(FunPOIs, VarRange)],
- %% Find where scope should begin
- From =
- case [R || #{range := R} <- pois_before(POIs, VarRange)] of
- [] ->
- %% No POIs before
- {0, 0};
- [BeforeRange|_] when IsInsideFunction ->
- %% Inside function, use beginning of closest function clause
- maps:get(from, BeforeRange);
- [BeforeRange|_] when BeforeFunRanges == [] ->
- %% No function before, use end of closest POI
- maps:get(to, BeforeRange);
- [BeforeRange|_] ->
- %% Use end of closest POI, including functions.
- max(maps:get(to, hd(BeforeFunRanges)),
- maps:get(to, BeforeRange))
- end,
- %% Find when scope should end
- To =
- case [R || #{range := R} <- pois_after(POIs, VarRange)] of
- [] when IsInsideFunction ->
- %% No POIs after, use end of function
- maps:get(to, CurrentFunRange);
- [] ->
- %% No POIs after, use end of document
- {999999999, 999999999};
- [AfterRange|_] when IsInsideFunction ->
- %% Inside function, use closest of end of function *OR*
- %% beginning of the next function clause
- min(maps:get(to, CurrentFunRange), maps:get(from, AfterRange));
- [AfterRange|_] ->
- %% Use beginning of next POI
- maps:get(from, AfterRange)
- end,
- #{from => From, to => To}
- end.
+ Attributes = [spec, callback, define, record, type_definition],
+ AttrPOIs = els_dt_document:pois(Document, Attributes),
+ case pois_match(AttrPOIs, VarRange) of
+ [#{range := Range}] ->
+ %% Inside attribute, simple.
+ Range;
+ [] ->
+ %% If variable is not inside an attribute we need to figure out where the
+ %% scope of the variable begins and ends.
+ %% The scope of variables inside functions are limited by function clauses
+ %% The scope of variables outside of function are limited by top-level
+ %% POIs (attributes and functions) before and after.
+ FunPOIs = els_poi:sort(els_dt_document:pois(Document, [function])),
+ POIs = els_poi:sort(
+ els_dt_document:pois(Document, [
+ function_clause
+ | Attributes
+ ])
+ ),
+ CurrentFunRange =
+ case pois_match(FunPOIs, VarRange) of
+ [] -> undefined;
+ [POI] -> range(POI)
+ end,
+ IsInsideFunction = CurrentFunRange /= undefined,
+ BeforeFunRanges = [range(POI) || POI <- pois_before(FunPOIs, VarRange)],
+ %% Find where scope should begin
+ From =
+ case [R || #{range := R} <- pois_before(POIs, VarRange)] of
+ [] ->
+ %% No POIs before
+ {0, 0};
+ [BeforeRange | _] when IsInsideFunction ->
+ %% Inside function, use beginning of closest function clause
+ maps:get(from, BeforeRange);
+ [BeforeRange | _] when BeforeFunRanges == [] ->
+ %% No function before, use end of closest POI
+ maps:get(to, BeforeRange);
+ [BeforeRange | _] ->
+ %% Use end of closest POI, including functions.
+ max(
+ maps:get(to, hd(BeforeFunRanges)),
+ maps:get(to, BeforeRange)
+ )
+ end,
+ %% Find when scope should end
+ To =
+ case [R || #{range := R} <- pois_after(POIs, VarRange)] of
+ [] when IsInsideFunction ->
+ %% No POIs after, use end of function
+ maps:get(to, CurrentFunRange);
+ [] ->
+ %% No POIs after, use end of document
+ {999999999, 999999999};
+ [AfterRange | _] when IsInsideFunction ->
+ %% Inside function, use closest of end of function *OR*
+ %% beginning of the next function clause
+ min(maps:get(to, CurrentFunRange), maps:get(from, AfterRange));
+ [AfterRange | _] ->
+ %% Use beginning of next POI
+ maps:get(from, AfterRange)
+ end,
+ #{from => From, to => To}
+ end.
-spec pois_before([poi()], poi_range()) -> [poi()].
pois_before(POIs, VarRange) ->
- %% Reverse since we are typically interested in the last POI
- lists:reverse([POI || POI <- POIs, els_range:compare(range(POI), VarRange)]).
+ %% Reverse since we are typically interested in the last POI
+ lists:reverse([POI || POI <- POIs, els_range:compare(range(POI), VarRange)]).
-spec pois_after([poi()], poi_range()) -> [poi()].
pois_after(POIs, VarRange) ->
- [POI || POI <- POIs, els_range:compare(VarRange, range(POI))].
+ [POI || POI <- POIs, els_range:compare(VarRange, range(POI))].
-spec pois_match([poi()], poi_range()) -> [poi()].
pois_match(POIs, Range) ->
- [POI || POI <- POIs, els_range:in(Range, range(POI))].
+ [POI || POI <- POIs, els_range:in(Range, range(POI))].
-spec range(poi()) -> poi_range().
range(#{kind := function, data := #{wrapping_range := Range}}) ->
- Range;
+ Range;
range(#{range := Range}) ->
- Range.
+ Range.
diff --git a/apps/els_lsp/src/els_server.erl b/apps/els_lsp/src/els_server.erl
index 56dac063a..7a06ba438 100644
--- a/apps/els_lsp/src/els_server.erl
+++ b/apps/els_lsp/src/els_server.erl
@@ -12,26 +12,26 @@
%% Exports
%%==============================================================================
--export([ start_link/0
- ]).
+-export([start_link/0]).
%% gen_server callbacks
--export([ init/1
- , handle_call/3
- , handle_cast/2
- ]).
+-export([
+ init/1,
+ handle_call/3,
+ handle_cast/2
+]).
%% API
--export([ process_requests/1
- , set_io_device/1
- , send_notification/2
- , send_request/2
- , send_response/2
- ]).
+-export([
+ process_requests/1,
+ set_io_device/1,
+ send_notification/2,
+ send_request/2,
+ send_response/2
+]).
%% Testing
--export([ reset_internal_state/0
- ]).
+-export([reset_internal_state/0]).
%%==============================================================================
%% Includes
@@ -46,11 +46,12 @@
%%==============================================================================
%% Record Definitions
%%==============================================================================
--record(state, { io_device :: any()
- , request_id :: number()
- , internal_state :: map()
- , pending :: [{number(), pid()}]
- }).
+-record(state, {
+ io_device :: any(),
+ request_id :: number(),
+ internal_state :: map(),
+ pending :: [{number(), pid()}]
+}).
%%==============================================================================
%% Type Definitions
@@ -62,181 +63,201 @@
%%==============================================================================
-spec start_link() -> {ok, pid()}.
start_link() ->
- {ok, Pid} = gen_server:start_link({local, ?SERVER}, ?MODULE, [], []),
- Cb = fun(Requests) ->
- gen_server:cast(Pid, {process_requests, Requests})
- end,
- {ok, _} = els_stdio:start_listener(Cb),
- {ok, Pid}.
+ {ok, Pid} = gen_server:start_link({local, ?SERVER}, ?MODULE, [], []),
+ Cb = fun(Requests) ->
+ gen_server:cast(Pid, {process_requests, Requests})
+ end,
+ {ok, _} = els_stdio:start_listener(Cb),
+ {ok, Pid}.
-spec process_requests([any()]) -> ok.
process_requests(Requests) ->
- gen_server:cast(?SERVER, {process_requests, Requests}).
+ gen_server:cast(?SERVER, {process_requests, Requests}).
-spec set_io_device(atom() | pid()) -> ok.
set_io_device(IoDevice) ->
- gen_server:call(?SERVER, {set_io_device, IoDevice}).
+ gen_server:call(?SERVER, {set_io_device, IoDevice}).
-spec send_notification(binary(), map()) -> ok.
send_notification(Method, Params) ->
- gen_server:cast(?SERVER, {notification, Method, Params}).
+ gen_server:cast(?SERVER, {notification, Method, Params}).
-spec send_request(binary(), map()) -> ok.
send_request(Method, Params) ->
- gen_server:cast(?SERVER, {request, Method, Params}).
+ gen_server:cast(?SERVER, {request, Method, Params}).
-spec send_response(pid(), any()) -> ok.
send_response(Job, Result) ->
- gen_server:cast(?SERVER, {response, Job, Result}).
+ gen_server:cast(?SERVER, {response, Job, Result}).
%%==============================================================================
%% Testing
%%==============================================================================
-spec reset_internal_state() -> ok.
reset_internal_state() ->
- gen_server:call(?MODULE, {reset_internal_state}).
+ gen_server:call(?MODULE, {reset_internal_state}).
%%==============================================================================
%% gen_server callbacks
%%==============================================================================
-spec init([]) -> {ok, state()}.
init([]) ->
- ?LOG_INFO("Starting els_server..."),
- State = #state{ request_id = 0
- , internal_state = #{open_buffers => sets:new()}
- , pending = []
- },
- {ok, State}.
+ ?LOG_INFO("Starting els_server..."),
+ State = #state{
+ request_id = 0,
+ internal_state = #{open_buffers => sets:new()},
+ pending = []
+ },
+ {ok, State}.
-spec handle_call(any(), any(), state()) -> {reply, any(), state()}.
handle_call({set_io_device, IoDevice}, _From, State) ->
- {reply, ok, State#state{io_device = IoDevice}};
+ {reply, ok, State#state{io_device = IoDevice}};
handle_call({reset_internal_state}, _From, State) ->
- {reply, ok, State#state{internal_state = #{}}}.
+ {reply, ok, State#state{internal_state = #{}}}.
-spec handle_cast(any(), state()) -> {noreply, state()}.
handle_cast({process_requests, Requests}, State0) ->
- State = lists:foldl(fun handle_request/2, State0, Requests),
- {noreply, State};
+ State = lists:foldl(fun handle_request/2, State0, Requests),
+ {noreply, State};
handle_cast({notification, Method, Params}, State) ->
- do_send_notification(Method, Params, State),
- {noreply, State};
+ do_send_notification(Method, Params, State),
+ {noreply, State};
handle_cast({request, Method, Params}, State0) ->
- State = do_send_request(Method, Params, State0),
- {noreply, State};
+ State = do_send_request(Method, Params, State0),
+ {noreply, State};
handle_cast({response, Job, Result}, State0) ->
- State = do_send_response(Job, Result, State0),
- {noreply, State};
+ State = do_send_response(Job, Result, State0),
+ {noreply, State};
handle_cast(_, State) ->
- {noreply, State}.
+ {noreply, State}.
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec handle_request(map(), state()) -> state().
-handle_request(#{ <<"method">> := <<"$/cancelRequest">>
- , <<"params">> := Params
- }, State0) ->
- #{<<"id">> := Id} = Params,
- #state{pending = Pending} = State0,
- case lists:keyfind(Id, 1, Pending) of
- false ->
- ?LOG_DEBUG("Trying to cancel not existing request [params=~p]",
- [Params]),
- State0;
- {RequestId, Job} when RequestId =:= Id ->
- ?LOG_DEBUG("[SERVER] Cancelling request [id=~p] [job=~p]", [Id, Job]),
- els_provider:cancel_request(Job),
- State0#state{pending = lists:keydelete(Id, 1, Pending)}
- end;
-handle_request(#{ <<"method">> := _ReqMethod } = Request
- , #state{ internal_state = InternalState
- , pending = Pending
- } = State0) ->
- Method = maps:get(<<"method">>, Request),
- Params = maps:get(<<"params">>, Request, #{}),
- Type = case maps:is_key(<<"id">>, Request) of
- true -> request;
- false -> notification
- end,
- case els_methods:dispatch(Method, Params, Type, InternalState) of
- {response, Result, NewInternalState} ->
- RequestId = maps:get(<<"id">>, Request),
- Response = els_protocol:response(RequestId, Result),
- ?LOG_DEBUG("[SERVER] Sending response [response=~s]", [Response]),
- send(Response, State0),
- State0#state{internal_state = NewInternalState};
- {error, Error, NewInternalState} ->
- RequestId = maps:get(<<"id">>, Request, null),
- ErrorResponse = els_protocol:error(RequestId, Error),
- ?LOG_DEBUG( "[SERVER] Sending error response [response=~s]"
- , [ErrorResponse]
- ),
- send(ErrorResponse, State0),
- State0#state{internal_state = NewInternalState};
- {noresponse, NewInternalState} ->
- ?LOG_DEBUG("[SERVER] No response", []),
- State0#state{internal_state = NewInternalState};
- {noresponse, BackgroundJob, NewInternalState} ->
- RequestId = maps:get(<<"id">>, Request),
- ?LOG_DEBUG("[SERVER] Suspending response [background_job=~p]",
- [BackgroundJob]),
- NewPending = [{RequestId, BackgroundJob}| Pending],
- State0#state{ internal_state = NewInternalState
- , pending = NewPending
- };
- {notification, M, P, NewInternalState} ->
- do_send_notification(M, P, State0),
- State0#state{internal_state = NewInternalState}
- end;
-handle_request(Response, State0) ->
- ?LOG_DEBUG( "[SERVER] got request response [response=~p]"
- , [Response]
+handle_request(
+ #{
+ <<"method">> := <<"$/cancelRequest">>,
+ <<"params">> := Params
+ },
+ State0
+) ->
+ #{<<"id">> := Id} = Params,
+ #state{pending = Pending} = State0,
+ case lists:keyfind(Id, 1, Pending) of
+ false ->
+ ?LOG_DEBUG(
+ "Trying to cancel not existing request [params=~p]",
+ [Params]
+ ),
+ State0;
+ {RequestId, Job} when RequestId =:= Id ->
+ ?LOG_DEBUG("[SERVER] Cancelling request [id=~p] [job=~p]", [Id, Job]),
+ els_provider:cancel_request(Job),
+ State0#state{pending = lists:keydelete(Id, 1, Pending)}
+ end;
+handle_request(
+ #{<<"method">> := _ReqMethod} = Request,
+ #state{
+ internal_state = InternalState,
+ pending = Pending
+ } = State0
+) ->
+ Method = maps:get(<<"method">>, Request),
+ Params = maps:get(<<"params">>, Request, #{}),
+ Type =
+ case maps:is_key(<<"id">>, Request) of
+ true -> request;
+ false -> notification
+ end,
+ case els_methods:dispatch(Method, Params, Type, InternalState) of
+ {response, Result, NewInternalState} ->
+ RequestId = maps:get(<<"id">>, Request),
+ Response = els_protocol:response(RequestId, Result),
+ ?LOG_DEBUG("[SERVER] Sending response [response=~s]", [Response]),
+ send(Response, State0),
+ State0#state{internal_state = NewInternalState};
+ {error, Error, NewInternalState} ->
+ RequestId = maps:get(<<"id">>, Request, null),
+ ErrorResponse = els_protocol:error(RequestId, Error),
+ ?LOG_DEBUG(
+ "[SERVER] Sending error response [response=~s]",
+ [ErrorResponse]
),
- State0.
+ send(ErrorResponse, State0),
+ State0#state{internal_state = NewInternalState};
+ {noresponse, NewInternalState} ->
+ ?LOG_DEBUG("[SERVER] No response", []),
+ State0#state{internal_state = NewInternalState};
+ {noresponse, BackgroundJob, NewInternalState} ->
+ RequestId = maps:get(<<"id">>, Request),
+ ?LOG_DEBUG(
+ "[SERVER] Suspending response [background_job=~p]",
+ [BackgroundJob]
+ ),
+ NewPending = [{RequestId, BackgroundJob} | Pending],
+ State0#state{
+ internal_state = NewInternalState,
+ pending = NewPending
+ };
+ {notification, M, P, NewInternalState} ->
+ do_send_notification(M, P, State0),
+ State0#state{internal_state = NewInternalState}
+ end;
+handle_request(Response, State0) ->
+ ?LOG_DEBUG(
+ "[SERVER] got request response [response=~p]",
+ [Response]
+ ),
+ State0.
-spec do_send_notification(binary(), map(), state()) -> ok.
%% This notification is specifically filtered out to avoid recursive
%% calling of log notifications (see issue #1050)
do_send_notification(<<"window/logMessage">> = Method, Params, State) ->
- Notification = els_protocol:notification(Method, Params),
- send(Notification, State);
+ Notification = els_protocol:notification(Method, Params),
+ send(Notification, State);
do_send_notification(Method, Params, State) ->
- Notification = els_protocol:notification(Method, Params),
- ?LOG_DEBUG( "[SERVER] Sending notification [notification=~s]"
- , [Notification]
- ),
- send(Notification, State).
+ Notification = els_protocol:notification(Method, Params),
+ ?LOG_DEBUG(
+ "[SERVER] Sending notification [notification=~s]",
+ [Notification]
+ ),
+ send(Notification, State).
-spec do_send_request(binary(), map(), state()) -> state().
do_send_request(Method, Params, #state{request_id = RequestId0} = State0) ->
- RequestId = RequestId0 + 1,
- Request = els_protocol:request(RequestId, Method, Params),
- ?LOG_DEBUG( "[SERVER] Sending request [request=~p]"
- , [Request]
- ),
- send(Request, State0),
- State0#state{request_id = RequestId}.
+ RequestId = RequestId0 + 1,
+ Request = els_protocol:request(RequestId, Method, Params),
+ ?LOG_DEBUG(
+ "[SERVER] Sending request [request=~p]",
+ [Request]
+ ),
+ send(Request, State0),
+ State0#state{request_id = RequestId}.
-spec do_send_response(pid(), any(), state()) -> state().
do_send_response(Job, Result, State0) ->
- #state{pending = Pending0} = State0,
- case lists:keyfind(Job, 2, Pending0) of
- false ->
- ?LOG_DEBUG(
- "[SERVER] Sending delayed response, but no request found [job=~p]",
- [Job]),
- State0;
- {RequestId, J} when J =:= Job ->
- Response = els_protocol:response(RequestId, Result),
- ?LOG_DEBUG( "[SERVER] Sending delayed response [job=~p] [response=~p]"
- , [Job, Response]
- ),
- send(Response, State0),
- Pending = lists:keydelete(RequestId, 1, Pending0),
- State0#state{pending = Pending}
- end.
+ #state{pending = Pending0} = State0,
+ case lists:keyfind(Job, 2, Pending0) of
+ false ->
+ ?LOG_DEBUG(
+ "[SERVER] Sending delayed response, but no request found [job=~p]",
+ [Job]
+ ),
+ State0;
+ {RequestId, J} when J =:= Job ->
+ Response = els_protocol:response(RequestId, Result),
+ ?LOG_DEBUG(
+ "[SERVER] Sending delayed response [job=~p] [response=~p]",
+ [Job, Response]
+ ),
+ send(Response, State0),
+ Pending = lists:keydelete(RequestId, 1, Pending0),
+ State0#state{pending = Pending}
+ end.
-spec send(binary(), state()) -> ok.
send(Payload, #state{io_device = IoDevice}) ->
- els_stdio:send(IoDevice, Payload).
+ els_stdio:send(IoDevice, Payload).
diff --git a/apps/els_lsp/src/els_snippets_server.erl b/apps/els_lsp/src/els_snippets_server.erl
index d8637fd67..06c74928f 100644
--- a/apps/els_lsp/src/els_snippets_server.erl
+++ b/apps/els_lsp/src/els_snippets_server.erl
@@ -7,21 +7,23 @@
%%==============================================================================
%% API
%%==============================================================================
--export([ builtin_snippets_dir/0
- , custom_snippets_dir/0
- , snippets/0
- , start_link/0
- ]).
+-export([
+ builtin_snippets_dir/0,
+ custom_snippets_dir/0,
+ snippets/0,
+ start_link/0
+]).
%%==============================================================================
%% Callbacks for the gen_server behaviour
%%==============================================================================
-behaviour(gen_server).
--export([ init/1
- , handle_call/3
- , handle_cast/2
- , handle_info/2
- ]).
+-export([
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2
+]).
-type state() :: #{}.
%%==============================================================================
@@ -45,107 +47,109 @@
%%==============================================================================
-spec start_link() -> {ok, pid()}.
start_link() ->
- gen_server:start_link({local, ?SERVER}, ?MODULE, unused, []).
+ gen_server:start_link({local, ?SERVER}, ?MODULE, unused, []).
-spec snippets() -> [completion_item()].
snippets() ->
- [build_snippet(Entry) || Entry <- ets:tab2list(?TABLE)].
+ [build_snippet(Entry) || Entry <- ets:tab2list(?TABLE)].
-spec builtin_snippets_dir() -> file:filename_all().
builtin_snippets_dir() ->
- filename:join(code:priv_dir(?APP), "snippets").
+ filename:join(code:priv_dir(?APP), "snippets").
-spec custom_snippets_dir() -> file:filename_all().
custom_snippets_dir() ->
- {ok, [[Home]]} = init:get_argument(home),
- filename:join([Home, ".config", "erlang_ls", "snippets"]).
+ {ok, [[Home]]} = init:get_argument(home),
+ filename:join([Home, ".config", "erlang_ls", "snippets"]).
%%==============================================================================
%% Callbacks for the gen_server behaviour
%%==============================================================================
-spec init(unused) -> {ok, state()}.
init(unused) ->
- load_snippets(),
- {ok, #{}}.
+ load_snippets(),
+ {ok, #{}}.
-spec handle_call(any(), {pid(), any()}, state()) -> {reply, any(), state()}.
handle_call(_Request, _From, State) ->
- {reply, not_implemented, State}.
+ {reply, not_implemented, State}.
-spec handle_cast(any(), state()) -> {noreply, state()}.
handle_cast(_Request, State) ->
- {noreply, State}.
+ {noreply, State}.
-spec handle_info(any(), state()) -> {noreply, state()}.
handle_info(_Request, State) ->
- {noreply, State}.
+ {noreply, State}.
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec load_snippets() -> ok.
load_snippets() ->
- init_snippets_table(),
- [insert_snippet(S) || S <- builtin_snippets() ++ custom_snippets()],
- ok.
+ init_snippets_table(),
+ [insert_snippet(S) || S <- builtin_snippets() ++ custom_snippets()],
+ ok.
-spec init_snippets_table() -> ok.
init_snippets_table() ->
- ?TABLE = ets:new(?TABLE, [public, named_table, {read_concurrency, true}]),
- ok.
+ ?TABLE = ets:new(?TABLE, [public, named_table, {read_concurrency, true}]),
+ ok.
-spec insert_snippet(snippet()) -> ok.
insert_snippet({Name, Content}) ->
- true = ets:insert(?TABLE, {unicode:characters_to_binary(Name), Content}),
- ok.
+ true = ets:insert(?TABLE, {unicode:characters_to_binary(Name), Content}),
+ ok.
-spec builtin_snippets() -> [snippet()].
builtin_snippets() ->
- case filelib:is_dir(code:priv_dir(?APP)) of
- false ->
- %% Probably running within an escript, so we need to extract the
- %% snippets from the archive
- snippets_from_escript();
- true ->
- snippets_from_dir(builtin_snippets_dir())
- end.
+ case filelib:is_dir(code:priv_dir(?APP)) of
+ false ->
+ %% Probably running within an escript, so we need to extract the
+ %% snippets from the archive
+ snippets_from_escript();
+ true ->
+ snippets_from_dir(builtin_snippets_dir())
+ end.
-spec snippets_from_escript() -> [snippet()].
snippets_from_escript() ->
- Name = escript:script_name(),
- {ok, Sections} = escript:extract(Name, []),
- Archive = proplists:get_value(archive, Sections),
- Fun = fun("els_lsp/priv/snippets/" ++ N, _GetInfo, GetBin, Acc) ->
- [{N, GetBin()}|Acc];
- (_Name, _GetInfo, _GetBin, Acc) ->
+ Name = escript:script_name(),
+ {ok, Sections} = escript:extract(Name, []),
+ Archive = proplists:get_value(archive, Sections),
+ Fun = fun
+ ("els_lsp/priv/snippets/" ++ N, _GetInfo, GetBin, Acc) ->
+ [{N, GetBin()} | Acc];
+ (_Name, _GetInfo, _GetBin, Acc) ->
Acc
- end,
- {ok, Snippets} = zip:foldl(Fun, [], {Name, Archive}),
- Snippets.
+ end,
+ {ok, Snippets} = zip:foldl(Fun, [], {Name, Archive}),
+ Snippets.
-spec custom_snippets() -> [snippet()].
custom_snippets() ->
- Dir = custom_snippets_dir(),
- ensure_dir(Dir),
- snippets_from_dir(Dir).
+ Dir = custom_snippets_dir(),
+ ensure_dir(Dir),
+ snippets_from_dir(Dir).
-spec snippets_from_dir(file:filename_all()) -> [snippet()].
snippets_from_dir(Dir) ->
- [snippet_from_file(Dir, Filename) || Filename <- filelib:wildcard("*", Dir)].
+ [snippet_from_file(Dir, Filename) || Filename <- filelib:wildcard("*", Dir)].
-spec snippet_from_file(file:filename_all(), file:filename_all()) -> snippet().
snippet_from_file(Dir, Filename) ->
- {ok, Content} = file:read_file(filename:join(Dir, Filename)),
- {Filename, Content}.
+ {ok, Content} = file:read_file(filename:join(Dir, Filename)),
+ {Filename, Content}.
-spec ensure_dir(file:filename_all()) -> ok.
ensure_dir(Dir) ->
- ok = filelib:ensure_dir(filename:join(Dir, "dummy")).
+ ok = filelib:ensure_dir(filename:join(Dir, "dummy")).
-spec build_snippet({binary(), binary()}) -> completion_item().
build_snippet({Name, Snippet}) ->
- #{ label => <<"snippet_", Name/binary>>
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , insertText => Snippet
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- }.
+ #{
+ label => <<"snippet_", Name/binary>>,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ insertText => Snippet,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
+ }.
diff --git a/apps/els_lsp/src/els_sup.erl b/apps/els_lsp/src/els_sup.erl
index 7dde6399d..567eae62d 100644
--- a/apps/els_lsp/src/els_sup.erl
+++ b/apps/els_lsp/src/els_sup.erl
@@ -13,10 +13,10 @@
%%==============================================================================
%% API
--export([ start_link/0 ]).
+-export([start_link/0]).
%% Supervisor Callbacks
--export([ init/1 ]).
+-export([init/1]).
%%==============================================================================
%% Includes
@@ -33,47 +33,56 @@
%%==============================================================================
-spec start_link() -> {ok, pid()}.
start_link() ->
- supervisor:start_link({local, ?SERVER}, ?MODULE, []).
+ supervisor:start_link({local, ?SERVER}, ?MODULE, []).
%%==============================================================================
%% supervisors callbacks
%%==============================================================================
-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init([]) ->
- SupFlags = #{ strategy => rest_for_one
- , intensity => 5
- , period => 60
- },
- {ok, Vsn} = application:get_key(vsn),
- ?LOG_INFO("Starting session (version ~p)", [Vsn]),
- restrict_stdio_access(),
- ChildSpecs = [ #{ id => els_config
- , start => {els_config, start_link, []}
- , shutdown => brutal_kill
- }
- , #{ id => els_db_server
- , start => {els_db_server, start_link, []}
- , shutdown => brutal_kill
- }
- , #{ id => els_background_job_sup
- , start => {els_background_job_sup, start_link, []}
- , type => supervisor
- }
- , #{ id => els_distribution_sup
- , start => {els_distribution_sup, start_link, []}
- , type => supervisor
- }
- , #{ id => els_snippets_server
- , start => {els_snippets_server, start_link, []}
- }
- , #{ id => els_server
- , start => {els_server, start_link, []}
- }
- , #{ id => els_provider
- , start => {els_provider, start_link, []}
- }
- ],
- {ok, {SupFlags, ChildSpecs}}.
+ SupFlags = #{
+ strategy => rest_for_one,
+ intensity => 5,
+ period => 60
+ },
+ {ok, Vsn} = application:get_key(vsn),
+ ?LOG_INFO("Starting session (version ~p)", [Vsn]),
+ restrict_stdio_access(),
+ ChildSpecs = [
+ #{
+ id => els_config,
+ start => {els_config, start_link, []},
+ shutdown => brutal_kill
+ },
+ #{
+ id => els_db_server,
+ start => {els_db_server, start_link, []},
+ shutdown => brutal_kill
+ },
+ #{
+ id => els_background_job_sup,
+ start => {els_background_job_sup, start_link, []},
+ type => supervisor
+ },
+ #{
+ id => els_distribution_sup,
+ start => {els_distribution_sup, start_link, []},
+ type => supervisor
+ },
+ #{
+ id => els_snippets_server,
+ start => {els_snippets_server, start_link, []}
+ },
+ #{
+ id => els_server,
+ start => {els_server, start_link, []}
+ },
+ #{
+ id => els_provider,
+ start => {els_provider, start_link, []}
+ }
+ ],
+ {ok, {SupFlags, ChildSpecs}}.
%% @doc Restrict access to standard I/O
%%
@@ -88,33 +97,34 @@ init([]) ->
%% which can print warnings to standard output.
-spec restrict_stdio_access() -> ok.
restrict_stdio_access() ->
- ?LOG_INFO("Use group leader as io_device"),
- case application:get_env(els_core, io_device, standard_io) of
- standard_io ->
- application:set_env(els_core, io_device, erlang:group_leader());
- _ -> ok
- end,
+ ?LOG_INFO("Use group leader as io_device"),
+ case application:get_env(els_core, io_device, standard_io) of
+ standard_io ->
+ application:set_env(els_core, io_device, erlang:group_leader());
+ _ ->
+ ok
+ end,
- ?LOG_INFO("Replace group leader to avoid unwanted output to stdout"),
- Pid = erlang:spawn(fun noop_group_leader/0),
- erlang:group_leader(Pid, self()),
- ok.
+ ?LOG_INFO("Replace group leader to avoid unwanted output to stdout"),
+ Pid = erlang:spawn(fun noop_group_leader/0),
+ erlang:group_leader(Pid, self()),
+ ok.
%% @doc Simulate a group leader but do nothing
-spec noop_group_leader() -> no_return().
noop_group_leader() ->
- receive
- Message ->
- ?LOG_INFO("noop_group_leader got [message=~p]", [Message]),
- case Message of
- {io_request, From, ReplyAs, getopts} ->
- %% We need to pass the underlying io opts, otherwise shell_docs does
- %% not know which encoding to use. See #754
- From ! {io_reply, ReplyAs, io:getopts()};
- {io_request, From, ReplyAs, _} ->
- From ! {io_reply, ReplyAs, ok};
- _ ->
- ok
- end,
- noop_group_leader()
- end.
+ receive
+ Message ->
+ ?LOG_INFO("noop_group_leader got [message=~p]", [Message]),
+ case Message of
+ {io_request, From, ReplyAs, getopts} ->
+ %% We need to pass the underlying io opts, otherwise shell_docs does
+ %% not know which encoding to use. See #754
+ From ! {io_reply, ReplyAs, io:getopts()};
+ {io_request, From, ReplyAs, _} ->
+ From ! {io_reply, ReplyAs, ok};
+ _ ->
+ ok
+ end,
+ noop_group_leader()
+ end.
diff --git a/apps/els_lsp/src/els_text_document_position_params.erl b/apps/els_lsp/src/els_text_document_position_params.erl
index 288f661e0..321f77b33 100644
--- a/apps/els_lsp/src/els_text_document_position_params.erl
+++ b/apps/els_lsp/src/els_text_document_position_params.erl
@@ -1,10 +1,11 @@
-module(els_text_document_position_params).
--export([ uri/1
- , line/1
- , character/1
- , uri_line_character/1
- ]).
+-export([
+ uri/1,
+ line/1,
+ character/1,
+ uri_line_character/1
+]).
-include("els_lsp.hrl").
@@ -13,17 +14,17 @@
-spec uri(params()) -> uri().
uri(Params) ->
- maps:get(<<"uri">>, maps:get(<<"textDocument">>, Params)).
+ maps:get(<<"uri">>, maps:get(<<"textDocument">>, Params)).
-spec line(params()) -> non_neg_integer().
line(Params) ->
- maps:get(<<"line">>, maps:get(<<"position">>, Params)).
+ maps:get(<<"line">>, maps:get(<<"position">>, Params)).
-spec character(params()) -> non_neg_integer().
character(Params) ->
- maps:get(<<"line">>, maps:get(<<"position">>, Params)).
+ maps:get(<<"line">>, maps:get(<<"position">>, Params)).
-spec uri_line_character(params()) ->
- {uri(), non_neg_integer(), non_neg_integer()}.
+ {uri(), non_neg_integer(), non_neg_integer()}.
uri_line_character(Params) ->
- {uri(Params), line(Params), character(Params)}.
+ {uri(Params), line(Params), character(Params)}.
diff --git a/apps/els_lsp/src/els_text_edit.erl b/apps/els_lsp/src/els_text_edit.erl
index 8a6536b26..195dade63 100644
--- a/apps/els_lsp/src/els_text_edit.erl
+++ b/apps/els_lsp/src/els_text_edit.erl
@@ -1,9 +1,10 @@
-module(els_text_edit).
--export([ diff_files/2
- , edit_insert_text/3
- , edit_replace_text/4
- ]).
+-export([
+ diff_files/2,
+ edit_insert_text/3,
+ edit_replace_text/4
+]).
-include("els_lsp.hrl").
@@ -41,49 +42,51 @@ make_text_edits(Diffs) ->
make_text_edits(Diffs, 0, []).
-spec make_text_edits([diff()], number(), [text_edit()]) -> [text_edit()].
-make_text_edits([{eq, Data}|T], Line, Acc) ->
+make_text_edits([{eq, Data} | T], Line, Acc) ->
make_text_edits(T, Line + length(Data), Acc);
-
-make_text_edits([{del, Del}, {ins, Ins}|T], Line, Acc) ->
+make_text_edits([{del, Del}, {ins, Ins} | T], Line, Acc) ->
Len = length(Del),
- Pos1 = #{ line => Line, character => 0 },
- Pos2 = #{ line => Line + Len, character => 0 },
- Edit = #{ range => #{ start => Pos1, 'end' => Pos2 }
- , newText => els_utils:to_binary(lists:concat(Ins))
- },
- make_text_edits(T, Line + Len, [Edit|Acc]);
-
-make_text_edits([{ins, Data}|T], Line, Acc) ->
- Pos = #{ line => Line, character => 0 },
- Edit = #{ range => #{ start => Pos, 'end' => Pos }
- , newText => els_utils:to_binary(lists:concat(Data))
- },
- make_text_edits(T, Line, [Edit|Acc]);
-
-make_text_edits([{del, Data}|T], Line, Acc) ->
+ Pos1 = #{line => Line, character => 0},
+ Pos2 = #{line => Line + Len, character => 0},
+ Edit = #{
+ range => #{start => Pos1, 'end' => Pos2},
+ newText => els_utils:to_binary(lists:concat(Ins))
+ },
+ make_text_edits(T, Line + Len, [Edit | Acc]);
+make_text_edits([{ins, Data} | T], Line, Acc) ->
+ Pos = #{line => Line, character => 0},
+ Edit = #{
+ range => #{start => Pos, 'end' => Pos},
+ newText => els_utils:to_binary(lists:concat(Data))
+ },
+ make_text_edits(T, Line, [Edit | Acc]);
+make_text_edits([{del, Data} | T], Line, Acc) ->
Len = length(Data),
- Pos1 = #{ line => Line, character => 0 },
- Pos2 = #{ line => Line + Len, character => 0 },
- Edit = #{ range => #{ start => Pos1, 'end' => Pos2 }
- , newText => <<"">>
- },
- make_text_edits(T, Line + Len, [Edit|Acc]);
-
-make_text_edits([], _Line, Acc) -> lists:reverse(Acc).
+ Pos1 = #{line => Line, character => 0},
+ Pos2 = #{line => Line + Len, character => 0},
+ Edit = #{
+ range => #{start => Pos1, 'end' => Pos2},
+ newText => <<"">>
+ },
+ make_text_edits(T, Line + Len, [Edit | Acc]);
+make_text_edits([], _Line, Acc) ->
+ lists:reverse(Acc).
-spec edit_insert_text(uri(), binary(), number()) -> map().
edit_insert_text(Uri, Data, Line) ->
- Pos = #{ line => Line, character => 0 },
- Edit = #{ range => #{ start => Pos, 'end' => Pos }
- , newText => els_utils:to_binary(Data)
- },
- #{ changes => #{ Uri => [Edit] }}.
+ Pos = #{line => Line, character => 0},
+ Edit = #{
+ range => #{start => Pos, 'end' => Pos},
+ newText => els_utils:to_binary(Data)
+ },
+ #{changes => #{Uri => [Edit]}}.
-spec edit_replace_text(uri(), binary(), number(), number()) -> map().
edit_replace_text(Uri, Data, LineFrom, LineTo) ->
- Pos1 = #{ line => LineFrom, character => 0 },
- Pos2 = #{ line => LineTo, character => 0 },
- Edit = #{ range => #{ start => Pos1, 'end' => Pos2 }
- , newText => els_utils:to_binary(Data)
- },
- #{ changes => #{ Uri => [Edit] }}.
+ Pos1 = #{line => LineFrom, character => 0},
+ Pos2 = #{line => LineTo, character => 0},
+ Edit = #{
+ range => #{start => Pos1, 'end' => Pos2},
+ newText => els_utils:to_binary(Data)
+ },
+ #{changes => #{Uri => [Edit]}}.
diff --git a/apps/els_lsp/src/els_text_search.erl b/apps/els_lsp/src/els_text_search.erl
index c55bdd37f..2e5b0a886 100644
--- a/apps/els_lsp/src/els_text_search.erl
+++ b/apps/els_lsp/src/els_text_search.erl
@@ -6,7 +6,7 @@
%%==============================================================================
%% API
%%==============================================================================
--export([ find_candidate_uris/1 ]).
+-export([find_candidate_uris/1]).
%%==============================================================================
%% Includes
@@ -18,29 +18,29 @@
%%==============================================================================
-spec find_candidate_uris({els_dt_references:poi_category(), any()}) -> [uri()].
find_candidate_uris(Id) ->
- Pattern = extract_pattern(Id),
- els_dt_document:find_candidates(Pattern).
+ Pattern = extract_pattern(Id),
+ els_dt_document:find_candidates(Pattern).
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec extract_pattern({els_dt_references:poi_category(), any()}) ->
- atom() | binary().
+ atom() | binary().
extract_pattern({function, {_M, F, _A}}) ->
- F;
+ F;
extract_pattern({type, {_M, F, _A}}) ->
- F;
+ F;
extract_pattern({macro, {Name, _Arity}}) ->
- Name;
+ Name;
extract_pattern({macro, Name}) ->
- Name;
+ Name;
extract_pattern({include, Id}) ->
- include_id(Id);
+ include_id(Id);
extract_pattern({include_lib, Id}) ->
- include_id(Id);
+ include_id(Id);
extract_pattern({behaviour, Name}) ->
- Name.
+ Name.
-spec include_id(string()) -> string().
include_id(Id) ->
- filename:rootname(filename:basename(Id)).
+ filename:rootname(filename:basename(Id)).
diff --git a/apps/els_lsp/src/els_text_synchronization.erl b/apps/els_lsp/src/els_text_synchronization.erl
index 7e2db6b9c..f9d3e23a3 100644
--- a/apps/els_lsp/src/els_text_synchronization.erl
+++ b/apps/els_lsp/src/els_text_synchronization.erl
@@ -3,102 +3,116 @@
-include_lib("kernel/include/logger.hrl").
-include("els_lsp.hrl").
--export([ sync_mode/0
- , did_change/1
- , did_open/1
- , did_save/1
- , did_close/1
- , did_change_watched_files/1
- ]).
+-export([
+ sync_mode/0,
+ did_change/1,
+ did_open/1,
+ did_save/1,
+ did_close/1,
+ did_change_watched_files/1
+]).
-spec sync_mode() -> text_document_sync_kind().
sync_mode() ->
- case els_config:get(incremental_sync) of
- true -> ?TEXT_DOCUMENT_SYNC_KIND_INCREMENTAL;
- false -> ?TEXT_DOCUMENT_SYNC_KIND_FULL
- end.
+ case els_config:get(incremental_sync) of
+ true -> ?TEXT_DOCUMENT_SYNC_KIND_INCREMENTAL;
+ false -> ?TEXT_DOCUMENT_SYNC_KIND_FULL
+ end.
-spec did_change(map()) -> ok | {ok, pid()}.
did_change(Params) ->
- ContentChanges = maps:get(<<"contentChanges">>, Params),
- TextDocument = maps:get(<<"textDocument">> , Params),
- Uri = maps:get(<<"uri">> , TextDocument),
- Version = maps:get(<<"version">> , TextDocument),
- case ContentChanges of
- [] ->
- ok;
- [Change] when not is_map_key(<<"range">>, Change) ->
- #{<<"text">> := Text} = Change,
- {ok, Document} = els_utils:lookup_document(Uri),
- NewDocument = Document#{text => Text, version => Version},
- els_dt_document:insert(NewDocument),
- background_index(NewDocument);
- _ ->
- ?LOG_DEBUG("didChange INCREMENTAL [changes: ~p]", [ContentChanges]),
- Edits = [to_edit(Change) || Change <- ContentChanges],
- {ok, #{text := Text0} = Document} = els_utils:lookup_document(Uri),
- Text = els_text:apply_edits(Text0, Edits),
- NewDocument = Document#{text => Text, version => Version},
- els_dt_document:insert(NewDocument),
- background_index(NewDocument)
- end.
+ ContentChanges = maps:get(<<"contentChanges">>, Params),
+ TextDocument = maps:get(<<"textDocument">>, Params),
+ Uri = maps:get(<<"uri">>, TextDocument),
+ Version = maps:get(<<"version">>, TextDocument),
+ case ContentChanges of
+ [] ->
+ ok;
+ [Change] when not is_map_key(<<"range">>, Change) ->
+ #{<<"text">> := Text} = Change,
+ {ok, Document} = els_utils:lookup_document(Uri),
+ NewDocument = Document#{text => Text, version => Version},
+ els_dt_document:insert(NewDocument),
+ background_index(NewDocument);
+ _ ->
+ ?LOG_DEBUG("didChange INCREMENTAL [changes: ~p]", [ContentChanges]),
+ Edits = [to_edit(Change) || Change <- ContentChanges],
+ {ok, #{text := Text0} = Document} = els_utils:lookup_document(Uri),
+ Text = els_text:apply_edits(Text0, Edits),
+ NewDocument = Document#{text => Text, version => Version},
+ els_dt_document:insert(NewDocument),
+ background_index(NewDocument)
+ end.
-spec did_open(map()) -> ok.
did_open(Params) ->
- #{<<"textDocument">> := #{ <<"uri">> := Uri
- , <<"text">> := Text
- , <<"version">> := Version
- }} = Params,
- {ok, Document} = els_utils:lookup_document(Uri),
- NewDocument = Document#{text => Text, version => Version},
- els_dt_document:insert(NewDocument),
- els_indexing:deep_index(NewDocument),
- ok.
+ #{
+ <<"textDocument">> := #{
+ <<"uri">> := Uri,
+ <<"text">> := Text,
+ <<"version">> := Version
+ }
+ } = Params,
+ {ok, Document} = els_utils:lookup_document(Uri),
+ NewDocument = Document#{text => Text, version => Version},
+ els_dt_document:insert(NewDocument),
+ els_indexing:deep_index(NewDocument),
+ ok.
-spec did_save(map()) -> ok.
did_save(_Params) ->
- ok.
+ ok.
-spec did_change_watched_files(map()) -> ok.
did_change_watched_files(Params) ->
- #{<<"changes">> := Changes} = Params,
- [handle_file_change(Uri, Type)
- || #{<<"uri">> := Uri, <<"type">> := Type} <- Changes],
- ok.
+ #{<<"changes">> := Changes} = Params,
+ [
+ handle_file_change(Uri, Type)
+ || #{<<"uri">> := Uri, <<"type">> := Type} <- Changes
+ ],
+ ok.
-spec did_close(map()) -> ok.
did_close(_Params) -> ok.
-spec to_edit(map()) -> els_text:edit().
to_edit(#{<<"text">> := Text, <<"range">> := Range}) ->
- #{ <<"start">> := #{<<"character">> := FromC, <<"line">> := FromL}
- , <<"end">> := #{<<"character">> := ToC, <<"line">> := ToL}
- } = Range,
- {#{ from => {FromL, FromC}
- , to => {ToL, ToC}
- }, els_utils:to_list(Text)}.
+ #{
+ <<"start">> := #{<<"character">> := FromC, <<"line">> := FromL},
+ <<"end">> := #{<<"character">> := ToC, <<"line">> := ToL}
+ } = Range,
+ {
+ #{
+ from => {FromL, FromC},
+ to => {ToL, ToC}
+ },
+ els_utils:to_list(Text)
+ }.
-spec handle_file_change(uri(), file_change_type()) -> ok.
-handle_file_change(Uri, Type) when Type =:= ?FILE_CHANGE_TYPE_CREATED;
- Type =:= ?FILE_CHANGE_TYPE_CHANGED ->
- reload_from_disk(Uri);
+handle_file_change(Uri, Type) when
+ Type =:= ?FILE_CHANGE_TYPE_CREATED;
+ Type =:= ?FILE_CHANGE_TYPE_CHANGED
+->
+ reload_from_disk(Uri);
handle_file_change(Uri, Type) when Type =:= ?FILE_CHANGE_TYPE_DELETED ->
- els_indexing:remove(Uri).
+ els_indexing:remove(Uri).
-spec reload_from_disk(uri()) -> ok.
reload_from_disk(Uri) ->
- {ok, Text} = file:read_file(els_uri:path(Uri)),
- {ok, Document} = els_utils:lookup_document(Uri),
- els_indexing:deep_index(Document#{text => Text}),
- ok.
+ {ok, Text} = file:read_file(els_uri:path(Uri)),
+ {ok, Document} = els_utils:lookup_document(Uri),
+ els_indexing:deep_index(Document#{text => Text}),
+ ok.
-spec background_index(els_dt_document:item()) -> {ok, pid()}.
background_index(#{uri := Uri} = Document) ->
- Config = #{ task => fun (Doc, _State) ->
- els_indexing:deep_index(Doc),
- ok
- end
- , entries => [Document]
- , title => <<"Indexing ", Uri/binary>>
- },
- els_background_job:new(Config).
+ Config = #{
+ task => fun(Doc, _State) ->
+ els_indexing:deep_index(Doc),
+ ok
+ end,
+ entries => [Document],
+ title => <<"Indexing ", Uri/binary>>
+ },
+ els_background_job:new(Config).
diff --git a/apps/els_lsp/src/els_text_synchronization_provider.erl b/apps/els_lsp/src/els_text_synchronization_provider.erl
index 3a75b8b94..913d5cdbd 100644
--- a/apps/els_lsp/src/els_text_synchronization_provider.erl
+++ b/apps/els_lsp/src/els_text_synchronization_provider.erl
@@ -1,9 +1,10 @@
-module(els_text_synchronization_provider).
-behaviour(els_provider).
--export([ handle_request/2
- , options/0
- ]).
+-export([
+ handle_request/2,
+ options/0
+]).
-include("els_lsp.hrl").
@@ -12,34 +13,35 @@
%%==============================================================================
-spec options() -> map().
options() ->
- #{ openClose => true
- , change => els_text_synchronization:sync_mode()
- , save => #{includeText => false}
- }.
+ #{
+ openClose => true,
+ change => els_text_synchronization:sync_mode(),
+ save => #{includeText => false}
+ }.
-spec handle_request(any(), any()) ->
- {diagnostics, uri(), [pid()]} |
- noresponse |
- {async, uri(), pid()}.
+ {diagnostics, uri(), [pid()]}
+ | noresponse
+ | {async, uri(), pid()}.
handle_request({did_open, Params}, _State) ->
- ok = els_text_synchronization:did_open(Params),
- #{<<"textDocument">> := #{ <<"uri">> := Uri}} = Params,
- {diagnostics, Uri, els_diagnostics:run_diagnostics(Uri)};
+ ok = els_text_synchronization:did_open(Params),
+ #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
+ {diagnostics, Uri, els_diagnostics:run_diagnostics(Uri)};
handle_request({did_change, Params}, _State) ->
- #{<<"textDocument">> := #{ <<"uri">> := Uri}} = Params,
- case els_text_synchronization:did_change(Params) of
- ok ->
- noresponse;
- {ok, Job} ->
- {async, Uri, Job}
- end;
+ #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
+ case els_text_synchronization:did_change(Params) of
+ ok ->
+ noresponse;
+ {ok, Job} ->
+ {async, Uri, Job}
+ end;
handle_request({did_save, Params}, _State) ->
- ok = els_text_synchronization:did_save(Params),
- #{<<"textDocument">> := #{ <<"uri">> := Uri}} = Params,
- {diagnostics, Uri, els_diagnostics:run_diagnostics(Uri)};
+ ok = els_text_synchronization:did_save(Params),
+ #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
+ {diagnostics, Uri, els_diagnostics:run_diagnostics(Uri)};
handle_request({did_close, Params}, _State) ->
- ok = els_text_synchronization:did_close(Params),
- noresponse;
+ ok = els_text_synchronization:did_close(Params),
+ noresponse;
handle_request({did_change_watched_files, Params}, _State) ->
- ok = els_text_synchronization:did_change_watched_files(Params),
- noresponse.
+ ok = els_text_synchronization:did_change_watched_files(Params),
+ noresponse.
diff --git a/apps/els_lsp/src/els_typer.erl b/apps/els_lsp/src/els_typer.erl
index de269a821..792f52fab 100644
--- a/apps/els_lsp/src/els_typer.erl
+++ b/apps/els_lsp/src/els_typer.erl
@@ -30,362 +30,405 @@
-module(els_typer).
--export([ get_info/1, get_type_spec/3 ]).
+-export([get_info/1, get_type_spec/3]).
-include("els_lsp.hrl").
--type files() :: [file:filename()].
--type callgraph() :: dialyzer_callgraph:callgraph().
+-type files() :: [file:filename()].
+-type callgraph() :: dialyzer_callgraph:callgraph().
-type codeserver() :: dialyzer_codeserver:codeserver().
--type plt() :: dialyzer_plt:plt().
-
--record(analysis,
- {mode = 'show',
- macros = [] :: [{atom(), term()}],
- includes = [] :: files(),
- codeserver = dialyzer_codeserver:new():: codeserver(),
- callgraph = dialyzer_callgraph:new() :: callgraph(),
- files = [] :: files(), % absolute names
- plt = none :: 'none' | file:filename(),
- show_succ = false :: boolean(),
- %% Files in 'fms' are compilable with option 'to_pp'; we keep them
- %% as {FileName, ModuleName} in case the ModuleName is different
- fms = [] :: [{file:filename(), module()}],
- ex_func = map__new() :: map_dict(),
- record = map__new() :: map_dict(),
- func = map__new() :: map_dict(),
- inc_func = map__new() :: map_dict(),
- trust_plt = dialyzer_plt:new() :: plt()}).
+-type plt() :: dialyzer_plt:plt().
+
+-record(analysis, {
+ mode = 'show',
+ macros = [] :: [{atom(), term()}],
+ includes = [] :: files(),
+ codeserver = dialyzer_codeserver:new() :: codeserver(),
+ callgraph = dialyzer_callgraph:new() :: callgraph(),
+ % absolute names
+ files = [] :: files(),
+ plt = none :: 'none' | file:filename(),
+ show_succ = false :: boolean(),
+ %% Files in 'fms' are compilable with option 'to_pp'; we keep them
+ %% as {FileName, ModuleName} in case the ModuleName is different
+ fms = [] :: [{file:filename(), module()}],
+ ex_func = map__new() :: map_dict(),
+ record = map__new() :: map_dict(),
+ func = map__new() :: map_dict(),
+ inc_func = map__new() :: map_dict(),
+ trust_plt = dialyzer_plt:new() :: plt()
+}).
-type analysis() :: #analysis{}.
--record(info, {records = maps:new() :: erl_types:type_table(),
- functions = [] :: [func_info()],
- types = map__new() :: map_dict()}).
+-record(info, {
+ records = maps:new() :: erl_types:type_table(),
+ functions = [] :: [func_info()],
+ types = map__new() :: map_dict()
+}).
-type info() :: #info{}.
--export_type([ info/0 ]).
+-export_type([info/0]).
-spec get_info(uri()) -> #info{}.
get_info(Uri) ->
- Path = binary_to_list(els_uri:path(Uri)),
- Macros = els_compiler_diagnostics:macro_options(),
- Includes = els_compiler_diagnostics:include_options(),
- Analysis = #analysis{ macros = Macros
- , includes = Includes
- },
- TrustedFiles = [],
- Analysis2 = extract(Analysis, TrustedFiles),
- Analysis3 = Analysis2#analysis{files = [Path]},
- Analysis4 = collect_info(Analysis3),
- Analysis5 = get_type_info(Analysis4),
- [{File, Module}] = Analysis5#analysis.fms,
- get_final_info(File, Module, Analysis5).
+ Path = binary_to_list(els_uri:path(Uri)),
+ Macros = els_compiler_diagnostics:macro_options(),
+ Includes = els_compiler_diagnostics:include_options(),
+ Analysis = #analysis{
+ macros = Macros,
+ includes = Includes
+ },
+ TrustedFiles = [],
+ Analysis2 = extract(Analysis, TrustedFiles),
+ Analysis3 = Analysis2#analysis{files = [Path]},
+ Analysis4 = collect_info(Analysis3),
+ Analysis5 = get_type_info(Analysis4),
+ [{File, Module}] = Analysis5#analysis.fms,
+ get_final_info(File, Module, Analysis5).
%%--------------------------------------------------------------------
-spec extract(analysis(), files()) -> analysis().
-extract(#analysis{macros = Macros,
- includes = Includes,
- trust_plt = TrustPLT} = Analysis, TrustedFiles) ->
- CodeServer = dialyzer_codeserver:new(),
- Fun =
- fun(File, CS) ->
- %% We include one more dir; the one above the one we are trusting
- %% E.g, for /home/tests/typer_ann/test.ann.erl, we should include
- %% /home/tests/ rather than /home/tests/typer_ann/
- CompOpts = dialyzer_utils:src_compiler_opts() ++ Includes ++ Macros,
- {ok, Core} = dialyzer_utils:get_core_from_src(File, CompOpts),
- {ok, RecDict} = dialyzer_utils:get_record_and_type_info(Core),
- Mod = list_to_atom(filename:basename(File, ".erl")),
- {ok, SpecDict, CbDict} = dialyzer_utils:get_spec_info(Mod, Core, RecDict),
- CS1 = dialyzer_codeserver:store_temp_records(Mod, RecDict, CS),
- dialyzer_codeserver:store_temp_contracts(Mod, SpecDict, CbDict, CS1)
+extract(
+ #analysis{
+ macros = Macros,
+ includes = Includes,
+ trust_plt = TrustPLT
+ } = Analysis,
+ TrustedFiles
+) ->
+ CodeServer = dialyzer_codeserver:new(),
+ Fun =
+ fun(File, CS) ->
+ %% We include one more dir; the one above the one we are trusting
+ %% E.g, for /home/tests/typer_ann/test.ann.erl, we should include
+ %% /home/tests/ rather than /home/tests/typer_ann/
+ CompOpts = dialyzer_utils:src_compiler_opts() ++ Includes ++ Macros,
+ {ok, Core} = dialyzer_utils:get_core_from_src(File, CompOpts),
+ {ok, RecDict} = dialyzer_utils:get_record_and_type_info(Core),
+ Mod = list_to_atom(filename:basename(File, ".erl")),
+ {ok, SpecDict, CbDict} = dialyzer_utils:get_spec_info(Mod, Core, RecDict),
+ CS1 = dialyzer_codeserver:store_temp_records(Mod, RecDict, CS),
+ dialyzer_codeserver:store_temp_contracts(Mod, SpecDict, CbDict, CS1)
+ end,
+ CodeServer1 = lists:foldl(Fun, CodeServer, TrustedFiles),
+ CodeServer2 =
+ dialyzer_utils:merge_types(
+ CodeServer1,
+ % XXX change to the PLT?
+ TrustPLT
+ ),
+ NewExpTypes = dialyzer_codeserver:get_temp_exported_types(CodeServer1),
+ case sets:size(NewExpTypes) of
+ 0 -> ok
end,
- CodeServer1 = lists:foldl(Fun, CodeServer, TrustedFiles),
- CodeServer2 =
- dialyzer_utils:merge_types(CodeServer1,
- TrustPLT), % XXX change to the PLT?
- NewExpTypes = dialyzer_codeserver:get_temp_exported_types(CodeServer1),
- case sets:size(NewExpTypes) of 0 -> ok end,
- CodeServer3 = dialyzer_codeserver:finalize_exported_types(NewExpTypes, CodeServer2),
- CodeServer4 = dialyzer_utils:process_record_remote_types(CodeServer3),
- NewCodeServer = dialyzer_contracts:process_contract_remote_types(CodeServer4),
- ContractsDict = dialyzer_codeserver:get_contracts(NewCodeServer),
- Contracts = orddict:from_list(dict:to_list(ContractsDict)),
- NewTrustPLT = dialyzer_plt:insert_contract_list(TrustPLT, Contracts),
- Analysis#analysis{trust_plt = NewTrustPLT}.
+ CodeServer3 = dialyzer_codeserver:finalize_exported_types(NewExpTypes, CodeServer2),
+ CodeServer4 = dialyzer_utils:process_record_remote_types(CodeServer3),
+ NewCodeServer = dialyzer_contracts:process_contract_remote_types(CodeServer4),
+ ContractsDict = dialyzer_codeserver:get_contracts(NewCodeServer),
+ Contracts = orddict:from_list(dict:to_list(ContractsDict)),
+ NewTrustPLT = dialyzer_plt:insert_contract_list(TrustPLT, Contracts),
+ Analysis#analysis{trust_plt = NewTrustPLT}.
%%--------------------------------------------------------------------
-spec get_type_info(analysis()) -> analysis().
-get_type_info(#analysis{callgraph = CallGraph,
- trust_plt = TrustPLT,
- codeserver = CodeServer} = Analysis) ->
- StrippedCallGraph = remove_external(CallGraph, TrustPLT),
- NewPlt = dialyzer_succ_typings:analyze_callgraph(StrippedCallGraph,
- TrustPLT,
- CodeServer),
- Analysis#analysis{callgraph = StrippedCallGraph, trust_plt = NewPlt}.
+get_type_info(
+ #analysis{
+ callgraph = CallGraph,
+ trust_plt = TrustPLT,
+ codeserver = CodeServer
+ } = Analysis
+) ->
+ StrippedCallGraph = remove_external(CallGraph, TrustPLT),
+ NewPlt = dialyzer_succ_typings:analyze_callgraph(
+ StrippedCallGraph,
+ TrustPLT,
+ CodeServer
+ ),
+ Analysis#analysis{callgraph = StrippedCallGraph, trust_plt = NewPlt}.
-spec remove_external(callgraph(), plt()) -> callgraph().
remove_external(CallGraph, PLT) ->
- {StrippedCG0, Ext} = dialyzer_callgraph:remove_external(CallGraph),
- case get_external(Ext, PLT) of
- [] -> ok;
- _Externals ->
- rcv_ext_types()
- end,
- StrippedCG0.
+ {StrippedCG0, Ext} = dialyzer_callgraph:remove_external(CallGraph),
+ case get_external(Ext, PLT) of
+ [] -> ok;
+ _Externals -> rcv_ext_types()
+ end,
+ StrippedCG0.
-spec get_external([{mfa(), mfa()}], plt()) -> [mfa()].
get_external(Exts, Plt) ->
- Fun = fun ({_From, To = {M, F, A}}, Acc) ->
- case dialyzer_plt:contains_mfa(Plt, To) of
- false ->
+ Fun = fun({_From, To = {M, F, A}}, Acc) ->
+ case dialyzer_plt:contains_mfa(Plt, To) of
+ false ->
case erl_bif_types:is_known(M, F, A) of
- true -> Acc;
- false -> [To|Acc]
+ true -> Acc;
+ false -> [To | Acc]
end;
- true -> Acc
- end
- end,
- lists:foldl(Fun, [], Exts).
+ true ->
+ Acc
+ end
+ end,
+ lists:foldl(Fun, [], Exts).
%%--------------------------------------------------------------------
%% Showing type information or annotating files with such information.
%%--------------------------------------------------------------------
--type fa() :: {atom(), arity()}.
+-type fa() :: {atom(), arity()}.
-type func_info() :: {non_neg_integer(), atom(), arity()}.
-spec get_final_info(string(), atom(), #analysis{}) -> #info{}.
get_final_info(File, Module, Analysis) ->
- Records = get_records(File, Analysis),
- Types = get_types(Module, Analysis, Records),
- Functions = get_functions(File, Analysis),
- #info{records = Records, functions = Functions, types = Types}.
+ Records = get_records(File, Analysis),
+ Types = get_types(Module, Analysis, Records),
+ Functions = get_functions(File, Analysis),
+ #info{records = Records, functions = Functions, types = Types}.
-spec get_records(string(), #analysis{}) -> any().
get_records(File, Analysis) ->
- map__lookup(File, Analysis#analysis.record).
+ map__lookup(File, Analysis#analysis.record).
-spec get_types(atom(), #analysis{}, any()) -> dict:dict().
get_types(Module, Analysis, Records) ->
- TypeInfoPlt = Analysis#analysis.trust_plt,
- TypeInfo =
- case dialyzer_plt:lookup_module(TypeInfoPlt, Module) of
- none -> [];
- {value, List} -> List
- end,
- CodeServer = Analysis#analysis.codeserver,
- TypeInfoList =
- case Analysis#analysis.show_succ of
- true ->
- [convert_type_info(I) || I <- TypeInfo];
- false ->
- [get_type(I, CodeServer, Records) || I <- TypeInfo]
- end,
- map__from_list(TypeInfoList).
+ TypeInfoPlt = Analysis#analysis.trust_plt,
+ TypeInfo =
+ case dialyzer_plt:lookup_module(TypeInfoPlt, Module) of
+ none -> [];
+ {value, List} -> List
+ end,
+ CodeServer = Analysis#analysis.codeserver,
+ TypeInfoList =
+ case Analysis#analysis.show_succ of
+ true ->
+ [convert_type_info(I) || I <- TypeInfo];
+ false ->
+ [get_type(I, CodeServer, Records) || I <- TypeInfo]
+ end,
+ map__from_list(TypeInfoList).
-spec convert_type_info({mfa(), any(), any()}) -> {fa(), {any(), any()}}.
convert_type_info({{_M, F, A}, Range, Arg}) ->
- {{F, A}, {Range, Arg}}.
+ {{F, A}, {Range, Arg}}.
-spec get_type({mfa(), any(), any()}, any(), any()) -> {fa(), {any(), any()}}.
get_type({{_M, F, A} = MFA, Range, Arg}, CodeServer, _Records) ->
- case dialyzer_codeserver:lookup_mfa_contract(MFA, CodeServer) of
- error ->
- {{F, A}, {Range, Arg}};
- {ok, {_FileLine, Contract, _Xtra}} ->
- Sig = erl_types:t_fun(Arg, Range),
- case dialyzer_contracts:check_contract(Contract, Sig) of
- ok -> {{F, A}, {contract, Contract}};
- {range_warnings, _} ->
- {{F, A}, {contract, Contract}};
- {error, {overlapping_contract, []}} ->
- {{F, A}, {contract, Contract}}
- end
- end.
+ case dialyzer_codeserver:lookup_mfa_contract(MFA, CodeServer) of
+ error ->
+ {{F, A}, {Range, Arg}};
+ {ok, {_FileLine, Contract, _Xtra}} ->
+ Sig = erl_types:t_fun(Arg, Range),
+ case dialyzer_contracts:check_contract(Contract, Sig) of
+ ok -> {{F, A}, {contract, Contract}};
+ {range_warnings, _} -> {{F, A}, {contract, Contract}};
+ {error, {overlapping_contract, []}} -> {{F, A}, {contract, Contract}}
+ end
+ end.
-spec get_functions(string(), #analysis{}) -> [any()].
get_functions(File, Analysis) ->
- Funcs = map__lookup(File, Analysis#analysis.func),
- Inc_Funcs = map__lookup(File, Analysis#analysis.inc_func),
- remove_module_info(Funcs) ++ normalize_incFuncs(Inc_Funcs).
+ Funcs = map__lookup(File, Analysis#analysis.func),
+ Inc_Funcs = map__lookup(File, Analysis#analysis.inc_func),
+ remove_module_info(Funcs) ++ normalize_incFuncs(Inc_Funcs).
-spec normalize_incFuncs([any()]) -> [any()].
normalize_incFuncs(Functions) ->
- [FunInfo || {_FileName, FunInfo} <- Functions].
+ [FunInfo || {_FileName, FunInfo} <- Functions].
-spec remove_module_info([func_info()]) -> [func_info()].
remove_module_info(FunInfoList) ->
- F = fun ({_,module_info,0}) -> false;
- ({_,module_info,1}) -> false;
- ({Line,F,A}) when is_integer(Line), is_atom(F), is_integer(A) -> true
- end,
- lists:filter(F, FunInfoList).
+ F = fun
+ ({_, module_info, 0}) -> false;
+ ({_, module_info, 1}) -> false;
+ ({Line, F, A}) when is_integer(Line), is_atom(F), is_integer(A) -> true
+ end,
+ lists:filter(F, FunInfoList).
-spec get_type_spec(atom(), arity(), #info{}) -> string().
get_type_spec(F, A, Info) ->
- Type = get_type_info({F,A}, Info#info.types),
- TypeStr =
- case Type of
- {contract, C} ->
- dialyzer_contracts:contract_to_string(C);
- {RetType, ArgType} ->
- Sig = erl_types:t_fun(ArgType, RetType),
- dialyzer_utils:format_sig(Sig, Info#info.records)
- end,
- Prefix = lists:concat(["-spec ", erl_types:atom_to_string(F)]),
- lists:concat([Prefix, TypeStr, "."]).
+ Type = get_type_info({F, A}, Info#info.types),
+ TypeStr =
+ case Type of
+ {contract, C} ->
+ dialyzer_contracts:contract_to_string(C);
+ {RetType, ArgType} ->
+ Sig = erl_types:t_fun(ArgType, RetType),
+ dialyzer_utils:format_sig(Sig, Info#info.records)
+ end,
+ Prefix = lists:concat(["-spec ", erl_types:atom_to_string(F)]),
+ lists:concat([Prefix, TypeStr, "."]).
-spec get_type_info({any(), any()}, dict:dict()) -> {any(), any()}.
get_type_info(Func, Types) ->
- case map__lookup(Func, Types) of
- {contract, _Fun} = C -> C;
- {_RetType, _ArgType} = RA -> RA
- end.
+ case map__lookup(Func, Types) of
+ {contract, _Fun} = C -> C;
+ {_RetType, _ArgType} = RA -> RA
+ end.
-type inc_file_info() :: {file:filename(), func_info()}.
--record(tmpAcc, {file :: file:filename(),
- module :: atom(),
- funcAcc = [] :: [func_info()],
- incFuncAcc = [] :: [inc_file_info()],
- dialyzerObj = [] :: [{mfa(), {_, _}}]}).
+-record(tmpAcc, {
+ file :: file:filename(),
+ module :: atom(),
+ funcAcc = [] :: [func_info()],
+ incFuncAcc = [] :: [inc_file_info()],
+ dialyzerObj = [] :: [{mfa(), {_, _}}]
+}).
-spec collect_info(analysis()) -> analysis().
collect_info(Analysis) ->
- DialyzerPlt = get_dialyzer_plt(),
- NewPlt = dialyzer_plt:merge_plts([Analysis#analysis.trust_plt, DialyzerPlt]),
- NewAnalysis = lists:foldl(fun collect_one_file_info/2,
- Analysis#analysis{trust_plt = NewPlt},
- Analysis#analysis.files),
- TmpCServer = NewAnalysis#analysis.codeserver,
- TmpCServer1 = dialyzer_utils:merge_types(TmpCServer, NewPlt),
- NewExpTypes = dialyzer_codeserver:get_temp_exported_types(TmpCServer),
- OldExpTypes = dialyzer_plt:get_exported_types(NewPlt),
- MergedExpTypes = sets:union(NewExpTypes, OldExpTypes),
- TmpCServer2 =
- dialyzer_codeserver:finalize_exported_types(MergedExpTypes, TmpCServer1),
- TmpCServer3 = dialyzer_utils:process_record_remote_types(TmpCServer2),
- NewCServer = dialyzer_contracts:process_contract_remote_types(TmpCServer3),
- NewAnalysis#analysis{codeserver = NewCServer}.
+ DialyzerPlt = get_dialyzer_plt(),
+ NewPlt = dialyzer_plt:merge_plts([Analysis#analysis.trust_plt, DialyzerPlt]),
+ NewAnalysis = lists:foldl(
+ fun collect_one_file_info/2,
+ Analysis#analysis{trust_plt = NewPlt},
+ Analysis#analysis.files
+ ),
+ TmpCServer = NewAnalysis#analysis.codeserver,
+ TmpCServer1 = dialyzer_utils:merge_types(TmpCServer, NewPlt),
+ NewExpTypes = dialyzer_codeserver:get_temp_exported_types(TmpCServer),
+ OldExpTypes = dialyzer_plt:get_exported_types(NewPlt),
+ MergedExpTypes = sets:union(NewExpTypes, OldExpTypes),
+ TmpCServer2 =
+ dialyzer_codeserver:finalize_exported_types(MergedExpTypes, TmpCServer1),
+ TmpCServer3 = dialyzer_utils:process_record_remote_types(TmpCServer2),
+ NewCServer = dialyzer_contracts:process_contract_remote_types(TmpCServer3),
+ NewAnalysis#analysis{codeserver = NewCServer}.
-spec collect_one_file_info(string(), #analysis{}) -> #analysis{}.
collect_one_file_info(File, Analysis) ->
- Macros = Analysis#analysis.macros,
- Includes = Analysis#analysis.includes,
- Options = dialyzer_utils:src_compiler_opts() ++ Includes ++ Macros,
- {ok, Core} = dialyzer_utils:get_core_from_src(File, Options),
- {ok, Records} = dialyzer_utils:get_record_and_type_info(Core),
- Mod = cerl:concrete(cerl:module_name(Core)),
- {ok, SpecInfo, CbInfo} = dialyzer_utils:get_spec_info(Mod, Core, Records),
- ExpTypes = get_exported_types_from_core(Core),
- analyze_core_tree(Core, Records, SpecInfo, CbInfo,
- ExpTypes, Analysis, File).
+ Macros = Analysis#analysis.macros,
+ Includes = Analysis#analysis.includes,
+ Options = dialyzer_utils:src_compiler_opts() ++ Includes ++ Macros,
+ {ok, Core} = dialyzer_utils:get_core_from_src(File, Options),
+ {ok, Records} = dialyzer_utils:get_record_and_type_info(Core),
+ Mod = cerl:concrete(cerl:module_name(Core)),
+ {ok, SpecInfo, CbInfo} = dialyzer_utils:get_spec_info(Mod, Core, Records),
+ ExpTypes = get_exported_types_from_core(Core),
+ analyze_core_tree(
+ Core,
+ Records,
+ SpecInfo,
+ CbInfo,
+ ExpTypes,
+ Analysis,
+ File
+ ).
-spec analyze_core_tree(any(), any(), any(), any(), sets:set(_), #analysis{}, string()) ->
- #analysis{}.
+ #analysis{}.
analyze_core_tree(Core, Records, SpecInfo, CbInfo, ExpTypes, Analysis, File) ->
- Module = cerl:concrete(cerl:module_name(Core)),
- TmpTree = cerl:from_records(Core),
- CS1 = Analysis#analysis.codeserver,
- NextLabel = dialyzer_codeserver:get_next_core_label(CS1),
- {Tree, NewLabel} = cerl_trees:label(TmpTree, NextLabel),
- CS2 = dialyzer_codeserver:insert(Module, Tree, CS1),
- CS3 = dialyzer_codeserver:set_next_core_label(NewLabel, CS2),
- CS4 = dialyzer_codeserver:store_temp_records(Module, Records, CS3),
- CS5 = dialyzer_codeserver:store_temp_contracts(Module, SpecInfo, CbInfo, CS4),
- OldExpTypes = dialyzer_codeserver:get_temp_exported_types(CS5),
- MergedExpTypes = sets:union(ExpTypes, OldExpTypes),
- CS6 = dialyzer_codeserver:insert_temp_exported_types(MergedExpTypes, CS5),
- Ex_Funcs = [{0,F,A} || {_,_,{F,A}} <- cerl:module_exports(Tree)],
- CG = Analysis#analysis.callgraph,
- {V, E} = dialyzer_callgraph:scan_core_tree(Tree, CG),
- dialyzer_callgraph:add_edges(E, V, CG),
- Fun = fun analyze_one_function/2,
- All_Defs = cerl:module_defs(Tree),
- Acc = lists:foldl(Fun, #tmpAcc{file = File, module = Module}, All_Defs),
- Exported_FuncMap = map__insert({File, Ex_Funcs}, Analysis#analysis.ex_func),
- %% we must sort all functions in the file which
- %% originate from this file by *numerical order* of lineNo
- Sorted_Functions = lists:keysort(1, Acc#tmpAcc.funcAcc),
- FuncMap = map__insert({File, Sorted_Functions}, Analysis#analysis.func),
- %% we do not need to sort functions which are imported from included files
- IncFuncMap = map__insert({File, Acc#tmpAcc.incFuncAcc},
- Analysis#analysis.inc_func),
- FMs = Analysis#analysis.fms ++ [{File, Module}],
- RecordMap = map__insert({File, Records}, Analysis#analysis.record),
- Analysis#analysis{fms = FMs,
- callgraph = CG,
- codeserver = CS6,
- ex_func = Exported_FuncMap,
- inc_func = IncFuncMap,
- record = RecordMap,
- func = FuncMap}.
+ Module = cerl:concrete(cerl:module_name(Core)),
+ TmpTree = cerl:from_records(Core),
+ CS1 = Analysis#analysis.codeserver,
+ NextLabel = dialyzer_codeserver:get_next_core_label(CS1),
+ {Tree, NewLabel} = cerl_trees:label(TmpTree, NextLabel),
+ CS2 = dialyzer_codeserver:insert(Module, Tree, CS1),
+ CS3 = dialyzer_codeserver:set_next_core_label(NewLabel, CS2),
+ CS4 = dialyzer_codeserver:store_temp_records(Module, Records, CS3),
+ CS5 = dialyzer_codeserver:store_temp_contracts(Module, SpecInfo, CbInfo, CS4),
+ OldExpTypes = dialyzer_codeserver:get_temp_exported_types(CS5),
+ MergedExpTypes = sets:union(ExpTypes, OldExpTypes),
+ CS6 = dialyzer_codeserver:insert_temp_exported_types(MergedExpTypes, CS5),
+ Ex_Funcs = [{0, F, A} || {_, _, {F, A}} <- cerl:module_exports(Tree)],
+ CG = Analysis#analysis.callgraph,
+ {V, E} = dialyzer_callgraph:scan_core_tree(Tree, CG),
+ dialyzer_callgraph:add_edges(E, V, CG),
+ Fun = fun analyze_one_function/2,
+ All_Defs = cerl:module_defs(Tree),
+ Acc = lists:foldl(Fun, #tmpAcc{file = File, module = Module}, All_Defs),
+ Exported_FuncMap = map__insert({File, Ex_Funcs}, Analysis#analysis.ex_func),
+ %% we must sort all functions in the file which
+ %% originate from this file by *numerical order* of lineNo
+ Sorted_Functions = lists:keysort(1, Acc#tmpAcc.funcAcc),
+ FuncMap = map__insert({File, Sorted_Functions}, Analysis#analysis.func),
+ %% we do not need to sort functions which are imported from included files
+ IncFuncMap = map__insert(
+ {File, Acc#tmpAcc.incFuncAcc},
+ Analysis#analysis.inc_func
+ ),
+ FMs = Analysis#analysis.fms ++ [{File, Module}],
+ RecordMap = map__insert({File, Records}, Analysis#analysis.record),
+ Analysis#analysis{
+ fms = FMs,
+ callgraph = CG,
+ codeserver = CS6,
+ ex_func = Exported_FuncMap,
+ inc_func = IncFuncMap,
+ record = RecordMap,
+ func = FuncMap
+ }.
-spec analyze_one_function({any(), any()}, #tmpAcc{}) -> #tmpAcc{}.
analyze_one_function({Var, FunBody} = Function, Acc) ->
- F = cerl:fname_id(Var),
- A = cerl:fname_arity(Var),
- TmpDialyzerObj = {{Acc#tmpAcc.module, F, A}, Function},
- NewDialyzerObj = Acc#tmpAcc.dialyzerObj ++ [TmpDialyzerObj],
- Anno = cerl:get_ann(FunBody),
- LineNo = get_line(Anno),
- FileName = get_file(Anno),
- BaseName = filename:basename(FileName),
- FuncInfo = {LineNo, F, A},
- OriginalName = Acc#tmpAcc.file,
- {FuncAcc, IncFuncAcc} =
- case (FileName =:= OriginalName) orelse (BaseName =:= OriginalName) of
- true -> %% Coming from original file
- %% io:format("Added function ~tp\n", [{LineNo, F, A}]),
- {Acc#tmpAcc.funcAcc ++ [FuncInfo], Acc#tmpAcc.incFuncAcc};
- false ->
- %% Coming from other sourses, including:
- %% -- .yrl (yecc-generated file)
- %% -- yeccpre.hrl (yecc-generated file)
- %% -- other cases
- {Acc#tmpAcc.funcAcc, Acc#tmpAcc.incFuncAcc ++ [{FileName, FuncInfo}]}
- end,
- Acc#tmpAcc{funcAcc = FuncAcc,
- incFuncAcc = IncFuncAcc,
- dialyzerObj = NewDialyzerObj}.
+ F = cerl:fname_id(Var),
+ A = cerl:fname_arity(Var),
+ TmpDialyzerObj = {{Acc#tmpAcc.module, F, A}, Function},
+ NewDialyzerObj = Acc#tmpAcc.dialyzerObj ++ [TmpDialyzerObj],
+ Anno = cerl:get_ann(FunBody),
+ LineNo = get_line(Anno),
+ FileName = get_file(Anno),
+ BaseName = filename:basename(FileName),
+ FuncInfo = {LineNo, F, A},
+ OriginalName = Acc#tmpAcc.file,
+ {FuncAcc, IncFuncAcc} =
+ case (FileName =:= OriginalName) orelse (BaseName =:= OriginalName) of
+ %% Coming from original file
+ true ->
+ %% io:format("Added function ~tp\n", [{LineNo, F, A}]),
+ {Acc#tmpAcc.funcAcc ++ [FuncInfo], Acc#tmpAcc.incFuncAcc};
+ false ->
+ %% Coming from other sourses, including:
+ %% -- .yrl (yecc-generated file)
+ %% -- yeccpre.hrl (yecc-generated file)
+ %% -- other cases
+ {Acc#tmpAcc.funcAcc, Acc#tmpAcc.incFuncAcc ++ [{FileName, FuncInfo}]}
+ end,
+ Acc#tmpAcc{
+ funcAcc = FuncAcc,
+ incFuncAcc = IncFuncAcc,
+ dialyzerObj = NewDialyzerObj
+ }.
-spec get_line([line()]) -> 'none' | integer().
-get_line([Line|_]) when is_integer(Line) -> Line;
-get_line([_|T]) -> get_line(T);
+get_line([Line | _]) when is_integer(Line) -> Line;
+get_line([_ | T]) -> get_line(T);
get_line([]) -> none.
-spec get_file([any()]) -> any().
-get_file([_|T]) -> get_file(T);
-get_file([]) -> "no_file". % should not happen
+get_file([_ | T]) -> get_file(T);
+% should not happen
+get_file([]) -> "no_file".
-spec get_dialyzer_plt() -> plt().
get_dialyzer_plt() ->
- PltFile = case els_config:get(plt_path) of
- undefined ->
+ PltFile =
+ case els_config:get(plt_path) of
+ undefined ->
dialyzer_plt:get_default_plt();
- PltPath ->
+ PltPath ->
PltPath
- end,
- dialyzer_plt:from_file(PltFile).
+ end,
+ dialyzer_plt:from_file(PltFile).
%% Exported Types
-spec get_exported_types_from_core(any()) -> sets:set().
get_exported_types_from_core(Core) ->
- Attrs = cerl:module_attrs(Core),
- ExpTypes1 = [cerl:concrete(L2) || {L1, L2} <- Attrs,
- cerl:is_literal(L1),
- cerl:is_literal(L2),
- cerl:concrete(L1) =:= 'export_type'],
- ExpTypes2 = lists:flatten(ExpTypes1),
- M = cerl:atom_val(cerl:module_name(Core)),
- sets:from_list([{M, F, A} || {F, A} <- ExpTypes2]).
+ Attrs = cerl:module_attrs(Core),
+ ExpTypes1 = [
+ cerl:concrete(L2)
+ || {L1, L2} <- Attrs,
+ cerl:is_literal(L1),
+ cerl:is_literal(L2),
+ cerl:concrete(L1) =:= 'export_type'
+ ],
+ ExpTypes2 = lists:flatten(ExpTypes1),
+ M = cerl:atom_val(cerl:module_name(Core)),
+ sets:from_list([{M, F, A} || {F, A} <- ExpTypes2]).
%%--------------------------------------------------------------------
%% Handle messages.
@@ -393,18 +436,18 @@ get_exported_types_from_core(Core) ->
-spec rcv_ext_types() -> [any()].
rcv_ext_types() ->
- Self = self(),
- Self ! {Self, done},
- rcv_ext_types(Self, []).
+ Self = self(),
+ Self ! {Self, done},
+ rcv_ext_types(Self, []).
-spec rcv_ext_types(pid(), [any()]) -> [any()].
rcv_ext_types(Self, ExtTypes) ->
- receive
- {Self, ext_types, ExtType} ->
- rcv_ext_types(Self, [ExtType|ExtTypes]);
- {Self, done} ->
- lists:usort(ExtTypes)
- end.
+ receive
+ {Self, ext_types, ExtType} ->
+ rcv_ext_types(Self, [ExtType | ExtTypes]);
+ {Self, done} ->
+ lists:usort(ExtTypes)
+ end.
%%--------------------------------------------------------------------
%% A convenient abstraction of a Key-Value mapping data structure
@@ -415,17 +458,21 @@ rcv_ext_types(Self, ExtTypes) ->
-spec map__new() -> map_dict().
map__new() ->
- dict:new().
+ dict:new().
-spec map__insert({term(), term()}, map_dict()) -> map_dict().
map__insert(Object, Map) ->
- {Key, Value} = Object,
- dict:store(Key, Value, Map).
+ {Key, Value} = Object,
+ dict:store(Key, Value, Map).
-spec map__lookup(term(), map_dict()) -> term().
map__lookup(Key, Map) ->
- try dict:fetch(Key, Map) catch error:_ -> none end.
+ try
+ dict:fetch(Key, Map)
+ catch
+ error:_ -> none
+ end.
-spec map__from_list([{fa(), term()}]) -> map_dict().
map__from_list(List) ->
- dict:from_list(List).
+ dict:from_list(List).
diff --git a/apps/els_lsp/src/els_unused_includes_diagnostics.erl b/apps/els_lsp/src/els_unused_includes_diagnostics.erl
index d2a7c9204..66932726f 100644
--- a/apps/els_lsp/src/els_unused_includes_diagnostics.erl
+++ b/apps/els_lsp/src/els_unused_includes_diagnostics.erl
@@ -11,10 +11,11 @@
%%==============================================================================
%% Exports
%%==============================================================================
--export([ is_default/0
- , run/1
- , source/0
- ]).
+-export([
+ is_default/0,
+ run/1,
+ source/0
+]).
%%==============================================================================
%% Includes
@@ -27,123 +28,131 @@
-spec is_default() -> boolean().
is_default() ->
- true.
+ true.
-spec run(uri()) -> [els_diagnostics:diagnostic()].
run(Uri) ->
- case els_utils:lookup_document(Uri) of
- {error, _Error} ->
- [];
- {ok, Document} ->
- UnusedIncludes = find_unused_includes(Document),
- [ els_diagnostics:make_diagnostic(
- els_protocol:range(inclusion_range(UI, Document))
- , <<"Unused file: ", (filename:basename(UI))/binary>>
- , ?DIAGNOSTIC_WARNING
- , source()
- , <> %% Additional data with complete path
- ) || UI <- UnusedIncludes ]
- end.
+ case els_utils:lookup_document(Uri) of
+ {error, _Error} ->
+ [];
+ {ok, Document} ->
+ UnusedIncludes = find_unused_includes(Document),
+ [
+ els_diagnostics:make_diagnostic(
+ els_protocol:range(inclusion_range(UI, Document)),
+ <<"Unused file: ", (filename:basename(UI))/binary>>,
+ ?DIAGNOSTIC_WARNING,
+ source(),
+ %% Additional data with complete path
+ <>
+ )
+ || UI <- UnusedIncludes
+ ]
+ end.
-spec source() -> binary().
source() ->
- <<"UnusedIncludes">>.
+ <<"UnusedIncludes">>.
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec find_unused_includes(els_dt_document:item()) -> [uri()].
find_unused_includes(#{uri := Uri} = Document) ->
- Graph = expand_includes(Document),
- POIs = els_dt_document:pois(Document,
- [ application
- , implicit_fun
- , import_entry
- , macro
- , record_expr
- , record_field
- , type_application
- , export_type_entry
- ]),
- IncludedUris0 = els_diagnostics_utils:included_uris(Document),
- IncludedUris1 = filter_includes_with_compiler_attributes(IncludedUris0),
- ExcludeUnusedIncludes =
- lists:filtermap(
- fun(Include) ->
- case els_utils:find_header(els_utils:filename_to_atom(Include)) of
- {ok, File} -> {true, File};
- {error, _Error} ->
- false
- end
- end, els_config:get(exclude_unused_includes)),
- IncludedUris = IncludedUris1 -- ExcludeUnusedIncludes,
- Fun = fun(POI, Acc) ->
- update_unused(Graph, Uri, POI, Acc)
- end,
- UnusedIncludes = lists:foldl(Fun, IncludedUris, POIs),
- digraph:delete(Graph),
- UnusedIncludes.
+ Graph = expand_includes(Document),
+ POIs = els_dt_document:pois(
+ Document,
+ [
+ application,
+ implicit_fun,
+ import_entry,
+ macro,
+ record_expr,
+ record_field,
+ type_application,
+ export_type_entry
+ ]
+ ),
+ IncludedUris0 = els_diagnostics_utils:included_uris(Document),
+ IncludedUris1 = filter_includes_with_compiler_attributes(IncludedUris0),
+ ExcludeUnusedIncludes =
+ lists:filtermap(
+ fun(Include) ->
+ case els_utils:find_header(els_utils:filename_to_atom(Include)) of
+ {ok, File} -> {true, File};
+ {error, _Error} -> false
+ end
+ end,
+ els_config:get(exclude_unused_includes)
+ ),
+ IncludedUris = IncludedUris1 -- ExcludeUnusedIncludes,
+ Fun = fun(POI, Acc) ->
+ update_unused(Graph, Uri, POI, Acc)
+ end,
+ UnusedIncludes = lists:foldl(Fun, IncludedUris, POIs),
+ digraph:delete(Graph),
+ UnusedIncludes.
-spec update_unused(digraph:graph(), uri(), poi(), [uri()]) -> [uri()].
update_unused(Graph, Uri, POI, Acc) ->
- case els_code_navigation:goto_definition(Uri, POI) of
- {ok, Uri, _DefinitionPOI} ->
- Acc;
- {ok, DefinitionUri, _DefinitionPOI} ->
- case digraph:get_path(Graph, DefinitionUri, Uri) of
- false ->
- Acc;
- Path ->
- Acc -- Path
- end;
- {error, _Reason} ->
- Acc
- end.
+ case els_code_navigation:goto_definition(Uri, POI) of
+ {ok, Uri, _DefinitionPOI} ->
+ Acc;
+ {ok, DefinitionUri, _DefinitionPOI} ->
+ case digraph:get_path(Graph, DefinitionUri, Uri) of
+ false ->
+ Acc;
+ Path ->
+ Acc -- Path
+ end;
+ {error, _Reason} ->
+ Acc
+ end.
-spec expand_includes(els_dt_document:item()) -> digraph:graph().
expand_includes(Document) ->
- DG = digraph:new(),
- AccFun = fun(#{uri := IncludedUri}, #{uri := IncluderUri}, _) ->
- Dest = digraph:add_vertex(DG, IncluderUri),
- Src = digraph:add_vertex(DG, IncludedUri),
- _IncludedBy = digraph:add_edge(DG, Src, Dest),
- DG
- end,
- els_diagnostics_utils:traverse_include_graph(AccFun, DG, Document).
+ DG = digraph:new(),
+ AccFun = fun(#{uri := IncludedUri}, #{uri := IncluderUri}, _) ->
+ Dest = digraph:add_vertex(DG, IncluderUri),
+ Src = digraph:add_vertex(DG, IncludedUri),
+ _IncludedBy = digraph:add_edge(DG, Src, Dest),
+ DG
+ end,
+ els_diagnostics_utils:traverse_include_graph(AccFun, DG, Document).
-spec inclusion_range(uri(), els_dt_document:item()) -> poi_range().
inclusion_range(Uri, Document) ->
- case els_range:inclusion_range(Uri, Document) of
- {ok, Range} ->
- Range;
- _ ->
- #{from => {1, 1}, to => {2, 1}}
- end.
+ case els_range:inclusion_range(Uri, Document) of
+ {ok, Range} ->
+ Range;
+ _ ->
+ #{from => {1, 1}, to => {2, 1}}
+ end.
%% @doc Given a list of included uris, filter out the ones containing
%% compiler attributes. If the Uri cannot be found, keep it in the list.
-spec filter_includes_with_compiler_attributes([uri()]) -> [uri()].
filter_includes_with_compiler_attributes(Uris) ->
- Filter = fun(Uri) ->
- case els_utils:lookup_document(Uri) of
- {error, _Error} ->
- {true, Uri};
- {ok, Document} ->
- case contains_compiler_attributes(Document) of
- true ->
+ Filter = fun(Uri) ->
+ case els_utils:lookup_document(Uri) of
+ {error, _Error} ->
+ {true, Uri};
+ {ok, Document} ->
+ case contains_compiler_attributes(Document) of
+ true ->
false;
- false ->
+ false ->
{true, Uri}
- end
- end
- end,
- lists:filtermap(Filter, Uris).
+ end
+ end
+ end,
+ lists:filtermap(Filter, Uris).
%% @doc Return true if the Document contains a compiler attribute.
-spec contains_compiler_attributes(els_dt_document:item()) -> boolean().
contains_compiler_attributes(Document) ->
- compiler_attributes(Document) =/= [].
+ compiler_attributes(Document) =/= [].
-spec compiler_attributes(els_dt_document:item()) -> [poi()].
compiler_attributes(Document) ->
- els_dt_document:pois(Document, [compile]).
+ els_dt_document:pois(Document, [compile]).
diff --git a/apps/els_lsp/src/els_unused_macros_diagnostics.erl b/apps/els_lsp/src/els_unused_macros_diagnostics.erl
index 55dcb177c..4c6cfd0e8 100644
--- a/apps/els_lsp/src/els_unused_macros_diagnostics.erl
+++ b/apps/els_lsp/src/els_unused_macros_diagnostics.erl
@@ -11,10 +11,11 @@
%%==============================================================================
%% Exports
%%==============================================================================
--export([ is_default/0
- , run/1
- , source/0
- ]).
+-export([
+ is_default/0,
+ run/1,
+ source/0
+]).
%%==============================================================================
%% Includes
@@ -27,47 +28,50 @@
-spec is_default() -> boolean().
is_default() ->
- true.
+ true.
-spec run(uri()) -> [els_diagnostics:diagnostic()].
run(Uri) ->
- case filename:extension(Uri) of
- <<".erl">> ->
- case els_utils:lookup_document(Uri) of
- {error, _Error} ->
- [];
- {ok, Document} ->
- UnusedMacros = find_unused_macros(Document),
- [make_diagnostic(POI) || POI <- UnusedMacros ]
- end;
- _ ->
- []
- end.
+ case filename:extension(Uri) of
+ <<".erl">> ->
+ case els_utils:lookup_document(Uri) of
+ {error, _Error} ->
+ [];
+ {ok, Document} ->
+ UnusedMacros = find_unused_macros(Document),
+ [make_diagnostic(POI) || POI <- UnusedMacros]
+ end;
+ _ ->
+ []
+ end.
-spec source() -> binary().
source() ->
- <<"UnusedMacros">>.
+ <<"UnusedMacros">>.
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec find_unused_macros(els_dt_document:item()) -> [poi()].
find_unused_macros(Document) ->
- Defines = els_dt_document:pois(Document, [define]),
- Macros = els_dt_document:pois(Document, [macro]),
- MacroIds = [Id || #{id := Id} <- Macros],
- [POI || #{id := Id} = POI <- Defines, not lists:member(Id, MacroIds)].
+ Defines = els_dt_document:pois(Document, [define]),
+ Macros = els_dt_document:pois(Document, [macro]),
+ MacroIds = [Id || #{id := Id} <- Macros],
+ [POI || #{id := Id} = POI <- Defines, not lists:member(Id, MacroIds)].
-spec make_diagnostic(poi()) -> els_diagnostics:diagnostic().
make_diagnostic(#{id := POIId, range := POIRange}) ->
- Range = els_protocol:range(POIRange),
- MacroName = case POIId of
- {Id, Arity} ->
- els_utils:to_binary(
- lists:flatten(io_lib:format("~s/~p", [Id, Arity])));
- Id -> atom_to_binary(Id, utf8)
- end,
- Message = <<"Unused macro: ", MacroName/binary>>,
- Severity = ?DIAGNOSTIC_WARNING,
- Source = source(),
- els_diagnostics:make_diagnostic(Range, Message, Severity, Source).
+ Range = els_protocol:range(POIRange),
+ MacroName =
+ case POIId of
+ {Id, Arity} ->
+ els_utils:to_binary(
+ lists:flatten(io_lib:format("~s/~p", [Id, Arity]))
+ );
+ Id ->
+ atom_to_binary(Id, utf8)
+ end,
+ Message = <<"Unused macro: ", MacroName/binary>>,
+ Severity = ?DIAGNOSTIC_WARNING,
+ Source = source(),
+ els_diagnostics:make_diagnostic(Range, Message, Severity, Source).
diff --git a/apps/els_lsp/src/els_unused_record_fields_diagnostics.erl b/apps/els_lsp/src/els_unused_record_fields_diagnostics.erl
index 084ce1a47..620978ef7 100644
--- a/apps/els_lsp/src/els_unused_record_fields_diagnostics.erl
+++ b/apps/els_lsp/src/els_unused_record_fields_diagnostics.erl
@@ -11,10 +11,11 @@
%%==============================================================================
%% Exports
%%==============================================================================
--export([ is_default/0
- , run/1
- , source/0
- ]).
+-export([
+ is_default/0,
+ run/1,
+ source/0
+]).
%%==============================================================================
%% Includes
@@ -27,42 +28,42 @@
-spec is_default() -> boolean().
is_default() ->
- true.
+ true.
-spec run(uri()) -> [els_diagnostics:diagnostic()].
run(Uri) ->
- case filename:extension(Uri) of
- <<".erl">> ->
- case els_utils:lookup_document(Uri) of
- {error, _Error} ->
- [];
- {ok, Document} ->
- UnusedRecordFields = find_unused_record_fields(Document),
- [make_diagnostic(POI) || POI <- UnusedRecordFields ]
- end;
- _ ->
- []
- end.
+ case filename:extension(Uri) of
+ <<".erl">> ->
+ case els_utils:lookup_document(Uri) of
+ {error, _Error} ->
+ [];
+ {ok, Document} ->
+ UnusedRecordFields = find_unused_record_fields(Document),
+ [make_diagnostic(POI) || POI <- UnusedRecordFields]
+ end;
+ _ ->
+ []
+ end.
-spec source() -> binary().
source() ->
- <<"UnusedRecordFields">>.
+ <<"UnusedRecordFields">>.
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec find_unused_record_fields(els_dt_document:item()) -> [poi()].
find_unused_record_fields(Document) ->
- Definitions = els_dt_document:pois(Document, [record_def_field]),
- Usages = els_dt_document:pois(Document, [record_field]),
- UsagesIds = lists:usort([Id || #{id := Id} <- Usages]),
- [POI || #{id := Id} = POI <- Definitions, not lists:member(Id, UsagesIds)].
+ Definitions = els_dt_document:pois(Document, [record_def_field]),
+ Usages = els_dt_document:pois(Document, [record_field]),
+ UsagesIds = lists:usort([Id || #{id := Id} <- Usages]),
+ [POI || #{id := Id} = POI <- Definitions, not lists:member(Id, UsagesIds)].
-spec make_diagnostic(poi()) -> els_diagnostics:diagnostic().
make_diagnostic(#{id := {RecName, RecField}, range := POIRange}) ->
- Range = els_protocol:range(POIRange),
- FullName = els_utils:to_binary(io_lib:format("#~p.~p", [RecName, RecField])),
- Message = <<"Unused record field: ", FullName/binary>>,
- Severity = ?DIAGNOSTIC_WARNING,
- Source = source(),
- els_diagnostics:make_diagnostic(Range, Message, Severity, Source).
+ Range = els_protocol:range(POIRange),
+ FullName = els_utils:to_binary(io_lib:format("#~p.~p", [RecName, RecField])),
+ Message = <<"Unused record field: ", FullName/binary>>,
+ Severity = ?DIAGNOSTIC_WARNING,
+ Source = source(),
+ els_diagnostics:make_diagnostic(Range, Message, Severity, Source).
diff --git a/apps/els_lsp/src/els_work_done_progress.erl b/apps/els_lsp/src/els_work_done_progress.erl
index eb777e120..9cb23deb8 100644
--- a/apps/els_lsp/src/els_work_done_progress.erl
+++ b/apps/els_lsp/src/els_work_done_progress.erl
@@ -12,42 +12,48 @@
%% Types
%%==============================================================================
-type percentage() :: 0..100.
--type value_begin() :: #{ kind := 'begin'
- , title := binary()
- , cancellable => boolean()
- , message => binary()
- , percentage => percentage()
- }.
--type value_report() :: #{ kind := 'report'
- , cancellable => boolean()
- , message => binary()
- , percentage => percentage()
- }.
--type value_end() :: #{ kind := 'end'
- , message => binary()
- }.
--type value() :: value_begin()
- | value_report()
- | value_end().
+-type value_begin() :: #{
+ kind := 'begin',
+ title := binary(),
+ cancellable => boolean(),
+ message => binary(),
+ percentage => percentage()
+}.
+-type value_report() :: #{
+ kind := 'report',
+ cancellable => boolean(),
+ message => binary(),
+ percentage => percentage()
+}.
+-type value_end() :: #{
+ kind := 'end',
+ message => binary()
+}.
+-type value() ::
+ value_begin()
+ | value_report()
+ | value_end().
--export_type([ value_begin/0
- , value_report/0
- , value_end/0
- , value/0
- ]).
+-export_type([
+ value_begin/0,
+ value_report/0,
+ value_end/0,
+ value/0
+]).
%%==============================================================================
%% Exports
%%==============================================================================
--export([ is_supported/0
- , send_create_request/0
- , value_begin/2
- , value_begin/3
- , value_report/1
- , value_report/2
- , value_end/1
- ]).
+-export([
+ is_supported/0,
+ send_create_request/0,
+ value_begin/2,
+ value_begin/3,
+ value_report/1,
+ value_report/2,
+ value_end/1
+]).
%%==============================================================================
%% API
@@ -55,56 +61,62 @@
-spec is_supported() -> boolean().
is_supported() ->
- case els_config:get(capabilities) of
- #{<<"window">> := #{<<"workDoneProgress">> := WorkDoneProgress }}
- when is_boolean(WorkDoneProgress) ->
- WorkDoneProgress;
- _ ->
- false
- end.
+ case els_config:get(capabilities) of
+ #{<<"window">> := #{<<"workDoneProgress">> := WorkDoneProgress}} when
+ is_boolean(WorkDoneProgress)
+ ->
+ WorkDoneProgress;
+ _ ->
+ false
+ end.
-spec send_create_request() -> els_progress:token().
send_create_request() ->
- Token = els_progress:token(),
- Method = <<"window/workDoneProgress/create">>,
- Params = #{token => Token},
- ok = els_server:send_request(Method, Params),
- Token.
+ Token = els_progress:token(),
+ Method = <<"window/workDoneProgress/create">>,
+ Params = #{token => Token},
+ ok = els_server:send_request(Method, Params),
+ Token.
-spec value_begin(binary(), binary()) -> value_begin().
value_begin(Title, Message) ->
- #{ kind => 'begin'
- , title => Title
- , cancellable => false
- , message => Message
- }.
+ #{
+ kind => 'begin',
+ title => Title,
+ cancellable => false,
+ message => Message
+ }.
-spec value_begin(binary(), binary(), percentage()) -> value_begin().
value_begin(Title, Message, Percentage) ->
- #{ kind => 'begin'
- , title => Title
- , cancellable => false
- , message => Message
- , percentage => Percentage
- }.
+ #{
+ kind => 'begin',
+ title => Title,
+ cancellable => false,
+ message => Message,
+ percentage => Percentage
+ }.
-spec value_report(binary()) -> value_report().
value_report(Message) ->
- #{ kind => 'report'
- , cancellable => false
- , message => Message
- }.
+ #{
+ kind => 'report',
+ cancellable => false,
+ message => Message
+ }.
-spec value_report(binary(), percentage()) -> value_report().
value_report(Message, Percentage) ->
- #{ kind => 'report'
- , cancellable => false
- , message => Message
- , percentage => Percentage
- }.
+ #{
+ kind => 'report',
+ cancellable => false,
+ message => Message,
+ percentage => Percentage
+ }.
-spec value_end(binary()) -> value_end().
value_end(Message) ->
- #{ kind => 'end'
- , message => Message
- }.
+ #{
+ kind => 'end',
+ message => Message
+ }.
diff --git a/apps/els_lsp/src/els_workspace_symbol_provider.erl b/apps/els_lsp/src/els_workspace_symbol_provider.erl
index ab2f27117..a88d709fe 100644
--- a/apps/els_lsp/src/els_workspace_symbol_provider.erl
+++ b/apps/els_lsp/src/els_workspace_symbol_provider.erl
@@ -2,9 +2,10 @@
-behaviour(els_provider).
--export([ is_enabled/0
- , handle_request/2
- ]).
+-export([
+ is_enabled/0,
+ handle_request/2
+]).
-include("els_lsp.hrl").
@@ -20,11 +21,11 @@ is_enabled() -> true.
-spec handle_request(any(), state()) -> {response, any()}.
handle_request({symbol, Params}, _State) ->
- %% TODO: Version 3.15 of the protocol introduces a much nicer way of
- %% specifying queries, allowing clients to send the symbol kind.
- #{<<"query">> := Query} = Params,
- TrimmedQuery = string:trim(Query),
- {response, modules(TrimmedQuery)}.
+ %% TODO: Version 3.15 of the protocol introduces a much nicer way of
+ %% specifying queries, allowing clients to send the symbol kind.
+ #{<<"query">> := Query} = Params,
+ TrimmedQuery = string:trim(Query),
+ {response, modules(TrimmedQuery)}.
%%==============================================================================
%% Internal Functions
@@ -32,44 +33,48 @@ handle_request({symbol, Params}, _State) ->
-spec modules(binary()) -> [symbol_information()].
modules(<<>>) ->
- [];
+ [];
modules(Query) ->
- {ok, All} = els_dt_document_index:find_by_kind(module),
- Compare = fun(#{id := X}, #{id := Y}) -> X < Y end,
- AllSorted = lists:sort(Compare, All),
- {ok, RePattern} = re:compile(Query),
- ReOpts = [{capture, none}],
- F = fun(Name) ->
- re:run(Name, RePattern, ReOpts) =:= match
- end,
- Filtered = filter_with_limit(AllSorted, F, ?LIMIT, []),
- [symbol_information(X) || X <- Filtered].
+ {ok, All} = els_dt_document_index:find_by_kind(module),
+ Compare = fun(#{id := X}, #{id := Y}) -> X < Y end,
+ AllSorted = lists:sort(Compare, All),
+ {ok, RePattern} = re:compile(Query),
+ ReOpts = [{capture, none}],
+ F = fun(Name) ->
+ re:run(Name, RePattern, ReOpts) =:= match
+ end,
+ Filtered = filter_with_limit(AllSorted, F, ?LIMIT, []),
+ [symbol_information(X) || X <- Filtered].
-spec symbol_information({binary(), uri()}) -> symbol_information().
symbol_information({Name, Uri}) ->
- Range = #{from => {1, 1}, to => {1, 1}},
- #{ name => Name
- , kind => ?SYMBOLKIND_MODULE
- , location => #{ uri => Uri
- , range => els_protocol:range(Range)
- }
- }.
+ Range = #{from => {1, 1}, to => {1, 1}},
+ #{
+ name => Name,
+ kind => ?SYMBOLKIND_MODULE,
+ location => #{
+ uri => Uri,
+ range => els_protocol:range(Range)
+ }
+ }.
--spec filter_with_limit( [els_dt_document_index:item()]
- , function()
- , integer()
- , [{binary(), uri()}]) ->
- [{binary(), uri()}].
+-spec filter_with_limit(
+ [els_dt_document_index:item()],
+ function(),
+ integer(),
+ [{binary(), uri()}]
+) ->
+ [{binary(), uri()}].
filter_with_limit(_Modules, _Filter, 0, Result) ->
- lists:reverse(Result);
+ lists:reverse(Result);
filter_with_limit([], _Filter, _Limit, Result) ->
- lists:reverse(Result);
+ lists:reverse(Result);
filter_with_limit([Item | Items], Filter, Limit, Result) ->
- #{kind := module, id := Module, uri := Uri} = Item,
- Name = atom_to_binary(Module, utf8),
- case Filter(Name) of
- true ->
- filter_with_limit(Items, Filter, Limit - 1, [{Name, Uri} | Result]);
- false ->
- filter_with_limit(Items, Filter, Limit, Result)
- end.
+ #{kind := module, id := Module, uri := Uri} = Item,
+ Name = atom_to_binary(Module, utf8),
+ case Filter(Name) of
+ true ->
+ filter_with_limit(Items, Filter, Limit - 1, [{Name, Uri} | Result]);
+ false ->
+ filter_with_limit(Items, Filter, Limit, Result)
+ end.
diff --git a/apps/els_lsp/src/erlang_ls.erl b/apps/els_lsp/src/erlang_ls.erl
index c8b612d5a..3c050cbbd 100644
--- a/apps/els_lsp/src/erlang_ls.erl
+++ b/apps/els_lsp/src/erlang_ls.erl
@@ -1,11 +1,12 @@
-module(erlang_ls).
--export([ main/1 ]).
+-export([main/1]).
--export([ parse_args/1
- , log_root/0
- , cache_root/0
- ]).
+-export([
+ parse_args/1,
+ log_root/0,
+ cache_root/0
+]).
%%==============================================================================
%% Includes
@@ -17,23 +18,25 @@
-spec main([any()]) -> ok.
main(Args) ->
- application:load(getopt),
- application:load(els_core),
- application:load(?APP),
- ok = parse_args(Args),
- application:set_env(els_core, server, els_server),
- configure_logging(),
- {ok, _} = application:ensure_all_started(?APP, permanent),
- patch_logging(),
- configure_client_logging(),
- ?LOG_INFO("Started erlang_ls server", []),
- receive _ -> ok end.
+ application:load(getopt),
+ application:load(els_core),
+ application:load(?APP),
+ ok = parse_args(Args),
+ application:set_env(els_core, server, els_server),
+ configure_logging(),
+ {ok, _} = application:ensure_all_started(?APP, permanent),
+ patch_logging(),
+ configure_client_logging(),
+ ?LOG_INFO("Started erlang_ls server", []),
+ receive
+ _ -> ok
+ end.
-spec print_version() -> ok.
print_version() ->
- {ok, Vsn} = application:get_key(?APP, vsn),
- io:format("Version: ~s~n", [Vsn]),
- ok.
+ {ok, Vsn} = application:get_key(?APP, vsn),
+ io:format("Version: ~s~n", [Vsn]),
+ ok.
%%==============================================================================
%% Argument parsing
@@ -41,62 +44,48 @@ print_version() ->
-spec parse_args([string()]) -> ok.
parse_args(Args) ->
- case getopt:parse(opt_spec_list(), Args) of
- {ok, {[version | _], _BadArgs}} ->
- print_version(),
- halt(0);
- {ok, {ParsedArgs, _BadArgs}} ->
- set_args(ParsedArgs);
- {error, {invalid_option, _}} ->
- getopt:usage(opt_spec_list(), "Erlang LS"),
- halt(1)
- end.
+ case getopt:parse(opt_spec_list(), Args) of
+ {ok, {[version | _], _BadArgs}} ->
+ print_version(),
+ halt(0);
+ {ok, {ParsedArgs, _BadArgs}} ->
+ set_args(ParsedArgs);
+ {error, {invalid_option, _}} ->
+ getopt:usage(opt_spec_list(), "Erlang LS"),
+ halt(1)
+ end.
-spec opt_spec_list() -> [getopt:option_spec()].
opt_spec_list() ->
- [ { version
- , $v
- , "version"
- , undefined
- , "Print the current version of Erlang LS"
- }
- , { transport
- , $t
- , "transport"
- , {string, "stdio"}
- , "DEPRECATED. Only the \"stdio\" transport is currently supported."
- }
- , { log_dir
- , $d
- , "log-dir"
- , {string, filename:basedir(user_log, "erlang_ls")}
- , "Directory where logs will be written."
- }
- , { log_level
- , $l
- , "log-level"
- , {string, ?DEFAULT_LOGGING_LEVEL}
- , "The log level that should be used."
- }
- ].
+ [
+ {version, $v, "version", undefined, "Print the current version of Erlang LS"},
+ {transport, $t, "transport", {string, "stdio"},
+ "DEPRECATED. Only the \"stdio\" transport is currently supported."},
+ {log_dir, $d, "log-dir", {string, filename:basedir(user_log, "erlang_ls")},
+ "Directory where logs will be written."},
+ {log_level, $l, "log-level", {string, ?DEFAULT_LOGGING_LEVEL},
+ "The log level that should be used."}
+ ].
-spec set_args([] | [getopt:compound_option()]) -> ok.
-set_args([]) -> ok;
-set_args([version | Rest]) -> set_args(Rest);
+set_args([]) ->
+ ok;
+set_args([version | Rest]) ->
+ set_args(Rest);
set_args([{Arg, Val} | Rest]) ->
- set(Arg, Val),
- set_args(Rest).
+ set(Arg, Val),
+ set_args(Rest).
-spec set(atom(), getopt:arg_value()) -> ok.
set(transport, _Transport) ->
- %% Deprecated option, only kept for backward compatibility.
- ok;
+ %% Deprecated option, only kept for backward compatibility.
+ ok;
set(log_dir, Dir) ->
- application:set_env(els_core, log_dir, Dir);
+ application:set_env(els_core, log_dir, Dir);
set(log_level, Level) ->
- application:set_env(els_core, log_level, list_to_atom(Level));
+ application:set_env(els_core, log_level, list_to_atom(Level));
set(port_old, Port) ->
- application:set_env(els_core, port, Port).
+ application:set_env(els_core, port, Port).
%%==============================================================================
%% Logger configuration
@@ -104,50 +93,49 @@ set(port_old, Port) ->
-spec configure_logging() -> ok.
configure_logging() ->
- LogFile = filename:join([log_root(), "server.log"]),
- {ok, LoggingLevel} = application:get_env(els_core, log_level),
- ok = filelib:ensure_dir(LogFile),
- [logger:remove_handler(H) || H <- logger:get_handler_ids()],
- Handler = #{ config => #{ file => LogFile }
- , level => LoggingLevel
- , formatter => { logger_formatter
- , #{ template => ?LSP_LOG_FORMAT }
- }
- },
- StdErrHandler = #{ config => #{ type => standard_error }
- , level => error
- , formatter => { logger_formatter
- , #{ template => ?LSP_LOG_FORMAT }
- }
- },
- logger:add_handler(els_core_handler, logger_std_h, Handler),
- logger:add_handler(els_stderr_handler, logger_std_h, StdErrHandler),
- logger:set_primary_config(level, LoggingLevel),
- ok.
+ LogFile = filename:join([log_root(), "server.log"]),
+ {ok, LoggingLevel} = application:get_env(els_core, log_level),
+ ok = filelib:ensure_dir(LogFile),
+ [logger:remove_handler(H) || H <- logger:get_handler_ids()],
+ Handler = #{
+ config => #{file => LogFile},
+ level => LoggingLevel,
+ formatter => {logger_formatter, #{template => ?LSP_LOG_FORMAT}}
+ },
+ StdErrHandler = #{
+ config => #{type => standard_error},
+ level => error,
+ formatter => {logger_formatter, #{template => ?LSP_LOG_FORMAT}}
+ },
+ logger:add_handler(els_core_handler, logger_std_h, Handler),
+ logger:add_handler(els_stderr_handler, logger_std_h, StdErrHandler),
+ logger:set_primary_config(level, LoggingLevel),
+ ok.
-spec patch_logging() -> ok.
patch_logging() ->
- %% The ssl_handler is added by ranch -> ssl
- logger:remove_handler(ssl_handler),
- ok.
+ %% The ssl_handler is added by ranch -> ssl
+ logger:remove_handler(ssl_handler),
+ ok.
-spec log_root() -> string().
log_root() ->
- {ok, LogDir} = application:get_env(els_core, log_dir),
- {ok, CurrentDir} = file:get_cwd(),
- Dirname = filename:basename(CurrentDir),
- filename:join([LogDir, Dirname]).
+ {ok, LogDir} = application:get_env(els_core, log_dir),
+ {ok, CurrentDir} = file:get_cwd(),
+ Dirname = filename:basename(CurrentDir),
+ filename:join([LogDir, Dirname]).
-spec cache_root() -> file:name().
cache_root() ->
- {ok, CurrentDir} = file:get_cwd(),
- Dirname = filename:basename(CurrentDir),
- filename:join(filename:basedir(user_cache, "erlang_ls"), Dirname).
+ {ok, CurrentDir} = file:get_cwd(),
+ Dirname = filename:basename(CurrentDir),
+ filename:join(filename:basedir(user_cache, "erlang_ls"), Dirname).
-spec configure_client_logging() -> ok.
configure_client_logging() ->
LoggingLevel = application:get_env(els_core, log_level, notice),
- ok = logger:add_handler( els_log_notification
- , els_log_notification
- , #{ level => LoggingLevel }
- ).
+ ok = logger:add_handler(
+ els_log_notification,
+ els_log_notification,
+ #{level => LoggingLevel}
+ ).
diff --git a/apps/els_lsp/test/els_call_hierarchy_SUITE.erl b/apps/els_lsp/test/els_call_hierarchy_SUITE.erl
index 22a1ca693..26c359a25 100644
--- a/apps/els_lsp/test/els_call_hierarchy_SUITE.erl
+++ b/apps/els_lsp/test/els_call_hierarchy_SUITE.erl
@@ -3,18 +3,20 @@
-include("els_lsp.hrl").
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ incoming_calls/1
- , outgoing_calls/1
- ]).
+-export([
+ incoming_calls/1,
+ outgoing_calls/1
+]).
%%==============================================================================
%% Includes
@@ -32,246 +34,346 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(TestCase, Config) ->
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Testcases
%%==============================================================================
-spec incoming_calls(config()) -> ok.
incoming_calls(Config) ->
- UriA = ?config(call_hierarchy_a_uri, Config),
- UriB = ?config(call_hierarchy_b_uri, Config),
- #{result := PrepareResult} =
- els_client:preparecallhierarchy(UriA, _Line = 11, _Char = 6),
- Data = els_utils:base64_encode_term(
- #{ poi =>
- #{ data =>
- #{ args => [{1, "Arg1"}]
- , wrapping_range => #{ from => {7, 1}
- , to => {17, 0}
- }
- , folding_range => #{ from => {7, ?END_OF_LINE}
- , to => {16, ?END_OF_LINE}
- }
- }
- , id => {function_a, 1}
- , kind => function
- , range => #{ from => {7, 1}
- , to => {7, 11}
- }
- }
- }),
- Item = #{ data => Data
- , detail => <<"call_hierarchy_a [L7]">>
- , kind => ?SYMBOLKIND_FUNCTION
- , name => <<"function_a/1">>
- , range =>
- #{ 'end' => #{character => 10, line => 6}
- , start => #{character => 0, line => 6}}
- , selectionRange =>
- #{ 'end' => #{character => 10, line => 6}
- , start => #{character => 0, line => 6}}
- , uri => UriA},
- ?assertEqual([Item], PrepareResult),
- #{result := Result} = els_client:callhierarchy_incomingcalls(Item),
- Calls = [ #{ from =>
- #{ data =>
- els_utils:base64_encode_term(
- #{ poi =>
- #{ data =>
- #{ args => [{1, "Arg1"}]
- , wrapping_range =>
- #{ from => {7, 1}
- , to => {14, 0}}
- , folding_range =>
- #{ from => {7, ?END_OF_LINE}
- , to => {13, ?END_OF_LINE}}}
- , id => {function_a, 1}
- , kind => function
- , range => #{from => {7, 1}, to => {7, 11}}}})
- , detail => <<"call_hierarchy_b [L11]">>
- , kind => 12
- , name => <<"function_a/1">>
- , range =>
- #{ 'end' => #{character => 29, line => 10}
- , start => #{character => 2, line => 10}
- }
- , selectionRange =>
- #{ 'end' => #{character => 29, line => 10}
- , start => #{character => 2, line => 10}
- }
- , uri => UriB}
- , fromRanges =>
- [#{ 'end' => #{character => 29, line => 10}
- , start => #{character => 2, line => 10}
- }]
- }
- , #{ from =>
- #{ data =>
- els_utils:base64_encode_term(
- #{ poi =>
- #{ data =>
- #{ args => [{1, "Arg1"}]
- , wrapping_range =>
- #{ from => {7, 1}
- , to => {17, 0}
- }
- , folding_range =>
- #{ from => {7, ?END_OF_LINE}
- , to => {16, ?END_OF_LINE}
- }
- }
- , id => {function_a, 1}
- , kind => function
- , range => #{ from => {7, 1}
- , to => {7, 11}
- }
- }
- })
- , detail => <<"call_hierarchy_a [L16]">>
- , kind => 12
- , name => <<"function_a/1">>
- , range =>
- #{ 'end' => #{ character => 12
- , line => 15
- }
- , start => #{ character => 2
- , line => 15
- }
- }
- , selectionRange =>
- #{ 'end' => #{ character => 12
- , line => 15
- }
- , start => #{ character => 2
- , line => 15
- }
- }
- , uri => UriA
- }
- , fromRanges =>
- [#{ 'end' => #{character => 12, line => 15}
- , start => #{character => 2, line => 15}
- }]}
- ],
- [?assert(lists:member(Call, Result)) || Call <- Calls],
- ?assertEqual(length(Calls), length(Result)).
+ UriA = ?config(call_hierarchy_a_uri, Config),
+ UriB = ?config(call_hierarchy_b_uri, Config),
+ #{result := PrepareResult} =
+ els_client:preparecallhierarchy(UriA, _Line = 11, _Char = 6),
+ Data = els_utils:base64_encode_term(
+ #{
+ poi =>
+ #{
+ data =>
+ #{
+ args => [{1, "Arg1"}],
+ wrapping_range => #{
+ from => {7, 1},
+ to => {17, 0}
+ },
+ folding_range => #{
+ from => {7, ?END_OF_LINE},
+ to => {16, ?END_OF_LINE}
+ }
+ },
+ id => {function_a, 1},
+ kind => function,
+ range => #{
+ from => {7, 1},
+ to => {7, 11}
+ }
+ }
+ }
+ ),
+ Item = #{
+ data => Data,
+ detail => <<"call_hierarchy_a [L7]">>,
+ kind => ?SYMBOLKIND_FUNCTION,
+ name => <<"function_a/1">>,
+ range =>
+ #{
+ 'end' => #{character => 10, line => 6},
+ start => #{character => 0, line => 6}
+ },
+ selectionRange =>
+ #{
+ 'end' => #{character => 10, line => 6},
+ start => #{character => 0, line => 6}
+ },
+ uri => UriA
+ },
+ ?assertEqual([Item], PrepareResult),
+ #{result := Result} = els_client:callhierarchy_incomingcalls(Item),
+ Calls = [
+ #{
+ from =>
+ #{
+ data =>
+ els_utils:base64_encode_term(
+ #{
+ poi =>
+ #{
+ data =>
+ #{
+ args => [{1, "Arg1"}],
+ wrapping_range =>
+ #{
+ from => {7, 1},
+ to => {14, 0}
+ },
+ folding_range =>
+ #{
+ from => {7, ?END_OF_LINE},
+ to => {13, ?END_OF_LINE}
+ }
+ },
+ id => {function_a, 1},
+ kind => function,
+ range => #{from => {7, 1}, to => {7, 11}}
+ }
+ }
+ ),
+ detail => <<"call_hierarchy_b [L11]">>,
+ kind => 12,
+ name => <<"function_a/1">>,
+ range =>
+ #{
+ 'end' => #{character => 29, line => 10},
+ start => #{character => 2, line => 10}
+ },
+ selectionRange =>
+ #{
+ 'end' => #{character => 29, line => 10},
+ start => #{character => 2, line => 10}
+ },
+ uri => UriB
+ },
+ fromRanges =>
+ [
+ #{
+ 'end' => #{character => 29, line => 10},
+ start => #{character => 2, line => 10}
+ }
+ ]
+ },
+ #{
+ from =>
+ #{
+ data =>
+ els_utils:base64_encode_term(
+ #{
+ poi =>
+ #{
+ data =>
+ #{
+ args => [{1, "Arg1"}],
+ wrapping_range =>
+ #{
+ from => {7, 1},
+ to => {17, 0}
+ },
+ folding_range =>
+ #{
+ from => {7, ?END_OF_LINE},
+ to => {16, ?END_OF_LINE}
+ }
+ },
+ id => {function_a, 1},
+ kind => function,
+ range => #{
+ from => {7, 1},
+ to => {7, 11}
+ }
+ }
+ }
+ ),
+ detail => <<"call_hierarchy_a [L16]">>,
+ kind => 12,
+ name => <<"function_a/1">>,
+ range =>
+ #{
+ 'end' => #{
+ character => 12,
+ line => 15
+ },
+ start => #{
+ character => 2,
+ line => 15
+ }
+ },
+ selectionRange =>
+ #{
+ 'end' => #{
+ character => 12,
+ line => 15
+ },
+ start => #{
+ character => 2,
+ line => 15
+ }
+ },
+ uri => UriA
+ },
+ fromRanges =>
+ [
+ #{
+ 'end' => #{character => 12, line => 15},
+ start => #{character => 2, line => 15}
+ }
+ ]
+ }
+ ],
+ [?assert(lists:member(Call, Result)) || Call <- Calls],
+ ?assertEqual(length(Calls), length(Result)).
-spec outgoing_calls(config()) -> ok.
outgoing_calls(Config) ->
- UriA = ?config(call_hierarchy_a_uri, Config),
- #{result := PrepareResult} =
- els_client:preparecallhierarchy(UriA, _Line = 9, _Char = 6),
- Data = els_utils:base64_encode_term(
- #{ poi =>
- #{ data => #{ args => [{1, "Arg1"}]
- , wrapping_range => #{ from => {7, 1}
- , to => {17, 0}
- }
- , folding_range => #{ from => {7, ?END_OF_LINE}
- , to => {16, ?END_OF_LINE}
- }
- }
- , id => {function_a, 1}
- , kind => function
- , range => #{ from => {7, 1}
- , to => {7, 11}
- }
- }
- }),
- Item = #{ data => Data
- , detail => <<"call_hierarchy_a [L7]">>
- , kind => ?SYMBOLKIND_FUNCTION
- , name => <<"function_a/1">>
- , range =>
- #{ 'end' => #{character => 10, line => 6}
- , start => #{character => 0, line => 6}}
- , selectionRange =>
- #{ 'end' => #{character => 10, line => 6}
- , start => #{character => 0, line => 6}}
- , uri => UriA},
- ?assertEqual([Item], PrepareResult),
- #{result := Result} = els_client:callhierarchy_outgoingcalls(Item),
- POIs = [els_utils:base64_decode_term(D) || #{to := #{data := D}} <- Result],
- ?assertEqual([#{ poi =>
- #{ data =>
- #{ args => [{1, "Arg1"}]
- , wrapping_range => #{ from => {7, 1}
- , to => {17, 0}
- }
- , folding_range => #{ from => {7, ?END_OF_LINE}
- , to => {16, ?END_OF_LINE}
- }}
- , id => {function_a, 1}
- , kind => function
- , range => #{ from => {7, 1}
- , to => {7, 11}
- }
- }}
- , #{ poi =>
- #{ data =>
- #{ args => []
- , wrapping_range => #{ from => {18, 1}
- , to => {20, 0}
- }
- , folding_range => #{ from => {18, ?END_OF_LINE}
- , to => {19, ?END_OF_LINE}
- }}
- , id => {function_b, 0}
- , kind => function
- , range => #{ from => {18, 1}
- , to => {18, 11}
- }
- }}
- , #{ poi =>
- #{ data =>
- #{ args => [{1, "Arg1"}]
- , wrapping_range => #{ from => {7, 1}
- , to => {14, 0}
- }
- , folding_range => #{ from => {7, ?END_OF_LINE}
- , to => {13, ?END_OF_LINE}
- }}
- , id => {function_a, 1}
- , kind => function
- , range => #{ from => {7, 1}
- , to => {7, 11}
- }
- }}
- , #{ poi =>
- #{ data =>
- #{ args => []
- , wrapping_range => #{ from => {18, 1}
- , to => {20, 0}
- }
- , folding_range => #{ from => {18, ?END_OF_LINE}
- , to => {19, ?END_OF_LINE}
- }}
- , id => {function_b, 0}
- , kind => function
- , range => #{ from => {18, 1}
- , to => {18, 11}
- }
- }}
- ]
- , POIs).
+ UriA = ?config(call_hierarchy_a_uri, Config),
+ #{result := PrepareResult} =
+ els_client:preparecallhierarchy(UriA, _Line = 9, _Char = 6),
+ Data = els_utils:base64_encode_term(
+ #{
+ poi =>
+ #{
+ data => #{
+ args => [{1, "Arg1"}],
+ wrapping_range => #{
+ from => {7, 1},
+ to => {17, 0}
+ },
+ folding_range => #{
+ from => {7, ?END_OF_LINE},
+ to => {16, ?END_OF_LINE}
+ }
+ },
+ id => {function_a, 1},
+ kind => function,
+ range => #{
+ from => {7, 1},
+ to => {7, 11}
+ }
+ }
+ }
+ ),
+ Item = #{
+ data => Data,
+ detail => <<"call_hierarchy_a [L7]">>,
+ kind => ?SYMBOLKIND_FUNCTION,
+ name => <<"function_a/1">>,
+ range =>
+ #{
+ 'end' => #{character => 10, line => 6},
+ start => #{character => 0, line => 6}
+ },
+ selectionRange =>
+ #{
+ 'end' => #{character => 10, line => 6},
+ start => #{character => 0, line => 6}
+ },
+ uri => UriA
+ },
+ ?assertEqual([Item], PrepareResult),
+ #{result := Result} = els_client:callhierarchy_outgoingcalls(Item),
+ POIs = [els_utils:base64_decode_term(D) || #{to := #{data := D}} <- Result],
+ ?assertEqual(
+ [
+ #{
+ poi =>
+ #{
+ data =>
+ #{
+ args => [{1, "Arg1"}],
+ wrapping_range => #{
+ from => {7, 1},
+ to => {17, 0}
+ },
+ folding_range => #{
+ from => {7, ?END_OF_LINE},
+ to => {16, ?END_OF_LINE}
+ }
+ },
+ id => {function_a, 1},
+ kind => function,
+ range => #{
+ from => {7, 1},
+ to => {7, 11}
+ }
+ }
+ },
+ #{
+ poi =>
+ #{
+ data =>
+ #{
+ args => [],
+ wrapping_range => #{
+ from => {18, 1},
+ to => {20, 0}
+ },
+ folding_range => #{
+ from => {18, ?END_OF_LINE},
+ to => {19, ?END_OF_LINE}
+ }
+ },
+ id => {function_b, 0},
+ kind => function,
+ range => #{
+ from => {18, 1},
+ to => {18, 11}
+ }
+ }
+ },
+ #{
+ poi =>
+ #{
+ data =>
+ #{
+ args => [{1, "Arg1"}],
+ wrapping_range => #{
+ from => {7, 1},
+ to => {14, 0}
+ },
+ folding_range => #{
+ from => {7, ?END_OF_LINE},
+ to => {13, ?END_OF_LINE}
+ }
+ },
+ id => {function_a, 1},
+ kind => function,
+ range => #{
+ from => {7, 1},
+ to => {7, 11}
+ }
+ }
+ },
+ #{
+ poi =>
+ #{
+ data =>
+ #{
+ args => [],
+ wrapping_range => #{
+ from => {18, 1},
+ to => {20, 0}
+ },
+ folding_range => #{
+ from => {18, ?END_OF_LINE},
+ to => {19, ?END_OF_LINE}
+ }
+ },
+ id => {function_b, 0},
+ kind => function,
+ range => #{
+ from => {18, 1},
+ to => {18, 11}
+ }
+ }
+ }
+ ],
+ POIs
+ ).
diff --git a/apps/els_lsp/test/els_code_action_SUITE.erl b/apps/els_lsp/test/els_code_action_SUITE.erl
index 403ac4aaa..6a36ee76a 100644
--- a/apps/els_lsp/test/els_code_action_SUITE.erl
+++ b/apps/els_lsp/test/els_code_action_SUITE.erl
@@ -1,23 +1,25 @@
-module(els_code_action_SUITE).
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ add_underscore_to_unused_var/1
- , export_unused_function/1
- , suggest_variable/1
- , fix_module_name/1
- , remove_unused_macro/1
- , remove_unused_import/1
- , create_undefined_function/1
- ]).
+-export([
+ add_underscore_to_unused_var/1,
+ export_unused_function/1,
+ suggest_variable/1,
+ fix_module_name/1,
+ remove_unused_macro/1,
+ remove_unused_import/1,
+ create_undefined_function/1
+]).
%%==============================================================================
%% Includes
@@ -35,27 +37,27 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(TestCase, Config) ->
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Const
%%==============================================================================
@@ -66,195 +68,286 @@ end_per_testcase(TestCase, Config) ->
%%==============================================================================
-spec add_underscore_to_unused_var(config()) -> ok.
add_underscore_to_unused_var(Config) ->
- Uri = ?config(code_action_uri, Config),
- Range = els_protocol:range(#{from => {?COMMENTS_LINES + 6, 3}
- , to => {?COMMENTS_LINES + 6, 4}}),
- Diag = #{ message => <<"variable 'A' is unused">>
- , range => Range
- , severity => 2
- , source => <<"Compiler">>
- },
- #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
- Expected =
- [ #{ edit => #{changes =>
- #{ binary_to_atom(Uri, utf8) =>
- [ #{ range => Range
- , newText => <<"_A">>
- }]
- }}
- , kind => <<"quickfix">>
- , title => <<"Add '_' to 'A'">>
- }
- ],
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(code_action_uri, Config),
+ Range = els_protocol:range(#{
+ from => {?COMMENTS_LINES + 6, 3},
+ to => {?COMMENTS_LINES + 6, 4}
+ }),
+ Diag = #{
+ message => <<"variable 'A' is unused">>,
+ range => Range,
+ severity => 2,
+ source => <<"Compiler">>
+ },
+ #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
+ Expected =
+ [
+ #{
+ edit => #{
+ changes =>
+ #{
+ binary_to_atom(Uri, utf8) =>
+ [
+ #{
+ range => Range,
+ newText => <<"_A">>
+ }
+ ]
+ }
+ },
+ kind => <<"quickfix">>,
+ title => <<"Add '_' to 'A'">>
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
-spec export_unused_function(config()) -> ok.
export_unused_function(Config) ->
- Uri = ?config(code_action_uri, Config),
- Range = els_protocol:range(#{from => {?COMMENTS_LINES + 12, 1}
- , to => {?COMMENTS_LINES + 12, 10}}),
- Diag = #{ message => <<"function function_c/0 is unused">>
- , range => Range
- , severity => 2
- , source => <<"Compiler">>
- },
- #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
- Expected =
- [ #{ edit => #{changes =>
- #{ binary_to_atom(Uri, utf8) =>
- [ #{ range =>
- #{'end' => #{ character => 0
- , line => ?COMMENTS_LINES + 3},
- start => #{character => 0
- , line => ?COMMENTS_LINES + 3}}
- , newText => <<"-export([function_c/0]).\n">>
- }
- ]}
- }
- , kind => <<"quickfix">>
- , title => <<"Export function_c/0">>
- }
- ],
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(code_action_uri, Config),
+ Range = els_protocol:range(#{
+ from => {?COMMENTS_LINES + 12, 1},
+ to => {?COMMENTS_LINES + 12, 10}
+ }),
+ Diag = #{
+ message => <<"function function_c/0 is unused">>,
+ range => Range,
+ severity => 2,
+ source => <<"Compiler">>
+ },
+ #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
+ Expected =
+ [
+ #{
+ edit => #{
+ changes =>
+ #{
+ binary_to_atom(Uri, utf8) =>
+ [
+ #{
+ range =>
+ #{
+ 'end' => #{
+ character => 0,
+ line => ?COMMENTS_LINES + 3
+ },
+ start => #{
+ character => 0,
+ line => ?COMMENTS_LINES + 3
+ }
+ },
+ newText => <<"-export([function_c/0]).\n">>
+ }
+ ]
+ }
+ },
+ kind => <<"quickfix">>,
+ title => <<"Export function_c/0">>
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
-spec suggest_variable(config()) -> ok.
suggest_variable(Config) ->
- Uri = ?config(code_action_uri, Config),
- Range = els_protocol:range(#{from => {?COMMENTS_LINES + 15, 9}
- , to => {?COMMENTS_LINES + 15, 13}}),
- Diag = #{ message => <<"variable 'Barf' is unbound">>
- , range => Range
- , severity => 3
- , source => <<"Compiler">>
- },
- #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
- Expected =
- [ #{ edit => #{changes =>
- #{ binary_to_atom(Uri, utf8) =>
- [#{ range => Range
- , newText => <<"Bar">>
- }]
- }}
- , kind => <<"quickfix">>
- , title => <<"Did you mean 'Bar'?">>
- }
- ],
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(code_action_uri, Config),
+ Range = els_protocol:range(#{
+ from => {?COMMENTS_LINES + 15, 9},
+ to => {?COMMENTS_LINES + 15, 13}
+ }),
+ Diag = #{
+ message => <<"variable 'Barf' is unbound">>,
+ range => Range,
+ severity => 3,
+ source => <<"Compiler">>
+ },
+ #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
+ Expected =
+ [
+ #{
+ edit => #{
+ changes =>
+ #{
+ binary_to_atom(Uri, utf8) =>
+ [
+ #{
+ range => Range,
+ newText => <<"Bar">>
+ }
+ ]
+ }
+ },
+ kind => <<"quickfix">>,
+ title => <<"Did you mean 'Bar'?">>
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
-spec fix_module_name(config()) -> ok.
fix_module_name(Config) ->
- Uri = ?config(code_action_uri, Config),
- Range = els_protocol:range(#{from => {?COMMENTS_LINES + 1, 9}
- , to => {?COMMENTS_LINES + 1, 25}}),
- Diag = #{ message => <<"Module name 'code_action_oops' does not "
- "match file name 'code_action'">>
- , range => Range
- , severity => 3
- , source => <<"Compiler">>
- },
- #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
- Expected =
- [ #{ edit => #{changes =>
- #{ binary_to_atom(Uri, utf8) =>
- [#{ range => Range
- , newText => <<"code_action">>
- }]
- }}
- , kind => <<"quickfix">>
- , title => <<"Change to -module(code_action).">>
- }
- ],
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(code_action_uri, Config),
+ Range = els_protocol:range(#{
+ from => {?COMMENTS_LINES + 1, 9},
+ to => {?COMMENTS_LINES + 1, 25}
+ }),
+ Diag = #{
+ message => <<
+ "Module name 'code_action_oops' does not "
+ "match file name 'code_action'"
+ >>,
+ range => Range,
+ severity => 3,
+ source => <<"Compiler">>
+ },
+ #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
+ Expected =
+ [
+ #{
+ edit => #{
+ changes =>
+ #{
+ binary_to_atom(Uri, utf8) =>
+ [
+ #{
+ range => Range,
+ newText => <<"code_action">>
+ }
+ ]
+ }
+ },
+ kind => <<"quickfix">>,
+ title => <<"Change to -module(code_action).">>
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
-spec remove_unused_macro(config()) -> ok.
remove_unused_macro(Config) ->
- Uri = ?config(code_action_uri, Config),
- Range = els_protocol:range(#{from => {?COMMENTS_LINES + 17, 9}
- , to => {?COMMENTS_LINES + 17, 15}}),
- LineRange = els_range:line(#{from => {?COMMENTS_LINES + 17, 9}
- , to => {?COMMENTS_LINES + 17, 15}}),
- Diag = #{ message => <<"Unused macro: TIMEOUT">>
- , range => Range
- , severity => 2
- , source => <<"UnusedMacros">>
- },
- #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
- Expected =
- [ #{ edit => #{changes =>
- #{ binary_to_atom(Uri, utf8) =>
- [#{ range => els_protocol:range(LineRange)
- , newText => <<"">>
- }]
- }}
- , kind => <<"quickfix">>
- , title => <<"Remove unused macro TIMEOUT.">>
- }
- ],
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(code_action_uri, Config),
+ Range = els_protocol:range(#{
+ from => {?COMMENTS_LINES + 17, 9},
+ to => {?COMMENTS_LINES + 17, 15}
+ }),
+ LineRange = els_range:line(#{
+ from => {?COMMENTS_LINES + 17, 9},
+ to => {?COMMENTS_LINES + 17, 15}
+ }),
+ Diag = #{
+ message => <<"Unused macro: TIMEOUT">>,
+ range => Range,
+ severity => 2,
+ source => <<"UnusedMacros">>
+ },
+ #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
+ Expected =
+ [
+ #{
+ edit => #{
+ changes =>
+ #{
+ binary_to_atom(Uri, utf8) =>
+ [
+ #{
+ range => els_protocol:range(LineRange),
+ newText => <<"">>
+ }
+ ]
+ }
+ },
+ kind => <<"quickfix">>,
+ title => <<"Remove unused macro TIMEOUT.">>
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
-spec remove_unused_import(config()) -> ok.
remove_unused_import(Config) ->
- Uri = ?config(code_action_uri, Config),
- Range = els_protocol:range(#{from => {?COMMENTS_LINES + 19, 15}
- , to => {?COMMENTS_LINES + 19, 40}}),
- LineRange = els_range:line(#{from => {?COMMENTS_LINES + 19, 15}
- , to => {?COMMENTS_LINES + 19, 40}}),
- {ok, FileName} = els_utils:find_header(
- els_utils:filename_to_atom("stdlib/include/assert.hrl")),
- Diag = #{ message => <<"Unused file: assert.hrl">>
- , range => Range
- , severity => 2
- , source => <<"UnusedIncludes">>
- , data => FileName
- },
- #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
- Expected =
- [ #{ edit => #{changes =>
- #{ binary_to_atom(Uri, utf8) =>
- [#{ range => els_protocol:range(LineRange)
- , newText => <<>>
- }]
- }}
- , kind => <<"quickfix">>
- , title => <<"Remove unused -include_lib(assert.hrl).">>
- }
- ],
- ?assertEqual(Expected, Result),
- ok.
-
+ Uri = ?config(code_action_uri, Config),
+ Range = els_protocol:range(#{
+ from => {?COMMENTS_LINES + 19, 15},
+ to => {?COMMENTS_LINES + 19, 40}
+ }),
+ LineRange = els_range:line(#{
+ from => {?COMMENTS_LINES + 19, 15},
+ to => {?COMMENTS_LINES + 19, 40}
+ }),
+ {ok, FileName} = els_utils:find_header(
+ els_utils:filename_to_atom("stdlib/include/assert.hrl")
+ ),
+ Diag = #{
+ message => <<"Unused file: assert.hrl">>,
+ range => Range,
+ severity => 2,
+ source => <<"UnusedIncludes">>,
+ data => FileName
+ },
+ #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
+ Expected =
+ [
+ #{
+ edit => #{
+ changes =>
+ #{
+ binary_to_atom(Uri, utf8) =>
+ [
+ #{
+ range => els_protocol:range(LineRange),
+ newText => <<>>
+ }
+ ]
+ }
+ },
+ kind => <<"quickfix">>,
+ title => <<"Remove unused -include_lib(assert.hrl).">>
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
-spec create_undefined_function((config())) -> ok.
create_undefined_function(Config) ->
- Uri = ?config(code_action_uri, Config),
- Range = els_protocol:range(#{from => {?COMMENTS_LINES + 23, 9}
- , to => {?COMMENTS_LINES + 23, 39}}),
- LineRange = els_range:line(#{from => {?COMMENTS_LINES + 23, 9}
- , to => {?COMMENTS_LINES + 23, 39}}),
- {ok, FileName} = els_utils:find_header(
- els_utils:filename_to_atom("stdlib/include/assert.hrl")),
- Diag = #{ message => <<"function e/0 undefined">>
- , range => Range
- , severity => 2
- , source => <<"">>
- , data => FileName
- },
- #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
- Expected =
- [ #{ edit => #{changes =>
- #{ binary_to_atom(Uri, utf8) =>
- [#{ range => els_protocol:range(LineRange)
- , newText =>
- <<"-spec e() -> ok. \n e() -> \n \t ok.">>
- }]
- }}
- , kind => <<"quickfix">>
- , title => <<"Add the undefined function e/0">>
- }
- ],
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(code_action_uri, Config),
+ Range = els_protocol:range(#{
+ from => {?COMMENTS_LINES + 23, 9},
+ to => {?COMMENTS_LINES + 23, 39}
+ }),
+ LineRange = els_range:line(#{
+ from => {?COMMENTS_LINES + 23, 9},
+ to => {?COMMENTS_LINES + 23, 39}
+ }),
+ {ok, FileName} = els_utils:find_header(
+ els_utils:filename_to_atom("stdlib/include/assert.hrl")
+ ),
+ Diag = #{
+ message => <<"function e/0 undefined">>,
+ range => Range,
+ severity => 2,
+ source => <<"">>,
+ data => FileName
+ },
+ #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
+ Expected =
+ [
+ #{
+ edit => #{
+ changes =>
+ #{
+ binary_to_atom(Uri, utf8) =>
+ [
+ #{
+ range => els_protocol:range(LineRange),
+ newText =>
+ <<"-spec e() -> ok. \n e() -> \n \t ok.">>
+ }
+ ]
+ }
+ },
+ kind => <<"quickfix">>,
+ title => <<"Add the undefined function e/0">>
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
diff --git a/apps/els_lsp/test/els_code_lens_SUITE.erl b/apps/els_lsp/test/els_code_lens_SUITE.erl
index 8c4f5b461..7090dcd33 100644
--- a/apps/els_lsp/test/els_code_lens_SUITE.erl
+++ b/apps/els_lsp/test/els_code_lens_SUITE.erl
@@ -1,21 +1,23 @@
-module(els_code_lens_SUITE).
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ default_lenses/1
- , server_info/1
- , ct_run_test/1
- , show_behaviour_usages/1
- , function_references/1
- ]).
+-export([
+ default_lenses/1,
+ server_info/1,
+ ct_run_test/1,
+ show_behaviour_usages/1,
+ function_references/1
+]).
%%==============================================================================
%% Includes
@@ -33,47 +35,47 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(server_info, Config) ->
- meck:new(els_code_lens_server_info, [passthrough, no_link]),
- meck:expect(els_code_lens_server_info, is_default, 0, true),
- %% Let's disable the suggest_spec lens to avoid noise
- meck:new(els_code_lens_suggest_spec, [passthrough, no_link]),
- meck:expect(els_code_lens_suggest_spec, is_default, 0, false),
- els_test_utils:init_per_testcase(server_info, Config);
+ meck:new(els_code_lens_server_info, [passthrough, no_link]),
+ meck:expect(els_code_lens_server_info, is_default, 0, true),
+ %% Let's disable the suggest_spec lens to avoid noise
+ meck:new(els_code_lens_suggest_spec, [passthrough, no_link]),
+ meck:expect(els_code_lens_suggest_spec, is_default, 0, false),
+ els_test_utils:init_per_testcase(server_info, Config);
init_per_testcase(ct_run_test, Config) ->
- meck:new(els_code_lens_ct_run_test, [passthrough, no_link]),
- meck:expect(els_code_lens_ct_run_test, is_default, 0, true),
- els_test_utils:init_per_testcase(server_info, Config);
+ meck:new(els_code_lens_ct_run_test, [passthrough, no_link]),
+ meck:expect(els_code_lens_ct_run_test, is_default, 0, true),
+ els_test_utils:init_per_testcase(server_info, Config);
init_per_testcase(TestCase, Config) ->
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(server_info, Config) ->
- els_test_utils:end_per_testcase(server_info, Config),
- meck:unload(els_code_lens_server_info),
- meck:unload(els_code_lens_suggest_spec),
- ok;
+ els_test_utils:end_per_testcase(server_info, Config),
+ meck:unload(els_code_lens_server_info),
+ meck:unload(els_code_lens_suggest_spec),
+ ok;
end_per_testcase(ct_run_test, Config) ->
- els_test_utils:end_per_testcase(ct_run_test, Config),
- meck:unload(els_code_lens_ct_run_test),
- ok;
+ els_test_utils:end_per_testcase(ct_run_test, Config),
+ meck:unload(els_code_lens_ct_run_test),
+ ok;
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Testcases
@@ -81,106 +83,147 @@ end_per_testcase(TestCase, Config) ->
-spec default_lenses(config()) -> ok.
default_lenses(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Result} = els_client:document_codelens(Uri),
- Commands = [els_command:without_prefix(Command) ||
- #{command := #{command := Command }} <- Result],
- ?assertEqual([ <<"function-references">>
- , <<"suggest-spec">>
- ]
- , lists:usort(Commands)),
- ?assertEqual(40, length(Commands)),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Result} = els_client:document_codelens(Uri),
+ Commands = [
+ els_command:without_prefix(Command)
+ || #{command := #{command := Command}} <- Result
+ ],
+ ?assertEqual(
+ [
+ <<"function-references">>,
+ <<"suggest-spec">>
+ ],
+ lists:usort(Commands)
+ ),
+ ?assertEqual(40, length(Commands)),
+ ok.
-spec server_info(config()) -> ok.
server_info(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Result} = els_client:document_codelens(Uri),
- PrefixedCommand = els_command:with_prefix(<<"server-info">>),
- FilteredResult = [L || #{ command := #{ command := Command }} = L
- <- Result, Command =:= PrefixedCommand],
- Title = <<"Erlang LS (in code_navigation) info">>,
- Expected =
- [ #{ command => #{ arguments => []
- , command => PrefixedCommand
- , title => Title
- }
- , data => []
- , range =>
- #{'end' => #{character => 0, line => 1},
- start => #{character => 0, line => 0}}
- }
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Result} = els_client:document_codelens(Uri),
+ PrefixedCommand = els_command:with_prefix(<<"server-info">>),
+ FilteredResult = [
+ L
+ || #{command := #{command := Command}} = L <-
+ Result,
+ Command =:= PrefixedCommand
],
- ?assertEqual(Expected, FilteredResult),
- ok.
+ Title = <<"Erlang LS (in code_navigation) info">>,
+ Expected =
+ [
+ #{
+ command => #{
+ arguments => [],
+ command => PrefixedCommand,
+ title => Title
+ },
+ data => [],
+ range =>
+ #{
+ 'end' => #{character => 0, line => 1},
+ start => #{character => 0, line => 0}
+ }
+ }
+ ],
+ ?assertEqual(Expected, FilteredResult),
+ ok.
-spec ct_run_test(config()) -> ok.
ct_run_test(Config) ->
- Uri = ?config(sample_SUITE_uri, Config),
- PrefixedCommand = els_command:with_prefix(<<"ct-run-test">>),
- #{result := Result} = els_client:document_codelens(Uri),
- FilteredResult = [L || #{ command := #{ command := Command }} = L
- <- Result, Command =:= PrefixedCommand],
- Expected = [ #{ command => #{ arguments => [ #{ arity => 1
- , function => <<"one">>
- , line => 58
- , module => <<"sample_SUITE">>
- , uri => Uri
- }]
- , command => PrefixedCommand
- , title => <<"Run test">>
- }
- , data => []
- , range => #{ 'end' => #{ character => 3
- , line => 57
- }
- , start => #{ character => 0
- , line => 57
- }}}],
- ?assertEqual(Expected, FilteredResult),
- ok.
+ Uri = ?config(sample_SUITE_uri, Config),
+ PrefixedCommand = els_command:with_prefix(<<"ct-run-test">>),
+ #{result := Result} = els_client:document_codelens(Uri),
+ FilteredResult = [
+ L
+ || #{command := #{command := Command}} = L <-
+ Result,
+ Command =:= PrefixedCommand
+ ],
+ Expected = [
+ #{
+ command => #{
+ arguments => [
+ #{
+ arity => 1,
+ function => <<"one">>,
+ line => 58,
+ module => <<"sample_SUITE">>,
+ uri => Uri
+ }
+ ],
+ command => PrefixedCommand,
+ title => <<"Run test">>
+ },
+ data => [],
+ range => #{
+ 'end' => #{
+ character => 3,
+ line => 57
+ },
+ start => #{
+ character => 0,
+ line => 57
+ }
+ }
+ }
+ ],
+ ?assertEqual(Expected, FilteredResult),
+ ok.
-spec show_behaviour_usages(config()) -> ok.
show_behaviour_usages(Config) ->
- Uri = ?config(behaviour_a_uri, Config),
- PrefixedCommand = els_command:with_prefix(<<"show-behaviour-usages">>),
- #{result := Result} = els_client:document_codelens(Uri),
- Expected = [#{ command =>
- #{ arguments => []
- , command => PrefixedCommand
- , title => <<"Behaviour used in 1 place(s)">>
- }
- , data => []
- , range =>
- #{ 'end' => #{character => 19, line => 0}
- , start => #{character => 8, line => 0}
- }}],
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(behaviour_a_uri, Config),
+ PrefixedCommand = els_command:with_prefix(<<"show-behaviour-usages">>),
+ #{result := Result} = els_client:document_codelens(Uri),
+ Expected = [
+ #{
+ command =>
+ #{
+ arguments => [],
+ command => PrefixedCommand,
+ title => <<"Behaviour used in 1 place(s)">>
+ },
+ data => [],
+ range =>
+ #{
+ 'end' => #{character => 19, line => 0},
+ start => #{character => 8, line => 0}
+ }
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
-spec function_references(config()) -> ok.
function_references(Config) ->
- Uri = ?config(code_lens_function_references_uri, Config),
- #{result := Result} = els_client:document_codelens(Uri),
- Expected = [ lens(5, 0)
- , lens(10, 1)
- , lens(14, 2)
- ],
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(code_lens_function_references_uri, Config),
+ #{result := Result} = els_client:document_codelens(Uri),
+ Expected = [
+ lens(5, 0),
+ lens(10, 1),
+ lens(14, 2)
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
-spec lens(number(), number()) -> map().
lens(Line, Usages) ->
- Title = unicode:characters_to_binary(
- io_lib:format("Used ~p times", [Usages])),
- #{ command =>
- #{ arguments => []
- , command => els_command:with_prefix(<<"function-references">>)
- , title => Title
- }
- , data => []
- , range =>
- #{ 'end' => #{character => 1, line => Line}
- , start => #{character => 0, line => Line}
- }
- }.
+ Title = unicode:characters_to_binary(
+ io_lib:format("Used ~p times", [Usages])
+ ),
+ #{
+ command =>
+ #{
+ arguments => [],
+ command => els_command:with_prefix(<<"function-references">>),
+ title => Title
+ },
+ data => [],
+ range =>
+ #{
+ 'end' => #{character => 1, line => Line},
+ start => #{character => 0, line => Line}
+ }
+ }.
diff --git a/apps/els_lsp/test/els_completion_SUITE.erl b/apps/els_lsp/test/els_completion_SUITE.erl
index 6754b4ecf..cd49477ec 100644
--- a/apps/els_lsp/test/els_completion_SUITE.erl
+++ b/apps/els_lsp/test/els_completion_SUITE.erl
@@ -1,51 +1,53 @@
-module(els_completion_SUITE).
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ attributes/1
- , attribute_behaviour/1
- , attribute_include/1
- , attribute_include_lib/1
- , attribute_export/1
- , attribute_export_incomplete/1
- , attribute_export_type/1
- , default_completions/1
- , empty_completions/1
- , exported_functions/1
- , exported_functions_arity/1
- , exported_types/1
- , functions_arity/1
- , functions_export_list/1
- , handle_empty_lines/1
- , handle_colon_inside_string/1
- , macros/1
- , only_exported_functions_after_colon/1
- , records/1
- , record_fields/1
- , types/1
- , types_export_list/1
- , variables/1
- , remote_fun/1
- , snippets/1
- , resolve_application_local/1
- , resolve_opaque_application_local/1
- , resolve_application_unexported_local/1
- , resolve_application_remote_self/1
- , resolve_application_remote_external/1
- , resolve_application_remote_otp/1
- , resolve_type_application_local/1
- , resolve_opaque_application_remote_self/1
- , resolve_type_application_remote_external/1
- , resolve_opaque_application_remote_external/1
- , resolve_type_application_remote_otp/1
- ]).
+-export([
+ attributes/1,
+ attribute_behaviour/1,
+ attribute_include/1,
+ attribute_include_lib/1,
+ attribute_export/1,
+ attribute_export_incomplete/1,
+ attribute_export_type/1,
+ default_completions/1,
+ empty_completions/1,
+ exported_functions/1,
+ exported_functions_arity/1,
+ exported_types/1,
+ functions_arity/1,
+ functions_export_list/1,
+ handle_empty_lines/1,
+ handle_colon_inside_string/1,
+ macros/1,
+ only_exported_functions_after_colon/1,
+ records/1,
+ record_fields/1,
+ types/1,
+ types_export_list/1,
+ variables/1,
+ remote_fun/1,
+ snippets/1,
+ resolve_application_local/1,
+ resolve_opaque_application_local/1,
+ resolve_application_unexported_local/1,
+ resolve_application_remote_self/1,
+ resolve_application_remote_external/1,
+ resolve_application_remote_otp/1,
+ resolve_type_application_local/1,
+ resolve_opaque_application_remote_self/1,
+ resolve_type_application_remote_external/1,
+ resolve_opaque_application_remote_external/1,
+ resolve_type_application_remote_otp/1
+]).
%%==============================================================================
%% Includes
@@ -64,1213 +66,1477 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(TestCase, Config) ->
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Testcases
%%==============================================================================
-spec attributes(config()) -> ok.
attributes(Config) ->
- Uri = ?config(completion_attributes_uri, Config),
- TriggerKindChar = ?COMPLETION_TRIGGER_KIND_CHARACTER,
- Expected = [ #{ insertText => <<"behaviour(${1:Behaviour}).">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-behaviour().">>
- }
- , #{ insertText => <<"define(${1:MACRO}, ${2:Value}).">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-define().">>
- }
- , #{ insertText => <<"export([${1:}]).">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-export().">>
- }
- , #{ insertText => <<"export_type([${1:}]).">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-export_type().">>
- }
- , #{ insertText => <<"if(${1:Pred}).\n${2:}\n-endif.">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-if().">>
- }
- , #{ insertText => <<"ifdef(${1:VAR}).\n${2:}\n-endif.">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-ifdef().">>
- }
- , #{ insertText => <<"ifndef(${1:VAR}).\n${2:}\n-endif.">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-ifndef().">>
- }
- , #{ insertText => <<"include(${1:}).">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-include().">>
- }
- , #{ insertText => <<"include_lib(${1:}).">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-include_lib().">>
- }
- , #{ insertText => <<"opaque ${1:name}() :: ${2:definition}.">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-opaque name() :: definition.">>
- }
- , #{ insertText => <<"record(${1:name}, {${2:field} = ${3:Value} "
- ":: ${4:Type}()}).">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-record().">>
- }
- , #{ insertText => <<"type ${1:name}() :: ${2:definition}.">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-type name() :: definition.">>
- }
- , #{ insertText => <<"dialyzer(${1:}).">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-dialyzer().">>
- }
- , #{ insertText => <<"compile(${1:}).">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-compile().">>
- }
- , #{ insertText => <<"import(${1:Module}, [${2:}]).">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-import().">>
- }
- , #{ insertText =>
- <<"callback ${1:name}(${2:Args}) -> ${3:return()}.">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-callback name(Args) -> return().">>
- }
- , #{ insertText => <<"on_load(${1:Function}).">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-on_load().">>
- }
- , #{ insertText => <<"vsn(${1:Version}).">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_SNIPPET
- , label => <<"-vsn(Version).">>}
- ],
- #{result := Completions} =
- els_client:completion(Uri, 5, 2, TriggerKindChar, <<"-">>),
- ?assertEqual([], Completions -- Expected),
- ?assertEqual([], Expected -- Completions),
- ok.
+ Uri = ?config(completion_attributes_uri, Config),
+ TriggerKindChar = ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ Expected = [
+ #{
+ insertText => <<"behaviour(${1:Behaviour}).">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-behaviour().">>
+ },
+ #{
+ insertText => <<"define(${1:MACRO}, ${2:Value}).">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-define().">>
+ },
+ #{
+ insertText => <<"export([${1:}]).">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-export().">>
+ },
+ #{
+ insertText => <<"export_type([${1:}]).">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-export_type().">>
+ },
+ #{
+ insertText => <<"if(${1:Pred}).\n${2:}\n-endif.">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-if().">>
+ },
+ #{
+ insertText => <<"ifdef(${1:VAR}).\n${2:}\n-endif.">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-ifdef().">>
+ },
+ #{
+ insertText => <<"ifndef(${1:VAR}).\n${2:}\n-endif.">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-ifndef().">>
+ },
+ #{
+ insertText => <<"include(${1:}).">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-include().">>
+ },
+ #{
+ insertText => <<"include_lib(${1:}).">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-include_lib().">>
+ },
+ #{
+ insertText => <<"opaque ${1:name}() :: ${2:definition}.">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-opaque name() :: definition.">>
+ },
+ #{
+ insertText => <<
+ "record(${1:name}, {${2:field} = ${3:Value} "
+ ":: ${4:Type}()})."
+ >>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-record().">>
+ },
+ #{
+ insertText => <<"type ${1:name}() :: ${2:definition}.">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-type name() :: definition.">>
+ },
+ #{
+ insertText => <<"dialyzer(${1:}).">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-dialyzer().">>
+ },
+ #{
+ insertText => <<"compile(${1:}).">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-compile().">>
+ },
+ #{
+ insertText => <<"import(${1:Module}, [${2:}]).">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-import().">>
+ },
+ #{
+ insertText =>
+ <<"callback ${1:name}(${2:Args}) -> ${3:return()}.">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-callback name(Args) -> return().">>
+ },
+ #{
+ insertText => <<"on_load(${1:Function}).">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-on_load().">>
+ },
+ #{
+ insertText => <<"vsn(${1:Version}).">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-vsn(Version).">>
+ }
+ ],
+ #{result := Completions} =
+ els_client:completion(Uri, 5, 2, TriggerKindChar, <<"-">>),
+ ?assertEqual([], Completions -- Expected),
+ ?assertEqual([], Expected -- Completions),
+ ok.
-spec attribute_behaviour(config()) -> ok.
attribute_behaviour(Config) ->
- Uri = ?config(completion_attributes_uri, Config),
- TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_INVOKED,
- Expected = [ #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_MODULE
- , label => <<"gen_event">>}
- , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_MODULE
- , label => <<"gen_server">>}
- , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_MODULE
- , label => <<"gen_statem">>}
- , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_MODULE
- , label => <<"supervisor">>}
- , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_MODULE
- , label => <<"behaviour_a">>}
- ],
- #{result := Completions} =
- els_client:completion(Uri, 2, 12, TriggerKindInvoked, <<"">>),
- [?assert(lists:member(E, Completions)) || E <- Expected],
- ok.
+ Uri = ?config(completion_attributes_uri, Config),
+ TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ Expected = [
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_MODULE,
+ label => <<"gen_event">>
+ },
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_MODULE,
+ label => <<"gen_server">>
+ },
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_MODULE,
+ label => <<"gen_statem">>
+ },
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_MODULE,
+ label => <<"supervisor">>
+ },
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_MODULE,
+ label => <<"behaviour_a">>
+ }
+ ],
+ #{result := Completions} =
+ els_client:completion(Uri, 2, 12, TriggerKindInvoked, <<"">>),
+ [?assert(lists:member(E, Completions)) || E <- Expected],
+ ok.
-spec attribute_include(config()) -> ok.
attribute_include(Config) ->
- Uri = ?config(completion_attributes_uri, Config),
- TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_CHARACTER,
- Expected = [#{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_FILE
- , label => <<"code_navigation.hrl">>
- }
- ],
- #{result := Completions} =
- els_client:completion(Uri, 3, 11, TriggerKindInvoked, <<"\"">>),
- [?assert(lists:member(E, Completions)) || E <- Expected],
- ok.
+ Uri = ?config(completion_attributes_uri, Config),
+ TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ Expected = [
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_FILE,
+ label => <<"code_navigation.hrl">>
+ }
+ ],
+ #{result := Completions} =
+ els_client:completion(Uri, 3, 11, TriggerKindInvoked, <<"\"">>),
+ [?assert(lists:member(E, Completions)) || E <- Expected],
+ ok.
-spec attribute_include_lib(config()) -> ok.
attribute_include_lib(Config) ->
- Uri = ?config(completion_attributes_uri, Config),
- TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_CHARACTER,
- Expected = [ #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_FILE
- , label => <<"code_navigation/include/rename.hrl">>
- }
- , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_FILE
- , label => <<"code_navigation/include/code_navigation.hrl">>
- }
- , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_FILE
- , label => <<"code_navigation/include/diagnostics.hrl">>
- }
- ],
- #{result := Completions} =
- els_client:completion(Uri, 4, 15, TriggerKindInvoked, <<"\"">>),
- [?assert(lists:member(E, Completions)) || E <- Expected],
- ok.
+ Uri = ?config(completion_attributes_uri, Config),
+ TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ Expected = [
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_FILE,
+ label => <<"code_navigation/include/rename.hrl">>
+ },
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_FILE,
+ label => <<"code_navigation/include/code_navigation.hrl">>
+ },
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_FILE,
+ label => <<"code_navigation/include/diagnostics.hrl">>
+ }
+ ],
+ #{result := Completions} =
+ els_client:completion(Uri, 4, 15, TriggerKindInvoked, <<"\"">>),
+ [?assert(lists:member(E, Completions)) || E <- Expected],
+ ok.
-spec attribute_export(config()) -> ok.
attribute_export(Config) ->
- Uri = ?config(completion_attributes_uri, Config),
- TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_INVOKED,
- Expected = [#{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , label => <<"unexported_function/0">>
- , data =>
- #{ arity => 0
- , function => <<"unexported_function">>
- , module => <<"completion_attributes">>
- }
- }
- ],
- NotExpected = [#{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , label => <<"exported_function/0">>
- , data =>
- #{ arity => 0
- , function => <<"exported_function">>
- , module => <<"completion_attributes">>
- }
- }
- ],
- #{result := Completions} =
- els_client:completion(Uri, 5, 10, TriggerKindInvoked, <<"">>),
- [?assert(lists:member(E, Completions)) || E <- Expected],
- [?assertNot(lists:member(E, Completions)) || E <- NotExpected],
- ok.
+ Uri = ?config(completion_attributes_uri, Config),
+ TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ Expected = [
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ label => <<"unexported_function/0">>,
+ data =>
+ #{
+ arity => 0,
+ function => <<"unexported_function">>,
+ module => <<"completion_attributes">>
+ }
+ }
+ ],
+ NotExpected = [
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ label => <<"exported_function/0">>,
+ data =>
+ #{
+ arity => 0,
+ function => <<"exported_function">>,
+ module => <<"completion_attributes">>
+ }
+ }
+ ],
+ #{result := Completions} =
+ els_client:completion(Uri, 5, 10, TriggerKindInvoked, <<"">>),
+ [?assert(lists:member(E, Completions)) || E <- Expected],
+ [?assertNot(lists:member(E, Completions)) || E <- NotExpected],
+ ok.
-spec attribute_export_incomplete(config()) -> ok.
attribute_export_incomplete(Config) ->
- Uri = ?config(completion_incomplete_uri, Config),
- TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_INVOKED,
- Expected = [#{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , label => <<"function_unexported/0">>
- , data =>
- #{ arity => 0
- , function => <<"function_unexported">>
- , module => <<"completion_incomplete">>
- }
- }
- ],
- NotExpected = [#{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , label => <<"function_exported/0">>
- , data =>
- #{ arity => 0
- , function => <<"function_exported">>
- , module => <<"completion_incomplete">>
- }
- }
- ],
- #{result := Completions} =
- els_client:completion(Uri, 4, 18, TriggerKindInvoked, <<"">>),
- [?assert(lists:member(E, Completions)) || E <- Expected],
- [?assertNot(lists:member(E, Completions)) || E <- NotExpected],
- ok.
+ Uri = ?config(completion_incomplete_uri, Config),
+ TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ Expected = [
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ label => <<"function_unexported/0">>,
+ data =>
+ #{
+ arity => 0,
+ function => <<"function_unexported">>,
+ module => <<"completion_incomplete">>
+ }
+ }
+ ],
+ NotExpected = [
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ label => <<"function_exported/0">>,
+ data =>
+ #{
+ arity => 0,
+ function => <<"function_exported">>,
+ module => <<"completion_incomplete">>
+ }
+ }
+ ],
+ #{result := Completions} =
+ els_client:completion(Uri, 4, 18, TriggerKindInvoked, <<"">>),
+ [?assert(lists:member(E, Completions)) || E <- Expected],
+ [?assertNot(lists:member(E, Completions)) || E <- NotExpected],
+ ok.
-spec attribute_export_type(config()) -> ok.
attribute_export_type(Config) ->
- Uri = ?config(completion_attributes_uri, Config),
- TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_INVOKED,
- Expected = [ #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM
- , label => <<"unexported_type/0">>
- , data => #{
- module => <<"completion_attributes">>
- , type => <<"unexported_type">>
- , arity => 0
- }
- }
- , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM
- , label => <<"unexported_opaque/0">>
- , data => #{
- module => <<"completion_attributes">>
- , type => <<"unexported_opaque">>
- , arity => 0
- }
- }
- ],
- NotExpected = [ #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM
- , label => <<"exported_type/0">>
- , data => #{}
- }
- , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM
- , label => <<"exported_opaque/0">>
- , data => #{}
- }
- ],
- #{result := Completions} =
- els_client:completion(Uri, 6, 15, TriggerKindInvoked, <<"">>),
- [?assert(lists:member(E, Completions)) || E <- Expected],
- [?assertNot(lists:member(E, Completions)) || E <- NotExpected],
- ok.
+ Uri = ?config(completion_attributes_uri, Config),
+ TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ Expected = [
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ label => <<"unexported_type/0">>,
+ data => #{
+ module => <<"completion_attributes">>,
+ type => <<"unexported_type">>,
+ arity => 0
+ }
+ },
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ label => <<"unexported_opaque/0">>,
+ data => #{
+ module => <<"completion_attributes">>,
+ type => <<"unexported_opaque">>,
+ arity => 0
+ }
+ }
+ ],
+ NotExpected = [
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ label => <<"exported_type/0">>,
+ data => #{}
+ },
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ label => <<"exported_opaque/0">>,
+ data => #{}
+ }
+ ],
+ #{result := Completions} =
+ els_client:completion(Uri, 6, 15, TriggerKindInvoked, <<"">>),
+ [?assert(lists:member(E, Completions)) || E <- Expected],
+ [?assertNot(lists:member(E, Completions)) || E <- NotExpected],
+ ok.
-spec default_completions(config()) -> ok.
default_completions(Config) ->
- TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- Uri = ?config(code_navigation_extra_uri, Config),
- Functions = [ #{ insertText => <<"do_3(${1:Arg1}, ${2:Arg2})">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , label => <<"do_3/2">>
- , data => #{ module => <<"code_navigation_extra">>
- , function => <<"do_3">>
- , arity => 2
- }
- }
- , #{ insertText => <<"do_2()">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , label => <<"do_2/0">>
- , data => #{ module => <<"code_navigation_extra">>
- , function => <<"do_2">>
- , arity => 0
- }
- }
- , #{ insertText => <<"do(${1:_Config})">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , label => <<"do/1">>
- , data => #{ module => <<"code_navigation_extra">>
- , function => <<"do">>
- , arity => 1
- }
- }
- , #{ insertText => <<"do_4(${1:Arg1}, ${2:Arg2})">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , label => <<"do_4/2">>
- , data => #{ module => <<"code_navigation_extra">>
- , function => <<"do_4">>
- , arity => 2
- }
- }
- , #{ insertText => <<"'DO_LOUDER'()">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , label => <<"'DO_LOUDER'/0">>
- , data => #{ module => <<"code_navigation_extra">>
- , function => <<"DO_LOUDER">>
- , arity => 0
- }
- }
- ],
-
- Expected1 = [ #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_MODULE
- , label => <<"code_lens_function_references">>
- }
- , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_MODULE
- , label => <<"code_navigation_extra">>
- }
- , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_MODULE
- , label => <<"code_navigation">>
- }
- , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_MODULE
- , label => <<"code_navigation_types">>
- }
- , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_MODULE
- , label => <<"code_navigation_undefined">>
- }
- , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_MODULE
- , label => <<"code_navigation_broken">>
- }
- , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_MODULE
- , label => <<"code_action">>
- }
- | Functions ],
-
- DefaultCompletion = els_completion_provider:keywords()
- ++ els_completion_provider:bifs(function, false)
- ++ els_snippets_server:snippets(),
- #{ result := Completion1
- } = els_client:completion(Uri, 9, 6, TriggerKind, <<"">>),
- ?assertEqual(
- lists:sort(Expected1),
- filter_completion(Completion1, DefaultCompletion)),
-
- Expected2 = [ #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_CONSTANT
- , label => <<"foo">>
- }
- | Functions ],
-
- #{ result := Completion2
- } = els_client:completion(Uri, 6, 14, TriggerKind, <<"">>),
- ?assertEqual(
- lists:sort(Expected2),
- filter_completion(Completion2, DefaultCompletion)),
-
- ok.
+ TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ Uri = ?config(code_navigation_extra_uri, Config),
+ Functions = [
+ #{
+ insertText => <<"do_3(${1:Arg1}, ${2:Arg2})">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ label => <<"do_3/2">>,
+ data => #{
+ module => <<"code_navigation_extra">>,
+ function => <<"do_3">>,
+ arity => 2
+ }
+ },
+ #{
+ insertText => <<"do_2()">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ label => <<"do_2/0">>,
+ data => #{
+ module => <<"code_navigation_extra">>,
+ function => <<"do_2">>,
+ arity => 0
+ }
+ },
+ #{
+ insertText => <<"do(${1:_Config})">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ label => <<"do/1">>,
+ data => #{
+ module => <<"code_navigation_extra">>,
+ function => <<"do">>,
+ arity => 1
+ }
+ },
+ #{
+ insertText => <<"do_4(${1:Arg1}, ${2:Arg2})">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ label => <<"do_4/2">>,
+ data => #{
+ module => <<"code_navigation_extra">>,
+ function => <<"do_4">>,
+ arity => 2
+ }
+ },
+ #{
+ insertText => <<"'DO_LOUDER'()">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ label => <<"'DO_LOUDER'/0">>,
+ data => #{
+ module => <<"code_navigation_extra">>,
+ function => <<"DO_LOUDER">>,
+ arity => 0
+ }
+ }
+ ],
+
+ Expected1 = [
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_MODULE,
+ label => <<"code_lens_function_references">>
+ },
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_MODULE,
+ label => <<"code_navigation_extra">>
+ },
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_MODULE,
+ label => <<"code_navigation">>
+ },
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_MODULE,
+ label => <<"code_navigation_types">>
+ },
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_MODULE,
+ label => <<"code_navigation_undefined">>
+ },
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_MODULE,
+ label => <<"code_navigation_broken">>
+ },
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_MODULE,
+ label => <<"code_action">>
+ }
+ | Functions
+ ],
+
+ DefaultCompletion =
+ els_completion_provider:keywords() ++
+ els_completion_provider:bifs(function, false) ++
+ els_snippets_server:snippets(),
+ #{result := Completion1} = els_client:completion(Uri, 9, 6, TriggerKind, <<"">>),
+ ?assertEqual(
+ lists:sort(Expected1),
+ filter_completion(Completion1, DefaultCompletion)
+ ),
+
+ Expected2 = [
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_CONSTANT,
+ label => <<"foo">>
+ }
+ | Functions
+ ],
+
+ #{result := Completion2} = els_client:completion(Uri, 6, 14, TriggerKind, <<"">>),
+ ?assertEqual(
+ lists:sort(Expected2),
+ filter_completion(Completion2, DefaultCompletion)
+ ),
+
+ ok.
-spec empty_completions(config()) -> ok.
empty_completions(Config) ->
- TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- Uri = ?config(code_navigation_extra_uri, Config),
+ TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ Uri = ?config(code_navigation_extra_uri, Config),
- #{ result := Completion
- } = els_client:completion(Uri, 5, 1, TriggerKind, <<"">>),
- ?assertEqual([], Completion),
- ok.
+ #{result := Completion} = els_client:completion(Uri, 5, 1, TriggerKind, <<"">>),
+ ?assertEqual([], Completion),
+ ok.
-spec exported_functions(config()) -> ok.
exported_functions(Config) ->
- TriggerKind = ?COMPLETION_TRIGGER_KIND_CHARACTER,
- Uri = ?config(code_navigation_uri, Config),
- ExpectedCompletion1 = expected_exported_functions(),
-
- #{result := Completion1} =
- els_client:completion(Uri, 32, 25, TriggerKind, <<":">>),
- ?assertEqual(lists:sort(ExpectedCompletion1), lists:sort(Completion1)),
-
- #{result := Completion2} =
- els_client:completion(Uri, 52, 34, TriggerKind, <<":">>),
- ExpectedCompletionArity = expected_exported_functions_arity_only(),
- ?assertEqual(lists:sort(ExpectedCompletionArity), lists:sort(Completion2)),
-
- ExpectedCompletionQuoted =
- [ #{ label => <<"do/1">>
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , data =>
- #{ module => <<"Code.Navigation.Elixirish">>
- , function => <<"do">>
- , arity => 1
+ TriggerKind = ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ Uri = ?config(code_navigation_uri, Config),
+ ExpectedCompletion1 = expected_exported_functions(),
+
+ #{result := Completion1} =
+ els_client:completion(Uri, 32, 25, TriggerKind, <<":">>),
+ ?assertEqual(lists:sort(ExpectedCompletion1), lists:sort(Completion1)),
+
+ #{result := Completion2} =
+ els_client:completion(Uri, 52, 34, TriggerKind, <<":">>),
+ ExpectedCompletionArity = expected_exported_functions_arity_only(),
+ ?assertEqual(lists:sort(ExpectedCompletionArity), lists:sort(Completion2)),
+
+ ExpectedCompletionQuoted =
+ [
+ #{
+ label => <<"do/1">>,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ data =>
+ #{
+ module => <<"Code.Navigation.Elixirish">>,
+ function => <<"do">>,
+ arity => 1
+ }
}
- }
- ],
- #{result := Completion3} =
- els_client:completion(Uri, 100, 39, TriggerKind, <<":">>),
- ?assertEqual(lists:sort(ExpectedCompletionQuoted), lists:sort(Completion3)),
+ ],
+ #{result := Completion3} =
+ els_client:completion(Uri, 100, 39, TriggerKind, <<":">>),
+ ?assertEqual(lists:sort(ExpectedCompletionQuoted), lists:sort(Completion3)),
- ok.
+ ok.
%% [#200] Complete only with arity for named funs
-spec exported_functions_arity(config()) -> ok.
exported_functions_arity(Config) ->
- TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- Uri = ?config(code_navigation_uri, Config),
- ExpectedCompletion = expected_exported_functions_arity_only(),
- #{result := Completion} =
- els_client:completion(Uri, 52, 35, TriggerKind, <<"">>),
- ?assertEqual(lists:sort(ExpectedCompletion), lists:sort(Completion)),
+ TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ Uri = ?config(code_navigation_uri, Config),
+ ExpectedCompletion = expected_exported_functions_arity_only(),
+ #{result := Completion} =
+ els_client:completion(Uri, 52, 35, TriggerKind, <<"">>),
+ ?assertEqual(lists:sort(ExpectedCompletion), lists:sort(Completion)),
- ok.
+ ok.
-spec exported_types(config()) -> ok.
exported_types(Config) ->
- TriggerKind = ?COMPLETION_TRIGGER_KIND_CHARACTER,
- Uri = ?config(code_navigation_uri, Config),
- Types = [ <<"date_time">>, <<"fd">>, <<"file_info">>, <<"filename">>
- , <<"filename_all">>, <<"io_device">>, <<"mode">>, <<"name">>
- , <<"name_all">>, <<"posix">>
- ],
- Expected = [ #{ insertText => <>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM
- , label => <>
- , data => #{ module => <<"file">>
- , type => T
- , arity => 0
- }
- }
- || T <- Types
- ],
+ TriggerKind = ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ Uri = ?config(code_navigation_uri, Config),
+ Types = [
+ <<"date_time">>,
+ <<"fd">>,
+ <<"file_info">>,
+ <<"filename">>,
+ <<"filename_all">>,
+ <<"io_device">>,
+ <<"mode">>,
+ <<"name">>,
+ <<"name_all">>,
+ <<"posix">>
+ ],
+ Expected = [
+ #{
+ insertText => <>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ label => <>,
+ data => #{
+ module => <<"file">>,
+ type => T,
+ arity => 0
+ }
+ }
+ || T <- Types
+ ],
- ct:comment("Exported types from module are returned in a spec context"),
- #{result := Completion1} =
- els_client:completion(Uri, 55, 60, TriggerKind, <<":">>),
- ?assertEqual(lists:sort(Expected), lists:sort(Completion1)),
+ ct:comment("Exported types from module are returned in a spec context"),
+ #{result := Completion1} =
+ els_client:completion(Uri, 55, 60, TriggerKind, <<":">>),
+ ?assertEqual(lists:sort(Expected), lists:sort(Completion1)),
- ok.
+ ok.
%% [#200] Complete only with arity for named funs
-spec functions_arity(config()) -> ok.
functions_arity(Config) ->
- TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- Uri = ?config(code_navigation_uri, Config),
- ExportedFunctions = [ {<<"callback_a">>, 0}
- , {<<"function_a">>, 0}
- , {<<"function_b">>, 0}
- , {<<"function_c">>, 0}
- , {<<"function_d">>, 0}
- , {<<"function_e">>, 0}
- , {<<"function_f">>, 0}
- , {<<"function_g">>, 1}
- , {<<"function_h">>, 0}
- , {<<"function_i">>, 0}
- , {<<"function_j">>, 0}
- , {<<"function_k">>, 0}
- , {<<"function_l">>, 2}
- , {<<"function_m">>, 1}
- , {<<"function_n">>, 0}
- , {<<"function_o">>, 0}
- , {<<"'PascalCaseFunction'">>, 1}
- , {<<"function_p">>, 1}
- , {<<"function_q">>, 0}
- , {<<"macro_b">>, 2}
- , {<<"function_mb">>, 0}
- ],
- ExpectedCompletion =
- [ #{ label =>
- <>
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , data => #{ module => <<"code_navigation">>
- , function => string:trim(FunName, both, [$'])
- , arity => Arity
- }
- }
- || {FunName, Arity} <- ExportedFunctions
- ] ++ els_completion_provider:bifs(function, true),
- #{result := Completion} =
- els_client:completion(Uri, 51, 17, TriggerKind, <<"">>),
- ?assertEqual(lists:sort(ExpectedCompletion), lists:sort(Completion)),
-
- ok.
+ TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ Uri = ?config(code_navigation_uri, Config),
+ ExportedFunctions = [
+ {<<"callback_a">>, 0},
+ {<<"function_a">>, 0},
+ {<<"function_b">>, 0},
+ {<<"function_c">>, 0},
+ {<<"function_d">>, 0},
+ {<<"function_e">>, 0},
+ {<<"function_f">>, 0},
+ {<<"function_g">>, 1},
+ {<<"function_h">>, 0},
+ {<<"function_i">>, 0},
+ {<<"function_j">>, 0},
+ {<<"function_k">>, 0},
+ {<<"function_l">>, 2},
+ {<<"function_m">>, 1},
+ {<<"function_n">>, 0},
+ {<<"function_o">>, 0},
+ {<<"'PascalCaseFunction'">>, 1},
+ {<<"function_p">>, 1},
+ {<<"function_q">>, 0},
+ {<<"macro_b">>, 2},
+ {<<"function_mb">>, 0}
+ ],
+ ExpectedCompletion =
+ [
+ #{
+ label =>
+ <>,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ data => #{
+ module => <<"code_navigation">>,
+ function => string:trim(FunName, both, [$']),
+ arity => Arity
+ }
+ }
+ || {FunName, Arity} <- ExportedFunctions
+ ] ++ els_completion_provider:bifs(function, true),
+ #{result := Completion} =
+ els_client:completion(Uri, 51, 17, TriggerKind, <<"">>),
+ ?assertEqual(lists:sort(ExpectedCompletion), lists:sort(Completion)),
+
+ ok.
%% [#196] Complete only with arity for funs inside the export list
-spec functions_export_list(config()) -> ok.
functions_export_list(Config) ->
- TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- Uri = ?config(code_navigation_extra_uri, Config),
- Expected = [ #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , label => <<"do_3/2">>
- , data =>
- #{ module => <<"code_navigation_extra">>
- , function => <<"do_3">>
- , arity => 2
- }
+ TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ Uri = ?config(code_navigation_extra_uri, Config),
+ Expected = [
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ label => <<"do_3/2">>,
+ data =>
+ #{
+ module => <<"code_navigation_extra">>,
+ function => <<"do_3">>,
+ arity => 2
}
- , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , label => <<"do_4/2">>
- , data =>
- #{ module => <<"code_navigation_extra">>
- , function => <<"do_4">>
- , arity => 2
- }
+ },
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ label => <<"do_4/2">>,
+ data =>
+ #{
+ module => <<"code_navigation_extra">>,
+ function => <<"do_4">>,
+ arity => 2
}
- ],
- #{result := Completion} =
- els_client:completion(Uri, 3, 13, TriggerKind, <<"">>),
- ?assertEqual(Expected, Completion).
+ }
+ ],
+ #{result := Completion} =
+ els_client:completion(Uri, 3, 13, TriggerKind, <<"">>),
+ ?assertEqual(Expected, Completion).
-spec handle_empty_lines(config()) -> ok.
handle_empty_lines(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- TriggerKind = ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ Uri = ?config(code_navigation_uri, Config),
+ TriggerKind = ?COMPLETION_TRIGGER_KIND_CHARACTER,
- #{ result := Completion1
- } = els_client:completion(Uri, 32, 1, TriggerKind, <<"">>),
- ?assertEqual([], Completion1),
+ #{result := Completion1} = els_client:completion(Uri, 32, 1, TriggerKind, <<"">>),
+ ?assertEqual([], Completion1),
- #{ result := Completion2
- } = els_client:completion(Uri, 32, 2, TriggerKind, <<":">>),
- ?assertEqual([], Completion2),
+ #{result := Completion2} = els_client:completion(Uri, 32, 2, TriggerKind, <<":">>),
+ ?assertEqual([], Completion2),
- ok.
+ ok.
-spec handle_colon_inside_string(config()) -> ok.
handle_colon_inside_string(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- TriggerKind = ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ Uri = ?config(code_navigation_uri, Config),
+ TriggerKind = ?COMPLETION_TRIGGER_KIND_CHARACTER,
- #{ result := Completion
- } = els_client:completion(Uri, 76, 10, TriggerKind, <<":">>),
- ?assertEqual([], Completion),
+ #{result := Completion} = els_client:completion(Uri, 76, 10, TriggerKind, <<":">>),
+ ?assertEqual([], Completion),
- ok.
+ ok.
-spec macros(config()) -> ok.
macros(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- TriggerKindChar = ?COMPLETION_TRIGGER_KIND_CHARACTER,
- TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_INVOKED,
- Expected = [ #{ kind => ?COMPLETION_ITEM_KIND_CONSTANT
- , label => <<"INCLUDED_MACRO_A">>
- , insertText => <<"INCLUDED_MACRO_A">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , data => #{}
- }
- , #{ kind => ?COMPLETION_ITEM_KIND_CONSTANT
- , label => <<"MACRO_A">>
- , insertText => <<"MACRO_A">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , data => #{}
- }
- , #{ kind => ?COMPLETION_ITEM_KIND_CONSTANT
- , label => <<"MACRO_A/1">>
- , insertText => <<"MACRO_A(${1:X})">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , data => #{}
- }
- , #{ kind => ?COMPLETION_ITEM_KIND_CONSTANT
- , label => <<"macro_A">>
- , insertText => <<"macro_A">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , data => #{}
- }
- , #{ kind => ?COMPLETION_ITEM_KIND_CONSTANT
- , label => <<"MACRO_FOR_TRANSITIVE_INCLUSION">>
- , insertText => <<"MACRO_FOR_TRANSITIVE_INCLUSION">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , data => #{}
- }
- ],
+ Uri = ?config(code_navigation_uri, Config),
+ TriggerKindChar = ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ Expected = [
+ #{
+ kind => ?COMPLETION_ITEM_KIND_CONSTANT,
+ label => <<"INCLUDED_MACRO_A">>,
+ insertText => <<"INCLUDED_MACRO_A">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ data => #{}
+ },
+ #{
+ kind => ?COMPLETION_ITEM_KIND_CONSTANT,
+ label => <<"MACRO_A">>,
+ insertText => <<"MACRO_A">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ data => #{}
+ },
+ #{
+ kind => ?COMPLETION_ITEM_KIND_CONSTANT,
+ label => <<"MACRO_A/1">>,
+ insertText => <<"MACRO_A(${1:X})">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ data => #{}
+ },
+ #{
+ kind => ?COMPLETION_ITEM_KIND_CONSTANT,
+ label => <<"macro_A">>,
+ insertText => <<"macro_A">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ data => #{}
+ },
+ #{
+ kind => ?COMPLETION_ITEM_KIND_CONSTANT,
+ label => <<"MACRO_FOR_TRANSITIVE_INCLUSION">>,
+ insertText => <<"MACRO_FOR_TRANSITIVE_INCLUSION">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ data => #{}
+ }
+ ],
- #{result := Completion1} =
- els_client:completion(Uri, 24, 1, TriggerKindChar, <<"?">>),
- [?assert(lists:member(E, Completion1)) || E <- Expected],
+ #{result := Completion1} =
+ els_client:completion(Uri, 24, 1, TriggerKindChar, <<"?">>),
+ [?assert(lists:member(E, Completion1)) || E <- Expected],
- #{result := Completion2} =
- els_client:completion(Uri, 40, 5, TriggerKindInvoked, <<"">>),
- [?assert(lists:member(E, Completion2)) || E <- Expected],
+ #{result := Completion2} =
+ els_client:completion(Uri, 40, 5, TriggerKindInvoked, <<"">>),
+ [?assert(lists:member(E, Completion2)) || E <- Expected],
- ok.
+ ok.
-spec only_exported_functions_after_colon(config()) -> ok.
only_exported_functions_after_colon(Config) ->
- TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- Uri = ?config(code_navigation_uri, Config),
- ExpectedCompletion = expected_exported_functions(),
+ TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ Uri = ?config(code_navigation_uri, Config),
+ ExpectedCompletion = expected_exported_functions(),
- #{result := Completion} =
- els_client:completion(Uri, 32, 26, TriggerKind, <<"d">>),
- ?assertEqual(lists:sort(ExpectedCompletion), lists:sort(Completion)),
+ #{result := Completion} =
+ els_client:completion(Uri, 32, 26, TriggerKind, <<"d">>),
+ ?assertEqual(lists:sort(ExpectedCompletion), lists:sort(Completion)),
- ok.
+ ok.
-spec records(config()) -> ok.
records(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- TriggerKindChar = ?COMPLETION_TRIGGER_KIND_CHARACTER,
- TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_INVOKED,
- Expected = [ #{ kind => ?COMPLETION_ITEM_KIND_STRUCT
- , label => <<"'?MODULE'">>
- , data => #{}
- }
- , #{ kind => ?COMPLETION_ITEM_KIND_STRUCT
- , label => <<"included_record_a">>
- , data => #{}
- }
- , #{ kind => ?COMPLETION_ITEM_KIND_STRUCT
- , label => <<"record_a">>
- , data => #{}
- }
- , #{ kind => ?COMPLETION_ITEM_KIND_STRUCT
- , label => <<"'PascalCaseRecord'">>
- , data => #{}
- }
- ],
+ Uri = ?config(code_navigation_uri, Config),
+ TriggerKindChar = ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ Expected = [
+ #{
+ kind => ?COMPLETION_ITEM_KIND_STRUCT,
+ label => <<"'?MODULE'">>,
+ data => #{}
+ },
+ #{
+ kind => ?COMPLETION_ITEM_KIND_STRUCT,
+ label => <<"included_record_a">>,
+ data => #{}
+ },
+ #{
+ kind => ?COMPLETION_ITEM_KIND_STRUCT,
+ label => <<"record_a">>,
+ data => #{}
+ },
+ #{
+ kind => ?COMPLETION_ITEM_KIND_STRUCT,
+ label => <<"'PascalCaseRecord'">>,
+ data => #{}
+ }
+ ],
- #{result := Completion1} =
- els_client:completion(Uri, 24, 1, TriggerKindChar, <<"#">>),
- ?assertEqual(lists:sort(Expected), lists:sort(Completion1)),
+ #{result := Completion1} =
+ els_client:completion(Uri, 24, 1, TriggerKindChar, <<"#">>),
+ ?assertEqual(lists:sort(Expected), lists:sort(Completion1)),
- #{result := Completion2} =
- els_client:completion(Uri, 23, 6, TriggerKindInvoked, <<"">>),
- ?assertEqual(lists:sort(Expected), lists:sort(Completion2)),
+ #{result := Completion2} =
+ els_client:completion(Uri, 23, 6, TriggerKindInvoked, <<"">>),
+ ?assertEqual(lists:sort(Expected), lists:sort(Completion2)),
- ok.
+ ok.
-spec record_fields(config()) -> ok.
record_fields(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- TriggerKindChar = ?COMPLETION_TRIGGER_KIND_CHARACTER,
- TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_INVOKED,
- Expected1 = [ #{ kind => ?COMPLETION_ITEM_KIND_FIELD
- , label => <<"field_a">>
- }
- , #{ kind => ?COMPLETION_ITEM_KIND_FIELD
- , label => <<"field_b">>
- }
- , #{ kind => ?COMPLETION_ITEM_KIND_FIELD
- , label => <<"'Field C'">>
- }
- ],
- #{result := Completion1} =
- els_client:completion(Uri, 34, 19, TriggerKindChar, <<".">>),
- ?assertEqual(lists:sort(Expected1), lists:sort(Completion1)),
-
- #{result := Completion2} =
- els_client:completion(Uri, 34, 22, TriggerKindInvoked, <<"">>),
- ?assertEqual(lists:sort(Expected1), lists:sort(Completion2)),
-
- Expected2 = [ #{ kind => ?COMPLETION_ITEM_KIND_FIELD
- , label => <<"included_field_a">>
- }
- , #{ kind => ?COMPLETION_ITEM_KIND_FIELD
- , label => <<"included_field_b">>
- }
- ],
- #{result := Completion3} =
- els_client:completion(Uri, 52, 60, TriggerKindChar, <<".">>),
- ?assertEqual(lists:sort(Expected2), lists:sort(Completion3)),
-
- #{result := Completion4} =
- els_client:completion(Uri, 52, 63, TriggerKindInvoked, <<"">>),
- ?assertEqual(lists:sort(Expected2), lists:sort(Completion4)),
-
- ExpectedQuoted = [ #{ kind => ?COMPLETION_ITEM_KIND_FIELD
- , label => <<"'Field #1'">>
- }
- , #{ kind => ?COMPLETION_ITEM_KIND_FIELD
- , label => <<"'Field #2'">>
- }
- ],
- #{result := Completion5} =
- els_client:completion(Uri, 52, 90, TriggerKindChar, <<".">>),
- ?assertEqual(lists:sort(ExpectedQuoted), lists:sort(Completion5)),
-
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ TriggerKindChar = ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ TriggerKindInvoked = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ Expected1 = [
+ #{
+ kind => ?COMPLETION_ITEM_KIND_FIELD,
+ label => <<"field_a">>
+ },
+ #{
+ kind => ?COMPLETION_ITEM_KIND_FIELD,
+ label => <<"field_b">>
+ },
+ #{
+ kind => ?COMPLETION_ITEM_KIND_FIELD,
+ label => <<"'Field C'">>
+ }
+ ],
+ #{result := Completion1} =
+ els_client:completion(Uri, 34, 19, TriggerKindChar, <<".">>),
+ ?assertEqual(lists:sort(Expected1), lists:sort(Completion1)),
+
+ #{result := Completion2} =
+ els_client:completion(Uri, 34, 22, TriggerKindInvoked, <<"">>),
+ ?assertEqual(lists:sort(Expected1), lists:sort(Completion2)),
+
+ Expected2 = [
+ #{
+ kind => ?COMPLETION_ITEM_KIND_FIELD,
+ label => <<"included_field_a">>
+ },
+ #{
+ kind => ?COMPLETION_ITEM_KIND_FIELD,
+ label => <<"included_field_b">>
+ }
+ ],
+ #{result := Completion3} =
+ els_client:completion(Uri, 52, 60, TriggerKindChar, <<".">>),
+ ?assertEqual(lists:sort(Expected2), lists:sort(Completion3)),
+
+ #{result := Completion4} =
+ els_client:completion(Uri, 52, 63, TriggerKindInvoked, <<"">>),
+ ?assertEqual(lists:sort(Expected2), lists:sort(Completion4)),
+
+ ExpectedQuoted = [
+ #{
+ kind => ?COMPLETION_ITEM_KIND_FIELD,
+ label => <<"'Field #1'">>
+ },
+ #{
+ kind => ?COMPLETION_ITEM_KIND_FIELD,
+ label => <<"'Field #2'">>
+ }
+ ],
+ #{result := Completion5} =
+ els_client:completion(Uri, 52, 90, TriggerKindChar, <<".">>),
+ ?assertEqual(lists:sort(ExpectedQuoted), lists:sort(Completion5)),
+
+ ok.
-spec types(config()) -> ok.
types(Config) ->
- TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- Uri = ?config(code_navigation_uri, Config),
- Expected = [ #{ insertText => <<"type_a()">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM
- , label => <<"type_a/0">>
- , data => #{ module => <<"code_navigation">>
- , type => <<"type_a">>
- , arity => 0
- }
- }
- , #{ insertText => <<"included_type_a()">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM
- , label => <<"included_type_a/0">>
- , data => #{ module => <<"code_navigation">>
- , type => <<"included_type_a">>
- , arity => 0
- }
- }
- , #{ insertText => <<"'INCLUDED_TYPE'(${1:T})">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM
- , label => <<"'INCLUDED_TYPE'/1">>
- , data => #{ module => <<"code_navigation">>
- , type => <<"INCLUDED_TYPE">>
- , arity => 1
- }
- }
- , #{ insertText => <<"type_b()">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM
- , label => <<"type_b/0">>
- , data => #{ module => <<"code_navigation">>
- , type => <<"type_b">>
- , arity => 0
- }
- }
- ],
+ TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ Uri = ?config(code_navigation_uri, Config),
+ Expected = [
+ #{
+ insertText => <<"type_a()">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ label => <<"type_a/0">>,
+ data => #{
+ module => <<"code_navigation">>,
+ type => <<"type_a">>,
+ arity => 0
+ }
+ },
+ #{
+ insertText => <<"included_type_a()">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ label => <<"included_type_a/0">>,
+ data => #{
+ module => <<"code_navigation">>,
+ type => <<"included_type_a">>,
+ arity => 0
+ }
+ },
+ #{
+ insertText => <<"'INCLUDED_TYPE'(${1:T})">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ label => <<"'INCLUDED_TYPE'/1">>,
+ data => #{
+ module => <<"code_navigation">>,
+ type => <<"INCLUDED_TYPE">>,
+ arity => 1
+ }
+ },
+ #{
+ insertText => <<"type_b()">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ label => <<"type_b/0">>,
+ data => #{
+ module => <<"code_navigation">>,
+ type => <<"type_b">>,
+ arity => 0
+ }
+ }
+ ],
- DefaultCompletion = els_completion_provider:keywords()
- ++ els_completion_provider:bifs(type_definition, false)
- ++ els_snippets_server:snippets(),
+ DefaultCompletion =
+ els_completion_provider:keywords() ++
+ els_completion_provider:bifs(type_definition, false) ++
+ els_snippets_server:snippets(),
- ct:comment("Types defined both in the current file and in includes"),
- #{result := Completion1} =
- els_client:completion(Uri, 55, 27, TriggerKind, <<"">>),
- ?assertEqual(
- lists:sort(Expected),
- filter_completion(Completion1, DefaultCompletion)),
+ ct:comment("Types defined both in the current file and in includes"),
+ #{result := Completion1} =
+ els_client:completion(Uri, 55, 27, TriggerKind, <<"">>),
+ ?assertEqual(
+ lists:sort(Expected),
+ filter_completion(Completion1, DefaultCompletion)
+ ),
- ok.
+ ok.
-spec types_export_list(config()) -> ok.
types_export_list(Config) ->
- TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- Uri = ?config(code_navigation_types_uri, Config),
- Expected = [ #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM
- , label => <<"type_b/0">>
- , data => #{ module => <<"code_navigation_types">>
- , type => <<"type_b">>
- , arity => 0
- }
- }
- , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM
- , label => <<"user_type_a/0">>
- , data => #{ module => <<"code_navigation_types">>
- , type => <<"user_type_a">>
- , arity => 0
- }
- }
- , #{ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM
- , label => <<"user_type_b/0">>
- , data => #{ module => <<"code_navigation_types">>
- , type => <<"user_type_b">>
- , arity => 0
- }
- }
- ],
- ct:comment("Types in an export_type section is provided with arity"),
- #{result := Completion} =
- els_client:completion(Uri, 5, 19, TriggerKind, <<"">>),
- ?assertEqual(Expected, Completion),
- ok.
+ TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ Uri = ?config(code_navigation_types_uri, Config),
+ Expected = [
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ label => <<"type_b/0">>,
+ data => #{
+ module => <<"code_navigation_types">>,
+ type => <<"type_b">>,
+ arity => 0
+ }
+ },
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ label => <<"user_type_a/0">>,
+ data => #{
+ module => <<"code_navigation_types">>,
+ type => <<"user_type_a">>,
+ arity => 0
+ }
+ },
+ #{
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ kind => ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ label => <<"user_type_b/0">>,
+ data => #{
+ module => <<"code_navigation_types">>,
+ type => <<"user_type_b">>,
+ arity => 0
+ }
+ }
+ ],
+ ct:comment("Types in an export_type section is provided with arity"),
+ #{result := Completion} =
+ els_client:completion(Uri, 5, 19, TriggerKind, <<"">>),
+ ?assertEqual(Expected, Completion),
+ ok.
-spec variables(config()) -> ok.
variables(Config) ->
- TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- Uri = ?config(code_navigation_extra_uri, Config),
- Expected = [ #{ kind => ?COMPLETION_ITEM_KIND_VARIABLE
- , label => <<"_Config">>
- }],
+ TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ Uri = ?config(code_navigation_extra_uri, Config),
+ Expected = [
+ #{
+ kind => ?COMPLETION_ITEM_KIND_VARIABLE,
+ label => <<"_Config">>
+ }
+ ],
- #{result := Completion} =
- els_client:completion(Uri, 5, 8, TriggerKind, <<"">>),
- ?assertEqual(lists:sort(Expected), lists:sort(Completion)),
+ #{result := Completion} =
+ els_client:completion(Uri, 5, 8, TriggerKind, <<"">>),
+ ?assertEqual(lists:sort(Expected), lists:sort(Completion)),
- ok.
+ ok.
expected_exported_functions() ->
- [ #{ label => <<"do/1">>
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , insertText => <<"do(${1:_Config})">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , data => #{ module => <<"code_navigation_extra">>
- , function => <<"do">>
- , arity => 1
- }
- }
- , #{ label => <<"do_2/0">>
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , insertText => <<"do_2()">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , data => #{ module => <<"code_navigation_extra">>
- , function => <<"do_2">>
- , arity => 0
- }
- }
- , #{ label => <<"'DO_LOUDER'/0">>
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , insertText => <<"'DO_LOUDER'()">>
- , insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
- , data => #{ module => <<"code_navigation_extra">>
- , function => <<"DO_LOUDER">>
- , arity => 0
- }
- }
- ].
+ [
+ #{
+ label => <<"do/1">>,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ insertText => <<"do(${1:_Config})">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ data => #{
+ module => <<"code_navigation_extra">>,
+ function => <<"do">>,
+ arity => 1
+ }
+ },
+ #{
+ label => <<"do_2/0">>,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ insertText => <<"do_2()">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ data => #{
+ module => <<"code_navigation_extra">>,
+ function => <<"do_2">>,
+ arity => 0
+ }
+ },
+ #{
+ label => <<"'DO_LOUDER'/0">>,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ insertText => <<"'DO_LOUDER'()">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ data => #{
+ module => <<"code_navigation_extra">>,
+ function => <<"DO_LOUDER">>,
+ arity => 0
+ }
+ }
+ ].
expected_exported_functions_arity_only() ->
- [ #{ label => <<"do/1">>
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , data => #{ module => <<"code_navigation_extra">>
- , function => <<"do">>
- , arity => 1
- }
- }
- , #{ label => <<"do_2/0">>
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , data => #{ module => <<"code_navigation_extra">>
- , function => <<"do_2">>
- , arity => 0
- }
- }
- , #{ label => <<"'DO_LOUDER'/0">>
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , data => #{ module => <<"code_navigation_extra">>
- , function => <<"DO_LOUDER">>
- , arity => 0
- }
- }
- ].
+ [
+ #{
+ label => <<"do/1">>,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ data => #{
+ module => <<"code_navigation_extra">>,
+ function => <<"do">>,
+ arity => 1
+ }
+ },
+ #{
+ label => <<"do_2/0">>,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ data => #{
+ module => <<"code_navigation_extra">>,
+ function => <<"do_2">>,
+ arity => 0
+ }
+ },
+ #{
+ label => <<"'DO_LOUDER'/0">>,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ data => #{
+ module => <<"code_navigation_extra">>,
+ function => <<"DO_LOUDER">>,
+ arity => 0
+ }
+ }
+ ].
%% [#790] Complete only with arity for remote applications
-spec remote_fun(config()) -> ok.
remote_fun(Config) ->
- TriggerKind = ?COMPLETION_TRIGGER_KIND_CHARACTER,
- Uri = ?config(completion_caller_uri, Config),
- ExpectedCompletion = [ #{ label => <<"complete_1/0">>
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , data => #{ module => <<"completion">>
- , function => <<"complete_1">>
- , arity => 0
- }
- }
- , #{ label => <<"complete_2/0">>
- , kind => ?COMPLETION_ITEM_KIND_FUNCTION
- , insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT
- , data => #{ module => <<"completion">>
- , function => <<"complete_2">>
- , arity => 0
- }
- }
- ],
- #{result := Completion} =
- els_client:completion(Uri, 6, 19, TriggerKind, <<":">>),
- ?assertEqual(lists:sort(ExpectedCompletion), lists:sort(Completion)),
-
- ok.
+ TriggerKind = ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ Uri = ?config(completion_caller_uri, Config),
+ ExpectedCompletion = [
+ #{
+ label => <<"complete_1/0">>,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ data => #{
+ module => <<"completion">>,
+ function => <<"complete_1">>,
+ arity => 0
+ }
+ },
+ #{
+ label => <<"complete_2/0">>,
+ kind => ?COMPLETION_ITEM_KIND_FUNCTION,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ data => #{
+ module => <<"completion">>,
+ function => <<"complete_2">>,
+ arity => 0
+ }
+ }
+ ],
+ #{result := Completion} =
+ els_client:completion(Uri, 6, 19, TriggerKind, <<":">>),
+ ?assertEqual(lists:sort(ExpectedCompletion), lists:sort(Completion)),
+
+ ok.
-spec snippets(config()) -> ok.
snippets(Config) ->
- TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- Uri = ?config(completion_snippets_uri, Config),
- #{result := Result} = els_client:completion(Uri, 3, 6, TriggerKind, <<"">>),
- Completions = [C || #{kind := ?COMPLETION_ITEM_KIND_SNIPPET} = C <- Result],
- SnippetsDir = els_snippets_server:builtin_snippets_dir(),
- Snippets = filelib:wildcard("*", SnippetsDir),
- CustomSnippetsDir = els_snippets_server:custom_snippets_dir(),
- CustomSnippets = filelib:wildcard("*", CustomSnippetsDir),
- Expected = lists:usort(Snippets ++ CustomSnippets),
- ?assertEqual(length(Expected), length(Completions)).
+ TriggerKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ Uri = ?config(completion_snippets_uri, Config),
+ #{result := Result} = els_client:completion(Uri, 3, 6, TriggerKind, <<"">>),
+ Completions = [C || #{kind := ?COMPLETION_ITEM_KIND_SNIPPET} = C <- Result],
+ SnippetsDir = els_snippets_server:builtin_snippets_dir(),
+ Snippets = filelib:wildcard("*", SnippetsDir),
+ CustomSnippetsDir = els_snippets_server:custom_snippets_dir(),
+ CustomSnippets = filelib:wildcard("*", CustomSnippetsDir),
+ Expected = lists:usort(Snippets ++ CustomSnippets),
+ ?assertEqual(length(Expected), length(Completions)).
filter_completion(Completion, ToFilter) ->
- CompletionSet = ordsets:from_list(Completion),
- FilterSet = ordsets:from_list(ToFilter),
- ?assertEqual(FilterSet, ordsets:intersection(CompletionSet, FilterSet)),
- ordsets:to_list(ordsets:subtract(CompletionSet, FilterSet)).
+ CompletionSet = ordsets:from_list(Completion),
+ FilterSet = ordsets:from_list(ToFilter),
+ ?assertEqual(FilterSet, ordsets:intersection(CompletionSet, FilterSet)),
+ ordsets:to_list(ordsets:subtract(CompletionSet, FilterSet)).
-spec resolve_application_local(config()) -> ok.
resolve_application_local(Config) ->
- Uri = ?config(completion_resolve_uri, Config),
- CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- #{result := CompletionItems} =
- els_client:completion(Uri, 14, 5, CompletionKind, <<"">>),
- [Selected] = select_completionitems(CompletionItems,
- ?COMPLETION_ITEM_KIND_FUNCTION, <<"call_1/0">>),
- #{result := Result} = els_client:completionitem_resolve(Selected),
- Expected = Selected#{ documentation =>
- #{ kind => <<"markdown">>
- , value => call_markdown(
- <<"call_1">>,
- <<"Call me maybe">>)
- }
- },
- ?assertEqual(Expected, Result).
+ Uri = ?config(completion_resolve_uri, Config),
+ CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ #{result := CompletionItems} =
+ els_client:completion(Uri, 14, 5, CompletionKind, <<"">>),
+ [Selected] = select_completionitems(
+ CompletionItems,
+ ?COMPLETION_ITEM_KIND_FUNCTION,
+ <<"call_1/0">>
+ ),
+ #{result := Result} = els_client:completionitem_resolve(Selected),
+ Expected = Selected#{
+ documentation =>
+ #{
+ kind => <<"markdown">>,
+ value => call_markdown(
+ <<"call_1">>,
+ <<"Call me maybe">>
+ )
+ }
+ },
+ ?assertEqual(Expected, Result).
-spec resolve_application_unexported_local(config()) -> ok.
resolve_application_unexported_local(Config) ->
- Uri = ?config(completion_resolve_uri, Config),
- CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- #{result := CompletionItems} =
- els_client:completion(Uri, 14, 5, CompletionKind, <<"">>),
- [Selected] = select_completionitems(CompletionItems,
- ?COMPLETION_ITEM_KIND_FUNCTION, <<"call_2/0">>),
- #{result := Result} = els_client:completionitem_resolve(Selected),
- Expected = Selected#{ documentation =>
- #{ kind => <<"markdown">>
- , value => call_markdown(
- <<"call_2">>,
- <<"Call me sometime">>)
- }
- },
- ?assertEqual(Expected, Result).
+ Uri = ?config(completion_resolve_uri, Config),
+ CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ #{result := CompletionItems} =
+ els_client:completion(Uri, 14, 5, CompletionKind, <<"">>),
+ [Selected] = select_completionitems(
+ CompletionItems,
+ ?COMPLETION_ITEM_KIND_FUNCTION,
+ <<"call_2/0">>
+ ),
+ #{result := Result} = els_client:completionitem_resolve(Selected),
+ Expected = Selected#{
+ documentation =>
+ #{
+ kind => <<"markdown">>,
+ value => call_markdown(
+ <<"call_2">>,
+ <<"Call me sometime">>
+ )
+ }
+ },
+ ?assertEqual(Expected, Result).
-spec resolve_application_remote_self(config()) -> ok.
resolve_application_remote_self(Config) ->
- Uri = ?config(completion_resolve_uri, Config),
- CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- #{result := CompletionItems} =
- els_client:completion(Uri, 13, 23, CompletionKind, <<":">>),
- [Selected] = select_completionitems(CompletionItems,
- ?COMPLETION_ITEM_KIND_FUNCTION, <<"call_1/0">>),
- #{result := Result} = els_client:completionitem_resolve(Selected),
-
- Expected = Selected#{ documentation =>
- #{ kind => <<"markdown">>
- , value => call_markdown(
- <<"call_1">>,
- <<"Call me maybe">>)
- }
- },
- ?assertEqual(Expected, Result).
+ Uri = ?config(completion_resolve_uri, Config),
+ CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ #{result := CompletionItems} =
+ els_client:completion(Uri, 13, 23, CompletionKind, <<":">>),
+ [Selected] = select_completionitems(
+ CompletionItems,
+ ?COMPLETION_ITEM_KIND_FUNCTION,
+ <<"call_1/0">>
+ ),
+ #{result := Result} = els_client:completionitem_resolve(Selected),
+
+ Expected = Selected#{
+ documentation =>
+ #{
+ kind => <<"markdown">>,
+ value => call_markdown(
+ <<"call_1">>,
+ <<"Call me maybe">>
+ )
+ }
+ },
+ ?assertEqual(Expected, Result).
-spec resolve_application_remote_external(config()) -> ok.
resolve_application_remote_external(Config) ->
- Uri = ?config(completion_resolve_uri, Config),
- CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- #{result := CompletionItems} =
- els_client:completion(Uri, 15, 25, CompletionKind, <<":">>),
- [Selected] = select_completionitems(CompletionItems,
- ?COMPLETION_ITEM_KIND_FUNCTION, <<"call_1/0">>),
- #{result := Result} = els_client:completionitem_resolve(Selected),
- Expected = Selected#{ documentation =>
- #{ kind => <<"markdown">>
- , value => call_markdown(
- <<"completion_resolve_2">>,
- <<"call_1">>,
- <<"I just met you">>)
- }
- },
- ?assertEqual(Expected, Result).
+ Uri = ?config(completion_resolve_uri, Config),
+ CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ #{result := CompletionItems} =
+ els_client:completion(Uri, 15, 25, CompletionKind, <<":">>),
+ [Selected] = select_completionitems(
+ CompletionItems,
+ ?COMPLETION_ITEM_KIND_FUNCTION,
+ <<"call_1/0">>
+ ),
+ #{result := Result} = els_client:completionitem_resolve(Selected),
+ Expected = Selected#{
+ documentation =>
+ #{
+ kind => <<"markdown">>,
+ value => call_markdown(
+ <<"completion_resolve_2">>,
+ <<"call_1">>,
+ <<"I just met you">>
+ )
+ }
+ },
+ ?assertEqual(Expected, Result).
-spec resolve_application_remote_otp(config()) -> ok.
resolve_application_remote_otp(Config) ->
- Uri = ?config(completion_resolve_uri, Config),
- CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- #{result := CompletionItems} =
- els_client:completion(Uri, 16, 8, CompletionKind, <<":">>),
- [Selected] = select_completionitems(CompletionItems,
- ?COMPLETION_ITEM_KIND_FUNCTION, <<"write/2">>),
- #{result := Result} = els_client:completionitem_resolve(Selected),
- Value = case has_eep48(file) of
- true -> <<"```erlang\nwrite(IoDevice, Bytes) -> ok | {error, "
- "Reason}\nwhen\n IoDevice :: io_device() | atom(),\n Bytes ::"
- " iodata(),\n Reason :: posix() | badarg | terminated.\n```\n\n"
- "---\n\nWrites `Bytes` to the file referenced by `IoDevice`\\. "
- "This function is the only way to write to a file opened in `raw`"
- " mode \\(although it works for normally opened files too\\)\\. "
- "Returns `ok` if successful, and `{error, Reason}` otherwise\\."
- "\n\nIf the file is opened with `encoding` set to something else "
- "than `latin1`, each byte written can result in many bytes being "
- "written to the file, as the byte range 0\\.\\.255 can represent "
- "anything between one and four bytes depending on value and UTF "
- "encoding type\\.\n\nTypical error reasons:\n\n* **`ebadf`** \n"
- " The file is not opened for writing\\.\n\n* **`enospc`** \n"
- " No space is left on the device\\.\n">>;
- false -> <<"## file:write/2\n\n---\n\n```erlang\n\n write(File, "
- "Bytes) when is_pid(File) orelse is_atom(File)\n\n write(#file_"
- "descriptor{module = Module} = Handle, Bytes) \n\n write(_, _) "
- "\n\n```\n\n```erlang\n-spec write(IoDevice, Bytes) -> ok | "
- "{error, Reason} when\n IoDevice :: io_device() | atom(),"
- "\n Bytes :: iodata(),\n Reason :: posix() | "
- "badarg | terminated.\n```">>
- end,
- Expected = Selected#{ documentation =>
- #{ kind => <<"markdown">>
- , value => Value
- }
- },
- ?assertEqual(Expected, Result).
+ Uri = ?config(completion_resolve_uri, Config),
+ CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ #{result := CompletionItems} =
+ els_client:completion(Uri, 16, 8, CompletionKind, <<":">>),
+ [Selected] = select_completionitems(
+ CompletionItems,
+ ?COMPLETION_ITEM_KIND_FUNCTION,
+ <<"write/2">>
+ ),
+ #{result := Result} = els_client:completionitem_resolve(Selected),
+ Value =
+ case has_eep48(file) of
+ true ->
+ <<
+ "```erlang\nwrite(IoDevice, Bytes) -> ok | {error, "
+ "Reason}\nwhen\n IoDevice :: io_device() | atom(),\n Bytes ::"
+ " iodata(),\n Reason :: posix() | badarg | terminated.\n```\n\n"
+ "---\n\nWrites `Bytes` to the file referenced by `IoDevice`\\. "
+ "This function is the only way to write to a file opened in `raw`"
+ " mode \\(although it works for normally opened files too\\)\\. "
+ "Returns `ok` if successful, and `{error, Reason}` otherwise\\."
+ "\n\nIf the file is opened with `encoding` set to something else "
+ "than `latin1`, each byte written can result in many bytes being "
+ "written to the file, as the byte range 0\\.\\.255 can represent "
+ "anything between one and four bytes depending on value and UTF "
+ "encoding type\\.\n\nTypical error reasons:\n\n* **`ebadf`** \n"
+ " The file is not opened for writing\\.\n\n* **`enospc`** \n"
+ " No space is left on the device\\.\n"
+ >>;
+ false ->
+ <<
+ "## file:write/2\n\n---\n\n```erlang\n\n write(File, "
+ "Bytes) when is_pid(File) orelse is_atom(File)\n\n write(#file_"
+ "descriptor{module = Module} = Handle, Bytes) \n\n write(_, _) "
+ "\n\n```\n\n```erlang\n-spec write(IoDevice, Bytes) -> ok | "
+ "{error, Reason} when\n IoDevice :: io_device() | atom(),"
+ "\n Bytes :: iodata(),\n Reason :: posix() | "
+ "badarg | terminated.\n```"
+ >>
+ end,
+ Expected = Selected#{
+ documentation =>
+ #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result).
call_markdown(F, Doc) ->
- call_markdown(<<"completion_resolve">>, F, Doc).
+ call_markdown(<<"completion_resolve">>, F, Doc).
call_markdown(M, F, Doc) ->
- case has_eep48_edoc() of
- true ->
- <<"```erlang\n",
- F/binary, "() -> ok.\n"
- "```\n\n"
- "---\n\n",
- Doc/binary, "\n">>;
- false ->
- <<"## ", M/binary, ":", F/binary, "/0\n\n"
- "---\n\n"
- "```erlang\n"
- "-spec ", F/binary, "() -> 'ok'.\n"
- "```\n\n",
- Doc/binary, "\n\n">>
- end.
+ case has_eep48_edoc() of
+ true ->
+ <<"```erlang\n", F/binary,
+ "() -> ok.\n"
+ "```\n\n"
+ "---\n\n", Doc/binary, "\n">>;
+ false ->
+ <<"## ", M/binary, ":", F/binary,
+ "/0\n\n"
+ "---\n\n"
+ "```erlang\n"
+ "-spec ", F/binary,
+ "() -> 'ok'.\n"
+ "```\n\n", Doc/binary, "\n\n">>
+ end.
-spec resolve_type_application_local(config()) -> ok.
resolve_type_application_local(Config) ->
- Uri = ?config(completion_resolve_type_uri, Config),
- CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- #{result := CompletionItems} =
- els_client:completion(Uri, 14, 16, CompletionKind, <<"">>),
- [Selected] = select_completionitems(CompletionItems,
- ?COMPLETION_ITEM_KIND_TYPE_PARAM, <<"mytype/0">>),
- #{result := Result} = els_client:completionitem_resolve(Selected),
- Value = case has_eep48_edoc() of
- true -> <<"```erlang\n-type mytype() :: "
- "completion_resolve_type:myopaque().\n```"
- "\n\n---\n\nThis is my type\n">>;
- false -> <<"```erlang\n-type mytype() :: "
- "completion_resolve_type:myopaque().\n```">>
- end,
- Expected = Selected#{ documentation =>
- #{ kind => <<"markdown">>
- , value => Value
- }
- },
- ?assertEqual(Expected, Result).
+ Uri = ?config(completion_resolve_type_uri, Config),
+ CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ #{result := CompletionItems} =
+ els_client:completion(Uri, 14, 16, CompletionKind, <<"">>),
+ [Selected] = select_completionitems(
+ CompletionItems,
+ ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ <<"mytype/0">>
+ ),
+ #{result := Result} = els_client:completionitem_resolve(Selected),
+ Value =
+ case has_eep48_edoc() of
+ true ->
+ <<
+ "```erlang\n-type mytype() :: "
+ "completion_resolve_type:myopaque().\n```"
+ "\n\n---\n\nThis is my type\n"
+ >>;
+ false ->
+ <<
+ "```erlang\n-type mytype() :: "
+ "completion_resolve_type:myopaque().\n```"
+ >>
+ end,
+ Expected = Selected#{
+ documentation =>
+ #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result).
-spec resolve_opaque_application_local(config()) -> ok.
resolve_opaque_application_local(Config) ->
- Uri = ?config(completion_resolve_type_uri, Config),
- CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- #{result := CompletionItems} =
- els_client:completion(Uri, 14, 17, CompletionKind, <<"">>),
- [Selected] = select_completionitems(CompletionItems,
- ?COMPLETION_ITEM_KIND_TYPE_PARAM, <<"myopaque/0">>),
- #{result := Result} = els_client:completionitem_resolve(Selected),
- Value = case has_eep48_edoc() of
- true -> <<"```erlang\n-opaque myopaque() \n```\n\n---\n\n"
- "This is my opaque\n">>;
- false -> <<"```erlang\n"
- "-opaque myopaque() :: term().\n```">>
- end,
- Expected = Selected#{ documentation =>
- #{ kind => <<"markdown">>
- , value => Value
- }
- },
- ?assertEqual(Expected, Result).
+ Uri = ?config(completion_resolve_type_uri, Config),
+ CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ #{result := CompletionItems} =
+ els_client:completion(Uri, 14, 17, CompletionKind, <<"">>),
+ [Selected] = select_completionitems(
+ CompletionItems,
+ ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ <<"myopaque/0">>
+ ),
+ #{result := Result} = els_client:completionitem_resolve(Selected),
+ Value =
+ case has_eep48_edoc() of
+ true ->
+ <<
+ "```erlang\n-opaque myopaque() \n```\n\n---\n\n"
+ "This is my opaque\n"
+ >>;
+ false ->
+ <<
+ "```erlang\n"
+ "-opaque myopaque() :: term().\n```"
+ >>
+ end,
+ Expected = Selected#{
+ documentation =>
+ #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result).
-spec resolve_opaque_application_remote_self(config()) -> ok.
resolve_opaque_application_remote_self(Config) ->
- Uri = ?config(completion_resolve_type_uri, Config),
- CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- #{result := CompletionItems} =
- els_client:completion(Uri, 14, 48, CompletionKind, <<":">>),
- [Selected] = select_completionitems(CompletionItems,
- ?COMPLETION_ITEM_KIND_TYPE_PARAM, <<"myopaque/0">>),
- #{result := Result} = els_client:completionitem_resolve(Selected),
-
- Value = case has_eep48_edoc() of
- true -> <<"```erlang\n-opaque myopaque() \n```\n\n---\n\n"
- "This is my opaque\n">>;
- false -> <<"```erlang\n"
- "-opaque myopaque() :: term().\n"
- "```">>
- end,
-
- Expected = Selected#{ documentation =>
- #{ kind => <<"markdown">>
- , value => Value
- }
- },
- ?assertEqual(Expected, Result).
+ Uri = ?config(completion_resolve_type_uri, Config),
+ CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ #{result := CompletionItems} =
+ els_client:completion(Uri, 14, 48, CompletionKind, <<":">>),
+ [Selected] = select_completionitems(
+ CompletionItems,
+ ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ <<"myopaque/0">>
+ ),
+ #{result := Result} = els_client:completionitem_resolve(Selected),
+
+ Value =
+ case has_eep48_edoc() of
+ true ->
+ <<
+ "```erlang\n-opaque myopaque() \n```\n\n---\n\n"
+ "This is my opaque\n"
+ >>;
+ false ->
+ <<
+ "```erlang\n"
+ "-opaque myopaque() :: term().\n"
+ "```"
+ >>
+ end,
+
+ Expected = Selected#{
+ documentation =>
+ #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result).
-spec resolve_type_application_remote_external(config()) -> ok.
resolve_type_application_remote_external(Config) ->
- Uri = ?config(completion_resolve_type_uri, Config),
- CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- #{result := CompletionItems} =
- els_client:completion(Uri, 15, 40, CompletionKind, <<":">>),
- [Selected] = select_completionitems(CompletionItems,
- ?COMPLETION_ITEM_KIND_TYPE_PARAM, <<"mytype/1">>),
- #{result := Result} = els_client:completionitem_resolve(Selected),
- Value = case has_eep48_edoc() of
- true -> <<"```erlang\n-type mytype(T) :: [T].\n```\n\n---\n\n"
- "Hello\n">>;
- false -> <<"```erlang\n-type mytype(T) :: [T].\n```">>
- end,
- Expected = Selected#{ documentation =>
- #{ kind => <<"markdown">>
- , value => Value
- }
- },
- ?assertEqual(Expected, Result).
+ Uri = ?config(completion_resolve_type_uri, Config),
+ CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ #{result := CompletionItems} =
+ els_client:completion(Uri, 15, 40, CompletionKind, <<":">>),
+ [Selected] = select_completionitems(
+ CompletionItems,
+ ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ <<"mytype/1">>
+ ),
+ #{result := Result} = els_client:completionitem_resolve(Selected),
+ Value =
+ case has_eep48_edoc() of
+ true ->
+ <<
+ "```erlang\n-type mytype(T) :: [T].\n```\n\n---\n\n"
+ "Hello\n"
+ >>;
+ false ->
+ <<"```erlang\n-type mytype(T) :: [T].\n```">>
+ end,
+ Expected = Selected#{
+ documentation =>
+ #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result).
-spec resolve_opaque_application_remote_external(config()) -> ok.
resolve_opaque_application_remote_external(Config) ->
- Uri = ?config(completion_resolve_type_uri, Config),
- CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- #{result := CompletionItems} =
- els_client:completion(Uri, 15, 40, CompletionKind, <<":">>),
- [Selected] = select_completionitems(CompletionItems,
- ?COMPLETION_ITEM_KIND_TYPE_PARAM, <<"myopaque/1">>),
- #{result := Result} = els_client:completionitem_resolve(Selected),
- Value = case has_eep48_edoc() of
- true -> <<"```erlang\n-opaque myopaque(T) \n```\n\n---\n\n"
- "Is there anybody in there\n">>;
- false -> <<"```erlang\n-opaque myopaque(T) :: [T].\n```">>
- end,
- Expected = Selected#{ documentation =>
- #{ kind => <<"markdown">>
- , value => Value
- }
- },
- ?assertEqual(Expected, Result).
+ Uri = ?config(completion_resolve_type_uri, Config),
+ CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ #{result := CompletionItems} =
+ els_client:completion(Uri, 15, 40, CompletionKind, <<":">>),
+ [Selected] = select_completionitems(
+ CompletionItems,
+ ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ <<"myopaque/1">>
+ ),
+ #{result := Result} = els_client:completionitem_resolve(Selected),
+ Value =
+ case has_eep48_edoc() of
+ true ->
+ <<
+ "```erlang\n-opaque myopaque(T) \n```\n\n---\n\n"
+ "Is there anybody in there\n"
+ >>;
+ false ->
+ <<"```erlang\n-opaque myopaque(T) :: [T].\n```">>
+ end,
+ Expected = Selected#{
+ documentation =>
+ #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result).
-spec resolve_type_application_remote_otp(config()) -> ok.
resolve_type_application_remote_otp(Config) ->
- Uri = ?config(completion_resolve_type_uri, Config),
- CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
- #{result := CompletionItems} =
- els_client:completion(Uri, 17, 8, CompletionKind, <<":">>),
- [Selected] = select_completionitems(CompletionItems,
- ?COMPLETION_ITEM_KIND_TYPE_PARAM, <<"name_all/0">>),
- #{result := Result} = els_client:completionitem_resolve(Selected),
- Value = case has_eep48(file) of
- true -> <<"```erlang\n-type name_all() ::\n string() |"
- " atom() | deep_list() | (RawFilename :: binary()).\n"
- "```\n\n---\n\nIf VM is in Unicode filename mode, "
- "characters are allowed to be \\> 255\\. `RawFilename`"
- " is a filename not subject to Unicode translation, "
- "meaning that it can contain characters not conforming"
- " to the Unicode encoding expected from the file system"
- " \\(that is, non\\-UTF\\-8 characters although the VM is"
- " started in Unicode filename mode\\)\\. Null characters "
- "\\(integer value zero\\) are *not* allowed in filenames "
- "\\(not even at the end\\)\\.\n">>;
- false -> <<"```erlang\n-type name_all() :: "
- "string() | atom() | deep_list() | "
- "(RawFilename :: binary()).\n```">>
- end,
- Expected = Selected#{ documentation =>
- #{ kind => <<"markdown">>
- , value => Value
- }
- },
- ?assertEqual(Expected, Result).
+ Uri = ?config(completion_resolve_type_uri, Config),
+ CompletionKind = ?COMPLETION_TRIGGER_KIND_INVOKED,
+ #{result := CompletionItems} =
+ els_client:completion(Uri, 17, 8, CompletionKind, <<":">>),
+ [Selected] = select_completionitems(
+ CompletionItems,
+ ?COMPLETION_ITEM_KIND_TYPE_PARAM,
+ <<"name_all/0">>
+ ),
+ #{result := Result} = els_client:completionitem_resolve(Selected),
+ Value =
+ case has_eep48(file) of
+ true ->
+ <<
+ "```erlang\n-type name_all() ::\n string() |"
+ " atom() | deep_list() | (RawFilename :: binary()).\n"
+ "```\n\n---\n\nIf VM is in Unicode filename mode, "
+ "characters are allowed to be \\> 255\\. `RawFilename`"
+ " is a filename not subject to Unicode translation, "
+ "meaning that it can contain characters not conforming"
+ " to the Unicode encoding expected from the file system"
+ " \\(that is, non\\-UTF\\-8 characters although the VM is"
+ " started in Unicode filename mode\\)\\. Null characters "
+ "\\(integer value zero\\) are *not* allowed in filenames "
+ "\\(not even at the end\\)\\.\n"
+ >>;
+ false ->
+ <<
+ "```erlang\n-type name_all() :: "
+ "string() | atom() | deep_list() | "
+ "(RawFilename :: binary()).\n```"
+ >>
+ end,
+ Expected = Selected#{
+ documentation =>
+ #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result).
select_completionitems(CompletionItems, Kind, Label) ->
- [CI || #{ kind := K , label := L
- } = CI <- CompletionItems, L =:= Label, K =:= Kind].
+ [CI || #{kind := K, label := L} = CI <- CompletionItems, L =:= Label, K =:= Kind].
has_eep48_edoc() ->
- list_to_integer(erlang:system_info(otp_release)) >= 24.
+ list_to_integer(erlang:system_info(otp_release)) >= 24.
has_eep48(Module) ->
- case catch code:get_doc(Module) of
- {ok, _} -> true;
- _ -> false
- end.
+ case catch code:get_doc(Module) of
+ {ok, _} -> true;
+ _ -> false
+ end.
diff --git a/apps/els_lsp/test/els_definition_SUITE.erl b/apps/els_lsp/test/els_definition_SUITE.erl
index dc2ed222b..2721d360e 100644
--- a/apps/els_lsp/test/els_definition_SUITE.erl
+++ b/apps/els_lsp/test/els_definition_SUITE.erl
@@ -4,53 +4,55 @@
-module(els_definition_SUITE).
%% CT Callbacks
--export([ all/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , suite/0
- ]).
+-export([
+ all/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ suite/0
+]).
%% Test cases
--export([ application_local/1
- , application_remote/1
- , atom/1
- , behaviour/1
- , definition_after_closing/1
- , duplicate_definition/1
- , export_entry/1
- , fun_local/1
- , fun_remote/1
- , import_entry/1
- , module_import_entry/1
- , include/1
- , include_lib/1
- , macro/1
- , macro_lowercase/1
- , macro_included/1
- , macro_with_args/1
- , macro_with_args_included/1
- , macro_with_implicit_args/1
- , parse_transform/1
- , record_access/1
- , record_access_included/1
- , record_access_macro_name/1
- , record_expr/1
- , record_expr_included/1
- , record_expr_macro_name/1
- , record_field/1
- , record_field_included/1
- , record_type_macro_name/1
- , type_application_remote/1
- , type_application_undefined/1
- , type_application_user/1
- , type_export_entry/1
- , variable/1
- , opaque_application_remote/1
- , opaque_application_user/1
- , parse_incomplete/1
- ]).
+-export([
+ application_local/1,
+ application_remote/1,
+ atom/1,
+ behaviour/1,
+ definition_after_closing/1,
+ duplicate_definition/1,
+ export_entry/1,
+ fun_local/1,
+ fun_remote/1,
+ import_entry/1,
+ module_import_entry/1,
+ include/1,
+ include_lib/1,
+ macro/1,
+ macro_lowercase/1,
+ macro_included/1,
+ macro_with_args/1,
+ macro_with_args_included/1,
+ macro_with_implicit_args/1,
+ parse_transform/1,
+ record_access/1,
+ record_access_included/1,
+ record_access_macro_name/1,
+ record_expr/1,
+ record_expr_included/1,
+ record_expr_macro_name/1,
+ record_field/1,
+ record_field_included/1,
+ record_type_macro_name/1,
+ type_application_remote/1,
+ type_application_undefined/1,
+ type_application_user/1,
+ type_export_entry/1,
+ variable/1,
+ opaque_application_remote/1,
+ opaque_application_user/1,
+ parse_incomplete/1
+]).
%%==============================================================================
%% Includes
@@ -68,461 +70,564 @@
%%==============================================================================
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(TestCase, Config) ->
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
%%==============================================================================
%% Testcases
%%==============================================================================
-spec application_local(config()) -> ok.
application_local(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 22, 5),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(Uri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {25, 1}, to => {25, 11}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 22, 5),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {25, 1}, to => {25, 11}}),
+ Range
+ ),
+ ok.
-spec application_remote(config()) -> ok.
application_remote(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 32, 13),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(?config(code_navigation_extra_uri, Config), DefUri),
- ?assertEqual( els_protocol:range(#{from => {5, 1}, to => {5, 3}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 32, 13),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(?config(code_navigation_extra_uri, Config), DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {5, 1}, to => {5, 3}}),
+ Range
+ ),
+ ok.
-spec atom(config()) -> ok.
atom(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def0 = els_client:definition(Uri, 84, 20),
- Def1 = els_client:definition(Uri, 85, 20),
- Def2 = els_client:definition(Uri, 86, 20),
- Def3 = els_client:definition(Uri, 85, 27),
- #{result := #{range := Range0, uri := DefUri0}} = Def0,
- #{result := #{range := Range1, uri := DefUri1}} = Def1,
- #{result := #{range := Range2, uri := DefUri2}} = Def2,
- #{result := #{range := Range3, uri := DefUri3}} = Def3,
- ?assertEqual(?config(code_navigation_types_uri, Config), DefUri0),
- ?assertEqual( els_protocol:range(#{from => {1, 9}, to => {1, 30}})
- , Range0),
- ?assertEqual(?config(code_navigation_extra_uri, Config), DefUri1),
- ?assertEqual( els_protocol:range(#{from => {1, 9}, to => {1, 30}})
- , Range1),
- ?assertEqual(?config(code_navigation_extra_uri, Config), DefUri2),
- ?assertEqual( els_protocol:range(#{from => {1, 9}, to => {1, 30}})
- , Range2),
- ?assertEqual(?config('Code.Navigation.Elixirish_uri', Config), DefUri3),
- ?assertEqual( els_protocol:range(#{from => {1, 9}, to => {1, 36}})
- , Range3),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def0 = els_client:definition(Uri, 84, 20),
+ Def1 = els_client:definition(Uri, 85, 20),
+ Def2 = els_client:definition(Uri, 86, 20),
+ Def3 = els_client:definition(Uri, 85, 27),
+ #{result := #{range := Range0, uri := DefUri0}} = Def0,
+ #{result := #{range := Range1, uri := DefUri1}} = Def1,
+ #{result := #{range := Range2, uri := DefUri2}} = Def2,
+ #{result := #{range := Range3, uri := DefUri3}} = Def3,
+ ?assertEqual(?config(code_navigation_types_uri, Config), DefUri0),
+ ?assertEqual(
+ els_protocol:range(#{from => {1, 9}, to => {1, 30}}),
+ Range0
+ ),
+ ?assertEqual(?config(code_navigation_extra_uri, Config), DefUri1),
+ ?assertEqual(
+ els_protocol:range(#{from => {1, 9}, to => {1, 30}}),
+ Range1
+ ),
+ ?assertEqual(?config(code_navigation_extra_uri, Config), DefUri2),
+ ?assertEqual(
+ els_protocol:range(#{from => {1, 9}, to => {1, 30}}),
+ Range2
+ ),
+ ?assertEqual(?config('Code.Navigation.Elixirish_uri', Config), DefUri3),
+ ?assertEqual(
+ els_protocol:range(#{from => {1, 9}, to => {1, 36}}),
+ Range3
+ ),
+ ok.
-spec behaviour(config()) -> ok.
behaviour(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 3, 16),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(?config(behaviour_a_uri, Config), DefUri),
- ?assertEqual( els_protocol:range(#{from => {1, 9}, to => {1, 20}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 3, 16),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(?config(behaviour_a_uri, Config), DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {1, 9}, to => {1, 20}}),
+ Range
+ ),
+ ok.
%% Issue #191: Definition not found after document is closed
-spec definition_after_closing(config()) -> ok.
definition_after_closing(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- ExtraUri = ?config(code_navigation_extra_uri, Config),
- Def = els_client:definition(Uri, 32, 13),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(ExtraUri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {5, 1}, to => {5, 3}})
- , Range),
-
- %% Close file, get definition
- ok = els_client:did_close(ExtraUri),
- Def1 = els_client:definition(Uri, 32, 13),
- #{result := #{range := Range, uri := DefUri}} = Def1,
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ ExtraUri = ?config(code_navigation_extra_uri, Config),
+ Def = els_client:definition(Uri, 32, 13),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(ExtraUri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {5, 1}, to => {5, 3}}),
+ Range
+ ),
+
+ %% Close file, get definition
+ ok = els_client:did_close(ExtraUri),
+ Def1 = els_client:definition(Uri, 32, 13),
+ #{result := #{range := Range, uri := DefUri}} = Def1,
+ ok.
-spec duplicate_definition(config()) -> ok.
duplicate_definition(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 57, 5),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(Uri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {60, 1}, to => {60, 11}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 57, 5),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {60, 1}, to => {60, 11}}),
+ Range
+ ),
+ ok.
-spec export_entry(config()) -> ok.
export_entry(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 8, 15),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(Uri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {28, 1}, to => {28, 11}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 8, 15),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {28, 1}, to => {28, 11}}),
+ Range
+ ),
+ ok.
-spec fun_local(config()) -> ok.
fun_local(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 51, 16),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(Uri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {25, 1}, to => {25, 11}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 51, 16),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {25, 1}, to => {25, 11}}),
+ Range
+ ),
+ ok.
-spec fun_remote(config()) -> ok.
fun_remote(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 52, 14),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(?config(code_navigation_extra_uri, Config), DefUri),
- ?assertEqual( els_protocol:range(#{from => {5, 1}, to => {5, 3}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 52, 14),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(?config(code_navigation_extra_uri, Config), DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {5, 1}, to => {5, 3}}),
+ Range
+ ),
+ ok.
-spec import_entry(config()) -> ok.
import_entry(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 10, 34),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(?config(code_navigation_extra_uri, Config), DefUri),
- ?assertEqual( els_protocol:range(#{from => {5, 1}, to => {5, 3}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 10, 34),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(?config(code_navigation_extra_uri, Config), DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {5, 1}, to => {5, 3}}),
+ Range
+ ),
+ ok.
-spec module_import_entry(config()) -> ok.
module_import_entry(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 90, 3),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(?config(code_navigation_extra_uri, Config), DefUri),
- ?assertEqual( els_protocol:range(#{from => {5, 1}, to => {5, 3}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 90, 3),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(?config(code_navigation_extra_uri, Config), DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {5, 1}, to => {5, 3}}),
+ Range
+ ),
+ ok.
-spec include(config()) -> ok.
include(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 12, 20),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(?config(code_navigation_h_uri, Config), DefUri),
- ?assertEqual( els_protocol:range(#{from => {1, 1}, to => {1, 1}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 12, 20),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(?config(code_navigation_h_uri, Config), DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {1, 1}, to => {1, 1}}),
+ Range
+ ),
+ ok.
-spec include_lib(config()) -> ok.
include_lib(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 13, 22),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(?config(code_navigation_h_uri, Config), DefUri),
- ?assertEqual( els_protocol:range(#{from => {1, 1}, to => {1, 1}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 13, 22),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(?config(code_navigation_h_uri, Config), DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {1, 1}, to => {1, 1}}),
+ Range
+ ),
+ ok.
-spec macro(config()) -> ok.
macro(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 26, 5),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(Uri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {18, 9}, to => {18, 16}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 26, 5),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {18, 9}, to => {18, 16}}),
+ Range
+ ),
+ ok.
-spec macro_lowercase(config()) -> ok.
macro_lowercase(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 48, 3),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(Uri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {45, 9}, to => {45, 16}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 48, 3),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {45, 9}, to => {45, 16}}),
+ Range
+ ),
+ ok.
-spec macro_included(config()) -> ok.
macro_included(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- UriHeader = ?config(code_navigation_h_uri, Config),
- #{result := #{range := Range1, uri := DefUri1}} =
- els_client:definition(Uri, 53, 19),
- ?assertEqual(UriHeader, DefUri1),
- ?assertEqual( els_protocol:range(#{from => {3, 9}, to => {3, 25}})
- , Range1),
- #{result := #{range := RangeQuoted, uri := DefUri2}} =
- els_client:definition(Uri, 52, 75),
- ?assertEqual(UriHeader, DefUri2),
- ?assertEqual( els_protocol:range(#{from => {7, 9}, to => {7, 27}})
- , RangeQuoted),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ UriHeader = ?config(code_navigation_h_uri, Config),
+ #{result := #{range := Range1, uri := DefUri1}} =
+ els_client:definition(Uri, 53, 19),
+ ?assertEqual(UriHeader, DefUri1),
+ ?assertEqual(
+ els_protocol:range(#{from => {3, 9}, to => {3, 25}}),
+ Range1
+ ),
+ #{result := #{range := RangeQuoted, uri := DefUri2}} =
+ els_client:definition(Uri, 52, 75),
+ ?assertEqual(UriHeader, DefUri2),
+ ?assertEqual(
+ els_protocol:range(#{from => {7, 9}, to => {7, 27}}),
+ RangeQuoted
+ ),
+ ok.
-spec macro_with_args(config()) -> ok.
macro_with_args(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 40, 9),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(Uri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {19, 9}, to => {19, 16}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 40, 9),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {19, 9}, to => {19, 16}}),
+ Range
+ ),
+ ok.
-spec macro_with_args_included(config()) -> ok.
macro_with_args_included(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 43, 9),
- #{result := #{uri := DefUri}} = Def,
- ?assertEqual( <<"assert.hrl">>
- , filename:basename(els_uri:path(DefUri))),
- %% Do not assert on line number to avoid binding to a specific OTP version
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 43, 9),
+ #{result := #{uri := DefUri}} = Def,
+ ?assertEqual(
+ <<"assert.hrl">>,
+ filename:basename(els_uri:path(DefUri))
+ ),
+ %% Do not assert on line number to avoid binding to a specific OTP version
+ ok.
-spec macro_with_implicit_args(config()) -> ok.
macro_with_implicit_args(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 124, 5),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(Uri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {118, 9}, to => {118, 16}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 124, 5),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {118, 9}, to => {118, 16}}),
+ Range
+ ),
+ ok.
-spec parse_transform(config()) -> ok.
parse_transform(Config) ->
- Uri = ?config(diagnostics_parse_transform_usage_uri, Config),
- Def = els_client:definition(Uri, 5, 45),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(?config(diagnostics_parse_transform_uri, Config), DefUri),
- ?assertEqual( els_protocol:range(#{from => {1, 9}, to => {1, 36}})
- , Range),
- ok.
+ Uri = ?config(diagnostics_parse_transform_usage_uri, Config),
+ Def = els_client:definition(Uri, 5, 45),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(?config(diagnostics_parse_transform_uri, Config), DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {1, 9}, to => {1, 36}}),
+ Range
+ ),
+ ok.
-spec record_access(config()) -> ok.
record_access(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 34, 13),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(Uri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {16, 9}, to => {16, 17}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 34, 13),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {16, 9}, to => {16, 17}}),
+ Range
+ ),
+ ok.
-spec record_access_included(config()) -> ok.
record_access_included(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 52, 43),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(?config(code_navigation_h_uri, Config), DefUri),
- ?assertEqual( els_protocol:range(#{from => {1, 9}, to => {1, 26}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 52, 43),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(?config(code_navigation_h_uri, Config), DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {1, 9}, to => {1, 26}}),
+ Range
+ ),
+ ok.
-spec record_access_macro_name(config()) -> ok.
record_access_macro_name(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 116, 33),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(Uri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {111, 9}, to => {111, 16}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 116, 33),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {111, 9}, to => {111, 16}}),
+ Range
+ ),
+ ok.
%% TODO: Additional constructors for POI
%% TODO: Navigation should return POI, not range
-spec record_expr(config()) -> ok.
record_expr(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 33, 11),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(Uri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {16, 9}, to => {16, 17}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 33, 11),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {16, 9}, to => {16, 17}}),
+ Range
+ ),
+ ok.
-spec record_expr_included(config()) -> ok.
record_expr_included(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 53, 30),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(?config(code_navigation_h_uri, Config), DefUri),
- ?assertEqual( els_protocol:range(#{from => {1, 9}, to => {1, 26}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 53, 30),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(?config(code_navigation_h_uri, Config), DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {1, 9}, to => {1, 26}}),
+ Range
+ ),
+ ok.
-spec record_expr_macro_name(config()) -> ok.
record_expr_macro_name(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 115, 11),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(Uri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {111, 9}, to => {111, 16}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 115, 11),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {111, 9}, to => {111, 16}}),
+ Range
+ ),
+ ok.
-spec record_field(config()) -> ok.
record_field(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 33, 20),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(Uri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {16, 20}, to => {16, 27}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 33, 20),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {16, 20}, to => {16, 27}}),
+ Range
+ ),
+ ok.
-spec record_field_included(config()) -> ok.
record_field_included(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 53, 45),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(?config(code_navigation_h_uri, Config), DefUri),
- ?assertEqual( els_protocol:range(#{from => {1, 29}, to => {1, 45}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 53, 45),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(?config(code_navigation_h_uri, Config), DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {1, 29}, to => {1, 45}}),
+ Range
+ ),
+ ok.
-spec record_type_macro_name(config()) -> ok.
record_type_macro_name(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 113, 28),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(Uri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {111, 9}, to => {111, 16}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 113, 28),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {111, 9}, to => {111, 16}}),
+ Range
+ ),
+ ok.
-spec type_application_remote(config()) -> ok.
type_application_remote(Config) ->
- ExtraUri = ?config(code_navigation_extra_uri, Config),
- TypesUri = ?config(code_navigation_types_uri, Config),
- Def = els_client:definition(ExtraUri, 11, 38),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(TypesUri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {3, 1}, to => {3, 26}})
- , Range),
- ok.
+ ExtraUri = ?config(code_navigation_extra_uri, Config),
+ TypesUri = ?config(code_navigation_types_uri, Config),
+ Def = els_client:definition(ExtraUri, 11, 38),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(TypesUri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {3, 1}, to => {3, 26}}),
+ Range
+ ),
+ ok.
-spec type_application_undefined(config()) -> ok.
type_application_undefined(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 55, 42),
- #{result := Result} = Def,
- Expected = [
- #{ range => #{'end' => #{character => 49, line => 54},
- start => #{character => 33, line => 54}}
- , uri => Uri}
- ],
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 55, 42),
+ #{result := Result} = Def,
+ Expected = [
+ #{
+ range => #{
+ 'end' => #{character => 49, line => 54},
+ start => #{character => 33, line => 54}
+ },
+ uri => Uri
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
-spec type_application_user(config()) -> ok.
type_application_user(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 55, 25),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(Uri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {37, 1}, to => {37, 25}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 55, 25),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {37, 1}, to => {37, 25}}),
+ Range
+ ),
+ ok.
-spec type_export_entry(config()) -> ok.
type_export_entry(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def = els_client:definition(Uri, 9, 17),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(Uri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {37, 1}, to => {37, 25}})
- , Range),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ Def = els_client:definition(Uri, 9, 17),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {37, 1}, to => {37, 25}}),
+ Range
+ ),
+ ok.
-spec variable(config()) -> ok.
variable(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- Def0 = els_client:definition(Uri, 104, 9),
- Def1 = els_client:definition(Uri, 105, 10),
- Def2 = els_client:definition(Uri, 107, 10),
- Def3 = els_client:definition(Uri, 108, 10),
- Def4 = els_client:definition(Uri, 19, 36),
- #{result := #{range := Range0, uri := DefUri0}} = Def0,
- #{result := #{range := Range1, uri := DefUri0}} = Def1,
- #{result := #{range := Range2, uri := DefUri0}} = Def2,
- #{result := #{range := Range3, uri := DefUri0}} = Def3,
- #{result := #{range := Range4, uri := DefUri0}} = Def4,
-
- ?assertEqual(?config(code_navigation_uri, Config), DefUri0),
- ?assertEqual( els_protocol:range(#{from => {103, 12}, to => {103, 15}})
- , Range0),
- ?assertEqual( els_protocol:range(#{from => {104, 3}, to => {104, 6}})
- , Range1),
- ?assertEqual( els_protocol:range(#{from => {106, 12}, to => {106, 15}})
- , Range2),
- ?assertEqual( els_protocol:range(#{from => {106, 12}, to => {106, 15}})
- , Range3),
- %% Inside macro
- ?assertEqual( els_protocol:range(#{from => {19, 17}, to => {19, 18}})
- , Range4),
- ok.
-
+ Uri = ?config(code_navigation_uri, Config),
+ Def0 = els_client:definition(Uri, 104, 9),
+ Def1 = els_client:definition(Uri, 105, 10),
+ Def2 = els_client:definition(Uri, 107, 10),
+ Def3 = els_client:definition(Uri, 108, 10),
+ Def4 = els_client:definition(Uri, 19, 36),
+ #{result := #{range := Range0, uri := DefUri0}} = Def0,
+ #{result := #{range := Range1, uri := DefUri0}} = Def1,
+ #{result := #{range := Range2, uri := DefUri0}} = Def2,
+ #{result := #{range := Range3, uri := DefUri0}} = Def3,
+ #{result := #{range := Range4, uri := DefUri0}} = Def4,
+
+ ?assertEqual(?config(code_navigation_uri, Config), DefUri0),
+ ?assertEqual(
+ els_protocol:range(#{from => {103, 12}, to => {103, 15}}),
+ Range0
+ ),
+ ?assertEqual(
+ els_protocol:range(#{from => {104, 3}, to => {104, 6}}),
+ Range1
+ ),
+ ?assertEqual(
+ els_protocol:range(#{from => {106, 12}, to => {106, 15}}),
+ Range2
+ ),
+ ?assertEqual(
+ els_protocol:range(#{from => {106, 12}, to => {106, 15}}),
+ Range3
+ ),
+ %% Inside macro
+ ?assertEqual(
+ els_protocol:range(#{from => {19, 17}, to => {19, 18}}),
+ Range4
+ ),
+ ok.
-spec opaque_application_remote(config()) -> ok.
opaque_application_remote(Config) ->
- ExtraUri = ?config(code_navigation_extra_uri, Config),
- TypesUri = ?config(code_navigation_types_uri, Config),
- Def = els_client:definition(ExtraUri, 16, 61),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(TypesUri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {7, 1}, to => {7, 35}})
- , Range),
- ok.
+ ExtraUri = ?config(code_navigation_extra_uri, Config),
+ TypesUri = ?config(code_navigation_types_uri, Config),
+ Def = els_client:definition(ExtraUri, 16, 61),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(TypesUri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {7, 1}, to => {7, 35}}),
+ Range
+ ),
+ ok.
-spec opaque_application_user(config()) -> ok.
opaque_application_user(Config) ->
- ExtraUri = ?config(code_navigation_extra_uri, Config),
- Def = els_client:definition(ExtraUri, 16, 24),
- #{result := #{range := Range, uri := DefUri}} = Def,
- ?assertEqual(ExtraUri, DefUri),
- ?assertEqual( els_protocol:range(#{from => {20, 1}, to => {20, 34}})
- , Range),
- ok.
+ ExtraUri = ?config(code_navigation_extra_uri, Config),
+ Def = els_client:definition(ExtraUri, 16, 24),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(ExtraUri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {20, 1}, to => {20, 34}}),
+ Range
+ ),
+ ok.
-spec parse_incomplete(config()) -> ok.
parse_incomplete(Config) ->
- Uri = ?config(code_navigation_broken_uri, Config),
- Range = els_protocol:range(#{from => {3, 1}, to => {3, 11}}),
- ?assertMatch( #{result := #{range := Range, uri := Uri}}
- , els_client:definition(Uri, 7, 3)),
- ?assertMatch( #{result := #{range := Range, uri := Uri}}
- , els_client:definition(Uri, 8, 3)),
- ?assertMatch( #{result := #{range := Range, uri := Uri}}
- , els_client:definition(Uri, 9, 8)),
- ?assertMatch( #{result := #{range := Range, uri := Uri}}
- , els_client:definition(Uri, 11, 7)),
- ?assertMatch( #{result := #{range := Range, uri := Uri}}
- , els_client:definition(Uri, 12, 12)),
- ?assertMatch( #{result := #{range := Range, uri := Uri}}
- , els_client:definition(Uri, 17, 3)),
- ?assertMatch( #{result := #{range := Range, uri := Uri}}
- , els_client:definition(Uri, 19, 3)),
- ok.
+ Uri = ?config(code_navigation_broken_uri, Config),
+ Range = els_protocol:range(#{from => {3, 1}, to => {3, 11}}),
+ ?assertMatch(
+ #{result := #{range := Range, uri := Uri}},
+ els_client:definition(Uri, 7, 3)
+ ),
+ ?assertMatch(
+ #{result := #{range := Range, uri := Uri}},
+ els_client:definition(Uri, 8, 3)
+ ),
+ ?assertMatch(
+ #{result := #{range := Range, uri := Uri}},
+ els_client:definition(Uri, 9, 8)
+ ),
+ ?assertMatch(
+ #{result := #{range := Range, uri := Uri}},
+ els_client:definition(Uri, 11, 7)
+ ),
+ ?assertMatch(
+ #{result := #{range := Range, uri := Uri}},
+ els_client:definition(Uri, 12, 12)
+ ),
+ ?assertMatch(
+ #{result := #{range := Range, uri := Uri}},
+ els_client:definition(Uri, 17, 3)
+ ),
+ ?assertMatch(
+ #{result := #{range := Range, uri := Uri}},
+ els_client:definition(Uri, 19, 3)
+ ),
+ ok.
diff --git a/apps/els_lsp/test/els_diagnostics_SUITE.erl b/apps/els_lsp/test/els_diagnostics_SUITE.erl
index 3cf0d48c6..361fa1ff0 100644
--- a/apps/els_lsp/test/els_diagnostics_SUITE.erl
+++ b/apps/els_lsp/test/els_diagnostics_SUITE.erl
@@ -1,53 +1,55 @@
-module(els_diagnostics_SUITE).
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ bound_var_in_pattern/1
- , compiler/1
- , compiler_with_behaviour/1
- , compiler_with_broken_behaviour/1
- , compiler_with_custom_macros/1
- , compiler_with_parse_transform/1
- , compiler_with_parse_transform_list/1
- , compiler_with_parse_transform_included/1
- , compiler_with_parse_transform_broken/1
- , compiler_with_parse_transform_deps/1
- , compiler_with_parse_transform_error/1
- , compiler_telemetry/1
- , code_path_extra_dirs/1
- , use_long_names/1
- , epp_with_nonexistent_macro/1
- , code_reload/1
- , code_reload_sticky_mod/1
- , elvis/1
- , escript/1
- , escript_warnings/1
- , escript_errors/1
- , crossref/1
- , crossref_autoimport/1
- , crossref_autoimport_disabled/1
- , crossref_pseudo_functions/1
- , unused_includes/1
- , unused_includes_compiler_attribute/1
- , exclude_unused_includes/1
- , unused_macros/1
- , unused_macros_refactorerl/1
- , unused_record_fields/1
- , gradualizer/1
- , module_name_check/1
- , module_name_check_whitespace/1
- , edoc_main/1
- , edoc_skip_app_src/1
- , edoc_custom_tags/1
- ]).
+-export([
+ bound_var_in_pattern/1,
+ compiler/1,
+ compiler_with_behaviour/1,
+ compiler_with_broken_behaviour/1,
+ compiler_with_custom_macros/1,
+ compiler_with_parse_transform/1,
+ compiler_with_parse_transform_list/1,
+ compiler_with_parse_transform_included/1,
+ compiler_with_parse_transform_broken/1,
+ compiler_with_parse_transform_deps/1,
+ compiler_with_parse_transform_error/1,
+ compiler_telemetry/1,
+ code_path_extra_dirs/1,
+ use_long_names/1,
+ epp_with_nonexistent_macro/1,
+ code_reload/1,
+ code_reload_sticky_mod/1,
+ elvis/1,
+ escript/1,
+ escript_warnings/1,
+ escript_errors/1,
+ crossref/1,
+ crossref_autoimport/1,
+ crossref_autoimport_disabled/1,
+ crossref_pseudo_functions/1,
+ unused_includes/1,
+ unused_includes_compiler_attribute/1,
+ exclude_unused_includes/1,
+ unused_macros/1,
+ unused_macros_refactorerl/1,
+ unused_record_fields/1,
+ gradualizer/1,
+ module_name_check/1,
+ module_name_check_whitespace/1,
+ edoc_main/1,
+ edoc_skip_app_src/1,
+ edoc_custom_tags/1
+]).
%%==============================================================================
%% Includes
@@ -66,820 +68,974 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
-init_per_testcase(TestCase, Config) when TestCase =:= code_reload orelse
- TestCase =:= code_reload_sticky_mod ->
- mock_rpc(),
- mock_code_reload_enabled(),
- els_test_utils:init_per_testcase(TestCase, Config);
-init_per_testcase(TestCase, Config)
- when TestCase =:= crossref orelse
- TestCase =:= crossref_pseudo_functions orelse
- TestCase =:= crossref_autoimport orelse
- TestCase =:= crossref_autoimport_disabled ->
- meck:new(els_crossref_diagnostics, [passthrough, no_link]),
- meck:expect(els_crossref_diagnostics, is_default, 0, true),
- els_mock_diagnostics:setup(),
- els_test_utils:init_per_testcase(TestCase, Config);
+init_per_testcase(TestCase, Config) when
+ TestCase =:= code_reload orelse
+ TestCase =:= code_reload_sticky_mod
+->
+ mock_rpc(),
+ mock_code_reload_enabled(),
+ els_test_utils:init_per_testcase(TestCase, Config);
+init_per_testcase(TestCase, Config) when
+ TestCase =:= crossref orelse
+ TestCase =:= crossref_pseudo_functions orelse
+ TestCase =:= crossref_autoimport orelse
+ TestCase =:= crossref_autoimport_disabled
+->
+ meck:new(els_crossref_diagnostics, [passthrough, no_link]),
+ meck:expect(els_crossref_diagnostics, is_default, 0, true),
+ els_mock_diagnostics:setup(),
+ els_test_utils:init_per_testcase(TestCase, Config);
init_per_testcase(code_path_extra_dirs, Config) ->
- meck:new(yamerl, [passthrough, no_link]),
- Content = <<"code_path_extra_dirs:\n",
- " - \"../code_navigation/*/\"\n">>,
- meck:expect(yamerl, decode_file, 2, fun(_, Opts) ->
- yamerl:decode(Content, Opts)
- end),
- els_mock_diagnostics:setup(),
- els_test_utils:init_per_testcase(code_path_extra_dirs, Config);
+ meck:new(yamerl, [passthrough, no_link]),
+ Content = <<"code_path_extra_dirs:\n", " - \"../code_navigation/*/\"\n">>,
+ meck:expect(yamerl, decode_file, 2, fun(_, Opts) ->
+ yamerl:decode(Content, Opts)
+ end),
+ els_mock_diagnostics:setup(),
+ els_test_utils:init_per_testcase(code_path_extra_dirs, Config);
init_per_testcase(use_long_names, Config) ->
- meck:new(yamerl, [passthrough, no_link]),
- Content = <<"runtime:\n",
- " use_long_names: true\n",
- " cookie: mycookie\n",
- " node_name: my_node\n">>,
- meck:expect(yamerl, decode_file, 2, fun(_, Opts) ->
- yamerl:decode(Content, Opts)
- end),
- els_mock_diagnostics:setup(),
- els_test_utils:init_per_testcase(code_path_extra_dirs, Config);
+ meck:new(yamerl, [passthrough, no_link]),
+ Content =
+ <<"runtime:\n", " use_long_names: true\n", " cookie: mycookie\n",
+ " node_name: my_node\n">>,
+ meck:expect(yamerl, decode_file, 2, fun(_, Opts) ->
+ yamerl:decode(Content, Opts)
+ end),
+ els_mock_diagnostics:setup(),
+ els_test_utils:init_per_testcase(code_path_extra_dirs, Config);
init_per_testcase(exclude_unused_includes = TestCase, Config) ->
- els_mock_diagnostics:setup(),
- NewConfig = els_test_utils:init_per_testcase(TestCase, Config),
- els_config:set(exclude_unused_includes, ["et/include/et.hrl"]),
- NewConfig;
+ els_mock_diagnostics:setup(),
+ NewConfig = els_test_utils:init_per_testcase(TestCase, Config),
+ els_config:set(exclude_unused_includes, ["et/include/et.hrl"]),
+ NewConfig;
init_per_testcase(TestCase, Config) when TestCase =:= compiler_telemetry ->
- els_mock_diagnostics:setup(),
- mock_compiler_telemetry_enabled(),
- els_test_utils:init_per_testcase(TestCase, Config);
+ els_mock_diagnostics:setup(),
+ mock_compiler_telemetry_enabled(),
+ els_test_utils:init_per_testcase(TestCase, Config);
init_per_testcase(TestCase, Config) when TestCase =:= gradualizer ->
- meck:new(els_gradualizer_diagnostics, [passthrough, no_link]),
- meck:expect(els_gradualizer_diagnostics, is_default, 0, true),
- els_mock_diagnostics:setup(),
- els_test_utils:init_per_testcase(TestCase, Config);
-
-init_per_testcase(TestCase, Config) when TestCase =:= edoc_main;
- TestCase =:= edoc_skip_app_src;
- TestCase =:= edoc_custom_tags ->
- meck:new(els_edoc_diagnostics, [passthrough, no_link]),
- meck:expect(els_edoc_diagnostics, is_default, 0, true),
- els_mock_diagnostics:setup(),
- els_test_utils:init_per_testcase(TestCase, Config);
-
-
+ meck:new(els_gradualizer_diagnostics, [passthrough, no_link]),
+ meck:expect(els_gradualizer_diagnostics, is_default, 0, true),
+ els_mock_diagnostics:setup(),
+ els_test_utils:init_per_testcase(TestCase, Config);
+init_per_testcase(TestCase, Config) when
+ TestCase =:= edoc_main;
+ TestCase =:= edoc_skip_app_src;
+ TestCase =:= edoc_custom_tags
+->
+ meck:new(els_edoc_diagnostics, [passthrough, no_link]),
+ meck:expect(els_edoc_diagnostics, is_default, 0, true),
+ els_mock_diagnostics:setup(),
+ els_test_utils:init_per_testcase(TestCase, Config);
% RefactorErl
-init_per_testcase(TestCase, Config)
- when TestCase =:= unused_macros_refactorerl ->
- mock_refactorerl(),
- els_test_utils:init_per_testcase(TestCase, Config);
-
+init_per_testcase(TestCase, Config) when
+ TestCase =:= unused_macros_refactorerl
+->
+ mock_refactorerl(),
+ els_test_utils:init_per_testcase(TestCase, Config);
init_per_testcase(TestCase, Config) ->
- els_mock_diagnostics:setup(),
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_mock_diagnostics:setup(),
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
-end_per_testcase(TestCase, Config) when TestCase =:= code_reload orelse
- TestCase =:= code_reload_sticky_mod ->
- unmock_rpc(),
- unmock_code_reload_enabled(),
- els_test_utils:end_per_testcase(TestCase, Config);
-end_per_testcase(TestCase, Config)
- when TestCase =:= crossref orelse
- TestCase =:= crossref_pseudo_functions orelse
- TestCase =:= crossref_autoimport orelse
- TestCase =:= crossref_autoimport_disabled ->
- meck:unload(els_crossref_diagnostics),
- els_test_utils:end_per_testcase(TestCase, Config),
- els_mock_diagnostics:teardown(),
- ok;
-end_per_testcase(TestCase, Config)
- when TestCase =:= code_path_extra_dirs orelse
- TestCase =:= use_long_names ->
- meck:unload(yamerl),
- els_test_utils:end_per_testcase(code_path_extra_dirs, Config),
- els_mock_diagnostics:teardown(),
- ok;
+end_per_testcase(TestCase, Config) when
+ TestCase =:= code_reload orelse
+ TestCase =:= code_reload_sticky_mod
+->
+ unmock_rpc(),
+ unmock_code_reload_enabled(),
+ els_test_utils:end_per_testcase(TestCase, Config);
+end_per_testcase(TestCase, Config) when
+ TestCase =:= crossref orelse
+ TestCase =:= crossref_pseudo_functions orelse
+ TestCase =:= crossref_autoimport orelse
+ TestCase =:= crossref_autoimport_disabled
+->
+ meck:unload(els_crossref_diagnostics),
+ els_test_utils:end_per_testcase(TestCase, Config),
+ els_mock_diagnostics:teardown(),
+ ok;
+end_per_testcase(TestCase, Config) when
+ TestCase =:= code_path_extra_dirs orelse
+ TestCase =:= use_long_names
+->
+ meck:unload(yamerl),
+ els_test_utils:end_per_testcase(code_path_extra_dirs, Config),
+ els_mock_diagnostics:teardown(),
+ ok;
end_per_testcase(exclude_unused_includes = TestCase, Config) ->
- els_config:set(exclude_unused_includes, []),
- els_test_utils:end_per_testcase(TestCase, Config),
- els_mock_diagnostics:teardown(),
- ok;
+ els_config:set(exclude_unused_includes, []),
+ els_test_utils:end_per_testcase(TestCase, Config),
+ els_mock_diagnostics:teardown(),
+ ok;
end_per_testcase(TestCase, Config) when TestCase =:= compiler_telemetry ->
- unmock_compiler_telemetry_enabled(),
- els_test_utils:end_per_testcase(TestCase, Config),
- els_mock_diagnostics:teardown(),
- ok;
+ unmock_compiler_telemetry_enabled(),
+ els_test_utils:end_per_testcase(TestCase, Config),
+ els_mock_diagnostics:teardown(),
+ ok;
end_per_testcase(TestCase, Config) when TestCase =:= gradualizer ->
- meck:unload(els_gradualizer_diagnostics),
- els_test_utils:end_per_testcase(TestCase, Config),
- els_mock_diagnostics:teardown(),
- ok;
-
-end_per_testcase(TestCase, Config) when TestCase =:= edoc_main;
- TestCase =:= edoc_skip_app_src;
- TestCase =:= edoc_custom_tags ->
- meck:unload(els_edoc_diagnostics),
- els_test_utils:end_per_testcase(TestCase, Config),
- els_mock_diagnostics:teardown(),
- ok;
-
-end_per_testcase(TestCase, Config)
- when TestCase =:= unused_macros_refactorerl ->
- unmock_refactoerl(),
- els_test_utils:end_per_testcase(TestCase, Config),
- els_mock_diagnostics:teardown(),
- ok;
+ meck:unload(els_gradualizer_diagnostics),
+ els_test_utils:end_per_testcase(TestCase, Config),
+ els_mock_diagnostics:teardown(),
+ ok;
+end_per_testcase(TestCase, Config) when
+ TestCase =:= edoc_main;
+ TestCase =:= edoc_skip_app_src;
+ TestCase =:= edoc_custom_tags
+->
+ meck:unload(els_edoc_diagnostics),
+ els_test_utils:end_per_testcase(TestCase, Config),
+ els_mock_diagnostics:teardown(),
+ ok;
+end_per_testcase(TestCase, Config) when
+ TestCase =:= unused_macros_refactorerl
+->
+ unmock_refactoerl(),
+ els_test_utils:end_per_testcase(TestCase, Config),
+ els_mock_diagnostics:teardown(),
+ ok;
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config),
- els_mock_diagnostics:teardown(),
- ok.
+ els_test_utils:end_per_testcase(TestCase, Config),
+ els_mock_diagnostics:teardown(),
+ ok.
% RefactorErl
-
%%==============================================================================
%% Testcases
%%==============================================================================
-spec bound_var_in_pattern(config()) -> ok.
bound_var_in_pattern(_Config) ->
- Path = src_path("diagnostics_bound_var_in_pattern.erl"),
- Source = <<"BoundVarInPattern">>,
- Errors = [],
- Warnings = [],
- Hints = [ #{ message => <<"Bound variable in pattern: Var1">>
- , range => {{5, 2}, {5, 6}}}
- , #{ message => <<"Bound variable in pattern: Var2">>
- , range => {{9, 9}, {9, 13}}}
- , #{ message => <<"Bound variable in pattern: Var4">>
- , range => {{17, 8}, {17, 12}}}
- , #{ message => <<"Bound variable in pattern: Var3">>
- , range => {{15, 10}, {15, 14}}}
- , #{ message => <<"Bound variable in pattern: Var5">>
- , range => {{23, 6}, {23, 10}}}
- %% erl_syntax_lib:annotate_bindings does not handle named funs
- %% correctly
- %% , #{ message => <<"Bound variable in pattern: New">>
- %% , range => {{28, 6}, {28, 9}}}
- %% , #{ message => <<"Bound variable in pattern: F">>
- %% , range => {{29, 6}, {29, 9}}}
- ],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("diagnostics_bound_var_in_pattern.erl"),
+ Source = <<"BoundVarInPattern">>,
+ Errors = [],
+ Warnings = [],
+ Hints = [
+ #{
+ message => <<"Bound variable in pattern: Var1">>,
+ range => {{5, 2}, {5, 6}}
+ },
+ #{
+ message => <<"Bound variable in pattern: Var2">>,
+ range => {{9, 9}, {9, 13}}
+ },
+ #{
+ message => <<"Bound variable in pattern: Var4">>,
+ range => {{17, 8}, {17, 12}}
+ },
+ #{
+ message => <<"Bound variable in pattern: Var3">>,
+ range => {{15, 10}, {15, 14}}
+ },
+ #{
+ message => <<"Bound variable in pattern: Var5">>,
+ range => {{23, 6}, {23, 10}}
+ }
+ %% erl_syntax_lib:annotate_bindings does not handle named funs
+ %% correctly
+ %% , #{ message => <<"Bound variable in pattern: New">>
+ %% , range => {{28, 6}, {28, 9}}}
+ %% , #{ message => <<"Bound variable in pattern: F">>
+ %% , range => {{29, 6}, {29, 9}}}
+ ],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec compiler(config()) -> ok.
compiler(_Config) ->
- Path = src_path("diagnostics.erl"),
- Source = <<"Compiler">>,
- Errors = [ #{ code => <<"L0000">>
- , message => <<"Issue in included file (1): bad attribute">>
- , range => {{3, 0}, {3, 35}}}
- , #{ code => <<"L0000">>
- , message => <<"Issue in included file (3): bad attribute">>
- , range => {{3, 0}, {3, 35}}}
- , #{ code => <<"L1295">>
- , message => <<"type undefined_type() undefined">>
- , range => {{5, 30}, {5, 44}}}
- ],
- Warnings = [ #{ code => <<"L1230">>
- , message => <<"function main/1 is unused">>
- , range => {{6, 0}, {6, 4}}}
- ],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("diagnostics.erl"),
+ Source = <<"Compiler">>,
+ Errors = [
+ #{
+ code => <<"L0000">>,
+ message => <<"Issue in included file (1): bad attribute">>,
+ range => {{3, 0}, {3, 35}}
+ },
+ #{
+ code => <<"L0000">>,
+ message => <<"Issue in included file (3): bad attribute">>,
+ range => {{3, 0}, {3, 35}}
+ },
+ #{
+ code => <<"L1295">>,
+ message => <<"type undefined_type() undefined">>,
+ range => {{5, 30}, {5, 44}}
+ }
+ ],
+ Warnings = [
+ #{
+ code => <<"L1230">>,
+ message => <<"function main/1 is unused">>,
+ range => {{6, 0}, {6, 4}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec compiler_with_behaviour(config()) -> ok.
compiler_with_behaviour(_Config) ->
- Path = src_path("diagnostics_behaviour_impl.erl"),
- Source = <<"Compiler">>,
- Errors = [],
- Warnings = [ #{ code => <<"L1284">>
- , message =>
- <<"undefined callback function one/0 "
- "(behaviour 'diagnostics_behaviour')">>
- , range => {{2, 0}, {2, 34}}},
- #{ code => <<"L1284">>
- , message =>
- <<"undefined callback function two/0 "
- "(behaviour 'diagnostics_behaviour')">>
- , range => {{2, 0}, {2, 34}}}
- ],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("diagnostics_behaviour_impl.erl"),
+ Source = <<"Compiler">>,
+ Errors = [],
+ Warnings = [
+ #{
+ code => <<"L1284">>,
+ message =>
+ <<
+ "undefined callback function one/0 "
+ "(behaviour 'diagnostics_behaviour')"
+ >>,
+ range => {{2, 0}, {2, 34}}
+ },
+ #{
+ code => <<"L1284">>,
+ message =>
+ <<
+ "undefined callback function two/0 "
+ "(behaviour 'diagnostics_behaviour')"
+ >>,
+ range => {{2, 0}, {2, 34}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
%% Testing #614
-spec compiler_with_broken_behaviour(config()) -> ok.
compiler_with_broken_behaviour(_Config) ->
- Path = src_path("code_navigation.erl"),
- {ok, Session} = els_test:start_session(Path),
- Diagnostics = els_test:wait_for_diagnostics(Session, <<"Compiler">>),
- els_test:assert_contains(
- #{ code => <<"L0000">>
- , message => <<"Issue in included file (5): syntax error before: ">>
- , range => {{2, 0}, {2, 24}}}, Diagnostics).
+ Path = src_path("code_navigation.erl"),
+ {ok, Session} = els_test:start_session(Path),
+ Diagnostics = els_test:wait_for_diagnostics(Session, <<"Compiler">>),
+ els_test:assert_contains(
+ #{
+ code => <<"L0000">>,
+ message => <<"Issue in included file (5): syntax error before: ">>,
+ range => {{2, 0}, {2, 24}}
+ },
+ Diagnostics
+ ).
-spec compiler_with_custom_macros(config()) -> ok.
compiler_with_custom_macros(_Config) ->
- %% This test uses priv/code_navigation/erlang_ls.config to define
- %% some macros.
- Path = src_path("diagnostics_macros.erl"),
- Source = <<"Compiler">>,
- Errors = case els_test:compiler_returns_column_numbers() of
- true ->
- %% diagnostic_macro has a spec with no '.' at the end
- %% which causes the poi for the spec to becomes the
- %% entire spec + function. So this range here is 8
- %% lines long.
- [ #{ code => <<"E1507">>
- , message => <<"undefined macro 'UNDEFINED'">>
- , range => {{2, 0}, {10, 6}}
- }
- ];
- false ->
- [ #{ code => <<"E1507">>
- , message => <<"undefined macro 'UNDEFINED'">>
- , range => {{8, 0}, {9, 0}}
- }
- ]
- end,
- Warnings = [],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ %% This test uses priv/code_navigation/erlang_ls.config to define
+ %% some macros.
+ Path = src_path("diagnostics_macros.erl"),
+ Source = <<"Compiler">>,
+ Errors =
+ case els_test:compiler_returns_column_numbers() of
+ true ->
+ %% diagnostic_macro has a spec with no '.' at the end
+ %% which causes the poi for the spec to becomes the
+ %% entire spec + function. So this range here is 8
+ %% lines long.
+ [
+ #{
+ code => <<"E1507">>,
+ message => <<"undefined macro 'UNDEFINED'">>,
+ range => {{2, 0}, {10, 6}}
+ }
+ ];
+ false ->
+ [
+ #{
+ code => <<"E1507">>,
+ message => <<"undefined macro 'UNDEFINED'">>,
+ range => {{8, 0}, {9, 0}}
+ }
+ ]
+ end,
+ Warnings = [],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec compiler_with_parse_transform(config()) -> ok.
compiler_with_parse_transform(_Config) ->
- _ = code:delete(diagnostics_parse_transform),
- _ = code:purge(diagnostics_parse_transform),
- Path = src_path("diagnostics_parse_transform_usage.erl"),
- Source = <<"Compiler">>,
- Errors = [],
- Warnings = [ #{ code => <<"L1268">>
- , message => <<"variable 'Args' is unused">>
- , range => {{6, 5}, {6, 9}}}
- ],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ _ = code:delete(diagnostics_parse_transform),
+ _ = code:purge(diagnostics_parse_transform),
+ Path = src_path("diagnostics_parse_transform_usage.erl"),
+ Source = <<"Compiler">>,
+ Errors = [],
+ Warnings = [
+ #{
+ code => <<"L1268">>,
+ message => <<"variable 'Args' is unused">>,
+ range => {{6, 5}, {6, 9}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec compiler_with_parse_transform_list(config()) -> ok.
compiler_with_parse_transform_list(_Config) ->
- _ = code:delete(diagnostics_parse_transform),
- _ = code:purge(diagnostics_parse_transform),
- Path = src_path("diagnostics_parse_transform_usage_list.erl"),
- Source = <<"Compiler">>,
- Errors = [],
- Warnings = [ #{ code => <<"L1268">>
- , message => <<"variable 'Args' is unused">>
- , range => {{6, 5}, {6, 9}}}
- ],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ _ = code:delete(diagnostics_parse_transform),
+ _ = code:purge(diagnostics_parse_transform),
+ Path = src_path("diagnostics_parse_transform_usage_list.erl"),
+ Source = <<"Compiler">>,
+ Errors = [],
+ Warnings = [
+ #{
+ code => <<"L1268">>,
+ message => <<"variable 'Args' is unused">>,
+ range => {{6, 5}, {6, 9}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec compiler_with_parse_transform_included(config()) -> ok.
compiler_with_parse_transform_included(_Config) ->
- _ = code:delete(diagnostics_parse_transform),
- _ = code:purge(diagnostics_parse_transform),
- Path = src_path("diagnostics_parse_transform_usage_included.erl"),
- Source = <<"Compiler">>,
- Errors = [],
- Warnings = [ #{ code => <<"L1268">>
- , message => <<"variable 'Args' is unused">>
- , range => {{6, 5}, {6, 9}}}
- ],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ _ = code:delete(diagnostics_parse_transform),
+ _ = code:purge(diagnostics_parse_transform),
+ Path = src_path("diagnostics_parse_transform_usage_included.erl"),
+ Source = <<"Compiler">>,
+ Errors = [],
+ Warnings = [
+ #{
+ code => <<"L1268">>,
+ message => <<"variable 'Args' is unused">>,
+ range => {{6, 5}, {6, 9}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec compiler_with_parse_transform_broken(config()) -> ok.
compiler_with_parse_transform_broken(_Config) ->
- Path = src_path("diagnostics_parse_transform_usage_broken.erl"),
- Source = <<"Compiler">>,
- Errors =
- [ #{ code => <<"L0000">>
- , message => <<"Issue in included file (10): syntax error before: ">>
- , range => {{4, 27}, {4, 61}}
- }
- , #{ code => <<"C1008">>
- , message => <<"undefined parse transform "
- "'diagnostics_parse_transform_broken'">>
- , range => {{0, 0}, {1, 0}}
- }
- ],
- Warnings = [],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("diagnostics_parse_transform_usage_broken.erl"),
+ Source = <<"Compiler">>,
+ Errors =
+ [
+ #{
+ code => <<"L0000">>,
+ message => <<"Issue in included file (10): syntax error before: ">>,
+ range => {{4, 27}, {4, 61}}
+ },
+ #{
+ code => <<"C1008">>,
+ message => <<
+ "undefined parse transform "
+ "'diagnostics_parse_transform_broken'"
+ >>,
+ range => {{0, 0}, {1, 0}}
+ }
+ ],
+ Warnings = [],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec compiler_with_parse_transform_deps(config()) -> ok.
compiler_with_parse_transform_deps(_Config) ->
- Path = src_path("diagnostics_parse_transform_deps_a.erl"),
- Source = <<"Compiler">>,
- Errors = [],
- Warnings = [ #{ code => <<"L1230">>
- , message => <<"function unused/0 is unused">>
- , range => {{4, 0}, {4, 6}}}
- ],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("diagnostics_parse_transform_deps_a.erl"),
+ Source = <<"Compiler">>,
+ Errors = [],
+ Warnings = [
+ #{
+ code => <<"L1230">>,
+ message => <<"function unused/0 is unused">>,
+ range => {{4, 0}, {4, 6}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
%% Issue 1140
-spec compiler_with_parse_transform_error(config()) -> ok.
compiler_with_parse_transform_error(_Config) ->
- Path = src_path("diagnostics_parse_transform_error.erl"),
- Source = <<"Compiler">>,
- Errors = [#{ code => <<"my_parse_transform">>
- , message => <<"custom_description">>
- , range => {{41, 0}, {42, 0}}
- }
- ],
- Warnings = [],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("diagnostics_parse_transform_error.erl"),
+ Source = <<"Compiler">>,
+ Errors = [
+ #{
+ code => <<"my_parse_transform">>,
+ message => <<"custom_description">>,
+ range => {{41, 0}, {42, 0}}
+ }
+ ],
+ Warnings = [],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec compiler_telemetry(config()) -> ok.
compiler_telemetry(Config) ->
- Path = src_path("diagnostics.erl"),
- Source = <<"Compiler">>,
- Errors = [ #{ code => <<"L0000">>
- , message => <<"Issue in included file (1): bad attribute">>
- , range => {{3, 0}, {3, 35}}
- }
- , #{ code => <<"L0000">>
- , message => <<"Issue in included file (3): bad attribute">>
- , range => {{3, 0}, {3, 35}}
- }
- , #{ code => <<"L1295">>
- , message => <<"type undefined_type() undefined">>
- , range => {{5, 30}, {5, 44}}
- }
- ],
- Warnings = [ #{ code => <<"L1230">>
- , message => <<"function main/1 is unused">>
- , range => {{6, 0}, {6, 4}}}
- ],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints),
- Telemetry = wait_for_compiler_telemetry(),
- #{ type := Type
- , uri := UriT
- , diagnostics := DiagnosticsCodes } = Telemetry,
- ?assertEqual(<<"erlang-diagnostic-codes">>, Type),
- Uri = ?config(diagnostics_uri, Config),
- ?assertEqual(Uri, UriT),
- ?assertEqual([ <<"L1230">>, <<"L0000">>, <<"L0000">>, <<"L1295">>]
- , DiagnosticsCodes),
- ok.
+ Path = src_path("diagnostics.erl"),
+ Source = <<"Compiler">>,
+ Errors = [
+ #{
+ code => <<"L0000">>,
+ message => <<"Issue in included file (1): bad attribute">>,
+ range => {{3, 0}, {3, 35}}
+ },
+ #{
+ code => <<"L0000">>,
+ message => <<"Issue in included file (3): bad attribute">>,
+ range => {{3, 0}, {3, 35}}
+ },
+ #{
+ code => <<"L1295">>,
+ message => <<"type undefined_type() undefined">>,
+ range => {{5, 30}, {5, 44}}
+ }
+ ],
+ Warnings = [
+ #{
+ code => <<"L1230">>,
+ message => <<"function main/1 is unused">>,
+ range => {{6, 0}, {6, 4}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints),
+ Telemetry = wait_for_compiler_telemetry(),
+ #{
+ type := Type,
+ uri := UriT,
+ diagnostics := DiagnosticsCodes
+ } = Telemetry,
+ ?assertEqual(<<"erlang-diagnostic-codes">>, Type),
+ Uri = ?config(diagnostics_uri, Config),
+ ?assertEqual(Uri, UriT),
+ ?assertEqual(
+ [<<"L1230">>, <<"L0000">>, <<"L0000">>, <<"L1295">>],
+ DiagnosticsCodes
+ ),
+ ok.
-spec code_path_extra_dirs(config()) -> ok.
code_path_extra_dirs(_Config) ->
- RootPath = binary_to_list(els_test_utils:root_path()),
- Dirs = [ AbsDir
- || Dir <- filelib:wildcard("*", RootPath),
- filelib:is_dir(AbsDir = filename:absname(Dir, RootPath))],
- ?assertMatch(true, lists:all(fun(Elem) -> code:del_path(Elem) end, Dirs)),
- ok.
+ RootPath = binary_to_list(els_test_utils:root_path()),
+ Dirs = [
+ AbsDir
+ || Dir <- filelib:wildcard("*", RootPath),
+ filelib:is_dir(AbsDir = filename:absname(Dir, RootPath))
+ ],
+ ?assertMatch(true, lists:all(fun(Elem) -> code:del_path(Elem) end, Dirs)),
+ ok.
-spec use_long_names(config()) -> ok.
use_long_names(_Config) ->
- {ok, HostName} = inet:gethostname(),
- NodeName = "my_node@" ++
- HostName ++ "." ++
- proplists:get_value(domain, inet:get_rc(), ""),
- Node = list_to_atom(NodeName),
- ?assertMatch(Node, els_config_runtime:get_node_name()),
- ok.
+ {ok, HostName} = inet:gethostname(),
+ NodeName =
+ "my_node@" ++
+ HostName ++ "." ++
+ proplists:get_value(domain, inet:get_rc(), ""),
+ Node = list_to_atom(NodeName),
+ ?assertMatch(Node, els_config_runtime:get_node_name()),
+ ok.
-spec epp_with_nonexistent_macro(config()) -> ok.
epp_with_nonexistent_macro(_Config) ->
- Path = include_path("nonexistent_macro.hrl"),
- Source = <<"Compiler">>,
- Errors = [ #{ code => <<"E1516">>
- , message => <<"can't find include file \"nonexisten-file.hrl\"">>
- , range => {{2, 0}, {3, 0}}
- }
- , #{ code => <<"E1507">>
- , message => <<"undefined macro 'MODULE'">>
- , range => {{4, 0}, {5, 0}}
- }
- , #{ code => <<"E1522">>
- , message => <<"-error(\"including nonexistent_macro.hrl "
- "is not allowed\").">>
- , range => {{6, 0}, {7, 0}}}
- ],
- Warnings = [],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = include_path("nonexistent_macro.hrl"),
+ Source = <<"Compiler">>,
+ Errors = [
+ #{
+ code => <<"E1516">>,
+ message => <<"can't find include file \"nonexisten-file.hrl\"">>,
+ range => {{2, 0}, {3, 0}}
+ },
+ #{
+ code => <<"E1507">>,
+ message => <<"undefined macro 'MODULE'">>,
+ range => {{4, 0}, {5, 0}}
+ },
+ #{
+ code => <<"E1522">>,
+ message => <<
+ "-error(\"including nonexistent_macro.hrl "
+ "is not allowed\")."
+ >>,
+ range => {{6, 0}, {7, 0}}
+ }
+ ],
+ Warnings = [],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec elvis(config()) -> ok.
elvis(_Config) ->
- {ok, Cwd} = file:get_cwd(),
- RootPath = els_test_utils:root_path(),
- try
- file:set_cwd(RootPath),
- Path = src_path("elvis_diagnostics.erl"),
- Source = <<"Elvis">>,
- Errors = [],
- Warnings = [ #{ code => operator_spaces
- , message => <<"Missing space right \",\" on line 6">>
- , range => {{5, 0}, {6, 0}}
- , relatedInformation => []
- }
- , #{ code => operator_spaces
- , message => <<"Missing space right \",\" on line 7">>
- , range => {{6, 0}, {7, 0}}
- , relatedInformation => []
- }
- ],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints)
- catch _Err ->
- file:set_cwd(Cwd)
- end,
- ok.
+ {ok, Cwd} = file:get_cwd(),
+ RootPath = els_test_utils:root_path(),
+ try
+ file:set_cwd(RootPath),
+ Path = src_path("elvis_diagnostics.erl"),
+ Source = <<"Elvis">>,
+ Errors = [],
+ Warnings = [
+ #{
+ code => operator_spaces,
+ message => <<"Missing space right \",\" on line 6">>,
+ range => {{5, 0}, {6, 0}},
+ relatedInformation => []
+ },
+ #{
+ code => operator_spaces,
+ message => <<"Missing space right \",\" on line 7">>,
+ range => {{6, 0}, {7, 0}},
+ relatedInformation => []
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints)
+ catch
+ _Err ->
+ file:set_cwd(Cwd)
+ end,
+ ok.
-spec escript(config()) -> ok.
escript(_Config) ->
- Path = src_path("diagnostics.escript"),
- Source = <<"Compiler">>,
- els_test:run_diagnostics_test(Path, Source, [], [], []).
+ Path = src_path("diagnostics.escript"),
+ Source = <<"Compiler">>,
+ els_test:run_diagnostics_test(Path, Source, [], [], []).
-spec escript_warnings(config()) -> ok.
escript_warnings(_Config) ->
- Path = src_path("diagnostics_warnings.escript"),
- Source = <<"Compiler">>,
- Errors = [],
- Warnings = [ #{ code => <<"L1230">>
- , message => <<"function unused/0 is unused">>
- , range => {{23, 0}, {24, 0}}
- }
- ],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("diagnostics_warnings.escript"),
+ Source = <<"Compiler">>,
+ Errors = [],
+ Warnings = [
+ #{
+ code => <<"L1230">>,
+ message => <<"function unused/0 is unused">>,
+ range => {{23, 0}, {24, 0}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec escript_errors(config()) -> ok.
escript_errors(_Config) ->
- Path = src_path("diagnostics_errors.escript"),
- Source = <<"Compiler">>,
- Errors = [ #{ code => <<"P1711">>
- , message => <<"syntax error before: tion_with_error">>
- , range => {{23, 0}, {24, 0}}
- }
- ],
- Warnings = [],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("diagnostics_errors.escript"),
+ Source = <<"Compiler">>,
+ Errors = [
+ #{
+ code => <<"P1711">>,
+ message => <<"syntax error before: tion_with_error">>,
+ range => {{23, 0}, {24, 0}}
+ }
+ ],
+ Warnings = [],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec code_reload(config()) -> ok.
code_reload(Config) ->
- Uri = ?config(diagnostics_uri, Config),
- Module = els_uri:module(Uri),
- ok = els_compiler_diagnostics:on_complete(Uri, []),
- {ok, HostName} = inet:gethostname(),
- NodeName = list_to_atom("fakenode@" ++ HostName),
- ?assert(meck:called(rpc, call, [NodeName, c, c, [Module]])),
- ok.
+ Uri = ?config(diagnostics_uri, Config),
+ Module = els_uri:module(Uri),
+ ok = els_compiler_diagnostics:on_complete(Uri, []),
+ {ok, HostName} = inet:gethostname(),
+ NodeName = list_to_atom("fakenode@" ++ HostName),
+ ?assert(meck:called(rpc, call, [NodeName, c, c, [Module]])),
+ ok.
-spec code_reload_sticky_mod(config()) -> ok.
code_reload_sticky_mod(Config) ->
- Uri = ?config(diagnostics_uri, Config),
- Module = els_uri:module(Uri),
- {ok, HostName} = inet:gethostname(),
- NodeName = list_to_atom("fakenode@" ++ HostName),
- meck:expect( rpc
- , call
- , fun(PNode, code, is_sticky, [_]) when PNode =:= NodeName ->
- true;
- (Node, Mod, Fun, Args) ->
- meck:passthrough([Node, Mod, Fun, Args])
- end
- ),
- ok = els_compiler_diagnostics:on_complete(Uri, []),
- ?assert(meck:called(rpc, call, [NodeName, code, is_sticky, [Module]])),
- ?assertNot(meck:called(rpc, call, [NodeName, c, c, [Module]])),
- ok.
+ Uri = ?config(diagnostics_uri, Config),
+ Module = els_uri:module(Uri),
+ {ok, HostName} = inet:gethostname(),
+ NodeName = list_to_atom("fakenode@" ++ HostName),
+ meck:expect(
+ rpc,
+ call,
+ fun
+ (PNode, code, is_sticky, [_]) when PNode =:= NodeName ->
+ true;
+ (Node, Mod, Fun, Args) ->
+ meck:passthrough([Node, Mod, Fun, Args])
+ end
+ ),
+ ok = els_compiler_diagnostics:on_complete(Uri, []),
+ ?assert(meck:called(rpc, call, [NodeName, code, is_sticky, [Module]])),
+ ?assertNot(meck:called(rpc, call, [NodeName, c, c, [Module]])),
+ ok.
-spec crossref(config()) -> ok.
crossref(_Config) ->
- Path = src_path("diagnostics_xref.erl"),
- Source = <<"CrossRef">>,
- Errors =
- [ #{ message => <<"Cannot find definition for function non_existing/0">>
- , range => {{6, 2}, {6, 14}}
- }
- , #{ message => <<"Cannot find definition for function lists:map/3">>
- , range => {{5, 2}, {5, 11}}
- }
- ],
- Warnings = [],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("diagnostics_xref.erl"),
+ Source = <<"CrossRef">>,
+ Errors =
+ [
+ #{
+ message => <<"Cannot find definition for function non_existing/0">>,
+ range => {{6, 2}, {6, 14}}
+ },
+ #{
+ message => <<"Cannot find definition for function lists:map/3">>,
+ range => {{5, 2}, {5, 11}}
+ }
+ ],
+ Warnings = [],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
%% #641
-spec crossref_pseudo_functions(config()) -> ok.
crossref_pseudo_functions(_Config) ->
- Path = src_path("diagnostics_xref_pseudo.erl"),
- Errors =
- [ #{ message =>
- <<"Cannot find definition for function "
- "unknown_module:nonexistent/0">>
- , range => {{34, 2}, {34, 28}}
- }
- , #{ message =>
- <<"Cannot find definition for function "
- "unknown_module:module_info/1">>
- , range => {{13, 2}, {13, 28}}
- }
- , #{ message =>
- <<"Cannot find definition for function "
- "unknown_module:module_info/0">>
- , range => {{12, 2}, {12, 28}}
- }
- ],
- els_test:run_diagnostics_test(Path, <<"CrossRef">>, Errors, [], []).
+ Path = src_path("diagnostics_xref_pseudo.erl"),
+ Errors =
+ [
+ #{
+ message =>
+ <<
+ "Cannot find definition for function "
+ "unknown_module:nonexistent/0"
+ >>,
+ range => {{34, 2}, {34, 28}}
+ },
+ #{
+ message =>
+ <<
+ "Cannot find definition for function "
+ "unknown_module:module_info/1"
+ >>,
+ range => {{13, 2}, {13, 28}}
+ },
+ #{
+ message =>
+ <<
+ "Cannot find definition for function "
+ "unknown_module:module_info/0"
+ >>,
+ range => {{12, 2}, {12, 28}}
+ }
+ ],
+ els_test:run_diagnostics_test(Path, <<"CrossRef">>, Errors, [], []).
%% #860
-spec crossref_autoimport(config()) -> ok.
crossref_autoimport(_Config) ->
- %% This testcase cannot be run from an Erlang source tree version,
- %% it needs a released version.
- Path = src_path("diagnostics_autoimport.erl"),
- els_test:run_diagnostics_test(Path, <<"CrossRef">>, [], [], []).
+ %% This testcase cannot be run from an Erlang source tree version,
+ %% it needs a released version.
+ Path = src_path("diagnostics_autoimport.erl"),
+ els_test:run_diagnostics_test(Path, <<"CrossRef">>, [], [], []).
%% #860
-spec crossref_autoimport_disabled(config()) -> ok.
crossref_autoimport_disabled(_Config) ->
- %% This testcase cannot be run from an Erlang source tree version,
- %% it needs a released version.
- Path = src_path("diagnostics_autoimport_disabled.erl"),
- els_test:run_diagnostics_test(Path, <<"CrossRef">>, [], [], []).
+ %% This testcase cannot be run from an Erlang source tree version,
+ %% it needs a released version.
+ Path = src_path("diagnostics_autoimport_disabled.erl"),
+ els_test:run_diagnostics_test(Path, <<"CrossRef">>, [], [], []).
-spec unused_includes(config()) -> ok.
unused_includes(_Config) ->
- Path = src_path("diagnostics_unused_includes.erl"),
- Source = <<"UnusedIncludes">>,
- Errors = [],
- {ok, FileName} = els_utils:find_header(
- els_utils:filename_to_atom("et/include/et.hrl")),
- Warnings = [#{ message => <<"Unused file: et.hrl">>
- , range => {{3, 0}, {3, 34}}
- , data => FileName
- }
- ],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("diagnostics_unused_includes.erl"),
+ Source = <<"UnusedIncludes">>,
+ Errors = [],
+ {ok, FileName} = els_utils:find_header(
+ els_utils:filename_to_atom("et/include/et.hrl")
+ ),
+ Warnings = [
+ #{
+ message => <<"Unused file: et.hrl">>,
+ range => {{3, 0}, {3, 34}},
+ data => FileName
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec unused_includes_compiler_attribute(config()) -> ok.
unused_includes_compiler_attribute(_Config) ->
- Path = src_path("diagnostics_unused_includes_compiler_attribute.erl"),
- Source = <<"UnusedIncludes">>,
- Errors = [],
- {ok, FileName} = els_utils:find_header(
- els_utils:filename_to_atom("kernel/include/file.hrl")),
- Warnings = [ #{ message => <<"Unused file: file.hrl">>
- , range => {{3, 0}, {3, 40}}
- , data => FileName
- }
- ],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("diagnostics_unused_includes_compiler_attribute.erl"),
+ Source = <<"UnusedIncludes">>,
+ Errors = [],
+ {ok, FileName} = els_utils:find_header(
+ els_utils:filename_to_atom("kernel/include/file.hrl")
+ ),
+ Warnings = [
+ #{
+ message => <<"Unused file: file.hrl">>,
+ range => {{3, 0}, {3, 40}},
+ data => FileName
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec exclude_unused_includes(config()) -> ok.
exclude_unused_includes(_Config) ->
- Path = src_path("diagnostics_unused_includes.erl"),
- Source = <<"UnusedIncludes">>,
- Errors = [],
- Warnings = [],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("diagnostics_unused_includes.erl"),
+ Source = <<"UnusedIncludes">>,
+ Errors = [],
+ Warnings = [],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec unused_macros(config()) -> ok.
unused_macros(_Config) ->
- Path = src_path("diagnostics_unused_macros.erl"),
- Source = <<"UnusedMacros">>,
- Errors = [],
- Warnings = [ #{ message => <<"Unused macro: UNUSED_MACRO">>
- , range => {{5, 8}, {5, 20}}
- },
- #{ message => <<"Unused macro: UNUSED_MACRO_WITH_ARG/1">>
- , range => {{6, 8}, {6, 29}}
- }
- ],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("diagnostics_unused_macros.erl"),
+ Source = <<"UnusedMacros">>,
+ Errors = [],
+ Warnings = [
+ #{
+ message => <<"Unused macro: UNUSED_MACRO">>,
+ range => {{5, 8}, {5, 20}}
+ },
+ #{
+ message => <<"Unused macro: UNUSED_MACRO_WITH_ARG/1">>,
+ range => {{6, 8}, {6, 29}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec unused_record_fields(config()) -> ok.
unused_record_fields(_Config) ->
- Path = src_path("diagnostics_unused_record_fields.erl"),
- Source = <<"UnusedRecordFields">>,
- Errors = [],
- Warnings =
- [ #{ message => <<"Unused record field: #unused_field.field_d">>
- , range => {{5, 32}, {5, 39}}
- }
- ],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("diagnostics_unused_record_fields.erl"),
+ Source = <<"UnusedRecordFields">>,
+ Errors = [],
+ Warnings =
+ [
+ #{
+ message => <<"Unused record field: #unused_field.field_d">>,
+ range => {{5, 32}, {5, 39}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec gradualizer(config()) -> ok.
gradualizer(_Config) ->
- Path = src_path("diagnostics_gradualizer.erl"),
- Source = <<"Gradualizer">>,
- Errors = [],
- Warnings = [ #{ message =>
- <<"The variable N is expected to have type integer() "
- "but it has type false | true\n">>
- , range => {{10, 0}, {11, 0}}}
- ],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-
+ Path = src_path("diagnostics_gradualizer.erl"),
+ Source = <<"Gradualizer">>,
+ Errors = [],
+ Warnings = [
+ #{
+ message =>
+ <<
+ "The variable N is expected to have type integer() "
+ "but it has type false | true\n"
+ >>,
+ range => {{10, 0}, {11, 0}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec module_name_check(config()) -> ok.
module_name_check(_Config) ->
- Path = src_path("diagnostics_module_name_check.erl"),
- Source = <<"Compiler (via Erlang LS)">>,
- Errors = [ #{ message =>
- <<"Module name 'module_name_check' does not match "
- "file name 'diagnostics_module_name_check'">>
- , range => {{0, 8}, {0, 25}}
- }
- ],
- Warnings = [],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("diagnostics_module_name_check.erl"),
+ Source = <<"Compiler (via Erlang LS)">>,
+ Errors = [
+ #{
+ message =>
+ <<
+ "Module name 'module_name_check' does not match "
+ "file name 'diagnostics_module_name_check'"
+ >>,
+ range => {{0, 8}, {0, 25}}
+ }
+ ],
+ Warnings = [],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec module_name_check_whitespace(config()) -> ok.
module_name_check_whitespace(_Config) ->
- Path = src_path("diagnostics module name check.erl"),
- Source = <<"Compiler (via Erlang LS)">>,
- Errors = [],
- Warnings = [],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("diagnostics module name check.erl"),
+ Source = <<"Compiler (via Erlang LS)">>,
+ Errors = [],
+ Warnings = [],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec edoc_main(config()) -> ok.
edoc_main(_Config) ->
- Path = src_path("edoc_diagnostics.erl"),
- Source = <<"Edoc">>,
- Errors = [ #{ message => <<"`-quote ended unexpectedly at line 13">>
- , range => {{12, 0}, {13, 0}}
- }
- ],
- Warnings = [ #{ message =>
- <<"tag @mydoc not recognized.">>
- , range => {{4, 0}, {5, 0}}
- }
- , #{ message =>
- <<"tag @docc not recognized.">>
- , range => {{8, 0}, {9, 0}}
- }
- ],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("edoc_diagnostics.erl"),
+ Source = <<"Edoc">>,
+ Errors = [
+ #{
+ message => <<"`-quote ended unexpectedly at line 13">>,
+ range => {{12, 0}, {13, 0}}
+ }
+ ],
+ Warnings = [
+ #{
+ message =>
+ <<"tag @mydoc not recognized.">>,
+ range => {{4, 0}, {5, 0}}
+ },
+ #{
+ message =>
+ <<"tag @docc not recognized.">>,
+ range => {{8, 0}, {9, 0}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec edoc_skip_app_src(config()) -> ok.
edoc_skip_app_src(_Config) ->
- Path = src_path("code_navigation.app.src"),
- Source = <<"Edoc">>,
- Errors = [],
- Warnings = [],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("code_navigation.app.src"),
+ Source = <<"Edoc">>,
+ Errors = [],
+ Warnings = [],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-spec edoc_custom_tags(config()) -> ok.
edoc_custom_tags(_Config) ->
- %% Custom tags are defined in priv/code_navigation/erlang_ls.config
- Path = src_path("edoc_diagnostics_custom_tags.erl"),
- Source = <<"Edoc">>,
- Errors = [],
- Warnings = [ #{ message =>
- <<"tag @docc not recognized.">>
- , range => {{9, 0}, {10, 0}}
- }
- ],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
-
+ %% Custom tags are defined in priv/code_navigation/erlang_ls.config
+ Path = src_path("edoc_diagnostics_custom_tags.erl"),
+ Source = <<"Edoc">>,
+ Errors = [],
+ Warnings = [
+ #{
+ message =>
+ <<"tag @docc not recognized.">>,
+ range => {{9, 0}, {10, 0}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
% RefactorErl test cases
-spec unused_macros_refactorerl(config()) -> ok.
unused_macros_refactorerl(_Config) ->
- Path = src_path("diagnostics_unused_macros.erl"),
- Source = <<"RefactorErl">>,
- Errors = [],
- Warnings = [ #{ message => <<"Unused macro: UNUSED_MACRO">>
- , range => {{5, 0}, {5, 35}}
- },
- #{ message => <<"Unused macro: UNUSED_MACRO_WITH_ARG">>
- , range => {{6, 0}, {6, 36}}
- }
- ],
- Hints = [],
- els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+ Path = src_path("diagnostics_unused_macros.erl"),
+ Source = <<"RefactorErl">>,
+ Errors = [],
+ Warnings = [
+ #{
+ message => <<"Unused macro: UNUSED_MACRO">>,
+ range => {{5, 0}, {5, 35}}
+ },
+ #{
+ message => <<"Unused macro: UNUSED_MACRO_WITH_ARG">>,
+ range => {{6, 0}, {6, 36}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
%%==============================================================================
%% Internal Functions
%%==============================================================================
mock_rpc() ->
- meck:new(rpc, [passthrough, no_link, unstick]),
- {ok, HostName} = inet:gethostname(),
- NodeName = list_to_atom("fakenode@" ++ HostName),
- meck:expect( rpc
- , call
- , fun(PNode, c, c, [Module]) when PNode =:= NodeName ->
- {ok, Module};
- (Node, Mod, Fun, Args) ->
- meck:passthrough([Node, Mod, Fun, Args])
- end
- ).
+ meck:new(rpc, [passthrough, no_link, unstick]),
+ {ok, HostName} = inet:gethostname(),
+ NodeName = list_to_atom("fakenode@" ++ HostName),
+ meck:expect(
+ rpc,
+ call,
+ fun
+ (PNode, c, c, [Module]) when PNode =:= NodeName ->
+ {ok, Module};
+ (Node, Mod, Fun, Args) ->
+ meck:passthrough([Node, Mod, Fun, Args])
+ end
+ ).
unmock_rpc() ->
- meck:unload(rpc).
+ meck:unload(rpc).
mock_code_reload_enabled() ->
- meck:new(els_config, [passthrough, no_link]),
- meck:expect( els_config
- , get
- , fun(code_reload) ->
- {ok, HostName} = inet:gethostname(),
- #{"node" => "fakenode@" ++ HostName};
- (Key) ->
- meck:passthrough([Key])
- end
- ).
+ meck:new(els_config, [passthrough, no_link]),
+ meck:expect(
+ els_config,
+ get,
+ fun
+ (code_reload) ->
+ {ok, HostName} = inet:gethostname(),
+ #{"node" => "fakenode@" ++ HostName};
+ (Key) ->
+ meck:passthrough([Key])
+ end
+ ).
unmock_code_reload_enabled() ->
- meck:unload(els_config).
+ meck:unload(els_config).
mock_compiler_telemetry_enabled() ->
- meck:new(els_config, [passthrough, no_link]),
- meck:expect( els_config
- , get
- , fun(compiler_telemetry_enabled) ->
- true;
- (Key) ->
- meck:passthrough([Key])
- end
- ),
- Self = self(),
- meck:expect( els_server
- , send_notification
- , fun(<<"telemetry/event">> = Method, Params) ->
- Self ! {on_complete_telemetry, Params},
- meck:passthrough([Method, Params]);
- (M, P) ->
- meck:passthrough([M, P])
- end
- ),
- ok.
+ meck:new(els_config, [passthrough, no_link]),
+ meck:expect(
+ els_config,
+ get,
+ fun
+ (compiler_telemetry_enabled) ->
+ true;
+ (Key) ->
+ meck:passthrough([Key])
+ end
+ ),
+ Self = self(),
+ meck:expect(
+ els_server,
+ send_notification,
+ fun
+ (<<"telemetry/event">> = Method, Params) ->
+ Self ! {on_complete_telemetry, Params},
+ meck:passthrough([Method, Params]);
+ (M, P) ->
+ meck:passthrough([M, P])
+ end
+ ),
+ ok.
-spec wait_for_compiler_telemetry() -> {uri(), [els_diagnostics:diagnostic()]}.
wait_for_compiler_telemetry() ->
- receive
- {on_complete_telemetry, Params} ->
- Params
- end.
+ receive
+ {on_complete_telemetry, Params} ->
+ Params
+ end.
unmock_compiler_telemetry_enabled() ->
- meck:unload(els_config),
- meck:unload(els_server).
+ meck:unload(els_config),
+ meck:unload(els_server).
src_path(Module) ->
- filename:join(["code_navigation", "src", Module]).
+ filename:join(["code_navigation", "src", Module]).
include_path(Header) ->
- filename:join(["code_navigation", "include", Header]).
+ filename:join(["code_navigation", "include", Header]).
% Mock RefactorErl utils
mock_refactorerl() ->
- {ok, HostName} = inet:gethostname(),
- NodeName = list_to_atom("referl_fake@" ++ HostName),
-
- meck:new(els_refactorerl_utils, [passthrough, no_link, unstick]),
- meck:expect(els_refactorerl_utils, run_diagnostics, 2,
- [ {# {'end' => #{character => 35, line => 5},
- start => #{character => 0, line => 5}},
- <<"Unused macro: UNUSED_MACRO">>},
- {# {'end' => #{character => 36, line => 6},
- start => #{character => 0, line => 6}},
- <<"Unused macro: UNUSED_MACRO_WITH_ARG">>}]
- ),
- meck:expect(els_refactorerl_utils, referl_node, 0, {ok, NodeName}),
- meck:expect(els_refactorerl_utils, add, 1, ok),
- meck:expect(els_refactorerl_utils, source_name, 0, <<"RefactorErl">>),
-
- meck:new(els_refactorerl_diagnostics, [passthrough, no_link, unstick]),
- meck:expect(els_refactorerl_diagnostics, is_default, 0, true).
+ {ok, HostName} = inet:gethostname(),
+ NodeName = list_to_atom("referl_fake@" ++ HostName),
+
+ meck:new(els_refactorerl_utils, [passthrough, no_link, unstick]),
+ meck:expect(
+ els_refactorerl_utils,
+ run_diagnostics,
+ 2,
+ [
+ {
+ #{
+ 'end' => #{character => 35, line => 5},
+ start => #{character => 0, line => 5}
+ },
+ <<"Unused macro: UNUSED_MACRO">>
+ },
+ {
+ #{
+ 'end' => #{character => 36, line => 6},
+ start => #{character => 0, line => 6}
+ },
+ <<"Unused macro: UNUSED_MACRO_WITH_ARG">>
+ }
+ ]
+ ),
+ meck:expect(els_refactorerl_utils, referl_node, 0, {ok, NodeName}),
+ meck:expect(els_refactorerl_utils, add, 1, ok),
+ meck:expect(els_refactorerl_utils, source_name, 0, <<"RefactorErl">>),
+ meck:new(els_refactorerl_diagnostics, [passthrough, no_link, unstick]),
+ meck:expect(els_refactorerl_diagnostics, is_default, 0, true).
unmock_refactoerl() ->
- meck:unload(els_refactorerl_diagnostics),
- meck:unload(els_refactorerl_utils).
\ No newline at end of file
+ meck:unload(els_refactorerl_diagnostics),
+ meck:unload(els_refactorerl_utils).
diff --git a/apps/els_lsp/test/els_document_highlight_SUITE.erl b/apps/els_lsp/test/els_document_highlight_SUITE.erl
index 29a5e44c0..dc3328e82 100644
--- a/apps/els_lsp/test/els_document_highlight_SUITE.erl
+++ b/apps/els_lsp/test/els_document_highlight_SUITE.erl
@@ -1,41 +1,43 @@
-module(els_document_highlight_SUITE).
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ application_local/1
- , application_remote/1
- , application_imported/1
- , function_definition/1
- , fun_local/1
- , fun_remote/1
- , atom/1
- , quoted_atom/1
- , record_def/1
- , record_expr/1
- , record_access/1
- , record_field/1
- , export/1
- , export_entry/1
- , export_type_entry/1
- , import/1
- , import_entry/1
- , type/1
- , type_application/1
- , opaque/1
- , macro_define/1
- , macro/1
- , spec/1
- , behaviour/1
- , callback/1
- ]).
+-export([
+ application_local/1,
+ application_remote/1,
+ application_imported/1,
+ function_definition/1,
+ fun_local/1,
+ fun_remote/1,
+ atom/1,
+ quoted_atom/1,
+ record_def/1,
+ record_expr/1,
+ record_access/1,
+ record_field/1,
+ export/1,
+ export_entry/1,
+ export_type_entry/1,
+ import/1,
+ import_entry/1,
+ type/1,
+ type_application/1,
+ opaque/1,
+ macro_define/1,
+ macro/1,
+ spec/1,
+ behaviour/1,
+ callback/1
+]).
%%==============================================================================
%% Includes
@@ -53,288 +55,287 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(TestCase, Config) ->
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Testcases
%%==============================================================================
-spec application_local(config()) -> ok.
application_local(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 22, 5),
- ExpectedLocations = expected_definitions(),
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 22, 5),
+ ExpectedLocations = expected_definitions(),
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec application_remote(config()) -> ok.
application_remote(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 32, 13),
- ExpectedLocations = [ #{range => #{from => {32, 3}, to => {32, 27}}}
- , #{range => #{from => {52, 8}, to => {52, 38}}}
- ],
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 32, 13),
+ ExpectedLocations = [
+ #{range => #{from => {32, 3}, to => {32, 27}}},
+ #{range => #{from => {52, 8}, to => {52, 38}}}
+ ],
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec application_imported(config()) -> ok.
application_imported(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 35, 4),
- ExpectedLocations = [ #{range => #{from => {35, 3}, to => {35, 9}}}
- ],
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 35, 4),
+ ExpectedLocations = [#{range => #{from => {35, 3}, to => {35, 9}}}],
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec function_definition(config()) -> ok.
function_definition(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 25, 1),
- ExpectedLocations = expected_definitions(),
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 25, 1),
+ ExpectedLocations = expected_definitions(),
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec fun_local(config()) -> ok.
fun_local(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 51, 16),
- ExpectedLocations = expected_definitions(),
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 51, 16),
+ ExpectedLocations = expected_definitions(),
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec fun_remote(config()) -> ok.
fun_remote(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 52, 14),
- ExpectedLocations = [ #{range => #{from => {32, 3}, to => {32, 27}}}
- , #{range => #{from => {52, 8}, to => {52, 38}}}
- ],
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 52, 14),
+ ExpectedLocations = [
+ #{range => #{from => {32, 3}, to => {32, 27}}},
+ #{range => #{from => {52, 8}, to => {52, 38}}}
+ ],
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec atom(config()) -> ok.
atom(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 94, 5),
- ExpectedLocations = [ #{range => #{from => {94, 4}, to => {94, 11}}}
- , #{range => #{from => {33, 18}, to => {33, 25}}}
- , #{range => #{from => {34, 19}, to => {34, 26}}}
- , #{range => #{from => {16, 20}, to => {16, 27}}}
- , #{range => #{from => {34, 44}, to => {34, 51}}}
- , #{range => #{from => {111, 19}, to => {111, 26}}}
- , #{range => #{from => {113, 33}, to => {113, 40}}}
- , #{range => #{from => {116, 14}, to => {116, 21}}}
- , #{range => #{from => {116, 39}, to => {116, 46}}}
- ],
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 94, 5),
+ ExpectedLocations = [
+ #{range => #{from => {94, 4}, to => {94, 11}}},
+ #{range => #{from => {33, 18}, to => {33, 25}}},
+ #{range => #{from => {34, 19}, to => {34, 26}}},
+ #{range => #{from => {16, 20}, to => {16, 27}}},
+ #{range => #{from => {34, 44}, to => {34, 51}}},
+ #{range => #{from => {111, 19}, to => {111, 26}}},
+ #{range => #{from => {113, 33}, to => {113, 40}}},
+ #{range => #{from => {116, 14}, to => {116, 21}}},
+ #{range => #{from => {116, 39}, to => {116, 46}}}
+ ],
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec quoted_atom(config()) -> ok.
quoted_atom(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations1} = els_client:document_highlight(Uri, 98, 1),
- ExpectedLocations1 = [ #{range => #{from => {98, 1}, to => {98, 21}}}
- , #{range => #{from => {5, 67}, to => {5, 89}}}
- ],
- assert_locations(ExpectedLocations1, Locations1),
- #{result := Locations2} = els_client:document_highlight(Uri, 101, 20),
- ExpectedLocations2 = [ #{range => #{from => {101, 5}, to => {101, 77}}}
- ],
- assert_locations(ExpectedLocations2, Locations2),
- #{result := Locations3} = els_client:document_highlight(Uri, 99, 18),
- ExpectedLocations3 = [ #{range => #{from => {99, 18}, to => {99, 27}}}
- , #{range => #{from => {16, 38}, to => {16, 47}}}
- ],
- assert_locations(ExpectedLocations3, Locations3),
- #{result := Locations4} = els_client:document_highlight(Uri, 100, 12),
- ExpectedLocations4 = [ #{range => #{from => {100, 7}, to => {100, 43}}}
- ],
- assert_locations(ExpectedLocations4, Locations4),
- #{result := Locations5} = els_client:document_highlight(Uri, 97, 48),
- ExpectedLocations5 = [ #{range => #{from => {97, 34}, to => {97, 68}}}
- ],
- assert_locations(ExpectedLocations5, Locations5),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations1} = els_client:document_highlight(Uri, 98, 1),
+ ExpectedLocations1 = [
+ #{range => #{from => {98, 1}, to => {98, 21}}},
+ #{range => #{from => {5, 67}, to => {5, 89}}}
+ ],
+ assert_locations(ExpectedLocations1, Locations1),
+ #{result := Locations2} = els_client:document_highlight(Uri, 101, 20),
+ ExpectedLocations2 = [#{range => #{from => {101, 5}, to => {101, 77}}}],
+ assert_locations(ExpectedLocations2, Locations2),
+ #{result := Locations3} = els_client:document_highlight(Uri, 99, 18),
+ ExpectedLocations3 = [
+ #{range => #{from => {99, 18}, to => {99, 27}}},
+ #{range => #{from => {16, 38}, to => {16, 47}}}
+ ],
+ assert_locations(ExpectedLocations3, Locations3),
+ #{result := Locations4} = els_client:document_highlight(Uri, 100, 12),
+ ExpectedLocations4 = [#{range => #{from => {100, 7}, to => {100, 43}}}],
+ assert_locations(ExpectedLocations4, Locations4),
+ #{result := Locations5} = els_client:document_highlight(Uri, 97, 48),
+ ExpectedLocations5 = [#{range => #{from => {97, 34}, to => {97, 68}}}],
+ assert_locations(ExpectedLocations5, Locations5),
+ ok.
-spec record_def(config()) -> ok.
record_def(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 16, 10),
- ExpectedLocations = record_uses(),
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 16, 10),
+ ExpectedLocations = record_uses(),
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec record_expr(config()) -> ok.
record_expr(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 23, 4),
- ExpectedLocations = record_uses(),
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 23, 4),
+ ExpectedLocations = record_uses(),
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec record_access(config()) -> ok.
record_access(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 34, 10),
- ExpectedLocations = record_uses(),
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 34, 10),
+ ExpectedLocations = record_uses(),
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec record_field(config()) -> ok.
record_field(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 16, 23),
- ExpectedLocations = [ #{range => #{from => {33, 18}, to => {33, 25}}}
- , #{range => #{from => {16, 20}, to => {16, 27}}}
- , #{range => #{from => {34, 19}, to => {34, 26}}}
- , #{range => #{from => {34, 44}, to => {34, 51}}}
- ],
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 16, 23),
+ ExpectedLocations = [
+ #{range => #{from => {33, 18}, to => {33, 25}}},
+ #{range => #{from => {16, 20}, to => {16, 27}}},
+ #{range => #{from => {34, 19}, to => {34, 26}}},
+ #{range => #{from => {34, 44}, to => {34, 51}}}
+ ],
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec export(config()) -> ok.
export(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 5, 5),
- ExpectedLocations = [ #{range => #{from => {5, 1}, to => {5, 108}}}
- ],
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 5, 5),
+ ExpectedLocations = [#{range => #{from => {5, 1}, to => {5, 108}}}],
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec export_entry(config()) -> ok.
export_entry(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 5, 25),
- ExpectedLocations = expected_definitions(),
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 5, 25),
+ ExpectedLocations = expected_definitions(),
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec export_type_entry(config()) -> ok.
export_type_entry(Config) ->
- Uri = ?config(code_navigation_types_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 5, 16),
- ExpectedLocations = [ #{range => #{from => {5, 16}, to => {5, 24}}}
- %% Should also include the definition, but does not
- %%, #{range => #{from => {3, 7}, to => {3, 13}}}
- ],
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_types_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 5, 16),
+ ExpectedLocations = [
+ #{range => #{from => {5, 16}, to => {5, 24}}}
+ %% Should also include the definition, but does not
+ %%, #{range => #{from => {3, 7}, to => {3, 13}}}
+ ],
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec import(config()) -> ok.
import(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 10, 3),
- ExpectedLocations = null,
- %% Should include this range
- %% ExpectedLocations = [ #{range => #{from => {10, 1}, to => {10, 51}}}
- %% ],
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 10, 3),
+ ExpectedLocations = null,
+ %% Should include this range
+ %% ExpectedLocations = [ #{range => #{from => {10, 1}, to => {10, 51}}}
+ %% ],
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec import_entry(config()) -> ok.
import_entry(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 10, 34),
- ExpectedLocations = [ #{range => #{from => {10, 34}, to => {10, 38}}}
- %% Should include uses of function but does not
- %% , #{range => #{from => {90, 3}, to => {90, 5}}}
- ],
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 10, 34),
+ ExpectedLocations = [
+ #{range => #{from => {10, 34}, to => {10, 38}}}
+ %% Should include uses of function but does not
+ %% , #{range => #{from => {90, 3}, to => {90, 5}}}
+ ],
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec type(config()) -> ok.
type(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 37, 9),
- ExpectedLocations = [ #{range => #{from => {37, 1}, to => {37, 25}}}
- %% Should also include the usage, but does not
- %%, #{range => #{from => {55, 23}, to => {55, 29}}}
- ],
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 37, 9),
+ ExpectedLocations = [
+ #{range => #{from => {37, 1}, to => {37, 25}}}
+ %% Should also include the usage, but does not
+ %%, #{range => #{from => {55, 23}, to => {55, 29}}}
+ ],
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec type_application(config()) -> ok.
type_application(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 55, 57),
- ExpectedLocations = [ #{range => #{from => {55, 55}, to => {55, 62}}}
- ],
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 55, 57),
+ ExpectedLocations = [#{range => #{from => {55, 55}, to => {55, 62}}}],
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec opaque(config()) -> ok.
opaque(Config) ->
- Uri = ?config(code_navigation_types_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 7, 9),
- ExpectedLocations = [ #{range => #{from => {7, 1}, to => {7, 35}}}
- ],
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_types_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 7, 9),
+ ExpectedLocations = [#{range => #{from => {7, 1}, to => {7, 35}}}],
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec macro_define(config()) -> ok.
macro_define(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 18, 10),
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 18, 10),
- ExpectedLocations = macro_uses(),
- assert_locations(ExpectedLocations, Locations),
- ok.
+ ExpectedLocations = macro_uses(),
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec macro(config()) -> ok.
macro(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 26, 6),
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 26, 6),
- ExpectedLocations = macro_uses(),
- assert_locations(ExpectedLocations, Locations),
- ok.
+ ExpectedLocations = macro_uses(),
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec spec(config()) -> ok.
spec(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 55, 11),
- %% The entire "-spec ... ." is part of the poi range
- ExpectedLocations = [ #{range => #{from => {55, 1}, to => {55, 65}}}
- ],
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 55, 11),
+ %% The entire "-spec ... ." is part of the poi range
+ ExpectedLocations = [#{range => #{from => {55, 1}, to => {55, 65}}}],
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec behaviour(config()) -> ok.
behaviour(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 3, 1),
- ExpectedLocations = [ #{range => #{from => {3, 1}, to => {3, 25}}}
- ],
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 3, 1),
+ ExpectedLocations = [#{range => #{from => {3, 1}, to => {3, 25}}}],
+ assert_locations(ExpectedLocations, Locations),
+ ok.
-spec callback(config()) -> ok.
callback(Config) ->
- Uri = ?config(rename_uri, Config),
- #{result := Locations} = els_client:document_highlight(Uri, 3, 10),
- ExpectedLocations = [ #{range => #{from => {3, 1}, to => {3, 34}}}
- ],
- assert_locations(ExpectedLocations, Locations),
- ok.
+ Uri = ?config(rename_uri, Config),
+ #{result := Locations} = els_client:document_highlight(Uri, 3, 10),
+ ExpectedLocations = [#{range => #{from => {3, 1}, to => {3, 34}}}],
+ assert_locations(ExpectedLocations, Locations),
+ ok.
%%==============================================================================
%% Internal functions
@@ -342,58 +343,81 @@ callback(Config) ->
-spec assert_locations([map()] | null, [map()] | null) -> ok.
assert_locations(null, null) ->
- ok;
+ ok;
assert_locations(ExpectedLocations, Locations) ->
- SortFun = fun(#{ range := #{ start := #{ line := StartLineA,
- character := StartCharA },
- 'end' := #{ line := EndLineA,
- character := EndCharA } } },
- #{ range := #{ start := #{ line := StartLineB,
- character := StartCharB },
- 'end' := #{ line := EndLineB,
- character := EndCharB } } }) ->
- {{StartLineA, StartCharA}, {EndLineA, EndCharA}}
- =<
- {{StartLineB, StartCharB}, {EndLineB, EndCharB}}
- end,
- ExpectedProtoLocs = lists:sort(SortFun, protocol_ranges(ExpectedLocations)),
- SortedLocations = lists:sort(SortFun, Locations),
- ?assertEqual(length(ExpectedLocations), length(Locations)),
- Pairs = lists:zip(SortedLocations, ExpectedProtoLocs),
- [ begin
- #{range := Range} = Location,
- #{range := ExpectedRange} = Expected,
- ?assertEqual(ExpectedRange, Range)
- end
- || {Location, Expected} <- Pairs
- ],
- ok.
+ SortFun = fun(
+ #{
+ range := #{
+ start := #{
+ line := StartLineA,
+ character := StartCharA
+ },
+ 'end' := #{
+ line := EndLineA,
+ character := EndCharA
+ }
+ }
+ },
+ #{
+ range := #{
+ start := #{
+ line := StartLineB,
+ character := StartCharB
+ },
+ 'end' := #{
+ line := EndLineB,
+ character := EndCharB
+ }
+ }
+ }
+ ) ->
+ {{StartLineA, StartCharA}, {EndLineA, EndCharA}} =<
+ {{StartLineB, StartCharB}, {EndLineB, EndCharB}}
+ end,
+ ExpectedProtoLocs = lists:sort(SortFun, protocol_ranges(ExpectedLocations)),
+ SortedLocations = lists:sort(SortFun, Locations),
+ ?assertEqual(length(ExpectedLocations), length(Locations)),
+ Pairs = lists:zip(SortedLocations, ExpectedProtoLocs),
+ [
+ begin
+ #{range := Range} = Location,
+ #{range := ExpectedRange} = Expected,
+ ?assertEqual(ExpectedRange, Range)
+ end
+ || {Location, Expected} <- Pairs
+ ],
+ ok.
protocol_ranges(Locations) ->
- [ L#{range => els_protocol:range(R)}
- || L = #{range := R} <- Locations ].
+ [
+ L#{range => els_protocol:range(R)}
+ || L = #{range := R} <- Locations
+ ].
-spec expected_definitions() -> [map()].
expected_definitions() ->
- [ #{range => #{from => {25, 1}, to => {25, 11}}}
- , #{range => #{from => {22, 3}, to => {22, 13}}}
- , #{range => #{from => {51, 7}, to => {51, 23}}}
- , #{range => #{from => {5, 25}, to => {5, 37}}}
- ].
+ [
+ #{range => #{from => {25, 1}, to => {25, 11}}},
+ #{range => #{from => {22, 3}, to => {22, 13}}},
+ #{range => #{from => {51, 7}, to => {51, 23}}},
+ #{range => #{from => {5, 25}, to => {5, 37}}}
+ ].
-spec record_uses() -> [map()].
record_uses() ->
- [ #{range => #{from => {16, 9}, to => {16, 17}}}
- , #{range => #{from => {23, 3}, to => {23, 12}}}
- , #{range => #{from => {33, 7}, to => {33, 16}}}
- , #{range => #{from => {34, 9}, to => {34, 18}}}
- , #{range => #{from => {34, 34}, to => {34, 43}}}
- , #{range => #{from => {99, 8}, to => {99, 17}}}
- ].
+ [
+ #{range => #{from => {16, 9}, to => {16, 17}}},
+ #{range => #{from => {23, 3}, to => {23, 12}}},
+ #{range => #{from => {33, 7}, to => {33, 16}}},
+ #{range => #{from => {34, 9}, to => {34, 18}}},
+ #{range => #{from => {34, 34}, to => {34, 43}}},
+ #{range => #{from => {99, 8}, to => {99, 17}}}
+ ].
-spec macro_uses() -> [map()].
macro_uses() ->
- [ #{range => #{from => {18, 9}, to => {18, 16}}}
- , #{range => #{from => {26, 3}, to => {26, 11}}}
- , #{range => #{from => {75, 23}, to => {75, 31}}}
- ].
+ [
+ #{range => #{from => {18, 9}, to => {18, 16}}},
+ #{range => #{from => {26, 3}, to => {26, 11}}},
+ #{range => #{from => {75, 23}, to => {75, 31}}}
+ ].
diff --git a/apps/els_lsp/test/els_document_symbol_SUITE.erl b/apps/els_lsp/test/els_document_symbol_SUITE.erl
index 5830c8929..1f4919a1d 100644
--- a/apps/els_lsp/test/els_document_symbol_SUITE.erl
+++ b/apps/els_lsp/test/els_document_symbol_SUITE.erl
@@ -1,17 +1,17 @@
-module(els_document_symbol_SUITE).
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ symbols/1
- ]).
+-export([symbols/1]).
-include("els_lsp.hrl").
@@ -31,133 +31,160 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(TestCase, Config) ->
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Testcases
%%==============================================================================
-spec symbols(config()) -> ok.
symbols(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Symbols} = els_client:document_symbol(Uri),
- Expected = lists:append([ expected_functions(Uri)
- , expected_macros(Uri)
- , expected_records(Uri)
- , expected_types(Uri)
- ]),
- ?assertEqual(length(Expected), length(Symbols)),
- Pairs = lists:zip(lists:sort(Expected), lists:sort(Symbols)),
- [?assertEqual(E, S) || {E, S} <- Pairs],
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Symbols} = els_client:document_symbol(Uri),
+ Expected = lists:append([
+ expected_functions(Uri),
+ expected_macros(Uri),
+ expected_records(Uri),
+ expected_types(Uri)
+ ]),
+ ?assertEqual(length(Expected), length(Symbols)),
+ Pairs = lists:zip(lists:sort(Expected), lists:sort(Symbols)),
+ [?assertEqual(E, S) || {E, S} <- Pairs],
+ ok.
%%==============================================================================
%% Internal Functions
%%==============================================================================
expected_functions(Uri) ->
- [ #{ kind => ?SYMBOLKIND_FUNCTION,
- location =>
- #{ range =>
- #{ 'end' => #{character => ToC, line => ToL},
- start => #{character => FromC, line => FromL}
- },
- uri => Uri
- },
- name => Name
- } || {Name, {FromL, FromC}, {ToL, ToC}} <- lists:append([functions()])].
+ [
+ #{
+ kind => ?SYMBOLKIND_FUNCTION,
+ location =>
+ #{
+ range =>
+ #{
+ 'end' => #{character => ToC, line => ToL},
+ start => #{character => FromC, line => FromL}
+ },
+ uri => Uri
+ },
+ name => Name
+ }
+ || {Name, {FromL, FromC}, {ToL, ToC}} <- lists:append([functions()])
+ ].
expected_macros(Uri) ->
- [ #{ kind => ?SYMBOLKIND_CONSTANT,
- location =>
- #{ range =>
- #{ 'end' => #{character => ToC, line => ToL},
- start => #{character => FromC, line => FromL}
- },
- uri => Uri
- },
- name => Name
- } || {Name, {FromL, FromC}, {ToL, ToC}} <- lists:append([macros()])].
+ [
+ #{
+ kind => ?SYMBOLKIND_CONSTANT,
+ location =>
+ #{
+ range =>
+ #{
+ 'end' => #{character => ToC, line => ToL},
+ start => #{character => FromC, line => FromL}
+ },
+ uri => Uri
+ },
+ name => Name
+ }
+ || {Name, {FromL, FromC}, {ToL, ToC}} <- lists:append([macros()])
+ ].
expected_records(Uri) ->
- [ #{ kind => ?SYMBOLKIND_STRUCT,
- location =>
- #{ range =>
- #{ 'end' => #{character => ToC, line => ToL},
- start => #{character => FromC, line => FromL}
- },
- uri => Uri
- },
- name => Name
- } || {Name, {FromL, FromC}, {ToL, ToC}} <- lists:append([records()])].
+ [
+ #{
+ kind => ?SYMBOLKIND_STRUCT,
+ location =>
+ #{
+ range =>
+ #{
+ 'end' => #{character => ToC, line => ToL},
+ start => #{character => FromC, line => FromL}
+ },
+ uri => Uri
+ },
+ name => Name
+ }
+ || {Name, {FromL, FromC}, {ToL, ToC}} <- lists:append([records()])
+ ].
expected_types(Uri) ->
- [ #{ kind => ?SYMBOLKIND_TYPE_PARAMETER,
- location =>
- #{ range =>
- #{ 'end' => #{character => ToC, line => ToL},
- start => #{character => FromC, line => FromL}
- },
- uri => Uri
- },
- name => Name
- } || {Name, {FromL, FromC}, {ToL, ToC}} <- lists:append([types()])].
+ [
+ #{
+ kind => ?SYMBOLKIND_TYPE_PARAMETER,
+ location =>
+ #{
+ range =>
+ #{
+ 'end' => #{character => ToC, line => ToL},
+ start => #{character => FromC, line => FromL}
+ },
+ uri => Uri
+ },
+ name => Name
+ }
+ || {Name, {FromL, FromC}, {ToL, ToC}} <- lists:append([types()])
+ ].
functions() ->
- [ {<<"function_a/0">>, {20, 0}, {20, 10}}
- , {<<"function_b/0">>, {24, 0}, {24, 10}}
- , {<<"callback_a/0">>, {27, 0}, {27, 10}}
- , {<<"function_c/0">>, {30, 0}, {30, 10}}
- , {<<"function_d/0">>, {38, 0}, {38, 10}}
- , {<<"function_e/0">>, {41, 0}, {41, 10}}
- , {<<"function_f/0">>, {46, 0}, {46, 10}}
- , {<<"function_g/1">>, {49, 0}, {49, 10}}
- , {<<"function_h/0">>, {55, 0}, {55, 10}}
- , {<<"function_i/0">>, {59, 0}, {59, 10}}
- , {<<"function_i/0">>, {61, 0}, {61, 10}}
- , {<<"function_j/0">>, {66, 0}, {66, 10}}
- , {<<"function_k/0">>, {73, 0}, {73, 10}}
- , {<<"function_l/2">>, {78, 0}, {78, 10}}
- , {<<"function_m/1">>, {83, 0}, {83, 10}}
- , {<<"function_n/0">>, {88, 0}, {88, 10}}
- , {<<"function_o/0">>, {92, 0}, {92, 10}}
- , {<<"PascalCaseFunction/1">>, {97, 0}, {97, 20}}
- , {<<"function_p/1">>, {102, 0}, {102, 10}}
- , {<<"function_q/0">>, {113, 0}, {113, 10}}
- , {<<"macro_b/2">>, {119, 0}, {119, 7}}
- , {<<"function_mb/0">>, {122, 0}, {122, 11}}
- ].
+ [
+ {<<"function_a/0">>, {20, 0}, {20, 10}},
+ {<<"function_b/0">>, {24, 0}, {24, 10}},
+ {<<"callback_a/0">>, {27, 0}, {27, 10}},
+ {<<"function_c/0">>, {30, 0}, {30, 10}},
+ {<<"function_d/0">>, {38, 0}, {38, 10}},
+ {<<"function_e/0">>, {41, 0}, {41, 10}},
+ {<<"function_f/0">>, {46, 0}, {46, 10}},
+ {<<"function_g/1">>, {49, 0}, {49, 10}},
+ {<<"function_h/0">>, {55, 0}, {55, 10}},
+ {<<"function_i/0">>, {59, 0}, {59, 10}},
+ {<<"function_i/0">>, {61, 0}, {61, 10}},
+ {<<"function_j/0">>, {66, 0}, {66, 10}},
+ {<<"function_k/0">>, {73, 0}, {73, 10}},
+ {<<"function_l/2">>, {78, 0}, {78, 10}},
+ {<<"function_m/1">>, {83, 0}, {83, 10}},
+ {<<"function_n/0">>, {88, 0}, {88, 10}},
+ {<<"function_o/0">>, {92, 0}, {92, 10}},
+ {<<"PascalCaseFunction/1">>, {97, 0}, {97, 20}},
+ {<<"function_p/1">>, {102, 0}, {102, 10}},
+ {<<"function_q/0">>, {113, 0}, {113, 10}},
+ {<<"macro_b/2">>, {119, 0}, {119, 7}},
+ {<<"function_mb/0">>, {122, 0}, {122, 11}}
+ ].
macros() ->
- [ {<<"macro_A">>, {44, 8}, {44, 15}}
- , {<<"MACRO_B">>, {117, 8}, {117, 15}}
- , {<<"MACRO_A">>, {17, 8}, {17, 15}}
- , {<<"MACRO_A/1">>, {18, 8}, {18, 15}}
- ].
+ [
+ {<<"macro_A">>, {44, 8}, {44, 15}},
+ {<<"MACRO_B">>, {117, 8}, {117, 15}},
+ {<<"MACRO_A">>, {17, 8}, {17, 15}},
+ {<<"MACRO_A/1">>, {18, 8}, {18, 15}}
+ ].
records() ->
- [ {<<"record_a">>, {15, 8}, {15, 16}}
- , {<<"?MODULE">>, {110, 8}, {110, 15}}
- ].
+ [
+ {<<"record_a">>, {15, 8}, {15, 16}},
+ {<<"?MODULE">>, {110, 8}, {110, 15}}
+ ].
types() ->
- [ {<<"type_a/0">>, {36, 0}, {36, 24}}
- ].
+ [{<<"type_a/0">>, {36, 0}, {36, 24}}].
diff --git a/apps/els_lsp/test/els_execute_command_SUITE.erl b/apps/els_lsp/test/els_execute_command_SUITE.erl
index 13eb112bf..de9bca43a 100644
--- a/apps/els_lsp/test/els_execute_command_SUITE.erl
+++ b/apps/els_lsp/test/els_execute_command_SUITE.erl
@@ -1,20 +1,22 @@
-module(els_execute_command_SUITE).
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ els_lsp_info/1
- , ct_run_test/1
- , strip_server_prefix/1
- , suggest_spec/1
- ]).
+-export([
+ els_lsp_info/1,
+ ct_run_test/1,
+ strip_server_prefix/1,
+ suggest_spec/1
+]).
%%==============================================================================
%% Includes
@@ -32,45 +34,49 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(ct_run_test, Config0) ->
- Config = els_test_utils:init_per_testcase(ct_run_test, Config0),
- setup_mocks(),
- Config;
+ Config = els_test_utils:init_per_testcase(ct_run_test, Config0),
+ setup_mocks(),
+ Config;
init_per_testcase(suggest_spec, Config0) ->
- Config = els_test_utils:init_per_testcase(suggest_spec, Config0),
- meck:new(els_protocol, [passthrough, no_link]),
- meck:expect( els_protocol, request, 3
- , fun(RequestId, Method, Params) ->
- meck:passthrough([RequestId, Method, Params])
- end),
- Config;
+ Config = els_test_utils:init_per_testcase(suggest_spec, Config0),
+ meck:new(els_protocol, [passthrough, no_link]),
+ meck:expect(
+ els_protocol,
+ request,
+ 3,
+ fun(RequestId, Method, Params) ->
+ meck:passthrough([RequestId, Method, Params])
+ end
+ ),
+ Config;
init_per_testcase(TestCase, Config) ->
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(ct_run_test, Config) ->
- teardown_mocks(),
- els_test_utils:end_per_testcase(ct_run_test, Config);
+ teardown_mocks(),
+ els_test_utils:end_per_testcase(ct_run_test, Config);
end_per_testcase(suggest_spec, Config) ->
- meck:unload(els_protocol),
- els_test_utils:end_per_testcase(ct_run_test, Config);
+ meck:unload(els_protocol),
+ els_test_utils:end_per_testcase(ct_run_test, Config);
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Testcases
@@ -78,137 +84,199 @@ end_per_testcase(TestCase, Config) ->
-spec els_lsp_info(config()) -> ok.
els_lsp_info(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- PrefixedCommand = els_command:with_prefix(<<"server-info">>),
- #{result := Result}
- = els_client:workspace_executecommand(PrefixedCommand, [#{uri => Uri}]),
- Expected = [],
- ?assertEqual(Expected, Result),
- Notifications = wait_for_notifications(2),
- [ begin
- ?assertEqual(maps:get(method, Notification), <<"window/showMessage">>),
- Params = maps:get(params, Notification),
- ?assertEqual(<<"Erlang LS (in code_navigation), ">>
- , binary:part(maps:get(message, Params), 0, 32))
- end || Notification <- Notifications ],
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ PrefixedCommand = els_command:with_prefix(<<"server-info">>),
+ #{result := Result} =
+ els_client:workspace_executecommand(PrefixedCommand, [#{uri => Uri}]),
+ Expected = [],
+ ?assertEqual(Expected, Result),
+ Notifications = wait_for_notifications(2),
+ [
+ begin
+ ?assertEqual(maps:get(method, Notification), <<"window/showMessage">>),
+ Params = maps:get(params, Notification),
+ ?assertEqual(
+ <<"Erlang LS (in code_navigation), ">>,
+ binary:part(maps:get(message, Params), 0, 32)
+ )
+ end
+ || Notification <- Notifications
+ ],
+ ok.
-spec ct_run_test(config()) -> ok.
ct_run_test(Config) ->
- Uri = ?config(sample_SUITE_uri, Config),
- PrefixedCommand = els_command:with_prefix(<<"ct-run-test">>),
- #{result := Result}
- = els_client:workspace_executecommand( PrefixedCommand
- , [#{ uri => Uri
- , module => sample_SUITE
- , function => one
- , arity => 1
- , line => 58
- }]),
- Expected = [],
- ?assertEqual(Expected, Result),
- els_test_utils:wait_until_mock_called(els_protocol, notification),
- ?assertEqual(1, meck:num_calls(els_distribution_server, rpc_call, '_')),
- Notifications = [{Method, Args} ||
- { _Pid
- , { els_protocol
- , notification
- , [<<"textDocument/publishDiagnostics">> = Method, Args]}
- , _Result
- } <- meck:history(els_protocol)],
- ?assertEqual([{ <<"textDocument/publishDiagnostics">>
- , #{diagnostics =>
- [ #{ message => <<"Test passed!">>
- , range =>
- #{ 'end' => #{character => 0, line => 58}
- , start => #{character => 0, line => 57}}
- , severity => 3
- , source => <<"Common Test">>}],
- uri => Uri}
- }]
- , Notifications),
- ok.
+ Uri = ?config(sample_SUITE_uri, Config),
+ PrefixedCommand = els_command:with_prefix(<<"ct-run-test">>),
+ #{result := Result} =
+ els_client:workspace_executecommand(
+ PrefixedCommand,
+ [
+ #{
+ uri => Uri,
+ module => sample_SUITE,
+ function => one,
+ arity => 1,
+ line => 58
+ }
+ ]
+ ),
+ Expected = [],
+ ?assertEqual(Expected, Result),
+ els_test_utils:wait_until_mock_called(els_protocol, notification),
+ ?assertEqual(1, meck:num_calls(els_distribution_server, rpc_call, '_')),
+ Notifications = [
+ {Method, Args}
+ || {_Pid, {els_protocol, notification, [<<"textDocument/publishDiagnostics">> = Method, Args]},
+ _Result} <- meck:history(els_protocol)
+ ],
+ ?assertEqual(
+ [
+ {<<"textDocument/publishDiagnostics">>, #{
+ diagnostics =>
+ [
+ #{
+ message => <<"Test passed!">>,
+ range =>
+ #{
+ 'end' => #{character => 0, line => 58},
+ start => #{character => 0, line => 57}
+ },
+ severity => 3,
+ source => <<"Common Test">>
+ }
+ ],
+ uri => Uri
+ }}
+ ],
+ Notifications
+ ),
+ ok.
-spec suggest_spec(config()) -> ok.
suggest_spec(Config) ->
- Uri = ?config(execute_command_suggest_spec_uri, Config),
- PrefixedCommand = els_command:with_prefix(<<"suggest-spec">>),
- #{result := Result}
- = els_client:workspace_executecommand(
- PrefixedCommand
- , [#{ uri => Uri
- , line => 12
- , spec => <<"-spec without_spec(number(),binary()) -> "
- "{number(),binary()}.">>
- }]),
- Expected = [],
- ?assertEqual(Expected, Result),
- Pattern = ['_', <<"workspace/applyEdit">>, '_'],
- ok = meck:wait(1, els_protocol, request, Pattern, 5000),
- History = meck:history(els_protocol),
- [Edit] = [Params || { _Pid, { els_protocol
- , request
- , [_RequestId, <<"workspace/applyEdit">>, Params]}
- , _Binary
- } <- History],
- #{edit := #{changes := #{Uri := [#{ newText := NewText
- , range := Range}]}}} = Edit,
- ?assertEqual(<<"-spec without_spec(number(),binary()) -> "
- "{number(),binary()}.\n"
- "without_spec(A, B) when is_binary(B) ->\n">>
- , NewText),
- ?assertEqual(#{ 'end' => #{ character => 0
- , line => 12
- }
- , start => #{ character => 0
- , line => 11
- }}, Range),
- ok.
+ Uri = ?config(execute_command_suggest_spec_uri, Config),
+ PrefixedCommand = els_command:with_prefix(<<"suggest-spec">>),
+ #{result := Result} =
+ els_client:workspace_executecommand(
+ PrefixedCommand,
+ [
+ #{
+ uri => Uri,
+ line => 12,
+ spec => <<
+ "-spec without_spec(number(),binary()) -> "
+ "{number(),binary()}."
+ >>
+ }
+ ]
+ ),
+ Expected = [],
+ ?assertEqual(Expected, Result),
+ Pattern = ['_', <<"workspace/applyEdit">>, '_'],
+ ok = meck:wait(1, els_protocol, request, Pattern, 5000),
+ History = meck:history(els_protocol),
+ [Edit] = [
+ Params
+ || {_Pid, {els_protocol, request, [_RequestId, <<"workspace/applyEdit">>, Params]}, _Binary} <-
+ History
+ ],
+ #{
+ edit := #{
+ changes := #{
+ Uri := [
+ #{
+ newText := NewText,
+ range := Range
+ }
+ ]
+ }
+ }
+ } = Edit,
+ ?assertEqual(
+ <<
+ "-spec without_spec(number(),binary()) -> "
+ "{number(),binary()}.\n"
+ "without_spec(A, B) when is_binary(B) ->\n"
+ >>,
+ NewText
+ ),
+ ?assertEqual(
+ #{
+ 'end' => #{
+ character => 0,
+ line => 12
+ },
+ start => #{
+ character => 0,
+ line => 11
+ }
+ },
+ Range
+ ),
+ ok.
-spec strip_server_prefix(config()) -> ok.
strip_server_prefix(_Config) ->
- PrefixedCommand = els_command:with_prefix(<<"server-info">>),
- ?assertEqual( <<"server-info">>
- , els_command:without_prefix(PrefixedCommand)),
+ PrefixedCommand = els_command:with_prefix(<<"server-info">>),
+ ?assertEqual(
+ <<"server-info">>,
+ els_command:without_prefix(PrefixedCommand)
+ ),
- ?assertEqual( <<"server-info">>
- , els_command:without_prefix(<<"123:server-info">>)),
+ ?assertEqual(
+ <<"server-info">>,
+ els_command:without_prefix(<<"123:server-info">>)
+ ),
- ?assertEqual( <<"server-info">>
- , els_command:without_prefix(<<"server-info">>)),
+ ?assertEqual(
+ <<"server-info">>,
+ els_command:without_prefix(<<"server-info">>)
+ ),
- ?assertEqual( <<"server-info:f">>
- , els_command:without_prefix(<<"13:server-info:f">>)),
- ok.
+ ?assertEqual(
+ <<"server-info:f">>,
+ els_command:without_prefix(<<"13:server-info:f">>)
+ ),
+ ok.
-spec setup_mocks() -> ok.
setup_mocks() ->
- meck:new(els_protocol, [passthrough, no_link]),
- meck:expect( els_distribution_server, rpc_call, 4
- , fun(_, _, _, _) -> {ok, <<"Test passed!">>} end),
- meck:expect( els_protocol, notification, 2
- , fun(Method, Params) ->
- meck:passthrough([Method, Params])
- end),
- ok.
+ meck:new(els_protocol, [passthrough, no_link]),
+ meck:expect(
+ els_distribution_server,
+ rpc_call,
+ 4,
+ fun(_, _, _, _) -> {ok, <<"Test passed!">>} end
+ ),
+ meck:expect(
+ els_protocol,
+ notification,
+ 2,
+ fun(Method, Params) ->
+ meck:passthrough([Method, Params])
+ end
+ ),
+ ok.
-spec teardown_mocks() -> ok.
teardown_mocks() ->
- meck:unload(els_protocol),
- ok.
+ meck:unload(els_protocol),
+ ok.
-spec wait_for_notifications(pos_integer()) -> [map()].
wait_for_notifications(Num) ->
- wait_for_notifications(Num, []).
+ wait_for_notifications(Num, []).
-spec wait_for_notifications(integer(), [map()]) -> [map()].
wait_for_notifications(Num, Acc) when Num =< 0 ->
- Acc;
+ Acc;
wait_for_notifications(Num, Acc) ->
- CheckFun = fun() -> case els_client:get_notifications() of
- [] -> false;
- Notifications -> {true, Notifications}
- end
- end,
- {ok, Notifications} = els_test_utils:wait_for_fun(CheckFun, 10, 3),
- wait_for_notifications(Num - length(Notifications), Acc).
+ CheckFun = fun() ->
+ case els_client:get_notifications() of
+ [] -> false;
+ Notifications -> {true, Notifications}
+ end
+ end,
+ {ok, Notifications} = els_test_utils:wait_for_fun(CheckFun, 10, 3),
+ wait_for_notifications(Num - length(Notifications), Acc).
diff --git a/apps/els_lsp/test/els_foldingrange_SUITE.erl b/apps/els_lsp/test/els_foldingrange_SUITE.erl
index 04338423b..3b67929c2 100644
--- a/apps/els_lsp/test/els_foldingrange_SUITE.erl
+++ b/apps/els_lsp/test/els_foldingrange_SUITE.erl
@@ -3,17 +3,17 @@
-include("els_lsp.hrl").
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ folding_range/1
- ]).
+-export([folding_range/1]).
%%==============================================================================
%% Includes
@@ -31,27 +31,27 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(TestCase, Config) ->
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Testcases
@@ -59,18 +59,21 @@ end_per_testcase(TestCase, Config) ->
-spec folding_range(config()) -> ok.
folding_range(Config) ->
- #{result := Result} =
- els_client:folding_range(?config(folding_ranges_uri, Config)),
- Expected = [ #{ endCharacter => ?END_OF_LINE
- , endLine => 3
- , startCharacter => ?END_OF_LINE
- , startLine => 2
- }
- , #{ endCharacter => ?END_OF_LINE
- , endLine => 10
- , startCharacter => ?END_OF_LINE
- , startLine => 7
- }
- ],
- ?assertEqual(Expected, Result),
- ok.
+ #{result := Result} =
+ els_client:folding_range(?config(folding_ranges_uri, Config)),
+ Expected = [
+ #{
+ endCharacter => ?END_OF_LINE,
+ endLine => 3,
+ startCharacter => ?END_OF_LINE,
+ startLine => 2
+ },
+ #{
+ endCharacter => ?END_OF_LINE,
+ endLine => 10,
+ startCharacter => ?END_OF_LINE,
+ startLine => 7
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
diff --git a/apps/els_lsp/test/els_formatter_SUITE.erl b/apps/els_lsp/test/els_formatter_SUITE.erl
index 35f7b7cd8..028748ef2 100644
--- a/apps/els_lsp/test/els_formatter_SUITE.erl
+++ b/apps/els_lsp/test/els_formatter_SUITE.erl
@@ -3,17 +3,17 @@
-include("els_lsp.hrl").
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ format_doc/1
- ]).
+-export([format_doc/1]).
%%==============================================================================
%% Includes
@@ -31,51 +31,61 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(TestCase, Config) ->
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Testcases
%%==============================================================================
-spec format_doc(config()) -> ok.
format_doc(Config) ->
- {ok, Cwd} = file:get_cwd(),
- RootPath = els_test_utils:root_path(),
- try
- file:set_cwd(RootPath),
- Uri = ?config(format_input_uri, Config),
- #{result := Result} = els_client:document_formatting(Uri, 8, true),
- ?assertEqual(
- [#{newText => <<"-spec main(any()) -> any().\n">>,
- range =>
- #{'end' => #{character => 0, line => 5},
- start => #{character => 0, line => 4}}},
- #{newText => <<" X.\n">>,
- range =>
- #{'end' => #{character => 0, line => 9},
- start => #{character => 0, line => 6}}}
- ]
- , Result)
- after
- file:set_cwd(Cwd)
- end,
- ok.
+ {ok, Cwd} = file:get_cwd(),
+ RootPath = els_test_utils:root_path(),
+ try
+ file:set_cwd(RootPath),
+ Uri = ?config(format_input_uri, Config),
+ #{result := Result} = els_client:document_formatting(Uri, 8, true),
+ ?assertEqual(
+ [
+ #{
+ newText => <<"-spec main(any()) -> any().\n">>,
+ range =>
+ #{
+ 'end' => #{character => 0, line => 5},
+ start => #{character => 0, line => 4}
+ }
+ },
+ #{
+ newText => <<" X.\n">>,
+ range =>
+ #{
+ 'end' => #{character => 0, line => 9},
+ start => #{character => 0, line => 6}
+ }
+ }
+ ],
+ Result
+ )
+ after
+ file:set_cwd(Cwd)
+ end,
+ ok.
diff --git a/apps/els_lsp/test/els_fungraph_SUITE.erl b/apps/els_lsp/test/els_fungraph_SUITE.erl
index 45e5bca5a..ff0f77ecf 100644
--- a/apps/els_lsp/test/els_fungraph_SUITE.erl
+++ b/apps/els_lsp/test/els_fungraph_SUITE.erl
@@ -8,24 +8,24 @@
-spec all() -> [atom()].
all() ->
- [dense_graph_traversal].
+ [dense_graph_traversal].
-spec dense_graph_traversal(_Config) -> ok.
dense_graph_traversal(_) ->
- MaxID = 40,
- % Visited nodes must be unique
- ?assertEqual(
- lists:reverse(lists:seq(1, MaxID)),
- els_fungraph:traverse(
- fun(ID, _From, Acc) -> [ID | Acc] end,
- [],
- 1,
- els_fungraph:new(
- fun(ID) -> ID end,
- fun(ID) ->
- % Each node has edges to nodes in range [ID; ID * 2]
- [NextID || NextID <- lists:seq(ID, ID * 2), NextID =< MaxID]
- end
- )
- )
- ).
+ MaxID = 40,
+ % Visited nodes must be unique
+ ?assertEqual(
+ lists:reverse(lists:seq(1, MaxID)),
+ els_fungraph:traverse(
+ fun(ID, _From, Acc) -> [ID | Acc] end,
+ [],
+ 1,
+ els_fungraph:new(
+ fun(ID) -> ID end,
+ fun(ID) ->
+ % Each node has edges to nodes in range [ID; ID * 2]
+ [NextID || NextID <- lists:seq(ID, ID * 2), NextID =< MaxID]
+ end
+ )
+ )
+ ).
diff --git a/apps/els_lsp/test/els_hover_SUITE.erl b/apps/els_lsp/test/els_hover_SUITE.erl
index 9382802a7..aa245846d 100644
--- a/apps/els_lsp/test/els_hover_SUITE.erl
+++ b/apps/els_lsp/test/els_hover_SUITE.erl
@@ -3,38 +3,40 @@
-include("els_lsp.hrl").
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ local_call_no_args/1
- , local_call_with_args/1
- , remote_call_multiple_clauses/1
- , local_call_edoc/1
- , remote_call_edoc/1
- , remote_call_otp/1
- , local_fun_expression/1
- , remote_fun_expression/1
- , no_poi/1
- , included_macro/1
- , local_macro/1
- , weird_macro/1
- , macro_with_zero_args/1
- , macro_with_args/1
- , local_record/1
- , included_record/1
- , local_type/1
- , remote_type/1
- , local_opaque/1
- , remote_opaque/1
- , nonexisting_type/1
- , nonexisting_module/1
- ]).
+-export([
+ local_call_no_args/1,
+ local_call_with_args/1,
+ remote_call_multiple_clauses/1,
+ local_call_edoc/1,
+ remote_call_edoc/1,
+ remote_call_otp/1,
+ local_fun_expression/1,
+ remote_fun_expression/1,
+ no_poi/1,
+ included_macro/1,
+ local_macro/1,
+ weird_macro/1,
+ macro_with_zero_args/1,
+ macro_with_args/1,
+ local_record/1,
+ included_record/1,
+ local_type/1,
+ remote_type/1,
+ local_opaque/1,
+ remote_opaque/1,
+ nonexisting_type/1,
+ nonexisting_module/1
+]).
%%==============================================================================
%% Includes
@@ -52,337 +54,415 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(TestCase, Config) ->
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Testcases
%%==============================================================================
local_call_no_args(Config) ->
- Uri = ?config(hover_docs_caller_uri, Config),
- #{result := Result} = els_client:hover(Uri, 10, 7),
- ?assert(maps:is_key(contents, Result)),
- Contents = maps:get(contents, Result),
- Value = <<"## local_call/0">>,
- Expected = #{ kind => <<"markdown">>
- , value => Value
- },
- ?assertEqual(Expected, Contents),
- ok.
+ Uri = ?config(hover_docs_caller_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 10, 7),
+ ?assert(maps:is_key(contents, Result)),
+ Contents = maps:get(contents, Result),
+ Value = <<"## local_call/0">>,
+ Expected = #{
+ kind => <<"markdown">>,
+ value => Value
+ },
+ ?assertEqual(Expected, Contents),
+ ok.
local_call_with_args(Config) ->
- Uri = ?config(hover_docs_caller_uri, Config),
- #{result := Result} = els_client:hover(Uri, 13, 7),
- ?assert(maps:is_key(contents, Result)),
- Contents = maps:get(contents, Result),
- Value = <<"## local_call/2\n\n"
- "---\n\n"
- "```erlang\n\n"
- " local_call(Arg1, Arg2) \n\n"
- "```\n\n"
- "```erlang\n"
- "-spec local_call(integer(), any()) -> tuple();\n"
- " (float(), any()) -> tuple().\n"
- "```">>,
- Expected = #{ kind => <<"markdown">>
- , value => Value
- },
- ?assertEqual(Expected, Contents),
- ok.
+ Uri = ?config(hover_docs_caller_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 13, 7),
+ ?assert(maps:is_key(contents, Result)),
+ Contents = maps:get(contents, Result),
+ Value = <<
+ "## local_call/2\n\n"
+ "---\n\n"
+ "```erlang\n\n"
+ " local_call(Arg1, Arg2) \n\n"
+ "```\n\n"
+ "```erlang\n"
+ "-spec local_call(integer(), any()) -> tuple();\n"
+ " (float(), any()) -> tuple().\n"
+ "```"
+ >>,
+ Expected = #{
+ kind => <<"markdown">>,
+ value => Value
+ },
+ ?assertEqual(Expected, Contents),
+ ok.
remote_call_multiple_clauses(Config) ->
- Uri = ?config(hover_docs_caller_uri, Config),
- #{result := Result} = els_client:hover(Uri, 16, 15),
- ?assert(maps:is_key(contents, Result)),
- Contents = maps:get(contents, Result),
- Value = <<"## hover_docs:multiple_clauses/1\n\n"
- "---\n\n"
- "```erlang\n\n"
- " multiple_clauses(L) when is_list(L)\n\n"
- " multiple_clauses(#{data := Data}) \n\n"
- " multiple_clauses(X) \n\n```">>,
- Expected = #{ kind => <<"markdown">>
- , value => Value
- },
- ?assertEqual(Expected, Contents),
- ok.
+ Uri = ?config(hover_docs_caller_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 16, 15),
+ ?assert(maps:is_key(contents, Result)),
+ Contents = maps:get(contents, Result),
+ Value = <<
+ "## hover_docs:multiple_clauses/1\n\n"
+ "---\n\n"
+ "```erlang\n\n"
+ " multiple_clauses(L) when is_list(L)\n\n"
+ " multiple_clauses(#{data := Data}) \n\n"
+ " multiple_clauses(X) \n\n```"
+ >>,
+ Expected = #{
+ kind => <<"markdown">>,
+ value => Value
+ },
+ ?assertEqual(Expected, Contents),
+ ok.
local_call_edoc(Config) ->
- Uri = ?config(hover_docs_caller_uri, Config),
- #{result := Result} = els_client:hover(Uri, 29, 5),
- ?assert(maps:is_key(contents, Result)),
- Contents = maps:get(contents, Result),
- Value = case has_eep48_edoc() of
- true -> <<"```erlang\nedoc() -> ok.\n```\n\n---\n\nAn edoc hover item\n">>;
- false -> <<"## edoc/0\n\n---\n\n```erlang\n-spec edoc() -> ok.\n```\n\n"
- "An edoc hover item\n\n">>
- end,
- Expected = #{ kind => <<"markdown">>
- , value => Value
- },
- ?assertEqual(Expected, Contents),
- ok.
+ Uri = ?config(hover_docs_caller_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 29, 5),
+ ?assert(maps:is_key(contents, Result)),
+ Contents = maps:get(contents, Result),
+ Value =
+ case has_eep48_edoc() of
+ true ->
+ <<"```erlang\nedoc() -> ok.\n```\n\n---\n\nAn edoc hover item\n">>;
+ false ->
+ <<
+ "## edoc/0\n\n---\n\n```erlang\n-spec edoc() -> ok.\n```\n\n"
+ "An edoc hover item\n\n"
+ >>
+ end,
+ Expected = #{
+ kind => <<"markdown">>,
+ value => Value
+ },
+ ?assertEqual(Expected, Contents),
+ ok.
remote_call_edoc(Config) ->
- Uri = ?config(hover_docs_caller_uri, Config),
- #{result := Result} = els_client:hover(Uri, 23, 12),
- ?assert(maps:is_key(contents, Result)),
- Contents = maps:get(contents, Result),
- Value = case has_eep48_edoc() of
- true -> <<"```erlang\nedoc() -> ok.\n```\n\n---\n\nAn edoc hover item\n">>;
- false -> <<"## hover_docs:edoc/0\n\n---\n\n```erlang\n-spec edoc() -> ok.\n"
- "```\n\nAn edoc hover item\n\n">>
- end,
- Expected = #{ kind => <<"markdown">>
- , value => Value
- },
- ?assertEqual(Expected, Contents),
- ok.
+ Uri = ?config(hover_docs_caller_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 23, 12),
+ ?assert(maps:is_key(contents, Result)),
+ Contents = maps:get(contents, Result),
+ Value =
+ case has_eep48_edoc() of
+ true ->
+ <<"```erlang\nedoc() -> ok.\n```\n\n---\n\nAn edoc hover item\n">>;
+ false ->
+ <<
+ "## hover_docs:edoc/0\n\n---\n\n```erlang\n-spec edoc() -> ok.\n"
+ "```\n\nAn edoc hover item\n\n"
+ >>
+ end,
+ Expected = #{
+ kind => <<"markdown">>,
+ value => Value
+ },
+ ?assertEqual(Expected, Contents),
+ ok.
remote_call_otp(Config) ->
- Uri = ?config(hover_docs_caller_uri, Config),
- #{result := Result} = els_client:hover(Uri, 26, 12),
- ?assert(maps:is_key(contents, Result)),
- Contents = maps:get(contents, Result),
- Value = case has_eep48(file) of
- true -> <<"```erlang\nwrite(IoDevice, Bytes) -> ok | {error, Reason}\n"
- "when\n IoDevice :: io_device() | atom(),\n Bytes :: iodata(),"
- "\n Reason :: posix() | badarg | terminated.\n```\n\n---\n\n"
- "Writes `Bytes` to the file referenced by `IoDevice`\\. This "
- "function is the only way to write to a file opened in `raw` "
- "mode \\(although it works for normally opened files too\\)\\. "
- "Returns `ok` if successful, and `{error, Reason}` otherwise\\."
- "\n\nIf the file is opened with `encoding` set to something else "
- "than `latin1`, each byte written can result in many bytes being "
- "written to the file, as the byte range 0\\.\\.255 can represent "
- "anything between one and four bytes depending on value and UTF "
- "encoding type\\.\n\nTypical error reasons:\n\n* **`ebadf`** \n"
- " The file is not opened for writing\\.\n\n* **`enospc`** \n"
- " No space is left on the device\\.\n">>;
- false -> <<"## file:write/2\n\n---\n\n```erlang\n\n write(File, Bytes) "
- "when is_pid(File) orelse is_atom(File)\n\n write(#file_"
- "descriptor{module = Module} = Handle, Bytes) \n\n "
- "write(_, _) \n\n```\n\n```erlang\n-spec write(IoDevice, Bytes)"
- " -> ok | {error, Reason} when\n IoDevice :: io_device() |"
- " atom(),\n Bytes :: iodata(),\n Reason :: posix() | "
- "badarg | terminated.\n```">>
- end,
- Expected = #{ kind => <<"markdown">>
- , value => Value
- },
- ?assertEqual(Expected, Contents),
- ok.
+ Uri = ?config(hover_docs_caller_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 26, 12),
+ ?assert(maps:is_key(contents, Result)),
+ Contents = maps:get(contents, Result),
+ Value =
+ case has_eep48(file) of
+ true ->
+ <<
+ "```erlang\nwrite(IoDevice, Bytes) -> ok | {error, Reason}\n"
+ "when\n IoDevice :: io_device() | atom(),\n Bytes :: iodata(),"
+ "\n Reason :: posix() | badarg | terminated.\n```\n\n---\n\n"
+ "Writes `Bytes` to the file referenced by `IoDevice`\\. This "
+ "function is the only way to write to a file opened in `raw` "
+ "mode \\(although it works for normally opened files too\\)\\. "
+ "Returns `ok` if successful, and `{error, Reason}` otherwise\\."
+ "\n\nIf the file is opened with `encoding` set to something else "
+ "than `latin1`, each byte written can result in many bytes being "
+ "written to the file, as the byte range 0\\.\\.255 can represent "
+ "anything between one and four bytes depending on value and UTF "
+ "encoding type\\.\n\nTypical error reasons:\n\n* **`ebadf`** \n"
+ " The file is not opened for writing\\.\n\n* **`enospc`** \n"
+ " No space is left on the device\\.\n"
+ >>;
+ false ->
+ <<
+ "## file:write/2\n\n---\n\n```erlang\n\n write(File, Bytes) "
+ "when is_pid(File) orelse is_atom(File)\n\n write(#file_"
+ "descriptor{module = Module} = Handle, Bytes) \n\n "
+ "write(_, _) \n\n```\n\n```erlang\n-spec write(IoDevice, Bytes)"
+ " -> ok | {error, Reason} when\n IoDevice :: io_device() |"
+ " atom(),\n Bytes :: iodata(),\n Reason :: posix() | "
+ "badarg | terminated.\n```"
+ >>
+ end,
+ Expected = #{
+ kind => <<"markdown">>,
+ value => Value
+ },
+ ?assertEqual(Expected, Contents),
+ ok.
local_fun_expression(Config) ->
- Uri = ?config(hover_docs_caller_uri, Config),
- #{result := Result} = els_client:hover(Uri, 19, 5),
- ?assert(maps:is_key(contents, Result)),
- Contents = maps:get(contents, Result),
- Value = <<"## local_call/2\n\n"
- "---\n\n"
- "```erlang\n\n"
- " local_call(Arg1, Arg2) \n\n"
- "```\n\n"
- "```erlang\n"
- "-spec local_call(integer(), any()) -> tuple();\n"
- " (float(), any()) -> tuple().\n"
- "```">>,
- Expected = #{ kind => <<"markdown">>
- , value => Value
- },
- ?assertEqual(Expected, Contents),
- ok.
+ Uri = ?config(hover_docs_caller_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 19, 5),
+ ?assert(maps:is_key(contents, Result)),
+ Contents = maps:get(contents, Result),
+ Value = <<
+ "## local_call/2\n\n"
+ "---\n\n"
+ "```erlang\n\n"
+ " local_call(Arg1, Arg2) \n\n"
+ "```\n\n"
+ "```erlang\n"
+ "-spec local_call(integer(), any()) -> tuple();\n"
+ " (float(), any()) -> tuple().\n"
+ "```"
+ >>,
+ Expected = #{
+ kind => <<"markdown">>,
+ value => Value
+ },
+ ?assertEqual(Expected, Contents),
+ ok.
remote_fun_expression(Config) ->
- Uri = ?config(hover_docs_caller_uri, Config),
- #{result := Result} = els_client:hover(Uri, 20, 10),
- ?assert(maps:is_key(contents, Result)),
- Contents = maps:get(contents, Result),
- Value = <<"## hover_docs:multiple_clauses/1\n\n"
- "---\n\n"
- "```erlang\n\n"
- " multiple_clauses(L) when is_list(L)\n\n"
- " multiple_clauses(#{data := Data}) \n\n"
- " multiple_clauses(X) \n\n```">>,
- Expected = #{ kind => <<"markdown">>
- , value => Value
- },
- ?assertEqual(Expected, Contents),
- ok.
+ Uri = ?config(hover_docs_caller_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 20, 10),
+ ?assert(maps:is_key(contents, Result)),
+ Contents = maps:get(contents, Result),
+ Value = <<
+ "## hover_docs:multiple_clauses/1\n\n"
+ "---\n\n"
+ "```erlang\n\n"
+ " multiple_clauses(L) when is_list(L)\n\n"
+ " multiple_clauses(#{data := Data}) \n\n"
+ " multiple_clauses(X) \n\n```"
+ >>,
+ Expected = #{
+ kind => <<"markdown">>,
+ value => Value
+ },
+ ?assertEqual(Expected, Contents),
+ ok.
local_macro(Config) ->
- Uri = ?config(hover_macro_uri, Config),
- #{result := Result} = els_client:hover(Uri, 6, 4),
- Value = <<"```erlang\n?LOCAL_MACRO = local_macro\n```">>,
- Expected = #{contents => #{ kind => <<"markdown">>
- , value => Value
- }},
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(hover_macro_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 6, 4),
+ Value = <<"```erlang\n?LOCAL_MACRO = local_macro\n```">>,
+ Expected = #{
+ contents => #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result),
+ ok.
included_macro(Config) ->
- Uri = ?config(hover_macro_uri, Config),
- #{result := Result} = els_client:hover(Uri, 7, 4),
- Value = <<"```erlang\n?INCLUDED_MACRO_A = included_macro_a\n```">>,
- Expected = #{contents => #{ kind => <<"markdown">>
- , value => Value
- }},
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(hover_macro_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 7, 4),
+ Value = <<"```erlang\n?INCLUDED_MACRO_A = included_macro_a\n```">>,
+ Expected = #{
+ contents => #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result),
+ ok.
weird_macro(Config) ->
- Uri = ?config(hover_macro_uri, Config),
- #{result := Result} = els_client:hover(Uri, 12, 20),
- Value = <<"```erlang\n?WEIRD_MACRO = A when A > 1\n```">>,
- Expected = #{contents => #{ kind => <<"markdown">>
- , value => Value
- }},
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(hover_macro_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 12, 20),
+ Value = <<"```erlang\n?WEIRD_MACRO = A when A > 1\n```">>,
+ Expected = #{
+ contents => #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result),
+ ok.
macro_with_zero_args(Config) ->
- Uri = ?config(hover_macro_uri, Config),
- #{result := Result} = els_client:hover(Uri, 18, 10),
- Value = <<"```erlang\n?MACRO_WITH_ARGS() = {macro}\n```">>,
- Expected = #{contents => #{ kind => <<"markdown">>
- , value => Value
- }},
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(hover_macro_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 18, 10),
+ Value = <<"```erlang\n?MACRO_WITH_ARGS() = {macro}\n```">>,
+ Expected = #{
+ contents => #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result),
+ ok.
macro_with_args(Config) ->
- Uri = ?config(hover_macro_uri, Config),
- #{result := Result} = els_client:hover(Uri, 19, 10),
- Value = <<"```erlang\n?MACRO_WITH_ARGS(X, Y) = {macro, X, Y}\n```">>,
- Expected = #{contents => #{ kind => <<"markdown">>
- , value => Value
- }},
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(hover_macro_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 19, 10),
+ Value = <<"```erlang\n?MACRO_WITH_ARGS(X, Y) = {macro, X, Y}\n```">>,
+ Expected = #{
+ contents => #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result),
+ ok.
no_poi(Config) ->
- Uri = ?config(hover_docs_caller_uri, Config),
- #{result := Result} = els_client:hover(Uri, 10, 1),
- ?assertEqual(null, Result),
- ok.
+ Uri = ?config(hover_docs_caller_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 10, 1),
+ ?assertEqual(null, Result),
+ ok.
local_record(Config) ->
- Uri = ?config(hover_record_expr_uri, Config),
- #{result := Result} = els_client:hover(Uri, 11, 4),
- Value = <<"```erlang\n-record(test_record, {\n"
- " field1 = 123,\n"
- " field2 = xyzzy,\n"
- " field3\n"
- "}).\n```">>,
- Expected = #{contents => #{ kind => <<"markdown">>
- , value => Value
- }},
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(hover_record_expr_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 11, 4),
+ Value = <<
+ "```erlang\n-record(test_record, {\n"
+ " field1 = 123,\n"
+ " field2 = xyzzy,\n"
+ " field3\n"
+ "}).\n```"
+ >>,
+ Expected = #{
+ contents => #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result),
+ ok.
included_record(Config) ->
- Uri = ?config(hover_record_expr_uri, Config),
- #{result := Result} = els_client:hover(Uri, 15, 4),
- Value = <<"```erlang\n"
- "-record(included_record_a, {included_field_a, included_field_b})."
- "\n```">>,
- Expected = #{contents => #{ kind => <<"markdown">>
- , value => Value
- }},
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(hover_record_expr_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 15, 4),
+ Value = <<
+ "```erlang\n"
+ "-record(included_record_a, {included_field_a, included_field_b})."
+ "\n```"
+ >>,
+ Expected = #{
+ contents => #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result),
+ ok.
local_type(Config) ->
- Uri = ?config(hover_type_uri, Config),
- #{result := Result} = els_client:hover(Uri, 6, 10),
- Value = case has_eep48_edoc() of
- true -> <<"```erlang\n-type type_a() :: any().\n```\n\n---\n\n\n">>;
- false -> <<"```erlang\n-type type_a() :: any().\n```">>
- end,
- Expected = #{contents => #{ kind => <<"markdown">>
- , value => Value
- }},
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(hover_type_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 6, 10),
+ Value =
+ case has_eep48_edoc() of
+ true -> <<"```erlang\n-type type_a() :: any().\n```\n\n---\n\n\n">>;
+ false -> <<"```erlang\n-type type_a() :: any().\n```">>
+ end,
+ Expected = #{
+ contents => #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result),
+ ok.
remote_type(Config) ->
- Uri = ?config(hover_type_uri, Config),
- #{result := Result} = els_client:hover(Uri, 10, 10),
- Value = case has_eep48_edoc() of
- true -> <<"```erlang\n-type type_a() :: atom().\n```\n\n---\n\n\n">>;
- false -> <<"```erlang\n-type type_a() :: atom().\n```">>
- end,
- Expected = #{contents => #{ kind => <<"markdown">>
- , value => Value
- }},
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(hover_type_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 10, 10),
+ Value =
+ case has_eep48_edoc() of
+ true -> <<"```erlang\n-type type_a() :: atom().\n```\n\n---\n\n\n">>;
+ false -> <<"```erlang\n-type type_a() :: atom().\n```">>
+ end,
+ Expected = #{
+ contents => #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result),
+ ok.
local_opaque(Config) ->
- Uri = ?config(hover_type_uri, Config),
- #{result := Result} = els_client:hover(Uri, 14, 10),
- Value = case has_eep48_edoc() of
- true -> <<"```erlang\n-opaque opaque_type_a() \n```\n\n---\n\n\n">>;
- false -> <<"```erlang\n-opaque opaque_type_a() :: any().\n```">>
- end,
- Expected = #{contents => #{ kind => <<"markdown">>
- , value => Value
- }},
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(hover_type_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 14, 10),
+ Value =
+ case has_eep48_edoc() of
+ true -> <<"```erlang\n-opaque opaque_type_a() \n```\n\n---\n\n\n">>;
+ false -> <<"```erlang\n-opaque opaque_type_a() :: any().\n```">>
+ end,
+ Expected = #{
+ contents => #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result),
+ ok.
remote_opaque(Config) ->
- Uri = ?config(hover_type_uri, Config),
- #{result := Result} = els_client:hover(Uri, 18, 10),
- Value = case has_eep48_edoc() of
- true -> <<"```erlang\n-opaque opaque_type_a() \n```\n\n---\n\n\n">>;
- false -> <<"```erlang\n-opaque opaque_type_a() :: atom().\n```">>
- end,
- Expected = #{contents => #{ kind => <<"markdown">>
- , value => Value
- }},
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(hover_type_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 18, 10),
+ Value =
+ case has_eep48_edoc() of
+ true -> <<"```erlang\n-opaque opaque_type_a() \n```\n\n---\n\n\n">>;
+ false -> <<"```erlang\n-opaque opaque_type_a() :: atom().\n```">>
+ end,
+ Expected = #{
+ contents => #{
+ kind => <<"markdown">>,
+ value => Value
+ }
+ },
+ ?assertEqual(Expected, Result),
+ ok.
nonexisting_type(Config) ->
- Uri = ?config(hover_type_uri, Config),
- #{result := Result} = els_client:hover(Uri, 22, 10),
- Expected = null,
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(hover_type_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 22, 10),
+ Expected = null,
+ ?assertEqual(Expected, Result),
+ ok.
nonexisting_module(Config) ->
- Uri = ?config(hover_nonexisting_uri, Config),
- #{result := Result} = els_client:hover(Uri, 6, 12),
- Expected = #{contents =>
- #{kind => <<"markdown">>,
- value => <<"## nonexisting:main/0">>}},
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(hover_nonexisting_uri, Config),
+ #{result := Result} = els_client:hover(Uri, 6, 12),
+ Expected = #{
+ contents =>
+ #{
+ kind => <<"markdown">>,
+ value => <<"## nonexisting:main/0">>
+ }
+ },
+ ?assertEqual(Expected, Result),
+ ok.
has_eep48_edoc() ->
- list_to_integer(erlang:system_info(otp_release)) >= 24.
+ list_to_integer(erlang:system_info(otp_release)) >= 24.
has_eep48(Module) ->
- case catch code:get_doc(Module) of
- {ok, _} -> true;
- _ -> false
- end.
+ case catch code:get_doc(Module) of
+ {ok, _} -> true;
+ _ -> false
+ end.
diff --git a/apps/els_lsp/test/els_implementation_SUITE.erl b/apps/els_lsp/test/els_implementation_SUITE.erl
index e4fae0782..8758ea5a9 100644
--- a/apps/els_lsp/test/els_implementation_SUITE.erl
+++ b/apps/els_lsp/test/els_implementation_SUITE.erl
@@ -3,18 +3,20 @@
-include("els_lsp.hrl").
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ gen_server_call/1
- , callback/1
- ]).
+-export([
+ gen_server_call/1,
+ callback/1
+]).
%%==============================================================================
%% Includes
@@ -32,27 +34,27 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(TestCase, Config) ->
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Testcases
@@ -60,34 +62,42 @@ end_per_testcase(TestCase, Config) ->
-spec gen_server_call(config()) -> ok.
gen_server_call(Config) ->
- Uri = ?config(my_gen_server_uri, Config),
- #{result := Result} = els_client:implementation(Uri, 30, 10),
- Expected = [ #{ range =>
- #{ 'end' => #{character => 4, line => 46}
- , start => #{character => 0, line => 46}
- }
- , uri => Uri
- }
- ],
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(my_gen_server_uri, Config),
+ #{result := Result} = els_client:implementation(Uri, 30, 10),
+ Expected = [
+ #{
+ range =>
+ #{
+ 'end' => #{character => 4, line => 46},
+ start => #{character => 0, line => 46}
+ },
+ uri => Uri
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
-spec callback(config()) -> ok.
callback(Config) ->
- Uri = ?config(implementation_uri, Config),
- #{result := Result} = els_client:implementation(Uri, 3, 20),
- Expected = [ #{ range =>
- #{ 'end' => #{character => 17, line => 6}
- , start => #{character => 0, line => 6}
- }
- , uri => ?config(implementation_a_uri, Config)
- }
- , #{ range =>
- #{ 'end' => #{character => 17, line => 6}
- , start => #{character => 0, line => 6}
- }
- , uri => ?config(implementation_b_uri, Config)
- }
- ],
- ?assertEqual(Expected, Result),
- ok.
+ Uri = ?config(implementation_uri, Config),
+ #{result := Result} = els_client:implementation(Uri, 3, 20),
+ Expected = [
+ #{
+ range =>
+ #{
+ 'end' => #{character => 17, line => 6},
+ start => #{character => 0, line => 6}
+ },
+ uri => ?config(implementation_a_uri, Config)
+ },
+ #{
+ range =>
+ #{
+ 'end' => #{character => 17, line => 6},
+ start => #{character => 0, line => 6}
+ },
+ uri => ?config(implementation_b_uri, Config)
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
diff --git a/apps/els_lsp/test/els_indexer_SUITE.erl b/apps/els_lsp/test/els_indexer_SUITE.erl
index dd97c4787..83a46f2f7 100644
--- a/apps/els_lsp/test/els_indexer_SUITE.erl
+++ b/apps/els_lsp/test/els_indexer_SUITE.erl
@@ -1,22 +1,24 @@
-module(els_indexer_SUITE).
%% CT Callbacks
--export([ all/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- ]).
+-export([
+ all/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2
+]).
%% Test cases
--export([ index_dir_not_dir/1
- , index_erl_file/1
- , index_hrl_file/1
- , index_unkown_extension/1
- , do_not_skip_generated_file_by_tag_by_default/1
- , skip_generated_file_by_tag/1
- , skip_generated_file_by_custom_tag/1
- ]).
+-export([
+ index_dir_not_dir/1,
+ index_erl_file/1,
+ index_hrl_file/1,
+ index_unkown_extension/1,
+ do_not_skip_generated_file_by_tag_by_default/1,
+ skip_generated_file_by_tag/1,
+ skip_generated_file_by_custom_tag/1
+]).
%%==============================================================================
%% Includes
@@ -35,130 +37,146 @@
%%==============================================================================
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(TestCase, Config) when
- TestCase =:= skip_generated_file_by_tag ->
- meck:new(els_config_indexing, [passthrough, no_link]),
- meck:expect(els_config_indexing, get_skip_generated_files, fun() -> true end),
- els_test_utils:init_per_testcase(TestCase, Config);
+ TestCase =:= skip_generated_file_by_tag
+->
+ meck:new(els_config_indexing, [passthrough, no_link]),
+ meck:expect(els_config_indexing, get_skip_generated_files, fun() -> true end),
+ els_test_utils:init_per_testcase(TestCase, Config);
init_per_testcase(TestCase, Config) when
- TestCase =:= skip_generated_file_by_custom_tag ->
- meck:new(els_config_indexing, [passthrough, no_link]),
- meck:expect(els_config_indexing,
- get_skip_generated_files,
- fun() -> true end),
- meck:expect(els_config_indexing,
- get_generated_files_tag,
- fun() -> "@customgeneratedtag" end),
- els_test_utils:init_per_testcase(TestCase, Config);
+ TestCase =:= skip_generated_file_by_custom_tag
+->
+ meck:new(els_config_indexing, [passthrough, no_link]),
+ meck:expect(
+ els_config_indexing,
+ get_skip_generated_files,
+ fun() -> true end
+ ),
+ meck:expect(
+ els_config_indexing,
+ get_generated_files_tag,
+ fun() -> "@customgeneratedtag" end
+ ),
+ els_test_utils:init_per_testcase(TestCase, Config);
init_per_testcase(TestCase, Config) ->
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) when
- TestCase =:= skip_generated_file_by_tag ->
- meck:unload(els_config_indexing),
- els_test_utils:end_per_testcase(TestCase, Config);
+ TestCase =:= skip_generated_file_by_tag
+->
+ meck:unload(els_config_indexing),
+ els_test_utils:end_per_testcase(TestCase, Config);
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Testcases
%%==============================================================================
-spec index_dir_not_dir(config()) -> ok.
index_dir_not_dir(Config) ->
- DataDir = ?config(data_dir, Config),
- NotDirPath = filename:join(DataDir, "not_a_dir"),
- file:write_file(NotDirPath, <<>>),
- ok = els_utils:fold_files( fun(_, _) -> ok end
- , fun(_) -> true end
- , NotDirPath
- , ok
- ),
- file:delete(NotDirPath),
- ok.
+ DataDir = ?config(data_dir, Config),
+ NotDirPath = filename:join(DataDir, "not_a_dir"),
+ file:write_file(NotDirPath, <<>>),
+ ok = els_utils:fold_files(
+ fun(_, _) -> ok end,
+ fun(_) -> true end,
+ NotDirPath,
+ ok
+ ),
+ file:delete(NotDirPath),
+ ok.
-spec index_erl_file(config()) -> ok.
index_erl_file(Config) ->
- DataDir = ?config(data_dir, Config),
- Path = filename:join(els_utils:to_binary(DataDir), "test.erl"),
- {ok, Uri} = els_indexing:shallow_index(Path, app),
- {ok, [#{id := test, kind := module}]} = els_dt_document:lookup(Uri),
- ok.
+ DataDir = ?config(data_dir, Config),
+ Path = filename:join(els_utils:to_binary(DataDir), "test.erl"),
+ {ok, Uri} = els_indexing:shallow_index(Path, app),
+ {ok, [#{id := test, kind := module}]} = els_dt_document:lookup(Uri),
+ ok.
-spec index_hrl_file(config()) -> ok.
index_hrl_file(Config) ->
- DataDir = ?config(data_dir, Config),
- Path = filename:join(els_utils:to_binary(DataDir), "test.hrl"),
- {ok, Uri} = els_indexing:shallow_index(Path, app),
- {ok, [#{id := test, kind := header}]} = els_dt_document:lookup(Uri),
- ok.
+ DataDir = ?config(data_dir, Config),
+ Path = filename:join(els_utils:to_binary(DataDir), "test.hrl"),
+ {ok, Uri} = els_indexing:shallow_index(Path, app),
+ {ok, [#{id := test, kind := header}]} = els_dt_document:lookup(Uri),
+ ok.
-spec index_unkown_extension(config()) -> ok.
index_unkown_extension(Config) ->
- DataDir = ?config(data_dir, Config),
- Path = filename:join(els_utils:to_binary(DataDir), "test.foo"),
- {ok, Uri} = els_indexing:shallow_index(Path, app),
- {ok, [#{kind := other}]} = els_dt_document:lookup(Uri),
- ok.
+ DataDir = ?config(data_dir, Config),
+ Path = filename:join(els_utils:to_binary(DataDir), "test.foo"),
+ {ok, Uri} = els_indexing:shallow_index(Path, app),
+ {ok, [#{kind := other}]} = els_dt_document:lookup(Uri),
+ ok.
-spec do_not_skip_generated_file_by_tag_by_default(config()) -> ok.
do_not_skip_generated_file_by_tag_by_default(Config) ->
- DataDir = data_dir(Config),
- GeneratedByTagUri = uri(DataDir, "generated_file_by_tag.erl"),
- GeneratedByCustomTagUri = uri(DataDir, "generated_file_by_custom_tag.erl"),
- ?assertEqual({4, 0, 0}, els_indexing:index_dir(DataDir, app)),
- {ok, [#{ id := generated_file_by_tag
- , kind := module
- }
- ]} = els_dt_document:lookup(GeneratedByTagUri),
- {ok, [#{ id := generated_file_by_custom_tag
- , kind := module
- }
- ]} = els_dt_document:lookup(GeneratedByCustomTagUri),
- ok.
+ DataDir = data_dir(Config),
+ GeneratedByTagUri = uri(DataDir, "generated_file_by_tag.erl"),
+ GeneratedByCustomTagUri = uri(DataDir, "generated_file_by_custom_tag.erl"),
+ ?assertEqual({4, 0, 0}, els_indexing:index_dir(DataDir, app)),
+ {ok, [
+ #{
+ id := generated_file_by_tag,
+ kind := module
+ }
+ ]} = els_dt_document:lookup(GeneratedByTagUri),
+ {ok, [
+ #{
+ id := generated_file_by_custom_tag,
+ kind := module
+ }
+ ]} = els_dt_document:lookup(GeneratedByCustomTagUri),
+ ok.
-spec skip_generated_file_by_tag(config()) -> ok.
skip_generated_file_by_tag(Config) ->
- DataDir = data_dir(Config),
- GeneratedByTagUri = uri(DataDir, "generated_file_by_tag.erl"),
- GeneratedByCustomTagUri = uri(DataDir, "generated_file_by_custom_tag.erl"),
- ?assertEqual({3, 1, 0}, els_indexing:index_dir(DataDir, app)),
- {ok, []} = els_dt_document:lookup(GeneratedByTagUri),
- {ok, [#{ id := generated_file_by_custom_tag
- , kind := module
- }
- ]} = els_dt_document:lookup(GeneratedByCustomTagUri),
- ok.
+ DataDir = data_dir(Config),
+ GeneratedByTagUri = uri(DataDir, "generated_file_by_tag.erl"),
+ GeneratedByCustomTagUri = uri(DataDir, "generated_file_by_custom_tag.erl"),
+ ?assertEqual({3, 1, 0}, els_indexing:index_dir(DataDir, app)),
+ {ok, []} = els_dt_document:lookup(GeneratedByTagUri),
+ {ok, [
+ #{
+ id := generated_file_by_custom_tag,
+ kind := module
+ }
+ ]} = els_dt_document:lookup(GeneratedByCustomTagUri),
+ ok.
-spec skip_generated_file_by_custom_tag(config()) -> ok.
skip_generated_file_by_custom_tag(Config) ->
- DataDir = data_dir(Config),
- GeneratedByTagUri = uri(DataDir, "generated_file_by_tag.erl"),
- GeneratedByCustomTagUri = uri(DataDir, "generated_file_by_custom_tag.erl"),
- ?assertEqual({3, 1, 0}, els_indexing:index_dir(DataDir, app)),
- {ok, [#{ id := generated_file_by_tag
- , kind := module
- }
- ]} = els_dt_document:lookup(GeneratedByTagUri),
- {ok, []} = els_dt_document:lookup(GeneratedByCustomTagUri),
- ok.
+ DataDir = data_dir(Config),
+ GeneratedByTagUri = uri(DataDir, "generated_file_by_tag.erl"),
+ GeneratedByCustomTagUri = uri(DataDir, "generated_file_by_custom_tag.erl"),
+ ?assertEqual({3, 1, 0}, els_indexing:index_dir(DataDir, app)),
+ {ok, [
+ #{
+ id := generated_file_by_tag,
+ kind := module
+ }
+ ]} = els_dt_document:lookup(GeneratedByTagUri),
+ {ok, []} = els_dt_document:lookup(GeneratedByCustomTagUri),
+ ok.
-spec data_dir(proplists:proplist()) -> binary().
data_dir(Config) ->
- ?config(data_dir, Config).
+ ?config(data_dir, Config).
-spec uri(binary(), string()) -> uri().
uri(DataDir, FileName) ->
- Path = els_utils:to_binary(filename:join(DataDir, FileName)),
- els_uri:uri(Path).
+ Path = els_utils:to_binary(filename:join(DataDir, FileName)),
+ els_uri:uri(Path).
diff --git a/apps/els_lsp/test/els_indexing_SUITE.erl b/apps/els_lsp/test/els_indexing_SUITE.erl
index 40673f9d9..79353edc1 100644
--- a/apps/els_lsp/test/els_indexing_SUITE.erl
+++ b/apps/els_lsp/test/els_indexing_SUITE.erl
@@ -1,17 +1,19 @@
-module(els_indexing_SUITE).
%% CT Callbacks
--export([ all/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- ]).
+-export([
+ all/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2
+]).
%% Test cases
--export([ index_otp/1
- , reindex_otp/1
- ]).
+-export([
+ index_otp/1,
+ reindex_otp/1
+]).
%%==============================================================================
%% Includes
@@ -29,79 +31,80 @@
%%==============================================================================
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(_TestCase, Config) ->
- {ok, Started} = application:ensure_all_started(els_lsp),
- RootDir = code:root_dir(),
- RootUri = els_uri:uri(els_utils:to_binary(RootDir)),
- %% Do not index the entire list of OTP apps in the pipelines.
- Cfg = #{"otp_apps_exclude" => otp_apps_exclude()},
- els_config:do_initialize(RootUri, [], #{}, {undefined, Cfg}),
- [{started, Started}|Config].
+ {ok, Started} = application:ensure_all_started(els_lsp),
+ RootDir = code:root_dir(),
+ RootUri = els_uri:uri(els_utils:to_binary(RootDir)),
+ %% Do not index the entire list of OTP apps in the pipelines.
+ Cfg = #{"otp_apps_exclude" => otp_apps_exclude()},
+ els_config:do_initialize(RootUri, [], #{}, {undefined, Cfg}),
+ [{started, Started} | Config].
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(_TestCase, Config) ->
- [application:stop(App) || App <- ?config(started, Config)],
- ok.
+ [application:stop(App) || App <- ?config(started, Config)],
+ ok.
%%==============================================================================
%% Testcases
%%==============================================================================
-spec index_otp(config()) -> ok.
index_otp(_Config) ->
- do_index_otp().
+ do_index_otp().
-spec reindex_otp(config()) -> ok.
reindex_otp(_Config) ->
- do_index_otp(),
- ok.
+ do_index_otp(),
+ ok.
-spec do_index_otp() -> ok.
do_index_otp() ->
- [els_indexing:index_dir(Dir, otp) || Dir <- els_config:get(otp_paths)],
- ok.
+ [els_indexing:index_dir(Dir, otp) || Dir <- els_config:get(otp_paths)],
+ ok.
-spec otp_apps_exclude() -> [string()].
otp_apps_exclude() ->
- [ "asn1"
- , "common_test"
- , "compiler"
- , "crypto"
- , "debugger"
- , "dialyzer"
- , "diameter"
- , "edoc"
- , "eldap"
- , "erl_docgen"
- , "erl_interface"
- , "et"
- , "eunit"
- , "ftp"
- , "inets"
- , "jinterface"
- , "megaco"
- , "mnesia"
- , "observer"
- , "os_mon"
- , "otp_mibs"
- , "parsetools"
- , "reltool"
- , "sasl"
- , "snmp"
- , "ssh"
- , "ssl"
- , "syntax_tools"
- , "tftp"
- , "xmerl"
- , "wx"
- ].
+ [
+ "asn1",
+ "common_test",
+ "compiler",
+ "crypto",
+ "debugger",
+ "dialyzer",
+ "diameter",
+ "edoc",
+ "eldap",
+ "erl_docgen",
+ "erl_interface",
+ "et",
+ "eunit",
+ "ftp",
+ "inets",
+ "jinterface",
+ "megaco",
+ "mnesia",
+ "observer",
+ "os_mon",
+ "otp_mibs",
+ "parsetools",
+ "reltool",
+ "sasl",
+ "snmp",
+ "ssh",
+ "ssl",
+ "syntax_tools",
+ "tftp",
+ "xmerl",
+ "wx"
+ ].
diff --git a/apps/els_lsp/test/els_initialization_SUITE.erl b/apps/els_lsp/test/els_initialization_SUITE.erl
index 02ec0ec5f..b2db2a509 100644
--- a/apps/els_lsp/test/els_initialization_SUITE.erl
+++ b/apps/els_lsp/test/els_initialization_SUITE.erl
@@ -3,25 +3,27 @@
-include("els_lsp.hrl").
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ initialize_default/1
- , initialize_custom_relative/1
- , initialize_custom_absolute/1
- , initialize_diagnostics_default/1
- , initialize_diagnostics_custom/1
- , initialize_diagnostics_invalid/1
- , initialize_lenses_default/1
- , initialize_lenses_custom/1
- , initialize_lenses_invalid/1
- ]).
+-export([
+ initialize_default/1,
+ initialize_custom_relative/1,
+ initialize_custom_absolute/1,
+ initialize_diagnostics_default/1,
+ initialize_diagnostics_custom/1,
+ initialize_diagnostics_invalid/1,
+ initialize_lenses_default/1,
+ initialize_lenses_custom/1,
+ initialize_lenses_invalid/1
+]).
%%==============================================================================
%% Includes
@@ -39,30 +41,30 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(_TestCase, Config) ->
- meck:new(els_distribution_server, [no_link, passthrough]),
- meck:expect(els_distribution_server, connect, 0, ok),
- Started = els_test_utils:start(),
- [{started, Started} | Config].
+ meck:new(els_distribution_server, [no_link, passthrough]),
+ meck:expect(els_distribution_server, connect, 0, ok),
+ Started = els_test_utils:start(),
+ [{started, Started} | Config].
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Testcases
@@ -70,129 +72,141 @@ end_per_testcase(TestCase, Config) ->
-spec initialize_default(config()) -> ok.
initialize_default(_Config) ->
- RootUri = els_test_utils:root_uri(),
- els_client:initialize(RootUri),
- Result = els_config:get(macros),
- Expected = [#{"name" => "DEFINED_WITHOUT_VALUE"},
- #{"name" => "DEFINED_WITH_VALUE", "value" => 1}],
- ?assertEqual(Expected, Result),
- ok.
+ RootUri = els_test_utils:root_uri(),
+ els_client:initialize(RootUri),
+ Result = els_config:get(macros),
+ Expected = [
+ #{"name" => "DEFINED_WITHOUT_VALUE"},
+ #{"name" => "DEFINED_WITH_VALUE", "value" => 1}
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
-spec initialize_custom_relative(config()) -> ok.
initialize_custom_relative(_Config) ->
- RootUri = els_test_utils:root_uri(),
- ConfigPath = <<"../rebar3_release/erlang_ls.config">>,
- InitOpts = #{ <<"erlang">>
- => #{ <<"config_path">> => ConfigPath }},
- els_client:initialize(RootUri, InitOpts),
- Result = els_config:get(macros),
- Expected = [#{"name" => "DEFINED_FOR_RELATIVE_TEST"}],
- ?assertEqual(Expected, Result),
- ok.
+ RootUri = els_test_utils:root_uri(),
+ ConfigPath = <<"../rebar3_release/erlang_ls.config">>,
+ InitOpts = #{
+ <<"erlang">> =>
+ #{<<"config_path">> => ConfigPath}
+ },
+ els_client:initialize(RootUri, InitOpts),
+ Result = els_config:get(macros),
+ Expected = [#{"name" => "DEFINED_FOR_RELATIVE_TEST"}],
+ ?assertEqual(Expected, Result),
+ ok.
-spec initialize_custom_absolute(config()) -> ok.
initialize_custom_absolute(_Config) ->
- RootUri = els_test_utils:root_uri(),
- ConfigPath = filename:join( els_uri:path(RootUri)
- , "../rebar3_release/erlang_ls.config"),
- InitOpts = #{ <<"erlang">>
- => #{ <<"config_path">> => ConfigPath }},
- els_client:initialize(RootUri, InitOpts),
- Result = els_config:get(macros),
- Expected = [#{"name" => "DEFINED_FOR_RELATIVE_TEST"}],
- ?assertEqual(Expected, Result),
- ok.
+ RootUri = els_test_utils:root_uri(),
+ ConfigPath = filename:join(
+ els_uri:path(RootUri),
+ "../rebar3_release/erlang_ls.config"
+ ),
+ InitOpts = #{
+ <<"erlang">> =>
+ #{<<"config_path">> => ConfigPath}
+ },
+ els_client:initialize(RootUri, InitOpts),
+ Result = els_config:get(macros),
+ Expected = [#{"name" => "DEFINED_FOR_RELATIVE_TEST"}],
+ ?assertEqual(Expected, Result),
+ ok.
-spec initialize_diagnostics_default(config()) -> ok.
initialize_diagnostics_default(Config) ->
- RootUri = els_test_utils:root_uri(),
- DataDir = ?config(data_dir, Config),
- ConfigPath = filename:join(DataDir, "diagnostics_default.config"),
- InitOpts = #{ <<"erlang">> => #{ <<"config_path">> => ConfigPath }},
- els_client:initialize(RootUri, InitOpts),
- Expected = els_diagnostics:default_diagnostics(),
- Result = els_diagnostics:enabled_diagnostics(),
- ?assertEqual(Expected, Result),
- ok.
+ RootUri = els_test_utils:root_uri(),
+ DataDir = ?config(data_dir, Config),
+ ConfigPath = filename:join(DataDir, "diagnostics_default.config"),
+ InitOpts = #{<<"erlang">> => #{<<"config_path">> => ConfigPath}},
+ els_client:initialize(RootUri, InitOpts),
+ Expected = els_diagnostics:default_diagnostics(),
+ Result = els_diagnostics:enabled_diagnostics(),
+ ?assertEqual(Expected, Result),
+ ok.
-spec initialize_diagnostics_custom(config()) -> ok.
initialize_diagnostics_custom(Config) ->
- RootUri = els_test_utils:root_uri(),
- DataDir = ?config(data_dir, Config),
- ConfigPath = filename:join(DataDir, "diagnostics_custom.config"),
- InitOpts = #{ <<"erlang">> => #{ <<"config_path">> => ConfigPath }},
- els_client:initialize(RootUri, InitOpts),
- Expected = [ <<"bound_var_in_pattern">>
- , <<"compiler">>
- , <<"crossref">>
- , <<"dialyzer">>
- , <<"unused_includes">>
- , <<"unused_macros">>
- , <<"unused_record_fields">>
- ],
- Result = els_diagnostics:enabled_diagnostics(),
- ?assertEqual(Expected, Result),
- ok.
+ RootUri = els_test_utils:root_uri(),
+ DataDir = ?config(data_dir, Config),
+ ConfigPath = filename:join(DataDir, "diagnostics_custom.config"),
+ InitOpts = #{<<"erlang">> => #{<<"config_path">> => ConfigPath}},
+ els_client:initialize(RootUri, InitOpts),
+ Expected = [
+ <<"bound_var_in_pattern">>,
+ <<"compiler">>,
+ <<"crossref">>,
+ <<"dialyzer">>,
+ <<"unused_includes">>,
+ <<"unused_macros">>,
+ <<"unused_record_fields">>
+ ],
+ Result = els_diagnostics:enabled_diagnostics(),
+ ?assertEqual(Expected, Result),
+ ok.
-spec initialize_diagnostics_invalid(config()) -> ok.
initialize_diagnostics_invalid(Config) ->
- RootUri = els_test_utils:root_uri(),
- DataDir = ?config(data_dir, Config),
- ConfigPath = filename:join(DataDir, "diagnostics_invalid.config"),
- InitOpts = #{ <<"erlang">> => #{ <<"config_path">> => ConfigPath }},
- els_client:initialize(RootUri, InitOpts),
- Result = els_diagnostics:enabled_diagnostics(),
- Expected = [ <<"bound_var_in_pattern">>
- , <<"compiler">>
- , <<"crossref">>
- , <<"dialyzer">>
- , <<"elvis">>
- , <<"unused_includes">>
- , <<"unused_macros">>
- , <<"unused_record_fields">>
- ],
- ?assertEqual(Expected, Result),
- ok.
+ RootUri = els_test_utils:root_uri(),
+ DataDir = ?config(data_dir, Config),
+ ConfigPath = filename:join(DataDir, "diagnostics_invalid.config"),
+ InitOpts = #{<<"erlang">> => #{<<"config_path">> => ConfigPath}},
+ els_client:initialize(RootUri, InitOpts),
+ Result = els_diagnostics:enabled_diagnostics(),
+ Expected = [
+ <<"bound_var_in_pattern">>,
+ <<"compiler">>,
+ <<"crossref">>,
+ <<"dialyzer">>,
+ <<"elvis">>,
+ <<"unused_includes">>,
+ <<"unused_macros">>,
+ <<"unused_record_fields">>
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
-spec initialize_lenses_default(config()) -> ok.
initialize_lenses_default(Config) ->
- RootUri = els_test_utils:root_uri(),
- DataDir = ?config(data_dir, Config),
- ConfigPath = filename:join(DataDir, "lenses_default.config"),
- InitOpts = #{ <<"erlang">> => #{ <<"config_path">> => ConfigPath }},
- els_client:initialize(RootUri, InitOpts),
- Expected = lists:sort(els_code_lens:default_lenses()),
- Result = els_code_lens:enabled_lenses(),
- ?assertEqual(Expected, lists:sort(Result)),
- ok.
+ RootUri = els_test_utils:root_uri(),
+ DataDir = ?config(data_dir, Config),
+ ConfigPath = filename:join(DataDir, "lenses_default.config"),
+ InitOpts = #{<<"erlang">> => #{<<"config_path">> => ConfigPath}},
+ els_client:initialize(RootUri, InitOpts),
+ Expected = lists:sort(els_code_lens:default_lenses()),
+ Result = els_code_lens:enabled_lenses(),
+ ?assertEqual(Expected, lists:sort(Result)),
+ ok.
-spec initialize_lenses_custom(config()) -> ok.
initialize_lenses_custom(Config) ->
- RootUri = els_test_utils:root_uri(),
- DataDir = ?config(data_dir, Config),
- ConfigPath = filename:join(DataDir, "lenses_custom.config"),
- InitOpts = #{ <<"erlang">> => #{ <<"config_path">> => ConfigPath }},
- els_client:initialize(RootUri, InitOpts),
- Expected = [ <<"function-references">>
- , <<"server-info">>
- , <<"suggest-spec">>
- ],
- Result = els_code_lens:enabled_lenses(),
- ?assertEqual(Expected, Result),
- ok.
+ RootUri = els_test_utils:root_uri(),
+ DataDir = ?config(data_dir, Config),
+ ConfigPath = filename:join(DataDir, "lenses_custom.config"),
+ InitOpts = #{<<"erlang">> => #{<<"config_path">> => ConfigPath}},
+ els_client:initialize(RootUri, InitOpts),
+ Expected = [
+ <<"function-references">>,
+ <<"server-info">>,
+ <<"suggest-spec">>
+ ],
+ Result = els_code_lens:enabled_lenses(),
+ ?assertEqual(Expected, Result),
+ ok.
-spec initialize_lenses_invalid(config()) -> ok.
initialize_lenses_invalid(Config) ->
- RootUri = els_test_utils:root_uri(),
- DataDir = ?config(data_dir, Config),
- ConfigPath = filename:join(DataDir, "lenses_invalid.config"),
- InitOpts = #{ <<"erlang">> => #{ <<"config_path">> => ConfigPath }},
- els_client:initialize(RootUri, InitOpts),
- Result = els_code_lens:enabled_lenses(),
- Expected = [ <<"ct-run-test">>
- , <<"function-references">>
- , <<"show-behaviour-usages">>
- , <<"suggest-spec">>
- ],
- ?assertEqual(Expected, Result),
- ok.
+ RootUri = els_test_utils:root_uri(),
+ DataDir = ?config(data_dir, Config),
+ ConfigPath = filename:join(DataDir, "lenses_invalid.config"),
+ InitOpts = #{<<"erlang">> => #{<<"config_path">> => ConfigPath}},
+ els_client:initialize(RootUri, InitOpts),
+ Result = els_code_lens:enabled_lenses(),
+ Expected = [
+ <<"ct-run-test">>,
+ <<"function-references">>,
+ <<"show-behaviour-usages">>,
+ <<"suggest-spec">>
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
diff --git a/apps/els_lsp/test/els_io_string_SUITE.erl b/apps/els_lsp/test/els_io_string_SUITE.erl
index d978b400c..a740b0e84 100644
--- a/apps/els_lsp/test/els_io_string_SUITE.erl
+++ b/apps/els_lsp/test/els_io_string_SUITE.erl
@@ -1,16 +1,16 @@
-module(els_io_string_SUITE).
%% CT Callbacks
--export([ init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ scan_forms/1
- ]).
+-export([scan_forms/1]).
%%==============================================================================
%% Includes
@@ -31,11 +31,11 @@ all() -> els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(_TestCase, Config) -> Config.
@@ -49,19 +49,19 @@ end_per_testcase(_TestCase, _Config) -> ok.
-spec scan_forms(config()) -> ok.
scan_forms(_Config) ->
- Path = path(),
- {ok, IoFile} = file:open(Path, [read]),
- Expected = scan_all_forms(IoFile, []),
- ok = file:close(IoFile),
+ Path = path(),
+ {ok, IoFile} = file:open(Path, [read]),
+ Expected = scan_all_forms(IoFile, []),
+ ok = file:close(IoFile),
- {ok, Text} = file:read_file(Path),
- IoString = els_io_string:new(Text),
- Result = scan_all_forms(IoString, []),
- ok = file:close(IoString),
+ {ok, Text} = file:read_file(Path),
+ IoString = els_io_string:new(Text),
+ Result = scan_all_forms(IoString, []),
+ ok = file:close(IoString),
- ?assertEqual(Expected, Result),
+ ?assertEqual(Expected, Result),
- ok.
+ ok.
%%==============================================================================
%% Helper functions
@@ -69,14 +69,14 @@ scan_forms(_Config) ->
-spec scan_all_forms(file:io_device(), [any()]) -> [any()].
scan_all_forms(IoDevice, Acc) ->
- case io:scan_erl_form(IoDevice, "") of
- {ok, Tokens, _} ->
- scan_all_forms(IoDevice, [Tokens | Acc]);
- {eof, _} ->
- Acc
- end.
+ case io:scan_erl_form(IoDevice, "") of
+ {ok, Tokens, _} ->
+ scan_all_forms(IoDevice, [Tokens | Acc]);
+ {eof, _} ->
+ Acc
+ end.
-spec path() -> string().
path() ->
- RootPath = els_test_utils:root_path(),
- filename:join([RootPath, "src", "code_navigation.erl"]).
+ RootPath = els_test_utils:root_path(),
+ filename:join([RootPath, "src", "code_navigation.erl"]).
diff --git a/apps/els_lsp/test/els_mock_diagnostics.erl b/apps/els_lsp/test/els_mock_diagnostics.erl
index 1e3ebd6a0..2be39d315 100644
--- a/apps/els_lsp/test/els_mock_diagnostics.erl
+++ b/apps/els_lsp/test/els_mock_diagnostics.erl
@@ -1,43 +1,46 @@
-module(els_mock_diagnostics).
--export([ setup/0
- , teardown/0
- , subscribe/0
- , wait_until_complete/0
- ]).
+-export([
+ setup/0,
+ teardown/0,
+ subscribe/0,
+ wait_until_complete/0
+]).
-spec setup() -> ok.
setup() ->
- meck:new(els_diagnostics_provider, [passthrough, no_link]).
+ meck:new(els_diagnostics_provider, [passthrough, no_link]).
-spec teardown() -> ok.
teardown() ->
- meck:unload(els_diagnostics_provider).
+ meck:unload(els_diagnostics_provider).
-spec subscribe() -> ok.
subscribe() ->
- Self = self(),
- meck:expect( els_diagnostics_provider
- , publish
- , fun(Uri, Diagnostics) ->
- Self ! {on_complete, Diagnostics},
- meck:passthrough([Uri, Diagnostics])
- end
- ),
- ok.
+ Self = self(),
+ meck:expect(
+ els_diagnostics_provider,
+ publish,
+ fun(Uri, Diagnostics) ->
+ Self ! {on_complete, Diagnostics},
+ meck:passthrough([Uri, Diagnostics])
+ end
+ ),
+ ok.
-spec wait_until_complete() -> [els_diagnostics:diagnostic()].
wait_until_complete() ->
- wait_until_complete(els_diagnostics:enabled_diagnostics(), []).
+ wait_until_complete(els_diagnostics:enabled_diagnostics(), []).
--spec wait_until_complete( [els_diagnostics:diagnostic_id()]
- , [els_diagnostics:diagnostic()]
- ) ->
- [els_diagnostics:diagnostic()].
+-spec wait_until_complete(
+ [els_diagnostics:diagnostic_id()],
+ [els_diagnostics:diagnostic()]
+) ->
+ [els_diagnostics:diagnostic()].
wait_until_complete([], Diagnostics) ->
- Diagnostics;
-wait_until_complete([_|Rest], _Diagnostics) ->
- receive
- {on_complete, Diagnostics} ->
- wait_until_complete(Rest, Diagnostics)
- end.
+ Diagnostics;
+wait_until_complete([_ | Rest], _Diagnostics) ->
+ receive
+ {on_complete, Diagnostics} ->
+ wait_until_complete(Rest, Diagnostics)
+ end.
diff --git a/apps/els_lsp/test/els_parser_SUITE.erl b/apps/els_lsp/test/els_parser_SUITE.erl
index ecc18ddc9..547021335 100644
--- a/apps/els_lsp/test/els_parser_SUITE.erl
+++ b/apps/els_lsp/test/els_parser_SUITE.erl
@@ -1,34 +1,36 @@
-module(els_parser_SUITE).
%% CT Callbacks
--export([ all/0
- , init_per_suite/1
- , end_per_suite/1
- ]).
+-export([
+ all/0,
+ init_per_suite/1,
+ end_per_suite/1
+]).
%% Test cases
--export([ specs_location/1
- , parse_invalid_code/1
- , parse_incomplete_function/1
- , parse_incomplete_spec/1
- , parse_incomplete_type/1
- , parse_no_tokens/1
- , define/1
- , underscore_macro/1
- , specs_with_record/1
- , types_with_record/1
- , types_with_types/1
- , record_def_with_types/1
- , record_def_with_record_type/1
- , callback_recursive/1
- , specs_recursive/1
- , types_recursive/1
- , opaque_recursive/1
- , record_def_recursive/1
- , var_in_application/1
- , unicode_clause_pattern/1
- , latin1_source_code/1
- ]).
+-export([
+ specs_location/1,
+ parse_invalid_code/1,
+ parse_incomplete_function/1,
+ parse_incomplete_spec/1,
+ parse_incomplete_type/1,
+ parse_no_tokens/1,
+ define/1,
+ underscore_macro/1,
+ specs_with_record/1,
+ types_with_record/1,
+ types_with_types/1,
+ record_def_with_types/1,
+ record_def_with_record_type/1,
+ callback_recursive/1,
+ specs_recursive/1,
+ types_recursive/1,
+ opaque_recursive/1,
+ record_def_recursive/1,
+ var_in_application/1,
+ unicode_clause_pattern/1,
+ latin1_source_code/1
+]).
%%==============================================================================
%% Includes
@@ -59,269 +61,320 @@ all() -> els_test_utils:all(?MODULE).
%% Issue #120
-spec specs_location(config()) -> ok.
specs_location(_Config) ->
- Text = "-spec foo(integer()) -> any(); (atom()) -> pid().",
- ?assertMatch([_], parse_find_pois(Text, spec, {foo, 1})),
- ok.
+ Text = "-spec foo(integer()) -> any(); (atom()) -> pid().",
+ ?assertMatch([_], parse_find_pois(Text, spec, {foo, 1})),
+ ok.
%% Issue #170 - scanning error does not crash the parser
-spec parse_invalid_code(config()) -> ok.
parse_invalid_code(_Config) ->
- Text = "foo(X) -> 16#.",
- %% Currently, if scanning fails (eg. invalid integer), no POIs are created
- {ok, []} = els_parser:parse(Text),
- %% In the future, it would be nice to have at least the POIs before the error
- %% ?assertMatch([#{id := {foo, 1}}], parse_find_pois(Text, function)),
- %% ?assertMatch([#{id := 'X'}], parse_find_pois(Text, variable)),
-
- %% Or at least the POIs from the previous forms
- Text2 =
- "bar() -> ok.\n"
- "foo() -> 'ato",
- %% (unterminated atom)
- {ok, []} = els_parser:parse(Text2),
- %% ?assertMatch([#{id := {bar, 0}}], parse_find_pois(Text2, function)),
- ok.
+ Text = "foo(X) -> 16#.",
+ %% Currently, if scanning fails (eg. invalid integer), no POIs are created
+ {ok, []} = els_parser:parse(Text),
+ %% In the future, it would be nice to have at least the POIs before the error
+ %% ?assertMatch([#{id := {foo, 1}}], parse_find_pois(Text, function)),
+ %% ?assertMatch([#{id := 'X'}], parse_find_pois(Text, variable)),
+
+ %% Or at least the POIs from the previous forms
+ Text2 =
+ "bar() -> ok.\n"
+ "foo() -> 'ato",
+ %% (unterminated atom)
+ {ok, []} = els_parser:parse(Text2),
+ %% ?assertMatch([#{id := {bar, 0}}], parse_find_pois(Text2, function)),
+ ok.
%% Issue #1037
-spec parse_incomplete_function(config()) -> ok.
parse_incomplete_function(_Config) ->
- Text = "f(VarA) -> VarB = g(), case h() of VarC -> Var",
-
- %% VarA and VarB are found, but VarC is not
- ?assertMatch([#{id := 'VarA'},
- #{id := 'VarB'}], parse_find_pois(Text, variable)),
- %% g() is found but h() is not
- ?assertMatch([#{id := {g, 0}}], parse_find_pois(Text, application)),
-
- ?assertMatch([#{id := {f, 1}}], parse_find_pois(Text, function)),
- ok.
+ Text = "f(VarA) -> VarB = g(), case h() of VarC -> Var",
+
+ %% VarA and VarB are found, but VarC is not
+ ?assertMatch(
+ [
+ #{id := 'VarA'},
+ #{id := 'VarB'}
+ ],
+ parse_find_pois(Text, variable)
+ ),
+ %% g() is found but h() is not
+ ?assertMatch([#{id := {g, 0}}], parse_find_pois(Text, application)),
+
+ ?assertMatch([#{id := {f, 1}}], parse_find_pois(Text, function)),
+ ok.
-spec parse_incomplete_spec(config()) -> ok.
parse_incomplete_spec(_Config) ->
- Text = "-spec f() -> aa bb cc\n.",
+ Text = "-spec f() -> aa bb cc\n.",
- %% spec range ends where the original dot ends, including ignored parts
- ?assertMatch([#{id := {f, 0}, range := #{from := {1, 1}, to := {2, 2}}}],
- parse_find_pois(Text, spec)),
- %% only first atom is found
- ?assertMatch([#{id := aa}], parse_find_pois(Text, atom)),
- ok.
+ %% spec range ends where the original dot ends, including ignored parts
+ ?assertMatch(
+ [#{id := {f, 0}, range := #{from := {1, 1}, to := {2, 2}}}],
+ parse_find_pois(Text, spec)
+ ),
+ %% only first atom is found
+ ?assertMatch([#{id := aa}], parse_find_pois(Text, atom)),
+ ok.
-spec parse_incomplete_type(config()) -> ok.
parse_incomplete_type(_Config) ->
- Text = "-type t(A) :: {A aa bb cc}\n.",
-
- %% type range ends where the original dot ends, including ignored parts
- ?assertMatch([#{id := {t, 1}, range := #{from := {1, 1}, to := {2, 2}}}],
- parse_find_pois(Text, type_definition)),
- %% only first var is found
- ?assertMatch([#{id := 'A'}], parse_find_pois(Text, variable)),
-
- Text2 = "-type t",
- ?assertMatch({ok, [#{ kind := type_definition, id := {t, 0}}]},
- els_parser:parse(Text2)),
- Text3 = "-type ?t",
- ?assertMatch({ok, [#{ kind := macro, id := t}]},
- els_parser:parse(Text3)),
- %% this is not incomplete - there is no way this will become valid erlang
- %% but erlfmt can parse it
- Text4 = "-type T",
- ?assertMatch({ok, [#{ kind := variable, id := 'T'}]},
- els_parser:parse(Text4)),
- Text5 = "-type [1, 2]",
- {ok, []} = els_parser:parse(Text5),
-
- %% no type args - assume zero args
- Text11 = "-type t :: 1.",
- ?assertMatch({ok, [#{ kind := type_definition, id := {t, 0}}]},
- els_parser:parse(Text11)),
- %% no macro args - this is 100% valid code
- Text12= "-type ?t :: 1.",
- ?assertMatch({ok, [#{ kind := macro, id := t}]},
- els_parser:parse(Text12)),
- Text13 = "-type T :: 1.",
- ?assertMatch({ok, [#{ kind := variable, id := 'T'}]},
- els_parser:parse(Text13)),
-
- ok.
+ Text = "-type t(A) :: {A aa bb cc}\n.",
+
+ %% type range ends where the original dot ends, including ignored parts
+ ?assertMatch(
+ [#{id := {t, 1}, range := #{from := {1, 1}, to := {2, 2}}}],
+ parse_find_pois(Text, type_definition)
+ ),
+ %% only first var is found
+ ?assertMatch([#{id := 'A'}], parse_find_pois(Text, variable)),
+
+ Text2 = "-type t",
+ ?assertMatch(
+ {ok, [#{kind := type_definition, id := {t, 0}}]},
+ els_parser:parse(Text2)
+ ),
+ Text3 = "-type ?t",
+ ?assertMatch(
+ {ok, [#{kind := macro, id := t}]},
+ els_parser:parse(Text3)
+ ),
+ %% this is not incomplete - there is no way this will become valid erlang
+ %% but erlfmt can parse it
+ Text4 = "-type T",
+ ?assertMatch(
+ {ok, [#{kind := variable, id := 'T'}]},
+ els_parser:parse(Text4)
+ ),
+ Text5 = "-type [1, 2]",
+ {ok, []} = els_parser:parse(Text5),
+
+ %% no type args - assume zero args
+ Text11 = "-type t :: 1.",
+ ?assertMatch(
+ {ok, [#{kind := type_definition, id := {t, 0}}]},
+ els_parser:parse(Text11)
+ ),
+ %% no macro args - this is 100% valid code
+ Text12 = "-type ?t :: 1.",
+ ?assertMatch(
+ {ok, [#{kind := macro, id := t}]},
+ els_parser:parse(Text12)
+ ),
+ Text13 = "-type T :: 1.",
+ ?assertMatch(
+ {ok, [#{kind := variable, id := 'T'}]},
+ els_parser:parse(Text13)
+ ),
+
+ ok.
%% Issue #1171
parse_no_tokens(_Config) ->
- %% scanning text containing only whitespaces returns an empty list of tokens,
- %% which used to crash els_parser
- Text1 = " \n ",
- {ok, []} = els_parser:parse(Text1),
- %% `els_parser:parse' actually catches the exception and only prints a warning
- %% log. In order to make sure there is no crash, we need to call an internal
- %% debug function that would really crash and make the test case fail
- error = els_parser:parse_incomplete_text(Text1, {1, 1}),
-
- %% same for text only containing comments
- Text2 = "%% only a comment",
- {ok, []} = els_parser:parse(Text2),
- error = els_parser:parse_incomplete_text(Text2, {1, 1}),
-
- %% trailing comment, also used to crash els_parser
- Text3 =
- "-module(m).\n"
- "%% trailing comment",
- {ok, [#{id := m, kind := module}]} = els_parser:parse(Text3).
+ %% scanning text containing only whitespaces returns an empty list of tokens,
+ %% which used to crash els_parser
+ Text1 = " \n ",
+ {ok, []} = els_parser:parse(Text1),
+ %% `els_parser:parse' actually catches the exception and only prints a warning
+ %% log. In order to make sure there is no crash, we need to call an internal
+ %% debug function that would really crash and make the test case fail
+ error = els_parser:parse_incomplete_text(Text1, {1, 1}),
+
+ %% same for text only containing comments
+ Text2 = "%% only a comment",
+ {ok, []} = els_parser:parse(Text2),
+ error = els_parser:parse_incomplete_text(Text2, {1, 1}),
+
+ %% trailing comment, also used to crash els_parser
+ Text3 =
+ "-module(m).\n"
+ "%% trailing comment",
+ {ok, [#{id := m, kind := module}]} = els_parser:parse(Text3).
-spec define(config()) -> ok.
define(_Config) ->
- ?assertMatch({ok, [ #{id := {'MACRO', 2}, kind := define}
- , #{id := 'B', kind := variable}
- , #{id := 'A', kind := variable}
- , #{id := 'B', kind := variable}
- , #{id := 'A', kind := variable}
- ]},
- els_parser:parse("-define(MACRO(A, B), A:B()).")).
+ ?assertMatch(
+ {ok, [
+ #{id := {'MACRO', 2}, kind := define},
+ #{id := 'B', kind := variable},
+ #{id := 'A', kind := variable},
+ #{id := 'B', kind := variable},
+ #{id := 'A', kind := variable}
+ ]},
+ els_parser:parse("-define(MACRO(A, B), A:B()).")
+ ).
-spec underscore_macro(config()) -> ok.
underscore_macro(_Config) ->
- ?assertMatch({ok, [#{id := {'_', 1}, kind := define} | _]},
- els_parser:parse("-define(_(Text), gettexter:gettext(Text)).")),
- ?assertMatch({ok, [#{id := '_', kind := define} | _]},
- els_parser:parse("-define(_, smth).")),
- ?assertMatch({ok, [#{id := '_', kind := macro}]},
- els_parser:parse("?_.")),
- ?assertMatch({ok, [#{id := {'_', 1}, kind := macro} | _]},
- els_parser:parse("?_(ok).")),
- ok.
+ ?assertMatch(
+ {ok, [#{id := {'_', 1}, kind := define} | _]},
+ els_parser:parse("-define(_(Text), gettexter:gettext(Text)).")
+ ),
+ ?assertMatch(
+ {ok, [#{id := '_', kind := define} | _]},
+ els_parser:parse("-define(_, smth).")
+ ),
+ ?assertMatch(
+ {ok, [#{id := '_', kind := macro}]},
+ els_parser:parse("?_.")
+ ),
+ ?assertMatch(
+ {ok, [#{id := {'_', 1}, kind := macro} | _]},
+ els_parser:parse("?_(ok).")
+ ),
+ ok.
%% Issue #815
-spec specs_with_record(config()) -> ok.
specs_with_record(_Config) ->
- Text = "-record(bar, {a, b}). -spec foo(#bar{}) -> any().",
- ?assertMatch([_], parse_find_pois(Text, record_expr, bar)),
- ok.
+ Text = "-record(bar, {a, b}). -spec foo(#bar{}) -> any().",
+ ?assertMatch([_], parse_find_pois(Text, record_expr, bar)),
+ ok.
%% Issue #818
-spec types_with_record(config()) -> ok.
types_with_record(_Config) ->
- Text1 = "-record(bar, {a, b}). -type foo() :: #bar{}.",
- ?assertMatch([_], parse_find_pois(Text1, record_expr, bar)),
+ Text1 = "-record(bar, {a, b}). -type foo() :: #bar{}.",
+ ?assertMatch([_], parse_find_pois(Text1, record_expr, bar)),
- Text2 = "-record(bar, {a, b}). -type foo() :: #bar{f1 :: t()}.",
- ?assertMatch([_], parse_find_pois(Text2, record_expr, bar)),
- ?assertMatch([_], parse_find_pois(Text2, record_field, {bar, f1})),
- ok.
+ Text2 = "-record(bar, {a, b}). -type foo() :: #bar{f1 :: t()}.",
+ ?assertMatch([_], parse_find_pois(Text2, record_expr, bar)),
+ ?assertMatch([_], parse_find_pois(Text2, record_field, {bar, f1})),
+ ok.
%% Issue #818
-spec types_with_types(config()) -> ok.
types_with_types(_Config) ->
- Text = "-type bar() :: {a,b}. -type foo() :: bar().",
- ?assertMatch([_], parse_find_pois(Text, type_application, {bar, 0})),
- ok.
+ Text = "-type bar() :: {a,b}. -type foo() :: bar().",
+ ?assertMatch([_], parse_find_pois(Text, type_application, {bar, 0})),
+ ok.
-spec record_def_with_types(config()) -> ok.
record_def_with_types(_Config) ->
- Text1 = "-record(r1, {f1 :: t1()}).",
- ?assertMatch([_], parse_find_pois(Text1, type_application, {t1, 0})),
+ Text1 = "-record(r1, {f1 :: t1()}).",
+ ?assertMatch([_], parse_find_pois(Text1, type_application, {t1, 0})),
- Text2 = "-record(r1, {f1 = defval :: t2()}).",
- ?assertMatch([_], parse_find_pois(Text2, type_application, {t2, 0})),
- %% No redundanct atom POIs
- ?assertMatch([#{id := defval}], parse_find_pois(Text2, atom)),
+ Text2 = "-record(r1, {f1 = defval :: t2()}).",
+ ?assertMatch([_], parse_find_pois(Text2, type_application, {t2, 0})),
+ %% No redundanct atom POIs
+ ?assertMatch([#{id := defval}], parse_find_pois(Text2, atom)),
- Text3 = "-record(r1, {f1 :: t1(integer())}).",
- ?assertMatch([_], parse_find_pois(Text3, type_application, {t1, 1})),
- %% POI for builtin types like integer()
- ?assertMatch([#{id := {t1, 1}}, #{id := {erlang, integer, 0}} ],
- parse_find_pois(Text3, type_application)),
+ Text3 = "-record(r1, {f1 :: t1(integer())}).",
+ ?assertMatch([_], parse_find_pois(Text3, type_application, {t1, 1})),
+ %% POI for builtin types like integer()
+ ?assertMatch(
+ [#{id := {t1, 1}}, #{id := {erlang, integer, 0}}],
+ parse_find_pois(Text3, type_application)
+ ),
- Text4 = "-record(r1, {f1 :: m:t1(integer())}).",
- ?assertMatch([_], parse_find_pois(Text4, type_application, {m, t1, 1})),
- %% No redundanct atom POIs
- ?assertMatch([], parse_find_pois(Text4, atom)),
+ Text4 = "-record(r1, {f1 :: m:t1(integer())}).",
+ ?assertMatch([_], parse_find_pois(Text4, type_application, {m, t1, 1})),
+ %% No redundanct atom POIs
+ ?assertMatch([], parse_find_pois(Text4, atom)),
- ok.
+ ok.
-spec record_def_with_record_type(config()) -> ok.
record_def_with_record_type(_Config) ->
- Text1 = "-record(r1, {f1 :: #r2{}}).",
- ?assertMatch([_], parse_find_pois(Text1, record_expr, r2)),
- %% No redundanct atom POIs
- ?assertMatch([], parse_find_pois(Text1, atom)),
-
- Text2 = "-record(r1, {f1 :: #r2{f2 :: t2()}}).",
- ?assertMatch([_], parse_find_pois(Text2, record_expr, r2)),
- ?assertMatch([_], parse_find_pois(Text2, record_field, {r2, f2})),
- %% No redundanct atom POIs
- ?assertMatch([], parse_find_pois(Text2, atom)),
- ok.
+ Text1 = "-record(r1, {f1 :: #r2{}}).",
+ ?assertMatch([_], parse_find_pois(Text1, record_expr, r2)),
+ %% No redundanct atom POIs
+ ?assertMatch([], parse_find_pois(Text1, atom)),
+
+ Text2 = "-record(r1, {f1 :: #r2{f2 :: t2()}}).",
+ ?assertMatch([_], parse_find_pois(Text2, record_expr, r2)),
+ ?assertMatch([_], parse_find_pois(Text2, record_field, {r2, f2})),
+ %% No redundanct atom POIs
+ ?assertMatch([], parse_find_pois(Text2, atom)),
+ ok.
-spec callback_recursive(config()) -> ok.
callback_recursive(_Config) ->
- Text = "-callback foo(#r1{f1 :: m:t1(#r2{f2 :: t2(t3())})}) -> any().",
- assert_recursive_types(Text).
+ Text = "-callback foo(#r1{f1 :: m:t1(#r2{f2 :: t2(t3())})}) -> any().",
+ assert_recursive_types(Text).
-spec specs_recursive(config()) -> ok.
specs_recursive(_Config) ->
- Text = "-spec foo(#r1{f1 :: m:t1(#r2{f2 :: t2(t3())})}) -> any().",
- assert_recursive_types(Text).
+ Text = "-spec foo(#r1{f1 :: m:t1(#r2{f2 :: t2(t3())})}) -> any().",
+ assert_recursive_types(Text).
-spec types_recursive(config()) -> ok.
types_recursive(_Config) ->
- Text = "-type foo() :: #r1{f1 :: m:t1(#r2{f2 :: t2(t3())})}.",
- assert_recursive_types(Text).
+ Text = "-type foo() :: #r1{f1 :: m:t1(#r2{f2 :: t2(t3())})}.",
+ assert_recursive_types(Text).
-spec opaque_recursive(config()) -> ok.
opaque_recursive(_Config) ->
- Text = "-opaque foo() :: #r1{f1 :: m:t1(#r2{f2 :: t2(t3())})}.",
- assert_recursive_types(Text).
+ Text = "-opaque foo() :: #r1{f1 :: m:t1(#r2{f2 :: t2(t3())})}.",
+ assert_recursive_types(Text).
-spec record_def_recursive(config()) -> ok.
record_def_recursive(_Config) ->
- Text = "-record(foo, {field :: #r1{f1 :: m:t1(#r2{f2 :: t2(t3())})}}).",
- assert_recursive_types(Text).
+ Text = "-record(foo, {field :: #r1{f1 :: m:t1(#r2{f2 :: t2(t3())})}}).",
+ assert_recursive_types(Text).
assert_recursive_types(Text) ->
- ?assertMatch([#{id := r1},
- #{id := r2}],
- parse_find_pois(Text, record_expr)),
- ?assertMatch([#{id := {r1, f1}},
- #{id := {r2, f2}}],
- parse_find_pois(Text, record_field)),
- ?assertMatch([#{id := {m, t1, 1}},
- #{id := {t2, 1}},
- #{id := {t3, 0}} | _],
- parse_find_pois(Text, type_application)),
- ok.
+ ?assertMatch(
+ [
+ #{id := r1},
+ #{id := r2}
+ ],
+ parse_find_pois(Text, record_expr)
+ ),
+ ?assertMatch(
+ [
+ #{id := {r1, f1}},
+ #{id := {r2, f2}}
+ ],
+ parse_find_pois(Text, record_field)
+ ),
+ ?assertMatch(
+ [
+ #{id := {m, t1, 1}},
+ #{id := {t2, 1}},
+ #{id := {t3, 0}}
+ | _
+ ],
+ parse_find_pois(Text, type_application)
+ ),
+ ok.
var_in_application(_Config) ->
- Text1 = "f() -> Mod:f(42).",
- ?assertMatch([#{id := 'Mod'}], parse_find_pois(Text1, variable)),
+ Text1 = "f() -> Mod:f(42).",
+ ?assertMatch([#{id := 'Mod'}], parse_find_pois(Text1, variable)),
- Text2 = "f() -> mod:Fun(42).",
- ?assertMatch([#{id := 'Fun'}], parse_find_pois(Text2, variable)),
- ok.
+ Text2 = "f() -> mod:Fun(42).",
+ ?assertMatch([#{id := 'Fun'}], parse_find_pois(Text2, variable)),
+ ok.
-spec unicode_clause_pattern(config()) -> ok.
unicode_clause_pattern(_Config) ->
- %% From OTP compiler's bs_utf_SUITE.erl
- Text = "match_literal(<<\"Мастер и Маргарита\"/utf8>>) -> mm_utf8.",
- ?assertMatch([#{data := <<"(<<\"", _/binary>>}],
- parse_find_pois(Text, function_clause, {match_literal, 1, 1})),
- ok.
+ %% From OTP compiler's bs_utf_SUITE.erl
+ Text = "match_literal(<<\"Мастер и Маргарита\"/utf8>>) -> mm_utf8.",
+ ?assertMatch(
+ [#{data := <<"(<<\"", _/binary>>}],
+ parse_find_pois(Text, function_clause, {match_literal, 1, 1})
+ ),
+ ok.
%% Issue #306, PR #592
-spec latin1_source_code(config()) -> ok.
latin1_source_code(_Config) ->
- Text = lists:flatten(["f(\"", 200, "\") -> 200. %% ", 200]),
- ?assertMatch([#{data := <<"(\"È\") "/utf8>>}],
- parse_find_pois(Text, function_clause, {f, 1, 1})),
- ok.
+ Text = lists:flatten(["f(\"", 200, "\") -> 200. %% ", 200]),
+ ?assertMatch(
+ [#{data := <<"(\"È\") "/utf8>>}],
+ parse_find_pois(Text, function_clause, {f, 1, 1})
+ ),
+ ok.
%%==============================================================================
%% Helper functions
%%==============================================================================
-spec parse_find_pois(string(), poi_kind()) -> [poi()].
parse_find_pois(Text, Kind) ->
- {ok, POIs} = els_parser:parse(Text),
- SortedPOIs = els_poi:sort(POIs),
- [POI || #{kind := Kind1} = POI <- SortedPOIs, Kind1 =:= Kind].
+ {ok, POIs} = els_parser:parse(Text),
+ SortedPOIs = els_poi:sort(POIs),
+ [POI || #{kind := Kind1} = POI <- SortedPOIs, Kind1 =:= Kind].
-spec parse_find_pois(string(), poi_kind(), poi_id()) -> [poi()].
parse_find_pois(Text, Kind, Id) ->
- [POI || #{id := Id1} = POI <- parse_find_pois(Text, Kind), Id1 =:= Id].
+ [POI || #{id := Id1} = POI <- parse_find_pois(Text, Kind), Id1 =:= Id].
diff --git a/apps/els_lsp/test/els_parser_macros_SUITE.erl b/apps/els_lsp/test/els_parser_macros_SUITE.erl
index 409c62242..4c391f1cf 100644
--- a/apps/els_lsp/test/els_parser_macros_SUITE.erl
+++ b/apps/els_lsp/test/els_parser_macros_SUITE.erl
@@ -1,24 +1,26 @@
-module(els_parser_macros_SUITE).
%% CT Callbacks
--export([ all/0
- , init_per_suite/1
- , end_per_suite/1
- ]).
+-export([
+ all/0,
+ init_per_suite/1,
+ end_per_suite/1
+]).
%% Test cases
--export([ callback_macro/1
- , spec_macro/1
- , type_macro/1
- , opaque_macro/1
- , wild_attrbibute_macro/1
- , type_name_macro/1
- , spec_name_macro/1
- , macro_in_application/1
- , record_def_field_macro/1
- , module_macro_as_record_name/1
- , other_macro_as_record_name/1
- ]).
+-export([
+ callback_macro/1,
+ spec_macro/1,
+ type_macro/1,
+ opaque_macro/1,
+ wild_attrbibute_macro/1,
+ type_name_macro/1,
+ spec_name_macro/1,
+ macro_in_application/1,
+ record_def_field_macro/1,
+ module_macro_as_record_name/1,
+ other_macro_as_record_name/1
+]).
%%==============================================================================
%% Includes
@@ -49,171 +51,193 @@ all() -> els_test_utils:all(?MODULE).
%%==============================================================================
-spec callback_macro(config()) -> ok.
callback_macro(_Config) ->
- Text = "-callback foo() -> ?M.",
- ?assertMatch([_], parse_find_pois(Text, callback, {foo, 0})),
- ?assertMatch([_], parse_find_pois(Text, macro, 'M')),
- ok.
+ Text = "-callback foo() -> ?M.",
+ ?assertMatch([_], parse_find_pois(Text, callback, {foo, 0})),
+ ?assertMatch([_], parse_find_pois(Text, macro, 'M')),
+ ok.
-spec spec_macro(config()) -> ok.
spec_macro(_Config) ->
- Text = "-spec foo() -> ?M().",
- ?assertMatch([_], parse_find_pois(Text, spec, {foo, 0})),
- ?assertMatch([_], parse_find_pois(Text, macro, {'M', 0})),
- ok.
+ Text = "-spec foo() -> ?M().",
+ ?assertMatch([_], parse_find_pois(Text, spec, {foo, 0})),
+ ?assertMatch([_], parse_find_pois(Text, macro, {'M', 0})),
+ ok.
-spec type_macro(config()) -> ok.
type_macro(_Config) ->
- Text = "-type t() :: ?M(a, b, c).",
- ?assertMatch([_], parse_find_pois(Text, type_definition, {t, 0})),
- ?assertMatch([_], parse_find_pois(Text, macro, {'M', 3})),
- ok.
+ Text = "-type t() :: ?M(a, b, c).",
+ ?assertMatch([_], parse_find_pois(Text, type_definition, {t, 0})),
+ ?assertMatch([_], parse_find_pois(Text, macro, {'M', 3})),
+ ok.
-spec opaque_macro(config()) -> ok.
opaque_macro(_Config) ->
- Text = "-opaque o() :: ?M(a, b).",
- ?assertMatch([_], parse_find_pois(Text, type_definition, {o, 0})),
- ?assertMatch([_], parse_find_pois(Text, macro, {'M', 2})),
- ok.
+ Text = "-opaque o() :: ?M(a, b).",
+ ?assertMatch([_], parse_find_pois(Text, type_definition, {o, 0})),
+ ?assertMatch([_], parse_find_pois(Text, macro, {'M', 2})),
+ ok.
-spec wild_attrbibute_macro(config()) -> ok.
wild_attrbibute_macro(_Config) ->
- %% This is parsed as -(?M(foo)), rather than -(?M)(foo)
- Text = "-?M(foo).",
- ?assertMatch([_], parse_find_pois(Text, macro, {'M', 1})),
- ?assertMatch([_], parse_find_pois(Text, atom, foo)),
- ok.
+ %% This is parsed as -(?M(foo)), rather than -(?M)(foo)
+ Text = "-?M(foo).",
+ ?assertMatch([_], parse_find_pois(Text, macro, {'M', 1})),
+ ?assertMatch([_], parse_find_pois(Text, atom, foo)),
+ ok.
type_name_macro(_Config) ->
- Text1 = "-type ?M() :: integer() | t().",
- ?assertMatch({ok, [#{kind := type_application, id := {t, 0}},
- #{kind := type_application, id := {erlang, integer, 0}},
- #{kind := macro, id := {'M', 0}}]},
- els_parser:parse(Text1)),
-
- %% The macro is parsed as (?M()), rather than (?M)()
- Text2 = "-type t() :: ?M().",
- ?assertMatch({ok, [#{kind := type_definition, id := {t, 0}},
- #{kind := macro, id := {'M', 0}}]},
- els_parser:parse(Text2)),
-
- %% In this case the macro is parsed as expected as (?T)()
- Text3 = "-type t() :: ?M:?T().",
- ?assertMatch({ok, [#{kind := type_definition, id := {t, 0}},
- #{kind := macro, id := 'M'},
- #{kind := macro, id := 'T'}]},
- els_parser:parse(Text3)),
- ok.
+ Text1 = "-type ?M() :: integer() | t().",
+ ?assertMatch(
+ {ok, [
+ #{kind := type_application, id := {t, 0}},
+ #{kind := type_application, id := {erlang, integer, 0}},
+ #{kind := macro, id := {'M', 0}}
+ ]},
+ els_parser:parse(Text1)
+ ),
+
+ %% The macro is parsed as (?M()), rather than (?M)()
+ Text2 = "-type t() :: ?M().",
+ ?assertMatch(
+ {ok, [
+ #{kind := type_definition, id := {t, 0}},
+ #{kind := macro, id := {'M', 0}}
+ ]},
+ els_parser:parse(Text2)
+ ),
+
+ %% In this case the macro is parsed as expected as (?T)()
+ Text3 = "-type t() :: ?M:?T().",
+ ?assertMatch(
+ {ok, [
+ #{kind := type_definition, id := {t, 0}},
+ #{kind := macro, id := 'M'},
+ #{kind := macro, id := 'T'}
+ ]},
+ els_parser:parse(Text3)
+ ),
+ ok.
spec_name_macro(_Config) ->
- %% Verify the parser does not crash on macros in spec function names and it
- %% still returns an unnamed spec-context and POIs from the definition body
- Text1 = "-spec ?M() -> integer() | t().",
- ?assertMatch([#{id := undefined}], parse_find_pois(Text1, spec)),
- ?assertMatch([_], parse_find_pois(Text1, type_application, {t, 0})),
+ %% Verify the parser does not crash on macros in spec function names and it
+ %% still returns an unnamed spec-context and POIs from the definition body
+ Text1 = "-spec ?M() -> integer() | t().",
+ ?assertMatch([#{id := undefined}], parse_find_pois(Text1, spec)),
+ ?assertMatch([_], parse_find_pois(Text1, type_application, {t, 0})),
- Text2 = "-spec ?MODULE:b() -> integer() | t().",
- ?assertMatch([#{id := undefined}], parse_find_pois(Text2, spec)),
- ?assertMatch([_], parse_find_pois(Text2, type_application, {t, 0})),
+ Text2 = "-spec ?MODULE:b() -> integer() | t().",
+ ?assertMatch([#{id := undefined}], parse_find_pois(Text2, spec)),
+ ?assertMatch([_], parse_find_pois(Text2, type_application, {t, 0})),
- Text3 = "-spec mod:?M() -> integer() | t().",
- ?assertMatch([#{id := undefined}], parse_find_pois(Text3, spec)),
- ?assertMatch([_], parse_find_pois(Text3, type_application, {t, 0})),
- ok.
+ Text3 = "-spec mod:?M() -> integer() | t().",
+ ?assertMatch([#{id := undefined}], parse_find_pois(Text3, spec)),
+ ?assertMatch([_], parse_find_pois(Text3, type_application, {t, 0})),
+ ok.
macro_in_application(_Config) ->
- Text1 = "f() -> ?M:f(42).",
- ?assertMatch([#{id := 'M'}], parse_find_pois(Text1, macro)),
+ Text1 = "f() -> ?M:f(42).",
+ ?assertMatch([#{id := 'M'}], parse_find_pois(Text1, macro)),
- Text2 = "f() -> ?M(mod):f(42).",
- ?assertMatch([#{id := {'M', 1}}], parse_find_pois(Text2, macro)),
+ Text2 = "f() -> ?M(mod):f(42).",
+ ?assertMatch([#{id := {'M', 1}}], parse_find_pois(Text2, macro)),
- %% This is not an application, only a module qualifier before macro M/1
- Text3 = "f() -> mod:?M(42).",
- ?assertMatch([#{id := {'M', 1}}], parse_find_pois(Text3, macro)),
+ %% This is not an application, only a module qualifier before macro M/1
+ Text3 = "f() -> mod:?M(42).",
+ ?assertMatch([#{id := {'M', 1}}], parse_find_pois(Text3, macro)),
- %% Application with macro M/0 as function name
- Text4 = "f() -> mod:?M()(42).",
- ?assertMatch([#{id := {'M', 0}}], parse_find_pois(Text4, macro)),
+ %% Application with macro M/0 as function name
+ Text4 = "f() -> mod:?M()(42).",
+ ?assertMatch([#{id := {'M', 0}}], parse_find_pois(Text4, macro)),
- %% Known limitation of the current implementation,
- %% ?MODULE is handled specially, converted to a local call
- Text5 = "f() -> ?MODULE:foo().",
- ?assertMatch([], parse_find_pois(Text5, macro)),
- ?assertMatch([#{id := {foo, 0}}], parse_find_pois(Text5, application)),
+ %% Known limitation of the current implementation,
+ %% ?MODULE is handled specially, converted to a local call
+ Text5 = "f() -> ?MODULE:foo().",
+ ?assertMatch([], parse_find_pois(Text5, macro)),
+ ?assertMatch([#{id := {foo, 0}}], parse_find_pois(Text5, application)),
- ok.
+ ok.
record_def_field_macro(_Config) ->
- Text1 = "-record(rec, {?M = 1}).",
- ?assertMatch({ok, [#{kind := record, id := rec},
- #{kind := macro, id := 'M'}]},
- els_parser:parse(Text1)),
-
- %% typed record field
- Text2 = "-record(rec, {?M :: integer()}).",
- ?assertMatch({ok, [#{kind := record, id := rec},
- #{kind := type_application, id := {erlang, integer, 0}},
- #{kind := macro, id := 'M'}]},
- els_parser:parse(Text2)),
- ok.
+ Text1 = "-record(rec, {?M = 1}).",
+ ?assertMatch(
+ {ok, [
+ #{kind := record, id := rec},
+ #{kind := macro, id := 'M'}
+ ]},
+ els_parser:parse(Text1)
+ ),
+
+ %% typed record field
+ Text2 = "-record(rec, {?M :: integer()}).",
+ ?assertMatch(
+ {ok, [
+ #{kind := record, id := rec},
+ #{kind := type_application, id := {erlang, integer, 0}},
+ #{kind := macro, id := 'M'}
+ ]},
+ els_parser:parse(Text2)
+ ),
+ ok.
%% Verify that record-related POIs are created with '?MODULE' name
%% also verify that no macro POI is created in these cases
module_macro_as_record_name(_Config) ->
- Text1 = "-record(?MODULE, {f = 1}).",
- ?assertMatch([#{data := #{field_list := [f]}}],
- parse_find_pois(Text1, record, '?MODULE')),
- ?assertMatch([_], parse_find_pois(Text1, record_def_field, {'?MODULE', f})),
- ?assertMatch([], parse_find_pois(Text1, macro)),
-
- Text2 = "-type t() :: #?MODULE{f :: integer()}.",
- ?assertMatch([_], parse_find_pois(Text2, record_expr, '?MODULE')),
- ?assertMatch([_], parse_find_pois(Text2, record_field, {'?MODULE', f})),
- ?assertMatch([], parse_find_pois(Text2, macro)),
-
- Text3 = "f(M) -> M#?MODULE.f.",
- ?assertMatch([_], parse_find_pois(Text3, record_expr, '?MODULE')),
- ?assertMatch([_], parse_find_pois(Text3, record_field, {'?MODULE', f})),
- ?assertMatch([], parse_find_pois(Text3, macro)),
-
- Text4 = "f(M) -> M#?MODULE{f = 1}.",
- ?assertMatch([_], parse_find_pois(Text4, record_expr, '?MODULE')),
- ?assertMatch([_], parse_find_pois(Text4, record_field, {'?MODULE', f})),
- ?assertMatch([], parse_find_pois(Text4, macro)),
- ok.
+ Text1 = "-record(?MODULE, {f = 1}).",
+ ?assertMatch(
+ [#{data := #{field_list := [f]}}],
+ parse_find_pois(Text1, record, '?MODULE')
+ ),
+ ?assertMatch([_], parse_find_pois(Text1, record_def_field, {'?MODULE', f})),
+ ?assertMatch([], parse_find_pois(Text1, macro)),
+
+ Text2 = "-type t() :: #?MODULE{f :: integer()}.",
+ ?assertMatch([_], parse_find_pois(Text2, record_expr, '?MODULE')),
+ ?assertMatch([_], parse_find_pois(Text2, record_field, {'?MODULE', f})),
+ ?assertMatch([], parse_find_pois(Text2, macro)),
+
+ Text3 = "f(M) -> M#?MODULE.f.",
+ ?assertMatch([_], parse_find_pois(Text3, record_expr, '?MODULE')),
+ ?assertMatch([_], parse_find_pois(Text3, record_field, {'?MODULE', f})),
+ ?assertMatch([], parse_find_pois(Text3, macro)),
+
+ Text4 = "f(M) -> M#?MODULE{f = 1}.",
+ ?assertMatch([_], parse_find_pois(Text4, record_expr, '?MODULE')),
+ ?assertMatch([_], parse_find_pois(Text4, record_field, {'?MODULE', f})),
+ ?assertMatch([], parse_find_pois(Text4, macro)),
+ ok.
%% Verify macro POIs are created in record name positions
other_macro_as_record_name(_Config) ->
- Text1 = "-record(?M, {f = 1}).",
- ?assertMatch([], parse_find_pois(Text1, record)),
- ?assertMatch([], parse_find_pois(Text1, record_def_field)),
- ?assertMatch([_], parse_find_pois(Text1, macro)),
-
- Text2 = "-type t() :: #?M{f :: integer()}.",
- ?assertMatch([], parse_find_pois(Text2, record_expr)),
- ?assertMatch([], parse_find_pois(Text2, record_field)),
- ?assertMatch([_], parse_find_pois(Text2, macro, 'M')),
-
- Text3 = "f(M) -> M#?M.f.",
- ?assertMatch([], parse_find_pois(Text3, record_expr)),
- ?assertMatch([], parse_find_pois(Text3, record_field)),
- ?assertMatch([_], parse_find_pois(Text3, macro, 'M')),
-
- Text4 = "f(M) -> M#?M{f = 1}.",
- ?assertMatch([], parse_find_pois(Text4, record_expr)),
- ?assertMatch([], parse_find_pois(Text4, record_field)),
- ?assertMatch([_], parse_find_pois(Text4, macro, 'M')),
- ok.
+ Text1 = "-record(?M, {f = 1}).",
+ ?assertMatch([], parse_find_pois(Text1, record)),
+ ?assertMatch([], parse_find_pois(Text1, record_def_field)),
+ ?assertMatch([_], parse_find_pois(Text1, macro)),
+
+ Text2 = "-type t() :: #?M{f :: integer()}.",
+ ?assertMatch([], parse_find_pois(Text2, record_expr)),
+ ?assertMatch([], parse_find_pois(Text2, record_field)),
+ ?assertMatch([_], parse_find_pois(Text2, macro, 'M')),
+
+ Text3 = "f(M) -> M#?M.f.",
+ ?assertMatch([], parse_find_pois(Text3, record_expr)),
+ ?assertMatch([], parse_find_pois(Text3, record_field)),
+ ?assertMatch([_], parse_find_pois(Text3, macro, 'M')),
+
+ Text4 = "f(M) -> M#?M{f = 1}.",
+ ?assertMatch([], parse_find_pois(Text4, record_expr)),
+ ?assertMatch([], parse_find_pois(Text4, record_field)),
+ ?assertMatch([_], parse_find_pois(Text4, macro, 'M')),
+ ok.
%%==============================================================================
%% Helper functions
%%==============================================================================
-spec parse_find_pois(string(), poi_kind()) -> [poi()].
parse_find_pois(Text, Kind) ->
- {ok, POIs} = els_parser:parse(Text),
- SortedPOIs = els_poi:sort(POIs),
- [POI || #{kind := Kind1} = POI <- SortedPOIs, Kind1 =:= Kind].
+ {ok, POIs} = els_parser:parse(Text),
+ SortedPOIs = els_poi:sort(POIs),
+ [POI || #{kind := Kind1} = POI <- SortedPOIs, Kind1 =:= Kind].
-spec parse_find_pois(string(), poi_kind(), poi_id()) -> [poi()].
parse_find_pois(Text, Kind, Id) ->
- [POI || #{id := Id1} = POI <- parse_find_pois(Text, Kind), Id1 =:= Id].
+ [POI || #{id := Id1} = POI <- parse_find_pois(Text, Kind), Id1 =:= Id].
diff --git a/apps/els_lsp/test/els_progress_SUITE.erl b/apps/els_lsp/test/els_progress_SUITE.erl
index 7bf68cd07..d8f9004f6 100644
--- a/apps/els_lsp/test/els_progress_SUITE.erl
+++ b/apps/els_lsp/test/els_progress_SUITE.erl
@@ -6,20 +6,22 @@
%%==============================================================================
%% Common Test Callbacks
%%==============================================================================
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%%==============================================================================
%% Testcases
%%==============================================================================
--export([ sample_job/1
- , failing_job/1
- ]).
+-export([
+ sample_job/1,
+ failing_job/1
+]).
%%==============================================================================
%% Includes
@@ -39,34 +41,34 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(sample_job = TestCase, Config) ->
- Task = fun(_, _) -> ok end,
- setup_mocks(Task),
- [{task, Task} | els_test_utils:init_per_testcase(TestCase, Config)];
+ Task = fun(_, _) -> ok end,
+ setup_mocks(Task),
+ [{task, Task} | els_test_utils:init_per_testcase(TestCase, Config)];
init_per_testcase(failing_job = TestCase, Config) ->
- Task = fun(_, _) -> exit(fail) end,
- setup_mocks(Task),
- [{task, Task} | els_test_utils:init_per_testcase(TestCase, Config)].
+ Task = fun(_, _) -> exit(fail) end,
+ setup_mocks(Task),
+ [{task, Task} | els_test_utils:init_per_testcase(TestCase, Config)].
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config),
- teardown_mocks().
+ els_test_utils:end_per_testcase(TestCase, Config),
+ teardown_mocks().
%%==============================================================================
%% Testcases
@@ -74,58 +76,59 @@ end_per_testcase(TestCase, Config) ->
-spec sample_job(config()) -> ok.
sample_job(_Config) ->
- {ok, Pid} = new_background_job(),
- wait_for_completion(Pid),
- ?assertEqual(length(entries()), meck:num_calls(sample_job, task, '_')),
- ?assertEqual(1, meck:num_calls(sample_job, on_complete, '_')),
- ?assertEqual(0, meck:num_calls(sample_job, on_error, '_')),
- ok.
+ {ok, Pid} = new_background_job(),
+ wait_for_completion(Pid),
+ ?assertEqual(length(entries()), meck:num_calls(sample_job, task, '_')),
+ ?assertEqual(1, meck:num_calls(sample_job, on_complete, '_')),
+ ?assertEqual(0, meck:num_calls(sample_job, on_error, '_')),
+ ok.
-spec failing_job(config()) -> ok.
failing_job(_Config) ->
- {ok, Pid} = new_background_job(),
- wait_for_completion(Pid),
- ?assertEqual(1, meck:num_calls(sample_job, task, '_')),
- ?assertEqual(0, meck:num_calls(sample_job, on_complete, '_')),
- ?assertEqual(1, meck:num_calls(sample_job, on_error, '_')),
- ok.
+ {ok, Pid} = new_background_job(),
+ wait_for_completion(Pid),
+ ?assertEqual(1, meck:num_calls(sample_job, task, '_')),
+ ?assertEqual(0, meck:num_calls(sample_job, on_complete, '_')),
+ ?assertEqual(1, meck:num_calls(sample_job, on_error, '_')),
+ ok.
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec wait_for_completion(pid()) -> ok.
wait_for_completion(Pid) ->
- case is_process_alive(Pid) of
- false ->
- ok;
- true ->
- timer:sleep(10),
- wait_for_completion(Pid)
- end.
+ case is_process_alive(Pid) of
+ false ->
+ ok;
+ true ->
+ timer:sleep(10),
+ wait_for_completion(Pid)
+ end.
-spec setup_mocks(fun((_, _) -> ok)) -> ok.
setup_mocks(Task) ->
- meck:new(sample_job, [non_strict, no_link]),
- meck:expect(sample_job, task, Task),
- meck:expect(sample_job, on_complete, fun(_) -> ok end),
- meck:expect(sample_job, on_error, fun(_) -> ok end),
- ok.
+ meck:new(sample_job, [non_strict, no_link]),
+ meck:expect(sample_job, task, Task),
+ meck:expect(sample_job, on_complete, fun(_) -> ok end),
+ meck:expect(sample_job, on_error, fun(_) -> ok end),
+ ok.
-spec teardown_mocks() -> ok.
teardown_mocks() ->
- meck:unload(sample_job),
- ok.
+ meck:unload(sample_job),
+ ok.
-spec new_background_job() -> {ok, pid()}.
new_background_job() ->
- Config = #{ task => fun sample_job:task/2
- , entries => entries()
- , on_complete => fun sample_job:on_complete/1
- , on_error => fun sample_job:on_error/1
- , title => <<"Sample job">>
- },
- els_background_job:new(Config).
+ Config = #{
+ task => fun sample_job:task/2,
+ entries => entries(),
+ on_complete => fun sample_job:on_complete/1,
+ on_error => fun sample_job:on_error/1,
+ title => <<"Sample job">>
+ },
+ els_background_job:new(Config).
-spec entries() -> [any()].
entries() ->
- lists:seq(1, 27).
+ lists:seq(1, 27).
diff --git a/apps/els_lsp/test/els_proper_gen.erl b/apps/els_lsp/test/els_proper_gen.erl
index be1e09296..ccf3071fe 100644
--- a/apps/els_lsp/test/els_proper_gen.erl
+++ b/apps/els_lsp/test/els_proper_gen.erl
@@ -17,34 +17,36 @@
%% Generators
%%==============================================================================
uri() ->
- ?LET( B
- , document()
- , els_uri:uri(filename:join([system_tmp_dir(), B ++ ".erl"]))
- ).
+ ?LET(
+ B,
+ document(),
+ els_uri:uri(filename:join([system_tmp_dir(), B ++ ".erl"]))
+ ).
root_uri() ->
- els_uri:uri(system_tmp_dir()).
+ els_uri:uri(system_tmp_dir()).
init_options() ->
- #{<<"indexingEnabled">> => false}.
+ #{<<"indexingEnabled">> => false}.
document() ->
- elements(["a", "b", "c"]).
+ elements(["a", "b", "c"]).
tokens() ->
- ?LET( Tokens
- , vector(10, token())
- , begin
- Concatenated = [string:join(Tokens, ","), "."],
- els_utils:to_binary(Concatenated)
+ ?LET(
+ Tokens,
+ vector(10, token()),
+ begin
+ Concatenated = [string:join(Tokens, ","), "."],
+ els_utils:to_binary(Concatenated)
end
- ).
+ ).
token() ->
- elements(["foo", "Bar", "\"baz\""]).
+ elements(["foo", "Bar", "\"baz\""]).
%%==============================================================================
%% Internal Functions
%%==============================================================================
system_tmp_dir() ->
- els_utils:to_binary(els_utils:system_tmp_dir()).
+ els_utils:to_binary(els_utils:system_tmp_dir()).
diff --git a/apps/els_lsp/test/els_rebar3_release_SUITE.erl b/apps/els_lsp/test/els_rebar3_release_SUITE.erl
index 4872e2f8c..d5cbb9bce 100644
--- a/apps/els_lsp/test/els_rebar3_release_SUITE.erl
+++ b/apps/els_lsp/test/els_rebar3_release_SUITE.erl
@@ -4,16 +4,17 @@
-module(els_rebar3_release_SUITE).
%% CT Callbacks
--export([ all/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , suite/0
- ]).
+-export([
+ all/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ suite/0
+]).
%% Test cases
--export([ code_navigation/1 ]).
+-export([code_navigation/1]).
%%==============================================================================
%% Includes
@@ -37,58 +38,61 @@
%%==============================================================================
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- RootPath = root_path(),
- AppPath = src_path(RootPath, "rebar3_release_app.erl"),
- SupPath = src_path(RootPath, "rebar3_release_sup.erl"),
- application:load(els_lsp),
- [ {root_uri, els_uri:uri(RootPath)}
- , {app_uri, els_uri:uri(AppPath)}
- , {sup_uri, els_uri:uri(SupPath)}
- | Config
- ].
+ RootPath = root_path(),
+ AppPath = src_path(RootPath, "rebar3_release_app.erl"),
+ SupPath = src_path(RootPath, "rebar3_release_sup.erl"),
+ application:load(els_lsp),
+ [
+ {root_uri, els_uri:uri(RootPath)},
+ {app_uri, els_uri:uri(AppPath)},
+ {sup_uri, els_uri:uri(SupPath)}
+ | Config
+ ].
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(_TestCase, Config) ->
- meck:new(els_distribution_server, [no_link, passthrough]),
- meck:expect(els_distribution_server, connect, 0, ok),
- Started = els_test_utils:start(),
- RootUri = ?config(root_uri, Config),
- AppUri = ?config(app_uri, Config),
- els_client:initialize(RootUri),
- {ok, AppText} = file:read_file(els_uri:path(AppUri)),
- els_client:did_open(AppUri, erlang, 1, AppText),
- els_indexing:find_and_deeply_index_file("rebar3_release_app.erl"),
- els_indexing:find_and_deeply_index_file("rebar3_release_sup.erl"),
- [{started, Started}|Config].
+ meck:new(els_distribution_server, [no_link, passthrough]),
+ meck:expect(els_distribution_server, connect, 0, ok),
+ Started = els_test_utils:start(),
+ RootUri = ?config(root_uri, Config),
+ AppUri = ?config(app_uri, Config),
+ els_client:initialize(RootUri),
+ {ok, AppText} = file:read_file(els_uri:path(AppUri)),
+ els_client:did_open(AppUri, erlang, 1, AppText),
+ els_indexing:find_and_deeply_index_file("rebar3_release_app.erl"),
+ els_indexing:find_and_deeply_index_file("rebar3_release_sup.erl"),
+ [{started, Started} | Config].
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
%%==============================================================================
%% Testcases
%%==============================================================================
-spec code_navigation(config()) -> ok.
code_navigation(Config) ->
- AppUri = ?config(app_uri, Config),
- SupUri = ?config(sup_uri, Config),
- #{result := Result} = els_client:definition(AppUri, 13, 12),
- #{range := DefRange, uri := SupUri} = Result,
- ?assertEqual( els_protocol:range(#{from => {16, 1}, to => {16, 11}})
- , DefRange),
- ok.
+ AppUri = ?config(app_uri, Config),
+ SupUri = ?config(sup_uri, Config),
+ #{result := Result} = els_client:definition(AppUri, 13, 12),
+ #{range := DefRange, uri := SupUri} = Result,
+ ?assertEqual(
+ els_protocol:range(#{from => {16, 1}, to => {16, 11}}),
+ DefRange
+ ),
+ ok.
%%==============================================================================
%% Internal Functions
@@ -96,9 +100,9 @@ code_navigation(Config) ->
-spec root_path() -> binary().
root_path() ->
- RootPath = filename:join([code:priv_dir(els_lsp), ?TEST_APP]),
- els_utils:to_binary(RootPath).
+ RootPath = filename:join([code:priv_dir(els_lsp), ?TEST_APP]),
+ els_utils:to_binary(RootPath).
-spec src_path(binary(), [any()]) -> binary().
src_path(RootPath, FileName) ->
- filename:join([RootPath, apps, ?TEST_APP, src, FileName]).
+ filename:join([RootPath, apps, ?TEST_APP, src, FileName]).
diff --git a/apps/els_lsp/test/els_references_SUITE.erl b/apps/els_lsp/test/els_references_SUITE.erl
index 87e303a51..065561bdc 100644
--- a/apps/els_lsp/test/els_references_SUITE.erl
+++ b/apps/els_lsp/test/els_references_SUITE.erl
@@ -1,40 +1,42 @@
-module(els_references_SUITE).
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ application_local/1
- , application_remote/1
- , function_definition/1
- , function_multiple_clauses/1
- , fun_local/1
- , fun_remote/1
- , export_entry/1
- , macro/1
- , included_macro/1
- , undefined_macro/1
- , module/1
- , record/1
- , record_field/1
- , included_record/1
- , included_record_field/1
- , undefined_record/1
- , undefined_record_field/1
- , type_local/1
- , type_remote/1
- , type_included/1
- , refresh_after_watched_file_deleted/1
- , refresh_after_watched_file_changed/1
- , refresh_after_watched_file_added/1
- , ignore_open_watched_file_added/1
- ]).
+-export([
+ application_local/1,
+ application_remote/1,
+ function_definition/1,
+ function_multiple_clauses/1,
+ fun_local/1,
+ fun_remote/1,
+ export_entry/1,
+ macro/1,
+ included_macro/1,
+ undefined_macro/1,
+ module/1,
+ record/1,
+ record_field/1,
+ included_record/1,
+ included_record_field/1,
+ undefined_record/1,
+ undefined_record_field/1,
+ type_local/1,
+ type_remote/1,
+ type_included/1,
+ refresh_after_watched_file_deleted/1,
+ refresh_after_watched_file_changed/1,
+ refresh_after_watched_file_added/1,
+ ignore_open_watched_file_added/1
+]).
%%==============================================================================
%% Includes
@@ -53,558 +55,649 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
-init_per_testcase(TestCase, Config0)
- when TestCase =:= refresh_after_watched_file_changed;
- TestCase =:= refresh_after_watched_file_deleted ->
- Config = els_test_utils:init_per_testcase(TestCase, Config0),
- PathB = ?config(watched_file_b_path, Config),
- {ok, OldContent} = file:read_file(PathB),
- [{old_content, OldContent}|Config];
+init_per_testcase(TestCase, Config0) when
+ TestCase =:= refresh_after_watched_file_changed;
+ TestCase =:= refresh_after_watched_file_deleted
+->
+ Config = els_test_utils:init_per_testcase(TestCase, Config0),
+ PathB = ?config(watched_file_b_path, Config),
+ {ok, OldContent} = file:read_file(PathB),
+ [{old_content, OldContent} | Config];
init_per_testcase(TestCase, Config) ->
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
-end_per_testcase(TestCase, Config)
- when TestCase =:= refresh_after_watched_file_changed;
- TestCase =:= refresh_after_watched_file_deleted ->
- PathB = ?config(watched_file_b_path, Config),
- ok = file:write_file(PathB, ?config(old_content, Config)),
- els_test_utils:end_per_testcase(TestCase, Config);
-end_per_testcase(TestCase, Config)
- when TestCase =:= refresh_after_watched_file_added;
- TestCase =:= ignore_open_watched_file_added ->
- PathB = ?config(watched_file_b_path, Config),
- ok = file:delete(filename:join(filename:dirname(PathB),
- "watched_file_c.erl")),
- els_test_utils:end_per_testcase(TestCase, Config);
+end_per_testcase(TestCase, Config) when
+ TestCase =:= refresh_after_watched_file_changed;
+ TestCase =:= refresh_after_watched_file_deleted
+->
+ PathB = ?config(watched_file_b_path, Config),
+ ok = file:write_file(PathB, ?config(old_content, Config)),
+ els_test_utils:end_per_testcase(TestCase, Config);
+end_per_testcase(TestCase, Config) when
+ TestCase =:= refresh_after_watched_file_added;
+ TestCase =:= ignore_open_watched_file_added
+->
+ PathB = ?config(watched_file_b_path, Config),
+ ok = file:delete(
+ filename:join(
+ filename:dirname(PathB),
+ "watched_file_c.erl"
+ )
+ ),
+ els_test_utils:end_per_testcase(TestCase, Config);
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Testcases
%%==============================================================================
-spec application_local(config()) -> ok.
application_local(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:references(Uri, 22, 5),
- ExpectedLocations = [ #{ uri => Uri
- , range => #{from => {22, 3}, to => {22, 13}}
- }
- , #{ uri => Uri
- , range => #{from => {51, 7}, to => {51, 23}}
- }
- ],
- assert_locations(Locations, ExpectedLocations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:references(Uri, 22, 5),
+ ExpectedLocations = [
+ #{
+ uri => Uri,
+ range => #{from => {22, 3}, to => {22, 13}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {51, 7}, to => {51, 23}}
+ }
+ ],
+ assert_locations(Locations, ExpectedLocations),
+ ok.
-spec application_remote(config()) -> ok.
application_remote(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:references(Uri, 32, 13),
- ExpectedLocations = [ #{ uri => Uri
- , range => #{from => {10, 34}, to => {10, 38}}
- }
- , #{ uri => Uri
- , range => #{from => {32, 3}, to => {32, 27}}
- }
- , #{ uri => Uri
- , range => #{from => {52, 8}, to => {52, 38}}
- }
- ],
- assert_locations(Locations, ExpectedLocations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:references(Uri, 32, 13),
+ ExpectedLocations = [
+ #{
+ uri => Uri,
+ range => #{from => {10, 34}, to => {10, 38}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {32, 3}, to => {32, 27}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {52, 8}, to => {52, 38}}
+ }
+ ],
+ assert_locations(Locations, ExpectedLocations),
+ ok.
-spec function_definition(config()) -> ok.
function_definition(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:references(Uri, 25, 1),
- ExpectedLocations = [ #{ uri => Uri
- , range => #{from => {22, 3}, to => {22, 13}}
- }
- , #{ uri => Uri
- , range => #{from => {51, 7}, to => {51, 23}}
- }
- ],
- assert_locations(Locations, ExpectedLocations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:references(Uri, 25, 1),
+ ExpectedLocations = [
+ #{
+ uri => Uri,
+ range => #{from => {22, 3}, to => {22, 13}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {51, 7}, to => {51, 23}}
+ }
+ ],
+ assert_locations(Locations, ExpectedLocations),
+ ok.
-spec function_multiple_clauses(config()) -> ok.
function_multiple_clauses(Config) ->
- Uri = ?config(hover_docs_uri, Config),
- UriCaller = ?config(hover_docs_caller_uri, Config),
- #{result := Locations} = els_client:references(Uri, 7, 1),
- ExpectedLocations = [ #{ uri => UriCaller
- , range => #{from => {16, 3}, to => {16, 30}}
- }
- , #{ uri => UriCaller
- , range => #{from => {20, 4}, to => {20, 37}}
- }
- ],
- assert_locations(Locations, ExpectedLocations),
- ok.
+ Uri = ?config(hover_docs_uri, Config),
+ UriCaller = ?config(hover_docs_caller_uri, Config),
+ #{result := Locations} = els_client:references(Uri, 7, 1),
+ ExpectedLocations = [
+ #{
+ uri => UriCaller,
+ range => #{from => {16, 3}, to => {16, 30}}
+ },
+ #{
+ uri => UriCaller,
+ range => #{from => {20, 4}, to => {20, 37}}
+ }
+ ],
+ assert_locations(Locations, ExpectedLocations),
+ ok.
-spec fun_local(config()) -> ok.
fun_local(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:references(Uri, 51, 16),
- ExpectedLocations = [ #{ uri => Uri
- , range => #{from => {22, 3}, to => {22, 13}}
- }
- , #{ uri => Uri
- , range => #{from => {51, 7}, to => {51, 23}}
- }
- ],
- assert_locations(Locations, ExpectedLocations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:references(Uri, 51, 16),
+ ExpectedLocations = [
+ #{
+ uri => Uri,
+ range => #{from => {22, 3}, to => {22, 13}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {51, 7}, to => {51, 23}}
+ }
+ ],
+ assert_locations(Locations, ExpectedLocations),
+ ok.
-spec fun_remote(config()) -> ok.
fun_remote(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:references(Uri, 52, 14),
- ExpectedLocations = [ #{ uri => Uri
- , range => #{from => {10, 34}, to => {10, 38}}
- }
- , #{ uri => Uri
- , range => #{from => {32, 3}, to => {32, 27}}
- }
- , #{ uri => Uri
- , range => #{from => {52, 8}, to => {52, 38}}
- }
- ],
- assert_locations(Locations, ExpectedLocations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:references(Uri, 52, 14),
+ ExpectedLocations = [
+ #{
+ uri => Uri,
+ range => #{from => {10, 34}, to => {10, 38}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {32, 3}, to => {32, 27}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {52, 8}, to => {52, 38}}
+ }
+ ],
+ assert_locations(Locations, ExpectedLocations),
+ ok.
-spec export_entry(config()) -> ok.
export_entry(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- #{result := Locations} = els_client:references(Uri, 5, 25),
- ExpectedLocations = [ #{ uri => Uri
- , range => #{from => {22, 3}, to => {22, 13}}
- }
- , #{ uri => Uri
- , range => #{from => {51, 7}, to => {51, 23}}
- }
- ],
- assert_locations(Locations, ExpectedLocations),
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ #{result := Locations} = els_client:references(Uri, 5, 25),
+ ExpectedLocations = [
+ #{
+ uri => Uri,
+ range => #{from => {22, 3}, to => {22, 13}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {51, 7}, to => {51, 23}}
+ }
+ ],
+ assert_locations(Locations, ExpectedLocations),
+ ok.
-spec macro(config()) -> ok.
macro(Config) ->
- Uri = ?config(code_navigation_uri, Config),
-
- ExpectedLocations = [ #{ uri => Uri
- , range => #{from => {26, 3}, to => {26, 11}}
- }
- , #{ uri => Uri
- , range => #{from => {75, 23}, to => {75, 31}}
- }
- ],
+ Uri = ?config(code_navigation_uri, Config),
+
+ ExpectedLocations = [
+ #{
+ uri => Uri,
+ range => #{from => {26, 3}, to => {26, 11}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {75, 23}, to => {75, 31}}
+ }
+ ],
- ct:comment("References for MACRO_A from usage"),
- #{result := Locations1} = els_client:references(Uri, 26, 6),
- assert_locations(Locations1, ExpectedLocations),
+ ct:comment("References for MACRO_A from usage"),
+ #{result := Locations1} = els_client:references(Uri, 26, 6),
+ assert_locations(Locations1, ExpectedLocations),
- ct:comment("References for MACRO_A from define"),
- #{result := Locations2} = els_client:references(Uri, 18, 12),
- assert_locations(Locations2, ExpectedLocations),
+ ct:comment("References for MACRO_A from define"),
+ #{result := Locations2} = els_client:references(Uri, 18, 12),
+ assert_locations(Locations2, ExpectedLocations),
- ok.
+ ok.
-spec included_macro(config()) -> ok.
included_macro(Config) ->
- Uri = ?config(diagnostics_unused_includes_uri, Config),
- HeaderUri = ?config(definition_h_uri, Config),
-
- ExpectedLocations = [ #{ uri => Uri
- , range => #{from => {14, 23}, to => {14, 54}}
- }
- ],
+ Uri = ?config(diagnostics_unused_includes_uri, Config),
+ HeaderUri = ?config(definition_h_uri, Config),
+
+ ExpectedLocations = [
+ #{
+ uri => Uri,
+ range => #{from => {14, 23}, to => {14, 54}}
+ }
+ ],
- ct:comment("References for MACRO_FOR_TRANSITIVE_INCLUSION from usage"),
- #{result := Locations} = els_client:references(Uri, 14, 30),
- ct:comment("References for MACRO_FOR_TRANSITIVE_INCLUSION from define"),
- #{result := Locations} = els_client:references(HeaderUri, 1, 20),
- assert_locations(Locations, ExpectedLocations),
+ ct:comment("References for MACRO_FOR_TRANSITIVE_INCLUSION from usage"),
+ #{result := Locations} = els_client:references(Uri, 14, 30),
+ ct:comment("References for MACRO_FOR_TRANSITIVE_INCLUSION from define"),
+ #{result := Locations} = els_client:references(HeaderUri, 1, 20),
+ assert_locations(Locations, ExpectedLocations),
- ok.
+ ok.
-spec undefined_macro(config()) -> ok.
undefined_macro(Config) ->
- Uri = ?config(code_navigation_undefined_uri, Config),
-
- ExpectedLocations = [ #{ uri => Uri
- , range => #{from => {6, 28}, to => {6, 40}}
- }
- , #{ uri => Uri
- , range => #{from => {8, 29}, to => {8, 41}}
- }
- ],
+ Uri = ?config(code_navigation_undefined_uri, Config),
+
+ ExpectedLocations = [
+ #{
+ uri => Uri,
+ range => #{from => {6, 28}, to => {6, 40}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {8, 29}, to => {8, 41}}
+ }
+ ],
- ct:comment("References for UNDEF_MACRO from usage"),
- #{result := Locations1} = els_client:references(Uri, 6, 30),
- assert_locations(Locations1, ExpectedLocations),
- ok.
+ ct:comment("References for UNDEF_MACRO from usage"),
+ #{result := Locations1} = els_client:references(Uri, 6, 30),
+ assert_locations(Locations1, ExpectedLocations),
+ ok.
-spec module(config()) -> ok.
module(Config) ->
- Uri = ?config(code_navigation_extra_uri, Config),
- #{result := Locations} = els_client:references(Uri, 1, 12),
- LocUri = ?config(code_navigation_uri, Config),
- ExpectedLocations = [ #{ uri => LocUri
- , range => #{from => {10, 34}, to => {10, 38}}
- }
- , #{ uri => LocUri
- , range => #{from => {32, 3}, to => {32, 27}}
- }
- , #{ uri => LocUri
- , range => #{from => {52, 8}, to => {52, 38}}
- }
- ],
- assert_locations(Locations, ExpectedLocations),
- ok.
+ Uri = ?config(code_navigation_extra_uri, Config),
+ #{result := Locations} = els_client:references(Uri, 1, 12),
+ LocUri = ?config(code_navigation_uri, Config),
+ ExpectedLocations = [
+ #{
+ uri => LocUri,
+ range => #{from => {10, 34}, to => {10, 38}}
+ },
+ #{
+ uri => LocUri,
+ range => #{from => {32, 3}, to => {32, 27}}
+ },
+ #{
+ uri => LocUri,
+ range => #{from => {52, 8}, to => {52, 38}}
+ }
+ ],
+ assert_locations(Locations, ExpectedLocations),
+ ok.
-spec record(config()) -> ok.
record(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- ExpectedLocations = [ #{ uri => Uri
- , range => #{from => {23, 3}, to => {23, 12}}
- }
- , #{ uri => Uri
- , range => #{from => {33, 7}, to => {33, 16}}
- }
- , #{ uri => Uri
- , range => #{from => {34, 9}, to => {34, 18}}
- }
- , #{ uri => Uri
- , range => #{from => {34, 34}, to => {34, 43}}
- }
- , #{ uri => Uri
- , range => #{from => {99, 8}, to => {99, 17}}
- }
- ],
-
- ct:comment("Find references record_a from a usage"),
- #{result := Locations} = els_client:references(Uri, 23, 3),
- ct:comment("Find references record_a from an access"),
- #{result := Locations} = els_client:references(Uri, 34, 15),
- ct:comment("Find references record_a from beginning of definition"),
- #{result := Locations} = els_client:references(Uri, 16, 9),
- ct:comment("Find references record_a from end of definition"),
- #{result := Locations} = els_client:references(Uri, 16, 17),
-
- assert_locations(Locations, ExpectedLocations),
-
- ct:comment("Check limits of record_a"),
- #{result := null} = els_client:references(Uri, 16, 8),
- #{result := null} = els_client:references(Uri, 16, 18),
-
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ ExpectedLocations = [
+ #{
+ uri => Uri,
+ range => #{from => {23, 3}, to => {23, 12}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {33, 7}, to => {33, 16}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {34, 9}, to => {34, 18}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {34, 34}, to => {34, 43}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {99, 8}, to => {99, 17}}
+ }
+ ],
+
+ ct:comment("Find references record_a from a usage"),
+ #{result := Locations} = els_client:references(Uri, 23, 3),
+ ct:comment("Find references record_a from an access"),
+ #{result := Locations} = els_client:references(Uri, 34, 15),
+ ct:comment("Find references record_a from beginning of definition"),
+ #{result := Locations} = els_client:references(Uri, 16, 9),
+ ct:comment("Find references record_a from end of definition"),
+ #{result := Locations} = els_client:references(Uri, 16, 17),
+
+ assert_locations(Locations, ExpectedLocations),
+
+ ct:comment("Check limits of record_a"),
+ #{result := null} = els_client:references(Uri, 16, 8),
+ #{result := null} = els_client:references(Uri, 16, 18),
+
+ ok.
-spec record_field(config()) -> ok.
record_field(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- ExpectedLocations = [ #{ uri => Uri
- , range => #{from => {33, 18}, to => {33, 25}}
- }
- , #{ uri => Uri
- , range => #{from => {34, 19}, to => {34, 26}}
- }
- , #{ uri => Uri
- , range => #{from => {34, 44}, to => {34, 51}}
- }
- ],
-
- ct:comment("Find references field_a from a usage"),
- #{result := Locations} = els_client:references(Uri, 33, 18),
- ct:comment("Find references field_a from an access"),
- #{result := Locations} = els_client:references(Uri, 34, 19),
- ct:comment("Find references field_a from beginning of definition"),
- #{result := Locations} = els_client:references(Uri, 16, 20),
- ct:comment("Find references field_a from end of definition"),
- #{result := Locations} = els_client:references(Uri, 16, 27),
-
- assert_locations(Locations, ExpectedLocations),
-
- ct:comment("Check limits of field_a"),
- #{result := null} = els_client:references(Uri, 16, 19),
- #{result := null} = els_client:references(Uri, 16, 28),
-
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ ExpectedLocations = [
+ #{
+ uri => Uri,
+ range => #{from => {33, 18}, to => {33, 25}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {34, 19}, to => {34, 26}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {34, 44}, to => {34, 51}}
+ }
+ ],
+
+ ct:comment("Find references field_a from a usage"),
+ #{result := Locations} = els_client:references(Uri, 33, 18),
+ ct:comment("Find references field_a from an access"),
+ #{result := Locations} = els_client:references(Uri, 34, 19),
+ ct:comment("Find references field_a from beginning of definition"),
+ #{result := Locations} = els_client:references(Uri, 16, 20),
+ ct:comment("Find references field_a from end of definition"),
+ #{result := Locations} = els_client:references(Uri, 16, 27),
+
+ assert_locations(Locations, ExpectedLocations),
+
+ ct:comment("Check limits of field_a"),
+ #{result := null} = els_client:references(Uri, 16, 19),
+ #{result := null} = els_client:references(Uri, 16, 28),
+
+ ok.
-spec included_record(config()) -> ok.
included_record(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- HeaderUri = ?config(code_navigation_h_uri, Config),
-
- ExpectedRecordLocations =
- [ #{ uri => Uri
- , range => #{from => {52, 41}, to => {52, 59}}
- }
- , #{ uri => Uri
- , range => #{from => {53, 23}, to => {53, 41}}
- }
- , #{ uri => Uri
- , range => #{from => {75, 4}, to => {75, 22}}
- }
- ],
- ct:comment("Find references of included_record_a from a usage"),
- #{result := RecordLocations} = els_client:references(Uri, 53, 30),
- ct:comment("Find references of included_record_a from definition"),
- #{result := RecordLocations} = els_client:references(HeaderUri, 1, 10),
- assert_locations(RecordLocations, ExpectedRecordLocations),
-
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ HeaderUri = ?config(code_navigation_h_uri, Config),
+
+ ExpectedRecordLocations =
+ [
+ #{
+ uri => Uri,
+ range => #{from => {52, 41}, to => {52, 59}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {53, 23}, to => {53, 41}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {75, 4}, to => {75, 22}}
+ }
+ ],
+ ct:comment("Find references of included_record_a from a usage"),
+ #{result := RecordLocations} = els_client:references(Uri, 53, 30),
+ ct:comment("Find references of included_record_a from definition"),
+ #{result := RecordLocations} = els_client:references(HeaderUri, 1, 10),
+ assert_locations(RecordLocations, ExpectedRecordLocations),
+
+ ok.
-spec included_record_field(config()) -> ok.
included_record_field(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- HeaderUri = ?config(code_navigation_h_uri, Config),
-
- ExpectedFieldLocations =
- [ #{ uri => Uri
- , range => #{from => {53, 42}, to => {53, 58}}
- }
- ],
- ct:comment("Find references of included_field_a from a usage"),
- #{result := FieldLocations} = els_client:references(Uri, 53, 45),
- ct:comment("Find references of included_field_a from definition"),
- #{result := FieldLocations} = els_client:references(HeaderUri, 1, 30),
- assert_locations(FieldLocations, ExpectedFieldLocations),
-
- ok.
+ Uri = ?config(code_navigation_uri, Config),
+ HeaderUri = ?config(code_navigation_h_uri, Config),
+
+ ExpectedFieldLocations =
+ [
+ #{
+ uri => Uri,
+ range => #{from => {53, 42}, to => {53, 58}}
+ }
+ ],
+ ct:comment("Find references of included_field_a from a usage"),
+ #{result := FieldLocations} = els_client:references(Uri, 53, 45),
+ ct:comment("Find references of included_field_a from definition"),
+ #{result := FieldLocations} = els_client:references(HeaderUri, 1, 30),
+ assert_locations(FieldLocations, ExpectedFieldLocations),
+
+ ok.
-spec undefined_record(config()) -> ok.
undefined_record(Config) ->
- Uri = ?config(code_navigation_undefined_uri, Config),
-
- ExpectedLocations = [ #{ uri => Uri
- , range => #{from => {6, 3}, to => {6, 13}}
- }
- , #{ uri => Uri
- , range => #{from => {8, 4}, to => {8, 14}}
- }
- ],
+ Uri = ?config(code_navigation_undefined_uri, Config),
+
+ ExpectedLocations = [
+ #{
+ uri => Uri,
+ range => #{from => {6, 3}, to => {6, 13}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {8, 4}, to => {8, 14}}
+ }
+ ],
- ct:comment("References for undef_rec from usage"),
- #{result := Locations1} = els_client:references(Uri, 6, 10),
- assert_locations(Locations1, ExpectedLocations),
- ok.
+ ct:comment("References for undef_rec from usage"),
+ #{result := Locations1} = els_client:references(Uri, 6, 10),
+ assert_locations(Locations1, ExpectedLocations),
+ ok.
-spec undefined_record_field(config()) -> ok.
undefined_record_field(Config) ->
- Uri = ?config(code_navigation_undefined_uri, Config),
-
- ExpectedLocations = [ #{ uri => Uri
- , range => #{from => {6, 14}, to => {6, 25}}
- }
- , #{ uri => Uri
- , range => #{from => {8, 15}, to => {8, 26}}
- }
- ],
-
- ct:comment("References for undef_field from usage"),
- #{result := Locations1} = els_client:references(Uri, 6, 20),
- assert_locations(Locations1, ExpectedLocations),
- ok.
+ Uri = ?config(code_navigation_undefined_uri, Config),
+
+ ExpectedLocations = [
+ #{
+ uri => Uri,
+ range => #{from => {6, 14}, to => {6, 25}}
+ },
+ #{
+ uri => Uri,
+ range => #{from => {8, 15}, to => {8, 26}}
+ }
+ ],
+ ct:comment("References for undef_field from usage"),
+ #{result := Locations1} = els_client:references(Uri, 6, 20),
+ assert_locations(Locations1, ExpectedLocations),
+ ok.
-spec type_local(config()) -> ok.
type_local(Config) ->
- Uri = ?config(code_navigation_uri, Config),
- ExpectedLocations = [ #{ uri => Uri
- , range => #{from => {55, 23}, to => {55, 29}}
- }
- ],
+ Uri = ?config(code_navigation_uri, Config),
+ ExpectedLocations = [
+ #{
+ uri => Uri,
+ range => #{from => {55, 23}, to => {55, 29}}
+ }
+ ],
- ct:comment("Find references type_a from its definition"),
- #{result := Locations} = els_client:references(Uri, 37, 9),
- ct:comment("Find references type_a from a usage"),
- #{result := Locations} = els_client:references(Uri, 55, 23),
- ct:comment("Find references type_a from beginning of definition"),
- #{result := Locations} = els_client:references(Uri, 37, 7),
- ct:comment("Find references type_a from end of definition"),
- #{result := Locations} = els_client:references(Uri, 37, 12),
+ ct:comment("Find references type_a from its definition"),
+ #{result := Locations} = els_client:references(Uri, 37, 9),
+ ct:comment("Find references type_a from a usage"),
+ #{result := Locations} = els_client:references(Uri, 55, 23),
+ ct:comment("Find references type_a from beginning of definition"),
+ #{result := Locations} = els_client:references(Uri, 37, 7),
+ ct:comment("Find references type_a from end of definition"),
+ #{result := Locations} = els_client:references(Uri, 37, 12),
- assert_locations(Locations, ExpectedLocations),
+ assert_locations(Locations, ExpectedLocations),
- ok.
+ ok.
-spec type_remote(config()) -> ok.
type_remote(Config) ->
- UriTypes = ?config(code_navigation_types_uri, Config),
- Uri = ?config(code_navigation_extra_uri, Config),
- ExpectedLocations = [ %% local reference from another type definition
- #{ uri => UriTypes
- , range => #{from => {11, 24}, to => {11, 30}}
- }
- %% remote reference from a spec
- , #{ uri => Uri
- , range => #{from => {11, 38}, to => {11, 66}}
- }
- ],
-
- ct:comment("Find references for type_a from a remote usage"),
- #{result := Locations} = els_client:references(Uri, 11, 45),
- ct:comment("Find references type_a from definition"),
- #{result := Locations} = els_client:references(UriTypes, 3, 8),
-
- assert_locations(Locations, ExpectedLocations),
-
- ok.
+ UriTypes = ?config(code_navigation_types_uri, Config),
+ Uri = ?config(code_navigation_extra_uri, Config),
+ %% local reference from another type definition
+ ExpectedLocations = [
+ #{
+ uri => UriTypes,
+ range => #{from => {11, 24}, to => {11, 30}}
+ },
+ %% remote reference from a spec
+ #{
+ uri => Uri,
+ range => #{from => {11, 38}, to => {11, 66}}
+ }
+ ],
+
+ ct:comment("Find references for type_a from a remote usage"),
+ #{result := Locations} = els_client:references(Uri, 11, 45),
+ ct:comment("Find references type_a from definition"),
+ #{result := Locations} = els_client:references(UriTypes, 3, 8),
+
+ assert_locations(Locations, ExpectedLocations),
+
+ ok.
-spec type_included(config()) -> ok.
type_included(Config) ->
- UriTypes = ?config(code_navigation_types_uri, Config),
- UriHeader = ?config(definition_h_uri, Config),
-
- ExpectedLocations = [ #{ uri => UriTypes
- , range => #{from => {15, 24}, to => {15, 30}}
- }
- ],
- ct:comment("Find references for type_b from a remote usage"),
- #{result := Locations} = els_client:references(UriTypes, 15, 25),
- ct:comment("Find references for type_b from definition"),
- #{result := Locations} = els_client:references(UriHeader, 2, 7),
- assert_locations(Locations, ExpectedLocations),
- ok.
+ UriTypes = ?config(code_navigation_types_uri, Config),
+ UriHeader = ?config(definition_h_uri, Config),
+
+ ExpectedLocations = [
+ #{
+ uri => UriTypes,
+ range => #{from => {15, 24}, to => {15, 30}}
+ }
+ ],
+ ct:comment("Find references for type_b from a remote usage"),
+ #{result := Locations} = els_client:references(UriTypes, 15, 25),
+ ct:comment("Find references for type_b from definition"),
+ #{result := Locations} = els_client:references(UriHeader, 2, 7),
+ assert_locations(Locations, ExpectedLocations),
+ ok.
-spec refresh_after_watched_file_deleted(config()) -> ok.
refresh_after_watched_file_deleted(Config) ->
- %% Before
- UriA = ?config(watched_file_a_uri, Config),
- UriB = ?config(watched_file_b_uri, Config),
- PathB = ?config(watched_file_b_path, Config),
- ExpectedLocationsBefore = [ #{ uri => UriB
- , range => #{from => {6, 3}, to => {6, 22}}
- }
- ],
- #{result := LocationsBefore} = els_client:references(UriA, 5, 2),
- assert_locations(LocationsBefore, ExpectedLocationsBefore),
- %% Delete (Simulate a checkout, rebase or similar)
- ok = file:delete(PathB),
- els_client:did_change_watched_files([{UriB, ?FILE_CHANGE_TYPE_DELETED}]),
- %% After
- #{result := null} = els_client:references(UriA, 5, 2),
- ok.
+ %% Before
+ UriA = ?config(watched_file_a_uri, Config),
+ UriB = ?config(watched_file_b_uri, Config),
+ PathB = ?config(watched_file_b_path, Config),
+ ExpectedLocationsBefore = [
+ #{
+ uri => UriB,
+ range => #{from => {6, 3}, to => {6, 22}}
+ }
+ ],
+ #{result := LocationsBefore} = els_client:references(UriA, 5, 2),
+ assert_locations(LocationsBefore, ExpectedLocationsBefore),
+ %% Delete (Simulate a checkout, rebase or similar)
+ ok = file:delete(PathB),
+ els_client:did_change_watched_files([{UriB, ?FILE_CHANGE_TYPE_DELETED}]),
+ %% After
+ #{result := null} = els_client:references(UriA, 5, 2),
+ ok.
-spec refresh_after_watched_file_changed(config()) -> ok.
refresh_after_watched_file_changed(Config) ->
- %% Before
- UriA = ?config(watched_file_a_uri, Config),
- UriB = ?config(watched_file_b_uri, Config),
- PathB = ?config(watched_file_b_path, Config),
- ExpectedLocationsBefore = [ #{ uri => UriB
- , range => #{from => {6, 3}, to => {6, 22}}
- }
- ],
- #{result := LocationsBefore} = els_client:references(UriA, 5, 2),
- assert_locations(LocationsBefore, ExpectedLocationsBefore),
- %% Edit (Simulate a checkout, rebase or similar)
- NewContent = re:replace(?config(old_content, Config),
- "watched_file_a:main()",
- "watched_file_a:main(), watched_file_a:main()"),
- ok = file:write_file(PathB, NewContent),
- els_client:did_change_watched_files([{UriB, ?FILE_CHANGE_TYPE_CHANGED}]),
- %% After
- ExpectedLocationsAfter = [ #{ uri => UriB
- , range => #{from => {6, 3}, to => {6, 22}}
- }
- , #{ uri => UriB
- , range => #{from => {6, 26}, to => {6, 45}}
- }
- ],
- #{result := LocationsAfter} = els_client:references(UriA, 5, 2),
- assert_locations(LocationsAfter, ExpectedLocationsAfter),
- ok.
+ %% Before
+ UriA = ?config(watched_file_a_uri, Config),
+ UriB = ?config(watched_file_b_uri, Config),
+ PathB = ?config(watched_file_b_path, Config),
+ ExpectedLocationsBefore = [
+ #{
+ uri => UriB,
+ range => #{from => {6, 3}, to => {6, 22}}
+ }
+ ],
+ #{result := LocationsBefore} = els_client:references(UriA, 5, 2),
+ assert_locations(LocationsBefore, ExpectedLocationsBefore),
+ %% Edit (Simulate a checkout, rebase or similar)
+ NewContent = re:replace(
+ ?config(old_content, Config),
+ "watched_file_a:main()",
+ "watched_file_a:main(), watched_file_a:main()"
+ ),
+ ok = file:write_file(PathB, NewContent),
+ els_client:did_change_watched_files([{UriB, ?FILE_CHANGE_TYPE_CHANGED}]),
+ %% After
+ ExpectedLocationsAfter = [
+ #{
+ uri => UriB,
+ range => #{from => {6, 3}, to => {6, 22}}
+ },
+ #{
+ uri => UriB,
+ range => #{from => {6, 26}, to => {6, 45}}
+ }
+ ],
+ #{result := LocationsAfter} = els_client:references(UriA, 5, 2),
+ assert_locations(LocationsAfter, ExpectedLocationsAfter),
+ ok.
-spec refresh_after_watched_file_added(config()) -> ok.
refresh_after_watched_file_added(Config) ->
- %% Before
- UriA = ?config(watched_file_a_uri, Config),
- UriB = ?config(watched_file_b_uri, Config),
- PathB = ?config(watched_file_b_path, Config),
- ExpectedLocationsBefore = [ #{ uri => UriB
- , range => #{from => {6, 3}, to => {6, 22}}
- }
- ],
- #{result := LocationsBefore} = els_client:references(UriA, 5, 2),
- assert_locations(LocationsBefore, ExpectedLocationsBefore),
- %% Add (Simulate a checkout, rebase or similar)
- DataDir = ?config(data_dir, Config),
- PathC = filename:join([DataDir, "watched_file_c.erl"]),
- NewPathC = filename:join(filename:dirname(PathB), "watched_file_c.erl"),
- NewUriC = els_uri:uri(NewPathC),
- {ok, _} = file:copy(PathC, NewPathC),
- els_client:did_change_watched_files([{NewUriC, ?FILE_CHANGE_TYPE_CREATED}]),
- %% After
- ExpectedLocationsAfter = [ #{ uri => NewUriC
- , range => #{from => {6, 3}, to => {6, 22}}
- }
- , #{ uri => UriB
- , range => #{from => {6, 3}, to => {6, 22}}
- }
- ],
- #{result := LocationsAfter} = els_client:references(UriA, 5, 2),
- assert_locations(LocationsAfter, ExpectedLocationsAfter),
- ok.
+ %% Before
+ UriA = ?config(watched_file_a_uri, Config),
+ UriB = ?config(watched_file_b_uri, Config),
+ PathB = ?config(watched_file_b_path, Config),
+ ExpectedLocationsBefore = [
+ #{
+ uri => UriB,
+ range => #{from => {6, 3}, to => {6, 22}}
+ }
+ ],
+ #{result := LocationsBefore} = els_client:references(UriA, 5, 2),
+ assert_locations(LocationsBefore, ExpectedLocationsBefore),
+ %% Add (Simulate a checkout, rebase or similar)
+ DataDir = ?config(data_dir, Config),
+ PathC = filename:join([DataDir, "watched_file_c.erl"]),
+ NewPathC = filename:join(filename:dirname(PathB), "watched_file_c.erl"),
+ NewUriC = els_uri:uri(NewPathC),
+ {ok, _} = file:copy(PathC, NewPathC),
+ els_client:did_change_watched_files([{NewUriC, ?FILE_CHANGE_TYPE_CREATED}]),
+ %% After
+ ExpectedLocationsAfter = [
+ #{
+ uri => NewUriC,
+ range => #{from => {6, 3}, to => {6, 22}}
+ },
+ #{
+ uri => UriB,
+ range => #{from => {6, 3}, to => {6, 22}}
+ }
+ ],
+ #{result := LocationsAfter} = els_client:references(UriA, 5, 2),
+ assert_locations(LocationsAfter, ExpectedLocationsAfter),
+ ok.
-spec ignore_open_watched_file_added(config()) -> ok.
ignore_open_watched_file_added(Config) ->
- %% Before
- UriA = ?config(watched_file_a_uri, Config),
- UriB = ?config(watched_file_b_uri, Config),
- PathB = ?config(watched_file_b_path, Config),
- ExpectedLocationsBefore = [ #{ uri => UriB
- , range => #{from => {6, 3}, to => {6, 22}}
- }
- ],
- #{result := LocationsBefore} = els_client:references(UriA, 5, 2),
- assert_locations(LocationsBefore, ExpectedLocationsBefore),
- %% Add (Simulate a checkout, rebase or similar)
- DataDir = ?config(data_dir, Config),
- PathC = filename:join([DataDir, "watched_file_c.erl"]),
- NewPathC = filename:join(filename:dirname(PathB), "watched_file_c.erl"),
- NewUriC = els_uri:uri(NewPathC),
- {ok, _} = file:copy(PathC, NewPathC),
- %% Open file, did_change_watched_files requests should be ignored
- els_client:did_open(NewUriC, erlang, 1, <<"dummy">>),
- els_client:did_change_watched_files([{NewUriC, ?FILE_CHANGE_TYPE_CREATED}]),
- %% After
- ExpectedLocationsOpen = [ #{ uri => UriB
- , range => #{from => {6, 3}, to => {6, 22}}
- }
- ],
- #{result := LocationsOpen} = els_client:references(UriA, 5, 2),
- assert_locations(LocationsOpen, ExpectedLocationsOpen),
- %% Close file, did_change_watched_files requests should be resumed
- els_client:did_close(NewUriC),
- els_client:did_change_watched_files([{NewUriC, ?FILE_CHANGE_TYPE_CREATED}]),
- %% After
- ExpectedLocationsClose = [ #{ uri => NewUriC
- , range => #{from => {6, 3}, to => {6, 22}}
- }
- , #{ uri => UriB
- , range => #{from => {6, 3}, to => {6, 22}}
- }
- ],
- #{result := LocationsClose} = els_client:references(UriA, 5, 2),
- assert_locations(LocationsClose, ExpectedLocationsClose),
- ok.
+ %% Before
+ UriA = ?config(watched_file_a_uri, Config),
+ UriB = ?config(watched_file_b_uri, Config),
+ PathB = ?config(watched_file_b_path, Config),
+ ExpectedLocationsBefore = [
+ #{
+ uri => UriB,
+ range => #{from => {6, 3}, to => {6, 22}}
+ }
+ ],
+ #{result := LocationsBefore} = els_client:references(UriA, 5, 2),
+ assert_locations(LocationsBefore, ExpectedLocationsBefore),
+ %% Add (Simulate a checkout, rebase or similar)
+ DataDir = ?config(data_dir, Config),
+ PathC = filename:join([DataDir, "watched_file_c.erl"]),
+ NewPathC = filename:join(filename:dirname(PathB), "watched_file_c.erl"),
+ NewUriC = els_uri:uri(NewPathC),
+ {ok, _} = file:copy(PathC, NewPathC),
+ %% Open file, did_change_watched_files requests should be ignored
+ els_client:did_open(NewUriC, erlang, 1, <<"dummy">>),
+ els_client:did_change_watched_files([{NewUriC, ?FILE_CHANGE_TYPE_CREATED}]),
+ %% After
+ ExpectedLocationsOpen = [
+ #{
+ uri => UriB,
+ range => #{from => {6, 3}, to => {6, 22}}
+ }
+ ],
+ #{result := LocationsOpen} = els_client:references(UriA, 5, 2),
+ assert_locations(LocationsOpen, ExpectedLocationsOpen),
+ %% Close file, did_change_watched_files requests should be resumed
+ els_client:did_close(NewUriC),
+ els_client:did_change_watched_files([{NewUriC, ?FILE_CHANGE_TYPE_CREATED}]),
+ %% After
+ ExpectedLocationsClose = [
+ #{
+ uri => NewUriC,
+ range => #{from => {6, 3}, to => {6, 22}}
+ },
+ #{
+ uri => UriB,
+ range => #{from => {6, 3}, to => {6, 22}}
+ }
+ ],
+ #{result := LocationsClose} = els_client:references(UriA, 5, 2),
+ assert_locations(LocationsClose, ExpectedLocationsClose),
+ ok.
%%==============================================================================
%% Internal functions
@@ -612,29 +705,30 @@ ignore_open_watched_file_added(Config) ->
-spec assert_locations([map()], [map()]) -> ok.
assert_locations(Locations, ExpectedLocations) ->
- ?assertEqual(length(ExpectedLocations),
- length(Locations),
- { {expected, ExpectedLocations}
- , {actual, Locations}
- }
- ),
- Pairs = lists:zip(sort_locations(Locations), ExpectedLocations),
- [ begin
- #{range := Range} = Location,
- #{uri := ExpectedUri, range := ExpectedRange} = Expected,
- ?assertMatch(#{uri := ExpectedUri}, Location),
- ?assertEqual( els_protocol:range(ExpectedRange)
- , Range
- )
- end
- || {Location, Expected} <- Pairs
- ],
- ok.
+ ?assertEqual(
+ length(ExpectedLocations),
+ length(Locations),
+ {{expected, ExpectedLocations}, {actual, Locations}}
+ ),
+ Pairs = lists:zip(sort_locations(Locations), ExpectedLocations),
+ [
+ begin
+ #{range := Range} = Location,
+ #{uri := ExpectedUri, range := ExpectedRange} = Expected,
+ ?assertMatch(#{uri := ExpectedUri}, Location),
+ ?assertEqual(
+ els_protocol:range(ExpectedRange),
+ Range
+ )
+ end
+ || {Location, Expected} <- Pairs
+ ],
+ ok.
sort_locations(Locations) ->
- lists:sort(fun compare_locations/2, Locations).
+ lists:sort(fun compare_locations/2, Locations).
compare_locations(#{range := R1}, #{range := R2}) ->
- #{start := #{line := L1, character := C1}} = R1,
- #{start := #{line := L2, character := C2}} = R2,
- {L1, C1} < {L2, C2}.
+ #{start := #{line := L1, character := C1}} = R1,
+ #{start := #{line := L2, character := C2}} = R2,
+ {L1, C1} < {L2, C2}.
diff --git a/apps/els_lsp/test/els_rename_SUITE.erl b/apps/els_lsp/test/els_rename_SUITE.erl
index 0d2e98f5c..8252fc73c 100644
--- a/apps/els_lsp/test/els_rename_SUITE.erl
+++ b/apps/els_lsp/test/els_rename_SUITE.erl
@@ -3,28 +3,30 @@
-include("els_lsp.hrl").
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ rename_behaviour_callback/1
- , rename_macro/1
- , rename_module/1
- , rename_variable/1
- , rename_function/1
- , rename_function_quoted_atom/1
- , rename_type/1
- , rename_opaque/1
- , rename_parametrized_macro/1
- , rename_macro_from_usage/1
- , rename_record/1
- , rename_record_field/1
- ]).
+-export([
+ rename_behaviour_callback/1,
+ rename_macro/1,
+ rename_module/1,
+ rename_variable/1,
+ rename_function/1,
+ rename_function_quoted_atom/1,
+ rename_type/1,
+ rename_opaque/1,
+ rename_parametrized_macro/1,
+ rename_macro_from_usage/1,
+ rename_record/1,
+ rename_record_field/1
+]).
%%==============================================================================
%% Includes
@@ -42,488 +44,730 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(TestCase, Config) ->
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Testcases
%%==============================================================================
-spec rename_behaviour_callback(config()) -> ok.
rename_behaviour_callback(Config) ->
- Uri = ?config(rename_uri, Config),
- Line = 2,
- Char = 9,
- NewName = <<"new_awesome_name">>,
- #{result := Result} = els_client:document_rename(Uri, Line, Char, NewName),
- Expected = #{changes =>
- #{ binary_to_atom(Uri, utf8) =>
- [ #{ newText => NewName
- , range =>
- #{ 'end' => #{character => 19, line => 2}
- , start => #{character => 10, line => 2}}}
- ]
- , binary_to_atom(?config(rename_usage1_uri, Config), utf8) =>
- [ #{ newText => NewName
- , range =>
- #{ 'end' => #{character => 18, line => 6}
- , start => #{character => 9, line => 6}}}
- , #{ newText => NewName
- , range =>
- #{ 'end' => #{character => 9, line => 9}
- , start => #{character => 0, line => 9}}}
- , #{ newText => NewName
- , range =>
- #{ 'end' => #{character => 9, line => 11}
- , start => #{character => 0, line => 11}}}
- , #{ newText => NewName
- , range =>
- #{ 'end' => #{character => 15, line => 8}
- , start => #{character => 6, line => 8}}}
- ]
- , binary_to_atom(?config(rename_usage2_uri, Config), utf8) =>
- [ #{ newText => NewName
- , range =>
- #{ 'end' => #{character => 18, line => 6}
- , start => #{character => 9, line => 6}}}
- , #{ newText => NewName
- , range =>
- #{ 'end' => #{character => 9, line => 8}
- , start => #{character => 0, line => 8}}}
- ]
- }
- },
- assert_changes(Expected, Result).
+ Uri = ?config(rename_uri, Config),
+ Line = 2,
+ Char = 9,
+ NewName = <<"new_awesome_name">>,
+ #{result := Result} = els_client:document_rename(Uri, Line, Char, NewName),
+ Expected = #{
+ changes =>
+ #{
+ binary_to_atom(Uri, utf8) =>
+ [
+ #{
+ newText => NewName,
+ range =>
+ #{
+ 'end' => #{character => 19, line => 2},
+ start => #{character => 10, line => 2}
+ }
+ }
+ ],
+ binary_to_atom(?config(rename_usage1_uri, Config), utf8) =>
+ [
+ #{
+ newText => NewName,
+ range =>
+ #{
+ 'end' => #{character => 18, line => 6},
+ start => #{character => 9, line => 6}
+ }
+ },
+ #{
+ newText => NewName,
+ range =>
+ #{
+ 'end' => #{character => 9, line => 9},
+ start => #{character => 0, line => 9}
+ }
+ },
+ #{
+ newText => NewName,
+ range =>
+ #{
+ 'end' => #{character => 9, line => 11},
+ start => #{character => 0, line => 11}
+ }
+ },
+ #{
+ newText => NewName,
+ range =>
+ #{
+ 'end' => #{character => 15, line => 8},
+ start => #{character => 6, line => 8}
+ }
+ }
+ ],
+ binary_to_atom(?config(rename_usage2_uri, Config), utf8) =>
+ [
+ #{
+ newText => NewName,
+ range =>
+ #{
+ 'end' => #{character => 18, line => 6},
+ start => #{character => 9, line => 6}
+ }
+ },
+ #{
+ newText => NewName,
+ range =>
+ #{
+ 'end' => #{character => 9, line => 8},
+ start => #{character => 0, line => 8}
+ }
+ }
+ ]
+ }
+ },
+ assert_changes(Expected, Result).
-spec rename_variable(config()) -> ok.
rename_variable(Config) ->
- Uri = ?config(rename_variable_uri, Config),
- UriAtom = binary_to_atom(Uri, utf8),
- NewName = <<"NewAwesomeName">>,
- %%
- #{result := Result1} = els_client:document_rename(Uri, 3, 3, NewName),
- Expected1 = #{changes => #{UriAtom => [ change(NewName, {3, 2}, {3, 5})
- , change(NewName, {2, 4}, {2, 7})
- ]}},
- #{result := Result2} = els_client:document_rename(Uri, 2, 5, NewName),
- Expected2 = #{changes => #{UriAtom => [ change(NewName, {3, 2}, {3, 5})
- , change(NewName, {2, 4}, {2, 7})
- ]}},
- #{result := Result3} = els_client:document_rename(Uri, 6, 3, NewName),
- Expected3 = #{changes => #{UriAtom => [ change(NewName, {6, 18}, {6, 21})
- , change(NewName, {6, 2}, {6, 5})
- , change(NewName, {5, 9}, {5, 12})
- , change(NewName, {4, 4}, {4, 7})
- ]}},
- #{result := Result4} = els_client:document_rename(Uri, 11, 3, NewName),
- Expected4 = #{changes => #{UriAtom => [ change(NewName, {11, 2}, {11, 5})
- , change(NewName, {10, 4}, {10, 7})
- ]}},
- %% Spec
- #{result := Result5} = els_client:document_rename(Uri, 13, 10, NewName),
- Expected5 = #{changes => #{UriAtom => [ change(NewName, {14, 15}, {14, 18})
- , change(NewName, {13, 18}, {13, 21})
- , change(NewName, {13, 10}, {13, 13})
- ]}},
- %% Record
- #{result := Result6} = els_client:document_rename(Uri, 18, 19, NewName),
- Expected6 = #{changes => #{UriAtom => [ change(NewName, {19, 20}, {19, 23})
- , change(NewName, {18, 19}, {18, 22})
- ]}},
- %% Macro
- #{result := Result7} = els_client:document_rename(Uri, 21, 20, NewName),
- Expected7 = #{changes => #{UriAtom => [ change(NewName, {21, 26}, {21, 29})
- , change(NewName, {21, 20}, {21, 23})
- , change(NewName, {21, 14}, {21, 17})
- ]}},
- %% Type
- #{result := Result8} = els_client:document_rename(Uri, 23, 11, NewName),
- Expected8 = #{changes => #{UriAtom => [ change(NewName, {23, 11}, {23, 14})
- , change(NewName, {23, 19}, {23, 22})
- ]}},
- %% Opaque
- #{result := Result9} = els_client:document_rename(Uri, 24, 15, NewName),
- Expected9 = #{changes => #{UriAtom => [ change(NewName, {24, 15}, {24, 18})
- , change(NewName, {24, 23}, {24, 26})
- ]}},
- %% Callback
- #{result := Result10} = els_client:document_rename(Uri, 1, 15, NewName),
- Expected10 = #{changes => #{UriAtom => [ change(NewName, {1, 23}, {1, 26})
- , change(NewName, {1, 15}, {1, 18})
- ]}},
- %% If
- #{result := Result11} = els_client:document_rename(Uri, 29, 4, NewName),
- Expected11 = #{changes => #{UriAtom => [ change(NewName, {29, 11}, {29, 14})
- , change(NewName, {29, 4}, {29, 7})
- ]}},
- assert_changes(Expected1, Result1),
- assert_changes(Expected2, Result2),
- assert_changes(Expected3, Result3),
- assert_changes(Expected4, Result4),
- assert_changes(Expected5, Result5),
- assert_changes(Expected6, Result6),
- assert_changes(Expected7, Result7),
- assert_changes(Expected8, Result8),
- assert_changes(Expected9, Result9),
- assert_changes(Expected10, Result10),
- assert_changes(Expected11, Result11).
+ Uri = ?config(rename_variable_uri, Config),
+ UriAtom = binary_to_atom(Uri, utf8),
+ NewName = <<"NewAwesomeName">>,
+ %%
+ #{result := Result1} = els_client:document_rename(Uri, 3, 3, NewName),
+ Expected1 = #{
+ changes => #{
+ UriAtom => [
+ change(NewName, {3, 2}, {3, 5}),
+ change(NewName, {2, 4}, {2, 7})
+ ]
+ }
+ },
+ #{result := Result2} = els_client:document_rename(Uri, 2, 5, NewName),
+ Expected2 = #{
+ changes => #{
+ UriAtom => [
+ change(NewName, {3, 2}, {3, 5}),
+ change(NewName, {2, 4}, {2, 7})
+ ]
+ }
+ },
+ #{result := Result3} = els_client:document_rename(Uri, 6, 3, NewName),
+ Expected3 = #{
+ changes => #{
+ UriAtom => [
+ change(NewName, {6, 18}, {6, 21}),
+ change(NewName, {6, 2}, {6, 5}),
+ change(NewName, {5, 9}, {5, 12}),
+ change(NewName, {4, 4}, {4, 7})
+ ]
+ }
+ },
+ #{result := Result4} = els_client:document_rename(Uri, 11, 3, NewName),
+ Expected4 = #{
+ changes => #{
+ UriAtom => [
+ change(NewName, {11, 2}, {11, 5}),
+ change(NewName, {10, 4}, {10, 7})
+ ]
+ }
+ },
+ %% Spec
+ #{result := Result5} = els_client:document_rename(Uri, 13, 10, NewName),
+ Expected5 = #{
+ changes => #{
+ UriAtom => [
+ change(NewName, {14, 15}, {14, 18}),
+ change(NewName, {13, 18}, {13, 21}),
+ change(NewName, {13, 10}, {13, 13})
+ ]
+ }
+ },
+ %% Record
+ #{result := Result6} = els_client:document_rename(Uri, 18, 19, NewName),
+ Expected6 = #{
+ changes => #{
+ UriAtom => [
+ change(NewName, {19, 20}, {19, 23}),
+ change(NewName, {18, 19}, {18, 22})
+ ]
+ }
+ },
+ %% Macro
+ #{result := Result7} = els_client:document_rename(Uri, 21, 20, NewName),
+ Expected7 = #{
+ changes => #{
+ UriAtom => [
+ change(NewName, {21, 26}, {21, 29}),
+ change(NewName, {21, 20}, {21, 23}),
+ change(NewName, {21, 14}, {21, 17})
+ ]
+ }
+ },
+ %% Type
+ #{result := Result8} = els_client:document_rename(Uri, 23, 11, NewName),
+ Expected8 = #{
+ changes => #{
+ UriAtom => [
+ change(NewName, {23, 11}, {23, 14}),
+ change(NewName, {23, 19}, {23, 22})
+ ]
+ }
+ },
+ %% Opaque
+ #{result := Result9} = els_client:document_rename(Uri, 24, 15, NewName),
+ Expected9 = #{
+ changes => #{
+ UriAtom => [
+ change(NewName, {24, 15}, {24, 18}),
+ change(NewName, {24, 23}, {24, 26})
+ ]
+ }
+ },
+ %% Callback
+ #{result := Result10} = els_client:document_rename(Uri, 1, 15, NewName),
+ Expected10 = #{
+ changes => #{
+ UriAtom => [
+ change(NewName, {1, 23}, {1, 26}),
+ change(NewName, {1, 15}, {1, 18})
+ ]
+ }
+ },
+ %% If
+ #{result := Result11} = els_client:document_rename(Uri, 29, 4, NewName),
+ Expected11 = #{
+ changes => #{
+ UriAtom => [
+ change(NewName, {29, 11}, {29, 14}),
+ change(NewName, {29, 4}, {29, 7})
+ ]
+ }
+ },
+ assert_changes(Expected1, Result1),
+ assert_changes(Expected2, Result2),
+ assert_changes(Expected3, Result3),
+ assert_changes(Expected4, Result4),
+ assert_changes(Expected5, Result5),
+ assert_changes(Expected6, Result6),
+ assert_changes(Expected7, Result7),
+ assert_changes(Expected8, Result8),
+ assert_changes(Expected9, Result9),
+ assert_changes(Expected10, Result10),
+ assert_changes(Expected11, Result11).
-spec rename_macro(config()) -> ok.
rename_macro(Config) ->
- Uri = ?config(rename_h_uri, Config),
- Line = 0,
- Char = 13,
- NewName = <<"NEW_AWESOME_NAME">>,
- NewNameUsage = <<"?NEW_AWESOME_NAME">>,
- #{result := Result} = els_client:document_rename(Uri, Line, Char, NewName),
- Expected = #{changes =>
- #{ binary_to_atom(Uri, utf8) =>
- [ #{ newText => NewName
- , range =>
- #{ 'end' => #{character => 17, line => 0}
- , start => #{character => 8, line => 0}}}
- ]
- , binary_to_atom(?config(rename_usage1_uri, Config), utf8) =>
- [ #{ newText => NewNameUsage
- , range =>
- #{ 'end' => #{character => 13, line => 15}
- , start => #{character => 3, line => 15}}}
- , #{ newText => NewNameUsage
- , range =>
- #{ 'end' => #{character => 25, line => 15}
- , start => #{character => 15, line => 15}}}
- ]
- , binary_to_atom(?config(rename_usage2_uri, Config), utf8) =>
- [ #{ newText => NewNameUsage
- , range =>
- #{ 'end' => #{character => 26, line => 11}
- , start => #{character => 16, line => 11}}}
- ]
- }
- },
- assert_changes(Expected, Result).
+ Uri = ?config(rename_h_uri, Config),
+ Line = 0,
+ Char = 13,
+ NewName = <<"NEW_AWESOME_NAME">>,
+ NewNameUsage = <<"?NEW_AWESOME_NAME">>,
+ #{result := Result} = els_client:document_rename(Uri, Line, Char, NewName),
+ Expected = #{
+ changes =>
+ #{
+ binary_to_atom(Uri, utf8) =>
+ [
+ #{
+ newText => NewName,
+ range =>
+ #{
+ 'end' => #{character => 17, line => 0},
+ start => #{character => 8, line => 0}
+ }
+ }
+ ],
+ binary_to_atom(?config(rename_usage1_uri, Config), utf8) =>
+ [
+ #{
+ newText => NewNameUsage,
+ range =>
+ #{
+ 'end' => #{character => 13, line => 15},
+ start => #{character => 3, line => 15}
+ }
+ },
+ #{
+ newText => NewNameUsage,
+ range =>
+ #{
+ 'end' => #{character => 25, line => 15},
+ start => #{character => 15, line => 15}
+ }
+ }
+ ],
+ binary_to_atom(?config(rename_usage2_uri, Config), utf8) =>
+ [
+ #{
+ newText => NewNameUsage,
+ range =>
+ #{
+ 'end' => #{character => 26, line => 11},
+ start => #{character => 16, line => 11}
+ }
+ }
+ ]
+ }
+ },
+ assert_changes(Expected, Result).
-spec rename_module(config()) -> ok.
rename_module(Config) ->
- UriA = ?config(rename_module_a_uri, Config),
- UriB = ?config(rename_module_b_uri, Config),
- NewName = <<"new_module">>,
- Path = filename:dirname(els_uri:path(UriA)),
- NewUri = els_uri:uri(filename:join(Path, <>)),
- #{result := #{documentChanges := Result}} =
- els_client:document_rename(UriA, 0, 14, NewName),
- Expected = [
- %% Module attribute
- #{ edits => [change(NewName, {0, 8}, {0, 23})]
- , textDocument => #{uri => UriA, version => null}}
- %% Rename file
- , #{ kind => <<"rename">>
- , newUri => NewUri
- , oldUri => UriA}
- %% Implicit function
- , #{ edits => [change(NewName, {12, 10}, {12, 25})]
- , textDocument => #{uri => UriB, version => null}}
- %% Function application
- , #{ edits => [change(NewName, {11, 2}, {11, 17})]
- , textDocument => #{uri => UriB, version => null}}
- %% Import
- , #{ edits => [change(NewName, {3, 8}, {3, 23})]
- , textDocument => #{uri => UriB, version => null}}
- %% Type application
- , #{ edits => [change(NewName, {7, 18}, {7, 33})]
- , textDocument => #{uri => UriB, version => null}}
- %% Behaviour
- , #{ edits => [change(NewName, {2, 11}, {2, 26})]
- , textDocument => #{uri => UriB, version => null}}
- ],
- ?assertEqual([], Result -- Expected),
- ?assertEqual([], Expected -- Result),
- ?assertEqual(lists:sort(Expected), lists:sort(Result)).
+ UriA = ?config(rename_module_a_uri, Config),
+ UriB = ?config(rename_module_b_uri, Config),
+ NewName = <<"new_module">>,
+ Path = filename:dirname(els_uri:path(UriA)),
+ NewUri = els_uri:uri(filename:join(Path, <>)),
+ #{result := #{documentChanges := Result}} =
+ els_client:document_rename(UriA, 0, 14, NewName),
+ Expected = [
+ %% Module attribute
+ #{
+ edits => [change(NewName, {0, 8}, {0, 23})],
+ textDocument => #{uri => UriA, version => null}
+ },
+ %% Rename file
+ #{
+ kind => <<"rename">>,
+ newUri => NewUri,
+ oldUri => UriA
+ },
+ %% Implicit function
+ #{
+ edits => [change(NewName, {12, 10}, {12, 25})],
+ textDocument => #{uri => UriB, version => null}
+ },
+ %% Function application
+ #{
+ edits => [change(NewName, {11, 2}, {11, 17})],
+ textDocument => #{uri => UriB, version => null}
+ },
+ %% Import
+ #{
+ edits => [change(NewName, {3, 8}, {3, 23})],
+ textDocument => #{uri => UriB, version => null}
+ },
+ %% Type application
+ #{
+ edits => [change(NewName, {7, 18}, {7, 33})],
+ textDocument => #{uri => UriB, version => null}
+ },
+ %% Behaviour
+ #{
+ edits => [change(NewName, {2, 11}, {2, 26})],
+ textDocument => #{uri => UriB, version => null}
+ }
+ ],
+ ?assertEqual([], Result -- Expected),
+ ?assertEqual([], Expected -- Result),
+ ?assertEqual(lists:sort(Expected), lists:sort(Result)).
-spec rename_function(config()) -> ok.
rename_function(Config) ->
- Uri = ?config(rename_function_uri, Config),
- ImportUri = ?config(rename_function_import_uri, Config),
- NewName = <<"new_function">>,
- %% Function
- #{result := Result} = els_client:document_rename(Uri, 4, 2, NewName),
- %% Function clause
- #{result := Result} = els_client:document_rename(Uri, 6, 2, NewName),
- %% Application
- #{result := Result} = els_client:document_rename(ImportUri, 7, 18, NewName),
- %% Implicit fun
- #{result := Result} = els_client:document_rename(Uri, 13, 10, NewName),
- %% Export entry
- #{result := Result} = els_client:document_rename(Uri, 1, 9, NewName),
- %% Import entry
- #{result := Result} = els_client:document_rename(ImportUri, 2, 26, NewName),
- %% Spec
- #{result := Result} = els_client:document_rename(Uri, 3, 2, NewName),
- Expected = #{changes =>
- #{binary_to_atom(Uri, utf8) =>
- [ change(NewName, {12, 23}, {12, 26})
- , change(NewName, {13, 10}, {13, 13})
- , change(NewName, {15, 27}, {15, 30})
- , change(NewName, {17, 11}, {17, 14})
- , change(NewName, {18, 2}, {18, 5})
- , change(NewName, {1, 9}, {1, 12})
- , change(NewName, {3, 6}, {3, 9})
- , change(NewName, {4, 0}, {4, 3})
- , change(NewName, {6, 0}, {6, 3})
- , change(NewName, {8, 0}, {8, 3})
- ],
- binary_to_atom(ImportUri, utf8) =>
- [ change(NewName, {7, 18}, {7, 21})
- , change(NewName, {2, 26}, {2, 29})
- , change(NewName, {6, 2}, {6, 5})
- ]}},
- assert_changes(Expected, Result).
+ Uri = ?config(rename_function_uri, Config),
+ ImportUri = ?config(rename_function_import_uri, Config),
+ NewName = <<"new_function">>,
+ %% Function
+ #{result := Result} = els_client:document_rename(Uri, 4, 2, NewName),
+ %% Function clause
+ #{result := Result} = els_client:document_rename(Uri, 6, 2, NewName),
+ %% Application
+ #{result := Result} = els_client:document_rename(ImportUri, 7, 18, NewName),
+ %% Implicit fun
+ #{result := Result} = els_client:document_rename(Uri, 13, 10, NewName),
+ %% Export entry
+ #{result := Result} = els_client:document_rename(Uri, 1, 9, NewName),
+ %% Import entry
+ #{result := Result} = els_client:document_rename(ImportUri, 2, 26, NewName),
+ %% Spec
+ #{result := Result} = els_client:document_rename(Uri, 3, 2, NewName),
+ Expected = #{
+ changes =>
+ #{
+ binary_to_atom(Uri, utf8) =>
+ [
+ change(NewName, {12, 23}, {12, 26}),
+ change(NewName, {13, 10}, {13, 13}),
+ change(NewName, {15, 27}, {15, 30}),
+ change(NewName, {17, 11}, {17, 14}),
+ change(NewName, {18, 2}, {18, 5}),
+ change(NewName, {1, 9}, {1, 12}),
+ change(NewName, {3, 6}, {3, 9}),
+ change(NewName, {4, 0}, {4, 3}),
+ change(NewName, {6, 0}, {6, 3}),
+ change(NewName, {8, 0}, {8, 3})
+ ],
+ binary_to_atom(ImportUri, utf8) =>
+ [
+ change(NewName, {7, 18}, {7, 21}),
+ change(NewName, {2, 26}, {2, 29}),
+ change(NewName, {6, 2}, {6, 5})
+ ]
+ }
+ },
+ assert_changes(Expected, Result).
-spec rename_function_quoted_atom(config()) -> ok.
rename_function_quoted_atom(Config) ->
- Uri = ?config(rename_function_uri, Config),
- Line = 21,
- Char = 2,
- NewName = <<"new_function">>,
- #{result := Result} = els_client:document_rename(Uri, Line, Char, NewName),
- Expected = #{changes =>
- #{binary_to_atom(Uri, utf8) =>
- [ change(NewName, {29, 23}, {29, 36})
- , change(NewName, {30, 10}, {30, 23})
- , change(NewName, {32, 27}, {32, 40})
- , change(NewName, {34, 2}, {34, 15})
- , change(NewName, {1, 16}, {1, 29})
- , change(NewName, {20, 6}, {20, 19})
- , change(NewName, {21, 0}, {21, 13})
- , change(NewName, {23, 0}, {23, 13})
- , change(NewName, {25, 0}, {25, 13})
- ]}},
- assert_changes(Expected, Result).
+ Uri = ?config(rename_function_uri, Config),
+ Line = 21,
+ Char = 2,
+ NewName = <<"new_function">>,
+ #{result := Result} = els_client:document_rename(Uri, Line, Char, NewName),
+ Expected = #{
+ changes =>
+ #{
+ binary_to_atom(Uri, utf8) =>
+ [
+ change(NewName, {29, 23}, {29, 36}),
+ change(NewName, {30, 10}, {30, 23}),
+ change(NewName, {32, 27}, {32, 40}),
+ change(NewName, {34, 2}, {34, 15}),
+ change(NewName, {1, 16}, {1, 29}),
+ change(NewName, {20, 6}, {20, 19}),
+ change(NewName, {21, 0}, {21, 13}),
+ change(NewName, {23, 0}, {23, 13}),
+ change(NewName, {25, 0}, {25, 13})
+ ]
+ }
+ },
+ assert_changes(Expected, Result).
-spec rename_type(config()) -> ok.
rename_type(Config) ->
- Uri = ?config(rename_type_uri, Config),
- NewName = <<"new_type">>,
- %% Definition
- #{result := Result} = els_client:document_rename(Uri, 3, 7, NewName),
- %% Application
- #{result := Result} = els_client:document_rename(Uri, 5, 18, NewName),
- %% Fully qualified application
- #{result := Result} = els_client:document_rename(Uri, 4, 30, NewName),
- %% Export
- #{result := Result} = els_client:document_rename(Uri, 1, 14, NewName),
- Expected = #{changes =>
- #{binary_to_atom(Uri, utf8) =>
- [ change(NewName, {5, 18}, {5, 21})
- , change(NewName, {4, 30}, {4, 33})
- , change(NewName, {1, 14}, {1, 17})
- , change(NewName, {3, 6}, {3, 9})
- ]}},
- assert_changes(Expected, Result).
+ Uri = ?config(rename_type_uri, Config),
+ NewName = <<"new_type">>,
+ %% Definition
+ #{result := Result} = els_client:document_rename(Uri, 3, 7, NewName),
+ %% Application
+ #{result := Result} = els_client:document_rename(Uri, 5, 18, NewName),
+ %% Fully qualified application
+ #{result := Result} = els_client:document_rename(Uri, 4, 30, NewName),
+ %% Export
+ #{result := Result} = els_client:document_rename(Uri, 1, 14, NewName),
+ Expected = #{
+ changes =>
+ #{
+ binary_to_atom(Uri, utf8) =>
+ [
+ change(NewName, {5, 18}, {5, 21}),
+ change(NewName, {4, 30}, {4, 33}),
+ change(NewName, {1, 14}, {1, 17}),
+ change(NewName, {3, 6}, {3, 9})
+ ]
+ }
+ },
+ assert_changes(Expected, Result).
-spec rename_opaque(config()) -> ok.
rename_opaque(Config) ->
- Uri = ?config(rename_type_uri, Config),
- NewName = <<"new_opaque">>,
- %% Definition
- #{result := Result} = els_client:document_rename(Uri, 4, 10, NewName),
- %% Application
- #{result := Result} = els_client:document_rename(Uri, 5, 29, NewName),
- %% Export
- #{result := Result} = els_client:document_rename(Uri, 1, 24, NewName),
- Expected = #{changes =>
- #{binary_to_atom(Uri, utf8) =>
- [ change(NewName, {5, 26}, {5, 29})
- , change(NewName, {1, 21}, {1, 24})
- , change(NewName, {4, 8}, {4, 11})
- ]}},
- assert_changes(Expected, Result).
+ Uri = ?config(rename_type_uri, Config),
+ NewName = <<"new_opaque">>,
+ %% Definition
+ #{result := Result} = els_client:document_rename(Uri, 4, 10, NewName),
+ %% Application
+ #{result := Result} = els_client:document_rename(Uri, 5, 29, NewName),
+ %% Export
+ #{result := Result} = els_client:document_rename(Uri, 1, 24, NewName),
+ Expected = #{
+ changes =>
+ #{
+ binary_to_atom(Uri, utf8) =>
+ [
+ change(NewName, {5, 26}, {5, 29}),
+ change(NewName, {1, 21}, {1, 24}),
+ change(NewName, {4, 8}, {4, 11})
+ ]
+ }
+ },
+ assert_changes(Expected, Result).
-spec rename_parametrized_macro(config()) -> ok.
rename_parametrized_macro(Config) ->
- Uri = ?config(rename_h_uri, Config),
- Line = 2,
- Char = 16,
- NewName = <<"NEW_AWESOME_NAME">>,
- NewNameUsage = <<"?NEW_AWESOME_NAME">>,
- #{result := Result} = els_client:document_rename(Uri, Line, Char, NewName),
- Expected = #{changes =>
- #{ binary_to_atom(Uri, utf8) =>
- [ #{ newText => NewName
- , range =>
- #{ 'end' => #{character => 30, line => 2}
- , start => #{character => 8, line => 2}}}
- ]
- , binary_to_atom(?config(rename_usage1_uri, Config), utf8) =>
- [ #{ newText => NewNameUsage
- , range =>
- #{ 'end' => #{character => 26, line => 18}
- , start => #{character => 3, line => 18}}}
- , #{ newText => NewNameUsage
- , range =>
- #{ 'end' => #{character => 54, line => 18}
- , start => #{character => 31, line => 18}}}
- ]
- , binary_to_atom(
- ?config(rename_usage2_uri, Config), utf8) =>
- [ #{ newText => NewNameUsage
- , range =>
- #{ 'end' => #{character => 52, line => 14}
- , start => #{character => 29, line => 14}}}
- ]
- }
- },
- assert_changes(Expected, Result).
+ Uri = ?config(rename_h_uri, Config),
+ Line = 2,
+ Char = 16,
+ NewName = <<"NEW_AWESOME_NAME">>,
+ NewNameUsage = <<"?NEW_AWESOME_NAME">>,
+ #{result := Result} = els_client:document_rename(Uri, Line, Char, NewName),
+ Expected = #{
+ changes =>
+ #{
+ binary_to_atom(Uri, utf8) =>
+ [
+ #{
+ newText => NewName,
+ range =>
+ #{
+ 'end' => #{character => 30, line => 2},
+ start => #{character => 8, line => 2}
+ }
+ }
+ ],
+ binary_to_atom(?config(rename_usage1_uri, Config), utf8) =>
+ [
+ #{
+ newText => NewNameUsage,
+ range =>
+ #{
+ 'end' => #{character => 26, line => 18},
+ start => #{character => 3, line => 18}
+ }
+ },
+ #{
+ newText => NewNameUsage,
+ range =>
+ #{
+ 'end' => #{character => 54, line => 18},
+ start => #{character => 31, line => 18}
+ }
+ }
+ ],
+ binary_to_atom(
+ ?config(rename_usage2_uri, Config), utf8
+ ) =>
+ [
+ #{
+ newText => NewNameUsage,
+ range =>
+ #{
+ 'end' => #{character => 52, line => 14},
+ start => #{character => 29, line => 14}
+ }
+ }
+ ]
+ }
+ },
+ assert_changes(Expected, Result).
-spec rename_macro_from_usage(config()) -> ok.
rename_macro_from_usage(Config) ->
- Uri = ?config(rename_usage1_uri, Config),
- Line = 15,
- Char = 7,
- NewName = <<"NEW_AWESOME_NAME">>,
- NewNameUsage = <<"?NEW_AWESOME_NAME">>,
- #{result := Result} = els_client:document_rename(Uri, Line, Char, NewName),
- Expected = #{changes =>
- #{ binary_to_atom(?config(rename_h_uri, Config), utf8) =>
- [ #{ newText => NewName
- , range =>
- #{ 'end' => #{character => 17, line => 0}
- , start => #{character => 8, line => 0}}}
- ]
- , binary_to_atom(?config(rename_usage1_uri, Config), utf8) =>
- [ #{ newText => NewNameUsage
- , range =>
- #{ 'end' => #{character => 13, line => 15}
- , start => #{character => 3, line => 15}}}
- , #{ newText => NewNameUsage
- , range =>
- #{ 'end' => #{character => 25, line => 15}
- , start => #{character => 15, line => 15}}}
- ]
- , binary_to_atom(?config(rename_usage2_uri, Config), utf8) =>
- [ #{ newText => NewNameUsage
- , range =>
- #{ 'end' => #{character => 26, line => 11}
- , start => #{character => 16, line => 11}}}
- ]
- }
- },
- assert_changes(Expected, Result).
+ Uri = ?config(rename_usage1_uri, Config),
+ Line = 15,
+ Char = 7,
+ NewName = <<"NEW_AWESOME_NAME">>,
+ NewNameUsage = <<"?NEW_AWESOME_NAME">>,
+ #{result := Result} = els_client:document_rename(Uri, Line, Char, NewName),
+ Expected = #{
+ changes =>
+ #{
+ binary_to_atom(?config(rename_h_uri, Config), utf8) =>
+ [
+ #{
+ newText => NewName,
+ range =>
+ #{
+ 'end' => #{character => 17, line => 0},
+ start => #{character => 8, line => 0}
+ }
+ }
+ ],
+ binary_to_atom(?config(rename_usage1_uri, Config), utf8) =>
+ [
+ #{
+ newText => NewNameUsage,
+ range =>
+ #{
+ 'end' => #{character => 13, line => 15},
+ start => #{character => 3, line => 15}
+ }
+ },
+ #{
+ newText => NewNameUsage,
+ range =>
+ #{
+ 'end' => #{character => 25, line => 15},
+ start => #{character => 15, line => 15}
+ }
+ }
+ ],
+ binary_to_atom(?config(rename_usage2_uri, Config), utf8) =>
+ [
+ #{
+ newText => NewNameUsage,
+ range =>
+ #{
+ 'end' => #{character => 26, line => 11},
+ start => #{character => 16, line => 11}
+ }
+ }
+ ]
+ }
+ },
+ assert_changes(Expected, Result).
-spec rename_record(config()) -> ok.
rename_record(Config) ->
- HdrUri = ?config(rename_h_uri, Config),
- UsageUri = ?config(rename_usage1_uri, Config),
- NewName = <<"new_record_name">>,
- NewNameUsage = <<"#new_record_name">>,
+ HdrUri = ?config(rename_h_uri, Config),
+ UsageUri = ?config(rename_usage1_uri, Config),
+ NewName = <<"new_record_name">>,
+ NewNameUsage = <<"#new_record_name">>,
- Expected = #{changes =>
- #{ binary_to_atom(HdrUri, utf8) =>
- [ #{ newText => NewName
- , range =>
- #{ 'end' => #{character => 18, line => 4}
- , start => #{character => 8, line => 4}}}
- ]
- , binary_to_atom(UsageUri, utf8) =>
- [ #{ newText => NewNameUsage
- , range =>
- #{ 'end' => #{character => 29, line => 20}
- , start => #{character => 18, line => 20}}}
- , #{ newText => NewNameUsage
- , range =>
- #{ 'end' => #{character => 18, line => 22}
- , start => #{character => 7, line => 22}}}
- , #{ newText => NewNameUsage
- , range =>
- #{ 'end' => #{character => 18, line => 24}
- , start => #{character => 7, line => 24}}}
- , #{ newText => NewNameUsage
- , range =>
- #{ 'end' => #{character => 15, line => 26}
- , start => #{character => 4, line => 26}}}
- ]
- }
- },
+ Expected = #{
+ changes =>
+ #{
+ binary_to_atom(HdrUri, utf8) =>
+ [
+ #{
+ newText => NewName,
+ range =>
+ #{
+ 'end' => #{character => 18, line => 4},
+ start => #{character => 8, line => 4}
+ }
+ }
+ ],
+ binary_to_atom(UsageUri, utf8) =>
+ [
+ #{
+ newText => NewNameUsage,
+ range =>
+ #{
+ 'end' => #{character => 29, line => 20},
+ start => #{character => 18, line => 20}
+ }
+ },
+ #{
+ newText => NewNameUsage,
+ range =>
+ #{
+ 'end' => #{character => 18, line => 22},
+ start => #{character => 7, line => 22}
+ }
+ },
+ #{
+ newText => NewNameUsage,
+ range =>
+ #{
+ 'end' => #{character => 18, line => 24},
+ start => #{character => 7, line => 24}
+ }
+ },
+ #{
+ newText => NewNameUsage,
+ range =>
+ #{
+ 'end' => #{character => 15, line => 26},
+ start => #{character => 4, line => 26}
+ }
+ }
+ ]
+ }
+ },
- %% definition
- #{result := Result} = els_client:document_rename(HdrUri, 4, 10, NewName),
- assert_changes(Expected, Result),
- %% usage
- #{result := Result2} = els_client:document_rename(UsageUri, 22, 10, NewName),
- assert_changes(Expected, Result2).
+ %% definition
+ #{result := Result} = els_client:document_rename(HdrUri, 4, 10, NewName),
+ assert_changes(Expected, Result),
+ %% usage
+ #{result := Result2} = els_client:document_rename(UsageUri, 22, 10, NewName),
+ assert_changes(Expected, Result2).
-spec rename_record_field(config()) -> ok.
rename_record_field(Config) ->
- HdrUri = ?config(rename_h_uri, Config),
- UsageUri = ?config(rename_usage1_uri, Config),
- NewName = <<"new_record_field_name">>,
+ HdrUri = ?config(rename_h_uri, Config),
+ UsageUri = ?config(rename_usage1_uri, Config),
+ NewName = <<"new_record_field_name">>,
- Expected = #{changes =>
- #{ binary_to_atom(HdrUri, utf8) =>
- [ #{ newText => NewName
- , range =>
- #{ 'end' => #{character => 33, line => 4}
- , start => #{character => 21, line => 4}}}
- ]
- , binary_to_atom(UsageUri, utf8) =>
- [ #{ newText => NewName
- , range =>
- #{ 'end' => #{character => 42, line => 20}
- , start => #{character => 30, line => 20}}}
- , #{ newText => NewName
- , range =>
- #{ 'end' => #{character => 31, line => 22}
- , start => #{character => 19, line => 22}}}
- , #{ newText => NewName
- , range =>
- #{ 'end' => #{character => 31, line => 24}
- , start => #{character => 19, line => 24}}}
- , #{ newText => NewName
- , range =>
- #{ 'end' => #{character => 28, line => 26}
- , start => #{character => 16, line => 26}}}
- ]
- }
- },
+ Expected = #{
+ changes =>
+ #{
+ binary_to_atom(HdrUri, utf8) =>
+ [
+ #{
+ newText => NewName,
+ range =>
+ #{
+ 'end' => #{character => 33, line => 4},
+ start => #{character => 21, line => 4}
+ }
+ }
+ ],
+ binary_to_atom(UsageUri, utf8) =>
+ [
+ #{
+ newText => NewName,
+ range =>
+ #{
+ 'end' => #{character => 42, line => 20},
+ start => #{character => 30, line => 20}
+ }
+ },
+ #{
+ newText => NewName,
+ range =>
+ #{
+ 'end' => #{character => 31, line => 22},
+ start => #{character => 19, line => 22}
+ }
+ },
+ #{
+ newText => NewName,
+ range =>
+ #{
+ 'end' => #{character => 31, line => 24},
+ start => #{character => 19, line => 24}
+ }
+ },
+ #{
+ newText => NewName,
+ range =>
+ #{
+ 'end' => #{character => 28, line => 26},
+ start => #{character => 16, line => 26}
+ }
+ }
+ ]
+ }
+ },
- %% definition
- #{result := Result} = els_client:document_rename(HdrUri, 4, 25, NewName),
- assert_changes(Expected, Result),
- %% usage
- #{result := Result2} = els_client:document_rename(UsageUri, 22, 25, NewName),
- assert_changes(Expected, Result2).
+ %% definition
+ #{result := Result} = els_client:document_rename(HdrUri, 4, 25, NewName),
+ assert_changes(Expected, Result),
+ %% usage
+ #{result := Result2} = els_client:document_rename(UsageUri, 22, 25, NewName),
+ assert_changes(Expected, Result2).
-assert_changes(#{ changes := ExpectedChanges }, #{ changes := Changes }) ->
- ?assertEqual(maps:keys(ExpectedChanges), maps:keys(Changes)),
- Pairs = lists:zip(lists:sort(maps:to_list(Changes)),
- lists:sort(maps:to_list(ExpectedChanges))),
- [ begin
- ?assertEqual(ExpectedKey, Key),
- ?assertEqual(lists:sort(Expected), lists:sort(Change))
- end
- || {{Key, Change}, {ExpectedKey, Expected}} <- Pairs
- ],
- ok.
+assert_changes(#{changes := ExpectedChanges}, #{changes := Changes}) ->
+ ?assertEqual(maps:keys(ExpectedChanges), maps:keys(Changes)),
+ Pairs = lists:zip(
+ lists:sort(maps:to_list(Changes)),
+ lists:sort(maps:to_list(ExpectedChanges))
+ ),
+ [
+ begin
+ ?assertEqual(ExpectedKey, Key),
+ ?assertEqual(lists:sort(Expected), lists:sort(Change))
+ end
+ || {{Key, Change}, {ExpectedKey, Expected}} <- Pairs
+ ],
+ ok.
change(NewName, {FromL, FromC}, {ToL, ToC}) ->
- #{ newText => NewName
- , range => #{ start => #{character => FromC, line => FromL}
- , 'end' => #{character => ToC, line => ToL}}}.
+ #{
+ newText => NewName,
+ range => #{
+ start => #{character => FromC, line => FromL},
+ 'end' => #{character => ToC, line => ToL}
+ }
+ }.
diff --git a/apps/els_lsp/test/els_server_SUITE.erl b/apps/els_lsp/test/els_server_SUITE.erl
index 776a1cee9..e199d43f3 100644
--- a/apps/els_lsp/test/els_server_SUITE.erl
+++ b/apps/els_lsp/test/els_server_SUITE.erl
@@ -1,17 +1,17 @@
-module(els_server_SUITE).
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ cancel_request/1
- ]).
+-export([cancel_request/1]).
%%==============================================================================
%% Includes
@@ -29,80 +29,85 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(cancel_request = TestCase, Config0) ->
- Config = els_test_utils:init_per_testcase(TestCase, Config0),
- %% Ensure the background job triggered by the code lens will never return
- meck:new(els_background_job, [no_link, passthrough]),
- meck:expect(els_background_job, new,
- fun(C0) ->
- C = C0#{ task => fun(_, _) ->
- timer:sleep(infinity)
- end},
- meck:passthrough([C])
- end),
- [ {mocks, [els_background_job]} | Config].
+ Config = els_test_utils:init_per_testcase(TestCase, Config0),
+ %% Ensure the background job triggered by the code lens will never return
+ meck:new(els_background_job, [no_link, passthrough]),
+ meck:expect(
+ els_background_job,
+ new,
+ fun(C0) ->
+ C = C0#{
+ task => fun(_, _) ->
+ timer:sleep(infinity)
+ end
+ },
+ meck:passthrough([C])
+ end
+ ),
+ [{mocks, [els_background_job]} | Config].
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
- Mocks = ?config(mocks, Config),
- [meck:unload(Mock) || Mock <- Mocks],
- els_test_utils:end_per_testcase(TestCase, Config).
+ Mocks = ?config(mocks, Config),
+ [meck:unload(Mock) || Mock <- Mocks],
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Testcases
%%==============================================================================
-spec cancel_request(config()) -> ok.
cancel_request(Config) ->
- %% Trigger a document/CodeLens request in the background
- Uri = ?config(code_navigation_uri, Config),
- spawn(fun() -> els_client:document_codelens(Uri) end),
- %% Extract the current background job (from the lens provider)
- Job = wait_until_one_lens_job(),
- %% Verify the background job is triggered
- ?assert(lists:member(Job, els_background_job:list())),
- %% Cancel the original request
- Result = els_client:'$_cancelrequest'(),
- %% Ensure the previous request is canceled
- wait_until_no_lens_jobs(),
- ?assertEqual(ok, Result).
+ %% Trigger a document/CodeLens request in the background
+ Uri = ?config(code_navigation_uri, Config),
+ spawn(fun() -> els_client:document_codelens(Uri) end),
+ %% Extract the current background job (from the lens provider)
+ Job = wait_until_one_lens_job(),
+ %% Verify the background job is triggered
+ ?assert(lists:member(Job, els_background_job:list())),
+ %% Cancel the original request
+ Result = els_client:'$_cancelrequest'(),
+ %% Ensure the previous request is canceled
+ wait_until_no_lens_jobs(),
+ ?assertEqual(ok, Result).
wait_until_one_lens_job() ->
- Jobs = get_current_lens_jobs(),
- case Jobs of
- [Job] ->
- Job;
- [] ->
- timer:sleep(100),
- wait_until_one_lens_job()
- end.
+ Jobs = get_current_lens_jobs(),
+ case Jobs of
+ [Job] ->
+ Job;
+ [] ->
+ timer:sleep(100),
+ wait_until_one_lens_job()
+ end.
-spec wait_until_no_lens_jobs() -> ok.
wait_until_no_lens_jobs() ->
- case get_current_lens_jobs() of
- [] ->
- ok;
- _ ->
- timer:sleep(100),
- wait_until_no_lens_jobs()
- end.
+ case get_current_lens_jobs() of
+ [] ->
+ ok;
+ _ ->
+ timer:sleep(100),
+ wait_until_no_lens_jobs()
+ end.
-spec get_current_lens_jobs() -> [pid()].
get_current_lens_jobs() ->
- State = sys:get_state(els_provider, 30 * 1000),
- #{in_progress := InProgress} = State,
- [Job || {_Uri, Job} <- InProgress].
+ State = sys:get_state(els_provider, 30 * 1000),
+ #{in_progress := InProgress} = State,
+ [Job || {_Uri, Job} <- InProgress].
diff --git a/apps/els_lsp/test/els_test.erl b/apps/els_lsp/test/els_test.erl
index 4d6195dd9..1879c4656 100644
--- a/apps/els_lsp/test/els_test.erl
+++ b/apps/els_lsp/test/els_test.erl
@@ -6,126 +6,151 @@
-opaque session() :: #{uri := uri()}.
-type source() :: binary().
-type code() :: binary().
--type simplified_diagnostic() :: #{ code => code()
- , range => {pos(), pos()}
- }.
--export_type([ session/0 ]).
-
--export([ run_diagnostics_test/5
- , start_session/1
- , wait_for_diagnostics/2
- , assert_errors/3
- , assert_warnings/3
- , assert_hints/3
- , assert_contains/2
- , compiler_returns_column_numbers/0
- ]).
-
--spec run_diagnostics_test(string(), source(),
- [els_diagnostics:diagnostic()],
- [els_diagnostics:diagnostic()],
- [els_diagnostics:diagnostic()]) -> ok.
+-type simplified_diagnostic() :: #{
+ code => code(),
+ range => {pos(), pos()}
+}.
+-export_type([session/0]).
+
+-export([
+ run_diagnostics_test/5,
+ start_session/1,
+ wait_for_diagnostics/2,
+ assert_errors/3,
+ assert_warnings/3,
+ assert_hints/3,
+ assert_contains/2,
+ compiler_returns_column_numbers/0
+]).
+
+-spec run_diagnostics_test(
+ string(),
+ source(),
+ [els_diagnostics:diagnostic()],
+ [els_diagnostics:diagnostic()],
+ [els_diagnostics:diagnostic()]
+) -> ok.
run_diagnostics_test(Path, Source, Errors, Warnings, Hints) ->
- {ok, Session} = start_session(Path),
- Diagnostics = wait_for_diagnostics(Session, Source),
- assert_errors(Source, Errors, Diagnostics),
- assert_warnings(Source, Warnings, Diagnostics),
- assert_hints(Source, Hints, Diagnostics).
+ {ok, Session} = start_session(Path),
+ Diagnostics = wait_for_diagnostics(Session, Source),
+ assert_errors(Source, Errors, Diagnostics),
+ assert_warnings(Source, Warnings, Diagnostics),
+ assert_hints(Source, Hints, Diagnostics).
-spec start_session(string()) -> {ok, session()}.
start_session(Path0) ->
- PrivDir = code:priv_dir(els_lsp),
- Path = filename:join([els_utils:to_binary(PrivDir), Path0]),
- Uri = els_uri:uri(Path),
- {ok, #{uri => Uri}}.
+ PrivDir = code:priv_dir(els_lsp),
+ Path = filename:join([els_utils:to_binary(PrivDir), Path0]),
+ Uri = els_uri:uri(Path),
+ {ok, #{uri => Uri}}.
-spec wait_for_diagnostics(session(), source()) ->
- [els_diagnostics:diagnostic()].
+ [els_diagnostics:diagnostic()].
wait_for_diagnostics(#{uri := Uri}, Source) ->
- els_mock_diagnostics:subscribe(),
- ok = els_client:did_save(Uri),
- Diagnostics = els_mock_diagnostics:wait_until_complete(),
- [D || #{source := S} = D <- Diagnostics, S =:= Source].
-
--spec assert_errors(source(),
- [els_diagnostics:diagnostic()],
- [els_diagnostics:diagnostic()]) -> ok.
+ els_mock_diagnostics:subscribe(),
+ ok = els_client:did_save(Uri),
+ Diagnostics = els_mock_diagnostics:wait_until_complete(),
+ [D || #{source := S} = D <- Diagnostics, S =:= Source].
+
+-spec assert_errors(
+ source(),
+ [els_diagnostics:diagnostic()],
+ [els_diagnostics:diagnostic()]
+) -> ok.
assert_errors(Source, Expected, Diagnostics) ->
- assert_diagnostics(Source, Expected, Diagnostics, ?DIAGNOSTIC_ERROR).
+ assert_diagnostics(Source, Expected, Diagnostics, ?DIAGNOSTIC_ERROR).
--spec assert_warnings(source(),
- [els_diagnostics:diagnostic()],
- [els_diagnostics:diagnostic()]) -> ok.
+-spec assert_warnings(
+ source(),
+ [els_diagnostics:diagnostic()],
+ [els_diagnostics:diagnostic()]
+) -> ok.
assert_warnings(Source, Expected, Diagnostics) ->
- assert_diagnostics(Source, Expected, Diagnostics, ?DIAGNOSTIC_WARNING).
+ assert_diagnostics(Source, Expected, Diagnostics, ?DIAGNOSTIC_WARNING).
--spec assert_hints(source(),
- [els_diagnostics:diagnostic()],
- [els_diagnostics:diagnostic()]) -> ok.
+-spec assert_hints(
+ source(),
+ [els_diagnostics:diagnostic()],
+ [els_diagnostics:diagnostic()]
+) -> ok.
assert_hints(Source, Expected, Diagnostics) ->
- assert_diagnostics(Source, Expected, Diagnostics, ?DIAGNOSTIC_HINT).
+ assert_diagnostics(Source, Expected, Diagnostics, ?DIAGNOSTIC_HINT).
--spec assert_contains(els_diagnostics:diagnostic(),
- [els_diagnostics:diagnostic()]) -> ok.
+-spec assert_contains(
+ els_diagnostics:diagnostic(),
+ [els_diagnostics:diagnostic()]
+) -> ok.
assert_contains(Diagnostic, Diagnostics) ->
- Simplified = [simplify_diagnostic(D) || D <- Diagnostics],
- ?assert(lists:member(Diagnostic, Simplified)).
-
--spec assert_diagnostics(source(),
- [els_diagnostics:diagnostic()],
- [els_diagnostics:diagnostic()],
- els_diagnostics:severity()) -> ok.
+ Simplified = [simplify_diagnostic(D) || D <- Diagnostics],
+ ?assert(lists:member(Diagnostic, Simplified)).
+
+-spec assert_diagnostics(
+ source(),
+ [els_diagnostics:diagnostic()],
+ [els_diagnostics:diagnostic()],
+ els_diagnostics:severity()
+) -> ok.
assert_diagnostics(Source, Expected, Diagnostics, Severity) ->
- Filtered = [D || #{severity := S} = D <- Diagnostics, S =:= Severity],
- Simplified = [simplify_diagnostic(D) || D <- Filtered],
- FixedExpected = [maybe_fix_range(Source, D) || D <- Expected],
- ?assertEqual(lists:sort(FixedExpected),
- lists:sort(Simplified),
- Filtered).
+ Filtered = [D || #{severity := S} = D <- Diagnostics, S =:= Severity],
+ Simplified = [simplify_diagnostic(D) || D <- Filtered],
+ FixedExpected = [maybe_fix_range(Source, D) || D <- Expected],
+ ?assertEqual(
+ lists:sort(FixedExpected),
+ lists:sort(Simplified),
+ Filtered
+ ).
-spec simplify_diagnostic(els_diagnostics:diagnostic()) ->
- simplified_diagnostic().
+ simplified_diagnostic().
simplify_diagnostic(Diagnostic) ->
- Range = simplify_range(maps:get(range, Diagnostic)),
- maps:put(range, Range,
- maps:remove(severity,
- maps:remove(source, Diagnostic))).
+ Range = simplify_range(maps:get(range, Diagnostic)),
+ maps:put(
+ range,
+ Range,
+ maps:remove(
+ severity,
+ maps:remove(source, Diagnostic)
+ )
+ ).
-spec simplify_range(range()) -> {pos(), pos()}.
simplify_range(Range) ->
- #{ start := #{ character := CharacterStart
- , line := LineStart
- }
- , 'end' := #{ character := CharacterEnd
- , line := LineEnd
- }
- } = Range,
- {{LineStart, CharacterStart}, {LineEnd, CharacterEnd}}.
+ #{
+ start := #{
+ character := CharacterStart,
+ line := LineStart
+ },
+ 'end' := #{
+ character := CharacterEnd,
+ line := LineEnd
+ }
+ } = Range,
+ {{LineStart, CharacterStart}, {LineEnd, CharacterEnd}}.
maybe_fix_range(<<"Compiler">>, Diagnostic) ->
- case compiler_returns_column_numbers() of
- false ->
- fix_range(Diagnostic);
- true ->
- Diagnostic
- end;
+ case compiler_returns_column_numbers() of
+ false ->
+ fix_range(Diagnostic);
+ true ->
+ Diagnostic
+ end;
maybe_fix_range(_Source, Diagnostic) ->
- Diagnostic.
+ Diagnostic.
fix_range(#{code := <<"L0000">>} = Diagnostic) ->
- Diagnostic;
+ Diagnostic;
fix_range(Diagnostic) ->
- #{ range := Range } = Diagnostic,
- {{StartLine, StartCol}, {EndLine, EndCol}} = Range,
- case StartCol =/= 0 orelse EndCol =/= 0 of
- true ->
- Diagnostic#{range => {{StartLine, 0}, {EndLine + 1, 0}}};
- false ->
- Diagnostic
- end.
+ #{range := Range} = Diagnostic,
+ {{StartLine, StartCol}, {EndLine, EndCol}} = Range,
+ case StartCol =/= 0 orelse EndCol =/= 0 of
+ true ->
+ Diagnostic#{range => {{StartLine, 0}, {EndLine + 1, 0}}};
+ false ->
+ Diagnostic
+ end.
compiler_returns_column_numbers() ->
- %% If epp:open/5 is exported we know that columns are not
- %% returned by the compiler warnings and errors.
- %% Should find a better heuristic for this.
- not erlang:function_exported(epp, open, 5).
+ %% If epp:open/5 is exported we know that columns are not
+ %% returned by the compiler warnings and errors.
+ %% Should find a better heuristic for this.
+ not erlang:function_exported(epp, open, 5).
diff --git a/apps/els_lsp/test/els_test_utils.erl b/apps/els_lsp/test/els_test_utils.erl
index bee03ad3f..0f5b4ab33 100644
--- a/apps/els_lsp/test/els_test_utils.erl
+++ b/apps/els_lsp/test/els_test_utils.erl
@@ -1,18 +1,19 @@
-module(els_test_utils).
--export([ all/1
- , all/2
- , end_per_suite/1
- , end_per_testcase/2
- , init_per_suite/1
- , init_per_testcase/2
- , start/0
- , wait_for/2
- , wait_for_fun/3
- , wait_until_mock_called/2
- , root_path/0
- , root_uri/0
- ]).
+-export([
+ all/1,
+ all/2,
+ end_per_suite/1,
+ end_per_testcase/2,
+ init_per_suite/1,
+ init_per_testcase/2,
+ start/0,
+ wait_for/2,
+ wait_for_fun/3,
+ wait_until_mock_called/2,
+ root_path/0,
+ root_uri/0
+]).
-include_lib("common_test/include/ct.hrl").
@@ -35,93 +36,97 @@ all(Module) -> all(Module, []).
-spec all(module(), [atom()]) -> [atom()].
all(Module, Functions) ->
- ExcludedFuns = [init_per_suite, end_per_suite, all, module_info | Functions],
- Exports = Module:module_info(exports),
- [F || {F, 1} <- Exports, not lists:member(F, ExcludedFuns)].
+ ExcludedFuns = [init_per_suite, end_per_suite, all, module_info | Functions],
+ Exports = Module:module_info(exports),
+ [F || {F, 1} <- Exports, not lists:member(F, ExcludedFuns)].
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- application:load(els_core),
- Config.
+ application:load(els_core),
+ Config.
-spec end_per_suite(config()) -> ok.
end_per_suite(_Config) ->
- ok.
+ ok.
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(_TestCase, Config) ->
- meck:new(els_distribution_server, [no_link, passthrough]),
- meck:expect(els_distribution_server, connect, 0, ok),
- Started = start(),
- els_client:initialize(root_uri(), #{indexingEnabled => false}),
- els_client:initialized(),
- SrcConfig = lists:flatten([index_file(S) || S <- sources()]),
- TestConfig = lists:flatten([index_file(S) || S <- tests()]),
- EscriptConfig = lists:flatten([index_file(S) || S <- escripts()]),
- IncludeConfig = lists:flatten([index_file(S) || S <- includes()]),
- lists:append( [ SrcConfig
- , TestConfig
- , EscriptConfig
- , IncludeConfig
- , [ {started, Started}
- | Config]
- ]).
+ meck:new(els_distribution_server, [no_link, passthrough]),
+ meck:expect(els_distribution_server, connect, 0, ok),
+ Started = start(),
+ els_client:initialize(root_uri(), #{indexingEnabled => false}),
+ els_client:initialized(),
+ SrcConfig = lists:flatten([index_file(S) || S <- sources()]),
+ TestConfig = lists:flatten([index_file(S) || S <- tests()]),
+ EscriptConfig = lists:flatten([index_file(S) || S <- escripts()]),
+ IncludeConfig = lists:flatten([index_file(S) || S <- includes()]),
+ lists:append([
+ SrcConfig,
+ TestConfig,
+ EscriptConfig,
+ IncludeConfig,
+ [
+ {started, Started}
+ | Config
+ ]
+ ]).
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(_TestCase, Config) ->
- meck:unload(els_distribution_server),
- [application:stop(App) || App <- ?config(started, Config)],
- ok.
+ meck:unload(els_distribution_server),
+ [application:stop(App) || App <- ?config(started, Config)],
+ ok.
-spec start() -> [atom()].
start() ->
- ClientIo = els_fake_stdio:start(),
- ServerIo = els_fake_stdio:start(),
- els_fake_stdio:connect(ClientIo, ServerIo),
- els_fake_stdio:connect(ServerIo, ClientIo),
- ok = application:set_env(els_core, io_device, ServerIo),
- {ok, Started} = application:ensure_all_started(els_lsp),
- els_client:start_link(#{io_device => ClientIo}),
- Started.
+ ClientIo = els_fake_stdio:start(),
+ ServerIo = els_fake_stdio:start(),
+ els_fake_stdio:connect(ClientIo, ServerIo),
+ els_fake_stdio:connect(ServerIo, ClientIo),
+ ok = application:set_env(els_core, io_device, ServerIo),
+ {ok, Started} = application:ensure_all_started(els_lsp),
+ els_client:start_link(#{io_device => ClientIo}),
+ Started.
-spec wait_for(any(), non_neg_integer()) -> ok.
wait_for(_Message, Timeout) when Timeout =< 0 ->
- timeout;
+ timeout;
wait_for(Message, Timeout) ->
- receive Message -> ok
- after 10 -> wait_for(Message, Timeout - 10)
- end.
+ receive
+ Message -> ok
+ after 10 -> wait_for(Message, Timeout - 10)
+ end.
--spec wait_for_fun(fun(), non_neg_integer(), non_neg_integer())
- -> {ok, any()} | timeout.
+-spec wait_for_fun(fun(), non_neg_integer(), non_neg_integer()) ->
+ {ok, any()} | timeout.
wait_for_fun(_CheckFun, _WaitTime, 0) ->
- timeout;
+ timeout;
wait_for_fun(CheckFun, WaitTime, Retries) ->
- case CheckFun() of
- true ->
- ok;
- {true, Value} ->
- {ok, Value};
- false ->
- timer:sleep(WaitTime),
- wait_for_fun(CheckFun, WaitTime, Retries - 1)
- end.
+ case CheckFun() of
+ true ->
+ ok;
+ {true, Value} ->
+ {ok, Value};
+ false ->
+ timer:sleep(WaitTime),
+ wait_for_fun(CheckFun, WaitTime, Retries - 1)
+ end.
-spec sources() -> [binary()].
sources() ->
- wildcard("*.erl", "src").
+ wildcard("*.erl", "src").
-spec tests() -> [binary()].
tests() ->
- wildcard("*.erl", "test").
+ wildcard("*.erl", "test").
-spec escripts() -> [binary()].
escripts() ->
- wildcard("*.escript", "src").
+ wildcard("*.escript", "src").
-spec includes() -> [binary()].
includes() ->
- wildcard("*.hrl", "include").
+ wildcard("*.hrl", "include").
%% @doc Index a file and produce the respective config entries
%%
@@ -130,14 +135,15 @@ includes() ->
%% accessing this information from test cases.
-spec index_file(binary()) -> [{atom(), any()}].
index_file(Path) ->
- Uri = els_uri:uri(Path),
- els_indexing:ensure_deeply_indexed(Uri),
- {ok, Text} = file:read_file(Path),
- ConfigId = config_id(Path),
- [ {atoms_append(ConfigId, '_path'), Path}
- , {atoms_append(ConfigId, '_uri'), Uri}
- , {atoms_append(ConfigId, '_text'), Text}
- ].
+ Uri = els_uri:uri(Path),
+ els_indexing:ensure_deeply_indexed(Uri),
+ {ok, Text} = file:read_file(Path),
+ ConfigId = config_id(Path),
+ [
+ {atoms_append(ConfigId, '_path'), Path},
+ {atoms_append(ConfigId, '_uri'), Uri},
+ {atoms_append(ConfigId, '_text'), Text}
+ ].
-spec suffix(binary()) -> binary().
suffix(<<".erl">>) -> <<"">>;
@@ -146,38 +152,40 @@ suffix(<<".escript">>) -> <<"_escript">>.
-spec config_id(string()) -> atom().
config_id(Path) ->
- Extension = filename:extension(Path),
- BaseName = filename:basename(Path, Extension),
- Suffix = suffix(Extension),
- binary_to_atom(<>, utf8).
+ Extension = filename:extension(Path),
+ BaseName = filename:basename(Path, Extension),
+ Suffix = suffix(Extension),
+ binary_to_atom(<>, utf8).
-spec atoms_append(atom(), atom()) -> atom().
atoms_append(Atom1, Atom2) ->
- Bin1 = atom_to_binary(Atom1, utf8),
- Bin2 = atom_to_binary(Atom2, utf8),
- binary_to_atom(<>, utf8).
+ Bin1 = atom_to_binary(Atom1, utf8),
+ Bin2 = atom_to_binary(Atom2, utf8),
+ binary_to_atom(<>, utf8).
-spec wait_until_mock_called(atom(), atom()) -> ok.
wait_until_mock_called(M, F) ->
- case meck:num_calls(M, F, '_') of
- 0 ->
- timer:sleep(100),
- wait_until_mock_called(M, F);
- _ ->
- ok
- end.
+ case meck:num_calls(M, F, '_') of
+ 0 ->
+ timer:sleep(100),
+ wait_until_mock_called(M, F);
+ _ ->
+ ok
+ end.
-spec root_path() -> binary().
root_path() ->
- PrivDir = code:priv_dir(els_lsp),
- els_utils:to_binary(filename:join([PrivDir, ?TEST_APP])).
+ PrivDir = code:priv_dir(els_lsp),
+ els_utils:to_binary(filename:join([PrivDir, ?TEST_APP])).
-spec root_uri() -> els_uri:uri().
root_uri() ->
- els_uri:uri(root_path()).
+ els_uri:uri(root_path()).
-spec wildcard(string(), string()) -> [binary()].
wildcard(Extension, Dir) ->
- RootDir = els_utils:to_list(root_path()),
- [els_utils:to_binary(filename:join([RootDir, Dir, Path])) ||
- Path <- filelib:wildcard(Extension, filename:join([RootDir, Dir]))].
+ RootDir = els_utils:to_list(root_path()),
+ [
+ els_utils:to_binary(filename:join([RootDir, Dir, Path]))
+ || Path <- filelib:wildcard(Extension, filename:join([RootDir, Dir]))
+ ].
diff --git a/apps/els_lsp/test/els_text_SUITE.erl b/apps/els_lsp/test/els_text_SUITE.erl
index aa1f9c7f8..ebb2df412 100644
--- a/apps/els_lsp/test/els_text_SUITE.erl
+++ b/apps/els_lsp/test/els_text_SUITE.erl
@@ -1,19 +1,21 @@
-module(els_text_SUITE).
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ apply_edits_single_line/1
- , apply_edits_multi_line/1
- , apply_edits_unicode/1
- ]).
+-export([
+ apply_edits_single_line/1,
+ apply_edits_multi_line/1,
+ apply_edits_unicode/1
+]).
%%==============================================================================
%% Includes
@@ -30,136 +32,156 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(_TestCase, Config) ->
- Config.
+ Config.
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(_TestCase, Config) ->
- Config.
+ Config.
%%==============================================================================
%% Testcases
%%==============================================================================
apply_edits_single_line(_Config) ->
- In = <<"a b c">>,
- C = fun(FromC, ToC, Str) -> {#{from => {0, FromC}, to => {0, ToC}}, Str} end,
- F = fun els_text:apply_edits/2,
- ?assertEqual(<<"a b c">>, F(In, [])),
- ?assertEqual(<<"a b c">>, F(In, [C(0, 0, "")])),
- ?assertEqual(<<"_a b c">>, F(In, [C(0, 0, "_")])),
- ?assertEqual(<<"_ b c">>, F(In, [C(0, 1, "_")])),
- ?assertEqual(<<"__ b c">>, F(In, [C(0, 1, "__")])),
- ?assertEqual(<<"__b c">>, F(In, [C(0, 2, "__")])),
- ?assertEqual(<<"a _ c">>, F(In, [C(2, 3, "_")])),
- ?assertEqual(<<"c">>, F(In, [C(0, 4, "")])),
- ?assertEqual(<<"a">>, F(In, [C(1, 5, "")])),
- ?assertEqual(<<"">>, F(In, [C(0, 5, "")])),
- ?assertEqual(<<"a b c!">>, F(In, [C(5, 5, "!")])),
- ?assertEqual(<<>>, F(<<>>, [ C(0, 0, "")
- , C(0, 0, "")
- ])),
- ?assertEqual(<<"cba">>, F(<<>>, [ C(0, 0, "a")
- , C(0, 0, "b")
- , C(0, 0, "c")
- ])),
- ?assertEqual(<<"abc">>, F(<<>>, [ C(0, 0, "c")
- , C(0, 0, "b")
- , C(0, 0, "a")
- ])),
- ?assertEqual(<<"ab">>, F(In, [ C(0, 0, "a")
- , C(1, 6, "")
- , C(1, 1, "b")
- ])),
- ?assertEqual(<<"aba b c">>, F(In, [ C(0, 0, "a")
- , C(1, 1, "b")
- ])),
- ?assertEqual(<<"_ c!">>, F(In, [ C(0, 3, "")
- , C(0, 0, "_")
- , C(3, 3, "!")
- ])),
- ok.
+ In = <<"a b c">>,
+ C = fun(FromC, ToC, Str) -> {#{from => {0, FromC}, to => {0, ToC}}, Str} end,
+ F = fun els_text:apply_edits/2,
+ ?assertEqual(<<"a b c">>, F(In, [])),
+ ?assertEqual(<<"a b c">>, F(In, [C(0, 0, "")])),
+ ?assertEqual(<<"_a b c">>, F(In, [C(0, 0, "_")])),
+ ?assertEqual(<<"_ b c">>, F(In, [C(0, 1, "_")])),
+ ?assertEqual(<<"__ b c">>, F(In, [C(0, 1, "__")])),
+ ?assertEqual(<<"__b c">>, F(In, [C(0, 2, "__")])),
+ ?assertEqual(<<"a _ c">>, F(In, [C(2, 3, "_")])),
+ ?assertEqual(<<"c">>, F(In, [C(0, 4, "")])),
+ ?assertEqual(<<"a">>, F(In, [C(1, 5, "")])),
+ ?assertEqual(<<"">>, F(In, [C(0, 5, "")])),
+ ?assertEqual(<<"a b c!">>, F(In, [C(5, 5, "!")])),
+ ?assertEqual(
+ <<>>,
+ F(<<>>, [
+ C(0, 0, ""),
+ C(0, 0, "")
+ ])
+ ),
+ ?assertEqual(
+ <<"cba">>,
+ F(<<>>, [
+ C(0, 0, "a"),
+ C(0, 0, "b"),
+ C(0, 0, "c")
+ ])
+ ),
+ ?assertEqual(
+ <<"abc">>,
+ F(<<>>, [
+ C(0, 0, "c"),
+ C(0, 0, "b"),
+ C(0, 0, "a")
+ ])
+ ),
+ ?assertEqual(
+ <<"ab">>,
+ F(In, [
+ C(0, 0, "a"),
+ C(1, 6, ""),
+ C(1, 1, "b")
+ ])
+ ),
+ ?assertEqual(
+ <<"aba b c">>,
+ F(In, [
+ C(0, 0, "a"),
+ C(1, 1, "b")
+ ])
+ ),
+ ?assertEqual(
+ <<"_ c!">>,
+ F(In, [
+ C(0, 3, ""),
+ C(0, 0, "_"),
+ C(3, 3, "!")
+ ])
+ ),
+ ok.
apply_edits_multi_line(_Config) ->
- In = <<"a b c\n"
- "d e f\n"
- "g h i\n">>,
- C = fun(FromL, FromC, ToL, ToC, Str) ->
- {#{from => {FromL, FromC}, to => {ToL, ToC}}, Str}
- end,
- F = fun els_text:apply_edits/2,
- ?assertEqual(In, F(In, [])),
- ?assertEqual(In, F(In, [C(0, 0, 0, 0, "")])),
- ?assertEqual(<<"_ b c\n",
- "d e f\n",
- "g h i\n">>, F(In, [C(0, 0, 0, 1, "_")])),
- ?assertEqual(<<"_a b c\n",
- "d e f\n",
- "g h i\n">>, F(In, [C(0, 0, 0, 0, "_")])),
- ?assertEqual(<<"a b c\n",
- "_d e f\n",
- "g h i\n">>, F(In, [C(1, 0, 1, 0, "_")])),
- ?assertEqual(<<"a b c\n",
- "d e f\n",
- "_g h i\n">>, F(In, [C(2, 0, 2, 0, "_")])),
- ?assertEqual(<<"a b c\n",
- "d e f\n",
- "g h i_\n">>, F(In, [C(2, 5, 2, 5, "_")])),
- ?assertEqual(<<"a b c\n",
- "d e f\n",
- "g h _\n">>, F(In, [C(2, 4, 2, 5, "_")])),
- ?assertEqual(<<"a b c\n",
- "d e f\n",
- "g _ i\n">>, F(In, [C(2, 2, 2, 3, "_")])),
- ?assertEqual(<<"a b c\n",
- "d e f\n",
- "g h i\n",
- "_">>, F(In, [C(3, 0, 3, 0, "_")])),
- ?assertEqual(<<"_">>, F(In, [C(0, 0, 3, 0, "_")])),
- ?assertEqual(<<"a b _\n",
- "d e _\n",
- "g h _\n">>, F(In, [ C(0, 4, 0, 5, "_")
- , C(1, 4, 1, 5, "_")
- , C(2, 4, 2, 5, "_")
- ])),
- ?assertEqual(<<"a b c\n",
- "g h i\n">>, F(In, [C(1, 0, 2, 0, "")])),
- ?assertEqual(<<"a b h i\n">>, F(In, [C(0, 3, 2, 1, "")])),
- ?assertEqual(<<"a b _ i\n">>, F(In, [ C(0, 3, 2, 1, "")
- , C(0, 4, 0, 5, "_")
- ])),
- ?assertEqual(<<"a b h _\n">>, F(In, [ C(0, 3, 2, 1, "")
- , C(0, 6, 0, 7, "_")
- ])),
- ?assertEqual(<<"a ba\n",
- "_\nc\n",
- " h i\n">>, F(In, [ C(0, 3, 2, 1, "a\nb\nc\n")
- , C(1, 0, 1, 1, "_")
- ])),
- ok.
+ In = <<
+ "a b c\n"
+ "d e f\n"
+ "g h i\n"
+ >>,
+ C = fun(FromL, FromC, ToL, ToC, Str) ->
+ {#{from => {FromL, FromC}, to => {ToL, ToC}}, Str}
+ end,
+ F = fun els_text:apply_edits/2,
+ ?assertEqual(In, F(In, [])),
+ ?assertEqual(In, F(In, [C(0, 0, 0, 0, "")])),
+ ?assertEqual(<<"_ b c\n", "d e f\n", "g h i\n">>, F(In, [C(0, 0, 0, 1, "_")])),
+ ?assertEqual(<<"_a b c\n", "d e f\n", "g h i\n">>, F(In, [C(0, 0, 0, 0, "_")])),
+ ?assertEqual(<<"a b c\n", "_d e f\n", "g h i\n">>, F(In, [C(1, 0, 1, 0, "_")])),
+ ?assertEqual(<<"a b c\n", "d e f\n", "_g h i\n">>, F(In, [C(2, 0, 2, 0, "_")])),
+ ?assertEqual(<<"a b c\n", "d e f\n", "g h i_\n">>, F(In, [C(2, 5, 2, 5, "_")])),
+ ?assertEqual(<<"a b c\n", "d e f\n", "g h _\n">>, F(In, [C(2, 4, 2, 5, "_")])),
+ ?assertEqual(<<"a b c\n", "d e f\n", "g _ i\n">>, F(In, [C(2, 2, 2, 3, "_")])),
+ ?assertEqual(<<"a b c\n", "d e f\n", "g h i\n", "_">>, F(In, [C(3, 0, 3, 0, "_")])),
+ ?assertEqual(<<"_">>, F(In, [C(0, 0, 3, 0, "_")])),
+ ?assertEqual(
+ <<"a b _\n", "d e _\n", "g h _\n">>,
+ F(In, [
+ C(0, 4, 0, 5, "_"),
+ C(1, 4, 1, 5, "_"),
+ C(2, 4, 2, 5, "_")
+ ])
+ ),
+ ?assertEqual(<<"a b c\n", "g h i\n">>, F(In, [C(1, 0, 2, 0, "")])),
+ ?assertEqual(<<"a b h i\n">>, F(In, [C(0, 3, 2, 1, "")])),
+ ?assertEqual(
+ <<"a b _ i\n">>,
+ F(In, [
+ C(0, 3, 2, 1, ""),
+ C(0, 4, 0, 5, "_")
+ ])
+ ),
+ ?assertEqual(
+ <<"a b h _\n">>,
+ F(In, [
+ C(0, 3, 2, 1, ""),
+ C(0, 6, 0, 7, "_")
+ ])
+ ),
+ ?assertEqual(
+ <<"a ba\n", "_\nc\n", " h i\n">>,
+ F(In, [
+ C(0, 3, 2, 1, "a\nb\nc\n"),
+ C(1, 0, 1, 1, "_")
+ ])
+ ),
+ ok.
apply_edits_unicode(_Config) ->
- In = <<"二郎"/utf8>>,
- C = fun(FromC, ToC, Str) -> {#{from => {0, FromC}, to => {0, ToC}}, Str} end,
- F = fun els_text:apply_edits/2,
- ?assertEqual(<<"㇐郎"/utf8>>, F(In, [C(0, 1, "㇐")])),
- ?assertEqual(<<"二㇐郎"/utf8>>, F(In, [C(1, 1, "㇐")])),
- ?assertEqual(<<"二㇐"/utf8>>, F(In, [C(1, 2, "㇐")])),
- ?assertEqual(<<"二郎㇐"/utf8>>, F(In, [C(2, 2, "㇐")])),
- ?assertEqual(<<"二郎㇐"/utf8>>, F(In, [C(2, 2, "㇐")])),
- ?assertEqual(<<"㇐二郎"/utf8>>, F(In, [C(0, 0, "㇐")])),
- ok.
+ In = <<"二郎"/utf8>>,
+ C = fun(FromC, ToC, Str) -> {#{from => {0, FromC}, to => {0, ToC}}, Str} end,
+ F = fun els_text:apply_edits/2,
+ ?assertEqual(<<"㇐郎"/utf8>>, F(In, [C(0, 1, "㇐")])),
+ ?assertEqual(<<"二㇐郎"/utf8>>, F(In, [C(1, 1, "㇐")])),
+ ?assertEqual(<<"二㇐"/utf8>>, F(In, [C(1, 2, "㇐")])),
+ ?assertEqual(<<"二郎㇐"/utf8>>, F(In, [C(2, 2, "㇐")])),
+ ?assertEqual(<<"二郎㇐"/utf8>>, F(In, [C(2, 2, "㇐")])),
+ ?assertEqual(<<"㇐二郎"/utf8>>, F(In, [C(0, 0, "㇐")])),
+ ok.
diff --git a/apps/els_lsp/test/els_text_edit_SUITE.erl b/apps/els_lsp/test/els_text_edit_SUITE.erl
index 94dc5eec9..03321052f 100644
--- a/apps/els_lsp/test/els_text_edit_SUITE.erl
+++ b/apps/els_lsp/test/els_text_edit_SUITE.erl
@@ -3,17 +3,17 @@
-include("els_lsp.hrl").
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ text_edit_diff/1
- ]).
+-export([text_edit_diff/1]).
%%==============================================================================
%% Includes
@@ -31,47 +31,59 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(TestCase, Config) ->
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Testcases
%%==============================================================================
-spec text_edit_diff(config()) -> ok.
text_edit_diff(Config) ->
- DiagnosticsUri = ?config(diagnostics_uri, Config),
- DiagnosticsPath = els_uri:path(DiagnosticsUri),
- DiagnosticsDiffPath = ?config('diagnostics.new_path', Config),
- Result = els_text_edit:diff_files(DiagnosticsPath, DiagnosticsDiffPath),
- [Edit1, Edit2] = Result,
- ?assertEqual( #{newText =>
- <<"%% Changed diagnostics.erl, to test diff generation\n">>,
- range =>
- #{'end' => #{character => 0, line => 1},
- start => #{character => 0, line => 1}}}
- , Edit1),
- ?assertEqual( #{newText => <<"main(X) -> X + 1.\n">>,
- range =>
- #{'end' => #{character => 0, line => 8},
- start => #{character => 0, line => 6}}}
- , Edit2),
- ok.
+ DiagnosticsUri = ?config(diagnostics_uri, Config),
+ DiagnosticsPath = els_uri:path(DiagnosticsUri),
+ DiagnosticsDiffPath = ?config('diagnostics.new_path', Config),
+ Result = els_text_edit:diff_files(DiagnosticsPath, DiagnosticsDiffPath),
+ [Edit1, Edit2] = Result,
+ ?assertEqual(
+ #{
+ newText =>
+ <<"%% Changed diagnostics.erl, to test diff generation\n">>,
+ range =>
+ #{
+ 'end' => #{character => 0, line => 1},
+ start => #{character => 0, line => 1}
+ }
+ },
+ Edit1
+ ),
+ ?assertEqual(
+ #{
+ newText => <<"main(X) -> X + 1.\n">>,
+ range =>
+ #{
+ 'end' => #{character => 0, line => 8},
+ start => #{character => 0, line => 6}
+ }
+ },
+ Edit2
+ ),
+ ok.
diff --git a/apps/els_lsp/test/els_workspace_symbol_SUITE.erl b/apps/els_lsp/test/els_workspace_symbol_SUITE.erl
index e443446cc..fe4540f74 100644
--- a/apps/els_lsp/test/els_workspace_symbol_SUITE.erl
+++ b/apps/els_lsp/test/els_workspace_symbol_SUITE.erl
@@ -1,20 +1,21 @@
-module(els_workspace_symbol_SUITE).
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ query_multiple/1
- , query_single/1
- , query_none/1
- ]).
-
+-export([
+ query_multiple/1,
+ query_single/1,
+ query_none/1
+]).
-include("els_lsp.hrl").
@@ -34,115 +35,135 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
- els_test_utils:init_per_suite(Config).
+ els_test_utils:init_per_suite(Config).
-spec end_per_suite(config()) -> ok.
end_per_suite(Config) ->
- els_test_utils:end_per_suite(Config).
+ els_test_utils:end_per_suite(Config).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(TestCase, Config) ->
- els_test_utils:init_per_testcase(TestCase, Config).
+ els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(TestCase, Config) ->
- els_test_utils:end_per_testcase(TestCase, Config).
+ els_test_utils:end_per_testcase(TestCase, Config).
%%==============================================================================
%% Testcases
%%==============================================================================
-spec query_multiple(config()) -> ok.
query_multiple(Config) ->
- Query = <<"code_navigation">>,
- Uri = ?config(code_navigation_uri, Config),
- ExtraUri = ?config(code_navigation_extra_uri, Config),
- TypesUri = ?config(code_navigation_types_uri, Config),
- UndefUri = ?config(code_navigation_undefined_uri, Config),
- BrokenUri = ?config(code_navigation_broken_uri, Config),
- #{result := Result} = els_client:workspace_symbol(Query),
- Expected = [ #{ kind => ?SYMBOLKIND_MODULE
- , location =>
- #{ range =>
- #{ 'end' => #{character => 0, line => 0}
- , start => #{character => 0, line => 0}
- }
- , uri => Uri
- }
- , name => <<"code_navigation">>
- }
- , #{ kind => ?SYMBOLKIND_MODULE
- , location =>
- #{ range =>
- #{ 'end' => #{character => 0, line => 0}
- , start => #{character => 0, line => 0}
- }
- , uri => ExtraUri
- }
- , name => <<"code_navigation_extra">>
- }
- , #{ kind => ?SYMBOLKIND_MODULE
- , location =>
- #{ range =>
- #{ 'end' => #{character => 0, line => 0}
- , start => #{character => 0, line => 0}
- }
- , uri => TypesUri
- }
- , name => <<"code_navigation_types">>
- }
- , #{ kind => ?SYMBOLKIND_MODULE
- , location =>
- #{ range =>
- #{ 'end' => #{character => 0, line => 0}
- , start => #{character => 0, line => 0}
- }
- , uri => UndefUri
- }
- , name => <<"code_navigation_undefined">>
- }
- , #{ kind => ?SYMBOLKIND_MODULE
- , location =>
- #{ range =>
- #{ 'end' => #{character => 0, line => 0}
- , start => #{character => 0, line => 0}
- }
- , uri => BrokenUri
- }
- , name => <<"code_navigation_broken">>
- }
- ],
- ?assertEqual(lists:sort(Expected), lists:sort(Result)),
- ok.
+ Query = <<"code_navigation">>,
+ Uri = ?config(code_navigation_uri, Config),
+ ExtraUri = ?config(code_navigation_extra_uri, Config),
+ TypesUri = ?config(code_navigation_types_uri, Config),
+ UndefUri = ?config(code_navigation_undefined_uri, Config),
+ BrokenUri = ?config(code_navigation_broken_uri, Config),
+ #{result := Result} = els_client:workspace_symbol(Query),
+ Expected = [
+ #{
+ kind => ?SYMBOLKIND_MODULE,
+ location =>
+ #{
+ range =>
+ #{
+ 'end' => #{character => 0, line => 0},
+ start => #{character => 0, line => 0}
+ },
+ uri => Uri
+ },
+ name => <<"code_navigation">>
+ },
+ #{
+ kind => ?SYMBOLKIND_MODULE,
+ location =>
+ #{
+ range =>
+ #{
+ 'end' => #{character => 0, line => 0},
+ start => #{character => 0, line => 0}
+ },
+ uri => ExtraUri
+ },
+ name => <<"code_navigation_extra">>
+ },
+ #{
+ kind => ?SYMBOLKIND_MODULE,
+ location =>
+ #{
+ range =>
+ #{
+ 'end' => #{character => 0, line => 0},
+ start => #{character => 0, line => 0}
+ },
+ uri => TypesUri
+ },
+ name => <<"code_navigation_types">>
+ },
+ #{
+ kind => ?SYMBOLKIND_MODULE,
+ location =>
+ #{
+ range =>
+ #{
+ 'end' => #{character => 0, line => 0},
+ start => #{character => 0, line => 0}
+ },
+ uri => UndefUri
+ },
+ name => <<"code_navigation_undefined">>
+ },
+ #{
+ kind => ?SYMBOLKIND_MODULE,
+ location =>
+ #{
+ range =>
+ #{
+ 'end' => #{character => 0, line => 0},
+ start => #{character => 0, line => 0}
+ },
+ uri => BrokenUri
+ },
+ name => <<"code_navigation_broken">>
+ }
+ ],
+ ?assertEqual(lists:sort(Expected), lists:sort(Result)),
+ ok.
-spec query_single(config()) -> ok.
query_single(Config) ->
- Query = <<"extra">>,
- ExtraUri = ?config(code_navigation_extra_uri, Config),
- #{result := Result} = els_client:workspace_symbol(Query),
- Expected = [ #{ kind => ?SYMBOLKIND_MODULE
- , location =>
- #{ range =>
- #{ 'end' => #{character => 0, line => 0}
- , start => #{character => 0, line => 0}
- }
- , uri => ExtraUri
- }
- , name => <<"code_navigation_extra">>
- }
- ],
- ?assertEqual(lists:sort(Expected), lists:sort(Result)),
- ok.
+ Query = <<"extra">>,
+ ExtraUri = ?config(code_navigation_extra_uri, Config),
+ #{result := Result} = els_client:workspace_symbol(Query),
+ Expected = [
+ #{
+ kind => ?SYMBOLKIND_MODULE,
+ location =>
+ #{
+ range =>
+ #{
+ 'end' => #{character => 0, line => 0},
+ start => #{character => 0, line => 0}
+ },
+ uri => ExtraUri
+ },
+ name => <<"code_navigation_extra">>
+ }
+ ],
+ ?assertEqual(lists:sort(Expected), lists:sort(Result)),
+ ok.
-spec query_none(config()) -> ok.
query_none(_Config) ->
- #{result := Result} = els_client:workspace_symbol(<<"invalid_query">>),
- ?assertEqual([], Result),
- ok.
+ #{result := Result} = els_client:workspace_symbol(<<"invalid_query">>),
+ ?assertEqual([], Result),
+ ok.
diff --git a/apps/els_lsp/test/erlang_ls_SUITE.erl b/apps/els_lsp/test/erlang_ls_SUITE.erl
index c5ffdfbe0..ca5d027e8 100644
--- a/apps/els_lsp/test/erlang_ls_SUITE.erl
+++ b/apps/els_lsp/test/erlang_ls_SUITE.erl
@@ -3,18 +3,20 @@
-include("els_lsp.hrl").
%% CT Callbacks
--export([ suite/0
- , init_per_suite/1
- , end_per_suite/1
- , init_per_testcase/2
- , end_per_testcase/2
- , all/0
- ]).
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
%% Test cases
--export([ parse_args/1
- , log_root/1
- ]).
+-export([
+ parse_args/1,
+ log_root/1
+]).
%%==============================================================================
%% Includes
@@ -32,64 +34,67 @@
%%==============================================================================
-spec suite() -> [tuple()].
suite() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {seconds, 30}}].
-spec all() -> [atom()].
all() ->
- els_test_utils:all(?MODULE).
+ els_test_utils:all(?MODULE).
-spec init_per_suite(config()) -> config().
init_per_suite(_Config) ->
- [].
+ [].
-spec end_per_suite(config()) -> ok.
end_per_suite(_Config) ->
- ok.
+ ok.
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(_TestCase, _Config) ->
- [].
+ [].
-spec end_per_testcase(atom(), config()) -> ok.
end_per_testcase(_TestCase, _Config) ->
- unset_all_env(els_core),
- ok.
+ unset_all_env(els_core),
+ ok.
%%==============================================================================
%% Helpers
%%==============================================================================
-spec unset_all_env(atom()) -> ok.
unset_all_env(Application) ->
- Envs = application:get_all_env(Application),
- unset_env(Application, Envs).
+ Envs = application:get_all_env(Application),
+ unset_env(Application, Envs).
-spec unset_env(atom(), list({atom(), term()})) -> ok.
-unset_env(_Application, []) -> ok;
+unset_env(_Application, []) ->
+ ok;
unset_env(Application, [{Par, _Val} | Rest]) ->
- application:unset_env(Application, Par),
- unset_env(Application, Rest).
+ application:unset_env(Application, Par),
+ unset_env(Application, Rest).
%%==============================================================================
%% Testcases
%%==============================================================================
-spec parse_args(config()) -> ok.
parse_args(_Config) ->
- Args = [ "--log-dir", "/test"
- , "--log-level", "error"
- ],
- erlang_ls:parse_args(Args),
- ?assertEqual('error', application:get_env(els_core, log_level, undefined)),
- ok.
+ Args = [
+ "--log-dir",
+ "/test",
+ "--log-level",
+ "error"
+ ],
+ erlang_ls:parse_args(Args),
+ ?assertEqual('error', application:get_env(els_core, log_level, undefined)),
+ ok.
-spec log_root(config()) -> ok.
log_root(_Config) ->
- meck:new(file, [unstick]),
- meck:expect(file, get_cwd, fun() -> {ok, "/root/els_lsp"} end),
+ meck:new(file, [unstick]),
+ meck:expect(file, get_cwd, fun() -> {ok, "/root/els_lsp"} end),
- Args = [ "--log-dir", "/somewhere_else/logs"
- ],
- erlang_ls:parse_args(Args),
- ?assertEqual("/somewhere_else/logs/els_lsp", erlang_ls:log_root()),
+ Args = ["--log-dir", "/somewhere_else/logs"],
+ erlang_ls:parse_args(Args),
+ ?assertEqual("/somewhere_else/logs/els_lsp", erlang_ls:log_root()),
- meck:unload(file),
- ok.
+ meck:unload(file),
+ ok.
diff --git a/apps/els_lsp/test/prop_statem.erl b/apps/els_lsp/test/prop_statem.erl
index a245bca87..74a2a8ec3 100644
--- a/apps/els_lsp/test/prop_statem.erl
+++ b/apps/els_lsp/test/prop_statem.erl
@@ -26,22 +26,23 @@
%% Initial State
%%==============================================================================
initial_state() ->
- #{ connected => false
- , initialized => false
- , initialized_sent => false
- , shutdown => false
- , documents => []
- }.
+ #{
+ connected => false,
+ initialized => false,
+ initialized_sent => false,
+ shutdown => false,
+ documents => []
+ }.
%%==============================================================================
%% Weights
%%==============================================================================
-weight(_S, '$_cancelrequest') -> 1;
+weight(_S, '$_cancelrequest') -> 1;
weight(_S, '$_settracenotification') -> 1;
-weight(_S, '$_unexpectedrequest') -> 1;
-weight(_S, shutdown) -> 1;
-weight(_S, exit) -> 1;
-weight(_S, _Cmd) -> 5.
+weight(_S, '$_unexpectedrequest') -> 1;
+weight(_S, shutdown) -> 1;
+weight(_S, exit) -> 1;
+weight(_S, _Cmd) -> 5.
%%==============================================================================
%% Commands
@@ -51,350 +52,374 @@ weight(_S, _Cmd) -> 5.
%% Connect
%%------------------------------------------------------------------------------
connect() ->
- ClientIo = els_fake_stdio:start(),
- {ok, ServerIo} = application:get_env(els_core, io_device),
- els_fake_stdio:connect(ClientIo, ServerIo),
- els_fake_stdio:connect(ServerIo, ClientIo),
- els_client:start_link(#{io_device => ClientIo}).
+ ClientIo = els_fake_stdio:start(),
+ {ok, ServerIo} = application:get_env(els_core, io_device),
+ els_fake_stdio:connect(ClientIo, ServerIo),
+ els_fake_stdio:connect(ServerIo, ClientIo),
+ els_client:start_link(#{io_device => ClientIo}).
connect_args(_S) ->
- [].
+ [].
connect_pre(#{connected := Connected} = _S) ->
- not Connected.
+ not Connected.
connect_next(S, _R, _Args) ->
- S#{connected => true}.
+ S#{connected => true}.
connect_post(_S, _Args, Res) ->
- ?assertMatch({ok, _}, Res),
- true.
+ ?assertMatch({ok, _}, Res),
+ true.
%%------------------------------------------------------------------------------
%% Initialize
%%------------------------------------------------------------------------------
initialize(RootUri, InitOptions) ->
- els_client:initialize(RootUri, InitOptions).
+ els_client:initialize(RootUri, InitOptions).
initialize_args(_S) ->
- [ els_proper_gen:root_uri()
- , els_proper_gen:init_options()
- ].
+ [
+ els_proper_gen:root_uri(),
+ els_proper_gen:init_options()
+ ].
initialize_pre(#{connected := Connected} = _S) ->
- Connected.
+ Connected.
initialize_next(#{shutdown := true} = S, _R, _Args) ->
- S;
+ S;
initialize_next(S, _R, _Args) ->
- S#{initialized => true}.
+ S#{initialized => true}.
initialize_post(#{shutdown := true}, _Args, Res) ->
- assert_invalid_request(Res),
- true;
+ assert_invalid_request(Res),
+ true;
initialize_post(_S, _Args, Res) ->
- Expected = els_general_provider:server_capabilities(),
- ?assertEqual(Expected, maps:get(result, Res)),
- true.
+ Expected = els_general_provider:server_capabilities(),
+ ?assertEqual(Expected, maps:get(result, Res)),
+ true.
%%------------------------------------------------------------------------------
%% Initialized
%%------------------------------------------------------------------------------
initialized() ->
- els_client:initialized().
+ els_client:initialized().
initialized_args(_S) ->
- [].
+ [].
-initialized_pre(#{ connected := Connected
- , initialized := Initialized
- , initialized_sent := InitializedSent
- } = _S) ->
- Connected andalso Initialized andalso not InitializedSent.
+initialized_pre(
+ #{
+ connected := Connected,
+ initialized := Initialized,
+ initialized_sent := InitializedSent
+ } = _S
+) ->
+ Connected andalso Initialized andalso not InitializedSent.
initialized_next(#{shutdown := true} = S, _R, _Args) ->
- S;
+ S;
initialized_next(S, _R, _Args) ->
- S#{initialized_sent => true}.
+ S#{initialized_sent => true}.
initialized_post(_S, _Args, Res) ->
- ?assertEqual(ok, Res),
- true.
+ ?assertEqual(ok, Res),
+ true.
%%------------------------------------------------------------------------------
%% $/cancelRequest
%%------------------------------------------------------------------------------
'$_cancelrequest'(RequestId) ->
- els_client:'$_cancelrequest'(RequestId).
+ els_client:'$_cancelrequest'(RequestId).
'$_cancelrequest_args'(_S) ->
- [pos_integer()].
+ [pos_integer()].
'$_cancelrequest_pre'(#{connected := Connected} = _S) ->
- Connected.
+ Connected.
'$_cancelrequest_pre'(_S, [_Id]) ->
- true.
+ true.
'$_cancelrequest_next'(S, _R, [_Id]) ->
- S.
+ S.
'$_cancelrequest_post'(_S, _Args, Res) ->
- ?assertEqual(ok, Res),
- true.
+ ?assertEqual(ok, Res),
+ true.
%%------------------------------------------------------------------------------
%% $/setTraceNotification
%%------------------------------------------------------------------------------
'$_settracenotification'() ->
- els_client:'$_settracenotification'().
+ els_client:'$_settracenotification'().
'$_settracenotification_args'(_S) ->
- [].
+ [].
'$_settracenotification_pre'(#{connected := Connected} = _S) ->
- Connected.
+ Connected.
'$_settracenotification_pre'(_S, []) ->
- true.
+ true.
'$_settracenotification_next'(S, _R, []) ->
- S.
+ S.
'$_settracenotification_post'(_S, _Args, Res) ->
- ?assertEqual(ok, Res),
- true.
+ ?assertEqual(ok, Res),
+ true.
%%------------------------------------------------------------------------------
%% $/unexpectedRequest
%%------------------------------------------------------------------------------
'$_unexpectedrequest'() ->
- els_client:'$_unexpectedrequest'().
+ els_client:'$_unexpectedrequest'().
'$_unexpectedrequest_args'(_S) ->
- [].
+ [].
'$_unexpectedrequest_pre'(#{connected := Connected} = _S) ->
- Connected.
+ Connected.
'$_unexpectedrequest_pre'(_S, []) ->
- true.
+ true.
'$_unexpectedrequest_next'(S, _R, []) ->
- S.
+ S.
'$_unexpectedrequest_post'(_S, _Args, Res) ->
- assert_method_not_found(Res),
- true.
+ assert_method_not_found(Res),
+ true.
%%------------------------------------------------------------------------------
%% textDocument/didOpen
%%------------------------------------------------------------------------------
did_open(Uri, LanguageId, Version, Text) ->
- els_client:did_open(Uri, LanguageId, Version, Text).
+ els_client:did_open(Uri, LanguageId, Version, Text).
did_open_args(_S) ->
- [els_proper_gen:uri(), <<"erlang">>, 0, els_proper_gen:tokens()].
+ [els_proper_gen:uri(), <<"erlang">>, 0, els_proper_gen:tokens()].
-did_open_pre(#{ connected := Connected
- , initialized_sent := InitializedSent
- } = _S) ->
- Connected andalso InitializedSent.
+did_open_pre(
+ #{
+ connected := Connected,
+ initialized_sent := InitializedSent
+ } = _S
+) ->
+ Connected andalso InitializedSent.
did_open_next(#{documents := Documents0} = S, _R, [Uri, _, _, _]) ->
- file:write_file(els_uri:path(Uri), <<"dummy">>),
- S#{ documents => Documents0 ++ [Uri]}.
+ file:write_file(els_uri:path(Uri), <<"dummy">>),
+ S#{documents => Documents0 ++ [Uri]}.
did_open_post(_S, _Args, Res) ->
- ?assertEqual(ok, Res),
- true.
+ ?assertEqual(ok, Res),
+ true.
%%------------------------------------------------------------------------------
%% textDocument/didSave
%%------------------------------------------------------------------------------
did_save(Uri) ->
- els_client:did_save(Uri).
+ els_client:did_save(Uri).
did_save_args(_S) ->
- [els_proper_gen:uri()].
+ [els_proper_gen:uri()].
-did_save_pre(#{ connected := Connected
- , initialized_sent := InitializedSent
- } = _S) ->
- Connected andalso InitializedSent.
+did_save_pre(
+ #{
+ connected := Connected,
+ initialized_sent := InitializedSent
+ } = _S
+) ->
+ Connected andalso InitializedSent.
did_save_next(S, _R, [Uri]) ->
- file:write_file(els_uri:path(Uri), <<"dummy">>),
- S.
+ file:write_file(els_uri:path(Uri), <<"dummy">>),
+ S.
did_save_post(_S, _Args, Res) ->
- ?assertEqual(ok, Res),
- true.
+ ?assertEqual(ok, Res),
+ true.
%%------------------------------------------------------------------------------
%% textDocument/didClose
%%------------------------------------------------------------------------------
did_close(Uri) ->
- els_client:did_close(Uri).
+ els_client:did_close(Uri).
did_close_args(_S) ->
- [els_proper_gen:uri()].
+ [els_proper_gen:uri()].
-did_close_pre(#{ connected := Connected
- , initialized_sent := InitializedSent
- } = _S) ->
- Connected andalso InitializedSent.
+did_close_pre(
+ #{
+ connected := Connected,
+ initialized_sent := InitializedSent
+ } = _S
+) ->
+ Connected andalso InitializedSent.
did_close_next(S, _R, _Args) ->
- S.
+ S.
did_close_post(_S, _Args, Res) ->
- ?assertEqual(ok, Res),
- true.
+ ?assertEqual(ok, Res),
+ true.
%%------------------------------------------------------------------------------
%% Shutdown
%%------------------------------------------------------------------------------
shutdown() ->
- els_client:shutdown().
+ els_client:shutdown().
shutdown_args(_S) ->
- [].
+ [].
shutdown_pre(#{connected := Connected} = _S) ->
- Connected.
+ Connected.
shutdown_next(#{initialized := false} = S, _R, _Args) ->
- S;
+ S;
shutdown_next(S, _R, _Args) ->
- S#{shutdown => true}.
+ S#{shutdown => true}.
shutdown_post(#{shutdown := true}, _Args, Res) ->
- assert_invalid_request(Res),
- true;
+ assert_invalid_request(Res),
+ true;
shutdown_post(#{initialized := false}, _Args, Res) ->
- assert_server_not_initialized(Res),
- true;
+ assert_server_not_initialized(Res),
+ true;
shutdown_post(_S, _Args, Res) ->
- ?assertMatch(#{result := null}, Res),
- true.
+ ?assertMatch(#{result := null}, Res),
+ true.
%%------------------------------------------------------------------------------
%% Shutdown
%%------------------------------------------------------------------------------
exit() ->
- els_client:exit().
+ els_client:exit().
exit_args(_S) ->
- [].
+ [].
exit_pre(#{connected := Connected} = _S) ->
- Connected.
+ Connected.
exit_next(S, _R, _Args) ->
- %% We disconnect to simulate the server goes down
- catch disconnect(),
- S#{shutdown => false, connected => false, initialized => false}.
+ %% We disconnect to simulate the server goes down
+ catch disconnect(),
+ S#{shutdown => false, connected => false, initialized => false}.
exit_post(S, _Args, Res) ->
- ExpectedExitCode = case maps:get(shutdown, S, false) of
- true -> 0;
- false -> 1
- end,
- els_test_utils:wait_for(halt_called, 1000),
- ?assert(meck:called(els_utils, halt, [ExpectedExitCode])),
- ?assertMatch(ok, Res),
- true.
+ ExpectedExitCode =
+ case maps:get(shutdown, S, false) of
+ true -> 0;
+ false -> 1
+ end,
+ els_test_utils:wait_for(halt_called, 1000),
+ ?assert(meck:called(els_utils, halt, [ExpectedExitCode])),
+ ?assertMatch(ok, Res),
+ true.
%%------------------------------------------------------------------------------
%% Disconnect
%%------------------------------------------------------------------------------
disconnect() ->
- els_client:stop().
+ els_client:stop().
disconnect_args(_S) ->
- [].
+ [].
disconnect_pre(#{connected := Connected} = _S) ->
- Connected.
+ Connected.
disconnect_next(S, _R, _Args) ->
- S#{connected => false}.
+ S#{connected => false}.
disconnect_post(_S, _Args, Res) ->
- ?assertEqual(ok, Res),
- true.
+ ?assertEqual(ok, Res),
+ true.
%%==============================================================================
%% The statem's property
%%==============================================================================
prop_main() ->
- Config = #{ setup_fun => fun setup/0
- , teardown_fun => fun teardown/1
- , cleanup_fun => fun cleanup/0
- },
- proper_contrib_statem:run(?MODULE, Config).
+ Config = #{
+ setup_fun => fun setup/0,
+ teardown_fun => fun teardown/1,
+ cleanup_fun => fun cleanup/0
+ },
+ proper_contrib_statem:run(?MODULE, Config).
%%==============================================================================
%% Setup
%%==============================================================================
setup() ->
- meck:new(els_distribution_server, [no_link, passthrough]),
- meck:new(els_compiler_diagnostics, [no_link, passthrough]),
- meck:new(els_dialyzer_diagnostics, [no_link, passthrough]),
- meck:new(els_elvis_diagnostics, [no_link, passthrough]),
- meck:new(els_utils, [no_link, passthrough]),
- meck:expect(els_distribution_server, start_distribution, 1, ok),
- meck:expect(els_distribution_server, connect, 0, ok),
- meck:expect(els_compiler_diagnostics, run, 1, []),
- meck:expect(els_dialyzer_diagnostics, run, 1, []),
- meck:expect(els_elvis_diagnostics, run, 1, []),
- Self = erlang:self(),
- HaltFun = fun(_X) -> Self ! halt_called, ok end,
- meck:expect(els_utils, halt, HaltFun),
- ServerIo = els_fake_stdio:start(),
- ok = application:set_env(els_core, io_device, ServerIo),
- application:ensure_all_started(els_lsp),
- ConfigFile = filename:join([els_utils:system_tmp_dir(), "erlang_ls.config"]),
- file:write_file(ConfigFile, <<"">>),
- ok.
+ meck:new(els_distribution_server, [no_link, passthrough]),
+ meck:new(els_compiler_diagnostics, [no_link, passthrough]),
+ meck:new(els_dialyzer_diagnostics, [no_link, passthrough]),
+ meck:new(els_elvis_diagnostics, [no_link, passthrough]),
+ meck:new(els_utils, [no_link, passthrough]),
+ meck:expect(els_distribution_server, start_distribution, 1, ok),
+ meck:expect(els_distribution_server, connect, 0, ok),
+ meck:expect(els_compiler_diagnostics, run, 1, []),
+ meck:expect(els_dialyzer_diagnostics, run, 1, []),
+ meck:expect(els_elvis_diagnostics, run, 1, []),
+ Self = erlang:self(),
+ HaltFun = fun(_X) ->
+ Self ! halt_called,
+ ok
+ end,
+ meck:expect(els_utils, halt, HaltFun),
+ ServerIo = els_fake_stdio:start(),
+ ok = application:set_env(els_core, io_device, ServerIo),
+ application:ensure_all_started(els_lsp),
+ ConfigFile = filename:join([els_utils:system_tmp_dir(), "erlang_ls.config"]),
+ file:write_file(ConfigFile, <<"">>),
+ ok.
%%==============================================================================
%% Teardown
%%==============================================================================
teardown(_) ->
- meck:unload(els_distribution_server),
- meck:unload(els_compiler_diagnostics),
- meck:unload(els_dialyzer_diagnostics),
- meck:unload(els_elvis_diagnostics),
- meck:unload(els_utils),
- ok.
+ meck:unload(els_distribution_server),
+ meck:unload(els_compiler_diagnostics),
+ meck:unload(els_dialyzer_diagnostics),
+ meck:unload(els_elvis_diagnostics),
+ meck:unload(els_utils),
+ ok.
%%==============================================================================
%% Cleanup
%%==============================================================================
cleanup() ->
- catch disconnect(),
- %% Restart the server, since though the client disconnects the
- %% server keeps its state.
- els_server:reset_internal_state(),
- ok.
+ catch disconnect(),
+ %% Restart the server, since though the client disconnects the
+ %% server keeps its state.
+ els_server:reset_internal_state(),
+ ok.
%%==============================================================================
%% Helper functions
%%==============================================================================
assert_invalid_request(Res) ->
- ?assertMatch( #{error := #{code := ?ERR_INVALID_REQUEST, message := _}}
- , Res).
+ ?assertMatch(
+ #{error := #{code := ?ERR_INVALID_REQUEST, message := _}},
+ Res
+ ).
assert_server_not_initialized(Res) ->
- ?assertMatch( #{error := #{code := ?ERR_SERVER_NOT_INITIALIZED, message := _}}
- , Res).
+ ?assertMatch(
+ #{error := #{code := ?ERR_SERVER_NOT_INITIALIZED, message := _}},
+ Res
+ ).
assert_method_not_found(Res) ->
- ?assertMatch( #{error := #{code := ?ERR_METHOD_NOT_FOUND, message := _}}
- , Res).
+ ?assertMatch(
+ #{error := #{code := ?ERR_METHOD_NOT_FOUND, message := _}},
+ Res
+ ).
meck_matcher_integer(N) ->
- meck_matcher:new(fun(X) -> X =:= N end).
+ meck_matcher:new(fun(X) -> X =:= N end).
diff --git a/elvis.config b/elvis.config
index 7a5060a1d..1fde3b598 100644
--- a/elvis.config
+++ b/elvis.config
@@ -1,90 +1,111 @@
-[{ elvis
- , [
- { config,
- [ #{ dirs => [ "apps/els_core/src"
- , "apps/els_core/test"
- , "apps/els_dap/src"
- , "apps/els_dap/test"
- , "apps/els_lsp/src"
- , "apps/els_lsp/test"
- ]
- , filter => "*.erl"
- , ruleset => erl_files
- , rules => [ {elvis_style, god_modules, #{ignore => [ els_client
- , els_completion_SUITE
- , els_dap_general_provider_SUITE
- , els_definition_SUITE
- , els_diagnostics_SUITE
- , els_document_highlight_SUITE
- , els_hover_SUITE
- , els_parser_SUITE
- , els_references_SUITE
- , els_methods
- ]}}
- , {elvis_style, dont_repeat_yourself, #{ ignore => [ els_diagnostics_SUITE
- , els_references_SUITE
- , els_dap_general_provider_SUITE
- ]
- , min_complexity => 20}}
- , {elvis_style, invalid_dynamic_call, #{ignore => [ els_compiler_diagnostics
- , els_server
- , els_dap_server
- , els_app
- , els_stdio
- , els_tcp
- , els_dap_test_utils
- , els_test_utils
- , edoc_report
- ]}}
- , {elvis_text_style, line_length, #{limit => 80, skip_comments => false}}
- , {elvis_style, operator_spaces, #{ rules => [ {right, ","}
- , {left , "-"}
- , {right, "+"}
- , {left , "+"}
- , {right, "*"}
- , {left , "*"}
- , {right, "--"}
- , {left , "--"}
- , {right, "++"}
- , {left , "++"}
- , {right, "->"}
- , {left , "->"}
- , {right, "=>"}
- , {left , "=>"}
- , {right, "<-"}
- , {left , "<-"}
- , {right, "<="}
- , {left , "<="}
- , {right, "||"}
- , {left , "||"}
- , {right, "!"}
- , {left , "!"}
- , {right, "=:="}
- , {left , "=:="}
- , {right, "=/="}
- , {left , "=/="}
- ]}}
- , {elvis_style, function_naming_convention, #{ignore => [ els_client
- , prop_statem
- ]}}
- , {elvis_style, no_debug_call, #{ignore => [erlang_ls, els_dap]}}
- , {elvis_style, atom_naming_convention, disable}
- , {elvis_style, state_record_and_type, disable}
- ]
- , ignore => [els_dodger, els_typer, els_erlfmt_ast, els_eep48_docs]
- }
- , #{ dirs => ["."]
- , filter => "Makefile"
- , ruleset => makefiles
- }
- , #{ dirs => ["."]
- , filter => "rebar.config"
- , ruleset => rebar_config
- }
- , #{ dirs => ["."]
- , filter => "elvis.config"
- , ruleset => elvis_config
- }
- ]}
- ]}
+[
+ {elvis, [
+ {config, [
+ #{
+ dirs => [
+ "apps/els_core/src",
+ "apps/els_core/test",
+ "apps/els_dap/src",
+ "apps/els_dap/test",
+ "apps/els_lsp/src",
+ "apps/els_lsp/test"
+ ],
+ filter => "*.erl",
+ ruleset => erl_files,
+ rules => [
+ {elvis_style, god_modules, #{
+ ignore => [
+ els_client,
+ els_completion_SUITE,
+ els_dap_general_provider_SUITE,
+ els_definition_SUITE,
+ els_diagnostics_SUITE,
+ els_document_highlight_SUITE,
+ els_hover_SUITE,
+ els_parser_SUITE,
+ els_references_SUITE,
+ els_methods
+ ]
+ }},
+ {elvis_style, dont_repeat_yourself, #{
+ ignore => [
+ els_diagnostics_SUITE,
+ els_references_SUITE,
+ els_dap_general_provider_SUITE
+ ],
+ min_complexity => 20
+ }},
+ {elvis_style, invalid_dynamic_call, #{
+ ignore => [
+ els_compiler_diagnostics,
+ els_server,
+ els_dap_server,
+ els_app,
+ els_stdio,
+ els_tcp,
+ els_dap_test_utils,
+ els_test_utils,
+ edoc_report
+ ]
+ }},
+ {elvis_text_style, line_length, #{limit => 100, skip_comments => false}},
+ {elvis_style, operator_spaces, #{
+ rules => [
+ {right, ","},
+ {left, "-"},
+ {right, "+"},
+ {left, "+"},
+ {right, "*"},
+ {left, "*"},
+ {right, "--"},
+ {left, "--"},
+ {right, "++"},
+ {left, "++"},
+ {right, "->"},
+ {left, "->"},
+ {right, "=>"},
+ {left, "=>"},
+ {right, "<-"},
+ {left, "<-"},
+ {right, "<="},
+ {left, "<="},
+ {right, "||"},
+ {left, "||"},
+ {right, "!"},
+ {left, "!"},
+ {right, "=:="},
+ {left, "=:="},
+ {right, "=/="},
+ {left, "=/="}
+ ]
+ }},
+ {elvis_style, function_naming_convention, #{
+ ignore => [
+ els_client,
+ prop_statem
+ ]
+ }},
+ {elvis_style, no_debug_call, #{ignore => [erlang_ls, els_dap]}},
+ {elvis_style, atom_naming_convention, disable},
+ {elvis_style, state_record_and_type, disable}
+ ],
+ ignore => [els_dodger, els_typer, els_erlfmt_ast, els_eep48_docs]
+ },
+ #{
+ dirs => ["."],
+ filter => "Makefile",
+ ruleset => makefiles
+ },
+ #{
+ dirs => ["."],
+ filter => "rebar.config",
+ ruleset => rebar_config
+ },
+ #{
+ dirs => ["."],
+ filter => "elvis.config",
+ ruleset => elvis_config
+ }
+ ]}
+ ]}
].
diff --git a/rebar.config b/rebar.config
index 702615e1c..eb9cd5fc4 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,89 +1,100 @@
-{erl_opts, [ debug_info
- , warnings_as_errors
- , warn_export_vars
- , warn_unused_import
- , warn_missing_spec_all
- ]
-}.
+{erl_opts, [
+ debug_info,
+ warnings_as_errors,
+ warn_export_vars,
+ warn_unused_import,
+ warn_missing_spec_all
+]}.
-{deps, [ {jsx, "3.0.0"}
- , {redbug, "2.0.6"}
- , {yamerl, {git, "https://github.com/erlang-ls/yamerl.git", {ref, "9a9f7a2e84554992f2e8e08a8060bfe97776a5b7"}}}
- , {docsh, "0.7.2"}
- , {elvis_core, "~> 1.3"}
- , {rebar3_format, "0.8.2"}
- %%, {erlfmt, "1.0.0"}
- , {erlfmt, {git, "https://github.com/gomoripeti/erlfmt.git", {tag, "erlang_ls_parser_error_loc"}}} %% Temp until erlfmt PR 325 is merged (commit d4422d1)
- , {ephemeral, "2.0.4"}
- , {tdiff, "0.1.2"}
- , {uuid, "2.0.1", {pkg, uuid_erl}}
- , {gradualizer, {git, "https://github.com/josefs/Gradualizer.git", {ref, "6e89b4e"}}}
- ]
-}.
+{deps, [
+ {jsx, "3.0.0"},
+ {redbug, "2.0.6"},
+ {yamerl,
+ {git, "https://github.com/erlang-ls/yamerl.git",
+ {ref, "9a9f7a2e84554992f2e8e08a8060bfe97776a5b7"}}},
+ {docsh, "0.7.2"},
+ {elvis_core, "~> 1.3"},
+ {rebar3_format, "0.8.2"},
+ %%, {erlfmt, "1.0.0"}
-{shell, [ {apps, [els_lsp]} ]}.
+ %% Temp until erlfmt PR 325 is merged (commit d4422d1)
+ {erlfmt,
+ {git, "https://github.com/gomoripeti/erlfmt.git", {tag, "erlang_ls_parser_error_loc"}}},
+ {ephemeral, "2.0.4"},
+ {tdiff, "0.1.2"},
+ {uuid, "2.0.1", {pkg, uuid_erl}},
+ {gradualizer, {git, "https://github.com/josefs/Gradualizer.git", {ref, "6e89b4e"}}}
+]}.
-{plugins, [ rebar3_proper
- , coveralls
- , rebar3_lint
- , {rebar3_bsp, {git, "https://github.com/erlang-ls/rebar3_bsp.git", {ref, "master"}}}
- ]
-}.
+{shell, [{apps, [els_lsp]}]}.
+
+{plugins, [
+ rebar3_proper,
+ coveralls,
+ rebar3_lint,
+ {rebar3_bsp, {git, "https://github.com/erlang-ls/rebar3_bsp.git", {ref, "master"}}}
+]}.
+
+{project_plugins, [erlfmt]}.
{minimum_otp_vsn, "22.0"}.
-{escript_emu_args, "%%! -connect_all false\n" }.
+{escript_emu_args, "%%! -connect_all false\n"}.
{escript_incl_extra, [{"els_lsp/priv/snippets/*", "_build/default/lib/"}]}.
{escript_main_app, els_lsp}.
{escript_name, erlang_ls}.
%% Keeping the debug profile as an alias, since many clients were
%% relying on it when starting the server.
-{profiles, [ { debug, [] }
- , { dap, [ {escript_name, els_dap}
- , {escript_main_app, els_dap}
- ]
- }
- , { test
- , [ { erl_opts, [ nowarn_export_all
- , nowarn_missing_spec_all
- ]
- }
- , { deps
- , [ {meck, "0.9.0"}
- , {proper, "1.3.0"}
- , {proper_contrib, "0.2.0"}
- , {coveralls, "2.2.0"}
- ]
- }
- ]
- }
- ]
-}.
+{profiles, [
+ {debug, []},
+ {dap, [
+ {escript_name, els_dap},
+ {escript_main_app, els_dap}
+ ]},
+ {test, [
+ {erl_opts, [
+ nowarn_export_all,
+ nowarn_missing_spec_all
+ ]},
+ {deps, [
+ {meck, "0.9.0"},
+ {proper, "1.3.0"},
+ {proper_contrib, "0.2.0"},
+ {coveralls, "2.2.0"}
+ ]}
+ ]}
+]}.
{cover_enabled, true}.
{cover_export_enabled, true}.
{coveralls_coverdata, ["_build/test/cover/ct.coverdata", "_build/test/cover/proper.coverdata"]}.
{coveralls_service_name, "github"}.
-{dialyzer, [ {warnings, [unknown]}
- , {plt_apps, all_deps}
- %% Depending on the OTP version, erl_types (used by
- %% els_typer), is either part of hipe or dialyzer.
- , {plt_extra_apps, [dialyzer, hipe, mnesia, common_test, debugger]}
- ]}.
+{dialyzer, [
+ {warnings, [unknown]},
+ {plt_apps, all_deps},
+ %% Depending on the OTP version, erl_types (used by
+ %% els_typer), is either part of hipe or dialyzer.
+ {plt_extra_apps, [dialyzer, hipe, mnesia, common_test, debugger]}
+]}.
{edoc_opts, [{preprocess, true}]}.
-{xref_checks, [ undefined_function_calls
- , undefined_functions
- , locals_not_used
- , deprecated_function_calls
- , deprecated_functions
- ]}.
+{xref_checks, [
+ undefined_function_calls,
+ undefined_functions,
+ locals_not_used,
+ deprecated_function_calls,
+ deprecated_functions
+]}.
%% Set xref ignores for functions introduced in OTP 23
{xref_ignores, [{code, get_doc, 1}, {shell_docs, render, 4}]}.
%% Disable warning_as_errors for redbug to avoid deprecation warnings.
-{overrides, [ {del, redbug, [{erl_opts, [warnings_as_errors]}]}
- ]}.
+{overrides, [{del, redbug, [{erl_opts, [warnings_as_errors]}]}]}.
+
+{erlfmt, [
+ write,
+ {files, ["apps/*/{src,include,test}/*.{erl,hrl,app.src}", "rebar.config", "elvis.config"]}
+]}.
From 2dfb48aca3879e5b44f6fd676f8349525262779f Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Fri, 13 May 2022 10:03:16 +0200
Subject: [PATCH 072/239] Do not read file from disk on didOpen (#1298)
---
apps/els_lsp/src/els_dt_document.erl | 6 +++++-
apps/els_lsp/src/els_text_synchronization.erl | 7 +++----
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/apps/els_lsp/src/els_dt_document.erl b/apps/els_lsp/src/els_dt_document.erl
index 42d7844db..56ddacfc2 100644
--- a/apps/els_lsp/src/els_dt_document.erl
+++ b/apps/els_lsp/src/els_dt_document.erl
@@ -27,6 +27,7 @@
-export([
new/3,
+ new/4,
pois/1,
pois/2,
get_element_at_pos/3,
@@ -168,9 +169,12 @@ delete(Uri) ->
-spec new(uri(), binary(), source()) -> item().
new(Uri, Text, Source) ->
+ new(Uri, Text, Source, _Version = null).
+
+-spec new(uri(), binary(), source(), version()) -> item().
+new(Uri, Text, Source, Version) ->
Extension = filename:extension(Uri),
Id = binary_to_atom(filename:basename(Uri, Extension), utf8),
- Version = null,
case Extension of
<<".erl">> ->
new(Uri, Text, Id, module, Source, Version);
diff --git a/apps/els_lsp/src/els_text_synchronization.erl b/apps/els_lsp/src/els_text_synchronization.erl
index f9d3e23a3..5c1659e10 100644
--- a/apps/els_lsp/src/els_text_synchronization.erl
+++ b/apps/els_lsp/src/els_text_synchronization.erl
@@ -53,10 +53,9 @@ did_open(Params) ->
<<"version">> := Version
}
} = Params,
- {ok, Document} = els_utils:lookup_document(Uri),
- NewDocument = Document#{text => Text, version => Version},
- els_dt_document:insert(NewDocument),
- els_indexing:deep_index(NewDocument),
+ Document = els_dt_document:new(Uri, Text, _Source = app, Version),
+ els_dt_document:insert(Document),
+ els_indexing:deep_index(Document),
ok.
-spec did_save(map()) -> ok.
From 64f58c92bec2e77f53b371dc4134eea55622575f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Mon, 16 May 2022 18:49:30 +0200
Subject: [PATCH 073/239] Add support for completion of predefined macros
(#1302)
---
apps/els_lsp/src/els_completion_provider.erl | 28 +++++++++++++++++---
1 file changed, 25 insertions(+), 3 deletions(-)
diff --git a/apps/els_lsp/src/els_completion_provider.erl b/apps/els_lsp/src/els_completion_provider.erl
index cda5fe921..808f65f42 100644
--- a/apps/els_lsp/src/els_completion_provider.erl
+++ b/apps/els_lsp/src/els_completion_provider.erl
@@ -143,7 +143,7 @@ find_completions(
?COMPLETION_TRIGGER_KIND_CHARACTER,
#{trigger := <<"?">>, document := Document}
) ->
- definitions(Document, define);
+ bifs(define, _ExportFormat = false) ++ definitions(Document, define);
find_completions(
_Prefix,
?COMPLETION_TRIGGER_KIND_CHARACTER,
@@ -205,10 +205,10 @@ find_completions(
exported_definitions(Module, TypeOrFun, ExportFormat);
%% Check for "[...] ?"
[{'?', _} | _] ->
- definitions(Document, define);
+ bifs(define, _ExportFormat = false) ++ definitions(Document, define);
%% Check for "[...] ?anything"
[_, {'?', _} | _] ->
- definitions(Document, define);
+ bifs(define, _ExportFormat = false) ++ definitions(Document, define);
%% Check for "[...] #anything."
[{'.', _}, {atom, _, RecordName}, {'#', _} | _] ->
record_fields(Document, RecordName);
@@ -801,6 +801,28 @@ bifs(type_definition, false = ExportFormat) ->
}
|| {_, A} = X <- Types
],
+ [completion_item(X, ExportFormat) || X <- POIs];
+bifs(define, ExportFormat) ->
+ Macros = [
+ 'MODULE',
+ 'MODULE_STRING',
+ 'FILE',
+ 'LINE',
+ 'MACHINE',
+ 'FUNCTION_NAME',
+ 'FUNCTION_ARITY',
+ 'OTP_RELEASE'
+ ],
+ Range = #{from => {0, 0}, to => {0, 0}},
+ POIs = [
+ #{
+ kind => define,
+ id => Id,
+ range => Range,
+ data => #{args => none}
+ }
+ || Id <- Macros
+ ],
[completion_item(X, ExportFormat) || X <- POIs].
-spec generate_arguments(string(), integer()) -> [{integer(), string()}].
From d19557a4018cde392006202be8407e4a983d5237 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Wed, 18 May 2022 14:17:44 -0700
Subject: [PATCH 074/239] Cleanup Points of Interest (#1303)
* Move poi/0 definition from header to module
* Introduce poi behaviour, implement label function for some of the pois
* Move symbol transformation functions to els_poi
* Move folding_range to els_poi
* Remove remains from folding_range poi_kind
---
apps/els_core/include/els_core.hrl | 43 -----
apps/els_core/src/els_poi.erl | 159 ++++++++++++++++++
apps/els_core/src/els_protocol.erl | 2 +-
apps/els_core/src/els_text.erl | 4 +-
apps/els_dap/src/els_dap_protocol.erl | 2 +-
.../els_bound_var_in_pattern_diagnostics.erl | 18 +-
apps/els_lsp/src/els_call_hierarchy_item.erl | 4 +-
.../src/els_call_hierarchy_provider.erl | 8 +-
apps/els_lsp/src/els_code_actions.erl | 3 +-
apps/els_lsp/src/els_code_lens.erl | 6 +-
.../els_lsp/src/els_code_lens_ct_run_test.erl | 4 +-
.../src/els_code_lens_function_references.erl | 8 +-
.../els_lsp/src/els_code_lens_server_info.erl | 6 +-
.../els_code_lens_show_behaviour_usages.erl | 8 +-
.../src/els_code_lens_suggest_spec.erl | 7 +-
apps/els_lsp/src/els_code_navigation.erl | 24 +--
apps/els_lsp/src/els_compiler_diagnostics.erl | 10 +-
apps/els_lsp/src/els_completion_provider.erl | 32 ++--
apps/els_lsp/src/els_crossref_diagnostics.erl | 4 +-
apps/els_lsp/src/els_definition_provider.erl | 14 +-
apps/els_lsp/src/els_diagnostics_utils.erl | 9 +-
apps/els_lsp/src/els_docs.erl | 4 +-
.../src/els_document_highlight_provider.erl | 17 +-
.../src/els_document_symbol_provider.erl | 32 +---
apps/els_lsp/src/els_dt_document.erl | 18 +-
apps/els_lsp/src/els_dt_references.erl | 14 +-
.../src/els_folding_range_provider.erl | 8 +-
apps/els_lsp/src/els_hover_provider.erl | 2 +-
.../src/els_implementation_provider.erl | 4 +-
apps/els_lsp/src/els_incomplete_parser.erl | 5 +-
apps/els_lsp/src/els_indexing.erl | 8 +-
apps/els_lsp/src/els_parser.erl | 63 +++----
apps/els_lsp/src/els_poi.erl | 67 --------
apps/els_lsp/src/els_poi_define.erl | 19 +++
apps/els_lsp/src/els_poi_function.erl | 17 ++
apps/els_lsp/src/els_poi_record.erl | 17 ++
apps/els_lsp/src/els_poi_type_definition.erl | 17 ++
apps/els_lsp/src/els_range.erl | 16 +-
apps/els_lsp/src/els_references_provider.erl | 12 +-
apps/els_lsp/src/els_rename_provider.erl | 18 +-
apps/els_lsp/src/els_scope.erl | 24 +--
.../src/els_unused_includes_diagnostics.erl | 6 +-
.../src/els_unused_macros_diagnostics.erl | 4 +-
.../els_unused_record_fields_diagnostics.erl | 4 +-
apps/els_lsp/test/els_parser_SUITE.erl | 4 +-
apps/els_lsp/test/els_parser_macros_SUITE.erl | 4 +-
46 files changed, 428 insertions(+), 351 deletions(-)
create mode 100644 apps/els_core/src/els_poi.erl
delete mode 100644 apps/els_lsp/src/els_poi.erl
create mode 100644 apps/els_lsp/src/els_poi_define.erl
create mode 100644 apps/els_lsp/src/els_poi_function.erl
create mode 100644 apps/els_lsp/src/els_poi_record.erl
create mode 100644 apps/els_lsp/src/els_poi_type_definition.erl
diff --git a/apps/els_core/include/els_core.hrl b/apps/els_core/include/els_core.hrl
index 9fed5171e..eed047393 100644
--- a/apps/els_core/include/els_core.hrl
+++ b/apps/els_core/include/els_core.hrl
@@ -615,49 +615,6 @@
%%------------------------------------------------------------------------------
-type pos() :: {integer(), integer()}.
-type uri() :: binary().
--type poi_kind() ::
- application
- | atom
- | behaviour
- | callback
- | define
- | export
- | export_entry
- | export_type
- | export_type_entry
- | folding_range
- | function
- | function_clause
- | implicit_fun
- | import_entry
- | include
- | include_lib
- | macro
- | module
- | parse_transform
- | record
- | record_def_field
- | record_expr
- | record_field
- | spec
- | type_application
- | type_definition
- | variable.
--type poi_range() :: #{from := pos(), to := pos()}.
--type poi_id() ::
- atom()
- %% record_def_field, record_field
- | {atom(), atom()}
- %% include, include_lib
- | string()
- | {atom(), arity()}
- | {module(), atom(), arity()}.
--type poi() :: #{
- kind := poi_kind(),
- id := poi_id(),
- data := any(),
- range := poi_range()
-}.
-type tree() :: erl_syntax:syntaxTree().
-endif.
diff --git a/apps/els_core/src/els_poi.erl b/apps/els_core/src/els_poi.erl
new file mode 100644
index 000000000..dd64a537f
--- /dev/null
+++ b/apps/els_core/src/els_poi.erl
@@ -0,0 +1,159 @@
+%%==============================================================================
+%% The Point Of Interest (a.k.a. _poi_) Data Structure
+%%==============================================================================
+-module(els_poi).
+
+%% Constructor
+-export([
+ new/3,
+ new/4
+]).
+
+-export([
+ match_pos/2,
+ sort/1,
+ label/1,
+ symbol_kind/1,
+ to_symbol/2,
+ folding_range/1
+]).
+
+%%==============================================================================
+%% Includes
+%%==============================================================================
+-include("els_core.hrl").
+
+%%==============================================================================
+%% Type Definitions
+%%==============================================================================
+-type poi_kind() ::
+ application
+ | atom
+ | behaviour
+ | callback
+ | define
+ | export
+ | export_entry
+ | export_type
+ | export_type_entry
+ | function
+ | function_clause
+ | implicit_fun
+ | import_entry
+ | include
+ | include_lib
+ | macro
+ | module
+ | parse_transform
+ | record
+ | record_def_field
+ | record_expr
+ | record_field
+ | spec
+ | type_application
+ | type_definition
+ | variable.
+-type poi_range() :: #{from := pos(), to := pos()}.
+-type poi_id() ::
+ atom()
+ %% record_def_field, record_field
+ | {atom(), atom()}
+ %% include, include_lib
+ | string()
+ | {atom(), arity()}
+ | {module(), atom(), arity()}.
+-type poi_data() :: any().
+-type poi() :: #{
+ kind := poi_kind(),
+ id := poi_id(),
+ data := poi_data(),
+ range := poi_range()
+}.
+-export_type([poi/0, poi_range/0, poi_id/0, poi_kind/0]).
+
+%%==============================================================================
+%% Behaviour Definition
+%%==============================================================================
+-callback label(poi()) -> binary().
+-callback symbol_kind() -> symbol_kind().
+
+%%==============================================================================
+%% API
+%%==============================================================================
+
+%% @doc Constructor for a Point of Interest.
+-spec new(poi_range(), poi_kind(), any()) -> poi().
+new(Range, Kind, Id) ->
+ new(Range, Kind, Id, undefined).
+
+%% @doc Constructor for a Point of Interest.
+-spec new(poi_range(), poi_kind(), any(), any()) -> poi().
+new(Range, Kind, Id, Data) ->
+ #{
+ kind => Kind,
+ id => Id,
+ data => Data,
+ range => Range
+ }.
+
+-spec match_pos([poi()], pos()) -> [poi()].
+match_pos(POIs, Pos) ->
+ [
+ POI
+ || #{
+ range := #{
+ from := From,
+ to := To
+ }
+ } = POI <- POIs,
+ (From =< Pos) andalso (Pos =< To)
+ ].
+
+%% @doc Sorts pois based on their range
+%%
+%% Order is defined using els_range:compare/2.
+-spec sort([poi()]) -> [poi()].
+sort(POIs) ->
+ lists:sort(fun compare/2, POIs).
+
+-spec label(els_poi:poi()) -> binary().
+label(#{kind := Kind} = POI) ->
+ (callback_module(Kind)):label(POI).
+
+-spec symbol_kind(els_poi:poi()) -> symbol_kind().
+symbol_kind(#{kind := Kind}) ->
+ (callback_module(Kind)):symbol_kind().
+
+-spec to_symbol(uri(), els_poi:poi()) -> symbol_information().
+to_symbol(Uri, POI) ->
+ #{range := Range} = POI,
+ #{
+ name => label(POI),
+ kind => symbol_kind(POI),
+ location => #{
+ uri => Uri,
+ range => els_protocol:range(Range)
+ }
+ }.
+
+-spec folding_range(els_poi:poi()) -> poi_range().
+folding_range(#{data := #{folding_range := Range}}) ->
+ Range.
+
+%%==============================================================================
+%% Internal Functions
+%%==============================================================================
+
+-spec compare(poi(), poi()) -> boolean().
+compare(#{range := A}, #{range := B}) ->
+ els_range:compare(A, B).
+
+-spec callback_module(poi_kind()) -> atom().
+callback_module(define) ->
+ els_poi_define;
+callback_module(function) ->
+ els_poi_function;
+callback_module(record) ->
+ els_poi_record;
+callback_module(type_definition) ->
+ els_poi_type_definition.
diff --git a/apps/els_core/src/els_protocol.erl b/apps/els_core/src/els_protocol.erl
index 4491da46e..cad7f3634 100644
--- a/apps/els_core/src/els_protocol.erl
+++ b/apps/els_core/src/els_protocol.erl
@@ -78,7 +78,7 @@ error(RequestId, Error) ->
%%==============================================================================
%% Data Structures
%%==============================================================================
--spec range(poi_range()) -> range().
+-spec range(els_poi:poi_range()) -> range().
range(#{from := {FromL, FromC}, to := {ToL, ToC}}) ->
#{
start => #{line => FromL - 1, character => FromC - 1},
diff --git a/apps/els_core/src/els_text.erl b/apps/els_core/src/els_text.erl
index f4299fe99..0d30da9fd 100644
--- a/apps/els_core/src/els_text.erl
+++ b/apps/els_core/src/els_text.erl
@@ -15,9 +15,7 @@
-export_type([edit/0]).
--include("els_core.hrl").
-
--type edit() :: {poi_range(), string()}.
+-type edit() :: {els_poi:poi_range(), string()}.
-type lines() :: [string() | binary()].
-type text() :: binary().
-type line_num() :: non_neg_integer().
diff --git a/apps/els_dap/src/els_dap_protocol.erl b/apps/els_dap/src/els_dap_protocol.erl
index 096190090..1259974cc 100644
--- a/apps/els_dap/src/els_dap_protocol.erl
+++ b/apps/els_dap/src/els_dap_protocol.erl
@@ -84,7 +84,7 @@ error_response(Seq, Command, Error) ->
%%==============================================================================
%% Data Structures
%%==============================================================================
--spec range(poi_range()) -> range().
+-spec range(els_poi:poi_range()) -> range().
range(#{from := {FromL, FromC}, to := {ToL, ToC}}) ->
#{
start => #{line => FromL - 1, character => FromC - 1},
diff --git a/apps/els_lsp/src/els_bound_var_in_pattern_diagnostics.erl b/apps/els_lsp/src/els_bound_var_in_pattern_diagnostics.erl
index 38535ae80..8140f91ce 100644
--- a/apps/els_lsp/src/els_bound_var_in_pattern_diagnostics.erl
+++ b/apps/els_lsp/src/els_bound_var_in_pattern_diagnostics.erl
@@ -50,13 +50,13 @@ source() ->
%% Internal Functions
%%==============================================================================
--spec find_vars(uri()) -> [poi()].
+-spec find_vars(uri()) -> [els_poi:poi()].
find_vars(Uri) ->
{ok, #{text := Text}} = els_utils:lookup_document(Uri),
{ok, Forms} = els_parser:parse_text(Text),
lists:flatmap(fun find_vars_in_form/1, Forms).
--spec find_vars_in_form(erl_syntax:forms()) -> [poi()].
+-spec find_vars_in_form(erl_syntax:forms()) -> [els_poi:poi()].
find_vars_in_form(Form) ->
case erl_syntax:type(Form) of
function ->
@@ -83,11 +83,11 @@ find_vars_in_form(Form) ->
[]
end.
--spec fold_subtrees([[tree()]], [poi()]) -> [poi()].
+-spec fold_subtrees([[tree()]], [els_poi:poi()]) -> [els_poi:poi()].
fold_subtrees(Subtrees, Acc) ->
erl_syntax_lib:foldl_listlist(fun find_vars_in_tree/2, Acc, Subtrees).
--spec find_vars_in_tree(tree(), [poi()]) -> [poi()].
+-spec find_vars_in_tree(tree(), [els_poi:poi()]) -> [els_poi:poi()].
find_vars_in_tree(Tree, Acc) ->
case erl_syntax:type(Tree) of
Type when
@@ -118,15 +118,15 @@ find_vars_in_tree(Tree, Acc) ->
fold_subtrees(erl_syntax:subtrees(Tree), Acc)
end.
--spec fold_pattern(tree(), [poi()]) -> [poi()].
+-spec fold_pattern(tree(), [els_poi:poi()]) -> [els_poi:poi()].
fold_pattern(Pattern, Acc) ->
erl_syntax_lib:fold(fun find_vars_in_pattern/2, Acc, Pattern).
--spec fold_pattern_list([tree()], [poi()]) -> [poi()].
+-spec fold_pattern_list([tree()], [els_poi:poi()]) -> [els_poi:poi()].
fold_pattern_list(Patterns, Acc) ->
lists:foldl(fun fold_pattern/2, Acc, Patterns).
--spec find_vars_in_pattern(tree(), [poi()]) -> [poi()].
+-spec find_vars_in_pattern(tree(), [els_poi:poi()]) -> [els_poi:poi()].
find_vars_in_pattern(Tree, Acc) ->
case erl_syntax:type(Tree) of
variable ->
@@ -143,14 +143,14 @@ find_vars_in_pattern(Tree, Acc) ->
Acc
end.
--spec variable(tree()) -> poi().
+-spec variable(tree()) -> els_poi:poi().
variable(Tree) ->
Id = erl_syntax:variable_name(Tree),
Pos = erl_syntax:get_pos(Tree),
Range = els_range:range(Pos, variable, Id, undefined),
els_poi:new(Range, variable, Id, undefined).
--spec make_diagnostic(poi()) -> els_diagnostics:diagnostic().
+-spec make_diagnostic(els_poi:poi()) -> els_diagnostics:diagnostic().
make_diagnostic(#{id := Id, range := POIRange}) ->
Range = els_protocol:range(POIRange),
VariableName = atom_to_binary(Id, utf8),
diff --git a/apps/els_lsp/src/els_call_hierarchy_item.erl b/apps/els_lsp/src/els_call_hierarchy_item.erl
index 920e16358..8d4755dbd 100644
--- a/apps/els_lsp/src/els_call_hierarchy_item.erl
+++ b/apps/els_lsp/src/els_call_hierarchy_item.erl
@@ -35,11 +35,11 @@
]).
%% @doc Extract and decode the POI from the data
--spec poi(item()) -> poi().
+-spec poi(item()) -> els_poi:poi().
poi(#{<<"data">> := Data}) ->
maps:get(poi, els_utils:base64_decode_term(Data)).
--spec new(binary(), uri(), poi_range(), poi_range(), data()) -> item().
+-spec new(binary(), uri(), els_poi:poi_range(), els_poi:poi_range(), data()) -> item().
new(Name, Uri, Range, SelectionRange, Data) ->
#{from := {StartLine, _}} = Range,
Detail = <<
diff --git a/apps/els_lsp/src/els_call_hierarchy_provider.erl b/apps/els_lsp/src/els_call_hierarchy_provider.erl
index 145c9c6e5..8084d80ee 100644
--- a/apps/els_lsp/src/els_call_hierarchy_provider.erl
+++ b/apps/els_lsp/src/els_call_hierarchy_provider.erl
@@ -76,7 +76,7 @@ incoming_calls(Items) ->
outgoing_calls(Items) ->
[#{to => Item, fromRanges => [Range]} || #{range := Range} = Item <- Items].
--spec function_to_item(uri(), poi()) -> els_call_hierarchy_item:item().
+-spec function_to_item(uri(), els_poi:poi()) -> els_call_hierarchy_item:item().
function_to_item(Uri, Function) ->
#{id := Id, range := Range} = Function,
Name = els_utils:function_signature(Id),
@@ -93,7 +93,7 @@ reference_to_item(Reference) ->
Data = #{poi => WrappingPOI},
els_call_hierarchy_item:new(Name, RefUri, POIRange, POIRange, Data).
--spec application_to_item(uri(), poi()) ->
+-spec application_to_item(uri(), els_poi:poi()) ->
{ok, els_call_hierarchy_item:item()} | {error, not_found}.
application_to_item(Uri, Application) ->
#{id := Id} = Application,
@@ -107,8 +107,8 @@ application_to_item(Uri, Application) ->
{error, Reason}
end.
--spec applications_in_function_range(uri(), poi()) ->
- [poi()].
+-spec applications_in_function_range(uri(), els_poi:poi()) ->
+ [els_poi:poi()].
applications_in_function_range(Uri, Function) ->
{ok, Document} = els_utils:lookup_document(Uri),
#{data := #{wrapping_range := WrappingRange}} = Function,
diff --git a/apps/els_lsp/src/els_code_actions.erl b/apps/els_lsp/src/els_code_actions.erl
index 7ca2e1a9d..4cb24e2de 100644
--- a/apps/els_lsp/src/els_code_actions.erl
+++ b/apps/els_lsp/src/els_code_actions.erl
@@ -165,7 +165,8 @@ remove_unused(Uri, _Range0, Data, [Import]) ->
[]
end.
--spec ensure_range(poi_range(), binary(), [poi()]) -> {ok, poi_range()} | error.
+-spec ensure_range(els_poi:poi_range(), binary(), [els_poi:poi()]) ->
+ {ok, els_poi:poi_range()} | error.
ensure_range(#{from := {Line, _}}, SubjectId, POIs) ->
SubjectAtom = binary_to_atom(SubjectId, utf8),
Ranges = [
diff --git a/apps/els_lsp/src/els_code_lens.erl b/apps/els_lsp/src/els_code_lens.erl
index 87f59dd51..e786743a2 100644
--- a/apps/els_lsp/src/els_code_lens.erl
+++ b/apps/els_lsp/src/els_code_lens.erl
@@ -9,10 +9,10 @@
%%==============================================================================
-callback init(els_dt_document:item()) -> state().
--callback command(els_dt_document:item(), poi(), state()) ->
+-callback command(els_dt_document:item(), els_poi:poi(), state()) ->
els_command:command().
-callback is_default() -> boolean().
--callback pois(els_dt_document:item()) -> [poi()].
+-callback pois(els_dt_document:item()) -> [els_poi:poi()].
-callback precondition(els_dt_document:item()) -> boolean().
-optional_callbacks([
init/1,
@@ -104,7 +104,7 @@ lenses(Id, Document) ->
%% Constructors
%%==============================================================================
--spec make_lens(atom(), els_dt_document:item(), poi(), state()) -> lens().
+-spec make_lens(atom(), els_dt_document:item(), els_poi:poi(), state()) -> lens().
make_lens(CbModule, Document, #{range := Range} = POI, State) ->
#{
range => els_protocol:range(Range),
diff --git a/apps/els_lsp/src/els_code_lens_ct_run_test.erl b/apps/els_lsp/src/els_code_lens_ct_run_test.erl
index e9c5d62e7..65974eedb 100644
--- a/apps/els_lsp/src/els_code_lens_ct_run_test.erl
+++ b/apps/els_lsp/src/els_code_lens_ct_run_test.erl
@@ -14,7 +14,7 @@
-include("els_lsp.hrl").
--spec command(els_dt_document:item(), poi(), els_code_lens:state()) ->
+-spec command(els_dt_document:item(), els_poi:poi(), els_code_lens:state()) ->
els_command:command().
command(#{uri := Uri} = _Document, POI, _State) ->
#{id := {F, A}, range := #{from := {Line, _}}} = POI,
@@ -35,7 +35,7 @@ command(#{uri := Uri} = _Document, POI, _State) ->
is_default() ->
false.
--spec pois(els_dt_document:item()) -> [poi()].
+-spec pois(els_dt_document:item()) -> [els_poi:poi()].
pois(Document) ->
Functions = els_dt_document:pois(Document, [function]),
[POI || #{id := {F, 1}} = POI <- Functions, not is_blacklisted(F)].
diff --git a/apps/els_lsp/src/els_code_lens_function_references.erl b/apps/els_lsp/src/els_code_lens_function_references.erl
index a49e1561b..e0696a556 100644
--- a/apps/els_lsp/src/els_code_lens_function_references.erl
+++ b/apps/els_lsp/src/els_code_lens_function_references.erl
@@ -7,17 +7,15 @@
command/3
]).
--include("els_lsp.hrl").
-
-spec is_default() -> boolean().
is_default() ->
true.
--spec pois(els_dt_document:item()) -> [poi()].
+-spec pois(els_dt_document:item()) -> [els_poi:poi()].
pois(Document) ->
els_dt_document:pois(Document, [function]).
--spec command(els_dt_document:item(), poi(), els_code_lens:state()) ->
+-spec command(els_dt_document:item(), els_poi:poi(), els_code_lens:state()) ->
els_command:command().
command(Document, POI, _State) ->
Title = title(Document, POI),
@@ -25,7 +23,7 @@ command(Document, POI, _State) ->
CommandArgs = [],
els_command:make_command(Title, CommandId, CommandArgs).
--spec title(els_dt_document:item(), poi()) -> binary().
+-spec title(els_dt_document:item(), els_poi:poi()) -> binary().
title(Document, POI) ->
#{uri := Uri} = Document,
M = els_uri:module(Uri),
diff --git a/apps/els_lsp/src/els_code_lens_server_info.erl b/apps/els_lsp/src/els_code_lens_server_info.erl
index 1d7f90aff..0d3cc9536 100644
--- a/apps/els_lsp/src/els_code_lens_server_info.erl
+++ b/apps/els_lsp/src/els_code_lens_server_info.erl
@@ -11,9 +11,7 @@
pois/1
]).
--include("els_lsp.hrl").
-
--spec command(els_dt_document:item(), poi(), els_code_lens:state()) ->
+-spec command(els_dt_document:item(), els_poi:poi(), els_code_lens:state()) ->
els_command:command().
command(_Document, _POI, _State) ->
Title = title(),
@@ -25,7 +23,7 @@ command(_Document, _POI, _State) ->
is_default() ->
false.
--spec pois(els_dt_document:item()) -> [poi()].
+-spec pois(els_dt_document:item()) -> [els_poi:poi()].
pois(_Document) ->
%% Return a dummy POI on the first line
[els_poi:new(#{from => {1, 1}, to => {2, 1}}, dummy, dummy)].
diff --git a/apps/els_lsp/src/els_code_lens_show_behaviour_usages.erl b/apps/els_lsp/src/els_code_lens_show_behaviour_usages.erl
index 47cd6dee8..1e4635d89 100644
--- a/apps/els_lsp/src/els_code_lens_show_behaviour_usages.erl
+++ b/apps/els_lsp/src/els_code_lens_show_behaviour_usages.erl
@@ -12,9 +12,7 @@
precondition/1
]).
--include("els_lsp.hrl").
-
--spec command(els_dt_document:item(), poi(), els_code_lens:state()) ->
+-spec command(els_dt_document:item(), els_poi:poi(), els_code_lens:state()) ->
els_command:command().
command(_Document, POI, _State) ->
Title = title(POI),
@@ -33,11 +31,11 @@ precondition(Document) ->
Callbacks = els_dt_document:pois(Document, [callback]),
length(Callbacks) > 0.
--spec pois(els_dt_document:item()) -> [poi()].
+-spec pois(els_dt_document:item()) -> [els_poi:poi()].
pois(Document) ->
els_dt_document:pois(Document, [module]).
--spec title(poi()) -> binary().
+-spec title(els_poi:poi()) -> binary().
title(#{id := Id} = _POI) ->
{ok, Refs} = els_dt_references:find_by_id(behaviour, Id),
Count = length(Refs),
diff --git a/apps/els_lsp/src/els_code_lens_suggest_spec.erl b/apps/els_lsp/src/els_code_lens_suggest_spec.erl
index 29df08130..16bf53ad7 100644
--- a/apps/els_lsp/src/els_code_lens_suggest_spec.erl
+++ b/apps/els_lsp/src/els_code_lens_suggest_spec.erl
@@ -14,7 +14,6 @@
%%==============================================================================
%% Includes
%%==============================================================================
--include("els_lsp.hrl").
-include_lib("kernel/include/logger.hrl").
%%==============================================================================
@@ -41,7 +40,7 @@ init(#{uri := Uri} = _Document) ->
'no_info'
end.
--spec command(els_dt_document:item(), poi(), state()) -> els_command:command().
+-spec command(els_dt_document:item(), els_poi:poi(), state()) -> els_command:command().
command(_Document, _POI, 'no_info') ->
CommandId = <<"suggest-spec">>,
Title = <<"Cannot extract specs (check logs for details)">>,
@@ -64,7 +63,7 @@ command(Document, #{range := #{from := {Line, _}}} = POI, Info) ->
is_default() ->
true.
--spec pois(els_dt_document:item()) -> [poi()].
+-spec pois(els_dt_document:item()) -> [els_poi:poi()].
pois(Document) ->
Functions = els_dt_document:pois(Document, [function]),
Specs = els_dt_document:pois(Document, [spec]),
@@ -74,7 +73,7 @@ pois(Document) ->
%%==============================================================================
%% Internal functions
%%==============================================================================
--spec get_type_spec(poi(), els_typer:info()) -> binary().
+-spec get_type_spec(els_poi:poi(), els_typer:info()) -> binary().
get_type_spec(POI, Info) ->
#{id := {Function, Arity}} = POI,
Spec = els_typer:get_type_spec(Function, Arity, Info),
diff --git a/apps/els_lsp/src/els_code_navigation.erl b/apps/els_lsp/src/els_code_navigation.erl
index c0b7a487d..b5ad84bcf 100644
--- a/apps/els_lsp/src/els_code_navigation.erl
+++ b/apps/els_lsp/src/els_code_navigation.erl
@@ -23,8 +23,8 @@
%% API
%%==============================================================================
--spec goto_definition(uri(), poi()) ->
- {ok, uri(), poi()} | {error, any()}.
+-spec goto_definition(uri(), els_poi:poi()) ->
+ {ok, uri(), els_poi:poi()} | {error, any()}.
goto_definition(
Uri,
Var = #{kind := variable}
@@ -142,13 +142,13 @@ is_imported_bif(_Uri, F, A) ->
true
end.
--spec find(uri() | [uri()], poi_kind(), any()) ->
- {ok, uri(), poi()} | {error, not_found}.
+-spec find(uri() | [uri()], els_poi:poi_kind(), any()) ->
+ {ok, uri(), els_poi:poi()} | {error, not_found}.
find(UriOrUris, Kind, Data) ->
find(UriOrUris, Kind, Data, sets:new()).
--spec find(uri() | [uri()], poi_kind(), any(), sets:set(binary())) ->
- {ok, uri(), poi()} | {error, not_found}.
+-spec find(uri() | [uri()], els_poi:poi_kind(), any(), sets:set(binary())) ->
+ {ok, uri(), els_poi:poi()} | {error, not_found}.
find([], _Kind, _Data, _AlreadyVisited) ->
{error, not_found};
find([Uri | Uris0], Kind, Data, AlreadyVisited) ->
@@ -170,11 +170,11 @@ find(Uri, Kind, Data, AlreadyVisited) ->
-spec find_in_document(
uri() | [uri()],
els_dt_document:item(),
- poi_kind(),
+ els_poi:poi_kind(),
any(),
sets:set(binary())
) ->
- {ok, uri(), poi()} | {error, any()}.
+ {ok, uri(), els_poi:poi()} | {error, any()}.
find_in_document([Uri | Uris0], Document, Kind, Data, AlreadyVisited) ->
POIs = els_dt_document:pois(Document, [Kind]),
case [POI || #{id := Id} = POI <- POIs, Id =:= Data] of
@@ -199,7 +199,7 @@ include_uris(Document) ->
POIs = els_dt_document:pois(Document, [include, include_lib]),
lists:foldl(fun add_include_uri/2, [], POIs).
--spec add_include_uri(poi(), [uri()]) -> [uri()].
+-spec add_include_uri(els_poi:poi(), [uri()]) -> [uri()].
add_include_uri(#{id := Id}, Acc) ->
case els_utils:find_header(els_utils:filename_to_atom(Id)) of
{ok, Uri} -> [Uri | Acc];
@@ -211,8 +211,8 @@ beginning() ->
#{range => #{from => {1, 1}, to => {1, 1}}}.
%% @doc check for a match in any of the module imported functions.
--spec maybe_imported(els_dt_document:item(), poi_kind(), any()) ->
- {ok, uri(), poi()} | {error, not_found}.
+-spec maybe_imported(els_dt_document:item(), els_poi:poi_kind(), any()) ->
+ {ok, uri(), els_poi:poi()} | {error, not_found}.
maybe_imported(Document, function, {F, A}) ->
POIs = els_dt_document:pois(Document, [import_entry]),
case [{M, F, A} || #{id := {M, FP, AP}} <- POIs, FP =:= F, AP =:= A] of
@@ -227,7 +227,7 @@ maybe_imported(Document, function, {F, A}) ->
maybe_imported(_Document, _Kind, _Data) ->
{error, not_found}.
--spec find_in_scope(uri(), poi()) -> [poi()].
+-spec find_in_scope(uri(), els_poi:poi()) -> [els_poi:poi()].
find_in_scope(Uri, #{kind := variable, id := VarId, range := VarRange}) ->
{ok, Document} = els_utils:lookup_document(Uri),
VarPOIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
diff --git a/apps/els_lsp/src/els_compiler_diagnostics.erl b/apps/els_lsp/src/els_compiler_diagnostics.erl
index 5e2f3af37..e3b3c0c34 100644
--- a/apps/els_lsp/src/els_compiler_diagnostics.erl
+++ b/apps/els_lsp/src/els_compiler_diagnostics.erl
@@ -186,7 +186,7 @@ diagnostics(Path, List, Severity) ->
-spec diagnostic(
string(),
string(),
- poi_range(),
+ els_poi:poi_range(),
els_dt_document:item(),
module(),
string(),
@@ -206,7 +206,7 @@ diagnostic(_Path, MessagePath, Range, Document, Module, Desc0, Severity) ->
Desc = io_lib:format("Issue in included file (~p): ~s", [Line, Desc1]),
diagnostic(InclusionRange, ?MODULE, Desc, Severity).
--spec diagnostic(poi_range(), module(), string(), integer()) ->
+-spec diagnostic(els_poi:poi_range(), module(), string(), integer()) ->
els_diagnostics:diagnostic().
diagnostic(Range, Module, Desc, Severity) ->
Message0 = lists:flatten(Module:format_error(Desc)),
@@ -648,7 +648,7 @@ make_code(Module, _Reason) ->
-spec range(
els_dt_document:item() | undefined,
erl_anno:anno() | none
-) -> poi_range().
+) -> els_poi:poi_range().
range(Document, Anno) ->
els_diagnostics_utils:range(Document, Anno).
@@ -656,7 +656,7 @@ range(Document, Anno) ->
%%
%% Given the path of e .hrl path, find its inclusion range within
%% a given document.
--spec inclusion_range(string(), els_dt_document:item()) -> poi_range().
+-spec inclusion_range(string(), els_dt_document:item()) -> els_poi:poi_range().
inclusion_range(IncludePath, Document) ->
case
inclusion_range(IncludePath, Document, include) ++
@@ -673,7 +673,7 @@ inclusion_range(IncludePath, Document) ->
els_dt_document:item(),
include | include_lib | behaviour | parse_transform
) ->
- [poi_range()].
+ [els_poi:poi_range()].
inclusion_range(IncludePath, Document, include) ->
POIs = els_dt_document:pois(Document, [include]),
IncludeId = els_utils:include_id(IncludePath),
diff --git a/apps/els_lsp/src/els_completion_provider.erl b/apps/els_lsp/src/els_completion_provider.erl
index 808f65f42..54f07ddca 100644
--- a/apps/els_lsp/src/els_completion_provider.erl
+++ b/apps/els_lsp/src/els_completion_provider.erl
@@ -534,21 +534,21 @@ is_behaviour(Uri) ->
%%==============================================================================
%% Functions, Types, Macros and Records
%%==============================================================================
--spec unexported_definitions(els_dt_document:item(), poi_kind()) -> items().
+-spec unexported_definitions(els_dt_document:item(), els_poi:poi_kind()) -> items().
unexported_definitions(Document, POIKind) ->
AllDefs = definitions(Document, POIKind, true, false),
ExportedDefs = definitions(Document, POIKind, true, true),
AllDefs -- ExportedDefs.
--spec definitions(els_dt_document:item(), poi_kind()) -> [map()].
+-spec definitions(els_dt_document:item(), els_poi:poi_kind()) -> [map()].
definitions(Document, POIKind) ->
definitions(Document, POIKind, _ExportFormat = false, _ExportedOnly = false).
--spec definitions(els_dt_document:item(), poi_kind(), boolean()) -> [map()].
+-spec definitions(els_dt_document:item(), els_poi:poi_kind(), boolean()) -> [map()].
definitions(Document, POIKind, ExportFormat) ->
definitions(Document, POIKind, ExportFormat, _ExportedOnly = false).
--spec definitions(els_dt_document:item(), poi_kind(), boolean(), boolean()) ->
+-spec definitions(els_dt_document:item(), els_poi:poi_kind(), boolean(), boolean()) ->
[map()].
definitions(Document, POIKind, ExportFormat, ExportedOnly) ->
POIs = els_scope:local_and_included_pois(Document, POIKind),
@@ -566,7 +566,7 @@ definitions(Document, POIKind, ExportFormat, ExportedOnly) ->
lists:usort(Items).
-spec completion_context(els_dt_document:item(), line(), column()) ->
- {boolean(), poi_kind()}.
+ {boolean(), els_poi:poi_kind()}.
completion_context(Document, Line, Column) ->
ExportFormat = is_in(Document, Line, Column, [export, export_type]),
POIKind =
@@ -578,7 +578,7 @@ completion_context(Document, Line, Column) ->
-spec resolve_definitions(
uri(),
- [poi()],
+ [els_poi:poi()],
[{atom(), arity()}],
boolean(),
boolean()
@@ -591,7 +591,7 @@ resolve_definitions(Uri, Functions, ExportsFA, ExportedOnly, ArityOnly) ->
not ExportedOnly orelse lists:member(FA, ExportsFA)
].
--spec resolve_definition(uri(), poi(), boolean()) -> map().
+-spec resolve_definition(uri(), els_poi:poi(), boolean()) -> map().
resolve_definition(Uri, #{kind := 'function', id := {F, A}} = POI, ArityOnly) ->
Data = #{
<<"module">> => els_uri:module(Uri),
@@ -613,7 +613,7 @@ resolve_definition(
resolve_definition(_Uri, POI, ArityOnly) ->
completion_item(POI, ArityOnly).
--spec exported_definitions(module(), poi_kind(), boolean()) -> [map()].
+-spec exported_definitions(module(), els_poi:poi_kind(), boolean()) -> [map()].
exported_definitions(Module, POIKind, ExportFormat) ->
case els_utils:find_module(Module) of
{ok, Uri} ->
@@ -670,7 +670,7 @@ record_fields(Document, RecordName) ->
]
end.
--spec find_record_definition(els_dt_document:item(), atom()) -> [poi()].
+-spec find_record_definition(els_dt_document:item(), atom()) -> [els_poi:poi()].
find_record_definition(Document, RecordName) ->
POIs = els_scope:local_and_included_pois(Document, record),
[X || X = #{id := Name} <- POIs, Name =:= RecordName].
@@ -729,7 +729,7 @@ keywords() ->
%% Built-in functions
%%==============================================================================
--spec bifs(poi_kind(), boolean()) -> [map()].
+-spec bifs(els_poi:poi_kind(), boolean()) -> [map()].
bifs(function, ExportFormat) ->
Range = #{from => {0, 0}, to => {0, 0}},
Exports = erlang:module_info(exports),
@@ -848,11 +848,11 @@ filter_by_prefix(Prefix, List, ToBinary, ItemFun) ->
%%==============================================================================
%% Helper functions
%%==============================================================================
--spec completion_item(poi(), boolean()) -> map().
+-spec completion_item(els_poi:poi(), boolean()) -> map().
completion_item(POI, ExportFormat) ->
completion_item(POI, #{}, ExportFormat).
--spec completion_item(poi(), map(), ExportFormat :: boolean()) -> map().
+-spec completion_item(els_poi:poi(), map(), ExportFormat :: boolean()) -> map().
completion_item(#{kind := Kind, id := {F, A}, data := POIData}, Data, false) when
Kind =:= function;
Kind =:= type_definition
@@ -956,7 +956,7 @@ snippet_support() ->
false
end.
--spec is_in(els_dt_document:item(), line(), column(), [poi_kind()]) ->
+-spec is_in(els_dt_document:item(), line(), column(), [els_poi:poi_kind()]) ->
boolean().
is_in(Document, Line, Column, POIKinds) ->
POIs = els_dt_document:get_element_at_pos(Document, Line, Column),
@@ -964,7 +964,7 @@ is_in(Document, Line, Column, POIKinds) ->
lists:any(IsKind, POIs).
%% @doc Maps a POI kind to its completion item kind
--spec completion_item_kind(poi_kind()) -> completion_item_kind().
+-spec completion_item_kind(els_poi:poi_kind()) -> completion_item_kind().
completion_item_kind(define) ->
?COMPLETION_ITEM_KIND_CONSTANT;
completion_item_kind(record) ->
@@ -975,8 +975,8 @@ completion_item_kind(function) ->
?COMPLETION_ITEM_KIND_FUNCTION.
%% @doc Maps a POI kind to its export entry POI kind
--spec export_entry_kind(poi_kind()) ->
- poi_kind() | {error, no_export_entry_kind}.
+-spec export_entry_kind(els_poi:poi_kind()) ->
+ els_poi:poi_kind() | {error, no_export_entry_kind}.
export_entry_kind(type_definition) -> export_type_entry;
export_entry_kind(function) -> export_entry;
export_entry_kind(_) -> {error, no_export_entry_kind}.
diff --git a/apps/els_lsp/src/els_crossref_diagnostics.erl b/apps/els_lsp/src/els_crossref_diagnostics.erl
index 2633401a8..d259f7cd7 100644
--- a/apps/els_lsp/src/els_crossref_diagnostics.erl
+++ b/apps/els_lsp/src/els_crossref_diagnostics.erl
@@ -54,7 +54,7 @@ source() ->
%%==============================================================================
%% Internal Functions
%%==============================================================================
--spec make_diagnostic(poi()) -> els_diagnostics:diagnostic().
+-spec make_diagnostic(els_poi:poi()) -> els_diagnostics:diagnostic().
make_diagnostic(#{range := Range, id := Id}) ->
Function =
case Id of
@@ -75,7 +75,7 @@ make_diagnostic(#{range := Range, id := Id}) ->
source()
).
--spec has_definition(poi(), els_dt_document:item()) -> boolean().
+-spec has_definition(els_poi:poi(), els_dt_document:item()) -> boolean().
has_definition(
#{
kind := application,
diff --git a/apps/els_lsp/src/els_definition_provider.erl b/apps/els_lsp/src/els_definition_provider.erl
index 2f692fbaa..f7726ed61 100644
--- a/apps/els_lsp/src/els_definition_provider.erl
+++ b/apps/els_lsp/src/els_definition_provider.erl
@@ -42,7 +42,7 @@ handle_request({definition, Params}, State) ->
{response, GoTo}
end.
--spec goto_definition(uri(), [poi()]) -> map() | null.
+-spec goto_definition(uri(), [els_poi:poi()]) -> map() | null.
goto_definition(_Uri, []) ->
null;
goto_definition(Uri, [POI | Rest]) ->
@@ -53,34 +53,34 @@ goto_definition(Uri, [POI | Rest]) ->
goto_definition(Uri, Rest)
end.
--spec match_incomplete(binary(), pos()) -> [poi()].
+-spec match_incomplete(binary(), pos()) -> [els_poi:poi()].
match_incomplete(Text, Pos) ->
%% Try parsing subsets of text to find a matching POI at Pos
match_after(Text, Pos) ++ match_line(Text, Pos).
--spec match_after(binary(), pos()) -> [poi()].
+-spec match_after(binary(), pos()) -> [els_poi:poi()].
match_after(Text, {Line, Character}) ->
%% Try to parse current line and the lines after it
POIs = els_incomplete_parser:parse_after(Text, Line),
MatchingPOIs = match_pois(POIs, {1, Character + 1}),
fix_line_offsets(MatchingPOIs, Line).
--spec match_line(binary(), pos()) -> [poi()].
+-spec match_line(binary(), pos()) -> [els_poi:poi()].
match_line(Text, {Line, Character}) ->
%% Try to parse only current line
POIs = els_incomplete_parser:parse_line(Text, Line),
MatchingPOIs = match_pois(POIs, {1, Character + 1}),
fix_line_offsets(MatchingPOIs, Line).
--spec match_pois([poi()], pos()) -> [poi()].
+-spec match_pois([els_poi:poi()], pos()) -> [els_poi:poi()].
match_pois(POIs, Pos) ->
els_poi:sort(els_poi:match_pos(POIs, Pos)).
--spec fix_line_offsets([poi()], integer()) -> [poi()].
+-spec fix_line_offsets([els_poi:poi()], integer()) -> [els_poi:poi()].
fix_line_offsets(POIs, Offset) ->
[fix_line_offset(POI, Offset) || POI <- POIs].
--spec fix_line_offset(poi(), integer()) -> poi().
+-spec fix_line_offset(els_poi:poi(), integer()) -> els_poi:poi().
fix_line_offset(
#{
range := #{
diff --git a/apps/els_lsp/src/els_diagnostics_utils.erl b/apps/els_lsp/src/els_diagnostics_utils.erl
index 85c3b1b3c..bd6d9039c 100644
--- a/apps/els_lsp/src/els_diagnostics_utils.erl
+++ b/apps/els_lsp/src/els_diagnostics_utils.erl
@@ -45,7 +45,7 @@ find_included_document(Uri) ->
-spec range(
els_dt_document:item() | undefined,
erl_anno:anno() | none
-) -> poi_range().
+) -> els_poi:poi_range().
range(Document, none) ->
range(Document, erl_anno:new(1));
range(Document, Anno) ->
@@ -55,10 +55,7 @@ range(Document, Anno) ->
Col when Document =:= undefined; Col =:= undefined ->
#{from => {Line, 1}, to => {Line + 1, 1}};
Col ->
- POIs0 = els_dt_document:get_element_at_pos(Document, Line, Col),
-
- %% Exclude folding range since line is more exact anyway
- POIs = [POI || #{kind := Kind} = POI <- POIs0, Kind =/= folding_range],
+ POIs = els_dt_document:get_element_at_pos(Document, Line, Col),
%% * If we find no pois that we just return the original line
%% * If we find a poi that start on the line and col as the anno
@@ -153,7 +150,7 @@ pt_deps(Module) ->
[]
end.
--spec applications_to_uris([poi()]) -> [uri()].
+-spec applications_to_uris([els_poi:poi()]) -> [uri()].
applications_to_uris(Applications) ->
Modules = [M || #{id := {M, _F, _A}} <- Applications],
Fun = fun(M, Acc) ->
diff --git a/apps/els_lsp/src/els_docs.erl b/apps/els_lsp/src/els_docs.erl
index e13230a2f..9b475dbbf 100644
--- a/apps/els_lsp/src/els_docs.erl
+++ b/apps/els_lsp/src/els_docs.erl
@@ -44,7 +44,7 @@
%%==============================================================================
%% API
%%==============================================================================
--spec docs(uri(), poi()) -> [els_markup_content:doc_entry()].
+-spec docs(uri(), els_poi:poi()) -> [els_markup_content:doc_entry()].
docs(_Uri, #{kind := Kind, id := {M, F, A}}) when
Kind =:= application;
Kind =:= implicit_fun
@@ -421,7 +421,7 @@ format_edoc(Desc) when is_map(Desc) ->
FormattedDoc = els_utils:to_list(docsh_edoc:format_edoc(Doc, #{})),
[{text, FormattedDoc}].
--spec macro_signature(poi_id(), [{integer(), string()}]) -> unicode:charlist().
+-spec macro_signature(els_poi:poi_id(), [{integer(), string()}]) -> unicode:charlist().
macro_signature({Name, _Arity}, Args) ->
[atom_to_list(Name), "(", lists:join(", ", [A || {_N, A} <- Args]), ")"];
macro_signature(Name, none) ->
diff --git a/apps/els_lsp/src/els_document_highlight_provider.erl b/apps/els_lsp/src/els_document_highlight_provider.erl
index 09dd58861..cccda4b69 100644
--- a/apps/els_lsp/src/els_document_highlight_provider.erl
+++ b/apps/els_lsp/src/els_document_highlight_provider.erl
@@ -42,7 +42,7 @@ handle_request({document_highlight, Params}, _State) ->
%% Internal functions
%%==============================================================================
--spec find_highlights(els_dt_document:item(), poi()) -> any().
+-spec find_highlights(els_dt_document:item(), els_poi:poi()) -> any().
find_highlights(Document, #{id := Id, kind := atom}) ->
AtomHighlights = do_find_highlights(Document, Id, [atom]),
FieldPOIs = els_dt_document:pois(Document, [
@@ -59,13 +59,12 @@ find_highlights(Document, #{id := Id, kind := Kind}) ->
POIs = els_dt_document:pois(Document, find_similar_kinds(Kind)),
Highlights = [
document_highlight(R)
- || #{id := I, kind := K, range := R} <- POIs,
- I =:= Id,
- K =/= 'folding_range'
+ || #{id := I, range := R} <- POIs,
+ I =:= Id
],
normalize_result(Highlights).
--spec do_find_highlights(els_dt_document:item(), poi_id(), [poi_kind()]) ->
+-spec do_find_highlights(els_dt_document:item(), els_poi:poi_id(), [els_poi:poi_kind()]) ->
any().
do_find_highlights(Document, Id, Kinds) ->
POIs = els_dt_document:pois(Document, Kinds),
@@ -75,7 +74,7 @@ do_find_highlights(Document, Id, Kinds) ->
I =:= Id
].
--spec document_highlight(poi_range()) -> map().
+-spec document_highlight(els_poi:poi_range()) -> map().
document_highlight(Range) ->
#{
range => els_protocol:range(Range),
@@ -88,11 +87,11 @@ normalize_result([]) ->
normalize_result(L) when is_list(L) ->
L.
--spec find_similar_kinds(poi_kind()) -> [poi_kind()].
+-spec find_similar_kinds(els_poi:poi_kind()) -> [els_poi:poi_kind()].
find_similar_kinds(Kind) ->
find_similar_kinds(Kind, kind_groups()).
--spec find_similar_kinds(poi_kind(), [[poi_kind()]]) -> [poi_kind()].
+-spec find_similar_kinds(els_poi:poi_kind(), [[els_poi:poi_kind()]]) -> [els_poi:poi_kind()].
find_similar_kinds(Kind, []) ->
[Kind];
find_similar_kinds(Kind, [Group | Groups]) ->
@@ -106,7 +105,7 @@ find_similar_kinds(Kind, [Group | Groups]) ->
%% Each group represents a list of POI kinds which represent the same or similar
%% objects (usually the definition and the usages of an object). Each POI kind
%% in one group must have the same id format.
--spec kind_groups() -> [[poi_kind()]].
+-spec kind_groups() -> [[els_poi:poi_kind()]].
kind_groups() ->
%% function
[
diff --git a/apps/els_lsp/src/els_document_symbol_provider.erl b/apps/els_lsp/src/els_document_symbol_provider.erl
index 4fb37036d..12e917c17 100644
--- a/apps/els_lsp/src/els_document_symbol_provider.erl
+++ b/apps/els_lsp/src/els_document_symbol_provider.erl
@@ -38,34 +38,4 @@ symbols(Uri) ->
record,
type_definition
]),
- lists:reverse([poi_to_symbol(Uri, POI) || POI <- POIs]).
-
--spec poi_to_symbol(uri(), poi()) -> symbol_information().
-poi_to_symbol(Uri, POI) ->
- #{range := Range, kind := Kind, id := Id} = POI,
- #{
- name => symbol_name(Kind, Id),
- kind => symbol_kind(Kind),
- location => #{
- uri => Uri,
- range => els_protocol:range(Range)
- }
- }.
-
--spec symbol_kind(poi_kind()) -> symbol_kind().
-symbol_kind(function) -> ?SYMBOLKIND_FUNCTION;
-symbol_kind(define) -> ?SYMBOLKIND_CONSTANT;
-symbol_kind(record) -> ?SYMBOLKIND_STRUCT;
-symbol_kind(type_definition) -> ?SYMBOLKIND_TYPE_PARAMETER.
-
--spec symbol_name(poi_kind(), any()) -> binary().
-symbol_name(function, {F, A}) ->
- els_utils:to_binary(io_lib:format("~s/~p", [F, A]));
-symbol_name(define, {Name, Arity}) ->
- els_utils:to_binary(io_lib:format("~s/~p", [Name, Arity]));
-symbol_name(define, Name) when is_atom(Name) ->
- atom_to_binary(Name, utf8);
-symbol_name(record, Name) when is_atom(Name) ->
- atom_to_binary(Name, utf8);
-symbol_name(type_definition, {Name, Arity}) ->
- els_utils:to_binary(io_lib:format("~s/~p", [Name, Arity])).
+ lists:reverse([els_poi:to_symbol(Uri, POI) || POI <- POIs]).
diff --git a/apps/els_lsp/src/els_dt_document.erl b/apps/els_lsp/src/els_dt_document.erl
index 56ddacfc2..5bf126fa5 100644
--- a/apps/els_lsp/src/els_dt_document.erl
+++ b/apps/els_lsp/src/els_dt_document.erl
@@ -63,7 +63,7 @@
id :: id() | '_',
kind :: kind() | '_',
text :: binary() | '_',
- pois :: [poi()] | '_' | ondemand,
+ pois :: [els_poi:poi()] | '_' | ondemand,
source :: source() | '$2',
words :: sets:set() | '_' | '$3',
version :: version() | '_'
@@ -75,7 +75,7 @@
id := id(),
kind := kind(),
text := binary(),
- pois => [poi()] | ondemand,
+ pois => [els_poi:poi()] | ondemand,
source => source(),
words => sets:set(),
version => version()
@@ -198,7 +198,7 @@ new(Uri, Text, Id, Kind, Source, Version) ->
}.
%% @doc Returns the list of POIs for the current document
--spec pois(item()) -> [poi()].
+-spec pois(item()) -> [els_poi:poi()].
pois(#{uri := Uri, pois := ondemand}) ->
#{pois := POIs} = els_indexing:ensure_deeply_indexed(Uri),
POIs;
@@ -207,12 +207,12 @@ pois(#{pois := POIs}) ->
%% @doc Returns the list of POIs of the given types for the current
%% document
--spec pois(item(), [poi_kind()]) -> [poi()].
+-spec pois(item(), [els_poi:poi_kind()]) -> [els_poi:poi()].
pois(Item, Kinds) ->
[POI || #{kind := K} = POI <- pois(Item), lists:member(K, Kinds)].
-spec get_element_at_pos(item(), non_neg_integer(), non_neg_integer()) ->
- [poi()].
+ [els_poi:poi()].
get_element_at_pos(Item, Line, Column) ->
POIs = pois(Item),
MatchedPOIs = els_poi:match_pos(POIs, {Line, Column}),
@@ -223,19 +223,19 @@ get_element_at_pos(Item, Line, Column) ->
uri(#{uri := Uri}) ->
Uri.
--spec functions_at_pos(item(), non_neg_integer(), non_neg_integer()) -> [poi()].
+-spec functions_at_pos(item(), non_neg_integer(), non_neg_integer()) -> [els_poi:poi()].
functions_at_pos(Item, Line, Column) ->
POIs = get_element_at_pos(Item, Line, Column),
[POI || #{kind := 'function'} = POI <- POIs].
-spec applications_at_pos(item(), non_neg_integer(), non_neg_integer()) ->
- [poi()].
+ [els_poi:poi()].
applications_at_pos(Item, Line, Column) ->
POIs = get_element_at_pos(Item, Line, Column),
[POI || #{kind := 'application'} = POI <- POIs].
-spec wrapping_functions(item(), non_neg_integer(), non_neg_integer()) ->
- [poi()].
+ [els_poi:poi()].
wrapping_functions(Document, Line, Column) ->
Range = #{from => {Line, Column}, to => {Line, Column}},
Functions = pois(Document, ['function']),
@@ -245,7 +245,7 @@ wrapping_functions(Document, Line, Column) ->
els_range:in(Range, WR)
].
--spec wrapping_functions(item(), range()) -> [poi()].
+-spec wrapping_functions(item(), range()) -> [els_poi:poi()].
wrapping_functions(Document, Range) ->
#{start := #{character := Character, line := Line}} = Range,
wrapping_functions(Document, Line, Character).
diff --git a/apps/els_lsp/src/els_dt_references.erl b/apps/els_lsp/src/els_dt_references.erl
index 1f6f35d59..40f89bddc 100644
--- a/apps/els_lsp/src/els_dt_references.erl
+++ b/apps/els_lsp/src/els_dt_references.erl
@@ -41,7 +41,7 @@
-record(els_dt_references, {
id :: any() | '_',
uri :: uri() | '_',
- range :: poi_range() | '_',
+ range :: els_poi:poi_range() | '_',
version :: version() | '_'
}).
-type els_dt_references() :: #els_dt_references{}.
@@ -49,7 +49,7 @@
-type item() :: #{
id := any(),
uri := uri(),
- range := poi_range(),
+ range := els_poi:poi_range(),
version := version()
}.
-export_type([item/0]).
@@ -79,7 +79,7 @@ opts() ->
%% API
%%==============================================================================
--spec from_item(poi_kind(), item()) -> els_dt_references().
+-spec from_item(els_poi:poi_kind(), item()) -> els_dt_references().
from_item(Kind, #{
id := Id,
uri := Uri,
@@ -127,12 +127,12 @@ versioned_delete_by_uri(Uri, Version) ->
end),
ok = els_db:select_delete(name(), MS).
--spec insert(poi_kind(), item()) -> ok | {error, any()}.
+-spec insert(els_poi:poi_kind(), item()) -> ok | {error, any()}.
insert(Kind, Map) when is_map(Map) ->
Record = from_item(Kind, Map),
els_db:write(name(), Record).
--spec versioned_insert(poi_kind(), item()) -> ok | {error, any()}.
+-spec versioned_insert(els_poi:poi_kind(), item()) -> ok | {error, any()}.
versioned_insert(Kind, #{id := Id, version := Version} = Map) ->
Record = from_item(Kind, Map),
Condition = fun(#els_dt_references{version = CurrentVersion}) ->
@@ -141,7 +141,7 @@ versioned_insert(Kind, #{id := Id, version := Version} = Map) ->
els_db:conditional_write(name(), Id, Record, Condition).
%% @doc Find by id
--spec find_by_id(poi_kind(), any()) -> {ok, [item()]} | {error, any()}.
+-spec find_by_id(els_poi:poi_kind(), any()) -> {ok, [item()]} | {error, any()}.
find_by_id(Kind, Id) ->
InternalId = {kind_to_category(Kind), Id},
Pattern = #els_dt_references{id = InternalId, _ = '_'},
@@ -154,7 +154,7 @@ find_by(#els_dt_references{id = Id} = Pattern) ->
{ok, Items} = els_db:match(name(), Pattern),
{ok, [to_item(Item) || Item <- Items]}.
--spec kind_to_category(poi_kind()) -> poi_category().
+-spec kind_to_category(els_poi:poi_kind()) -> poi_category().
kind_to_category(Kind) when
Kind =:= application;
Kind =:= export_entry;
diff --git a/apps/els_lsp/src/els_folding_range_provider.erl b/apps/els_lsp/src/els_folding_range_provider.erl
index 1e4189b26..5d15c9832 100644
--- a/apps/els_lsp/src/els_folding_range_provider.erl
+++ b/apps/els_lsp/src/els_folding_range_provider.erl
@@ -28,8 +28,8 @@ handle_request({document_foldingrange, Params}, _State) ->
Response =
case
[
- folding_range(Range)
- || #{data := #{folding_range := Range = #{}}} <- POIs
+ poi_range_to_folding_range(els_poi:folding_range(POI))
+ || POI <- POIs, els_poi:folding_range(POI) =/= oneliner
]
of
[] -> null;
@@ -41,8 +41,8 @@ handle_request({document_foldingrange, Params}, _State) ->
%% Internal functions
%%==============================================================================
--spec folding_range(poi_range()) -> folding_range().
-folding_range(#{from := {FromLine, FromCol}, to := {ToLine, ToCol}}) ->
+-spec poi_range_to_folding_range(els_poi:poi_range()) -> folding_range().
+poi_range_to_folding_range(#{from := {FromLine, FromCol}, to := {ToLine, ToCol}}) ->
#{
startLine => FromLine - 1,
startCharacter => FromCol,
diff --git a/apps/els_lsp/src/els_hover_provider.erl b/apps/els_lsp/src/els_hover_provider.erl
index d097ef390..e084e2af4 100644
--- a/apps/els_lsp/src/els_hover_provider.erl
+++ b/apps/els_lsp/src/els_hover_provider.erl
@@ -65,7 +65,7 @@ get_docs({Uri, Line, Character}, _) ->
POIs = els_dt_document:get_element_at_pos(Doc, Line + 1, Character + 1),
do_get_docs(Uri, POIs).
--spec do_get_docs(uri(), [poi()]) -> map() | null.
+-spec do_get_docs(uri(), [els_poi:poi()]) -> map() | null.
do_get_docs(_Uri, []) ->
null;
do_get_docs(Uri, [POI | Rest]) ->
diff --git a/apps/els_lsp/src/els_implementation_provider.erl b/apps/els_lsp/src/els_implementation_provider.erl
index 145fb3e8b..21cbc4e82 100644
--- a/apps/els_lsp/src/els_implementation_provider.erl
+++ b/apps/els_lsp/src/els_implementation_provider.erl
@@ -39,14 +39,14 @@ handle_request({implementation, Params}, _State) ->
els_dt_document:item(),
non_neg_integer(),
non_neg_integer()
-) -> [{uri(), poi()}].
+) -> [{uri(), els_poi:poi()}].
find_implementation(Document, Line, Character) ->
case els_dt_document:get_element_at_pos(Document, Line + 1, Character + 1) of
[POI | _] -> implementation(Document, POI);
[] -> []
end.
--spec implementation(els_dt_document:item(), poi()) -> [{uri(), poi()}].
+-spec implementation(els_dt_document:item(), els_poi:poi()) -> [{uri(), els_poi:poi()}].
implementation(Document, #{kind := application, id := MFA}) ->
#{uri := Uri} = Document,
case callback(MFA) of
diff --git a/apps/els_lsp/src/els_incomplete_parser.erl b/apps/els_lsp/src/els_incomplete_parser.erl
index 8d59ac2cb..178ffc335 100644
--- a/apps/els_lsp/src/els_incomplete_parser.erl
+++ b/apps/els_lsp/src/els_incomplete_parser.erl
@@ -2,16 +2,15 @@
-export([parse_after/2]).
-export([parse_line/2]).
--include("els_lsp.hrl").
-include_lib("kernel/include/logger.hrl").
--spec parse_after(binary(), integer()) -> [poi()].
+-spec parse_after(binary(), integer()) -> [els_poi:poi()].
parse_after(Text, Line) ->
{_, AfterText} = els_text:split_at_line(Text, Line),
{ok, POIs} = els_parser:parse(AfterText),
POIs.
--spec parse_line(binary(), integer()) -> [poi()].
+-spec parse_line(binary(), integer()) -> [els_poi:poi()].
parse_line(Text, Line) ->
LineText0 = string:trim(els_text:line(Text, Line), trailing, ",;"),
case els_parser:parse(LineText0) of
diff --git a/apps/els_lsp/src/els_indexing.erl b/apps/els_lsp/src/els_indexing.erl
index b017eec35..b110b5cf6 100644
--- a/apps/els_lsp/src/els_indexing.erl
+++ b/apps/els_lsp/src/els_indexing.erl
@@ -97,13 +97,13 @@ deep_index(Document0) ->
end,
Document.
--spec index_signatures(atom(), uri(), binary(), [poi()], version()) -> ok.
+-spec index_signatures(atom(), uri(), binary(), [els_poi:poi()], version()) -> ok.
index_signatures(Id, Uri, Text, POIs, Version) ->
ok = els_dt_signatures:versioned_delete_by_uri(Uri, Version),
[index_signature(Id, Text, POI, Version) || #{kind := spec} = POI <- POIs],
ok.
--spec index_signature(atom(), binary(), poi(), version()) -> ok.
+-spec index_signature(atom(), binary(), els_poi:poi(), version()) -> ok.
index_signature(_M, _Text, #{id := undefined}, _Version) ->
ok;
index_signature(M, Text, #{id := {F, A}, range := Range}, Version) ->
@@ -115,7 +115,7 @@ index_signature(M, Text, #{id := {F, A}, range := Range}, Version) ->
version => Version
}).
--spec index_references(atom(), uri(), [poi()], version()) -> ok.
+-spec index_references(atom(), uri(), [els_poi:poi()], version()) -> ok.
index_references(Id, Uri, POIs, Version) ->
ok = els_dt_references:versioned_delete_by_uri(Uri, Version),
%% Function
@@ -138,7 +138,7 @@ index_references(Id, Uri, POIs, Version) ->
],
ok.
--spec index_reference(atom(), uri(), poi(), version()) -> ok.
+-spec index_reference(atom(), uri(), els_poi:poi(), version()) -> ok.
index_reference(M, Uri, #{id := {F, A}} = POI, Version) ->
index_reference(M, Uri, POI#{id => {M, F, A}}, Version);
index_reference(_M, Uri, #{kind := Kind, id := Id, range := Range}, Version) ->
diff --git a/apps/els_lsp/src/els_parser.erl b/apps/els_lsp/src/els_parser.erl
index f860ee837..8ffb6fec7 100644
--- a/apps/els_lsp/src/els_parser.erl
+++ b/apps/els_lsp/src/els_parser.erl
@@ -29,7 +29,7 @@
%%==============================================================================
%% API
%%==============================================================================
--spec parse(binary()) -> {ok, [poi()]}.
+-spec parse(binary()) -> {ok, [els_poi:poi()]}.
parse(Text) ->
String = els_utils:to_list(Text),
case erlfmt:read_nodes_string("nofile", String) of
@@ -72,7 +72,7 @@ parse_incomplete_text(Text, {_Line, _Col} = StartLoc) ->
%% Internal Functions
%%==============================================================================
--spec parse_forms([erlfmt_parse:abstract_node()]) -> deep_list(poi()).
+-spec parse_forms([erlfmt_parse:abstract_node()]) -> deep_list(els_poi:poi()).
parse_forms(Forms) ->
[
try
@@ -88,7 +88,7 @@ parse_forms(Forms) ->
|| Form <- Forms
].
--spec parse_form(erlfmt_parse:abstract_node()) -> deep_list(poi()).
+-spec parse_form(erlfmt_parse:abstract_node()) -> deep_list(els_poi:poi()).
parse_form({raw_string, Anno, Text}) ->
StartLoc = erlfmt_scan:get_anno(location, Anno),
RangeTokens = scan_text(Text, StartLoc),
@@ -172,7 +172,7 @@ ensure_dot(Tokens) ->
%% completion items. Using the tokens provides accurate position for the
%% beginning and end for this sections, and can also handle the situations when
%% the code is not parsable.
--spec find_attribute_tokens([erlfmt_scan:token()]) -> [poi()].
+-spec find_attribute_tokens([erlfmt_scan:token()]) -> [els_poi:poi()].
find_attribute_tokens([{'-', Anno}, {atom, _, Name} | [_ | _] = Rest]) when
Name =:= export;
Name =:= export_type
@@ -187,13 +187,13 @@ find_attribute_tokens([{'-', Anno}, {atom, _, spec} | [_ | _] = Rest]) ->
find_attribute_tokens(_) ->
[].
--spec points_of_interest(tree()) -> [[poi()]].
+-spec points_of_interest(tree()) -> [[els_poi:poi()]].
points_of_interest(Tree) ->
FoldFun = fun(T, Acc) -> [do_points_of_interest(T) | Acc] end,
fold(FoldFun, [], Tree).
%% @doc Return the list of points of interest for a given `Tree'.
--spec do_points_of_interest(tree()) -> [poi()].
+-spec do_points_of_interest(tree()) -> [els_poi:poi()].
do_points_of_interest(Tree) ->
try
case erl_syntax:type(Tree) of
@@ -229,7 +229,7 @@ do_points_of_interest(Tree) ->
throw:syntax_error -> []
end.
--spec application(tree()) -> [poi()].
+-spec application(tree()) -> [els_poi:poi()].
application(Tree) ->
case application_mfa(Tree) of
undefined ->
@@ -296,7 +296,7 @@ application_with_variable(Operator, A) ->
undefined
end.
--spec attribute(tree()) -> [poi()].
+-spec attribute(tree()) -> [els_poi:poi()].
attribute(Tree) ->
Pos = erl_syntax:get_pos(Tree),
try {attribute_name_atom(Tree), erl_syntax:attribute_arguments(Tree)} of
@@ -436,7 +436,7 @@ attribute(Tree) ->
[]
end.
--spec record_attribute_pois(tree(), tree(), atom(), tree()) -> [poi()].
+-spec record_attribute_pois(tree(), tree(), atom(), tree()) -> [els_poi:poi()].
record_attribute_pois(Tree, Record, RecordName, Fields) ->
FieldList = record_def_field_name_list(Fields),
{StartLine, StartColumn} = get_start_location(Tree),
@@ -456,7 +456,7 @@ record_attribute_pois(Tree, Record, RecordName, Fields) ->
| record_def_fields(Fields, RecordName)
].
--spec find_compile_options_pois(tree()) -> [poi()].
+-spec find_compile_options_pois(tree()) -> [els_poi:poi()].
find_compile_options_pois(Arg) ->
case erl_syntax:type(Arg) of
list ->
@@ -481,7 +481,7 @@ find_compile_options_pois(Arg) ->
[]
end.
--spec find_export_pois(tree(), export | export_type, tree()) -> [poi()].
+-spec find_export_pois(tree(), export | export_type, tree()) -> [els_poi:poi()].
find_export_pois(Tree, AttrName, Arg) ->
Exports = erl_syntax:list_elements(Arg),
EntryPoiKind =
@@ -496,7 +496,7 @@ find_export_pois(Tree, AttrName, Arg) ->
].
-spec find_export_entry_pois(export_entry | export_type_entry, [tree()]) ->
- [poi()].
+ [els_poi:poi()].
find_export_entry_pois(EntryPoiKind, Exports) ->
lists:flatten(
[
@@ -516,7 +516,7 @@ find_export_entry_pois(EntryPoiKind, Exports) ->
]
).
--spec find_import_entry_pois(tree(), [tree()]) -> [poi()].
+-spec find_import_entry_pois(tree(), [tree()]) -> [els_poi:poi()].
find_import_entry_pois(ModTree, Imports) ->
M = erl_syntax:atom_value(ModTree),
lists:flatten(
@@ -557,7 +557,7 @@ type_args(Args) ->
|| {N, T} <- lists:zip(lists:seq(1, length(Args)), Args)
].
--spec function(tree()) -> [poi()].
+-spec function(tree()) -> [els_poi:poi()].
function(Tree) ->
FunName = erl_syntax:function_name(Tree),
Clauses = erl_syntax:function_clauses(Tree),
@@ -633,7 +633,7 @@ args_from_subtrees(Trees) ->
|| {N, T} <- lists:zip(lists:seq(1, Arity), Trees)
].
--spec implicit_fun(tree()) -> [poi()].
+-spec implicit_fun(tree()) -> [els_poi:poi()].
implicit_fun(Tree) ->
FunSpec =
try erl_syntax_lib:analyze_implicit_fun(Tree) of
@@ -666,7 +666,7 @@ implicit_fun(Tree) ->
[poi(erl_syntax:get_pos(Tree), implicit_fun, FunSpec, Data)]
end.
--spec macro(tree()) -> [poi()].
+-spec macro(tree()) -> [els_poi:poi()].
macro(Tree) ->
Anno = macro_location(Tree),
[poi(Anno, macro, macro_name(Tree))].
@@ -712,7 +712,7 @@ record_def_field_name_list(Fields) ->
undefined
).
--spec record_def_fields(tree(), atom()) -> [poi()].
+-spec record_def_fields(tree(), atom()) -> [els_poi:poi()].
record_def_fields(Fields, RecordName) ->
map_record_def_fields(
fun(F, R) ->
@@ -722,7 +722,7 @@ record_def_fields(Fields, RecordName) ->
RecordName
).
--spec record_access(tree()) -> [poi()].
+-spec record_access(tree()) -> [els_poi:poi()].
record_access(Tree) ->
RecordNode = erl_syntax:record_access_type(Tree),
case is_record_name(RecordNode) of
@@ -732,7 +732,7 @@ record_access(Tree) ->
[]
end.
--spec record_access_pois(tree(), atom()) -> [poi()].
+-spec record_access_pois(tree(), atom()) -> [els_poi:poi()].
record_access_pois(Tree, Record) ->
FieldNode = erl_syntax:record_access_field(Tree),
FieldPoi =
@@ -748,7 +748,7 @@ record_access_pois(Tree, Record) ->
| FieldPoi
].
--spec record_expr(tree()) -> [poi()].
+-spec record_expr(tree()) -> [els_poi:poi()].
record_expr(Tree) ->
RecordNode = erl_syntax:record_expr_type(Tree),
case is_record_name(RecordNode) of
@@ -758,7 +758,7 @@ record_expr(Tree) ->
[]
end.
--spec record_expr_pois(tree(), tree(), atom()) -> [poi()].
+-spec record_expr_pois(tree(), tree(), atom()) -> [els_poi:poi()].
record_expr_pois(Tree, RecordNode, Record) ->
FieldPois = lists:append(
[
@@ -772,7 +772,7 @@ record_expr_pois(Tree, RecordNode, Record) ->
| FieldPois
].
--spec record_type(tree()) -> [poi()].
+-spec record_type(tree()) -> [els_poi:poi()].
record_type(Tree) ->
RecordNode = erl_syntax:record_type_name(Tree),
case is_record_name(RecordNode) of
@@ -782,7 +782,7 @@ record_type(Tree) ->
[]
end.
--spec record_type_pois(tree(), tree(), atom()) -> [poi()].
+-spec record_type_pois(tree(), tree(), atom()) -> [els_poi:poi()].
record_type_pois(Tree, RecordNode, Record) ->
FieldPois = lists:append(
[
@@ -796,7 +796,7 @@ record_type_pois(Tree, RecordNode, Record) ->
| FieldPois
].
--spec record_field_name(tree(), atom(), poi_kind()) -> [poi()].
+-spec record_field_name(tree(), atom(), els_poi:poi_kind()) -> [els_poi:poi()].
record_field_name(FieldNode, Record, Kind) ->
NameNode =
case erl_syntax:type(FieldNode) of
@@ -831,7 +831,7 @@ is_record_name(RecordNameNode) ->
false
end.
--spec type_application(tree()) -> [poi()].
+-spec type_application(tree()) -> [els_poi:poi()].
type_application(Tree) ->
Type = erl_syntax:type(Tree),
case erl_syntax_lib:analyze_type_application(Tree) of
@@ -859,7 +859,7 @@ type_application(Tree) ->
[poi(Pos, type_application, Id)]
end.
--spec variable(tree()) -> [poi()].
+-spec variable(tree()) -> [els_poi:poi()].
variable(Tree) ->
Pos = erl_syntax:get_pos(Tree),
case Pos of
@@ -867,7 +867,7 @@ variable(Tree) ->
_ -> [poi(Pos, variable, node_name(Tree))]
end.
--spec atom(tree()) -> [poi()].
+-spec atom(tree()) -> [els_poi:poi()].
atom(Tree) ->
Pos = erl_syntax:get_pos(Tree),
case Pos of
@@ -951,12 +951,13 @@ get_name_arity(Tree) ->
false
end.
--spec poi(pos() | {pos(), pos()} | erl_anno:anno(), poi_kind(), any()) -> poi().
+-spec poi(pos() | {pos(), pos()} | erl_anno:anno(), els_poi:poi_kind(), any()) ->
+ els_poi:poi().
poi(Pos, Kind, Id) ->
poi(Pos, Kind, Id, undefined).
--spec poi(pos() | {pos(), pos()} | erl_anno:anno(), poi_kind(), any(), any()) ->
- poi().
+-spec poi(pos() | {pos(), pos()} | erl_anno:anno(), els_poi:poi_kind(), any(), any()) ->
+ els_poi:poi().
poi(Pos, Kind, Id, Data) ->
Range = els_range:range(Pos, Kind, Id, Data),
els_poi:new(Range, Kind, Id, Data).
@@ -1173,7 +1174,7 @@ skip_function_entries(FunList) ->
%% Helpers for determining valid Folding Ranges
-spec exceeds_one_line(erl_anno:line(), erl_anno:line()) ->
- poi_range() | oneliner.
+ els_poi:poi_range() | oneliner.
exceeds_one_line(StartLine, EndLine) when EndLine > StartLine ->
#{
from => {StartLine, ?END_OF_LINE},
diff --git a/apps/els_lsp/src/els_poi.erl b/apps/els_lsp/src/els_poi.erl
deleted file mode 100644
index 53cc5abf9..000000000
--- a/apps/els_lsp/src/els_poi.erl
+++ /dev/null
@@ -1,67 +0,0 @@
-%%==============================================================================
-%% The Point Of Interest (a.k.a. _poi_) Data Structure
-%%==============================================================================
--module(els_poi).
-
-%% Constructor
--export([
- new/3,
- new/4
-]).
-
--export([
- match_pos/2,
- sort/1
-]).
-
-%%==============================================================================
-%% Includes
-%%==============================================================================
--include("els_lsp.hrl").
-
-%%==============================================================================
-%% API
-%%==============================================================================
-
-%% @doc Constructor for a Point of Interest.
--spec new(poi_range(), poi_kind(), any()) -> poi().
-new(Range, Kind, Id) ->
- new(Range, Kind, Id, undefined).
-
-%% @doc Constructor for a Point of Interest.
--spec new(poi_range(), poi_kind(), any(), any()) -> poi().
-new(Range, Kind, Id, Data) ->
- #{
- kind => Kind,
- id => Id,
- data => Data,
- range => Range
- }.
-
--spec match_pos([poi()], pos()) -> [poi()].
-match_pos(POIs, Pos) ->
- [
- POI
- || #{
- range := #{
- from := From,
- to := To
- }
- } = POI <- POIs,
- (From =< Pos) andalso (Pos =< To)
- ].
-
-%% @doc Sorts pois based on their range
-%%
-%% Order is defined using els_range:compare/2.
--spec sort([poi()]) -> [poi()].
-sort(POIs) ->
- lists:sort(fun compare/2, POIs).
-
-%%==============================================================================
-%% Internal Functions
-%%==============================================================================
-
--spec compare(poi(), poi()) -> boolean().
-compare(#{range := A}, #{range := B}) ->
- els_range:compare(A, B).
diff --git a/apps/els_lsp/src/els_poi_define.erl b/apps/els_lsp/src/els_poi_define.erl
new file mode 100644
index 000000000..ac11471de
--- /dev/null
+++ b/apps/els_lsp/src/els_poi_define.erl
@@ -0,0 +1,19 @@
+-module(els_poi_define).
+
+-behaviour(els_poi).
+-export([label/1, symbol_kind/0]).
+
+-include("els_lsp.hrl").
+
+-opaque id() :: {atom(), arity()}.
+-export_type([id/0]).
+
+-spec label(els_poi:poi()) -> binary().
+label(#{id := {Name, Arity}}) ->
+ els_utils:to_binary(io_lib:format("~s/~p", [Name, Arity]));
+label(#{id := Name}) when is_atom(Name) ->
+ atom_to_binary(Name, utf8).
+
+-spec symbol_kind() -> ?SYMBOLKIND_CONSTANT.
+symbol_kind() ->
+ ?SYMBOLKIND_CONSTANT.
diff --git a/apps/els_lsp/src/els_poi_function.erl b/apps/els_lsp/src/els_poi_function.erl
new file mode 100644
index 000000000..e57f7ea7d
--- /dev/null
+++ b/apps/els_lsp/src/els_poi_function.erl
@@ -0,0 +1,17 @@
+-module(els_poi_function).
+
+-behaviour(els_poi).
+-export([label/1, symbol_kind/0]).
+
+-include("els_lsp.hrl").
+
+-opaque id() :: {atom(), arity()}.
+-export_type([id/0]).
+
+-spec label(els_poi:poi()) -> binary().
+label(#{id := {F, A}}) ->
+ els_utils:to_binary(io_lib:format("~s/~p", [F, A])).
+
+-spec symbol_kind() -> ?SYMBOLKIND_FUNCTION.
+symbol_kind() ->
+ ?SYMBOLKIND_FUNCTION.
diff --git a/apps/els_lsp/src/els_poi_record.erl b/apps/els_lsp/src/els_poi_record.erl
new file mode 100644
index 000000000..19132ee1c
--- /dev/null
+++ b/apps/els_lsp/src/els_poi_record.erl
@@ -0,0 +1,17 @@
+-module(els_poi_record).
+
+-behaviour(els_poi).
+-export([label/1, symbol_kind/0]).
+
+-include("els_lsp.hrl").
+
+-opaque id() :: {atom(), arity()}.
+-export_type([id/0]).
+
+-spec label(els_poi:poi()) -> binary().
+label(#{id := Name}) when is_atom(Name) ->
+ atom_to_binary(Name, utf8).
+
+-spec symbol_kind() -> ?SYMBOLKIND_STRUCT.
+symbol_kind() ->
+ ?SYMBOLKIND_STRUCT.
diff --git a/apps/els_lsp/src/els_poi_type_definition.erl b/apps/els_lsp/src/els_poi_type_definition.erl
new file mode 100644
index 000000000..b2ce74a38
--- /dev/null
+++ b/apps/els_lsp/src/els_poi_type_definition.erl
@@ -0,0 +1,17 @@
+-module(els_poi_type_definition).
+
+-behaviour(els_poi).
+-export([label/1, symbol_kind/0]).
+
+-include("els_lsp.hrl").
+
+-opaque id() :: {atom(), arity()}.
+-export_type([id/0]).
+
+-spec label(els_poi:poi()) -> binary().
+label(#{id := {Name, Arity}}) ->
+ els_utils:to_binary(io_lib:format("~s/~p", [Name, Arity])).
+
+-spec symbol_kind() -> ?SYMBOLKIND_TYPE_PARAMETER.
+symbol_kind() ->
+ ?SYMBOLKIND_TYPE_PARAMETER.
diff --git a/apps/els_lsp/src/els_range.erl b/apps/els_lsp/src/els_range.erl
index 5e3c2f339..88f26c76e 100644
--- a/apps/els_lsp/src/els_range.erl
+++ b/apps/els_lsp/src/els_range.erl
@@ -12,7 +12,7 @@
inclusion_range/2
]).
--spec compare(poi_range(), poi_range()) -> boolean().
+-spec compare(els_poi:poi_range(), els_poi:poi_range()) -> boolean().
compare(
#{from := FromA, to := ToA},
#{from := FromB, to := ToB}
@@ -28,12 +28,12 @@ compare(
compare(_, _) ->
false.
--spec in(poi_range(), poi_range()) -> boolean().
+-spec in(els_poi:poi_range(), els_poi:poi_range()) -> boolean().
in(#{from := FromA, to := ToA}, #{from := FromB, to := ToB}) ->
FromA >= FromB andalso ToA =< ToB.
--spec range(pos() | {pos(), pos()} | erl_anno:anno(), poi_kind(), any(), any()) ->
- poi_range().
+-spec range(pos() | {pos(), pos()} | erl_anno:anno(), els_poi:poi_kind(), any(), any()) ->
+ els_poi:poi_range().
range({{_Line, _Column} = From, {_ToLine, _ToColumn} = To}, Name, _, _Data) when
Name =:= export;
Name =:= export_type;
@@ -48,19 +48,19 @@ range({Line, Column}, function_clause, {F, _A, _Index}, _Data) ->
range(Anno, _Type, _Id, _Data) ->
range(Anno).
--spec range(erl_anno:anno()) -> poi_range().
+-spec range(erl_anno:anno()) -> els_poi:poi_range().
range(Anno) ->
From = erl_anno:location(Anno),
%% To = erl_anno:end_location(Anno),
To = proplists:get_value(end_location, erl_anno:to_term(Anno)),
#{from => From, to => To}.
--spec line(poi_range()) -> poi_range().
+-spec line(els_poi:poi_range()) -> els_poi:poi_range().
line(#{from := {FromL, _}, to := {ToL, _}}) ->
#{from => {FromL, 1}, to => {ToL + 1, 1}}.
%% @doc Converts a LSP range into a POI range
--spec to_poi_range(range()) -> poi_range().
+-spec to_poi_range(range()) -> els_poi:poi_range().
to_poi_range(#{'start' := Start, 'end' := End}) ->
#{'line' := LineStart, 'character' := CharStart} = Start,
#{'line' := LineEnd, 'character' := CharEnd} = End,
@@ -77,7 +77,7 @@ to_poi_range(#{<<"start">> := Start, <<"end">> := End}) ->
}.
-spec inclusion_range(uri(), els_dt_document:item()) ->
- {ok, poi_range()} | error.
+ {ok, els_poi:poi_range()} | error.
inclusion_range(Uri, Document) ->
Path = binary_to_list(els_uri:path(Uri)),
case
diff --git a/apps/els_lsp/src/els_references_provider.erl b/apps/els_lsp/src/els_references_provider.erl
index ad595bfdd..c2c4f35dd 100644
--- a/apps/els_lsp/src/els_references_provider.erl
+++ b/apps/els_lsp/src/els_references_provider.erl
@@ -53,7 +53,7 @@ handle_request({references, Params}, _State) ->
%% Internal functions
%%==============================================================================
--spec find_references(uri(), poi()) -> [location()].
+-spec find_references(uri(), els_poi:poi()) -> [location()].
find_references(Uri, #{
kind := Kind,
id := Id
@@ -127,7 +127,7 @@ find_references(_Uri, #{kind := Kind, id := Name}) when
find_references(_Uri, _POI) ->
[].
--spec find_scoped_references_for_def(uri(), poi()) -> [{uri(), poi()}].
+-spec find_scoped_references_for_def(uri(), els_poi:poi()) -> [{uri(), els_poi:poi()}].
find_scoped_references_for_def(Uri, #{kind := Kind, id := Name}) ->
Kinds = kind_to_ref_kinds(Kind),
Refs = els_scope:local_and_includer_pois(Uri, Kinds),
@@ -138,7 +138,7 @@ find_scoped_references_for_def(Uri, #{kind := Kind, id := Name}) ->
N =:= Name
].
--spec kind_to_ref_kinds(poi_kind()) -> [poi_kind()].
+-spec kind_to_ref_kinds(els_poi:poi_kind()) -> [els_poi:poi_kind()].
kind_to_ref_kinds(define) ->
[macro];
kind_to_ref_kinds(record) ->
@@ -176,16 +176,16 @@ find_references_to_module(Uri) ->
ExcludeLocalRefs = fun(Loc) -> maps:get(uri, Loc) =/= Uri end,
lists:filter(ExcludeLocalRefs, ExportRefs ++ ExportTypeRefs ++ BehaviourRefs).
--spec find_references_for_id(poi_kind(), any()) -> [location()].
+-spec find_references_for_id(els_poi:poi_kind(), any()) -> [location()].
find_references_for_id(Kind, Id) ->
{ok, Refs} = els_dt_references:find_by_id(Kind, Id),
[location(U, R) || #{uri := U, range := R} <- Refs].
--spec uri_pois_to_locations([{uri(), poi()}]) -> [location()].
+-spec uri_pois_to_locations([{uri(), els_poi:poi()}]) -> [location()].
uri_pois_to_locations(Refs) ->
[location(U, R) || {U, #{range := R}} <- Refs].
--spec location(uri(), poi_range()) -> location().
+-spec location(uri(), els_poi:poi_range()) -> location().
location(Uri, Range) ->
#{
uri => Uri,
diff --git a/apps/els_lsp/src/els_rename_provider.erl b/apps/els_lsp/src/els_rename_provider.erl
index 9685a51b3..024662bdd 100644
--- a/apps/els_lsp/src/els_rename_provider.erl
+++ b/apps/els_lsp/src/els_rename_provider.erl
@@ -45,7 +45,7 @@ handle_request({rename, Params}, _State) ->
%%==============================================================================
%% Internal functions
%%==============================================================================
--spec workspace_edits(uri(), [poi()], binary()) -> null | [any()].
+-spec workspace_edits(uri(), [els_poi:poi()], binary()) -> null | [any()].
workspace_edits(_Uri, [], _NewName) ->
null;
workspace_edits(OldUri, [#{kind := module} = POI | _], NewName) ->
@@ -172,11 +172,11 @@ workspace_edits(Uri, [#{kind := 'callback'} = POI | _], NewName) ->
workspace_edits(_Uri, _POIs, _NewName) ->
null.
--spec editable_range(poi()) -> range().
+-spec editable_range(els_poi:poi()) -> range().
editable_range(POI) ->
editable_range(POI, function).
--spec editable_range(poi(), function | module) -> range().
+-spec editable_range(els_poi:poi(), function | module) -> range().
editable_range(#{kind := Kind, data := #{mod_range := Range}}, module) when
Kind =:= application;
Kind =:= implicit_fun;
@@ -203,7 +203,7 @@ editable_range(#{kind := Kind, data := #{name_range := Range}}, function) when
editable_range(#{kind := _Kind, range := Range}, _) ->
els_protocol:range(Range).
--spec changes(uri(), poi(), binary()) -> #{uri() => [text_edit()]} | null.
+-spec changes(uri(), els_poi:poi(), binary()) -> #{uri() => [text_edit()]} | null.
changes(Uri, #{kind := module} = Mod, NewName) ->
#{Uri => [#{range => editable_range(Mod), newText => NewName}]};
changes(Uri, #{kind := variable} = Var, NewName) ->
@@ -309,7 +309,7 @@ changes(Uri, #{kind := DefKind} = DefPoi, NewName) when
changes(_Uri, _POI, _NewName) ->
null.
--spec new_name(poi(), binary()) -> binary().
+-spec new_name(els_poi:poi(), binary()) -> binary().
new_name(#{kind := macro}, NewName) ->
<<"?", NewName/binary>>;
new_name(#{kind := record_expr}, NewName) ->
@@ -317,8 +317,8 @@ new_name(#{kind := record_expr}, NewName) ->
new_name(_, NewName) ->
NewName.
--spec convert_references_to_pois([els_dt_references:item()], [poi_kind()]) ->
- [{uri(), poi()}].
+-spec convert_references_to_pois([els_dt_references:item()], [els_poi:poi_kind()]) ->
+ [{uri(), els_poi:poi()}].
convert_references_to_pois(Refs, Kinds) ->
UriPOIs = lists:foldl(
fun
@@ -346,7 +346,7 @@ convert_references_to_pois(Refs, Kinds) ->
).
%% @doc Find all uses of imported function in Uri
--spec import_changes(uri(), poi(), binary()) -> [text_edit()].
+-spec import_changes(uri(), els_poi:poi(), binary()) -> [text_edit()].
import_changes(Uri, #{kind := import_entry, id := {_M, F, A}}, NewName) ->
case els_utils:lookup_document(Uri) of
{ok, Doc} ->
@@ -364,6 +364,6 @@ import_changes(Uri, #{kind := import_entry, id := {_M, F, A}}, NewName) ->
import_changes(_Uri, _POI, _NewName) ->
[].
--spec change(poi(), binary()) -> text_edit().
+-spec change(els_poi:poi(), binary()) -> text_edit().
change(POI, NewName) ->
#{range => editable_range(POI), newText => NewName}.
diff --git a/apps/els_lsp/src/els_scope.erl b/apps/els_lsp/src/els_scope.erl
index 66c1ae35a..f2398f79e 100644
--- a/apps/els_lsp/src/els_scope.erl
+++ b/apps/els_lsp/src/els_scope.erl
@@ -10,8 +10,8 @@
-include("els_lsp.hrl").
%% @doc Return POIs of the provided `Kinds' in the document and included files
--spec local_and_included_pois(els_dt_document:item(), poi_kind() | [poi_kind()]) ->
- [poi()].
+-spec local_and_included_pois(els_dt_document:item(), els_poi:poi_kind() | [els_poi:poi_kind()]) ->
+ [els_poi:poi()].
local_and_included_pois(Document, Kind) when is_atom(Kind) ->
local_and_included_pois(Document, [Kind]);
local_and_included_pois(Document, Kinds) ->
@@ -21,7 +21,7 @@ local_and_included_pois(Document, Kinds) ->
]).
%% @doc Return POIs of the provided `Kinds' in included files from `Document'
--spec included_pois(els_dt_document:item(), [poi_kind()]) -> [poi()].
+-spec included_pois(els_dt_document:item(), [els_poi:poi_kind()]) -> [els_poi:poi()].
included_pois(Document, Kinds) ->
els_diagnostics_utils:traverse_include_graph(
fun(IncludedDocument, _Includer, Acc) ->
@@ -33,15 +33,15 @@ included_pois(Document, Kinds) ->
%% @doc Return POIs of the provided `Kinds' in the local document and files that
%% (maybe recursively) include it
--spec local_and_includer_pois(uri(), [poi_kind()]) ->
- [{uri(), [poi()]}].
+-spec local_and_includer_pois(uri(), [els_poi:poi_kind()]) ->
+ [{uri(), [els_poi:poi()]}].
local_and_includer_pois(LocalUri, Kinds) ->
[
{Uri, find_pois_by_uri(Uri, Kinds)}
|| Uri <- local_and_includers(LocalUri)
].
--spec find_pois_by_uri(uri(), [poi_kind()]) -> [poi()].
+-spec find_pois_by_uri(uri(), [els_poi:poi_kind()]) -> [els_poi:poi()].
find_pois_by_uri(Uri, Kinds) ->
{ok, Document} = els_utils:lookup_document(Uri),
els_dt_document:pois(Document, Kinds).
@@ -72,7 +72,7 @@ find_includers(Uri) ->
find_includers(include_lib, IncludeLibId)
).
--spec find_includers(poi_kind(), string()) -> [uri()].
+-spec find_includers(els_poi:poi_kind(), string()) -> [uri()].
find_includers(Kind, Id) ->
{ok, Items} = els_dt_references:find_by_id(Kind, Id),
[Uri || #{uri := Uri} <- Items].
@@ -80,7 +80,7 @@ find_includers(Kind, Id) ->
%% @doc Find the rough scope of a variable, this is based on heuristics and
%% won't always be correct.
%% `VarRange' is expected to be the range of the variable.
--spec variable_scope_range(poi_range(), els_dt_document:item()) -> poi_range().
+-spec variable_scope_range(els_poi:poi_range(), els_dt_document:item()) -> els_poi:poi_range().
variable_scope_range(VarRange, Document) ->
Attributes = [spec, callback, define, record, type_definition],
AttrPOIs = els_dt_document:pois(Document, Attributes),
@@ -147,20 +147,20 @@ variable_scope_range(VarRange, Document) ->
#{from => From, to => To}
end.
--spec pois_before([poi()], poi_range()) -> [poi()].
+-spec pois_before([els_poi:poi()], els_poi:poi_range()) -> [els_poi:poi()].
pois_before(POIs, VarRange) ->
%% Reverse since we are typically interested in the last POI
lists:reverse([POI || POI <- POIs, els_range:compare(range(POI), VarRange)]).
--spec pois_after([poi()], poi_range()) -> [poi()].
+-spec pois_after([els_poi:poi()], els_poi:poi_range()) -> [els_poi:poi()].
pois_after(POIs, VarRange) ->
[POI || POI <- POIs, els_range:compare(VarRange, range(POI))].
--spec pois_match([poi()], poi_range()) -> [poi()].
+-spec pois_match([els_poi:poi()], els_poi:poi_range()) -> [els_poi:poi()].
pois_match(POIs, Range) ->
[POI || POI <- POIs, els_range:in(Range, range(POI))].
--spec range(poi()) -> poi_range().
+-spec range(els_poi:poi()) -> els_poi:poi_range().
range(#{kind := function, data := #{wrapping_range := Range}}) ->
Range;
range(#{range := Range}) ->
diff --git a/apps/els_lsp/src/els_unused_includes_diagnostics.erl b/apps/els_lsp/src/els_unused_includes_diagnostics.erl
index 66932726f..f9810d976 100644
--- a/apps/els_lsp/src/els_unused_includes_diagnostics.erl
+++ b/apps/els_lsp/src/els_unused_includes_diagnostics.erl
@@ -93,7 +93,7 @@ find_unused_includes(#{uri := Uri} = Document) ->
digraph:delete(Graph),
UnusedIncludes.
--spec update_unused(digraph:graph(), uri(), poi(), [uri()]) -> [uri()].
+-spec update_unused(digraph:graph(), uri(), els_poi:poi(), [uri()]) -> [uri()].
update_unused(Graph, Uri, POI, Acc) ->
case els_code_navigation:goto_definition(Uri, POI) of
{ok, Uri, _DefinitionPOI} ->
@@ -120,7 +120,7 @@ expand_includes(Document) ->
end,
els_diagnostics_utils:traverse_include_graph(AccFun, DG, Document).
--spec inclusion_range(uri(), els_dt_document:item()) -> poi_range().
+-spec inclusion_range(uri(), els_dt_document:item()) -> els_poi:poi_range().
inclusion_range(Uri, Document) ->
case els_range:inclusion_range(Uri, Document) of
{ok, Range} ->
@@ -153,6 +153,6 @@ filter_includes_with_compiler_attributes(Uris) ->
contains_compiler_attributes(Document) ->
compiler_attributes(Document) =/= [].
--spec compiler_attributes(els_dt_document:item()) -> [poi()].
+-spec compiler_attributes(els_dt_document:item()) -> [els_poi:poi()].
compiler_attributes(Document) ->
els_dt_document:pois(Document, [compile]).
diff --git a/apps/els_lsp/src/els_unused_macros_diagnostics.erl b/apps/els_lsp/src/els_unused_macros_diagnostics.erl
index 4c6cfd0e8..5ef831506 100644
--- a/apps/els_lsp/src/els_unused_macros_diagnostics.erl
+++ b/apps/els_lsp/src/els_unused_macros_diagnostics.erl
@@ -52,14 +52,14 @@ source() ->
%%==============================================================================
%% Internal Functions
%%==============================================================================
--spec find_unused_macros(els_dt_document:item()) -> [poi()].
+-spec find_unused_macros(els_dt_document:item()) -> [els_poi:poi()].
find_unused_macros(Document) ->
Defines = els_dt_document:pois(Document, [define]),
Macros = els_dt_document:pois(Document, [macro]),
MacroIds = [Id || #{id := Id} <- Macros],
[POI || #{id := Id} = POI <- Defines, not lists:member(Id, MacroIds)].
--spec make_diagnostic(poi()) -> els_diagnostics:diagnostic().
+-spec make_diagnostic(els_poi:poi()) -> els_diagnostics:diagnostic().
make_diagnostic(#{id := POIId, range := POIRange}) ->
Range = els_protocol:range(POIRange),
MacroName =
diff --git a/apps/els_lsp/src/els_unused_record_fields_diagnostics.erl b/apps/els_lsp/src/els_unused_record_fields_diagnostics.erl
index 620978ef7..4e92e46da 100644
--- a/apps/els_lsp/src/els_unused_record_fields_diagnostics.erl
+++ b/apps/els_lsp/src/els_unused_record_fields_diagnostics.erl
@@ -52,14 +52,14 @@ source() ->
%%==============================================================================
%% Internal Functions
%%==============================================================================
--spec find_unused_record_fields(els_dt_document:item()) -> [poi()].
+-spec find_unused_record_fields(els_dt_document:item()) -> [els_poi:poi()].
find_unused_record_fields(Document) ->
Definitions = els_dt_document:pois(Document, [record_def_field]),
Usages = els_dt_document:pois(Document, [record_field]),
UsagesIds = lists:usort([Id || #{id := Id} <- Usages]),
[POI || #{id := Id} = POI <- Definitions, not lists:member(Id, UsagesIds)].
--spec make_diagnostic(poi()) -> els_diagnostics:diagnostic().
+-spec make_diagnostic(els_poi:poi()) -> els_diagnostics:diagnostic().
make_diagnostic(#{id := {RecName, RecField}, range := POIRange}) ->
Range = els_protocol:range(POIRange),
FullName = els_utils:to_binary(io_lib:format("#~p.~p", [RecName, RecField])),
diff --git a/apps/els_lsp/test/els_parser_SUITE.erl b/apps/els_lsp/test/els_parser_SUITE.erl
index 547021335..a6b151edc 100644
--- a/apps/els_lsp/test/els_parser_SUITE.erl
+++ b/apps/els_lsp/test/els_parser_SUITE.erl
@@ -369,12 +369,12 @@ latin1_source_code(_Config) ->
%%==============================================================================
%% Helper functions
%%==============================================================================
--spec parse_find_pois(string(), poi_kind()) -> [poi()].
+-spec parse_find_pois(string(), els_poi:poi_kind()) -> [els_poi:poi()].
parse_find_pois(Text, Kind) ->
{ok, POIs} = els_parser:parse(Text),
SortedPOIs = els_poi:sort(POIs),
[POI || #{kind := Kind1} = POI <- SortedPOIs, Kind1 =:= Kind].
--spec parse_find_pois(string(), poi_kind(), poi_id()) -> [poi()].
+-spec parse_find_pois(string(), els_poi:poi_kind(), els_poi:poi_id()) -> [els_poi:poi()].
parse_find_pois(Text, Kind, Id) ->
[POI || #{id := Id1} = POI <- parse_find_pois(Text, Kind), Id1 =:= Id].
diff --git a/apps/els_lsp/test/els_parser_macros_SUITE.erl b/apps/els_lsp/test/els_parser_macros_SUITE.erl
index 4c391f1cf..be4dccf25 100644
--- a/apps/els_lsp/test/els_parser_macros_SUITE.erl
+++ b/apps/els_lsp/test/els_parser_macros_SUITE.erl
@@ -232,12 +232,12 @@ other_macro_as_record_name(_Config) ->
%%==============================================================================
%% Helper functions
%%==============================================================================
--spec parse_find_pois(string(), poi_kind()) -> [poi()].
+-spec parse_find_pois(string(), els_poi:poi_kind()) -> [els_poi:poi()].
parse_find_pois(Text, Kind) ->
{ok, POIs} = els_parser:parse(Text),
SortedPOIs = els_poi:sort(POIs),
[POI || #{kind := Kind1} = POI <- SortedPOIs, Kind1 =:= Kind].
--spec parse_find_pois(string(), poi_kind(), poi_id()) -> [poi()].
+-spec parse_find_pois(string(), els_poi:poi_kind(), els_poi:poi_id()) -> [els_poi:poi()].
parse_find_pois(Text, Kind, Id) ->
[POI || #{id := Id1} = POI <- parse_find_pois(Text, Kind), Id1 =:= Id].
From 27a04895213282e4504ea7a791436fc717a04d13 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Wed, 18 May 2022 23:19:17 +0200
Subject: [PATCH 075/239] Improve create undefined function quick action
(#1301)
* Improve the create undefined function quick action
* Create function right after current function
* Create function with correct number of arguments
* Don't leave trailing whitespaces
* Don't create spec
* Update .editorconfig as erlfmt uses 4 spaces for indentation
---
.editorconfig | 2 +-
.../priv/code_navigation/src/code_action.erl | 3 +-
apps/els_lsp/src/els_code_actions.erl | 36 ++++++----
apps/els_lsp/test/els_code_action_SUITE.erl | 68 +++++++++++++++----
4 files changed, 80 insertions(+), 29 deletions(-)
diff --git a/.editorconfig b/.editorconfig
index 7c31c4841..c277dff9a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -8,7 +8,7 @@ end_of_line = LF
[*]
indent_style = space
-indent_size = 2
+indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 80
diff --git a/apps/els_lsp/priv/code_navigation/src/code_action.erl b/apps/els_lsp/priv/code_navigation/src/code_action.erl
index d6a6f2863..21dc75053 100644
--- a/apps/els_lsp/priv/code_navigation/src/code_action.erl
+++ b/apps/els_lsp/priv/code_navigation/src/code_action.erl
@@ -20,5 +20,6 @@ function_c() ->
-include_lib("stdlib/include/assert.hrl").
function_d() ->
- e(),
+ foobar(),
+ foobar(x,y,z),
ok.
diff --git a/apps/els_lsp/src/els_code_actions.erl b/apps/els_lsp/src/els_code_actions.erl
index 4cb24e2de..17c894400 100644
--- a/apps/els_lsp/src/els_code_actions.erl
+++ b/apps/els_lsp/src/els_code_actions.erl
@@ -12,27 +12,39 @@
-include("els_lsp.hrl").
-spec create_function(uri(), range(), binary(), [binary()]) -> [map()].
-create_function(Uri, _Range, _Data, [UndefinedFun]) ->
+create_function(Uri, Range0, _Data, [UndefinedFun]) ->
{ok, Document} = els_utils:lookup_document(Uri),
- case els_poi:sort(els_dt_document:pois(Document)) of
- [] ->
- [];
- POIs ->
- #{range := #{to := {Line, _Col}}} = lists:last(POIs),
- [FunctionName, _Arity] = string:split(UndefinedFun, "/"),
+ Range = els_range:to_poi_range(Range0),
+ FunPOIs = els_dt_document:pois(Document, [function]),
+ %% Figure out which function the error was found in, as we want to
+ %% create the function right after the current function.
+ %% (Where the wrapping_range ends)
+ case
+ [
+ R
+ || #{data := #{wrapping_range := R}} <- FunPOIs,
+ els_range:in(Range, R)
+ ]
+ of
+ [#{to := {Line, _}} | _] ->
+ [Name, ArityBin] = string:split(UndefinedFun, "/"),
+ Arity = binary_to_integer(ArityBin),
+ Args = string:join(lists:duplicate(Arity, "_"), ", "),
+ SpecAndFun = io_lib:format("~s(~s) ->\n ok.\n\n", [Name, Args]),
[
make_edit_action(
Uri,
- <<"Add the undefined function ", UndefinedFun/binary>>,
+ <<"Create function ", UndefinedFun/binary>>,
?CODE_ACTION_KIND_QUICKFIX,
- <<"-spec ", FunctionName/binary, "() -> ok. \n ", FunctionName/binary,
- "() -> \n \t ok.">>,
+ iolist_to_binary(SpecAndFun),
els_protocol:range(#{
from => {Line + 1, 1},
- to => {Line + 2, 1}
+ to => {Line + 1, 1}
})
)
- ]
+ ];
+ _ ->
+ []
end.
-spec export_function(uri(), range(), binary(), [binary()]) -> [map()].
diff --git a/apps/els_lsp/test/els_code_action_SUITE.erl b/apps/els_lsp/test/els_code_action_SUITE.erl
index 6a36ee76a..dcb337c91 100644
--- a/apps/els_lsp/test/els_code_action_SUITE.erl
+++ b/apps/els_lsp/test/els_code_action_SUITE.erl
@@ -18,7 +18,8 @@
fix_module_name/1,
remove_unused_macro/1,
remove_unused_import/1,
- create_undefined_function/1
+ create_undefined_function/1,
+ create_undefined_function_arity/1
]).
%%==============================================================================
@@ -311,22 +312,55 @@ remove_unused_import(Config) ->
create_undefined_function(Config) ->
Uri = ?config(code_action_uri, Config),
Range = els_protocol:range(#{
- from => {?COMMENTS_LINES + 23, 9},
- to => {?COMMENTS_LINES + 23, 39}
+ from => {23, 2},
+ to => {23, 8}
}),
- LineRange = els_range:line(#{
- from => {?COMMENTS_LINES + 23, 9},
- to => {?COMMENTS_LINES + 23, 39}
+ Diag = #{
+ message => <<"function foobar/0 undefined">>,
+ range => Range,
+ severity => 2,
+ source => <<"">>
+ },
+ #{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
+ Expected =
+ [
+ #{
+ edit => #{
+ changes =>
+ #{
+ binary_to_atom(Uri, utf8) =>
+ [
+ #{
+ range =>
+ els_protocol:range(#{
+ from => {27, 1},
+ to => {27, 1}
+ }),
+ newText =>
+ <<"foobar() ->\n ok.\n\n">>
+ }
+ ]
+ }
+ },
+ kind => <<"quickfix">>,
+ title => <<"Create function foobar/0">>
+ }
+ ],
+ ?assertEqual(Expected, Result),
+ ok.
+
+-spec create_undefined_function_arity((config())) -> ok.
+create_undefined_function_arity(Config) ->
+ Uri = ?config(code_action_uri, Config),
+ Range = els_protocol:range(#{
+ from => {24, 2},
+ to => {24, 8}
}),
- {ok, FileName} = els_utils:find_header(
- els_utils:filename_to_atom("stdlib/include/assert.hrl")
- ),
Diag = #{
- message => <<"function e/0 undefined">>,
+ message => <<"function foobar/3 undefined">>,
range => Range,
severity => 2,
- source => <<"">>,
- data => FileName
+ source => <<"">>
},
#{result := Result} = els_client:document_codeaction(Uri, Range, [Diag]),
Expected =
@@ -338,15 +372,19 @@ create_undefined_function(Config) ->
binary_to_atom(Uri, utf8) =>
[
#{
- range => els_protocol:range(LineRange),
+ range =>
+ els_protocol:range(#{
+ from => {27, 1},
+ to => {27, 1}
+ }),
newText =>
- <<"-spec e() -> ok. \n e() -> \n \t ok.">>
+ <<"foobar(_, _, _) ->\n ok.\n\n">>
}
]
}
},
kind => <<"quickfix">>,
- title => <<"Add the undefined function e/0">>
+ title => <<"Create function foobar/3">>
}
],
?assertEqual(Expected, Result),
From b5359d53c5c509759f9d7ed16ba152c5e5065789 Mon Sep 17 00:00:00 2001
From: Stefan Grundmann
Date: Thu, 19 May 2022 00:13:15 +0000
Subject: [PATCH 076/239] fix els_typer on otp25 (#1305)
the undocumented function dialyzer_succ_typings:analyze_callgraph/3 is not in OTP25.
use the (for the purpose of els_typer:get_type_info/1) equivalent
dialyzer_succ_typings:analyze_callgraph/5 when ?OTP_VERSION >= 25
fix #1304
---
apps/els_lsp/src/els_typer.erl | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/apps/els_lsp/src/els_typer.erl b/apps/els_lsp/src/els_typer.erl
index 792f52fab..aa1861210 100644
--- a/apps/els_lsp/src/els_typer.erl
+++ b/apps/els_lsp/src/els_typer.erl
@@ -134,6 +134,24 @@ extract(
-spec get_type_info(analysis()) -> analysis().
+-if(?OTP_RELEASE >= 25).
+get_type_info(
+ #analysis{
+ callgraph = CallGraph,
+ trust_plt = TrustPLT,
+ codeserver = CodeServer
+ } = Analysis
+) ->
+ StrippedCallGraph = remove_external(CallGraph, TrustPLT),
+ NewPlt = dialyzer_succ_typings:analyze_callgraph(
+ StrippedCallGraph,
+ TrustPLT,
+ CodeServer,
+ none,
+ []
+ ),
+ Analysis#analysis{callgraph = StrippedCallGraph, trust_plt = NewPlt}.
+-else.
get_type_info(
#analysis{
callgraph = CallGraph,
@@ -148,6 +166,7 @@ get_type_info(
CodeServer
),
Analysis#analysis{callgraph = StrippedCallGraph, trust_plt = NewPlt}.
+-endif.
-spec remove_external(callgraph(), plt()) -> callgraph().
From 97721deaafc46b78d93046829bacb54f29c2a4ba Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Thu, 19 May 2022 09:11:02 -0700
Subject: [PATCH 077/239] Add telemetry events for diagnostics and indexing
(#1306)
---
apps/els_lsp/src/els_diagnostics.erl | 14 +++++++++++++-
apps/els_lsp/src/els_indexing.erl | 14 +++++++++++++-
apps/els_lsp/src/els_telemetry.erl | 10 ++++++++++
apps/els_lsp/test/els_diagnostics_SUITE.erl | 2 +-
4 files changed, 37 insertions(+), 3 deletions(-)
create mode 100644 apps/els_lsp/src/els_telemetry.erl
diff --git a/apps/els_lsp/src/els_diagnostics.erl b/apps/els_lsp/src/els_diagnostics.erl
index 1ce399c5f..c27b09760 100644
--- a/apps/els_lsp/src/els_diagnostics.erl
+++ b/apps/els_lsp/src/els_diagnostics.erl
@@ -125,6 +125,7 @@ run_diagnostic(Uri, Id) ->
Source = CbModule:source(),
Module = atom_to_binary(els_uri:module(Uri), utf8),
Title = <>,
+ Start = erlang:monotonic_time(millisecond),
Config = #{
task => fun(U, _) -> CbModule:run(U) end,
entries => [Uri],
@@ -137,7 +138,18 @@ run_diagnostic(Uri, Id) ->
false ->
ok
end,
- els_diagnostics_provider:notify(Diagnostics, self())
+ els_diagnostics_provider:notify(Diagnostics, self()),
+ End = erlang:monotonic_time(millisecond),
+ Duration = End - Start,
+ Event = #{
+ uri => Uri,
+ source => Source,
+ duration_ms => Duration,
+ number_of_diagnostics => length(Diagnostics),
+ type => <<"diagnostics">>
+ },
+ ?LOG_DEBUG("Diagnostics completed. [event=~p]", [Event]),
+ els_telemetry:send_notification(Event)
end
},
{ok, Pid} = els_background_job:new(Config),
diff --git a/apps/els_lsp/src/els_indexing.erl b/apps/els_lsp/src/els_indexing.erl
index b110b5cf6..c7a7be880 100644
--- a/apps/els_lsp/src/els_indexing.erl
+++ b/apps/els_lsp/src/els_indexing.erl
@@ -201,6 +201,7 @@ start(Group, Skip, SkipTag, Entries, Source) ->
{Su, Sk, Fa} = index_dir(Dir, Skip, SkipTag, Source),
{Succeeded0 + Su, Skipped0 + Sk, Failed0 + Fa}
end,
+ Start = erlang:monotonic_time(millisecond),
Config = #{
task => Task,
entries => Entries,
@@ -208,11 +209,22 @@ start(Group, Skip, SkipTag, Entries, Source) ->
initial_state => {0, 0, 0},
on_complete =>
fun({Succeeded, Skipped, Failed}) ->
+ End = erlang:monotonic_time(millisecond),
+ Duration = End - Start,
+ Event = #{
+ group => Group,
+ duration_ms => Duration,
+ succeeded => Succeeded,
+ skipped => Skipped,
+ failed => Failed,
+ type => <<"indexing">>
+ },
?LOG_INFO(
"Completed indexing for ~s "
"(succeeded: ~p, skipped: ~p, failed: ~p)",
[Group, Succeeded, Skipped, Failed]
- )
+ ),
+ els_telemetry:send_notification(Event)
end
},
{ok, _Pid} = els_background_job:new(Config),
diff --git a/apps/els_lsp/src/els_telemetry.erl b/apps/els_lsp/src/els_telemetry.erl
new file mode 100644
index 000000000..62a137e66
--- /dev/null
+++ b/apps/els_lsp/src/els_telemetry.erl
@@ -0,0 +1,10 @@
+-module(els_telemetry).
+
+-export([send_notification/1]).
+
+-type event() :: map().
+
+-spec send_notification(event()) -> ok.
+send_notification(Event) ->
+ Method = <<"telemetry/event">>,
+ els_server:send_notification(Method, Event).
diff --git a/apps/els_lsp/test/els_diagnostics_SUITE.erl b/apps/els_lsp/test/els_diagnostics_SUITE.erl
index 361fa1ff0..04a9a8da4 100644
--- a/apps/els_lsp/test/els_diagnostics_SUITE.erl
+++ b/apps/els_lsp/test/els_diagnostics_SUITE.erl
@@ -988,7 +988,7 @@ mock_compiler_telemetry_enabled() ->
-spec wait_for_compiler_telemetry() -> {uri(), [els_diagnostics:diagnostic()]}.
wait_for_compiler_telemetry() ->
receive
- {on_complete_telemetry, Params} ->
+ {on_complete_telemetry, #{type := <<"erlang-diagnostic-codes">>} = Params} ->
Params
end.
From 2347a09dfd351cc055ef92fcf9e942f7ab9b8e0c Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Mon, 23 May 2022 13:37:28 +0200
Subject: [PATCH 078/239] Avoid crash while reloading non-existing file (#1308)
It can happen during a rebase operation that a file appears/disappears
multiple times in a very short timeframe. Specifically, since
`didChangeWatchedFiles` notifications are asynchronous, it can happen
that a file is deleted before a notification is processed by the
server. In such a case, the server should simply ignore the
notification, since a new one will arrive.
Also, there is no need to deeply index files during a rebase, so let's
convert to a shallow indexing.
We mark the source of the file as 'app', which has the only side
effect of indexing references.
---
apps/els_lsp/src/els_indexing.erl | 1 +
apps/els_lsp/src/els_text_synchronization.erl | 23 ++++++++++++++++---
2 files changed, 21 insertions(+), 3 deletions(-)
diff --git a/apps/els_lsp/src/els_indexing.erl b/apps/els_lsp/src/els_indexing.erl
index c7a7be880..2ce08646f 100644
--- a/apps/els_lsp/src/els_indexing.erl
+++ b/apps/els_lsp/src/els_indexing.erl
@@ -10,6 +10,7 @@
maybe_start/0,
ensure_deeply_indexed/1,
shallow_index/2,
+ shallow_index/3,
deep_index/1,
remove/1
]).
diff --git a/apps/els_lsp/src/els_text_synchronization.erl b/apps/els_lsp/src/els_text_synchronization.erl
index 5c1659e10..4587bdf37 100644
--- a/apps/els_lsp/src/els_text_synchronization.erl
+++ b/apps/els_lsp/src/els_text_synchronization.erl
@@ -99,9 +99,26 @@ handle_file_change(Uri, Type) when Type =:= ?FILE_CHANGE_TYPE_DELETED ->
-spec reload_from_disk(uri()) -> ok.
reload_from_disk(Uri) ->
- {ok, Text} = file:read_file(els_uri:path(Uri)),
- {ok, Document} = els_utils:lookup_document(Uri),
- els_indexing:deep_index(Document#{text => Text}),
+ Path = els_uri:path(Uri),
+ case file:read_file(Path) of
+ {ok, Text} ->
+ els_indexing:shallow_index(Uri, Text, app);
+ {error, Error} ->
+ %% File is not accessible. This can happen, for example,
+ %% during a rebase operation, when the file "appears and
+ %% disappears" multiple times in a very short
+ %% timeframe. Just log the fact as a warning, but keep
+ %% going. It should be possible to buffer
+ %% 'didChangeWatchedFiles' requests, but it's not a big
+ %% deal.
+ ?LOG_WARNING(
+ "Error while reloading from disk. Ignoring. "
+ "[uri=~p] [error=~p]",
+ [
+ Uri, Error
+ ]
+ )
+ end,
ok.
-spec background_index(els_dt_document:item()) -> {ok, pid()}.
From c6f64583dd5062f32b99111031d60bf08a9bc138 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Wed, 25 May 2022 09:44:31 +0200
Subject: [PATCH 079/239] Return set, log as an error (#1311)
---
apps/els_lsp/src/els_dt_document.erl | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/apps/els_lsp/src/els_dt_document.erl b/apps/els_lsp/src/els_dt_document.erl
index 5bf126fa5..f372dc7bb 100644
--- a/apps/els_lsp/src/els_dt_document.erl
+++ b/apps/els_lsp/src/els_dt_document.erl
@@ -298,6 +298,9 @@ get_words(Text) ->
Words
end,
lists:foldl(Fun, sets:new(), Tokens);
- {error, ErrorInfo, _ErrorLocation} ->
- ?LOG_DEBUG("Errors while get_words ~p", [ErrorInfo])
+ {error, ErrorInfo, ErrorLocation} ->
+ ?LOG_WARNING("Errors while get_words [info=~p] [location=~p]", [
+ ErrorInfo, ErrorLocation
+ ]),
+ sets:new()
end.
From 0e3411fc44b04aa41c7f91ca97c20ec9d8e9e56c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Wed, 25 May 2022 09:45:11 +0200
Subject: [PATCH 080/239] Only follow includes for hrl files (#1313)
---
apps/els_lsp/src/els_scope.erl | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/apps/els_lsp/src/els_scope.erl b/apps/els_lsp/src/els_scope.erl
index f2398f79e..a266b7d79 100644
--- a/apps/els_lsp/src/els_scope.erl
+++ b/apps/els_lsp/src/els_scope.erl
@@ -65,12 +65,18 @@ find_includers_loop(Uri, Acc0) ->
-spec find_includers(uri()) -> [uri()].
find_includers(Uri) ->
- IncludeId = els_utils:include_id(els_uri:path(Uri)),
- IncludeLibId = els_utils:include_lib_id(els_uri:path(Uri)),
- lists:usort(
- find_includers(include, IncludeId) ++
- find_includers(include_lib, IncludeLibId)
- ).
+ Path = els_uri:path(Uri),
+ case filename:extension(Path) of
+ <<".hrl">> ->
+ IncludeId = els_utils:include_id(Path),
+ IncludeLibId = els_utils:include_lib_id(Path),
+ lists:usort(
+ find_includers(include, IncludeId) ++
+ find_includers(include_lib, IncludeLibId)
+ );
+ _ ->
+ []
+ end.
-spec find_includers(els_poi:poi_kind(), string()) -> [uri()].
find_includers(Kind, Id) ->
From b5a27307456ed5aac138b43b4d989d15433db76c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Thu, 26 May 2022 11:50:58 +0200
Subject: [PATCH 081/239] Add atom typo diagnostics (#1315)
---
.../priv/code_navigation/src/atom_typo.erl | 19 +++++
.../els_lsp/src/els_atom_typo_diagnostics.erl | 72 +++++++++++++++++++
apps/els_lsp/src/els_code_action_provider.erl | 3 +-
apps/els_lsp/src/els_code_actions.erl | 15 +++-
apps/els_lsp/src/els_diagnostics.erl | 1 +
apps/els_lsp/test/els_diagnostics_SUITE.erl | 49 +++++++++++++
6 files changed, 157 insertions(+), 2 deletions(-)
create mode 100644 apps/els_lsp/priv/code_navigation/src/atom_typo.erl
create mode 100644 apps/els_lsp/src/els_atom_typo_diagnostics.erl
diff --git a/apps/els_lsp/priv/code_navigation/src/atom_typo.erl b/apps/els_lsp/priv/code_navigation/src/atom_typo.erl
new file mode 100644
index 000000000..152a6587d
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/atom_typo.erl
@@ -0,0 +1,19 @@
+-module(atom_typo).
+-export([f/0]).
+
+f() ->
+ %% typos
+ ture,
+ falsee,
+ fales,
+ undifened,
+ udefined,
+ errorr,
+ %% ok
+ true,
+ false,
+ fails,
+ undefined,
+ unified,
+ error,
+ ok.
diff --git a/apps/els_lsp/src/els_atom_typo_diagnostics.erl b/apps/els_lsp/src/els_atom_typo_diagnostics.erl
new file mode 100644
index 000000000..dd2cc42d6
--- /dev/null
+++ b/apps/els_lsp/src/els_atom_typo_diagnostics.erl
@@ -0,0 +1,72 @@
+%%==============================================================================
+%% AtomTypo diagnostics
+%% Catch common atom typos
+%%==============================================================================
+-module(els_atom_typo_diagnostics).
+
+%%==============================================================================
+%% Behaviours
+%%==============================================================================
+-behaviour(els_diagnostics).
+
+%%==============================================================================
+%% Exports
+%%==============================================================================
+-export([
+ is_default/0,
+ run/1,
+ source/0
+]).
+
+%%==============================================================================
+%% Includes
+%%==============================================================================
+-include("els_lsp.hrl").
+
+%%==============================================================================
+%% Callback Functions
+%%==============================================================================
+
+-spec is_default() -> boolean().
+is_default() ->
+ false.
+
+-spec run(uri()) -> [els_diagnostics:diagnostic()].
+run(Uri) ->
+ case els_utils:lookup_document(Uri) of
+ {error, _Error} ->
+ [];
+ {ok, Document} ->
+ Atoms = [<<"false">>, <<"true">>, <<"undefined">>, <<"error">>],
+ POIs = els_dt_document:pois(Document, [atom]),
+ [
+ make_diagnostic(POI, Atom)
+ || #{id := Id} = POI <- POIs,
+ Atom <- Atoms,
+ atom_to_binary(Id, utf8) =/= Atom,
+ els_utils:jaro_distance(atom_to_binary(Id, utf8), Atom) > 0.9
+ ]
+ end.
+
+-spec source() -> binary().
+source() ->
+ <<"AtomTypo">>.
+
+%%==============================================================================
+%% Internal Functions
+%%==============================================================================
+-spec make_diagnostic(els_poi:poi(), binary()) -> els_diagnostics:diagnostic().
+make_diagnostic(#{range := Range}, Atom) ->
+ Message = els_utils:to_binary(
+ io_lib:format(
+ "Atom typo? Did you mean: ~s",
+ [Atom]
+ )
+ ),
+ Severity = ?DIAGNOSTIC_WARNING,
+ els_diagnostics:make_diagnostic(
+ els_protocol:range(Range),
+ Message,
+ Severity,
+ source()
+ ).
diff --git a/apps/els_lsp/src/els_code_action_provider.erl b/apps/els_lsp/src/els_code_action_provider.erl
index 3f4bbfdc7..213e82418 100644
--- a/apps/els_lsp/src/els_code_action_provider.erl
+++ b/apps/els_lsp/src/els_code_action_provider.erl
@@ -51,7 +51,8 @@ make_code_actions(
fun els_code_actions:fix_module_name/4},
{"Unused macro: (.*)", fun els_code_actions:remove_macro/4},
{"function (.*) undefined", fun els_code_actions:create_function/4},
- {"Unused file: (.*)", fun els_code_actions:remove_unused/4}
+ {"Unused file: (.*)", fun els_code_actions:remove_unused/4},
+ {"Atom typo\\? Did you mean: (.*)", fun els_code_actions:fix_atom_typo/4}
],
Uri,
Range,
diff --git a/apps/els_lsp/src/els_code_actions.erl b/apps/els_lsp/src/els_code_actions.erl
index 17c894400..7e8c06598 100644
--- a/apps/els_lsp/src/els_code_actions.erl
+++ b/apps/els_lsp/src/els_code_actions.erl
@@ -6,7 +6,8 @@
ignore_variable/4,
remove_macro/4,
remove_unused/4,
- suggest_variable/4
+ suggest_variable/4,
+ fix_atom_typo/4
]).
-include("els_lsp.hrl").
@@ -177,6 +178,18 @@ remove_unused(Uri, _Range0, Data, [Import]) ->
[]
end.
+-spec fix_atom_typo(uri(), range(), binary(), [binary()]) -> [map()].
+fix_atom_typo(Uri, Range, _Data, [Atom]) ->
+ [
+ make_edit_action(
+ Uri,
+ <<"Fix typo: ", Atom/binary>>,
+ ?CODE_ACTION_KIND_QUICKFIX,
+ Atom,
+ Range
+ )
+ ].
+
-spec ensure_range(els_poi:poi_range(), binary(), [els_poi:poi()]) ->
{ok, els_poi:poi_range()} | error.
ensure_range(#{from := {Line, _}}, SubjectId, POIs) ->
diff --git a/apps/els_lsp/src/els_diagnostics.erl b/apps/els_lsp/src/els_diagnostics.erl
index c27b09760..2c5c500aa 100644
--- a/apps/els_lsp/src/els_diagnostics.erl
+++ b/apps/els_lsp/src/els_diagnostics.erl
@@ -65,6 +65,7 @@
-spec available_diagnostics() -> [diagnostic_id()].
available_diagnostics() ->
[
+ <<"atom_typo">>,
<<"bound_var_in_pattern">>,
<<"compiler">>,
<<"crossref">>,
diff --git a/apps/els_lsp/test/els_diagnostics_SUITE.erl b/apps/els_lsp/test/els_diagnostics_SUITE.erl
index 04a9a8da4..92cd367ac 100644
--- a/apps/els_lsp/test/els_diagnostics_SUITE.erl
+++ b/apps/els_lsp/test/els_diagnostics_SUITE.erl
@@ -12,6 +12,7 @@
%% Test cases
-export([
+ atom_typo/1,
bound_var_in_pattern/1,
compiler/1,
compiler_with_behaviour/1,
@@ -90,6 +91,13 @@ init_per_testcase(TestCase, Config) when
mock_rpc(),
mock_code_reload_enabled(),
els_test_utils:init_per_testcase(TestCase, Config);
+init_per_testcase(TestCase, Config) when
+ TestCase =:= atom_typo
+->
+ meck:new(els_atom_typo_diagnostics, [passthrough, no_link]),
+ meck:expect(els_atom_typo_diagnostics, is_default, 0, true),
+ els_mock_diagnostics:setup(),
+ els_test_utils:init_per_testcase(TestCase, Config);
init_per_testcase(TestCase, Config) when
TestCase =:= crossref orelse
TestCase =:= crossref_pseudo_functions orelse
@@ -152,6 +160,13 @@ init_per_testcase(TestCase, Config) ->
els_test_utils:init_per_testcase(TestCase, Config).
-spec end_per_testcase(atom(), config()) -> ok.
+end_per_testcase(TestCase, Config) when
+ TestCase =:= atom_typo
+->
+ meck:unload(els_atom_typo_diagnostics),
+ els_test_utils:end_per_testcase(TestCase, Config),
+ els_mock_diagnostics:teardown(),
+ ok;
end_per_testcase(TestCase, Config) when
TestCase =:= code_reload orelse
TestCase =:= code_reload_sticky_mod
@@ -218,6 +233,40 @@ end_per_testcase(TestCase, Config) ->
%%==============================================================================
%% Testcases
%%==============================================================================
+-spec atom_typo(config()) -> ok.
+atom_typo(_Config) ->
+ Path = src_path("atom_typo.erl"),
+ Source = <<"AtomTypo">>,
+ Errors = [],
+ Warnings = [
+ #{
+ message => <<"Atom typo? Did you mean: true">>,
+ range => {{5, 2}, {5, 6}}
+ },
+ #{
+ message => <<"Atom typo? Did you mean: false">>,
+ range => {{6, 2}, {6, 8}}
+ },
+ #{
+ message => <<"Atom typo? Did you mean: false">>,
+ range => {{7, 2}, {7, 7}}
+ },
+ #{
+ message => <<"Atom typo? Did you mean: undefined">>,
+ range => {{8, 2}, {8, 11}}
+ },
+ #{
+ message => <<"Atom typo? Did you mean: undefined">>,
+ range => {{9, 2}, {9, 10}}
+ },
+ #{
+ message => <<"Atom typo? Did you mean: error">>,
+ range => {{10, 2}, {10, 8}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+
-spec bound_var_in_pattern(config()) -> ok.
bound_var_in_pattern(_Config) ->
Path = src_path("diagnostics_bound_var_in_pattern.erl"),
From f49cb290746d1fbce65010f57cba698df476ad8a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Thu, 26 May 2022 11:52:21 +0200
Subject: [PATCH 082/239] Add completion support for features (#1314)
---
apps/els_lsp/src/els_completion_provider.erl | 49 ++++++++++++++++----
apps/els_lsp/test/els_completion_SUITE.erl | 6 +++
2 files changed, 45 insertions(+), 10 deletions(-)
diff --git a/apps/els_lsp/src/els_completion_provider.erl b/apps/els_lsp/src/els_completion_provider.erl
index 54f07ddca..0a77d536f 100644
--- a/apps/els_lsp/src/els_completion_provider.erl
+++ b/apps/els_lsp/src/els_completion_provider.erl
@@ -233,6 +233,15 @@ find_completions(
%% Check for "-export_type(["
[{'[', _}, {'(', _}, {atom, _, export_type}, {'-', _}] ->
unexported_definitions(Document, type_definition);
+ %% Check for "-feature("
+ [{'(', _}, {atom, _, feature}, {'-', _}] ->
+ features();
+ %% Check for "?FEATURE_ENABLED("
+ [{'(', _}, {var, _, 'FEATURE_ENABLED'}, {'?', _} | _] ->
+ features();
+ %% Check for "?FEATURE_AVAILABLE("
+ [{'(', _}, {var, _, 'FEATURE_AVAILABLE'}, {'?', _} | _] ->
+ features();
%% Check for "-behaviour(anything"
[{atom, _, _}, {'(', _}, {atom, _, Attribute}, {'-', _}] when
Attribute =:= behaviour; Attribute =:= behavior
@@ -287,6 +296,7 @@ attributes() ->
snippet(attribute_dialyzer),
snippet(attribute_export),
snippet(attribute_export_type),
+ snippet(attribute_feature),
snippet(attribute_if),
snippet(attribute_ifdef),
snippet(attribute_ifndef),
@@ -416,6 +426,8 @@ snippet(attribute_on_load) ->
);
snippet(attribute_export_type) ->
snippet(<<"-export_type().">>, <<"export_type([${1:}]).">>);
+snippet(attribute_feature) ->
+ snippet(<<"-feature().">>, <<"feature(${1:Feature}, ${2:enable}).">>);
snippet(attribute_include) ->
snippet(<<"-include().">>, <<"include(${1:}).">>);
snippet(attribute_include_lib) ->
@@ -804,14 +816,16 @@ bifs(type_definition, false = ExportFormat) ->
[completion_item(X, ExportFormat) || X <- POIs];
bifs(define, ExportFormat) ->
Macros = [
- 'MODULE',
- 'MODULE_STRING',
- 'FILE',
- 'LINE',
- 'MACHINE',
- 'FUNCTION_NAME',
- 'FUNCTION_ARITY',
- 'OTP_RELEASE'
+ {'MODULE', none},
+ {'MODULE_STRING', none},
+ {'FILE', none},
+ {'LINE', none},
+ {'MACHINE', none},
+ {'FUNCTION_NAME', none},
+ {'FUNCTION_ARITY', none},
+ {'OTP_RELEASE', none},
+ {{'FEATURE_AVAILABLE', 1}, [{1, "Feature"}]},
+ {{'FEATURE_ENABLED', 1}, [{1, "Feature"}]}
],
Range = #{from => {0, 0}, to => {0, 0}},
POIs = [
@@ -819,9 +833,9 @@ bifs(define, ExportFormat) ->
kind => define,
id => Id,
range => Range,
- data => #{args => none}
+ data => #{args => Args}
}
- || Id <- Macros
+ || {Id, Args} <- Macros
],
[completion_item(X, ExportFormat) || X <- POIs].
@@ -905,6 +919,21 @@ completion_item(#{kind := Kind = define, id := Name, data := Info}, Data, _) ->
data => Data
}.
+-spec features() -> items().
+features() ->
+ %% Hardcoded for now. Could use erl_features:all() in the future.
+ Features = [maybe_expr],
+ [
+ #{
+ label => atom_to_binary(Feature, utf8),
+ kind => ?COMPLETION_ITEM_KIND_CONSTANT,
+ insertText => atom_to_binary(Feature, utf8),
+ insertTextFormat => ?INSERT_TEXT_FORMAT_PLAIN_TEXT,
+ data => #{}
+ }
+ || Feature <- Features
+ ].
+
-spec macro_label(atom() | {atom(), non_neg_integer()}) -> binary().
macro_label({Name, Arity}) ->
els_utils:to_binary(io_lib:format("~ts/~p", [Name, Arity]));
diff --git a/apps/els_lsp/test/els_completion_SUITE.erl b/apps/els_lsp/test/els_completion_SUITE.erl
index cd49477ec..1c84091cd 100644
--- a/apps/els_lsp/test/els_completion_SUITE.erl
+++ b/apps/els_lsp/test/els_completion_SUITE.erl
@@ -120,6 +120,12 @@ attributes(Config) ->
kind => ?COMPLETION_ITEM_KIND_SNIPPET,
label => <<"-export_type().">>
},
+ #{
+ insertText => <<"feature(${1:Feature}, ${2:enable}).">>,
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
+ kind => ?COMPLETION_ITEM_KIND_SNIPPET,
+ label => <<"-feature().">>
+ },
#{
insertText => <<"if(${1:Pred}).\n${2:}\n-endif.">>,
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
From d5f6dce2e4488fb200c394eed900f4fe773f3199 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Thu, 26 May 2022 11:52:31 +0200
Subject: [PATCH 083/239] Truncate log file once it reaches 10MB, keep max 5
archive files (#1317)
---
apps/els_lsp/src/erlang_ls.erl | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/apps/els_lsp/src/erlang_ls.erl b/apps/els_lsp/src/erlang_ls.erl
index 3c050cbbd..72e7297e5 100644
--- a/apps/els_lsp/src/erlang_ls.erl
+++ b/apps/els_lsp/src/erlang_ls.erl
@@ -15,6 +15,8 @@
-include_lib("els_lsp/include/els_lsp.hrl").
-define(DEFAULT_LOGGING_LEVEL, "info").
+-define(LOG_MAX_NO_BYTES, 10 * 1000 * 1000).
+-define(LOG_MAX_NO_FILES, 5).
-spec main([any()]) -> ok.
main(Args) ->
@@ -98,7 +100,9 @@ configure_logging() ->
ok = filelib:ensure_dir(LogFile),
[logger:remove_handler(H) || H <- logger:get_handler_ids()],
Handler = #{
- config => #{file => LogFile},
+ config => #{
+ file => LogFile, max_no_bytes => ?LOG_MAX_NO_BYTES, max_no_files => ?LOG_MAX_NO_FILES
+ },
level => LoggingLevel,
formatter => {logger_formatter, #{template => ?LSP_LOG_FORMAT}}
},
From de41fd1f998e6db425fa61ba17e6fe5ae3c17c56 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Thu, 26 May 2022 11:52:40 +0200
Subject: [PATCH 084/239] Relax supervision max restart intensity (#1316)
Move from 5 restarts in 1 minute to 10 restarts in 10 seconds.
Background jobs are often not critical. This change should prevent
unnecessary node crashes.
---
apps/els_lsp/src/els_background_job_sup.erl | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/els_lsp/src/els_background_job_sup.erl b/apps/els_lsp/src/els_background_job_sup.erl
index 4b8258e9c..96b148f22 100644
--- a/apps/els_lsp/src/els_background_job_sup.erl
+++ b/apps/els_lsp/src/els_background_job_sup.erl
@@ -37,8 +37,8 @@ start_link() ->
init([]) ->
SupFlags = #{
strategy => simple_one_for_one,
- intensity => 5,
- period => 60
+ intensity => 10,
+ period => 10
},
ChildSpecs = [
#{
From 78c0ab295047fdf19cba64c4f5f6d5e8551ca12e Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Tue, 31 May 2022 09:43:57 +0200
Subject: [PATCH 085/239] Degrade gracefully to no diagnostics if text cannot
be parsed (#1318)
The user was already not affected by these errors, since diagnostics
are computed as a background job, but this allows us to reduce the
noise in logs.
---
.../diagnostics_bound_var_in_pattern_cannot_parse.erl | 6 ++++++
.../src/els_bound_var_in_pattern_diagnostics.erl | 9 +++++++--
apps/els_lsp/test/els_diagnostics_SUITE.erl | 10 ++++++++++
3 files changed, 23 insertions(+), 2 deletions(-)
create mode 100644 apps/els_lsp/priv/code_navigation/src/diagnostics_bound_var_in_pattern_cannot_parse.erl
diff --git a/apps/els_lsp/priv/code_navigation/src/diagnostics_bound_var_in_pattern_cannot_parse.erl b/apps/els_lsp/priv/code_navigation/src/diagnostics_bound_var_in_pattern_cannot_parse.erl
new file mode 100644
index 000000000..3f4b261d3
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/diagnostics_bound_var_in_pattern_cannot_parse.erl
@@ -0,0 +1,6 @@
+-module(diagnostics_bound_var_in_pattern_cannot_parse).
+
+f(Var1) ->
+ Var1 = 1.
+
+g() ->'
diff --git a/apps/els_lsp/src/els_bound_var_in_pattern_diagnostics.erl b/apps/els_lsp/src/els_bound_var_in_pattern_diagnostics.erl
index 8140f91ce..4e61a8ce4 100644
--- a/apps/els_lsp/src/els_bound_var_in_pattern_diagnostics.erl
+++ b/apps/els_lsp/src/els_bound_var_in_pattern_diagnostics.erl
@@ -53,8 +53,13 @@ source() ->
-spec find_vars(uri()) -> [els_poi:poi()].
find_vars(Uri) ->
{ok, #{text := Text}} = els_utils:lookup_document(Uri),
- {ok, Forms} = els_parser:parse_text(Text),
- lists:flatmap(fun find_vars_in_form/1, Forms).
+ case els_parser:parse_text(Text) of
+ {ok, Forms} ->
+ lists:flatmap(fun find_vars_in_form/1, Forms);
+ {error, Error} ->
+ ?LOG_DEBUG("Cannot parse text [text=~p] [error=~p]", [Text, Error]),
+ []
+ end.
-spec find_vars_in_form(erl_syntax:forms()) -> [els_poi:poi()].
find_vars_in_form(Form) ->
diff --git a/apps/els_lsp/test/els_diagnostics_SUITE.erl b/apps/els_lsp/test/els_diagnostics_SUITE.erl
index 92cd367ac..c858fcc2e 100644
--- a/apps/els_lsp/test/els_diagnostics_SUITE.erl
+++ b/apps/els_lsp/test/els_diagnostics_SUITE.erl
@@ -14,6 +14,7 @@
-export([
atom_typo/1,
bound_var_in_pattern/1,
+ bound_var_in_pattern_cannot_parse/1,
compiler/1,
compiler_with_behaviour/1,
compiler_with_broken_behaviour/1,
@@ -303,6 +304,15 @@ bound_var_in_pattern(_Config) ->
],
els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+-spec bound_var_in_pattern_cannot_parse(config()) -> ok.
+bound_var_in_pattern_cannot_parse(_Config) ->
+ Path = src_path("diagnostics_bound_var_in_pattern_cannot_parse.erl"),
+ Source = <<"BoundVarInPattern">>,
+ Errors = [],
+ Warnings = [],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+
-spec compiler(config()) -> ok.
compiler(_Config) ->
Path = src_path("diagnostics.erl"),
From 90a09f1a67366418b0effb129b334183c093790f Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Wed, 1 Jun 2022 15:50:35 +0200
Subject: [PATCH 086/239] Only store include/include_lib POIs as string (#1319)
In case of incorrect syntax, it can happen that the content of an
include or include_lib is a tree and not a string. Since the
erl_syntax:string_value does not guarantee a string as the output,
let's explicitly validate the returned value. This will ensure that
non-string values are not stored in the DB, causing diagnostics to
fail due to wrong type assumptions.
---
.../src/diagnostics_unused_includes_broken.erl | 5 +++++
apps/els_lsp/src/els_parser.erl | 15 +++++++++++----
apps/els_lsp/test/els_diagnostics_SUITE.erl | 10 ++++++++++
3 files changed, 26 insertions(+), 4 deletions(-)
create mode 100644 apps/els_lsp/priv/code_navigation/src/diagnostics_unused_includes_broken.erl
diff --git a/apps/els_lsp/priv/code_navigation/src/diagnostics_unused_includes_broken.erl b/apps/els_lsp/priv/code_navigation/src/diagnostics_unused_includes_broken.erl
new file mode 100644
index 000000000..64adf47f1
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/diagnostics_unused_includes_broken.erl
@@ -0,0 +1,5 @@
+-module(diagnostics_unused_includes_broken).
+
+-include_lib(
+ "foo"-include_lib("foo")
+).
diff --git a/apps/els_lsp/src/els_parser.erl b/apps/els_lsp/src/els_parser.erl
index 8ffb6fec7..05d35ab84 100644
--- a/apps/els_lsp/src/els_parser.erl
+++ b/apps/els_lsp/src/els_parser.erl
@@ -363,10 +363,10 @@ attribute(Tree) ->
Args = define_args(Define),
Data = #{value_range => ValueRange, args => Args},
[poi(DefinePos, define, define_name(Define), Data)];
- {include, [String]} ->
- [poi(Pos, include, erl_syntax:string_value(String))];
- {include_lib, [String]} ->
- [poi(Pos, include_lib, erl_syntax:string_value(String))];
+ {include, [Node]} ->
+ include_pois(Pos, include, Node);
+ {include_lib, [Node]} ->
+ include_pois(Pos, include_lib, Node);
{record, [Record, Fields]} ->
case is_record_name(Record) of
{true, RecordName} ->
@@ -1292,3 +1292,10 @@ get_end_location(Tree) ->
%% erl_anno:end_location(erl_syntax:get_pos(Tree)).
Anno = erl_syntax:get_pos(Tree),
proplists:get_value(end_location, erl_anno:to_term(Anno)).
+
+-spec include_pois(pos(), include | include_lib, tree()) -> [els_poi:poi()].
+include_pois(Pos, Type, Node) ->
+ case erl_syntax:type(Node) of
+ string -> [poi(Pos, Type, erl_syntax:string_value(Node))];
+ _ -> []
+ end.
diff --git a/apps/els_lsp/test/els_diagnostics_SUITE.erl b/apps/els_lsp/test/els_diagnostics_SUITE.erl
index c858fcc2e..524804c69 100644
--- a/apps/els_lsp/test/els_diagnostics_SUITE.erl
+++ b/apps/els_lsp/test/els_diagnostics_SUITE.erl
@@ -41,6 +41,7 @@
crossref_pseudo_functions/1,
unused_includes/1,
unused_includes_compiler_attribute/1,
+ unused_includes_broken/1,
exclude_unused_includes/1,
unused_macros/1,
unused_macros_refactorerl/1,
@@ -823,6 +824,15 @@ unused_includes_compiler_attribute(_Config) ->
Hints = [],
els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+-spec unused_includes_broken(config()) -> ok.
+unused_includes_broken(_Config) ->
+ Path = src_path("diagnostics_unused_includes_broken.erl"),
+ Source = <<"UnusedIncludes">>,
+ Errors = [],
+ Warnings = [],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+
-spec exclude_unused_includes(config()) -> ok.
exclude_unused_includes(_Config) ->
Path = src_path("diagnostics_unused_includes.erl"),
From 9ce2bc29066202aff88470be8e5809747e06a774 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Fri, 3 Jun 2022 15:03:27 +0200
Subject: [PATCH 087/239] Pin rebar3_lint version to be able to support OTP 22
(#1320)
---
rebar.config | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rebar.config b/rebar.config
index eb9cd5fc4..dd9f25c9f 100644
--- a/rebar.config
+++ b/rebar.config
@@ -31,7 +31,7 @@
{plugins, [
rebar3_proper,
coveralls,
- rebar3_lint,
+ {rebar3_lint, "1.0.2"},
{rebar3_bsp, {git, "https://github.com/erlang-ls/rebar3_bsp.git", {ref, "master"}}}
]}.
From 32ced53f3aff690b3d9e4634877148b34e9e5a9a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Fri, 3 Jun 2022 15:41:07 +0200
Subject: [PATCH 088/239] Treat incomplete trigger kind as invoked completion
(#1321)
---
apps/els_lsp/src/els_completion_provider.erl | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/apps/els_lsp/src/els_completion_provider.erl b/apps/els_lsp/src/els_completion_provider.erl
index 0a77d536f..cda22bc53 100644
--- a/apps/els_lsp/src/els_completion_provider.erl
+++ b/apps/els_lsp/src/els_completion_provider.erl
@@ -181,13 +181,16 @@ find_completions(
end;
find_completions(
Prefix,
- ?COMPLETION_TRIGGER_KIND_INVOKED,
+ TriggerKind,
#{
document := Document,
line := Line,
column := Column
}
-) ->
+) when
+ TriggerKind =:= ?COMPLETION_TRIGGER_KIND_INVOKED;
+ TriggerKind =:= ?COMPLETION_TRIGGER_KIND_FOR_INCOMPLETE_COMPLETIONS
+->
case lists:reverse(els_text:tokens(Prefix)) of
%% Check for "[...] fun atom:"
[{':', _}, {atom, _, Module}, {'fun', _} | _] ->
From 6f51ea1677b0f508514a03352517e69de8ef9685 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Thu, 9 Jun 2022 12:24:54 +0200
Subject: [PATCH 089/239] Optimize unused includes detection by avoiding
unnecessary work (#1322)
While identifying unused includes, the current implementation performs
a go-to-definition operation for all POIs within a document, even if
all the .hrl candidates have already been excluded.
This change stops the iteration as soon as no candidates are present.
The whole detection algorithm could probably be revisited (it would be
good to check if this problem has been tackled in literature), but for
now this optimization will lower the execution time for "unused
includes" diagnostics and reduce the stress on the language server,
especially for big modules.
As an example, this optimization reduces the computing time for
"unused includes" diagnostics for the `els_parser.erl` module from ~1s
to ~200ms.
---
.../src/els_unused_includes_diagnostics.erl | 41 ++++++++++---------
1 file changed, 22 insertions(+), 19 deletions(-)
diff --git a/apps/els_lsp/src/els_unused_includes_diagnostics.erl b/apps/els_lsp/src/els_unused_includes_diagnostics.erl
index f9810d976..e29c142a0 100644
--- a/apps/els_lsp/src/els_unused_includes_diagnostics.erl
+++ b/apps/els_lsp/src/els_unused_includes_diagnostics.erl
@@ -86,28 +86,31 @@ find_unused_includes(#{uri := Uri} = Document) ->
els_config:get(exclude_unused_includes)
),
IncludedUris = IncludedUris1 -- ExcludeUnusedIncludes,
- Fun = fun(POI, Acc) ->
- update_unused(Graph, Uri, POI, Acc)
- end,
- UnusedIncludes = lists:foldl(Fun, IncludedUris, POIs),
+ UnusedIncludes = update_unused(IncludedUris, Graph, Uri, POIs),
digraph:delete(Graph),
UnusedIncludes.
--spec update_unused(digraph:graph(), uri(), els_poi:poi(), [uri()]) -> [uri()].
-update_unused(Graph, Uri, POI, Acc) ->
- case els_code_navigation:goto_definition(Uri, POI) of
- {ok, Uri, _DefinitionPOI} ->
- Acc;
- {ok, DefinitionUri, _DefinitionPOI} ->
- case digraph:get_path(Graph, DefinitionUri, Uri) of
- false ->
- Acc;
- Path ->
- Acc -- Path
- end;
- {error, _Reason} ->
- Acc
- end.
+-spec update_unused([uri()], digraph:graph(), uri(), [els_poi:poi()]) -> [uri()].
+update_unused(Acc = [], _Graph, _Uri, _POIs) ->
+ Acc;
+update_unused(Acc, _Graph, _Uri, _POIs = []) ->
+ Acc;
+update_unused(Acc, Graph, Uri, [POI | POIs]) ->
+ NewAcc =
+ case els_code_navigation:goto_definition(Uri, POI) of
+ {ok, DefinitionUri, _DefinitionPOI} when DefinitionUri =:= Uri ->
+ Acc;
+ {ok, DefinitionUri, _DefinitionPOI} ->
+ case digraph:get_path(Graph, DefinitionUri, Uri) of
+ false ->
+ Acc;
+ Path ->
+ Acc -- Path
+ end;
+ {error, _Reason} ->
+ Acc
+ end,
+ update_unused(NewAcc, Graph, Uri, POIs).
-spec expand_includes(els_dt_document:item()) -> digraph:graph().
expand_includes(Document) ->
From c74e3f735fa518ebf009b1b32c16dc179ee6e353 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Mon, 13 Jun 2022 09:02:25 +0200
Subject: [PATCH 090/239] Consider argument of ifdef, ifndef, undef attributes
to be a macro (#1327)
---
apps/els_lsp/src/els_parser.erl | 8 ++++++++
apps/els_lsp/test/els_parser_SUITE.erl | 20 ++++++++++++++++++++
2 files changed, 28 insertions(+)
diff --git a/apps/els_lsp/src/els_parser.erl b/apps/els_lsp/src/els_parser.erl
index 05d35ab84..19d42f930 100644
--- a/apps/els_lsp/src/els_parser.erl
+++ b/apps/els_lsp/src/els_parser.erl
@@ -429,6 +429,14 @@ attribute(Tree) ->
undefined ->
[poi(Pos, spec, undefined)]
end;
+ {Attribute, [{Type, Anno, Name}]} when
+ (Attribute =:= ifdef orelse
+ Attribute =:= ifndef orelse
+ Attribute =:= undef),
+ (Type =:= var orelse
+ Type =:= atom)
+ ->
+ poi(Anno, macro, Name);
_ ->
[]
catch
diff --git a/apps/els_lsp/test/els_parser_SUITE.erl b/apps/els_lsp/test/els_parser_SUITE.erl
index a6b151edc..896bd8f39 100644
--- a/apps/els_lsp/test/els_parser_SUITE.erl
+++ b/apps/els_lsp/test/els_parser_SUITE.erl
@@ -16,6 +16,9 @@
parse_incomplete_type/1,
parse_no_tokens/1,
define/1,
+ ifdef/1,
+ ifndef/1,
+ undef/1,
underscore_macro/1,
specs_with_record/1,
types_with_record/1,
@@ -203,6 +206,23 @@ define(_Config) ->
els_parser:parse("-define(MACRO(A, B), A:B()).")
).
+-spec ifdef(config()) -> ok.
+ifdef(_Config) ->
+ Text = "-ifdef(FOO).",
+ ?assertMatch([#{id := 'FOO'}], parse_find_pois(Text, macro)),
+ Text2 = "-ifdef(foo).",
+ ?assertMatch([#{id := 'foo'}], parse_find_pois(Text2, macro)).
+
+-spec ifndef(config()) -> ok.
+ifndef(_Config) ->
+ Text = "-ifndef(FOO).",
+ ?assertMatch([#{id := 'FOO'}], parse_find_pois(Text, macro)).
+
+-spec undef(config()) -> ok.
+undef(_Config) ->
+ Text = "-undef(FOO).",
+ ?assertMatch([#{id := 'FOO'}], parse_find_pois(Text, macro)).
+
-spec underscore_macro(config()) -> ok.
underscore_macro(_Config) ->
?assertMatch(
From 550165e4b08b7094faf76941c7e552b05f470faf Mon Sep 17 00:00:00 2001
From: Michael Davis
Date: Mon, 13 Jun 2022 02:07:53 -0500
Subject: [PATCH 091/239] [#1300] Implement textDocument/signatureHelp. (#1307)
---
apps/els_core/include/els_core.hrl | 8 +-
apps/els_core/src/els_client.erl | 14 ++
apps/els_core/src/els_provider.erl | 6 +-
.../code_navigation/src/signature_help.erl | 18 ++
apps/els_lsp/src/els_general_provider.erl | 96 +++++---
apps/els_lsp/src/els_methods.erl | 12 +
.../src/els_signature_help_provider.erl | 216 ++++++++++++++++
.../els_lsp/test/els_signature_help_SUITE.erl | 231 ++++++++++++++++++
8 files changed, 555 insertions(+), 46 deletions(-)
create mode 100644 apps/els_lsp/priv/code_navigation/src/signature_help.erl
create mode 100644 apps/els_lsp/src/els_signature_help_provider.erl
create mode 100644 apps/els_lsp/test/els_signature_help_SUITE.erl
diff --git a/apps/els_core/include/els_core.hrl b/apps/els_core/include/els_core.hrl
index eed047393..72365e9a7 100644
--- a/apps/els_core/include/els_core.hrl
+++ b/apps/els_core/include/els_core.hrl
@@ -540,17 +540,17 @@
%%------------------------------------------------------------------------------
-type parameter_information() :: #{
label := binary(),
- documentation => binary()
+ documentation => markup_content()
}.
-type signature_information() :: #{
label := binary(),
- documentation => binary(),
+ documentation => markup_content(),
parameters => [parameter_information()]
}.
-type signature_help() :: #{
signatures := [signature_information()],
- active_signature => number(),
- active_parameters => number()
+ activeSignature => non_neg_integer(),
+ activeParameter => non_neg_integer()
}.
%%------------------------------------------------------------------------------
diff --git a/apps/els_core/src/els_client.erl b/apps/els_core/src/els_client.erl
index a3137e38d..6d3a9b7db 100644
--- a/apps/els_core/src/els_client.erl
+++ b/apps/els_core/src/els_client.erl
@@ -26,6 +26,7 @@
'$_unexpectedrequest'/0,
completion/5,
completionitem_resolve/1,
+ signature_help/3,
definition/3,
did_open/4,
did_save/1,
@@ -126,6 +127,10 @@ completion(Uri, Line, Char, TriggerKind, TriggerCharacter) ->
completionitem_resolve(CompletionItem) ->
gen_server:call(?SERVER, {completionitem_resolve, CompletionItem}).
+-spec signature_help(uri(), non_neg_integer(), non_neg_integer()) -> ok.
+signature_help(Uri, Line, Char) ->
+ gen_server:call(?SERVER, {signature_help, {Uri, Line, Char}}).
+
-spec definition(uri(), non_neg_integer(), non_neg_integer()) -> ok.
definition(Uri, Line, Char) ->
gen_server:call(?SERVER, {definition, {Uri, Line, Char}}).
@@ -431,6 +436,7 @@ do_handle_messages([Message | Messages], Pending, Notifications, Requests) ->
-spec method_lookup(atom()) -> binary().
method_lookup(completion) -> <<"textDocument/completion">>;
method_lookup(completionitem_resolve) -> <<"completionItem/resolve">>;
+method_lookup(signature_help) -> <<"textDocument/signatureHelp">>;
method_lookup(definition) -> <<"textDocument/definition">>;
method_lookup(document_symbol) -> <<"textDocument/documentSymbol">>;
method_lookup(references) -> <<"textDocument/references">>;
@@ -481,6 +487,14 @@ request_params({completion, {Uri, Line, Char, TriggerKind, TriggerCharacter}}) -
};
request_params({completionitem_resolve, CompletionItem}) ->
CompletionItem;
+request_params({signature_help, {Uri, Line, Char}}) ->
+ #{
+ textDocument => #{uri => Uri},
+ position => #{
+ line => Line - 1,
+ character => Char - 1
+ }
+ };
request_params({initialize, {RootUri, InitOptions}}) ->
ContentFormat = [?MARKDOWN, ?PLAINTEXT],
TextDocument = #{
diff --git a/apps/els_core/src/els_provider.erl b/apps/els_core/src/els_provider.erl
index f4391f4e7..714200df3 100644
--- a/apps/els_core/src/els_provider.erl
+++ b/apps/els_core/src/els_provider.erl
@@ -48,7 +48,8 @@
| els_code_lens_provider
| els_execute_command_provider
| els_rename_provider
- | els_text_synchronization_provider.
+ | els_text_synchronization_provider
+ | els_signature_help_provider.
-type request() :: {atom() | binary(), map()}.
-type state() :: #{
in_progress := [progress_entry()],
@@ -227,7 +228,8 @@ available_providers() ->
els_diagnostics_provider,
els_rename_provider,
els_call_hierarchy_provider,
- els_text_synchronization_provider
+ els_text_synchronization_provider,
+ els_signature_help_provider
].
%%==============================================================================
diff --git a/apps/els_lsp/priv/code_navigation/src/signature_help.erl b/apps/els_lsp/priv/code_navigation/src/signature_help.erl
new file mode 100644
index 000000000..ac27d0ae5
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/signature_help.erl
@@ -0,0 +1,18 @@
+-module(signature_help).
+
+-export([min/2,maps_get/0,record_max/0,multiline/0]).
+
+min(A, B) ->
+ erlang:min(A, B).
+
+maps_get() ->
+ maps:get(key, #{}, false).
+
+record_max() ->
+ erlang:max({a, b, c}, {d, e, f}).
+
+multiline() ->
+ erlang:min(
+ 1,
+ 2
+ ).
diff --git a/apps/els_lsp/src/els_general_provider.erl b/apps/els_lsp/src/els_general_provider.erl
index 06ab4b65d..9a067f0a9 100644
--- a/apps/els_lsp/src/els_general_provider.erl
+++ b/apps/els_lsp/src/els_general_provider.erl
@@ -115,47 +115,63 @@ handle_request({exit, #{status := Status}}, _State) ->
-spec server_capabilities() -> server_capabilities().
server_capabilities() ->
{ok, Version} = application:get_key(?APP, vsn),
+ Capabilities =
+ #{
+ textDocumentSync =>
+ els_text_synchronization_provider:options(),
+ hoverProvider => true,
+ completionProvider =>
+ #{
+ resolveProvider => true,
+ triggerCharacters =>
+ els_completion_provider:trigger_characters()
+ },
+ signatureHelpProvider =>
+ #{
+ triggerCharacters =>
+ els_signature_help_provider:trigger_characters()
+ },
+ definitionProvider =>
+ els_definition_provider:is_enabled(),
+ referencesProvider =>
+ els_references_provider:is_enabled(),
+ documentHighlightProvider =>
+ els_document_highlight_provider:is_enabled(),
+ documentSymbolProvider =>
+ els_document_symbol_provider:is_enabled(),
+ workspaceSymbolProvider =>
+ els_workspace_symbol_provider:is_enabled(),
+ codeActionProvider =>
+ els_code_action_provider:is_enabled(),
+ documentFormattingProvider =>
+ els_formatting_provider:is_enabled_document(),
+ documentRangeFormattingProvider =>
+ els_formatting_provider:is_enabled_range(),
+ foldingRangeProvider =>
+ els_folding_range_provider:is_enabled(),
+ implementationProvider =>
+ els_implementation_provider:is_enabled(),
+ executeCommandProvider =>
+ els_execute_command_provider:options(),
+ codeLensProvider =>
+ els_code_lens_provider:options(),
+ renameProvider =>
+ els_rename_provider:is_enabled(),
+ callHierarchyProvider =>
+ els_call_hierarchy_provider:is_enabled()
+ },
+ ActiveCapabilities =
+ case els_signature_help_provider:is_enabled() of
+ %% This pattern can never match because is_enabled/0 is currently
+ %% hard-coded to `false'. When enabling signature help manually,
+ %% uncomment this branch.
+ %% true ->
+ %% Capabilities;
+ false ->
+ maps:remove(signatureHelpProvider, Capabilities)
+ end,
#{
- capabilities =>
- #{
- textDocumentSync =>
- els_text_synchronization_provider:options(),
- hoverProvider => true,
- completionProvider =>
- #{
- resolveProvider => true,
- triggerCharacters =>
- els_completion_provider:trigger_characters()
- },
- definitionProvider =>
- els_definition_provider:is_enabled(),
- referencesProvider =>
- els_references_provider:is_enabled(),
- documentHighlightProvider =>
- els_document_highlight_provider:is_enabled(),
- documentSymbolProvider =>
- els_document_symbol_provider:is_enabled(),
- workspaceSymbolProvider =>
- els_workspace_symbol_provider:is_enabled(),
- codeActionProvider =>
- els_code_action_provider:is_enabled(),
- documentFormattingProvider =>
- els_formatting_provider:is_enabled_document(),
- documentRangeFormattingProvider =>
- els_formatting_provider:is_enabled_range(),
- foldingRangeProvider =>
- els_folding_range_provider:is_enabled(),
- implementationProvider =>
- els_implementation_provider:is_enabled(),
- executeCommandProvider =>
- els_execute_command_provider:options(),
- codeLensProvider =>
- els_code_lens_provider:options(),
- renameProvider =>
- els_rename_provider:is_enabled(),
- callHierarchyProvider =>
- els_call_hierarchy_provider:is_enabled()
- },
+ capabilities => ActiveCapabilities,
serverInfo =>
#{
name => <<"Erlang LS">>,
diff --git a/apps/els_lsp/src/els_methods.erl b/apps/els_lsp/src/els_methods.erl
index bd4ed2217..d8a43d9fa 100644
--- a/apps/els_lsp/src/els_methods.erl
+++ b/apps/els_lsp/src/els_methods.erl
@@ -28,6 +28,7 @@
textdocument_codelens/2,
textdocument_rename/2,
textdocument_preparecallhierarchy/2,
+ textdocument_signaturehelp/2,
callhierarchy_incomingcalls/2,
callhierarchy_outgoingcalls/2,
workspace_executecommand/2,
@@ -423,6 +424,17 @@ textdocument_preparecallhierarchy(Params, State) ->
els_provider:handle_request(Provider, {prepare, Params}),
{response, Response, State}.
+%%==============================================================================
+%% textDocument/signatureHelp
+%%==============================================================================
+
+-spec textdocument_signaturehelp(params(), state()) -> result().
+textdocument_signaturehelp(Params, State) ->
+ Provider = els_signature_help_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {signature_help, Params}),
+ {response, Response, State}.
+
%%==============================================================================
%% callHierarchy/incomingCalls
%%==============================================================================
diff --git a/apps/els_lsp/src/els_signature_help_provider.erl b/apps/els_lsp/src/els_signature_help_provider.erl
new file mode 100644
index 000000000..ce9443874
--- /dev/null
+++ b/apps/els_lsp/src/els_signature_help_provider.erl
@@ -0,0 +1,216 @@
+-module(els_signature_help_provider).
+
+-behaviour(els_provider).
+
+-include("els_lsp.hrl").
+-include_lib("kernel/include/logger.hrl").
+
+-export([
+ is_enabled/0,
+ handle_request/2,
+ trigger_characters/0
+]).
+
+-type item() :: {remote, atom(), atom()} | {local, atom()}.
+-type parameter_number() :: non_neg_integer().
+%% Parameter numbers are 0-indexed.
+
+-spec trigger_characters() -> [binary()].
+trigger_characters() ->
+ [<<"(">>, <<",">>, <<")">>].
+
+%%==============================================================================
+%% els_provider functions
+%%==============================================================================
+-spec is_enabled() -> boolean().
+is_enabled() ->
+ false.
+
+-spec handle_request(els_provider:request(), any()) -> {response, signature_help() | null}.
+handle_request({signature_help, Params}, _State) ->
+ #{
+ <<"position">> := #{
+ <<"line">> := Line,
+ <<"character">> := Character
+ },
+ <<"textDocument">> := #{<<"uri">> := Uri}
+ } = Params,
+ {ok, #{text := Text} = Document} = els_utils:lookup_document(Uri),
+ Prefix = els_text:line(Text, Line, Character),
+ Tokens = lists:reverse(els_text:tokens(Prefix)),
+ case find_signature(Tokens, Text, Line - 1) of
+ {ok, Item, ActiveParameter} ->
+ {response, signatures(Document, Item, ActiveParameter)};
+ none ->
+ {response, null}
+ end.
+
+%%==============================================================================
+%% Internal functions
+%%==============================================================================
+-spec find_signature(
+ Tokens :: [tuple()],
+ Text :: binary(),
+ Line :: non_neg_integer()
+) ->
+ {ok, item(), parameter_number()} | none.
+find_signature(Tokens, Text, Line) ->
+ find_signature(Tokens, [0], Text, Line).
+
+-spec find_signature(
+ Tokens :: [tuple()],
+ ParameterStack :: [parameter_number()],
+ Text :: binary(),
+ Line :: non_neg_integer()
+) ->
+ {ok, item(), parameter_number()} | none.
+%% An unmatched open parenthesis is the start of a signature.
+find_signature([{'(', _} | Rest], [ActiveParameter], _Text, _Line) ->
+ case Rest of
+ %% Check for "[...] module:func("
+ [{atom, _, Func}, {':', _}, {atom, _, Module} | _Rest] ->
+ {ok, {remote, Module, Func}, ActiveParameter};
+ %% Check for "-attribute("
+ [{atom, _, _Attribute}, {'-', _} | _Rest] ->
+ none;
+ %% Check for "[...] func("
+ [{atom, _, Func} | _Rest] ->
+ {ok, {local, Func}, ActiveParameter};
+ _Tokens ->
+ none
+ end;
+%% A comma outside of any data structure (list, tuple, map, or binary) is a
+%% separator between arguments, so we increment the active parameter count.
+find_signature([{',', _} | Rest], [ActiveParameter | ParameterStack], Text, Line) ->
+ find_signature(Rest, [ActiveParameter + 1 | ParameterStack], Text, Line);
+%% Calls may contain any sort of expression but not statements, so when we
+%% see a '.', we know we've failed to find a signature.
+find_signature([{dot, _} | _Rest], _ParameterStack, _Text, _Line) ->
+ none;
+%% When closing a scope, push a new parameter counter onto the stack.
+find_signature([{ScopeClose, _} | Rest], ParameterStack, Text, Line) when
+ ScopeClose =:= ')';
+ ScopeClose =:= '}';
+ ScopeClose =:= ']';
+ ScopeClose =:= '>>'
+->
+ find_signature(Rest, [0 | ParameterStack], Text, Line);
+%% When opening a scope, pop the extra parameter counter if it exists.
+find_signature([{ScopeOpen, _} | Rest], ParameterStack, Text, Line) when
+ ScopeOpen =:= '(';
+ ScopeOpen =:= '{';
+ ScopeOpen =:= '[';
+ ScopeOpen =:= '<<'
+->
+ ParameterStack1 =
+ case ParameterStack of
+ [_] -> [0];
+ [_Head | Tail] -> Tail
+ end,
+ find_signature(Rest, ParameterStack1, Text, Line);
+%% Discard any other tokens
+find_signature([_ | Rest], ParameterStack, Text, Line) ->
+ find_signature(Rest, ParameterStack, Text, Line);
+%% If there are no lines remaining in the file, then we failed to find any
+%% signatures and are done.
+find_signature([], _ParameterStack, _Text, 0) ->
+ none;
+%% If we have exhausted the set of tokens on this line, scan backwards a line
+%% (up in the document) since expressions may be split across multiple lines.
+find_signature([], ParameterStack, Text, Line) ->
+ LineContents = els_text:line(Text, Line),
+ Tokens = lists:reverse(els_text:tokens(LineContents)),
+ find_signature(Tokens, ParameterStack, Text, Line - 1).
+
+-spec signatures(els_dt_document:item(), item(), parameter_number()) ->
+ signature_help() | null.
+signatures(Document, Item, ActiveParameter) ->
+ {Module, Function, POIs} =
+ case Item of
+ {local, F} ->
+ #{uri := Uri} = Document,
+ M = els_uri:module(Uri),
+ LocalPOIs = els_scope:local_and_included_pois(Document, function),
+ {M, F, LocalPOIs};
+ {remote, M, F} ->
+ {M, F, exported_function_pois(M)}
+ end,
+ SignaturePOIs =
+ lists:sort(
+ fun(#{id := {_, AArity}}, #{id := {_, BArity}}) -> AArity < BArity end,
+ [
+ POI
+ || #{id := {POIFunc, _Arity}} = POI <- POIs,
+ POIFunc =:= Function
+ ]
+ ),
+ ?LOG_DEBUG(
+ "Signature Help. [item=~p] [pois=~p]",
+ [Item, SignaturePOIs]
+ ),
+ case SignaturePOIs of
+ [] ->
+ null;
+ [_ | _] ->
+ %% The active signature is the signature with the smallest arity
+ %% that is at least as large as the active parameter, defaulting
+ %% to the highest arity signature. The active signature is zero
+ %% indexed.
+ ActiveSignature =
+ index_where(
+ fun(#{id := {_, Arity}}) -> Arity > ActiveParameter end,
+ SignaturePOIs,
+ length(SignaturePOIs) - 1
+ ),
+ #{
+ activeParameter => ActiveParameter,
+ activeSignature => ActiveSignature,
+ signatures => [signature_item(Module, POI) || POI <- SignaturePOIs]
+ }
+ end.
+
+-spec signature_item(atom(), els_poi:poi()) -> signature_information().
+signature_item(Module, #{data := #{args := Args}, id := {Function, Arity}}) ->
+ DocEntries = els_docs:function_docs('remote', Module, Function, Arity),
+ #{
+ documentation => els_markup_content:new(DocEntries),
+ label => label(Function, Args),
+ parameters => [#{label => els_utils:to_binary(Name)} || {_Index, Name} <- Args]
+ }.
+
+-spec exported_function_pois(atom()) -> [els_poi:poi()].
+exported_function_pois(Module) ->
+ case els_utils:find_module(Module) of
+ {ok, Uri} ->
+ case els_utils:lookup_document(Uri) of
+ {ok, Document} ->
+ Exports = [
+ FA
+ || #{id := FA} <- els_scope:local_and_included_pois(Document, export_entry)
+ ],
+ [
+ POI
+ || #{id := FA} = POI <- els_scope:local_and_included_pois(Document, function),
+ lists:member(FA, Exports)
+ ];
+ {error, _} ->
+ []
+ end;
+ {error, _} ->
+ []
+ end.
+
+-spec label(atom(), [tuple()]) -> binary().
+label(Function, Args0) ->
+ ArgList = ["(", string:join([Name || {_Index, Name} <- Args0], ", "), ")"],
+ els_utils:to_binary([atom_to_binary(Function, utf8) | ArgList]).
+
+-spec index_where(Predicate, list(), Default) -> non_neg_integer() | Default when
+ Predicate :: fun((term()) -> boolean()),
+ Default :: term().
+index_where(Predicate, List, Default) ->
+ {IndexedList, _Acc} = lists:mapfoldl(fun(Item, Acc) -> {{Acc, Item}, Acc + 1} end, 0, List),
+ case lists:search(fun({_Index, Item}) -> Predicate(Item) end, IndexedList) of
+ {value, {Index, _Item}} -> Index;
+ false -> Default
+ end.
diff --git a/apps/els_lsp/test/els_signature_help_SUITE.erl b/apps/els_lsp/test/els_signature_help_SUITE.erl
new file mode 100644
index 000000000..5cdf0fe6c
--- /dev/null
+++ b/apps/els_lsp/test/els_signature_help_SUITE.erl
@@ -0,0 +1,231 @@
+-module(els_signature_help_SUITE).
+%% CT Callbacks
+-export([
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0
+]).
+
+%% Test cases
+-export([
+ remote_call/1,
+ switch_signature_between_arities/1,
+ non_trigger_character_request/1,
+ argument_expressions_may_contain_commas/1,
+ multiline_call/1
+]).
+
+%%==============================================================================
+%% Includes
+%%==============================================================================
+%% -include_lib("els_core/include/els_core.hrl").
+-include_lib("common_test/include/ct.hrl").
+-include_lib("stdlib/include/assert.hrl").
+
+%%==============================================================================
+%% Types
+%%==============================================================================
+-type config() :: [{atom(), any()}].
+
+%%==============================================================================
+%% CT Callbacks
+%%==============================================================================
+-spec suite() -> [tuple()].
+suite() ->
+ [{timetrap, {seconds, 30}}].
+
+-spec all() -> [atom()].
+all() ->
+ els_test_utils:all(?MODULE).
+
+-spec init_per_suite(config()) -> config().
+init_per_suite(Config) ->
+ els_test_utils:init_per_suite(Config).
+
+-spec end_per_suite(config()) -> ok.
+end_per_suite(Config) ->
+ els_test_utils:end_per_suite(Config).
+
+-spec init_per_testcase(atom(), config()) -> config().
+init_per_testcase(TestCase, Config) ->
+ els_test_utils:init_per_testcase(TestCase, Config).
+
+-spec end_per_testcase(atom(), config()) -> ok.
+end_per_testcase(TestCase, Config) ->
+ els_test_utils:end_per_testcase(TestCase, Config).
+
+%%==============================================================================
+%% Testcases
+%%==============================================================================
+-spec remote_call(config()) -> ok.
+remote_call(Config) ->
+ %% Line 6 of this document: " erlang:min(A, B)."
+ Uri = ?config(signature_help_uri, Config),
+ %% On the "(" of "erlang:min("
+ #{result := Result1} = els_client:signature_help(Uri, 6, 16),
+ ?assertMatch(
+ #{
+ activeSignature := 0,
+ activeParameter := 0,
+ signatures := [
+ #{
+ label := <<"min(A, B)">>,
+ parameters := [#{label := <<"A">>}, #{label := <<"B">>}],
+ documentation := #{
+ kind := <<"markdown">>
+ }
+ }
+ ]
+ },
+ Result1
+ ),
+ %% On the "," of "erlang:min(A,"
+ #{result := Result2} = els_client:signature_help(Uri, 6, 18),
+ ?assertMatch(
+ #{
+ activeSignature := 0,
+ activeParameter := 1,
+ signatures := [#{label := <<"min(A, B)">>}]
+ },
+ Result2
+ ),
+ %% On the ")" of "erlang:min(A, B)"
+ #{result := Result3} = els_client:signature_help(Uri, 6, 21),
+ ?assertEqual(null, Result3),
+ ok.
+
+-spec switch_signature_between_arities(config()) -> ok.
+switch_signature_between_arities(Config) ->
+ %% Line 9 of this document: " maps:get(key, #{}, false)."
+ Uri = ?config(signature_help_uri, Config),
+ %% On the "(" of "maps:get("
+ #{result := Result1} = els_client:signature_help(Uri, 9, 14),
+ ?assertMatch(
+ #{
+ activeSignature := 0,
+ activeParameter := 0,
+ signatures := [
+ #{
+ label := <<"get(Arg1, Arg2)">>,
+ parameters := [#{label := <<"Arg1">>}, #{label := <<"Arg2">>}],
+ documentation := #{
+ kind := <<"markdown">>
+ }
+ },
+ #{
+ label := <<"get(Key, Map, Default)">>,
+ parameters := [
+ #{label := <<"Key">>}, #{label := <<"Map">>}, #{label := <<"Default">>}
+ ],
+ documentation := #{
+ kind := <<"markdown">>
+ }
+ }
+ ]
+ },
+ Result1
+ ),
+ %% On the "," of "maps:get(key,"
+ #{result := Result2} = els_client:signature_help(Uri, 9, 18),
+ ?assertMatch(#{activeSignature := 0, activeParameter := 1}, Result2),
+ %% On the second "," of "maps:get(key, #{},", we switch to the higher
+ %% arity `maps:get/3' signature
+ #{result := Result3} = els_client:signature_help(Uri, 9, 23),
+ ?assertMatch(#{activeSignature := 1, activeParameter := 2}, Result3),
+ %% On the ")" of "maps:get(key, #{}, false)"
+ #{result := Result4} = els_client:signature_help(Uri, 9, 30),
+ ?assertEqual(null, Result4),
+ ok.
+
+-spec non_trigger_character_request(config()) -> ok.
+non_trigger_character_request(Config) ->
+ %% Line 9 of this document: " maps:get(key, #{}, false)."
+ Uri = ?config(signature_help_uri, Config),
+ %% On the "e" of "maps:get(key"
+ #{result := Result} = els_client:signature_help(Uri, 9, 16),
+ ?assertMatch(
+ #{
+ activeSignature := 0,
+ activeParameter := 0,
+ signatures := [
+ #{
+ label := <<"get(Arg1, Arg2)">>,
+ parameters := [#{label := <<"Arg1">>}, #{label := <<"Arg2">>}],
+ documentation := #{
+ kind := <<"markdown">>
+ }
+ }
+ | _
+ ]
+ },
+ Result
+ ),
+ ok.
+
+-spec argument_expressions_may_contain_commas(config()) -> ok.
+argument_expressions_may_contain_commas(Config) ->
+ %% Line 12 of this document: " erlang:max({a, b, c}, {d, e, f})."
+ Uri = ?config(signature_help_uri, Config),
+ %% On the "," of "erlang:max({a,"
+ %% The comma belongs to the argument expression instead of separating
+ %% arguments.
+ #{result := Result1} = els_client:signature_help(Uri, 12, 19),
+ ?assertMatch(
+ #{
+ activeSignature := 0,
+ activeParameter := 0,
+ signatures := [
+ #{
+ label := <<"max(A, B)">>,
+ parameters := [#{label := <<"A">>}, #{label := <<"B">>}],
+ documentation := #{
+ kind := <<"markdown">>
+ }
+ }
+ ]
+ },
+ Result1
+ ),
+ %% On the last "," of "erlang:max({a, b, c}, {d,"
+ #{result := Result2} = els_client:signature_help(Uri, 12, 30),
+ ?assertMatch(
+ #{
+ activeSignature := 0,
+ activeParameter := 1,
+ signatures := [#{label := <<"max(A, B)">>}]
+ },
+ Result2
+ ),
+ ok.
+
+-spec multiline_call(config()) -> ok.
+multiline_call(Config) ->
+ %% The block being tested here is lines 15-18:
+ %%
+ %% erlang:min(
+ %% 1,
+ %% 2
+ %% ).
+ Uri = ?config(signature_help_uri, Config),
+ %% On the "," on line 16
+ #{result := Result} = els_client:signature_help(Uri, 16, 9),
+ ?assertMatch(
+ #{
+ activeSignature := 0,
+ activeParameter := 1,
+ signatures := [
+ #{
+ label := <<"min(A, B)">>,
+ parameters := [#{label := <<"A">>}, #{label := <<"B">>}],
+ documentation := #{
+ kind := <<"markdown">>
+ }
+ }
+ ]
+ },
+ Result
+ ),
+ ok.
From 6d15aa4daa34ab50d09e750bddd19b34ca6ad700 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Mon, 13 Jun 2022 11:39:48 +0200
Subject: [PATCH 092/239] Handle vars when parsing function applications
(#1326)
---
.../code_navigation/src/diagnostics_xref.erl | 6 +-
apps/els_lsp/src/els_crossref_diagnostics.erl | 16 +++++
apps/els_lsp/src/els_parser.erl | 40 ++++++++++-
apps/els_lsp/test/els_parser_SUITE.erl | 68 ++++++++++++++++++-
4 files changed, 126 insertions(+), 4 deletions(-)
diff --git a/apps/els_lsp/priv/code_navigation/src/diagnostics_xref.erl b/apps/els_lsp/priv/code_navigation/src/diagnostics_xref.erl
index 564317b5f..1e716ea2b 100644
--- a/apps/els_lsp/priv/code_navigation/src/diagnostics_xref.erl
+++ b/apps/els_lsp/priv/code_navigation/src/diagnostics_xref.erl
@@ -4,7 +4,11 @@
main() ->
lists:map(1, 2, 3),
- non_existing() ++ existing().
+ non_existing() ++ existing() ++ dynamic_call(foo, bar).
existing() ->
lists:seq(1, 3).
+
+dynamic_call(Foo, Bar) ->
+ Foo:bar(),
+ foo:Bar().
diff --git a/apps/els_lsp/src/els_crossref_diagnostics.erl b/apps/els_lsp/src/els_crossref_diagnostics.erl
index d259f7cd7..927121c60 100644
--- a/apps/els_lsp/src/els_crossref_diagnostics.erl
+++ b/apps/els_lsp/src/els_crossref_diagnostics.erl
@@ -116,6 +116,22 @@ has_definition(
_
) ->
true;
+has_definition(
+ #{
+ kind := application,
+ data := #{mod_is_variable := true}
+ },
+ _
+) ->
+ true;
+has_definition(
+ #{
+ kind := application,
+ data := #{fun_is_variable := true}
+ },
+ _
+) ->
+ true;
has_definition(
#{
kind := application,
diff --git a/apps/els_lsp/src/els_parser.erl b/apps/els_lsp/src/els_parser.erl
index 19d42f930..d88562508 100644
--- a/apps/els_lsp/src/els_parser.erl
+++ b/apps/els_lsp/src/els_parser.erl
@@ -234,6 +234,12 @@ application(Tree) ->
case application_mfa(Tree) of
undefined ->
[];
+ {{variable, F}, A} ->
+ Pos = erl_syntax:get_pos(erl_syntax:application_operator(Tree)),
+ [
+ poi(Pos, application, {F, A}, #{fun_is_variable => true}),
+ poi(Pos, variable, F)
+ ];
{F, A} ->
Pos = erl_syntax:get_pos(erl_syntax:application_operator(Tree)),
case erl_internal:bif(F, A) of
@@ -242,6 +248,22 @@ application(Tree) ->
%% Local call
false -> [poi(Pos, application, {F, A})]
end;
+ {{ModType, M}, {FunType, F}, A} ->
+ ModFunTree = erl_syntax:application_operator(Tree),
+ Pos = erl_syntax:get_pos(ModFunTree),
+ FunTree = erl_syntax:module_qualifier_body(ModFunTree),
+ ModTree = erl_syntax:module_qualifier_argument(ModFunTree),
+ FunPos = erl_syntax:get_pos(FunTree),
+ ModPos = erl_syntax:get_pos(ModTree),
+ Data = #{
+ name_range => els_range:range(FunPos),
+ mod_range => els_range:range(ModPos),
+ fun_is_variable => FunType =:= variable,
+ mod_is_variable => ModType =:= variable
+ },
+ [poi(Pos, application, {M, F, A}, Data)] ++
+ [poi(ModPos, variable, M) || ModType =:= variable] ++
+ [poi(FunPos, variable, F) || FunType =:= variable];
MFA ->
ModFunTree = erl_syntax:application_operator(Tree),
Pos = erl_syntax:get_pos(ModFunTree),
@@ -255,7 +277,11 @@ application(Tree) ->
end.
-spec application_mfa(tree()) ->
- {module(), atom(), arity()} | {atom(), arity()} | undefined.
+ {module(), atom(), arity()}
+ | {atom(), arity()}
+ | {{atom(), atom()}, {atom(), atom()}, arity()}
+ | {{atom(), atom()}, arity()}
+ | undefined.
application_mfa(Tree) ->
case erl_syntax_lib:analyze_application(Tree) of
%% Remote call
@@ -272,12 +298,15 @@ application_mfa(Tree) ->
Operator = erl_syntax:application_operator(Tree),
case erl_syntax:type(Operator) of
module_qualifier -> application_with_variable(Operator, A);
+ variable -> {{variable, node_name(Operator)}, A};
_ -> undefined
end
end.
-spec application_with_variable(tree(), arity()) ->
- {atom(), arity()} | undefined.
+ {{atom(), atom()}, {atom(), atom()}, arity()}
+ | {atom(), arity()}
+ | undefined.
application_with_variable(Operator, A) ->
Module = erl_syntax:module_qualifier_argument(Operator),
Function = erl_syntax:module_qualifier_body(Operator),
@@ -292,6 +321,13 @@ application_with_variable(Operator, A) ->
{'MODULE', F} -> {F, A};
_ -> undefined
end;
+ {ModType, FunType} when
+ ModType =:= variable orelse ModType =:= atom,
+ FunType =:= variable orelse FunType =:= atom
+ ->
+ ModuleName = node_name(Module),
+ FunctionName = node_name(Function),
+ {{ModType, ModuleName}, {FunType, FunctionName}, A};
_ ->
undefined
end.
diff --git a/apps/els_lsp/test/els_parser_SUITE.erl b/apps/els_lsp/test/els_parser_SUITE.erl
index 896bd8f39..e7eb61527 100644
--- a/apps/els_lsp/test/els_parser_SUITE.erl
+++ b/apps/els_lsp/test/els_parser_SUITE.erl
@@ -198,9 +198,10 @@ define(_Config) ->
?assertMatch(
{ok, [
#{id := {'MACRO', 2}, kind := define},
- #{id := 'B', kind := variable},
+ #{id := {'A', 'B', 0}, kind := application},
#{id := 'A', kind := variable},
#{id := 'B', kind := variable},
+ #{id := 'B', kind := variable},
#{id := 'A', kind := variable}
]},
els_parser:parse("-define(MACRO(A, B), A:B()).")
@@ -361,9 +362,74 @@ assert_recursive_types(Text) ->
var_in_application(_Config) ->
Text1 = "f() -> Mod:f(42).",
?assertMatch([#{id := 'Mod'}], parse_find_pois(Text1, variable)),
+ ?assertMatch(
+ [
+ #{
+ id := {'Mod', f, 1},
+ data := #{
+ mod_is_variable := true,
+ fun_is_variable := false
+ }
+ }
+ ],
+ parse_find_pois(Text1, application)
+ ),
Text2 = "f() -> mod:Fun(42).",
?assertMatch([#{id := 'Fun'}], parse_find_pois(Text2, variable)),
+ ?assertMatch(
+ [
+ #{
+ id := {mod, 'Fun', 1},
+ data := #{
+ mod_is_variable := false,
+ fun_is_variable := true
+ }
+ }
+ ],
+ parse_find_pois(Text2, application)
+ ),
+
+ Text3 = "f() -> Mod:Fun(42).",
+ ?assertMatch([#{id := 'Mod'}, #{id := 'Fun'}], parse_find_pois(Text3, variable)),
+ ?assertMatch(
+ [
+ #{
+ id := {'Mod', 'Fun', 1},
+ data := #{
+ mod_is_variable := true,
+ fun_is_variable := true
+ }
+ }
+ ],
+ parse_find_pois(Text3, application)
+ ),
+ Text4 = "f() -> Fun(42).",
+ ?assertMatch([#{id := 'Fun'}], parse_find_pois(Text4, variable)),
+ ?assertMatch(
+ [
+ #{
+ id := {'Fun', 1},
+ data := #{fun_is_variable := true}
+ }
+ ],
+ parse_find_pois(Text4, application)
+ ),
+
+ Text5 = "f() -> (Mod):(Fun)(42).",
+ ?assertMatch([#{id := 'Mod'}, #{id := 'Fun'}], parse_find_pois(Text5, variable)),
+ ?assertMatch(
+ [
+ #{
+ id := {'Mod', 'Fun', 1},
+ data := #{
+ mod_is_variable := true,
+ fun_is_variable := true
+ }
+ }
+ ],
+ parse_find_pois(Text5, application)
+ ),
ok.
-spec unicode_clause_pattern(config()) -> ok.
From 3a8c4faddd2efdec4e42fc93fb75bc76f40daa99 Mon Sep 17 00:00:00 2001
From: Robin Morisset
Date: Mon, 13 Jun 2022 18:47:58 +0200
Subject: [PATCH 093/239] [#1205] Implement goto_definition for testcases.
(#1330)
* Implement goto_definition for testcases
Summary:
https://github.com/erlang-ls/erlang_ls/issues/1205 suggested supporting the goto-definition feature for testcases.
Testcases are atoms (function definitions) and currently whenever doing goto-definition on an atom we treat it as a module.
I simply modified goto_definition to first look for a function of arity 1 in the current module when it is unclear whether the user is looking for a module or a testcase.
Test Plan:
I added the "testcase" test-case in els_definition_SUITE, following the structure of all other test-cases in that file.
This new testcase corresponds to doing "goto defintion" on the atom "one" which appears in "all() -> [ one ]" in sample_SUITE.erl.
It verifies that we are sent to the definition of one at the bottom of sample_SUITE.erl.
I then checked that this new test-case passed, and that I did not break any other test in that suite. In particular, there were already tests for goto-definition on module names, and they still pass.
Reviewers:
robertoaloi
* Fix style nit
* Fix comment line too long
---
apps/els_lsp/src/els_code_navigation.erl | 13 ++++++++++++-
apps/els_lsp/test/els_definition_SUITE.erl | 13 +++++++++++++
2 files changed, 25 insertions(+), 1 deletion(-)
diff --git a/apps/els_lsp/src/els_code_navigation.erl b/apps/els_lsp/src/els_code_navigation.erl
index b5ad84bcf..e5dc405a0 100644
--- a/apps/els_lsp/src/els_code_navigation.erl
+++ b/apps/els_lsp/src/els_code_navigation.erl
@@ -70,11 +70,22 @@ goto_definition(
Result ->
Result
end;
+goto_definition(
+ Uri,
+ #{kind := atom, id := Id} = POI
+) ->
+ %% Two interesting cases for atoms: testcases and modules.
+ %% Testcases are functions with arity 1, so we first look for a function
+ %% with the same name and arity 1 in the local scope
+ %% If we can't find it, we hope that the atom refers to a module.
+ case find(Uri, function, {Id, 1}) of
+ {error, _Error} -> goto_definition(Uri, POI#{kind := module});
+ Else -> Else
+ end;
goto_definition(
_Uri,
#{kind := Kind, id := Module}
) when
- Kind =:= atom;
Kind =:= behaviour;
Kind =:= module
->
diff --git a/apps/els_lsp/test/els_definition_SUITE.erl b/apps/els_lsp/test/els_definition_SUITE.erl
index 2721d360e..e0d82a53f 100644
--- a/apps/els_lsp/test/els_definition_SUITE.erl
+++ b/apps/els_lsp/test/els_definition_SUITE.erl
@@ -44,6 +44,7 @@
record_field/1,
record_field_included/1,
record_type_macro_name/1,
+ testcase/1,
type_application_remote/1,
type_application_undefined/1,
type_application_user/1,
@@ -164,6 +165,18 @@ behaviour(Config) ->
),
ok.
+-spec testcase(config()) -> ok.
+testcase(Config) ->
+ Uri = ?config(sample_SUITE_uri, Config),
+ Def = els_client:definition(Uri, 35, 6),
+ #{result := #{range := Range, uri := DefUri}} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assertEqual(
+ els_protocol:range(#{from => {58, 1}, to => {58, 4}}),
+ Range
+ ),
+ ok.
+
%% Issue #191: Definition not found after document is closed
-spec definition_after_closing(config()) -> ok.
definition_after_closing(Config) ->
From 2c539fd167e6cef229d9e710f4c4a136e36dae1d Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Tue, 14 Jun 2022 14:54:40 +0200
Subject: [PATCH 094/239] Merge server and provider processes. (#1329)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Merge server and provider processes.
The els_provide process is a remain from the times where providers had
their own dedicated processes and it does not currently provide
additional value. It actually makes provider errors not bubble up to
the client. In fact, in case of a provider error the provider process
would crash, but the server would indefinitely wait for a
response. The error would not be returned via LSP.
This change simplifies the architecture of Erlang LS by merging the
els_server and els_provider processes:
* Get rid of els_provider process and internal_state
* Remove obsolete available_providers function
* Do not pass server state to providers
As a follow up, it should be possible to:
* Pass requests as-they-are (as opposed to only the parameters) to
providers, avoiding an un-necessary translation layer
* Generalize the code in els_method
* Update apps/els_core/src/els_provider.erl
Co-authored-by: Michał Muskała
* Fix dialyzer spec
Co-authored-by: Michał Muskała
---
apps/els_core/src/els_provider.erl | 245 +-----------------
.../src/els_call_hierarchy_provider.erl | 19 +-
apps/els_lsp/src/els_code_action_provider.erl | 8 +-
apps/els_lsp/src/els_code_lens_provider.erl | 8 +-
apps/els_lsp/src/els_completion_provider.erl | 8 +-
apps/els_lsp/src/els_definition_provider.erl | 10 +-
apps/els_lsp/src/els_diagnostics_provider.erl | 8 +-
.../src/els_document_highlight_provider.erl | 11 +-
.../src/els_document_symbol_provider.erl | 8 +-
.../src/els_execute_command_provider.erl | 8 +-
.../src/els_folding_range_provider.erl | 6 +-
apps/els_lsp/src/els_formatting_provider.erl | 22 +-
apps/els_lsp/src/els_general_provider.erl | 14 +-
apps/els_lsp/src/els_hover_provider.erl | 8 +-
.../src/els_implementation_provider.erl | 6 +-
apps/els_lsp/src/els_methods.erl | 144 ++++++----
apps/els_lsp/src/els_references_provider.erl | 6 +-
apps/els_lsp/src/els_rename_provider.erl | 6 +-
apps/els_lsp/src/els_server.erl | 195 ++++++++++----
.../src/els_signature_help_provider.erl | 7 +-
apps/els_lsp/src/els_sup.erl | 4 -
.../src/els_text_synchronization_provider.erl | 14 +-
.../src/els_workspace_symbol_provider.erl | 8 +-
apps/els_lsp/test/els_server_SUITE.erl | 2 +-
apps/els_lsp/test/prop_statem.erl | 2 +-
25 files changed, 323 insertions(+), 454 deletions(-)
diff --git a/apps/els_core/src/els_provider.erl b/apps/els_core/src/els_provider.erl
index 714200df3..a95050e1e 100644
--- a/apps/els_core/src/els_provider.erl
+++ b/apps/els_core/src/els_provider.erl
@@ -2,254 +2,33 @@
%% API
-export([
- handle_request/2,
- start_link/0,
- available_providers/0,
- cancel_request/1,
- cancel_request_by_uri/1
-]).
-
--behaviour(gen_server).
--export([
- init/1,
- handle_call/3,
- handle_cast/2,
- handle_info/2,
- terminate/2
+ handle_request/2
]).
%%==============================================================================
%% Includes
%%==============================================================================
--include_lib("kernel/include/logger.hrl").
+-include("els_core.hrl").
+
+-callback handle_request(provider_request()) -> provider_result().
--callback handle_request(request(), any()) ->
+-type provider() :: module().
+-type provider_request() :: {atom(), map()}.
+-type provider_result() ::
{async, uri(), pid()}
| {response, any()}
| {diagnostics, uri(), [pid()]}
| noresponse.
--callback handle_info(any(), any()) -> any().
--optional_callbacks([handle_info/2]).
--type config() :: any().
--type provider() ::
- els_completion_provider
- | els_definition_provider
- | els_document_symbol_provider
- | els_hover_provider
- | els_references_provider
- | els_formatting_provider
- | els_document_highlight_provider
- | els_workspace_symbol_provider
- | els_folding_range_provider
- | els_implementation_provider
- | els_code_action_provider
- | els_general_provider
- | els_code_lens_provider
- | els_execute_command_provider
- | els_rename_provider
- | els_text_synchronization_provider
- | els_signature_help_provider.
--type request() :: {atom() | binary(), map()}.
--type state() :: #{
- in_progress := [progress_entry()],
- in_progress_diagnostics := [diagnostic_entry()],
- open_buffers := sets:set(buffer())
-}.
--type buffer() :: uri().
--type progress_entry() :: {uri(), job()}.
--type diagnostic_entry() :: #{
- uri := uri(),
- pending := [job()],
- diagnostics := [els_diagnostics:diagnostic()]
-}.
--type job() :: pid().
-%% TODO: Redefining uri() due to a type conflict with request()
--type uri() :: binary().
-export_type([
- config/0,
provider/0,
- request/0,
- state/0
+ provider_request/0,
+ provider_result/0
]).
%%==============================================================================
-%% Macro Definitions
-%%==============================================================================
--define(SERVER, ?MODULE).
-
-%%==============================================================================
-%% External functions
+%% API
%%==============================================================================
-
--spec start_link() -> {ok, pid()}.
-start_link() ->
- gen_server:start_link({local, ?SERVER}, ?MODULE, unused, []).
-
--spec handle_request(provider(), request()) -> any().
+-spec handle_request(provider(), provider_request()) -> provider_result().
handle_request(Provider, Request) ->
- gen_server:call(?SERVER, {handle_request, Provider, Request}, infinity).
-
--spec cancel_request(pid()) -> any().
-cancel_request(Job) ->
- gen_server:cast(?SERVER, {cancel_request, Job}).
-
--spec cancel_request_by_uri(uri()) -> any().
-cancel_request_by_uri(Uri) ->
- gen_server:cast(?SERVER, {cancel_request_by_uri, Uri}).
-
-%%==============================================================================
-%% gen_server callbacks
-%%==============================================================================
-
--spec init(unused) -> {ok, state()}.
-init(unused) ->
- %% Ensure the terminate function is called on shutdown, allowing the
- %% job to clean up.
- process_flag(trap_exit, true),
- {ok, #{
- in_progress => [],
- in_progress_diagnostics => [],
- open_buffers => sets:new()
- }}.
-
--spec handle_call(any(), {pid(), any()}, state()) ->
- {reply, any(), state()}.
-handle_call({handle_request, Provider, Request}, _From, State) ->
- #{in_progress := InProgress, in_progress_diagnostics := InProgressDiagnostics} =
- State,
- case Provider:handle_request(Request, State) of
- {async, Uri, Job} ->
- {reply, {async, Job}, State#{in_progress => [{Uri, Job} | InProgress]}};
- {response, Response} ->
- {reply, {response, Response}, State};
- {diagnostics, Uri, Jobs} ->
- Entry = #{uri => Uri, pending => Jobs, diagnostics => []},
- NewState =
- State#{in_progress_diagnostics => [Entry | InProgressDiagnostics]},
- {reply, noresponse, NewState};
- noresponse ->
- {reply, noresponse, State}
- end.
-
--spec handle_cast(any(), state()) -> {noreply, state()}.
-handle_cast({cancel_request, Job}, State) ->
- ?LOG_DEBUG("Cancelling request [job=~p]", [Job]),
- els_background_job:stop(Job),
- #{in_progress := InProgress} = State,
- NewState = State#{in_progress => lists:keydelete(Job, 2, InProgress)},
- {noreply, NewState};
-handle_cast({cancel_request_by_uri, Uri}, State) ->
- #{in_progress := InProgress0} = State,
- Fun = fun({U, Job}) ->
- case U =:= Uri of
- true ->
- els_background_job:stop(Job),
- false;
- false ->
- true
- end
- end,
- InProgress = lists:filtermap(Fun, InProgress0),
- ?LOG_DEBUG("Cancelling requests by Uri [uri=~p]", [Uri]),
- NewState = State#{in_progress => InProgress},
- {noreply, NewState}.
-
--spec handle_info(any(), state()) -> {noreply, state()}.
-handle_info({result, Result, Job}, State) ->
- ?LOG_DEBUG("Received result [job=~p]", [Job]),
- #{in_progress := InProgress} = State,
- els_server:send_response(Job, Result),
- NewState = State#{in_progress => lists:keydelete(Job, 2, InProgress)},
- {noreply, NewState};
-%% LSP 3.15 introduce versioning for diagnostics. Until all clients
-%% support it, we need to keep track of old diagnostics and re-publish
-%% them every time we get a new chunk.
-handle_info({diagnostics, Diagnostics, Job}, State) ->
- #{in_progress_diagnostics := InProgress} = State,
- ?LOG_DEBUG("Received diagnostics [job=~p]", [Job]),
- case find_entry(Job, InProgress) of
- {ok, {
- #{
- pending := Jobs,
- diagnostics := OldDiagnostics,
- uri := Uri
- },
- Rest
- }} ->
- NewDiagnostics = Diagnostics ++ OldDiagnostics,
- els_diagnostics_provider:publish(Uri, NewDiagnostics),
- NewState =
- case lists:delete(Job, Jobs) of
- [] ->
- State#{in_progress_diagnostics => Rest};
- Remaining ->
- State#{
- in_progress_diagnostics =>
- [
- #{
- pending => Remaining,
- diagnostics => NewDiagnostics,
- uri => Uri
- }
- | Rest
- ]
- }
- end,
- {noreply, NewState};
- {error, not_found} ->
- {noreply, State}
- end;
-handle_info(_Request, State) ->
- {noreply, State}.
-
--spec terminate(any(), state()) -> ok.
-terminate(_Reason, #{in_progress := InProgress}) ->
- [els_background_job:stop(Job) || {_Uri, Job} <- InProgress],
- ok.
-
--spec available_providers() -> [provider()].
-available_providers() ->
- [
- els_completion_provider,
- els_definition_provider,
- els_document_symbol_provider,
- els_hover_provider,
- els_references_provider,
- els_formatting_provider,
- els_document_highlight_provider,
- els_workspace_symbol_provider,
- els_folding_range_provider,
- els_implementation_provider,
- els_code_action_provider,
- els_general_provider,
- els_code_lens_provider,
- els_execute_command_provider,
- els_diagnostics_provider,
- els_rename_provider,
- els_call_hierarchy_provider,
- els_text_synchronization_provider,
- els_signature_help_provider
- ].
-
-%%==============================================================================
-%% Internal Functions
-%%==============================================================================
--spec find_entry(job(), [diagnostic_entry()]) ->
- {ok, {diagnostic_entry(), [diagnostic_entry()]}}
- | {error, not_found}.
-find_entry(Job, InProgress) ->
- find_entry(Job, InProgress, []).
-
--spec find_entry(job(), [diagnostic_entry()], [diagnostic_entry()]) ->
- {ok, {diagnostic_entry(), [diagnostic_entry()]}}
- | {error, not_found}.
-find_entry(_Job, [], []) ->
- {error, not_found};
-find_entry(Job, [#{pending := Pending} = Entry | Rest], Acc) ->
- case lists:member(Job, Pending) of
- true ->
- {ok, {Entry, Rest ++ Acc}};
- false ->
- find_entry(Job, Rest, [Entry | Acc])
- end.
+ Provider:handle_request(Request).
diff --git a/apps/els_lsp/src/els_call_hierarchy_provider.erl b/apps/els_lsp/src/els_call_hierarchy_provider.erl
index 8084d80ee..d374e97ce 100644
--- a/apps/els_lsp/src/els_call_hierarchy_provider.erl
+++ b/apps/els_lsp/src/els_call_hierarchy_provider.erl
@@ -4,7 +4,7 @@
-export([
is_enabled/0,
- handle_request/2
+ handle_request/1
]).
%%==============================================================================
@@ -13,36 +13,27 @@
-include("els_lsp.hrl").
-include_lib("kernel/include/logger.hrl").
-%%==============================================================================
-%% Defines
-%%==============================================================================
-
-%%==============================================================================
-%% Types
-%%==============================================================================
--type state() :: any().
-
%%==============================================================================
%% els_provider functions
%%==============================================================================
-spec is_enabled() -> boolean().
is_enabled() -> true.
--spec handle_request(any(), state()) -> {response, any()}.
-handle_request({prepare, Params}, _State) ->
+-spec handle_request(any()) -> {response, any()}.
+handle_request({prepare, Params}) ->
{Uri, Line, Char} =
els_text_document_position_params:uri_line_character(Params),
{ok, Document} = els_utils:lookup_document(Uri),
Functions = els_dt_document:wrapping_functions(Document, Line + 1, Char + 1),
Items = [function_to_item(Uri, F) || F <- Functions],
{response, Items};
-handle_request({incoming_calls, Params}, _State) ->
+handle_request({incoming_calls, Params}) ->
#{<<"item">> := #{<<"uri">> := Uri} = Item} = Params,
POI = els_call_hierarchy_item:poi(Item),
References = els_references_provider:find_references(Uri, POI),
Items = [reference_to_item(Reference) || Reference <- References],
{response, incoming_calls(Items)};
-handle_request({outgoing_calls, Params}, _State) ->
+handle_request({outgoing_calls, Params}) ->
#{<<"item">> := Item} = Params,
#{<<"uri">> := Uri} = Item,
POI = els_call_hierarchy_item:poi(Item),
diff --git a/apps/els_lsp/src/els_code_action_provider.erl b/apps/els_lsp/src/els_code_action_provider.erl
index 213e82418..f42534c86 100644
--- a/apps/els_lsp/src/els_code_action_provider.erl
+++ b/apps/els_lsp/src/els_code_action_provider.erl
@@ -4,21 +4,19 @@
-export([
is_enabled/0,
- handle_request/2
+ handle_request/1
]).
-include("els_lsp.hrl").
--type state() :: any().
-
%%==============================================================================
%% els_provider functions
%%==============================================================================
-spec is_enabled() -> boolean().
is_enabled() -> true.
--spec handle_request(any(), state()) -> {response, any()}.
-handle_request({document_codeaction, Params}, _State) ->
+-spec handle_request(any()) -> {response, any()}.
+handle_request({document_codeaction, Params}) ->
#{
<<"textDocument">> := #{<<"uri">> := Uri},
<<"range">> := RangeLSP,
diff --git a/apps/els_lsp/src/els_code_lens_provider.erl b/apps/els_lsp/src/els_code_lens_provider.erl
index 08feb56de..9cb2b468f 100644
--- a/apps/els_lsp/src/els_code_lens_provider.erl
+++ b/apps/els_lsp/src/els_code_lens_provider.erl
@@ -4,7 +4,7 @@
-export([
is_enabled/0,
options/0,
- handle_request/2
+ handle_request/1
]).
-include("els_lsp.hrl").
@@ -20,8 +20,8 @@ is_enabled() -> true.
options() ->
#{resolveProvider => false}.
--spec handle_request(any(), any()) -> {async, uri(), pid()}.
-handle_request({document_codelens, Params}, _State) ->
+-spec handle_request(any()) -> {async, uri(), pid()}.
+handle_request({document_codelens, Params}) ->
#{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
?LOG_DEBUG("Starting lenses job [uri=~p]", [Uri]),
Job = run_lenses_job(Uri),
@@ -47,7 +47,7 @@ run_lenses_job(Uri) ->
title => <<"Lenses">>,
on_complete =>
fun(Lenses) ->
- els_provider ! {result, Lenses, self()},
+ els_server ! {result, Lenses, self()},
ok
end
},
diff --git a/apps/els_lsp/src/els_completion_provider.erl b/apps/els_lsp/src/els_completion_provider.erl
index cda22bc53..3f32a1e43 100644
--- a/apps/els_lsp/src/els_completion_provider.erl
+++ b/apps/els_lsp/src/els_completion_provider.erl
@@ -6,7 +6,7 @@
-include_lib("kernel/include/logger.hrl").
-export([
- handle_request/2,
+ handle_request/1,
trigger_characters/0
]).
@@ -33,8 +33,8 @@
trigger_characters() ->
[<<":">>, <<"#">>, <<"?">>, <<".">>, <<"-">>, <<"\"">>].
--spec handle_request(els_provider:request(), any()) -> {response, any()}.
-handle_request({completion, Params}, _State) ->
+-spec handle_request(els_provider:provider_request()) -> {response, any()}.
+handle_request({completion, Params}) ->
#{
<<"position">> := #{
<<"line">> := Line,
@@ -74,7 +74,7 @@ handle_request({completion, Params}, _State) ->
},
Completions = find_completions(Prefix, TriggerKind, Opts),
{response, Completions};
-handle_request({resolve, CompletionItem}, _State) ->
+handle_request({resolve, CompletionItem}) ->
{response, resolve(CompletionItem)}.
%%==============================================================================
diff --git a/apps/els_lsp/src/els_definition_provider.erl b/apps/els_lsp/src/els_definition_provider.erl
index f7726ed61..d9fa53737 100644
--- a/apps/els_lsp/src/els_definition_provider.erl
+++ b/apps/els_lsp/src/els_definition_provider.erl
@@ -4,21 +4,19 @@
-export([
is_enabled/0,
- handle_request/2
+ handle_request/1
]).
-include("els_lsp.hrl").
--type state() :: any().
-
%%==============================================================================
%% els_provider functions
%%==============================================================================
-spec is_enabled() -> boolean().
is_enabled() -> true.
--spec handle_request(any(), state()) -> {response, any()}.
-handle_request({definition, Params}, State) ->
+-spec handle_request(any()) -> {response, any()}.
+handle_request({definition, Params}) ->
#{
<<"position">> := #{
<<"line">> := Line,
@@ -34,7 +32,7 @@ handle_request({definition, Params}, State) ->
IncompletePOIs = match_incomplete(Text, {Line, Character}),
case goto_definition(Uri, IncompletePOIs) of
null ->
- els_references_provider:handle_request({references, Params}, State);
+ els_references_provider:handle_request({references, Params});
GoTo ->
{response, GoTo}
end;
diff --git a/apps/els_lsp/src/els_diagnostics_provider.erl b/apps/els_lsp/src/els_diagnostics_provider.erl
index 90902b1a4..69cf75884 100644
--- a/apps/els_lsp/src/els_diagnostics_provider.erl
+++ b/apps/els_lsp/src/els_diagnostics_provider.erl
@@ -5,7 +5,7 @@
-export([
is_enabled/0,
options/0,
- handle_request/2
+ handle_request/1
]).
-export([
@@ -29,8 +29,8 @@ is_enabled() -> true.
options() ->
#{}.
--spec handle_request(any(), any()) -> {diagnostics, uri(), [pid()]}.
-handle_request({run_diagnostics, Params}, _State) ->
+-spec handle_request(any()) -> {diagnostics, uri(), [pid()]}.
+handle_request({run_diagnostics, Params}) ->
#{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
?LOG_DEBUG("Starting diagnostics jobs [uri=~p]", [Uri]),
Jobs = els_diagnostics:run_diagnostics(Uri),
@@ -41,7 +41,7 @@ handle_request({run_diagnostics, Params}, _State) ->
%%==============================================================================
-spec notify([els_diagnostics:diagnostic()], pid()) -> ok.
notify(Diagnostics, Job) ->
- els_provider ! {diagnostics, Diagnostics, Job},
+ els_server ! {diagnostics, Diagnostics, Job},
ok.
-spec publish(uri(), [els_diagnostics:diagnostic()]) -> ok.
diff --git a/apps/els_lsp/src/els_document_highlight_provider.erl b/apps/els_lsp/src/els_document_highlight_provider.erl
index cccda4b69..51f2477f2 100644
--- a/apps/els_lsp/src/els_document_highlight_provider.erl
+++ b/apps/els_lsp/src/els_document_highlight_provider.erl
@@ -4,7 +4,7 @@
-export([
is_enabled/0,
- handle_request/2
+ handle_request/1
]).
%%==============================================================================
@@ -12,19 +12,14 @@
%%==============================================================================
-include("els_lsp.hrl").
-%%==============================================================================
-%% Types
-%%==============================================================================
--type state() :: any().
-
%%==============================================================================
%% els_provider functions
%%==============================================================================
-spec is_enabled() -> boolean().
is_enabled() -> true.
--spec handle_request(any(), state()) -> {response, any()}.
-handle_request({document_highlight, Params}, _State) ->
+-spec handle_request(any()) -> {response, any()}.
+handle_request({document_highlight, Params}) ->
#{
<<"position">> := #{
<<"line">> := Line,
diff --git a/apps/els_lsp/src/els_document_symbol_provider.erl b/apps/els_lsp/src/els_document_symbol_provider.erl
index 12e917c17..7a6e2db21 100644
--- a/apps/els_lsp/src/els_document_symbol_provider.erl
+++ b/apps/els_lsp/src/els_document_symbol_provider.erl
@@ -4,21 +4,19 @@
-export([
is_enabled/0,
- handle_request/2
+ handle_request/1
]).
-include("els_lsp.hrl").
--type state() :: any().
-
%%==============================================================================
%% els_provider functions
%%==============================================================================
-spec is_enabled() -> boolean().
is_enabled() -> true.
--spec handle_request(any(), state()) -> {response, any()}.
-handle_request({document_symbol, Params}, _State) ->
+-spec handle_request(any()) -> {response, any()}.
+handle_request({document_symbol, Params}) ->
#{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
Symbols = symbols(Uri),
case Symbols of
diff --git a/apps/els_lsp/src/els_execute_command_provider.erl b/apps/els_lsp/src/els_execute_command_provider.erl
index 79d801f2a..16857e5b2 100644
--- a/apps/els_lsp/src/els_execute_command_provider.erl
+++ b/apps/els_lsp/src/els_execute_command_provider.erl
@@ -5,7 +5,7 @@
-export([
is_enabled/0,
options/0,
- handle_request/2
+ handle_request/1
]).
%%==============================================================================
@@ -14,8 +14,6 @@
-include("els_lsp.hrl").
-include_lib("kernel/include/logger.hrl").
--type state() :: any().
-
%%==============================================================================
%% els_provider functions
%%==============================================================================
@@ -34,8 +32,8 @@ options() ->
]
}.
--spec handle_request(any(), state()) -> {response, any()}.
-handle_request({workspace_executecommand, Params}, _State) ->
+-spec handle_request(any()) -> {response, any()}.
+handle_request({workspace_executecommand, Params}) ->
#{<<"command">> := PrefixedCommand} = Params,
Arguments = maps:get(<<"arguments">>, Params, []),
Result = execute_command(
diff --git a/apps/els_lsp/src/els_folding_range_provider.erl b/apps/els_lsp/src/els_folding_range_provider.erl
index 5d15c9832..c441bebab 100644
--- a/apps/els_lsp/src/els_folding_range_provider.erl
+++ b/apps/els_lsp/src/els_folding_range_provider.erl
@@ -6,7 +6,7 @@
-export([
is_enabled/0,
- handle_request/2
+ handle_request/1
]).
%%==============================================================================
@@ -20,8 +20,8 @@
-spec is_enabled() -> boolean().
is_enabled() -> true.
--spec handle_request(tuple(), any()) -> {response, folding_range_result()}.
-handle_request({document_foldingrange, Params}, _State) ->
+-spec handle_request(tuple()) -> {response, folding_range_result()}.
+handle_request({document_foldingrange, Params}) ->
#{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
{ok, Document} = els_utils:lookup_document(Uri),
POIs = els_dt_document:pois(Document, [function, record]),
diff --git a/apps/els_lsp/src/els_formatting_provider.erl b/apps/els_lsp/src/els_formatting_provider.erl
index 8901c1680..a52e4958d 100644
--- a/apps/els_lsp/src/els_formatting_provider.erl
+++ b/apps/els_lsp/src/els_formatting_provider.erl
@@ -3,8 +3,7 @@
-behaviour(els_provider).
-export([
- init/0,
- handle_request/2,
+ handle_request/1,
is_enabled/0,
is_enabled_document/0,
is_enabled_range/0,
@@ -15,13 +14,6 @@
%% Includes
%%==============================================================================
-include("els_lsp.hrl").
--include_lib("kernel/include/logger.hrl").
-
-%%==============================================================================
-%% Types
-%%==============================================================================
--type formatter() :: fun((string(), string(), formatting_options()) -> boolean()).
--type state() :: [formatter()].
%%==============================================================================
%% Macro Definitions
@@ -31,10 +23,6 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec init() -> state().
-init() ->
- [fun format_document_local/3].
-
%% Keep the behaviour happy
-spec is_enabled() -> boolean().
is_enabled() -> is_enabled_document().
@@ -52,8 +40,8 @@ is_enabled_range() ->
-spec is_enabled_on_type() -> document_ontypeformatting_options().
is_enabled_on_type() -> false.
--spec handle_request(any(), state()) -> {response, any()}.
-handle_request({document_formatting, Params}, _State) ->
+-spec handle_request(any()) -> {response, any()}.
+handle_request({document_formatting, Params}) ->
#{
<<"options">> := Options,
<<"textDocument">> := #{<<"uri">> := Uri}
@@ -65,7 +53,7 @@ handle_request({document_formatting, Params}, _State) ->
RelativePath ->
format_document(Path, RelativePath, Options)
end;
-handle_request({document_rangeformatting, Params}, _State) ->
+handle_request({document_rangeformatting, Params}) ->
#{
<<"range">> := #{
<<"start">> := StartPos,
@@ -78,7 +66,7 @@ handle_request({document_rangeformatting, Params}, _State) ->
{ok, Document} = els_utils:lookup_document(Uri),
{ok, TextEdit} = rangeformat_document(Uri, Document, Range, Options),
{response, TextEdit};
-handle_request({document_ontypeformatting, Params}, _State) ->
+handle_request({document_ontypeformatting, Params}) ->
#{
<<"position">> := #{
<<"line">> := Line,
diff --git a/apps/els_lsp/src/els_general_provider.erl b/apps/els_lsp/src/els_general_provider.erl
index 9a067f0a9..8cda414e3 100644
--- a/apps/els_lsp/src/els_general_provider.erl
+++ b/apps/els_lsp/src/els_general_provider.erl
@@ -3,7 +3,7 @@
-behaviour(els_provider).
-export([
is_enabled/0,
- handle_request/2
+ handle_request/1
]).
-export([server_capabilities/0]).
@@ -44,7 +44,6 @@
-type exit_request() :: {exit, exit_params()}.
-type exit_params() :: #{status => atom()}.
-type exit_result() :: null.
--type state() :: any().
%%==============================================================================
%% els_provider functions
@@ -56,15 +55,14 @@ is_enabled() -> true.
initialize_request()
| initialized_request()
| shutdown_request()
- | exit_request(),
- state()
+ | exit_request()
) ->
{response,
initialize_result()
| initialized_result()
| shutdown_result()
| exit_result()}.
-handle_request({initialize, Params}, _State) ->
+handle_request({initialize, Params}) ->
#{
<<"rootUri">> := RootUri0,
<<"capabilities">> := Capabilities
@@ -86,7 +84,7 @@ handle_request({initialize, Params}, _State) ->
end,
ok = els_config:initialize(RootUri, Capabilities, InitOptions, true),
{response, server_capabilities()};
-handle_request({initialized, _Params}, _State) ->
+handle_request({initialized, _Params}) ->
RootUri = els_config:get(root_uri),
NodeName = els_distribution_server:node_name(
<<"erlang_ls">>,
@@ -96,9 +94,9 @@ handle_request({initialized, _Params}, _State) ->
?LOG_INFO("Started distribution for: [~p]", [NodeName]),
els_indexing:maybe_start(),
{response, null};
-handle_request({shutdown, _Params}, _State) ->
+handle_request({shutdown, _Params}) ->
{response, null};
-handle_request({exit, #{status := Status}}, _State) ->
+handle_request({exit, #{status := Status}}) ->
?LOG_INFO("Language server stopping..."),
ExitCode =
case Status of
diff --git a/apps/els_lsp/src/els_hover_provider.erl b/apps/els_lsp/src/els_hover_provider.erl
index e084e2af4..e49aa0803 100644
--- a/apps/els_lsp/src/els_hover_provider.erl
+++ b/apps/els_lsp/src/els_hover_provider.erl
@@ -7,7 +7,7 @@
-export([
is_enabled/0,
- handle_request/2
+ handle_request/1
]).
-include("els_lsp.hrl").
@@ -24,8 +24,8 @@
is_enabled() ->
true.
--spec handle_request(any(), any()) -> {async, uri(), pid()}.
-handle_request({hover, Params}, _State) ->
+-spec handle_request(any()) -> {async, uri(), pid()}.
+handle_request({hover, Params}) ->
#{
<<"position">> := #{
<<"line">> := Line,
@@ -52,7 +52,7 @@ run_hover_job(Uri, Line, Character) ->
title => <<"Hover">>,
on_complete =>
fun(HoverResp) ->
- els_provider ! {result, HoverResp, self()},
+ els_server ! {result, HoverResp, self()},
ok
end
},
diff --git a/apps/els_lsp/src/els_implementation_provider.erl b/apps/els_lsp/src/els_implementation_provider.erl
index 21cbc4e82..34cffaea2 100644
--- a/apps/els_lsp/src/els_implementation_provider.erl
+++ b/apps/els_lsp/src/els_implementation_provider.erl
@@ -6,7 +6,7 @@
-export([
is_enabled/0,
- handle_request/2
+ handle_request/1
]).
%%==============================================================================
@@ -15,8 +15,8 @@
-spec is_enabled() -> boolean().
is_enabled() -> true.
--spec handle_request(tuple(), els_provider:state()) -> {response, [location()]}.
-handle_request({implementation, Params}, _State) ->
+-spec handle_request(tuple()) -> {response, [location()]}.
+handle_request({implementation, Params}) ->
#{
<<"position">> := #{
<<"line">> := Line,
diff --git a/apps/els_lsp/src/els_methods.erl b/apps/els_lsp/src/els_methods.erl
index d8a43d9fa..ba88f90b0 100644
--- a/apps/els_lsp/src/els_methods.erl
+++ b/apps/els_lsp/src/els_methods.erl
@@ -43,20 +43,21 @@
-include_lib("kernel/include/logger.hrl").
-type method_name() :: binary().
--type state() :: map().
-type params() :: map().
-type result() ::
- {response, params() | null, state()}
- | {error, params(), state()}
- | {noresponse, state()}
- | {noresponse, pid(), state()}
- | {notification, binary(), params(), state()}.
+ {response, params() | null, els_server:state()}
+ | {error, params(), els_server:state()}
+ | {noresponse, els_server:state()}
+ | {noresponse, uri(), pid(), els_server:state()}
+ | {notification, binary(), params(), els_server:state()}
+ | {diagnostics, uri(), [pid()], els_server:state()}
+ | {async, uri(), pid(), els_server:state()}.
-type request_type() :: notification | request.
%%==============================================================================
%% @doc Dispatch the handling of the method to els_method
%%==============================================================================
--spec dispatch(method_name(), params(), request_type(), state()) -> result().
+-spec dispatch(method_name(), params(), request_type(), els_server:state()) -> result().
dispatch(<<"$/", Method/binary>>, Params, notification, State) ->
Msg = "Ignoring $/ notification [method=~p] [params=~p]",
Fmt = [Method, Params],
@@ -81,17 +82,25 @@ dispatch(Method, Params, _Type, State) ->
not_implemented_method(Method, State);
Type:Reason:Stack ->
?LOG_ERROR(
- "Unexpected error [type=~p] [error=~p] [stack=~p]",
+ "Internal [type=~p] [error=~p] [stack=~p]",
[Type, Reason, Stack]
),
Error = #{
- code => ?ERR_UNKNOWN_ERROR_CODE,
- message => <<"Unexpected error while ", Method/binary>>
+ type => Type,
+ reason => Reason,
+ stack => Stack,
+ method => Method,
+ params => Params
},
- {error, Error, State}
+ ErrorMsg = els_utils:to_binary(lists:flatten(io_lib:format("~p", [Error]))),
+ ErrorResponse = #{
+ code => ?ERR_INTERNAL_ERROR,
+ message => <<"Internal Error: ", ErrorMsg/binary>>
+ },
+ {error, ErrorResponse, State}
end.
--spec do_dispatch(atom(), params(), state()) -> result().
+-spec do_dispatch(atom(), params(), els_server:state()) -> result().
do_dispatch(exit, Params, State) ->
els_methods:exit(Params, State);
do_dispatch(_Function, _Params, #{status := shutdown} = State) ->
@@ -113,7 +122,7 @@ do_dispatch(_Function, _Params, State) ->
},
{error, Result, State}.
--spec not_implemented_method(method_name(), state()) -> result().
+-spec not_implemented_method(method_name(), els_server:state()) -> result().
not_implemented_method(Method, State) ->
?LOG_WARNING("[Method not implemented] [method=~s]", [Method]),
Message = <<"Method not implemented: ", Method/binary>>,
@@ -137,7 +146,7 @@ method_to_function_name(Method) ->
%% Initialize
%%==============================================================================
--spec initialize(params(), state()) -> result().
+-spec initialize(params(), els_server:state()) -> result().
initialize(Params, State) ->
Provider = els_general_provider,
Request = {initialize, Params},
@@ -148,7 +157,7 @@ initialize(Params, State) ->
%% Initialized
%%==============================================================================
--spec initialized(params(), state()) -> result().
+-spec initialized(params(), els_server:state()) -> result().
initialized(Params, State) ->
Provider = els_general_provider,
Request = {initialized, Params},
@@ -173,7 +182,7 @@ initialized(Params, State) ->
%% shutdown
%%==============================================================================
--spec shutdown(params(), state()) -> result().
+-spec shutdown(params(), els_server:state()) -> result().
shutdown(Params, State) ->
Provider = els_general_provider,
Request = {shutdown, Params},
@@ -184,54 +193,56 @@ shutdown(Params, State) ->
%% exit
%%==============================================================================
--spec exit(params(), state()) -> no_return().
+-spec exit(params(), els_server:state()) -> no_return().
exit(_Params, State) ->
Provider = els_general_provider,
- Request = {exit, #{status => maps:get(status, State, undefined)}},
+ Request = {exit, #{status => maps:get(status, State)}},
{response, _Response} = els_provider:handle_request(Provider, Request),
- {noresponse, #{}}.
+ %% Only reached by property-based test (where halt/1 is mocked for
+ %% faster iteration
+ {noresponse, State#{status => exiting}}.
%%==============================================================================
%% textDocument/didopen
%%==============================================================================
--spec textdocument_didopen(params(), state()) -> result().
+-spec textdocument_didopen(params(), els_server:state()) -> result().
textdocument_didopen(Params, #{open_buffers := OpenBuffers} = State) ->
#{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
Provider = els_text_synchronization_provider,
Request = {did_open, Params},
- noresponse = els_provider:handle_request(Provider, Request),
- {noresponse, State#{open_buffers => sets:add_element(Uri, OpenBuffers)}}.
+ {diagnostics, Uri, Jobs} = els_provider:handle_request(Provider, Request),
+ {diagnostics, Uri, Jobs, State#{open_buffers => sets:add_element(Uri, OpenBuffers)}}.
%%==============================================================================
%% textDocument/didchange
%%==============================================================================
--spec textdocument_didchange(params(), state()) -> result().
-textdocument_didchange(Params, State) ->
+-spec textdocument_didchange(params(), els_server:state()) -> result().
+textdocument_didchange(Params, State0) ->
#{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
- els_provider:cancel_request_by_uri(Uri),
+ State = cancel_request_by_uri(Uri, State0),
Provider = els_text_synchronization_provider,
Request = {did_change, Params},
- els_provider:handle_request(Provider, Request),
+ _ = els_provider:handle_request(Provider, Request),
{noresponse, State}.
%%==============================================================================
%% textDocument/didsave
%%==============================================================================
--spec textdocument_didsave(params(), state()) -> result().
+-spec textdocument_didsave(params(), els_server:state()) -> result().
textdocument_didsave(Params, State) ->
Provider = els_text_synchronization_provider,
Request = {did_save, Params},
- noresponse = els_provider:handle_request(Provider, Request),
- {noresponse, State}.
+ {diagnostics, Uri, Jobs} = els_provider:handle_request(Provider, Request),
+ {diagnostics, Uri, Jobs, State}.
%%==============================================================================
%% textDocument/didclose
%%==============================================================================
--spec textdocument_didclose(params(), state()) -> result().
+-spec textdocument_didclose(params(), els_server:state()) -> result().
textdocument_didclose(Params, #{open_buffers := OpenBuffers} = State) ->
#{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
Provider = els_text_synchronization_provider,
@@ -243,7 +254,7 @@ textdocument_didclose(Params, #{open_buffers := OpenBuffers} = State) ->
%% textdocument/documentSymbol
%%==============================================================================
--spec textdocument_documentsymbol(params(), state()) -> result().
+-spec textdocument_documentsymbol(params(), els_server:state()) -> result().
textdocument_documentsymbol(Params, State) ->
Provider = els_document_symbol_provider,
Request = {document_symbol, Params},
@@ -254,17 +265,17 @@ textdocument_documentsymbol(Params, State) ->
%% textDocument/hover
%%==============================================================================
--spec textdocument_hover(params(), state()) -> result().
+-spec textdocument_hover(params(), els_server:state()) -> result().
textdocument_hover(Params, State) ->
Provider = els_hover_provider,
- {async, Job} = els_provider:handle_request(Provider, {hover, Params}),
- {noresponse, Job, State}.
+ {async, Uri, Job} = els_provider:handle_request(Provider, {hover, Params}),
+ {async, Uri, Job, State}.
%%==============================================================================
%% textDocument/completion
%%==============================================================================
--spec textdocument_completion(params(), state()) -> result().
+-spec textdocument_completion(params(), els_server:state()) -> result().
textdocument_completion(Params, State) ->
Provider = els_completion_provider,
{response, Response} =
@@ -275,7 +286,7 @@ textdocument_completion(Params, State) ->
%% completionItem/resolve
%%==============================================================================
--spec completionitem_resolve(params(), state()) -> result().
+-spec completionitem_resolve(params(), els_server:state()) -> result().
completionitem_resolve(Params, State) ->
Provider = els_completion_provider,
{response, Response} =
@@ -286,7 +297,7 @@ completionitem_resolve(Params, State) ->
%% textDocument/definition
%%==============================================================================
--spec textdocument_definition(params(), state()) -> result().
+-spec textdocument_definition(params(), els_server:state()) -> result().
textdocument_definition(Params, State) ->
Provider = els_definition_provider,
{response, Response} =
@@ -297,7 +308,7 @@ textdocument_definition(Params, State) ->
%% textDocument/references
%%==============================================================================
--spec textdocument_references(params(), state()) -> result().
+-spec textdocument_references(params(), els_server:state()) -> result().
textdocument_references(Params, State) ->
Provider = els_references_provider,
{response, Response} =
@@ -308,7 +319,7 @@ textdocument_references(Params, State) ->
%% textDocument/documentHightlight
%%==============================================================================
--spec textdocument_documenthighlight(params(), state()) -> result().
+-spec textdocument_documenthighlight(params(), els_server:state()) -> result().
textdocument_documenthighlight(Params, State) ->
Provider = els_document_highlight_provider,
{response, Response} =
@@ -319,7 +330,7 @@ textdocument_documenthighlight(Params, State) ->
%% textDocument/formatting
%%==============================================================================
--spec textdocument_formatting(params(), state()) -> result().
+-spec textdocument_formatting(params(), els_server:state()) -> result().
textdocument_formatting(Params, State) ->
Provider = els_formatting_provider,
{response, Response} =
@@ -330,7 +341,7 @@ textdocument_formatting(Params, State) ->
%% textDocument/rangeFormatting
%%==============================================================================
--spec textdocument_rangeformatting(params(), state()) -> result().
+-spec textdocument_rangeformatting(params(), els_server:state()) -> result().
textdocument_rangeformatting(Params, State) ->
Provider = els_formatting_provider,
{response, Response} =
@@ -341,7 +352,7 @@ textdocument_rangeformatting(Params, State) ->
%% textDocument/onTypeFormatting
%%==============================================================================
--spec textdocument_ontypeformatting(params(), state()) -> result().
+-spec textdocument_ontypeformatting(params(), els_server:state()) -> result().
textdocument_ontypeformatting(Params, State) ->
Provider = els_formatting_provider,
{response, Response} =
@@ -352,7 +363,7 @@ textdocument_ontypeformatting(Params, State) ->
%% textDocument/foldingRange
%%==============================================================================
--spec textdocument_foldingrange(params(), state()) -> result().
+-spec textdocument_foldingrange(params(), els_server:state()) -> result().
textdocument_foldingrange(Params, State) ->
Provider = els_folding_range_provider,
{response, Response} =
@@ -363,7 +374,7 @@ textdocument_foldingrange(Params, State) ->
%% textDocument/implementation
%%==============================================================================
--spec textdocument_implementation(params(), state()) -> result().
+-spec textdocument_implementation(params(), els_server:state()) -> result().
textdocument_implementation(Params, State) ->
Provider = els_implementation_provider,
{response, Response} =
@@ -374,7 +385,7 @@ textdocument_implementation(Params, State) ->
%% workspace/didChangeConfiguration
%%==============================================================================
--spec workspace_didchangeconfiguration(params(), state()) -> result().
+-spec workspace_didchangeconfiguration(params(), els_server:state()) -> result().
workspace_didchangeconfiguration(_Params, State) ->
%% Some clients send this notification on startup, even though we
%% have no server-side config. So swallow it without complaining.
@@ -384,7 +395,7 @@ workspace_didchangeconfiguration(_Params, State) ->
%% textDocument/codeAction
%%==============================================================================
--spec textdocument_codeaction(params(), state()) -> result().
+-spec textdocument_codeaction(params(), els_server:state()) -> result().
textdocument_codeaction(Params, State) ->
Provider = els_code_action_provider,
{response, Response} =
@@ -395,18 +406,18 @@ textdocument_codeaction(Params, State) ->
%% textDocument/codeLens
%%==============================================================================
--spec textdocument_codelens(params(), state()) -> result().
+-spec textdocument_codelens(params(), els_server:state()) -> result().
textdocument_codelens(Params, State) ->
Provider = els_code_lens_provider,
- {async, Job} =
+ {async, Uri, Job} =
els_provider:handle_request(Provider, {document_codelens, Params}),
- {noresponse, Job, State}.
+ {async, Uri, Job, State}.
%%==============================================================================
%% textDocument/rename
%%==============================================================================
--spec textdocument_rename(params(), state()) -> result().
+-spec textdocument_rename(params(), els_server:state()) -> result().
textdocument_rename(Params, State) ->
Provider = els_rename_provider,
{response, Response} =
@@ -417,7 +428,7 @@ textdocument_rename(Params, State) ->
%% textDocument/preparePreparecallhierarchy
%%==============================================================================
--spec textdocument_preparecallhierarchy(params(), state()) -> result().
+-spec textdocument_preparecallhierarchy(params(), els_server:state()) -> result().
textdocument_preparecallhierarchy(Params, State) ->
Provider = els_call_hierarchy_provider,
{response, Response} =
@@ -428,7 +439,7 @@ textdocument_preparecallhierarchy(Params, State) ->
%% textDocument/signatureHelp
%%==============================================================================
--spec textdocument_signaturehelp(params(), state()) -> result().
+-spec textdocument_signaturehelp(params(), els_server:state()) -> result().
textdocument_signaturehelp(Params, State) ->
Provider = els_signature_help_provider,
{response, Response} =
@@ -439,7 +450,7 @@ textdocument_signaturehelp(Params, State) ->
%% callHierarchy/incomingCalls
%%==============================================================================
--spec callhierarchy_incomingcalls(params(), state()) -> result().
+-spec callhierarchy_incomingcalls(params(), els_server:state()) -> result().
callhierarchy_incomingcalls(Params, State) ->
Provider = els_call_hierarchy_provider,
{response, Response} =
@@ -450,7 +461,7 @@ callhierarchy_incomingcalls(Params, State) ->
%% callHierarchy/outgoingCalls
%%==============================================================================
--spec callhierarchy_outgoingcalls(params(), state()) -> result().
+-spec callhierarchy_outgoingcalls(params(), els_server:state()) -> result().
callhierarchy_outgoingcalls(Params, State) ->
Provider = els_call_hierarchy_provider,
{response, Response} =
@@ -461,7 +472,7 @@ callhierarchy_outgoingcalls(Params, State) ->
%% workspace/executeCommand
%%==============================================================================
--spec workspace_executecommand(params(), state()) -> result().
+-spec workspace_executecommand(params(), els_server:state()) -> result().
workspace_executecommand(Params, State) ->
Provider = els_execute_command_provider,
{response, Response} =
@@ -472,7 +483,7 @@ workspace_executecommand(Params, State) ->
%% workspace/didChangeWatchedFiles
%%==============================================================================
--spec workspace_didchangewatchedfiles(map(), state()) -> result().
+-spec workspace_didchangewatchedfiles(map(), els_server:state()) -> result().
workspace_didchangewatchedfiles(Params0, State) ->
#{open_buffers := OpenBuffers} = State,
#{<<"changes">> := Changes0} = Params0,
@@ -491,9 +502,28 @@ workspace_didchangewatchedfiles(Params0, State) ->
%% workspace/symbol
%%==============================================================================
--spec workspace_symbol(map(), state()) -> result().
+-spec workspace_symbol(map(), els_server:state()) -> result().
workspace_symbol(Params, State) ->
Provider = els_workspace_symbol_provider,
{response, Response} =
els_provider:handle_request(Provider, {symbol, Params}),
{response, Response, State}.
+
+%%==============================================================================
+%% Internal Functions
+%%==============================================================================
+-spec cancel_request_by_uri(uri(), els_server:state()) -> els_server:state().
+cancel_request_by_uri(Uri, State) ->
+ #{in_progress := InProgress0} = State,
+ Fun = fun({U, Job}) ->
+ case U =:= Uri of
+ true ->
+ els_background_job:stop(Job),
+ false;
+ false ->
+ true
+ end
+ end,
+ InProgress = lists:filtermap(Fun, InProgress0),
+ ?LOG_DEBUG("Cancelling requests by Uri [uri=~p]", [Uri]),
+ State#{in_progress => InProgress}.
diff --git a/apps/els_lsp/src/els_references_provider.erl b/apps/els_lsp/src/els_references_provider.erl
index c2c4f35dd..aa67b70b1 100644
--- a/apps/els_lsp/src/els_references_provider.erl
+++ b/apps/els_lsp/src/els_references_provider.erl
@@ -4,7 +4,7 @@
-export([
is_enabled/0,
- handle_request/2
+ handle_request/1
]).
%% For use in other providers
@@ -29,8 +29,8 @@
-spec is_enabled() -> boolean().
is_enabled() -> true.
--spec handle_request(any(), any()) -> {response, [location()] | null}.
-handle_request({references, Params}, _State) ->
+-spec handle_request(any()) -> {response, [location()] | null}.
+handle_request({references, Params}) ->
#{
<<"position">> := #{
<<"line">> := Line,
diff --git a/apps/els_lsp/src/els_rename_provider.erl b/apps/els_lsp/src/els_rename_provider.erl
index 024662bdd..6d33e0ffb 100644
--- a/apps/els_lsp/src/els_rename_provider.erl
+++ b/apps/els_lsp/src/els_rename_provider.erl
@@ -3,7 +3,7 @@
-behaviour(els_provider).
-export([
- handle_request/2,
+ handle_request/1,
is_enabled/0
]).
@@ -27,8 +27,8 @@
-spec is_enabled() -> boolean().
is_enabled() -> true.
--spec handle_request(any(), any()) -> {response, any()}.
-handle_request({rename, Params}, _State) ->
+-spec handle_request(any()) -> {response, any()}.
+handle_request({rename, Params}) ->
#{
<<"textDocument">> := #{<<"uri">> := Uri},
<<"position">> := #{
diff --git a/apps/els_lsp/src/els_server.erl b/apps/els_lsp/src/els_server.erl
index 7a06ba438..e87ce12d1 100644
--- a/apps/els_lsp/src/els_server.erl
+++ b/apps/els_lsp/src/els_server.erl
@@ -18,7 +18,9 @@
-export([
init/1,
handle_call/3,
- handle_cast/2
+ handle_cast/2,
+ handle_info/2,
+ terminate/2
]).
%% API
@@ -31,32 +33,40 @@
]).
%% Testing
--export([reset_internal_state/0]).
+-export([reset_state/0]).
%%==============================================================================
%% Includes
%%==============================================================================
-include_lib("kernel/include/logger.hrl").
+-include_lib("els_core/include/els_core.hrl").
%%==============================================================================
%% Macros
%%==============================================================================
-define(SERVER, ?MODULE).
-%%==============================================================================
-%% Record Definitions
-%%==============================================================================
--record(state, {
- io_device :: any(),
- request_id :: number(),
- internal_state :: map(),
- pending :: [{number(), pid()}]
-}).
-
%%==============================================================================
%% Type Definitions
%%==============================================================================
--type state() :: #state{}.
+-type state() ::
+ #{
+ status := started | initialized | shutdown | exiting,
+ io_device := pid() | standard_io,
+ request_id := number(),
+ pending := [{number(), pid()}],
+ open_buffers := sets:set(buffer()),
+ in_progress := [progress_entry()],
+ in_progress_diagnostics := [diagnostic_entry()]
+ }.
+-type buffer() :: uri().
+-type progress_entry() :: {uri(), pid()}.
+-type diagnostic_entry() :: #{
+ uri := uri(),
+ pending := [pid()],
+ diagnostics := [els_diagnostics:diagnostic()]
+}.
+-export_type([diagnostic_entry/0, state/0]).
%%==============================================================================
%% API
@@ -93,28 +103,40 @@ send_response(Job, Result) ->
%%==============================================================================
%% Testing
%%==============================================================================
--spec reset_internal_state() -> ok.
-reset_internal_state() ->
- gen_server:call(?MODULE, {reset_internal_state}).
+-spec reset_state() -> ok.
+reset_state() ->
+ gen_server:call(?MODULE, {reset_state}).
%%==============================================================================
%% gen_server callbacks
%%==============================================================================
-spec init([]) -> {ok, state()}.
init([]) ->
+ %% Ensure the terminate function is called on shutdown, allowing the
+ %% job to clean up.
+ process_flag(trap_exit, true),
?LOG_INFO("Starting els_server..."),
- State = #state{
- request_id = 0,
- internal_state = #{open_buffers => sets:new()},
- pending = []
+ State = #{
+ status => started,
+ io_device => standard_io,
+ request_id => 0,
+ pending => [],
+ open_buffers => sets:new(),
+ in_progress => [],
+ in_progress_diagnostics => []
},
{ok, State}.
-spec handle_call(any(), any(), state()) -> {reply, any(), state()}.
handle_call({set_io_device, IoDevice}, _From, State) ->
- {reply, ok, State#state{io_device = IoDevice}};
-handle_call({reset_internal_state}, _From, State) ->
- {reply, ok, State#state{internal_state = #{}}}.
+ {reply, ok, State#{io_device := IoDevice}};
+handle_call({reset_state}, _From, State) ->
+ {reply, ok, State#{
+ status => started,
+ open_buffers => sets:new(),
+ in_progress => [],
+ in_progress_diagnostics => []
+ }}.
-spec handle_cast(any(), state()) -> {noreply, state()}.
handle_cast({process_requests, Requests}, State0) ->
@@ -132,6 +154,59 @@ handle_cast({response, Job, Result}, State0) ->
handle_cast(_, State) ->
{noreply, State}.
+-spec handle_info(any(), els_server:state()) -> {noreply, els_server:state()}.
+handle_info({result, Result, Job}, State0) ->
+ ?LOG_DEBUG("Received result [job=~p]", [Job]),
+ #{in_progress := InProgress} = State0,
+ State = do_send_response(Job, Result, State0),
+ NewState = State#{in_progress => lists:keydelete(Job, 2, InProgress)},
+ {noreply, NewState};
+%% LSP 3.15 introduce versioning for diagnostics. Until all clients
+%% support it, we need to keep track of old diagnostics and re-publish
+%% them every time we get a new chunk.
+handle_info({diagnostics, Diagnostics, Job}, State) ->
+ #{in_progress_diagnostics := InProgress} = State,
+ ?LOG_DEBUG("Received diagnostics [job=~p]", [Job]),
+ case find_entry(Job, InProgress) of
+ {ok, {
+ #{
+ pending := Jobs,
+ diagnostics := OldDiagnostics,
+ uri := Uri
+ },
+ Rest
+ }} ->
+ NewDiagnostics = Diagnostics ++ OldDiagnostics,
+ els_diagnostics_provider:publish(Uri, NewDiagnostics),
+ NewState =
+ case lists:delete(Job, Jobs) of
+ [] ->
+ State#{in_progress_diagnostics => Rest};
+ Remaining ->
+ State#{
+ in_progress_diagnostics =>
+ [
+ #{
+ pending => Remaining,
+ diagnostics => NewDiagnostics,
+ uri => Uri
+ }
+ | Rest
+ ]
+ }
+ end,
+ {noreply, NewState};
+ {error, not_found} ->
+ {noreply, State}
+ end;
+handle_info(_Request, State) ->
+ {noreply, State}.
+
+-spec terminate(any(), els_server:state()) -> ok.
+terminate(_Reason, #{in_progress := InProgress}) ->
+ [els_background_job:stop(Job) || {_Uri, Job} <- InProgress],
+ ok.
+
%%==============================================================================
%% Internal Functions
%%==============================================================================
@@ -144,7 +219,7 @@ handle_request(
State0
) ->
#{<<"id">> := Id} = Params,
- #state{pending = Pending} = State0,
+ #{pending := Pending, in_progress := InProgress} = State0,
case lists:keyfind(Id, 1, Pending) of
false ->
?LOG_DEBUG(
@@ -154,14 +229,18 @@ handle_request(
State0;
{RequestId, Job} when RequestId =:= Id ->
?LOG_DEBUG("[SERVER] Cancelling request [id=~p] [job=~p]", [Id, Job]),
- els_provider:cancel_request(Job),
- State0#state{pending = lists:keydelete(Id, 1, Pending)}
+ els_background_job:stop(Job),
+ State0#{
+ pending => lists:keydelete(Id, 1, Pending),
+ in_progress => lists:keydelete(Job, 2, InProgress)
+ }
end;
handle_request(
#{<<"method">> := _ReqMethod} = Request,
- #state{
- internal_state = InternalState,
- pending = Pending
+ #{
+ pending := Pending,
+ in_progress := InProgress,
+ in_progress_diagnostics := InProgressDiagnostics
} = State0
) ->
Method = maps:get(<<"method">>, Request),
@@ -171,14 +250,14 @@ handle_request(
true -> request;
false -> notification
end,
- case els_methods:dispatch(Method, Params, Type, InternalState) of
- {response, Result, NewInternalState} ->
+ case els_methods:dispatch(Method, Params, Type, State0) of
+ {response, Result, State} ->
RequestId = maps:get(<<"id">>, Request),
Response = els_protocol:response(RequestId, Result),
?LOG_DEBUG("[SERVER] Sending response [response=~s]", [Response]),
send(Response, State0),
- State0#state{internal_state = NewInternalState};
- {error, Error, NewInternalState} ->
+ State;
+ {error, Error, State} ->
RequestId = maps:get(<<"id">>, Request, null),
ErrorResponse = els_protocol:error(RequestId, Error),
?LOG_DEBUG(
@@ -186,24 +265,27 @@ handle_request(
[ErrorResponse]
),
send(ErrorResponse, State0),
- State0#state{internal_state = NewInternalState};
- {noresponse, NewInternalState} ->
+ State;
+ {noresponse, State} ->
?LOG_DEBUG("[SERVER] No response", []),
- State0#state{internal_state = NewInternalState};
- {noresponse, BackgroundJob, NewInternalState} ->
+ State;
+ {async, Uri, BackgroundJob, State} ->
RequestId = maps:get(<<"id">>, Request),
?LOG_DEBUG(
"[SERVER] Suspending response [background_job=~p]",
[BackgroundJob]
),
NewPending = [{RequestId, BackgroundJob} | Pending],
- State0#state{
- internal_state = NewInternalState,
- pending = NewPending
+ State#{
+ pending => NewPending,
+ in_progress => [{Uri, BackgroundJob} | InProgress]
};
- {notification, M, P, NewInternalState} ->
+ {diagnostics, Uri, Jobs, State} ->
+ Entry = #{uri => Uri, pending => Jobs, diagnostics => []},
+ State#{in_progress_diagnostics => [Entry | InProgressDiagnostics]};
+ {notification, M, P, State} ->
do_send_notification(M, P, State0),
- State0#state{internal_state = NewInternalState}
+ State
end;
handle_request(Response, State0) ->
?LOG_DEBUG(
@@ -227,7 +309,7 @@ do_send_notification(Method, Params, State) ->
send(Notification, State).
-spec do_send_request(binary(), map(), state()) -> state().
-do_send_request(Method, Params, #state{request_id = RequestId0} = State0) ->
+do_send_request(Method, Params, #{request_id := RequestId0} = State0) ->
RequestId = RequestId0 + 1,
Request = els_protocol:request(RequestId, Method, Params),
?LOG_DEBUG(
@@ -235,11 +317,11 @@ do_send_request(Method, Params, #state{request_id = RequestId0} = State0) ->
[Request]
),
send(Request, State0),
- State0#state{request_id = RequestId}.
+ State0#{request_id => RequestId}.
-spec do_send_response(pid(), any(), state()) -> state().
do_send_response(Job, Result, State0) ->
- #state{pending = Pending0} = State0,
+ #{pending := Pending0} = State0,
case lists:keyfind(Job, 2, Pending0) of
false ->
?LOG_DEBUG(
@@ -255,9 +337,28 @@ do_send_response(Job, Result, State0) ->
),
send(Response, State0),
Pending = lists:keydelete(RequestId, 1, Pending0),
- State0#state{pending = Pending}
+ State0#{pending => Pending}
end.
-spec send(binary(), state()) -> ok.
-send(Payload, #state{io_device = IoDevice}) ->
+send(Payload, #{io_device := IoDevice}) ->
els_stdio:send(IoDevice, Payload).
+
+-spec find_entry(pid(), [els_server:diagnostic_entry()]) ->
+ {ok, {els_server:diagnostic_entry(), [els_server:diagnostic_entry()]}}
+ | {error, not_found}.
+find_entry(Job, InProgress) ->
+ find_entry(Job, InProgress, []).
+
+-spec find_entry(pid(), [els_server:diagnostic_entry()], [els_server:diagnostic_entry()]) ->
+ {ok, {els_server:diagnostic_entry(), [els_server:diagnostic_entry()]}}
+ | {error, not_found}.
+find_entry(_Job, [], []) ->
+ {error, not_found};
+find_entry(Job, [#{pending := Pending} = Entry | Rest], Acc) ->
+ case lists:member(Job, Pending) of
+ true ->
+ {ok, {Entry, Rest ++ Acc}};
+ false ->
+ find_entry(Job, Rest, [Entry | Acc])
+ end.
diff --git a/apps/els_lsp/src/els_signature_help_provider.erl b/apps/els_lsp/src/els_signature_help_provider.erl
index ce9443874..86391a21f 100644
--- a/apps/els_lsp/src/els_signature_help_provider.erl
+++ b/apps/els_lsp/src/els_signature_help_provider.erl
@@ -7,7 +7,7 @@
-export([
is_enabled/0,
- handle_request/2,
+ handle_request/1,
trigger_characters/0
]).
@@ -26,8 +26,9 @@ trigger_characters() ->
is_enabled() ->
false.
--spec handle_request(els_provider:request(), any()) -> {response, signature_help() | null}.
-handle_request({signature_help, Params}, _State) ->
+-spec handle_request(els_provider:provider_request()) ->
+ {response, signature_help() | null}.
+handle_request({signature_help, Params}) ->
#{
<<"position">> := #{
<<"line">> := Line,
diff --git a/apps/els_lsp/src/els_sup.erl b/apps/els_lsp/src/els_sup.erl
index 567eae62d..cbef27ec0 100644
--- a/apps/els_lsp/src/els_sup.erl
+++ b/apps/els_lsp/src/els_sup.erl
@@ -76,10 +76,6 @@ init([]) ->
#{
id => els_server,
start => {els_server, start_link, []}
- },
- #{
- id => els_provider,
- start => {els_provider, start_link, []}
}
],
{ok, {SupFlags, ChildSpecs}}.
diff --git a/apps/els_lsp/src/els_text_synchronization_provider.erl b/apps/els_lsp/src/els_text_synchronization_provider.erl
index 913d5cdbd..2fd80fc25 100644
--- a/apps/els_lsp/src/els_text_synchronization_provider.erl
+++ b/apps/els_lsp/src/els_text_synchronization_provider.erl
@@ -2,7 +2,7 @@
-behaviour(els_provider).
-export([
- handle_request/2,
+ handle_request/1,
options/0
]).
@@ -19,15 +19,15 @@ options() ->
save => #{includeText => false}
}.
--spec handle_request(any(), any()) ->
+-spec handle_request(any()) ->
{diagnostics, uri(), [pid()]}
| noresponse
| {async, uri(), pid()}.
-handle_request({did_open, Params}, _State) ->
+handle_request({did_open, Params}) ->
ok = els_text_synchronization:did_open(Params),
#{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
{diagnostics, Uri, els_diagnostics:run_diagnostics(Uri)};
-handle_request({did_change, Params}, _State) ->
+handle_request({did_change, Params}) ->
#{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
case els_text_synchronization:did_change(Params) of
ok ->
@@ -35,13 +35,13 @@ handle_request({did_change, Params}, _State) ->
{ok, Job} ->
{async, Uri, Job}
end;
-handle_request({did_save, Params}, _State) ->
+handle_request({did_save, Params}) ->
ok = els_text_synchronization:did_save(Params),
#{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
{diagnostics, Uri, els_diagnostics:run_diagnostics(Uri)};
-handle_request({did_close, Params}, _State) ->
+handle_request({did_close, Params}) ->
ok = els_text_synchronization:did_close(Params),
noresponse;
-handle_request({did_change_watched_files, Params}, _State) ->
+handle_request({did_change_watched_files, Params}) ->
ok = els_text_synchronization:did_change_watched_files(Params),
noresponse.
diff --git a/apps/els_lsp/src/els_workspace_symbol_provider.erl b/apps/els_lsp/src/els_workspace_symbol_provider.erl
index a88d709fe..c2e57f304 100644
--- a/apps/els_lsp/src/els_workspace_symbol_provider.erl
+++ b/apps/els_lsp/src/els_workspace_symbol_provider.erl
@@ -4,23 +4,21 @@
-export([
is_enabled/0,
- handle_request/2
+ handle_request/1
]).
-include("els_lsp.hrl").
-define(LIMIT, 100).
--type state() :: any().
-
%%==============================================================================
%% els_provider functions
%%==============================================================================
-spec is_enabled() -> boolean().
is_enabled() -> true.
--spec handle_request(any(), state()) -> {response, any()}.
-handle_request({symbol, Params}, _State) ->
+-spec handle_request(any()) -> {response, any()}.
+handle_request({symbol, Params}) ->
%% TODO: Version 3.15 of the protocol introduces a much nicer way of
%% specifying queries, allowing clients to send the symbol kind.
#{<<"query">> := Query} = Params,
diff --git a/apps/els_lsp/test/els_server_SUITE.erl b/apps/els_lsp/test/els_server_SUITE.erl
index e199d43f3..b873f7ee2 100644
--- a/apps/els_lsp/test/els_server_SUITE.erl
+++ b/apps/els_lsp/test/els_server_SUITE.erl
@@ -108,6 +108,6 @@ wait_until_no_lens_jobs() ->
-spec get_current_lens_jobs() -> [pid()].
get_current_lens_jobs() ->
- State = sys:get_state(els_provider, 30 * 1000),
+ State = sys:get_state(els_server, 30 * 1000),
#{in_progress := InProgress} = State,
[Job || {_Uri, Job} <- InProgress].
diff --git a/apps/els_lsp/test/prop_statem.erl b/apps/els_lsp/test/prop_statem.erl
index 74a2a8ec3..91bb1f7c7 100644
--- a/apps/els_lsp/test/prop_statem.erl
+++ b/apps/els_lsp/test/prop_statem.erl
@@ -396,7 +396,7 @@ cleanup() ->
catch disconnect(),
%% Restart the server, since though the client disconnects the
%% server keeps its state.
- els_server:reset_internal_state(),
+ els_server:reset_state(),
ok.
%%==============================================================================
From 637022b7584ae3b6f13a1ecfd4c8f0b1a879130e Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Tue, 14 Jun 2022 14:55:14 +0200
Subject: [PATCH 095/239] Register capabilities dynamically, complete support
for didChangedWatchedFiles (#1332)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Register capability for didChangeWatchedFiles
If the client supports it, be notified about OS changes about watched files.
* Register capabilities dynamically, complete support for didChangedWatchedFiles
By being able to register capabilities dynamically, Erlang LS can
instruct the client to send `didChangeWatchedFiles`
requests whenever a file or directory is modified outside of the
client itself. Through this mechanism it is possible to prevent text
synchronization issues and crashes during events such as a rebase or a checkout.
* Update apps/els_lsp/src/els_general_provider.erl
Co-authored-by: Michał Muskała
---
apps/els_lsp/src/els_general_provider.erl | 37 +++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/apps/els_lsp/src/els_general_provider.erl b/apps/els_lsp/src/els_general_provider.erl
index 8cda414e3..d50f54ad8 100644
--- a/apps/els_lsp/src/els_general_provider.erl
+++ b/apps/els_lsp/src/els_general_provider.erl
@@ -90,6 +90,7 @@ handle_request({initialized, _Params}) ->
<<"erlang_ls">>,
filename:basename(RootUri)
),
+ register_capabilities(),
els_distribution_server:start_distribution(NodeName),
?LOG_INFO("Started distribution for: [~p]", [NodeName]),
els_indexing:maybe_start(),
@@ -176,3 +177,39 @@ server_capabilities() ->
version => els_utils:to_binary(Version)
}
}.
+
+-spec register_capabilities() -> ok.
+register_capabilities() ->
+ Methods = [<<"didChangeWatchedFiles">>],
+ ClientCapabilities = els_config:get(capabilities),
+ Registrations = [
+ dynamic_registration_options(Method)
+ || Method <- Methods, is_dynamic_registration_enabled(Method, ClientCapabilities)
+ ],
+ case Registrations of
+ [] ->
+ ?LOG_INFO("Skipping dynamic capabilities registration");
+ _ ->
+ Params = #{registrations => Registrations},
+ els_server:send_request(<<"client/registerCapability">>, Params)
+ end.
+
+-spec is_dynamic_registration_enabled(binary(), map()) -> boolean().
+is_dynamic_registration_enabled(Method, ClientCapabilities) ->
+ maps:get(
+ <<"dynamicRegistration">>,
+ maps:get(Method, maps:get(<<"workspace">>, ClientCapabilities, #{}), #{}),
+ false
+ ).
+
+-spec dynamic_registration_options(binary()) -> map().
+dynamic_registration_options(<<"didChangeWatchedFiles">>) ->
+ RootPath = els_uri:path(els_config:get(root_uri)),
+ GlobPattern = filename:join([RootPath, "**", "*.{e,h}rl"]),
+ #{
+ id => <<"workspace/didChangeWatchedFiles">>,
+ method => <<"workspace/didChangeWatchedFiles">>,
+ registerOptions => #{
+ watchers => [#{globPattern => GlobPattern}]
+ }
+ }.
From 4ad07492c2f577da4a1fbd79877036f820d9e2c3 Mon Sep 17 00:00:00 2001
From: Gabriela Sampaio
Date: Mon, 20 Jun 2022 14:56:14 +0100
Subject: [PATCH 096/239] GotoDef returning multiple definitions for atoms
(#1338)
Allowing for more flexibility when dealing with Erlang atoms. Hence, whenever the user tries to navigate to a definition, they will be able to choose which definition to navigate to.
Tasks: T123303743
---
.../code_navigation/src/code_navigation.erl | 10 ++
.../src/els_call_hierarchy_provider.erl | 2 +-
apps/els_lsp/src/els_code_navigation.erl | 101 +++++++----
apps/els_lsp/src/els_crossref_diagnostics.erl | 2 +-
apps/els_lsp/src/els_definition_provider.erl | 12 +-
apps/els_lsp/src/els_docs.erl | 4 +-
apps/els_lsp/src/els_references_provider.erl | 4 +-
apps/els_lsp/src/els_rename_provider.erl | 2 +-
.../src/els_unused_includes_diagnostics.erl | 6 +-
apps/els_lsp/test/els_code_lens_SUITE.erl | 2 +-
apps/els_lsp/test/els_completion_SUITE.erl | 7 +-
apps/els_lsp/test/els_definition_SUITE.erl | 167 ++++++++++++------
.../test/els_document_symbol_SUITE.erl | 7 +-
.../els_lsp/test/els_rebar3_release_SUITE.erl | 2 +-
14 files changed, 219 insertions(+), 109 deletions(-)
diff --git a/apps/els_lsp/priv/code_navigation/src/code_navigation.erl b/apps/els_lsp/priv/code_navigation/src/code_navigation.erl
index 56fb6247c..0ceff8bc2 100644
--- a/apps/els_lsp/priv/code_navigation/src/code_navigation.erl
+++ b/apps/els_lsp/priv/code_navigation/src/code_navigation.erl
@@ -122,3 +122,13 @@ macro_b(_X, _Y) ->
function_mb() ->
?MACRO_B(m, b).
+
+code_navigation() -> code_navigation.
+
+code_navigation(X) -> X.
+
+multiple_instances_same_file() -> {code_navigation, [simple_list], "abc"}.
+
+code_navigation_extra(X, Y, Z) -> [code_navigation_extra, X, Y, Z].
+
+multiple_instances_diff_file() -> code_navigation_extra.
diff --git a/apps/els_lsp/src/els_call_hierarchy_provider.erl b/apps/els_lsp/src/els_call_hierarchy_provider.erl
index d374e97ce..d7171994a 100644
--- a/apps/els_lsp/src/els_call_hierarchy_provider.erl
+++ b/apps/els_lsp/src/els_call_hierarchy_provider.erl
@@ -90,7 +90,7 @@ application_to_item(Uri, Application) ->
#{id := Id} = Application,
Name = els_utils:function_signature(Id),
case els_code_navigation:goto_definition(Uri, Application) of
- {ok, DefUri, DefPOI} ->
+ {ok, [{DefUri, DefPOI} | _]} ->
DefRange = maps:get(range, DefPOI),
Data = #{poi => DefPOI},
{ok, els_call_hierarchy_item:new(Name, DefUri, DefRange, DefRange, Data)};
diff --git a/apps/els_lsp/src/els_code_navigation.erl b/apps/els_lsp/src/els_code_navigation.erl
index e5dc405a0..86e8630c4 100644
--- a/apps/els_lsp/src/els_code_navigation.erl
+++ b/apps/els_lsp/src/els_code_navigation.erl
@@ -24,7 +24,7 @@
%%==============================================================================
-spec goto_definition(uri(), els_poi:poi()) ->
- {ok, uri(), els_poi:poi()} | {error, any()}.
+ {ok, [{uri(), els_poi:poi()}]} | {error, any()}.
goto_definition(
Uri,
Var = #{kind := variable}
@@ -33,7 +33,7 @@ goto_definition(
%% first occurrence of the variable in variable scope.
case find_in_scope(Uri, Var) of
[Var | _] -> {error, already_at_definition};
- [POI | _] -> {ok, Uri, POI};
+ [POI | _] -> {ok, [{Uri, POI}]};
% Probably due to parse error
[] -> {error, nothing_in_scope}
end;
@@ -46,7 +46,7 @@ goto_definition(
Kind =:= import_entry
->
case els_utils:find_module(M) of
- {ok, Uri} -> find(Uri, function, {F, A});
+ {ok, Uri} -> defs_to_res(find(Uri, function, {F, A}));
{error, Error} -> {error, Error}
end;
goto_definition(
@@ -60,27 +60,26 @@ goto_definition(
%% try to find local function first
%% fall back to bif search if unsuccessful
case find(Uri, function, {F, A}) of
- {error, Error} ->
+ [] ->
case is_imported_bif(Uri, F, A) of
true ->
goto_definition(Uri, POI#{id := {erlang, F, A}});
false ->
- {error, Error}
+ {error, not_found}
end;
Result ->
- Result
+ defs_to_res(Result)
end;
goto_definition(
Uri,
- #{kind := atom, id := Id} = POI
+ #{kind := atom, id := Id}
) ->
- %% Two interesting cases for atoms: testcases and modules.
- %% Testcases are functions with arity 1, so we first look for a function
- %% with the same name and arity 1 in the local scope
- %% If we can't find it, we hope that the atom refers to a module.
- case find(Uri, function, {Id, 1}) of
- {error, _Error} -> goto_definition(Uri, POI#{kind := module});
- Else -> Else
+ %% Two interesting cases for atoms: functions and modules.
+ %% We return all function defs with any arity combined with module defs.
+ DefsFun = find(Uri, function, {Id, any_arity}),
+ case els_utils:find_module(Id) of
+ {ok, ModUri} -> defs_to_res(DefsFun ++ find(ModUri, module, Id));
+ {error, _Error} -> defs_to_res(DefsFun)
end;
goto_definition(
_Uri,
@@ -90,7 +89,7 @@ goto_definition(
Kind =:= module
->
case els_utils:find_module(Module) of
- {ok, Uri} -> find(Uri, module, Module);
+ {ok, Uri} -> defs_to_res(find(Uri, module, Module));
{error, Error} -> {error, Error}
end;
goto_definition(
@@ -101,37 +100,37 @@ goto_definition(
} = POI
) ->
case find(Uri, define, Define) of
- {error, not_found} ->
+ [] ->
goto_definition(Uri, POI#{id => MacroName});
Else ->
- Else
+ defs_to_res(Else)
end;
goto_definition(Uri, #{kind := macro, id := Define}) ->
- find(Uri, define, Define);
+ defs_to_res(find(Uri, define, Define));
goto_definition(Uri, #{kind := record_expr, id := Record}) ->
- find(Uri, record, Record);
+ defs_to_res(find(Uri, record, Record));
goto_definition(Uri, #{kind := record_field, id := {Record, Field}}) ->
- find(Uri, record_def_field, {Record, Field});
+ defs_to_res(find(Uri, record_def_field, {Record, Field}));
goto_definition(_Uri, #{kind := Kind, id := Id}) when
Kind =:= include;
Kind =:= include_lib
->
case els_utils:find_header(els_utils:filename_to_atom(Id)) of
- {ok, Uri} -> {ok, Uri, beginning()};
+ {ok, Uri} -> {ok, [{Uri, beginning()}]};
{error, Error} -> {error, Error}
end;
goto_definition(_Uri, #{kind := type_application, id := {M, T, A}}) ->
case els_utils:find_module(M) of
- {ok, Uri} -> find(Uri, type_definition, {T, A});
+ {ok, Uri} -> defs_to_res(find(Uri, type_definition, {T, A}));
{error, Error} -> {error, Error}
end;
goto_definition(Uri, #{kind := Kind, id := {T, A}}) when
Kind =:= type_application; Kind =:= export_type_entry
->
- find(Uri, type_definition, {T, A});
+ defs_to_res(find(Uri, type_definition, {T, A}));
goto_definition(_Uri, #{kind := parse_transform, id := Module}) ->
case els_utils:find_module(Module) of
- {ok, Uri} -> find(Uri, module, Module);
+ {ok, Uri} -> defs_to_res(find(Uri, module, Module));
{error, Error} -> {error, Error}
end;
goto_definition(_Filename, _) ->
@@ -153,15 +152,19 @@ is_imported_bif(_Uri, F, A) ->
true
end.
+-spec defs_to_res([{uri(), els_poi:poi()}]) -> {ok, [{uri(), els_poi:poi()}]} | {error, not_found}.
+defs_to_res([]) -> {error, not_found};
+defs_to_res(Defs) -> {ok, Defs}.
+
-spec find(uri() | [uri()], els_poi:poi_kind(), any()) ->
- {ok, uri(), els_poi:poi()} | {error, not_found}.
+ [{uri(), els_poi:poi()}].
find(UriOrUris, Kind, Data) ->
find(UriOrUris, Kind, Data, sets:new()).
-spec find(uri() | [uri()], els_poi:poi_kind(), any(), sets:set(binary())) ->
- {ok, uri(), els_poi:poi()} | {error, not_found}.
+ [{uri(), els_poi:poi()}].
find([], _Kind, _Data, _AlreadyVisited) ->
- {error, not_found};
+ [];
find([Uri | Uris0], Kind, Data, AlreadyVisited) ->
case sets:is_element(Uri, AlreadyVisited) of
true ->
@@ -185,24 +188,46 @@ find(Uri, Kind, Data, AlreadyVisited) ->
any(),
sets:set(binary())
) ->
- {ok, uri(), els_poi:poi()} | {error, any()}.
+ [{uri(), els_poi:poi()}].
find_in_document([Uri | Uris0], Document, Kind, Data, AlreadyVisited) ->
POIs = els_dt_document:pois(Document, [Kind]),
- case [POI || #{id := Id} = POI <- POIs, Id =:= Data] of
+ Defs = [POI || #{id := Id} = POI <- POIs, Id =:= Data],
+ {AllDefs, MultipleDefs} =
+ case Data of
+ {_, any_arity} when Kind =:= function ->
+ %% Including defs with any arity
+ AnyArity = [
+ POI
+ || #{id := {F, _}} = POI <- POIs, Kind =:= function, Data =:= {F, any_arity}
+ ],
+ {AnyArity, true};
+ _ ->
+ {Defs, false}
+ end,
+ case AllDefs of
[] ->
case maybe_imported(Document, Kind, Data) of
- {ok, U, P} ->
- {ok, U, P};
- {error, not_found} ->
+ [] ->
find(
lists:usort(include_uris(Document) ++ Uris0),
Kind,
Data,
AlreadyVisited
- )
+ );
+ Else ->
+ Else
end;
Definitions ->
- {ok, Uri, hd(els_poi:sort(Definitions))}
+ SortedDefs = els_poi:sort(Definitions),
+ case MultipleDefs of
+ true ->
+ %% This will be the case only when the user tries to
+ %% navigate to the definition of an atom
+ [{Uri, POI} || POI <- SortedDefs];
+ false ->
+ %% In the general case, we return only one def
+ [{Uri, hd(SortedDefs)}]
+ end
end.
-spec include_uris(els_dt_document:item()) -> [uri()].
@@ -223,20 +248,20 @@ beginning() ->
%% @doc check for a match in any of the module imported functions.
-spec maybe_imported(els_dt_document:item(), els_poi:poi_kind(), any()) ->
- {ok, uri(), els_poi:poi()} | {error, not_found}.
+ [{uri(), els_poi:poi()}].
maybe_imported(Document, function, {F, A}) ->
POIs = els_dt_document:pois(Document, [import_entry]),
case [{M, F, A} || #{id := {M, FP, AP}} <- POIs, FP =:= F, AP =:= A] of
[] ->
- {error, not_found};
+ [];
[{M, F, A} | _] ->
case els_utils:find_module(M) of
{ok, Uri0} -> find(Uri0, function, {F, A});
- {error, not_found} -> {error, not_found}
+ {error, not_found} -> []
end
end;
maybe_imported(_Document, _Kind, _Data) ->
- {error, not_found}.
+ [].
-spec find_in_scope(uri(), els_poi:poi()) -> [els_poi:poi()].
find_in_scope(Uri, #{kind := variable, id := VarId, range := VarRange}) ->
diff --git a/apps/els_lsp/src/els_crossref_diagnostics.erl b/apps/els_lsp/src/els_crossref_diagnostics.erl
index 927121c60..c89abbc79 100644
--- a/apps/els_lsp/src/els_crossref_diagnostics.erl
+++ b/apps/els_lsp/src/els_crossref_diagnostics.erl
@@ -142,7 +142,7 @@ has_definition(
lager_definition(Level, Arity);
has_definition(POI, #{uri := Uri}) ->
case els_code_navigation:goto_definition(Uri, POI) of
- {ok, _Uri, _POI} ->
+ {ok, _Defs} ->
true;
{error, _Error} ->
false
diff --git a/apps/els_lsp/src/els_definition_provider.erl b/apps/els_lsp/src/els_definition_provider.erl
index d9fa53737..28b737bd5 100644
--- a/apps/els_lsp/src/els_definition_provider.erl
+++ b/apps/els_lsp/src/els_definition_provider.erl
@@ -40,13 +40,19 @@ handle_request({definition, Params}) ->
{response, GoTo}
end.
--spec goto_definition(uri(), [els_poi:poi()]) -> map() | null.
+-spec goto_definition(uri(), [els_poi:poi()]) -> [map()] | null.
goto_definition(_Uri, []) ->
null;
goto_definition(Uri, [POI | Rest]) ->
case els_code_navigation:goto_definition(Uri, POI) of
- {ok, DefUri, #{range := Range}} ->
- #{uri => DefUri, range => els_protocol:range(Range)};
+ {ok, Definitions} ->
+ lists:map(
+ fun({DefUri, DefPOI}) ->
+ #{range := Range} = DefPOI,
+ #{uri => DefUri, range => els_protocol:range(Range)}
+ end,
+ Definitions
+ );
_ ->
goto_definition(Uri, Rest)
end.
diff --git a/apps/els_lsp/src/els_docs.erl b/apps/els_lsp/src/els_docs.erl
index 9b475dbbf..3f56bfe18 100644
--- a/apps/els_lsp/src/els_docs.erl
+++ b/apps/els_lsp/src/els_docs.erl
@@ -59,7 +59,7 @@ docs(Uri, #{kind := Kind, id := {F, A}}) when
function_docs('local', M, F, A);
docs(Uri, #{kind := macro, id := Name} = POI) ->
case els_code_navigation:goto_definition(Uri, POI) of
- {ok, DefUri, #{data := #{args := Args, value_range := ValueRange}}} when
+ {ok, [{DefUri, #{data := #{args := Args, value_range := ValueRange}}}]} when
is_list(Args); is_atom(Name)
->
NameStr = macro_signature(Name, Args),
@@ -73,7 +73,7 @@ docs(Uri, #{kind := macro, id := Name} = POI) ->
end;
docs(Uri, #{kind := record_expr} = POI) ->
case els_code_navigation:goto_definition(Uri, POI) of
- {ok, DefUri, #{data := #{value_range := ValueRange}}} ->
+ {ok, [{DefUri, #{data := #{value_range := ValueRange}}}]} ->
ValueText = get_valuetext(DefUri, ValueRange),
[{code_line, ValueText}];
diff --git a/apps/els_lsp/src/els_references_provider.erl b/apps/els_lsp/src/els_references_provider.erl
index aa67b70b1..ffdbced47 100644
--- a/apps/els_lsp/src/els_references_provider.erl
+++ b/apps/els_lsp/src/els_references_provider.erl
@@ -109,9 +109,9 @@ find_references(Uri, Poi = #{kind := Kind}) when
Kind =:= type_application
->
case els_code_navigation:goto_definition(Uri, Poi) of
- {ok, DefUri, DefPoi} ->
+ {ok, [{DefUri, DefPoi}]} ->
find_references(DefUri, DefPoi);
- {error, _} ->
+ _ ->
%% look for references only in the current document
uri_pois_to_locations(
find_scoped_references_for_def(Uri, Poi)
diff --git a/apps/els_lsp/src/els_rename_provider.erl b/apps/els_lsp/src/els_rename_provider.erl
index 6d33e0ffb..1a6fa7a12 100644
--- a/apps/els_lsp/src/els_rename_provider.erl
+++ b/apps/els_lsp/src/els_rename_provider.erl
@@ -114,7 +114,7 @@ workspace_edits(Uri, [#{kind := Kind} = POI | _], NewName) when
Kind =:= type_application
->
case els_code_navigation:goto_definition(Uri, POI) of
- {ok, DefUri, DefPOI} ->
+ {ok, [{DefUri, DefPOI}]} ->
#{changes => changes(DefUri, DefPOI, NewName)};
_ ->
null
diff --git a/apps/els_lsp/src/els_unused_includes_diagnostics.erl b/apps/els_lsp/src/els_unused_includes_diagnostics.erl
index e29c142a0..3aae099d1 100644
--- a/apps/els_lsp/src/els_unused_includes_diagnostics.erl
+++ b/apps/els_lsp/src/els_unused_includes_diagnostics.erl
@@ -98,16 +98,16 @@ update_unused(Acc, _Graph, _Uri, _POIs = []) ->
update_unused(Acc, Graph, Uri, [POI | POIs]) ->
NewAcc =
case els_code_navigation:goto_definition(Uri, POI) of
- {ok, DefinitionUri, _DefinitionPOI} when DefinitionUri =:= Uri ->
+ {ok, [{DefinitionUri, _DefinitionPOI} | _]} when DefinitionUri =:= Uri ->
Acc;
- {ok, DefinitionUri, _DefinitionPOI} ->
+ {ok, [{DefinitionUri, _DefinitionPOI} | _]} ->
case digraph:get_path(Graph, DefinitionUri, Uri) of
false ->
Acc;
Path ->
Acc -- Path
end;
- {error, _Reason} ->
+ _ ->
Acc
end,
update_unused(NewAcc, Graph, Uri, POIs).
diff --git a/apps/els_lsp/test/els_code_lens_SUITE.erl b/apps/els_lsp/test/els_code_lens_SUITE.erl
index 7090dcd33..fcd104947 100644
--- a/apps/els_lsp/test/els_code_lens_SUITE.erl
+++ b/apps/els_lsp/test/els_code_lens_SUITE.erl
@@ -96,7 +96,7 @@ default_lenses(Config) ->
],
lists:usort(Commands)
),
- ?assertEqual(40, length(Commands)),
+ ?assertEqual(50, length(Commands)),
ok.
-spec server_info(config()) -> ok.
diff --git a/apps/els_lsp/test/els_completion_SUITE.erl b/apps/els_lsp/test/els_completion_SUITE.erl
index 1c84091cd..1313ad042 100644
--- a/apps/els_lsp/test/els_completion_SUITE.erl
+++ b/apps/els_lsp/test/els_completion_SUITE.erl
@@ -665,7 +665,12 @@ functions_arity(Config) ->
{<<"function_p">>, 1},
{<<"function_q">>, 0},
{<<"macro_b">>, 2},
- {<<"function_mb">>, 0}
+ {<<"function_mb">>, 0},
+ {<<"code_navigation">>, 0},
+ {<<"code_navigation">>, 1},
+ {<<"multiple_instances_same_file">>, 0},
+ {<<"code_navigation_extra">>, 3},
+ {<<"multiple_instances_diff_file">>, 0}
],
ExpectedCompletion =
[
diff --git a/apps/els_lsp/test/els_definition_SUITE.erl b/apps/els_lsp/test/els_definition_SUITE.erl
index e0d82a53f..dcc14f568 100644
--- a/apps/els_lsp/test/els_definition_SUITE.erl
+++ b/apps/els_lsp/test/els_definition_SUITE.erl
@@ -34,6 +34,8 @@
macro_with_args/1,
macro_with_args_included/1,
macro_with_implicit_args/1,
+ multiple_atom_instances_same_mod/1,
+ multiple_atom_instances_diff_mod/1,
parse_transform/1,
record_access/1,
record_access_included/1,
@@ -100,7 +102,7 @@ suite() ->
application_local(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 22, 5),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(Uri, DefUri),
?assertEqual(
els_protocol:range(#{from => {25, 1}, to => {25, 11}}),
@@ -112,7 +114,7 @@ application_local(Config) ->
application_remote(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 32, 13),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(?config(code_navigation_extra_uri, Config), DefUri),
?assertEqual(
els_protocol:range(#{from => {5, 1}, to => {5, 3}}),
@@ -127,23 +129,35 @@ atom(Config) ->
Def1 = els_client:definition(Uri, 85, 20),
Def2 = els_client:definition(Uri, 86, 20),
Def3 = els_client:definition(Uri, 85, 27),
- #{result := #{range := Range0, uri := DefUri0}} = Def0,
- #{result := #{range := Range1, uri := DefUri1}} = Def1,
- #{result := #{range := Range2, uri := DefUri2}} = Def2,
- #{result := #{range := Range3, uri := DefUri3}} = Def3,
+ #{result := [#{range := Range0, uri := DefUri0}]} = Def0,
+ #{result := [#{range := Range1, uri := DefUri1}, #{range := Range12, uri := DefUri12}]} =
+ Def1,
+ #{result := [#{range := Range2, uri := DefUri2}, #{range := Range22, uri := DefUri22}]} =
+ Def2,
+ #{result := [#{range := Range3, uri := DefUri3}]} = Def3,
?assertEqual(?config(code_navigation_types_uri, Config), DefUri0),
?assertEqual(
els_protocol:range(#{from => {1, 9}, to => {1, 30}}),
Range0
),
- ?assertEqual(?config(code_navigation_extra_uri, Config), DefUri1),
+ ?assertEqual(?config(code_navigation_extra_uri, Config), DefUri12),
?assertEqual(
els_protocol:range(#{from => {1, 9}, to => {1, 30}}),
+ Range12
+ ),
+ ?assertEqual(Uri, DefUri1),
+ ?assertEqual(
+ els_protocol:range(#{from => {132, 1}, to => {132, 22}}),
Range1
),
- ?assertEqual(?config(code_navigation_extra_uri, Config), DefUri2),
+ ?assertEqual(?config(code_navigation_extra_uri, Config), DefUri22),
?assertEqual(
els_protocol:range(#{from => {1, 9}, to => {1, 30}}),
+ Range22
+ ),
+ ?assertEqual(Uri, DefUri2),
+ ?assertEqual(
+ els_protocol:range(#{from => {132, 1}, to => {132, 22}}),
Range2
),
?assertEqual(?config('Code.Navigation.Elixirish_uri', Config), DefUri3),
@@ -157,7 +171,7 @@ atom(Config) ->
behaviour(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 3, 16),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(?config(behaviour_a_uri, Config), DefUri),
?assertEqual(
els_protocol:range(#{from => {1, 9}, to => {1, 20}}),
@@ -169,7 +183,7 @@ behaviour(Config) ->
testcase(Config) ->
Uri = ?config(sample_SUITE_uri, Config),
Def = els_client:definition(Uri, 35, 6),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(Uri, DefUri),
?assertEqual(
els_protocol:range(#{from => {58, 1}, to => {58, 4}}),
@@ -177,13 +191,58 @@ testcase(Config) ->
),
ok.
+-spec multiple_atom_instances_same_mod(config()) -> ok.
+multiple_atom_instances_same_mod(Config) ->
+ Uri = ?config(code_navigation_uri, Config),
+ Defs = els_client:definition(Uri, 130, 36),
+ #{result := Results} = Defs,
+ ?assertEqual(3, length(Results)),
+ ExpectedRanges = [
+ els_protocol:range(#{from => {1, 9}, to => {1, 24}}),
+ els_protocol:range(#{from => {126, 1}, to => {126, 16}}),
+ els_protocol:range(#{from => {128, 1}, to => {128, 16}})
+ ],
+ lists:foreach(
+ fun(Def) ->
+ #{range := Range, uri := DefUri} = Def,
+ ?assertEqual(Uri, DefUri),
+ ?assert(lists:member(Range, ExpectedRanges))
+ end,
+ Results
+ ),
+ ok.
+
+-spec multiple_atom_instances_diff_mod(config()) -> ok.
+multiple_atom_instances_diff_mod(Config) ->
+ Uri = ?config(code_navigation_uri, Config),
+ Defs = els_client:definition(Uri, 134, 35),
+ #{result := Results} = Defs,
+ ?assertEqual(2, length(Results)),
+ RangeDef1 = els_protocol:range(#{from => {132, 1}, to => {132, 22}}),
+ RangeDef2 = els_protocol:range(#{from => {1, 9}, to => {1, 30}}),
+ Uri2 = ?config(code_navigation_extra_uri, Config),
+ ?assertMatch(
+ [
+ #{
+ range := RangeDef1,
+ uri := Uri
+ },
+ #{
+ range := RangeDef2,
+ uri := Uri2
+ }
+ ],
+ Results
+ ),
+ ok.
+
%% Issue #191: Definition not found after document is closed
-spec definition_after_closing(config()) -> ok.
definition_after_closing(Config) ->
Uri = ?config(code_navigation_uri, Config),
ExtraUri = ?config(code_navigation_extra_uri, Config),
Def = els_client:definition(Uri, 32, 13),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(ExtraUri, DefUri),
?assertEqual(
els_protocol:range(#{from => {5, 1}, to => {5, 3}}),
@@ -193,14 +252,14 @@ definition_after_closing(Config) ->
%% Close file, get definition
ok = els_client:did_close(ExtraUri),
Def1 = els_client:definition(Uri, 32, 13),
- #{result := #{range := Range, uri := DefUri}} = Def1,
+ #{result := [#{range := Range, uri := DefUri}]} = Def1,
ok.
-spec duplicate_definition(config()) -> ok.
duplicate_definition(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 57, 5),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(Uri, DefUri),
?assertEqual(
els_protocol:range(#{from => {60, 1}, to => {60, 11}}),
@@ -212,7 +271,7 @@ duplicate_definition(Config) ->
export_entry(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 8, 15),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(Uri, DefUri),
?assertEqual(
els_protocol:range(#{from => {28, 1}, to => {28, 11}}),
@@ -224,7 +283,7 @@ export_entry(Config) ->
fun_local(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 51, 16),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(Uri, DefUri),
?assertEqual(
els_protocol:range(#{from => {25, 1}, to => {25, 11}}),
@@ -236,7 +295,7 @@ fun_local(Config) ->
fun_remote(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 52, 14),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(?config(code_navigation_extra_uri, Config), DefUri),
?assertEqual(
els_protocol:range(#{from => {5, 1}, to => {5, 3}}),
@@ -248,7 +307,7 @@ fun_remote(Config) ->
import_entry(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 10, 34),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(?config(code_navigation_extra_uri, Config), DefUri),
?assertEqual(
els_protocol:range(#{from => {5, 1}, to => {5, 3}}),
@@ -260,7 +319,7 @@ import_entry(Config) ->
module_import_entry(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 90, 3),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(?config(code_navigation_extra_uri, Config), DefUri),
?assertEqual(
els_protocol:range(#{from => {5, 1}, to => {5, 3}}),
@@ -272,7 +331,7 @@ module_import_entry(Config) ->
include(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 12, 20),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(?config(code_navigation_h_uri, Config), DefUri),
?assertEqual(
els_protocol:range(#{from => {1, 1}, to => {1, 1}}),
@@ -284,7 +343,7 @@ include(Config) ->
include_lib(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 13, 22),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(?config(code_navigation_h_uri, Config), DefUri),
?assertEqual(
els_protocol:range(#{from => {1, 1}, to => {1, 1}}),
@@ -296,7 +355,7 @@ include_lib(Config) ->
macro(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 26, 5),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(Uri, DefUri),
?assertEqual(
els_protocol:range(#{from => {18, 9}, to => {18, 16}}),
@@ -308,7 +367,7 @@ macro(Config) ->
macro_lowercase(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 48, 3),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(Uri, DefUri),
?assertEqual(
els_protocol:range(#{from => {45, 9}, to => {45, 16}}),
@@ -320,14 +379,14 @@ macro_lowercase(Config) ->
macro_included(Config) ->
Uri = ?config(code_navigation_uri, Config),
UriHeader = ?config(code_navigation_h_uri, Config),
- #{result := #{range := Range1, uri := DefUri1}} =
+ #{result := [#{range := Range1, uri := DefUri1}]} =
els_client:definition(Uri, 53, 19),
?assertEqual(UriHeader, DefUri1),
?assertEqual(
els_protocol:range(#{from => {3, 9}, to => {3, 25}}),
Range1
),
- #{result := #{range := RangeQuoted, uri := DefUri2}} =
+ #{result := [#{range := RangeQuoted, uri := DefUri2}]} =
els_client:definition(Uri, 52, 75),
?assertEqual(UriHeader, DefUri2),
?assertEqual(
@@ -340,7 +399,7 @@ macro_included(Config) ->
macro_with_args(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 40, 9),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(Uri, DefUri),
?assertEqual(
els_protocol:range(#{from => {19, 9}, to => {19, 16}}),
@@ -352,7 +411,7 @@ macro_with_args(Config) ->
macro_with_args_included(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 43, 9),
- #{result := #{uri := DefUri}} = Def,
+ #{result := [#{uri := DefUri}]} = Def,
?assertEqual(
<<"assert.hrl">>,
filename:basename(els_uri:path(DefUri))
@@ -364,7 +423,7 @@ macro_with_args_included(Config) ->
macro_with_implicit_args(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 124, 5),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(Uri, DefUri),
?assertEqual(
els_protocol:range(#{from => {118, 9}, to => {118, 16}}),
@@ -376,7 +435,7 @@ macro_with_implicit_args(Config) ->
parse_transform(Config) ->
Uri = ?config(diagnostics_parse_transform_usage_uri, Config),
Def = els_client:definition(Uri, 5, 45),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(?config(diagnostics_parse_transform_uri, Config), DefUri),
?assertEqual(
els_protocol:range(#{from => {1, 9}, to => {1, 36}}),
@@ -388,7 +447,7 @@ parse_transform(Config) ->
record_access(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 34, 13),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(Uri, DefUri),
?assertEqual(
els_protocol:range(#{from => {16, 9}, to => {16, 17}}),
@@ -400,7 +459,7 @@ record_access(Config) ->
record_access_included(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 52, 43),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(?config(code_navigation_h_uri, Config), DefUri),
?assertEqual(
els_protocol:range(#{from => {1, 9}, to => {1, 26}}),
@@ -412,7 +471,7 @@ record_access_included(Config) ->
record_access_macro_name(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 116, 33),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(Uri, DefUri),
?assertEqual(
els_protocol:range(#{from => {111, 9}, to => {111, 16}}),
@@ -426,7 +485,7 @@ record_access_macro_name(Config) ->
record_expr(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 33, 11),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(Uri, DefUri),
?assertEqual(
els_protocol:range(#{from => {16, 9}, to => {16, 17}}),
@@ -438,7 +497,7 @@ record_expr(Config) ->
record_expr_included(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 53, 30),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(?config(code_navigation_h_uri, Config), DefUri),
?assertEqual(
els_protocol:range(#{from => {1, 9}, to => {1, 26}}),
@@ -450,7 +509,7 @@ record_expr_included(Config) ->
record_expr_macro_name(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 115, 11),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(Uri, DefUri),
?assertEqual(
els_protocol:range(#{from => {111, 9}, to => {111, 16}}),
@@ -462,7 +521,7 @@ record_expr_macro_name(Config) ->
record_field(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 33, 20),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(Uri, DefUri),
?assertEqual(
els_protocol:range(#{from => {16, 20}, to => {16, 27}}),
@@ -474,7 +533,7 @@ record_field(Config) ->
record_field_included(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 53, 45),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(?config(code_navigation_h_uri, Config), DefUri),
?assertEqual(
els_protocol:range(#{from => {1, 29}, to => {1, 45}}),
@@ -486,7 +545,7 @@ record_field_included(Config) ->
record_type_macro_name(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 113, 28),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(Uri, DefUri),
?assertEqual(
els_protocol:range(#{from => {111, 9}, to => {111, 16}}),
@@ -499,7 +558,7 @@ type_application_remote(Config) ->
ExtraUri = ?config(code_navigation_extra_uri, Config),
TypesUri = ?config(code_navigation_types_uri, Config),
Def = els_client:definition(ExtraUri, 11, 38),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(TypesUri, DefUri),
?assertEqual(
els_protocol:range(#{from => {3, 1}, to => {3, 26}}),
@@ -528,7 +587,7 @@ type_application_undefined(Config) ->
type_application_user(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 55, 25),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(Uri, DefUri),
?assertEqual(
els_protocol:range(#{from => {37, 1}, to => {37, 25}}),
@@ -540,7 +599,7 @@ type_application_user(Config) ->
type_export_entry(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 9, 17),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(Uri, DefUri),
?assertEqual(
els_protocol:range(#{from => {37, 1}, to => {37, 25}}),
@@ -556,11 +615,11 @@ variable(Config) ->
Def2 = els_client:definition(Uri, 107, 10),
Def3 = els_client:definition(Uri, 108, 10),
Def4 = els_client:definition(Uri, 19, 36),
- #{result := #{range := Range0, uri := DefUri0}} = Def0,
- #{result := #{range := Range1, uri := DefUri0}} = Def1,
- #{result := #{range := Range2, uri := DefUri0}} = Def2,
- #{result := #{range := Range3, uri := DefUri0}} = Def3,
- #{result := #{range := Range4, uri := DefUri0}} = Def4,
+ #{result := [#{range := Range0, uri := DefUri0}]} = Def0,
+ #{result := [#{range := Range1, uri := DefUri0}]} = Def1,
+ #{result := [#{range := Range2, uri := DefUri0}]} = Def2,
+ #{result := [#{range := Range3, uri := DefUri0}]} = Def3,
+ #{result := [#{range := Range4, uri := DefUri0}]} = Def4,
?assertEqual(?config(code_navigation_uri, Config), DefUri0),
?assertEqual(
@@ -591,7 +650,7 @@ opaque_application_remote(Config) ->
ExtraUri = ?config(code_navigation_extra_uri, Config),
TypesUri = ?config(code_navigation_types_uri, Config),
Def = els_client:definition(ExtraUri, 16, 61),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(TypesUri, DefUri),
?assertEqual(
els_protocol:range(#{from => {7, 1}, to => {7, 35}}),
@@ -603,7 +662,7 @@ opaque_application_remote(Config) ->
opaque_application_user(Config) ->
ExtraUri = ?config(code_navigation_extra_uri, Config),
Def = els_client:definition(ExtraUri, 16, 24),
- #{result := #{range := Range, uri := DefUri}} = Def,
+ #{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(ExtraUri, DefUri),
?assertEqual(
els_protocol:range(#{from => {20, 1}, to => {20, 34}}),
@@ -616,31 +675,31 @@ parse_incomplete(Config) ->
Uri = ?config(code_navigation_broken_uri, Config),
Range = els_protocol:range(#{from => {3, 1}, to => {3, 11}}),
?assertMatch(
- #{result := #{range := Range, uri := Uri}},
+ #{result := [#{range := Range, uri := Uri}]},
els_client:definition(Uri, 7, 3)
),
?assertMatch(
- #{result := #{range := Range, uri := Uri}},
+ #{result := [#{range := Range, uri := Uri}]},
els_client:definition(Uri, 8, 3)
),
?assertMatch(
- #{result := #{range := Range, uri := Uri}},
+ #{result := [#{range := Range, uri := Uri}]},
els_client:definition(Uri, 9, 8)
),
?assertMatch(
- #{result := #{range := Range, uri := Uri}},
+ #{result := [#{range := Range, uri := Uri}]},
els_client:definition(Uri, 11, 7)
),
?assertMatch(
- #{result := #{range := Range, uri := Uri}},
+ #{result := [#{range := Range, uri := Uri}]},
els_client:definition(Uri, 12, 12)
),
?assertMatch(
- #{result := #{range := Range, uri := Uri}},
+ #{result := [#{range := Range, uri := Uri}]},
els_client:definition(Uri, 17, 3)
),
?assertMatch(
- #{result := #{range := Range, uri := Uri}},
+ #{result := [#{range := Range, uri := Uri}]},
els_client:definition(Uri, 19, 3)
),
ok.
diff --git a/apps/els_lsp/test/els_document_symbol_SUITE.erl b/apps/els_lsp/test/els_document_symbol_SUITE.erl
index 1f4919a1d..2c53ee087 100644
--- a/apps/els_lsp/test/els_document_symbol_SUITE.erl
+++ b/apps/els_lsp/test/els_document_symbol_SUITE.erl
@@ -169,7 +169,12 @@ functions() ->
{<<"function_p/1">>, {102, 0}, {102, 10}},
{<<"function_q/0">>, {113, 0}, {113, 10}},
{<<"macro_b/2">>, {119, 0}, {119, 7}},
- {<<"function_mb/0">>, {122, 0}, {122, 11}}
+ {<<"function_mb/0">>, {122, 0}, {122, 11}},
+ {<<"code_navigation/0">>, {125, 0}, {125, 15}},
+ {<<"code_navigation/1">>, {127, 0}, {127, 15}},
+ {<<"multiple_instances_same_file/0">>, {129, 0}, {129, 28}},
+ {<<"code_navigation_extra/3">>, {131, 0}, {131, 21}},
+ {<<"multiple_instances_diff_file/0">>, {133, 0}, {133, 28}}
].
macros() ->
diff --git a/apps/els_lsp/test/els_rebar3_release_SUITE.erl b/apps/els_lsp/test/els_rebar3_release_SUITE.erl
index d5cbb9bce..01ae7a399 100644
--- a/apps/els_lsp/test/els_rebar3_release_SUITE.erl
+++ b/apps/els_lsp/test/els_rebar3_release_SUITE.erl
@@ -87,7 +87,7 @@ code_navigation(Config) ->
AppUri = ?config(app_uri, Config),
SupUri = ?config(sup_uri, Config),
#{result := Result} = els_client:definition(AppUri, 13, 12),
- #{range := DefRange, uri := SupUri} = Result,
+ [#{range := DefRange, uri := SupUri}] = Result,
?assertEqual(
els_protocol:range(#{from => {16, 1}, to => {16, 11}}),
DefRange
From 274beb7ff8589622ede3669af5850f7958dcd936 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Katk=C3=B3=20Dominik?=
<56202545+kdmnk@users.noreply.github.com>
Date: Tue, 21 Jun 2022 17:15:10 +0200
Subject: [PATCH 097/239] Integrate Wrangler (#1228)
---
apps/els_core/src/els_client.erl | 1 +
apps/els_core/src/els_config.erl | 41 +++++
apps/els_lsp/src/els_code_action_provider.erl | 5 +-
apps/els_lsp/src/els_code_lens_provider.erl | 3 +-
.../src/els_document_highlight_provider.erl | 12 +-
.../src/els_execute_command_provider.erl | 27 ++-
apps/els_lsp/src/els_general_provider.erl | 12 +-
apps/els_lsp/src/els_methods.erl | 14 +-
.../src/els_semantic_token_provider.erl | 25 +++
apps/els_lsp/src/wrangler_handler.erl | 168 ++++++++++++++++++
rebar.config | 14 +-
11 files changed, 303 insertions(+), 19 deletions(-)
create mode 100644 apps/els_lsp/src/els_semantic_token_provider.erl
create mode 100644 apps/els_lsp/src/wrangler_handler.erl
diff --git a/apps/els_core/src/els_client.erl b/apps/els_core/src/els_client.erl
index 6d3a9b7db..542684959 100644
--- a/apps/els_core/src/els_client.erl
+++ b/apps/els_core/src/els_client.erl
@@ -453,6 +453,7 @@ method_lookup(did_close) -> <<"textDocument/didClose">>;
method_lookup(hover) -> <<"textDocument/hover">>;
method_lookup(implementation) -> <<"textDocument/implementation">>;
method_lookup(folding_range) -> <<"textDocument/foldingRange">>;
+method_lookup(semantic_tokens) -> <<"textDocument/semanticTokens/full">>;
method_lookup(preparecallhierarchy) -> <<"textDocument/prepareCallHierarchy">>;
method_lookup(callhierarchy_incomingcalls) -> <<"callHierarchy/incomingCalls">>;
method_lookup(callhierarchy_outgoingcalls) -> <<"callHierarchy/outgoingCalls">>;
diff --git a/apps/els_core/src/els_config.erl b/apps/els_core/src/els_config.erl
index 741207016..c3e25bea9 100644
--- a/apps/els_core/src/els_config.erl
+++ b/apps/els_core/src/els_config.erl
@@ -58,6 +58,7 @@
| indexing_enabled
| compiler_telemetry_enabled
| refactorerl
+ | wrangler
| edoc_custom_tags.
-type path() :: file:filename().
@@ -79,6 +80,7 @@
code_reload => map() | 'disabled',
indexing_enabled => boolean(),
compiler_telemetry_enabled => boolean(),
+ wrangler => map() | 'notconfigured',
refactorerl => map() | 'notconfigured'
}.
@@ -148,6 +150,45 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
RefactorErl = maps:get("refactorerl", Config, notconfigured),
+ %% Initialize and start Wrangler
+ case maps:get("wrangler", Config, notconfigured) of
+ notconfigured ->
+ ok = set(wrangler, notconfigured);
+ Wrangler ->
+ ok = set(wrangler, Wrangler),
+ case maps:get("path", Wrangler, notconfigured) of
+ notconfigured ->
+ ?LOG_INFO(
+ "Wrangler path is not configured,\n"
+ " assuming it is installed system-wide."
+ );
+ Path ->
+ case code:add_path(Path) of
+ true ->
+ ok;
+ {error, bad_directory} ->
+ ?LOG_INFO(
+ "Wrangler path is configured but\n"
+ " not a valid ebin directory: ~p",
+ [Path]
+ )
+ end
+ end,
+ case application:load(wrangler) of
+ ok ->
+ case apply(api_wrangler, start, []) of
+ % Function defined in Wrangler.
+ % Using apply to circumvent tests resulting in 'unknown function'.
+ ok ->
+ ?LOG_INFO("Wrangler started successfully");
+ {error, Reason} ->
+ ?LOG_INFO("Wrangler could not be started: ~p", [Reason])
+ end;
+ {error, Reason} ->
+ ?LOG_INFO("Wrangler could not be loaded: ~p", [Reason])
+ end
+ end,
+
%% Passed by the LSP client
ok = set(root_uri, RootUri),
%% Read from the configuration file
diff --git a/apps/els_lsp/src/els_code_action_provider.erl b/apps/els_lsp/src/els_code_action_provider.erl
index f42534c86..a40daa761 100644
--- a/apps/els_lsp/src/els_code_action_provider.erl
+++ b/apps/els_lsp/src/els_code_action_provider.erl
@@ -31,8 +31,9 @@ handle_request({document_codeaction, Params}) ->
%% @doc Result: `(Command | CodeAction)[] | null'
-spec code_actions(uri(), range(), code_action_context()) -> [map()].
-code_actions(Uri, _Range, #{<<"diagnostics">> := Diagnostics}) ->
- lists:flatten([make_code_actions(Uri, D) || D <- Diagnostics]).
+code_actions(Uri, Range, #{<<"diagnostics">> := Diagnostics}) ->
+ lists:flatten([make_code_actions(Uri, D) || D <- Diagnostics]) ++
+ wrangler_handler:get_code_actions(Uri, Range).
-spec make_code_actions(uri(), map()) -> [map()].
make_code_actions(
diff --git a/apps/els_lsp/src/els_code_lens_provider.erl b/apps/els_lsp/src/els_code_lens_provider.erl
index 9cb2b468f..cc6301b8b 100644
--- a/apps/els_lsp/src/els_code_lens_provider.erl
+++ b/apps/els_lsp/src/els_code_lens_provider.erl
@@ -40,7 +40,8 @@ run_lenses_job(Uri) ->
[
els_code_lens:lenses(Id, Doc)
|| Id <- els_code_lens:enabled_lenses()
- ]
+ ] ++
+ wrangler_handler:get_code_lenses(Doc)
)
end,
entries => [Document],
diff --git a/apps/els_lsp/src/els_document_highlight_provider.erl b/apps/els_lsp/src/els_document_highlight_provider.erl
index 51f2477f2..86b5014e6 100644
--- a/apps/els_lsp/src/els_document_highlight_provider.erl
+++ b/apps/els_lsp/src/els_document_highlight_provider.erl
@@ -28,9 +28,15 @@ handle_request({document_highlight, Params}) ->
<<"textDocument">> := #{<<"uri">> := Uri}
} = Params,
{ok, Document} = els_utils:lookup_document(Uri),
- case els_dt_document:get_element_at_pos(Document, Line + 1, Character + 1) of
- [POI | _] -> {response, find_highlights(Document, POI)};
- [] -> {response, null}
+ Highlights =
+ case els_dt_document:get_element_at_pos(Document, Line + 1, Character + 1) of
+ [POI | _] -> find_highlights(Document, POI);
+ [] -> null
+ end,
+ case {Highlights, wrangler_handler:get_highlights(Uri, Line, Character)} of
+ {H, null} -> {response, H};
+ {_, H} -> {response, H}
+ %% overwrites them for more transparent Wrangler forms.
end.
%%==============================================================================
diff --git a/apps/els_lsp/src/els_execute_command_provider.erl b/apps/els_lsp/src/els_execute_command_provider.erl
index 16857e5b2..9cc587242 100644
--- a/apps/els_lsp/src/els_execute_command_provider.erl
+++ b/apps/els_lsp/src/els_execute_command_provider.erl
@@ -22,13 +22,17 @@ is_enabled() -> true.
-spec options() -> map().
options() ->
+ Commands = [
+ <<"server-info">>,
+ <<"ct-run-test">>,
+ <<"show-behaviour-usages">>,
+ <<"suggest-spec">>,
+ <<"function-references">>
+ ],
#{
commands => [
- els_command:with_prefix(<<"server-info">>),
- els_command:with_prefix(<<"ct-run-test">>),
- els_command:with_prefix(<<"show-behaviour-usages">>),
- els_command:with_prefix(<<"suggest-spec">>),
- els_command:with_prefix(<<"function-references">>)
+ els_command:with_prefix(Cmd)
+ || Cmd <- Commands ++ wrangler_handler:enabled_commands()
]
}.
@@ -106,8 +110,13 @@ execute_command(<<"suggest-spec">>, [
els_server:send_request(Method, Params),
[];
execute_command(Command, Arguments) ->
- ?LOG_INFO(
- "Unsupported command: [Command=~p] [Arguments=~p]",
- [Command, Arguments]
- ),
+ case wrangler_handler:execute_command(Command, Arguments) of
+ true ->
+ ok;
+ _ ->
+ ?LOG_INFO(
+ "Unsupported command: [Command=~p] [Arguments=~p]",
+ [Command, Arguments]
+ )
+ end,
[].
diff --git a/apps/els_lsp/src/els_general_provider.erl b/apps/els_lsp/src/els_general_provider.erl
index d50f54ad8..eda88b11b 100644
--- a/apps/els_lsp/src/els_general_provider.erl
+++ b/apps/els_lsp/src/els_general_provider.erl
@@ -157,7 +157,17 @@ server_capabilities() ->
renameProvider =>
els_rename_provider:is_enabled(),
callHierarchyProvider =>
- els_call_hierarchy_provider:is_enabled()
+ els_call_hierarchy_provider:is_enabled(),
+ semanticTokensProvider =>
+ #{
+ legend =>
+ #{
+ tokenTypes => wrangler_handler:semantic_token_types(),
+ tokenModifiers => wrangler_handler:semantic_token_modifiers()
+ },
+ range => false,
+ full => els_semantic_token_provider:is_enabled()
+ }
},
ActiveCapabilities =
case els_signature_help_provider:is_enabled() of
diff --git a/apps/els_lsp/src/els_methods.erl b/apps/els_lsp/src/els_methods.erl
index ba88f90b0..8a0cb4289 100644
--- a/apps/els_lsp/src/els_methods.erl
+++ b/apps/els_lsp/src/els_methods.erl
@@ -28,6 +28,7 @@
textdocument_codelens/2,
textdocument_rename/2,
textdocument_preparecallhierarchy/2,
+ textdocument_semantictokens_full/2,
textdocument_signaturehelp/2,
callhierarchy_incomingcalls/2,
callhierarchy_outgoingcalls/2,
@@ -137,7 +138,7 @@ not_implemented_method(Method, State) ->
method_to_function_name(<<"$/", Method/binary>>) ->
method_to_function_name(<<"$_", Method/binary>>);
method_to_function_name(Method) ->
- Replaced = string:replace(Method, <<"/">>, <<"_">>),
+ Replaced = string:replace(Method, <<"/">>, <<"_">>, all),
Lower = string:lowercase(Replaced),
Binary = els_utils:to_binary(Lower),
binary_to_atom(Binary, utf8).
@@ -424,6 +425,17 @@ textdocument_rename(Params, State) ->
els_provider:handle_request(Provider, {rename, Params}),
{response, Response, State}.
+%%==============================================================================
+%% textDocument/semanticTokens/full
+%%==============================================================================
+
+-spec textdocument_semantictokens_full(params(), els_server:state()) -> result().
+textdocument_semantictokens_full(Params, State) ->
+ Provider = els_semantic_token_provider,
+ {response, Response} =
+ els_provider:handle_request(Provider, {semantic_tokens, Params}),
+ {response, Response, State}.
+
%%==============================================================================
%% textDocument/preparePreparecallhierarchy
%%==============================================================================
diff --git a/apps/els_lsp/src/els_semantic_token_provider.erl b/apps/els_lsp/src/els_semantic_token_provider.erl
new file mode 100644
index 000000000..398b20f08
--- /dev/null
+++ b/apps/els_lsp/src/els_semantic_token_provider.erl
@@ -0,0 +1,25 @@
+-module(els_semantic_token_provider).
+
+-behaviour(els_provider).
+
+-include("els_lsp.hrl").
+-export([handle_request/1, is_enabled/0]).
+
+%%==============================================================================
+%% els_provider functions
+%%==============================================================================
+
+-spec is_enabled() -> boolean().
+is_enabled() ->
+ %% Currently this is used by Wrangler only.
+ wrangler_handler:is_enabled().
+
+-spec handle_request(any()) -> {response, any()}.
+handle_request({semantic_tokens, Params}) ->
+ #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
+ Result = #{<<"data">> => semantic_tokens(Uri)},
+ {response, Result}.
+
+-spec semantic_tokens(uri()) -> [integer()].
+semantic_tokens(Uri) ->
+ wrangler_handler:get_semantic_tokens(Uri).
diff --git a/apps/els_lsp/src/wrangler_handler.erl b/apps/els_lsp/src/wrangler_handler.erl
new file mode 100644
index 000000000..44cfc13d8
--- /dev/null
+++ b/apps/els_lsp/src/wrangler_handler.erl
@@ -0,0 +1,168 @@
+%! Wrangler refactor form.
+%! Choose some of the highlighted refactoring candidates then exit the form.
+%! Before exiting, do not edit the file manually.
+%%==============================================================================
+%% A module to call Wrangler functions.
+%% If wrangler is not configured, neutral elements will be returned.
+%%
+%% Using apply to circumvent tests resulting in 'unknown function' errors.
+%%==============================================================================
+
+-module(wrangler_handler).
+-export([
+ is_enabled/0,
+ wrangler_config/0,
+ get_code_actions/2,
+ get_code_lenses/1,
+ enabled_commands/0,
+ execute_command/2,
+ get_highlights/3,
+ semantic_token_types/0,
+ semantic_token_modifiers/0,
+ get_semantic_tokens/1
+]).
+
+-include("els_lsp.hrl").
+-include_lib("kernel/include/logger.hrl").
+
+%%==============================================================================
+%% Configuration related functions.
+%%==============================================================================
+
+%% Check if Wrangler is enabled in the config file.
+-spec is_enabled() -> boolean().
+is_enabled() ->
+ case els_config:get(wrangler) of
+ notconfigured -> false;
+ Config -> maps:get("enabled", Config, false)
+ end.
+
+%% Returns Wrangler`s config from the config file.
+%% Used by Wrangler.
+-spec wrangler_config() -> map().
+wrangler_config() ->
+ els_config:get(wrangler).
+
+%% Returns the semantic token types defined by Wrangler.
+%% Used to register server capabilities.
+-spec semantic_token_types() -> any().
+semantic_token_types() ->
+ case is_enabled() of
+ true -> apply(wls_semantic_tokens, token_types, []);
+ false -> []
+ end.
+
+%% Returns the semantic token modifiers defined by Wrangler.
+%% Used to register server capabilities.
+-spec semantic_token_modifiers() -> any().
+semantic_token_modifiers() ->
+ case is_enabled() of
+ true -> apply(wls_semantic_tokens, token_modifiers, []);
+ false -> []
+ end.
+
+%% Returns the enabled Wrangler commands.
+%% Used to register server capabilities.
+-spec enabled_commands() -> [els_command:command_id()].
+enabled_commands() ->
+ case is_enabled() of
+ true ->
+ Commands = apply(wls_execute_command_provider, enabled_commands, []),
+ ?LOG_INFO("Wrangler Enabled Commands: ~p", [Commands]),
+ Commands;
+ false ->
+ []
+ end.
+
+%%==============================================================================
+%% Getters for the language features provided by Wrangler.
+%%==============================================================================
+
+-spec get_code_actions(uri(), range()) -> [map()].
+get_code_actions(Uri, Range) ->
+ case is_enabled() of
+ true ->
+ case apply(wls_code_actions, get_actions, [Uri, Range]) of
+ [] ->
+ [];
+ Actions ->
+ ?LOG_INFO("Wrangler Code Actions: ~p", [Actions]),
+ Actions
+ end;
+ false ->
+ []
+ end.
+
+-spec get_code_lenses(els_dt_document:item()) -> [els_code_lens:lens()].
+get_code_lenses(Document) ->
+ case is_enabled() of
+ true ->
+ case
+ lists:flatten([
+ apply(wls_code_lens, lenses, [Id, Document])
+ || Id <- apply(wls_code_lens, enabled_lenses, [])
+ ])
+ of
+ [] ->
+ [];
+ Lenses ->
+ ?LOG_INFO("Wrangler Code Lenses: ~p", [Lenses]),
+ Lenses
+ end;
+ false ->
+ []
+ end.
+
+-spec get_highlights(uri(), integer(), integer()) -> 'null' | [map()].
+get_highlights(Uri, Line, Character) ->
+ case is_enabled() of
+ true ->
+ case apply(wls_highlight, get_highlights, [Uri, {Line, Character}]) of
+ null ->
+ null;
+ Highlights ->
+ ?LOG_INFO("Wrangler Highlights: ~p", [Highlights]),
+ Highlights
+ end;
+ false ->
+ null
+ end.
+
+-spec get_semantic_tokens(uri()) -> [integer()].
+get_semantic_tokens(Uri) ->
+ case is_enabled() of
+ true ->
+ case apply(wls_semantic_tokens, semantic_tokens, [Uri]) of
+ [] ->
+ [];
+ SemanticTokens ->
+ ?LOG_INFO("Wrangler Semantic Tokens: ~p", [SemanticTokens]),
+ SemanticTokens
+ end;
+ false ->
+ []
+ end.
+
+%%==============================================================================
+%% Passing commands to Wrangler.
+%%==============================================================================
+
+-spec execute_command(els_command:command_id(), [any()]) -> boolean().
+execute_command(Command, Arguments) ->
+ case is_enabled() of
+ true ->
+ case
+ lists:member(
+ Command,
+ apply(wls_execute_command_provider, enabled_commands, [])
+ )
+ of
+ true ->
+ apply(wls_execute_command_provider, execute_command, [Command, Arguments]),
+ true;
+ false ->
+ false
+ end;
+ false ->
+ false
+ end.
diff --git a/rebar.config b/rebar.config
index dd9f25c9f..a2a9dbf88 100644
--- a/rebar.config
+++ b/rebar.config
@@ -88,8 +88,18 @@
deprecated_function_calls,
deprecated_functions
]}.
-%% Set xref ignores for functions introduced in OTP 23
-{xref_ignores, [{code, get_doc, 1}, {shell_docs, render, 4}]}.
+%% Set xref ignores for functions introduced in OTP 23 & function of wrangler
+{xref_ignores, [
+ {code, get_doc, 1},
+ {shell_docs, render, 4},
+ wrangler_handler,
+ api_wrangler,
+ wls_code_lens,
+ wls_highlight,
+ wls_semantic_tokens,
+ wls_code_actions,
+ wls_execute_command_provider
+]}.
%% Disable warning_as_errors for redbug to avoid deprecation warnings.
{overrides, [{del, redbug, [{erl_opts, [warnings_as_errors]}]}]}.
From 9f70a427d8f18085918f9a3775eca671d11fbadc Mon Sep 17 00:00:00 2001
From: Michael Davis
Date: Mon, 4 Jul 2022 03:47:05 -0500
Subject: [PATCH 098/239] [#1310] Add a configuration option for advertised LSP
providers. (#1341)
* add a configuration option for advertised LSP providers
* clean up `is_enabled/0` callbacks
---
apps/els_core/src/els_config.erl | 8 +-
.../src/els_call_hierarchy_provider.erl | 4 -
apps/els_lsp/src/els_code_action_provider.erl | 4 -
apps/els_lsp/src/els_code_lens_provider.erl | 4 -
apps/els_lsp/src/els_definition_provider.erl | 4 -
apps/els_lsp/src/els_diagnostics_provider.erl | 4 -
.../src/els_document_highlight_provider.erl | 4 -
.../src/els_document_symbol_provider.erl | 4 -
.../src/els_execute_command_provider.erl | 4 -
.../src/els_folding_range_provider.erl | 4 -
apps/els_lsp/src/els_formatting_provider.erl | 26 +--
apps/els_lsp/src/els_general_provider.erl | 165 +++++++++++++-----
apps/els_lsp/src/els_hover_provider.erl | 5 -
.../src/els_implementation_provider.erl | 4 -
apps/els_lsp/src/els_references_provider.erl | 4 -
apps/els_lsp/src/els_rename_provider.erl | 6 +-
.../src/els_semantic_token_provider.erl | 7 +-
.../src/els_signature_help_provider.erl | 5 -
.../src/els_workspace_symbol_provider.erl | 4 -
.../els_lsp/test/els_initialization_SUITE.erl | 47 ++++-
.../providers_custom.config | 5 +
.../providers_default.config | 0
.../providers_invalid.config | 5 +
23 files changed, 192 insertions(+), 135 deletions(-)
create mode 100644 apps/els_lsp/test/els_initialization_SUITE_data/providers_custom.config
create mode 100644 apps/els_lsp/test/els_initialization_SUITE_data/providers_default.config
create mode 100644 apps/els_lsp/test/els_initialization_SUITE_data/providers_invalid.config
diff --git a/apps/els_core/src/els_config.erl b/apps/els_core/src/els_config.erl
index c3e25bea9..3f16f4b92 100644
--- a/apps/els_core/src/els_config.erl
+++ b/apps/els_core/src/els_config.erl
@@ -59,7 +59,8 @@
| compiler_telemetry_enabled
| refactorerl
| wrangler
- | edoc_custom_tags.
+ | edoc_custom_tags
+ | providers.
-type path() :: file:filename().
-type state() :: #{
@@ -81,7 +82,8 @@
indexing_enabled => boolean(),
compiler_telemetry_enabled => boolean(),
wrangler => map() | 'notconfigured',
- refactorerl => map() | 'notconfigured'
+ refactorerl => map() | 'notconfigured',
+ providers => map()
}.
%%==============================================================================
@@ -149,6 +151,7 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
IndexingEnabled = maps:get(<<"indexingEnabled">>, InitOptions, true),
RefactorErl = maps:get("refactorerl", Config, notconfigured),
+ Providers = maps:get("providers", Config, #{}),
%% Initialize and start Wrangler
case maps:get("wrangler", Config, notconfigured) of
@@ -201,6 +204,7 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
ok = set(macros, Macros),
ok = set(plt_path, DialyzerPltPath),
ok = set(code_reload, CodeReload),
+ ok = set(providers, Providers),
?LOG_INFO("Config=~p", [Config]),
ok = set(
runtime,
diff --git a/apps/els_lsp/src/els_call_hierarchy_provider.erl b/apps/els_lsp/src/els_call_hierarchy_provider.erl
index d7171994a..a48cdcedd 100644
--- a/apps/els_lsp/src/els_call_hierarchy_provider.erl
+++ b/apps/els_lsp/src/els_call_hierarchy_provider.erl
@@ -3,7 +3,6 @@
-behaviour(els_provider).
-export([
- is_enabled/0,
handle_request/1
]).
@@ -16,9 +15,6 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec is_enabled() -> boolean().
-is_enabled() -> true.
-
-spec handle_request(any()) -> {response, any()}.
handle_request({prepare, Params}) ->
{Uri, Line, Char} =
diff --git a/apps/els_lsp/src/els_code_action_provider.erl b/apps/els_lsp/src/els_code_action_provider.erl
index a40daa761..5830466a6 100644
--- a/apps/els_lsp/src/els_code_action_provider.erl
+++ b/apps/els_lsp/src/els_code_action_provider.erl
@@ -3,7 +3,6 @@
-behaviour(els_provider).
-export([
- is_enabled/0,
handle_request/1
]).
@@ -12,9 +11,6 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec is_enabled() -> boolean().
-is_enabled() -> true.
-
-spec handle_request(any()) -> {response, any()}.
handle_request({document_codeaction, Params}) ->
#{
diff --git a/apps/els_lsp/src/els_code_lens_provider.erl b/apps/els_lsp/src/els_code_lens_provider.erl
index cc6301b8b..c12d06ab6 100644
--- a/apps/els_lsp/src/els_code_lens_provider.erl
+++ b/apps/els_lsp/src/els_code_lens_provider.erl
@@ -2,7 +2,6 @@
-behaviour(els_provider).
-export([
- is_enabled/0,
options/0,
handle_request/1
]).
@@ -13,9 +12,6 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec is_enabled() -> boolean().
-is_enabled() -> true.
-
-spec options() -> map().
options() ->
#{resolveProvider => false}.
diff --git a/apps/els_lsp/src/els_definition_provider.erl b/apps/els_lsp/src/els_definition_provider.erl
index 28b737bd5..f28dba876 100644
--- a/apps/els_lsp/src/els_definition_provider.erl
+++ b/apps/els_lsp/src/els_definition_provider.erl
@@ -3,7 +3,6 @@
-behaviour(els_provider).
-export([
- is_enabled/0,
handle_request/1
]).
@@ -12,9 +11,6 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec is_enabled() -> boolean().
-is_enabled() -> true.
-
-spec handle_request(any()) -> {response, any()}.
handle_request({definition, Params}) ->
#{
diff --git a/apps/els_lsp/src/els_diagnostics_provider.erl b/apps/els_lsp/src/els_diagnostics_provider.erl
index 69cf75884..e6db8a635 100644
--- a/apps/els_lsp/src/els_diagnostics_provider.erl
+++ b/apps/els_lsp/src/els_diagnostics_provider.erl
@@ -3,7 +3,6 @@
-behaviour(els_provider).
-export([
- is_enabled/0,
options/0,
handle_request/1
]).
@@ -22,9 +21,6 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec is_enabled() -> boolean().
-is_enabled() -> true.
-
-spec options() -> map().
options() ->
#{}.
diff --git a/apps/els_lsp/src/els_document_highlight_provider.erl b/apps/els_lsp/src/els_document_highlight_provider.erl
index 86b5014e6..81b49c84e 100644
--- a/apps/els_lsp/src/els_document_highlight_provider.erl
+++ b/apps/els_lsp/src/els_document_highlight_provider.erl
@@ -3,7 +3,6 @@
-behaviour(els_provider).
-export([
- is_enabled/0,
handle_request/1
]).
@@ -15,9 +14,6 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec is_enabled() -> boolean().
-is_enabled() -> true.
-
-spec handle_request(any()) -> {response, any()}.
handle_request({document_highlight, Params}) ->
#{
diff --git a/apps/els_lsp/src/els_document_symbol_provider.erl b/apps/els_lsp/src/els_document_symbol_provider.erl
index 7a6e2db21..db4160834 100644
--- a/apps/els_lsp/src/els_document_symbol_provider.erl
+++ b/apps/els_lsp/src/els_document_symbol_provider.erl
@@ -3,7 +3,6 @@
-behaviour(els_provider).
-export([
- is_enabled/0,
handle_request/1
]).
@@ -12,9 +11,6 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec is_enabled() -> boolean().
-is_enabled() -> true.
-
-spec handle_request(any()) -> {response, any()}.
handle_request({document_symbol, Params}) ->
#{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
diff --git a/apps/els_lsp/src/els_execute_command_provider.erl b/apps/els_lsp/src/els_execute_command_provider.erl
index 9cc587242..31f651c2e 100644
--- a/apps/els_lsp/src/els_execute_command_provider.erl
+++ b/apps/els_lsp/src/els_execute_command_provider.erl
@@ -3,7 +3,6 @@
-behaviour(els_provider).
-export([
- is_enabled/0,
options/0,
handle_request/1
]).
@@ -17,9 +16,6 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec is_enabled() -> boolean().
-is_enabled() -> true.
-
-spec options() -> map().
options() ->
Commands = [
diff --git a/apps/els_lsp/src/els_folding_range_provider.erl b/apps/els_lsp/src/els_folding_range_provider.erl
index c441bebab..0507a830c 100644
--- a/apps/els_lsp/src/els_folding_range_provider.erl
+++ b/apps/els_lsp/src/els_folding_range_provider.erl
@@ -5,7 +5,6 @@
-include("els_lsp.hrl").
-export([
- is_enabled/0,
handle_request/1
]).
@@ -17,9 +16,6 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec is_enabled() -> boolean().
-is_enabled() -> true.
-
-spec handle_request(tuple()) -> {response, folding_range_result()}.
handle_request({document_foldingrange, Params}) ->
#{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
diff --git a/apps/els_lsp/src/els_formatting_provider.erl b/apps/els_lsp/src/els_formatting_provider.erl
index a52e4958d..2fa5bd105 100644
--- a/apps/els_lsp/src/els_formatting_provider.erl
+++ b/apps/els_lsp/src/els_formatting_provider.erl
@@ -3,11 +3,7 @@
-behaviour(els_provider).
-export([
- handle_request/1,
- is_enabled/0,
- is_enabled_document/0,
- is_enabled_range/0,
- is_enabled_on_type/0
+ handle_request/1
]).
%%==============================================================================
@@ -23,23 +19,6 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
-%% Keep the behaviour happy
--spec is_enabled() -> boolean().
-is_enabled() -> is_enabled_document().
-
--spec is_enabled_document() -> boolean().
-is_enabled_document() -> true.
-
--spec is_enabled_range() -> boolean().
-is_enabled_range() ->
- false.
-
-%% NOTE: because erlang_ls does not send incremental document changes
-%% via `textDocument/didChange`, this kind of formatting does not
-%% make sense.
--spec is_enabled_on_type() -> document_ontypeformatting_options().
-is_enabled_on_type() -> false.
-
-spec handle_request(any()) -> {response, any()}.
handle_request({document_formatting, Params}) ->
#{
@@ -66,6 +45,9 @@ handle_request({document_rangeformatting, Params}) ->
{ok, Document} = els_utils:lookup_document(Uri),
{ok, TextEdit} = rangeformat_document(Uri, Document, Range, Options),
{response, TextEdit};
+%% NOTE: because erlang_ls does not send incremental document changes
+%% via `textDocument/didChange`, this kind of formatting does not
+%% make sense.
handle_request({document_ontypeformatting, Params}) ->
#{
<<"position">> := #{
diff --git a/apps/els_lsp/src/els_general_provider.erl b/apps/els_lsp/src/els_general_provider.erl
index eda88b11b..f97932813 100644
--- a/apps/els_lsp/src/els_general_provider.erl
+++ b/apps/els_lsp/src/els_general_provider.erl
@@ -2,7 +2,8 @@
-behaviour(els_provider).
-export([
- is_enabled/0,
+ default_providers/0,
+ enabled_providers/0,
handle_request/1
]).
@@ -44,13 +45,11 @@
-type exit_request() :: {exit, exit_params()}.
-type exit_params() :: #{status => atom()}.
-type exit_result() :: null.
+-type provider_id() :: string().
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec is_enabled() -> boolean().
-is_enabled() -> true.
-
-spec handle_request(
initialize_request()
| initialized_request()
@@ -111,10 +110,61 @@ handle_request({exit, #{status := Status}}) ->
%% API
%%==============================================================================
+%% @doc Give all available providers
+-spec available_providers() -> [provider_id()].
+available_providers() ->
+ [
+ "text-document-sync",
+ "hover",
+ "completion",
+ "signature-help",
+ "definition",
+ "references",
+ "document-highlight",
+ "document-symbol",
+ "workspace-symbol",
+ "code-action",
+ "document-formatting",
+ "document-range-formatting",
+ "document-on-type-formatting",
+ "folding-range",
+ "implementation",
+ "execute-command",
+ "code-lens",
+ "rename",
+ "call-hierarchy",
+ "semantic-tokens"
+ ].
+
+%% @doc Give the list of all providers enabled by default.
+-spec default_providers() -> [provider_id()].
+default_providers() ->
+ available_providers() --
+ [
+ "document-range-formatting",
+ %% NOTE: because erlang_ls does not send incremental document changes
+ %% via `textDocument/didChange', this kind of formatting does not
+ %% make sense.
+ "document-on-type-formatting",
+ %% Signature help is experimental.
+ "signature-help"
+ ].
+
+%% @doc Give the list of all providers enabled by the current configuration.
+-spec enabled_providers() -> [provider_id()].
+enabled_providers() ->
+ Config = els_config:get(providers),
+ Default = default_providers(),
+ Enabled = maps:get("enabled", Config, []),
+ Disabled = maps:get("disabled", Config, []),
+ lists:usort((Default ++ valid(Enabled)) -- valid(Disabled)).
+
+%% @doc Give the LSP server capabilities map for all capabilities enabled by
+%% the current configuration.
-spec server_capabilities() -> server_capabilities().
server_capabilities() ->
{ok, Version} = application:get_key(?APP, vsn),
- Capabilities =
+ AvailableCapabilities =
#{
textDocumentSync =>
els_text_synchronization_provider:options(),
@@ -130,34 +180,22 @@ server_capabilities() ->
triggerCharacters =>
els_signature_help_provider:trigger_characters()
},
- definitionProvider =>
- els_definition_provider:is_enabled(),
- referencesProvider =>
- els_references_provider:is_enabled(),
- documentHighlightProvider =>
- els_document_highlight_provider:is_enabled(),
- documentSymbolProvider =>
- els_document_symbol_provider:is_enabled(),
- workspaceSymbolProvider =>
- els_workspace_symbol_provider:is_enabled(),
- codeActionProvider =>
- els_code_action_provider:is_enabled(),
- documentFormattingProvider =>
- els_formatting_provider:is_enabled_document(),
- documentRangeFormattingProvider =>
- els_formatting_provider:is_enabled_range(),
- foldingRangeProvider =>
- els_folding_range_provider:is_enabled(),
- implementationProvider =>
- els_implementation_provider:is_enabled(),
+ definitionProvider => true,
+ referencesProvider => true,
+ documentHighlightProvider => true,
+ documentSymbolProvider => true,
+ workspaceSymbolProvider => true,
+ codeActionProvider => true,
+ documentFormattingProvider => true,
+ documentRangeFormattingProvider => false,
+ foldingRangeProvider => true,
+ implementationProvider => true,
executeCommandProvider =>
els_execute_command_provider:options(),
codeLensProvider =>
els_code_lens_provider:options(),
- renameProvider =>
- els_rename_provider:is_enabled(),
- callHierarchyProvider =>
- els_call_hierarchy_provider:is_enabled(),
+ renameProvider => true,
+ callHierarchyProvider => true,
semanticTokensProvider =>
#{
legend =>
@@ -166,21 +204,19 @@ server_capabilities() ->
tokenModifiers => wrangler_handler:semantic_token_modifiers()
},
range => false,
- full => els_semantic_token_provider:is_enabled()
+ full => wrangler_handler:is_enabled()
}
},
- ActiveCapabilities =
- case els_signature_help_provider:is_enabled() of
- %% This pattern can never match because is_enabled/0 is currently
- %% hard-coded to `false'. When enabling signature help manually,
- %% uncomment this branch.
- %% true ->
- %% Capabilities;
- false ->
- maps:remove(signatureHelpProvider, Capabilities)
- end,
+ EnabledProviders = enabled_providers(),
+ ConfiguredCapabilities =
+ maps:filter(
+ fun(Provider, _Config) ->
+ lists:member(provider_id(Provider), EnabledProviders)
+ end,
+ AvailableCapabilities
+ ),
#{
- capabilities => ActiveCapabilities,
+ capabilities => ConfiguredCapabilities,
serverInfo =>
#{
name => <<"Erlang LS">>,
@@ -223,3 +259,50 @@ dynamic_registration_options(<<"didChangeWatchedFiles">>) ->
watchers => [#{globPattern => GlobPattern}]
}
}.
+
+-spec valid([any()]) -> [provider_id()].
+valid(ProviderIds) ->
+ {Valid, Invalid} = lists:partition(fun is_valid_provider_id/1, ProviderIds),
+ case Invalid of
+ [] ->
+ ok;
+ _ ->
+ Fmt = "Discarding invalid providers in config file: ~p",
+ Args = [Invalid],
+ Msg = lists:flatten(io_lib:format(Fmt, Args)),
+ ?LOG_WARNING(Msg),
+ els_server:send_notification(
+ <<"window/showMessage">>,
+ #{
+ type => ?MESSAGE_TYPE_WARNING,
+ message => els_utils:to_binary(Msg)
+ }
+ )
+ end,
+ Valid.
+
+-spec is_valid_provider_id(any()) -> boolean().
+is_valid_provider_id(ProviderId) ->
+ lists:member(ProviderId, available_providers()).
+
+-spec provider_id(atom()) -> provider_id().
+provider_id(textDocumentSync) -> "text-document-sync";
+provider_id(completionProvider) -> "completion";
+provider_id(hoverProvider) -> "hover";
+provider_id(signatureHelpProvider) -> "signature-help";
+provider_id(definitionProvider) -> "definition";
+provider_id(referencesProvider) -> "references";
+provider_id(documentHighlightProvider) -> "document-highlight";
+provider_id(documentSymbolProvider) -> "document-symbol";
+provider_id(workspaceSymbolProvider) -> "workspace-symbol";
+provider_id(codeActionProvider) -> "code-action";
+provider_id(documentFormattingProvider) -> "document-formatting";
+provider_id(documentRangeFormattingProvider) -> "document-range-formatting";
+provider_id(documentOnTypeFormattingProvider) -> "document-on-type-formatting";
+provider_id(foldingRangeProvider) -> "folding-range";
+provider_id(implementationProvider) -> "implementation";
+provider_id(executeCommandProvider) -> "execute-command";
+provider_id(codeLensProvider) -> "code-lens";
+provider_id(renameProvider) -> "rename";
+provider_id(callHierarchyProvider) -> "call-hierarchy";
+provider_id(semanticTokensProvider) -> "semantic-tokens".
diff --git a/apps/els_lsp/src/els_hover_provider.erl b/apps/els_lsp/src/els_hover_provider.erl
index e49aa0803..7426d0d43 100644
--- a/apps/els_lsp/src/els_hover_provider.erl
+++ b/apps/els_lsp/src/els_hover_provider.erl
@@ -6,7 +6,6 @@
-behaviour(els_provider).
-export([
- is_enabled/0,
handle_request/1
]).
@@ -20,10 +19,6 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec is_enabled() -> boolean().
-is_enabled() ->
- true.
-
-spec handle_request(any()) -> {async, uri(), pid()}.
handle_request({hover, Params}) ->
#{
diff --git a/apps/els_lsp/src/els_implementation_provider.erl b/apps/els_lsp/src/els_implementation_provider.erl
index 34cffaea2..daba5776d 100644
--- a/apps/els_lsp/src/els_implementation_provider.erl
+++ b/apps/els_lsp/src/els_implementation_provider.erl
@@ -5,16 +5,12 @@
-include("els_lsp.hrl").
-export([
- is_enabled/0,
handle_request/1
]).
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec is_enabled() -> boolean().
-is_enabled() -> true.
-
-spec handle_request(tuple()) -> {response, [location()]}.
handle_request({implementation, Params}) ->
#{
diff --git a/apps/els_lsp/src/els_references_provider.erl b/apps/els_lsp/src/els_references_provider.erl
index ffdbced47..e7c164a8f 100644
--- a/apps/els_lsp/src/els_references_provider.erl
+++ b/apps/els_lsp/src/els_references_provider.erl
@@ -3,7 +3,6 @@
-behaviour(els_provider).
-export([
- is_enabled/0,
handle_request/1
]).
@@ -26,9 +25,6 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec is_enabled() -> boolean().
-is_enabled() -> true.
-
-spec handle_request(any()) -> {response, [location()] | null}.
handle_request({references, Params}) ->
#{
diff --git a/apps/els_lsp/src/els_rename_provider.erl b/apps/els_lsp/src/els_rename_provider.erl
index 1a6fa7a12..52477b405 100644
--- a/apps/els_lsp/src/els_rename_provider.erl
+++ b/apps/els_lsp/src/els_rename_provider.erl
@@ -3,8 +3,7 @@
-behaviour(els_provider).
-export([
- handle_request/1,
- is_enabled/0
+ handle_request/1
]).
%%==============================================================================
@@ -24,9 +23,6 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec is_enabled() -> boolean().
-is_enabled() -> true.
-
-spec handle_request(any()) -> {response, any()}.
handle_request({rename, Params}) ->
#{
diff --git a/apps/els_lsp/src/els_semantic_token_provider.erl b/apps/els_lsp/src/els_semantic_token_provider.erl
index 398b20f08..571c1876d 100644
--- a/apps/els_lsp/src/els_semantic_token_provider.erl
+++ b/apps/els_lsp/src/els_semantic_token_provider.erl
@@ -3,17 +3,12 @@
-behaviour(els_provider).
-include("els_lsp.hrl").
--export([handle_request/1, is_enabled/0]).
+-export([handle_request/1]).
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec is_enabled() -> boolean().
-is_enabled() ->
- %% Currently this is used by Wrangler only.
- wrangler_handler:is_enabled().
-
-spec handle_request(any()) -> {response, any()}.
handle_request({semantic_tokens, Params}) ->
#{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
diff --git a/apps/els_lsp/src/els_signature_help_provider.erl b/apps/els_lsp/src/els_signature_help_provider.erl
index 86391a21f..30c8f5ce3 100644
--- a/apps/els_lsp/src/els_signature_help_provider.erl
+++ b/apps/els_lsp/src/els_signature_help_provider.erl
@@ -6,7 +6,6 @@
-include_lib("kernel/include/logger.hrl").
-export([
- is_enabled/0,
handle_request/1,
trigger_characters/0
]).
@@ -22,10 +21,6 @@ trigger_characters() ->
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec is_enabled() -> boolean().
-is_enabled() ->
- false.
-
-spec handle_request(els_provider:provider_request()) ->
{response, signature_help() | null}.
handle_request({signature_help, Params}) ->
diff --git a/apps/els_lsp/src/els_workspace_symbol_provider.erl b/apps/els_lsp/src/els_workspace_symbol_provider.erl
index c2e57f304..034984614 100644
--- a/apps/els_lsp/src/els_workspace_symbol_provider.erl
+++ b/apps/els_lsp/src/els_workspace_symbol_provider.erl
@@ -3,7 +3,6 @@
-behaviour(els_provider).
-export([
- is_enabled/0,
handle_request/1
]).
@@ -14,9 +13,6 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec is_enabled() -> boolean().
-is_enabled() -> true.
-
-spec handle_request(any()) -> {response, any()}.
handle_request({symbol, Params}) ->
%% TODO: Version 3.15 of the protocol introduces a much nicer way of
diff --git a/apps/els_lsp/test/els_initialization_SUITE.erl b/apps/els_lsp/test/els_initialization_SUITE.erl
index b2db2a509..0faf511f3 100644
--- a/apps/els_lsp/test/els_initialization_SUITE.erl
+++ b/apps/els_lsp/test/els_initialization_SUITE.erl
@@ -22,7 +22,10 @@
initialize_diagnostics_invalid/1,
initialize_lenses_default/1,
initialize_lenses_custom/1,
- initialize_lenses_invalid/1
+ initialize_lenses_invalid/1,
+ initialize_providers_default/1,
+ initialize_providers_custom/1,
+ initialize_providers_invalid/1
]).
%%==============================================================================
@@ -210,3 +213,45 @@ initialize_lenses_invalid(Config) ->
],
?assertEqual(Expected, Result),
ok.
+
+-spec initialize_providers_default(config()) -> ok.
+initialize_providers_default(Config) ->
+ RootUri = els_test_utils:root_uri(),
+ DataDir = ?config(data_dir, Config),
+ ConfigPath = filename:join(DataDir, "providers_default.config"),
+ InitOpts = #{<<"erlang">> => #{<<"config_path">> => ConfigPath}},
+ els_client:initialize(RootUri, InitOpts),
+ Result = els_general_provider:enabled_providers(),
+ Expected = lists:usort(els_general_provider:default_providers()),
+ ?assertEqual(Expected, Result),
+ #{capabilities := Capabilities} = els_general_provider:server_capabilities(),
+ ?assertEqual(true, maps:is_key(hoverProvider, Capabilities)),
+ ok.
+
+-spec initialize_providers_custom(config()) -> ok.
+initialize_providers_custom(Config) ->
+ RootUri = els_test_utils:root_uri(),
+ DataDir = ?config(data_dir, Config),
+ ConfigPath = filename:join(DataDir, "providers_custom.config"),
+ InitOpts = #{<<"erlang">> => #{<<"config_path">> => ConfigPath}},
+ els_client:initialize(RootUri, InitOpts),
+ EnabledProviders = els_general_provider:enabled_providers(),
+ ?assertEqual(false, lists:member("hover", EnabledProviders)),
+ ?assertEqual(true, lists:member("document-on-type-formatting", EnabledProviders)),
+ #{capabilities := Capabilities} = els_general_provider:server_capabilities(),
+ ?assertEqual(false, maps:is_key(hoverProvider, Capabilities)),
+ ok.
+
+-spec initialize_providers_invalid(config()) -> ok.
+initialize_providers_invalid(Config) ->
+ RootUri = els_test_utils:root_uri(),
+ DataDir = ?config(data_dir, Config),
+ ConfigPath = filename:join(DataDir, "providers_invalid.config"),
+ InitOpts = #{<<"erlang">> => #{<<"config_path">> => ConfigPath}},
+ els_client:initialize(RootUri, InitOpts),
+ Result = els_general_provider:enabled_providers(),
+ Expected = lists:usort(els_general_provider:default_providers()),
+ ?assertEqual(Expected, Result),
+ #{capabilities := Capabilities} = els_general_provider:server_capabilities(),
+ ?assertEqual(true, maps:is_key(hoverProvider, Capabilities)),
+ ok.
diff --git a/apps/els_lsp/test/els_initialization_SUITE_data/providers_custom.config b/apps/els_lsp/test/els_initialization_SUITE_data/providers_custom.config
new file mode 100644
index 000000000..214b469f3
--- /dev/null
+++ b/apps/els_lsp/test/els_initialization_SUITE_data/providers_custom.config
@@ -0,0 +1,5 @@
+providers:
+ enabled:
+ - document-on-type-formatting
+ disabled:
+ - hover
diff --git a/apps/els_lsp/test/els_initialization_SUITE_data/providers_default.config b/apps/els_lsp/test/els_initialization_SUITE_data/providers_default.config
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/els_lsp/test/els_initialization_SUITE_data/providers_invalid.config b/apps/els_lsp/test/els_initialization_SUITE_data/providers_invalid.config
new file mode 100644
index 000000000..21f6a93b7
--- /dev/null
+++ b/apps/els_lsp/test/els_initialization_SUITE_data/providers_invalid.config
@@ -0,0 +1,5 @@
+providers:
+ enabled:
+ - hover
+ disabled:
+ - ssignaturee-hhelpp # Typos intentional
From d067267b906239c883fed6e0f9e69c4eb94dd580 Mon Sep 17 00:00:00 2001
From: Michael Davis
Date: Mon, 4 Jul 2022 03:50:07 -0500
Subject: [PATCH 099/239] Do not send the 'body' field for DAP Initialized
Event. (#1345)
The body field is required on most DAP Events and optional on Terminated but
is actually not included in Initialized at all. Clients parsing the Events
strictly will think an Initialized Event with a body is malformed, so this
change drops the field entirely for Initialized.
https://microsoft.github.io/debug-adapter-protocol/specification#Events_Initialized
---
apps/els_dap/src/els_dap_protocol.erl | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/apps/els_dap/src/els_dap_protocol.erl b/apps/els_dap/src/els_dap_protocol.erl
index 1259974cc..d7c8e6b45 100644
--- a/apps/els_dap/src/els_dap_protocol.erl
+++ b/apps/els_dap/src/els_dap_protocol.erl
@@ -30,8 +30,16 @@
%%==============================================================================
%% Messaging API
%%==============================================================================
--spec event(number(), binary(), any()) -> binary().
-%% TODO: Body is optional
+
+-spec event(number(), binary(), map()) -> binary().
+event(Seq, <<"initialized">> = EventType, _Body) ->
+ %% The initialized event has no body.
+ Message = #{
+ type => <<"event">>,
+ seq => Seq,
+ event => EventType
+ },
+ content(jsx:encode(Message));
event(Seq, EventType, Body) ->
Message = #{
type => <<"event">>,
From 6f88d037e86db91b43c8bce53eb448ab5666e328 Mon Sep 17 00:00:00 2001
From: Alan Zimmerman
Date: Tue, 26 Jul 2022 14:38:16 +0100
Subject: [PATCH 100/239] Make "callHierarchy/incomingCalls" more resilient
els_dt_document:wrapping_functions/2 can return zero or one items in a list.
Explicitly deal with both options.
See https://github.com/erlang-ls/erlang_ls/pull/1096#discussion_r718748583
---
.../src/els_call_hierarchy_provider.erl | 19 ++++++++++++-------
1 file changed, 12 insertions(+), 7 deletions(-)
diff --git a/apps/els_lsp/src/els_call_hierarchy_provider.erl b/apps/els_lsp/src/els_call_hierarchy_provider.erl
index a48cdcedd..4f9cd63a5 100644
--- a/apps/els_lsp/src/els_call_hierarchy_provider.erl
+++ b/apps/els_lsp/src/els_call_hierarchy_provider.erl
@@ -27,7 +27,7 @@ handle_request({incoming_calls, Params}) ->
#{<<"item">> := #{<<"uri">> := Uri} = Item} = Params,
POI = els_call_hierarchy_item:poi(Item),
References = els_references_provider:find_references(Uri, POI),
- Items = [reference_to_item(Reference) || Reference <- References],
+ Items = lists:flatten([reference_to_item(Reference) || Reference <- References]),
{response, incoming_calls(Items)};
handle_request({outgoing_calls, Params}) ->
#{<<"item">> := Item} = Params,
@@ -70,15 +70,20 @@ function_to_item(Uri, Function) ->
Data = #{poi => Function},
els_call_hierarchy_item:new(Name, Uri, Range, Range, Data).
--spec reference_to_item(location()) -> els_call_hierarchy_item:item().
+-spec reference_to_item(location()) -> [els_call_hierarchy_item:item()].
reference_to_item(Reference) ->
#{uri := RefUri, range := RefRange} = Reference,
{ok, RefDoc} = els_utils:lookup_document(RefUri),
- [WrappingPOI] = els_dt_document:wrapping_functions(RefDoc, RefRange),
- Name = els_utils:function_signature(maps:get(id, WrappingPOI)),
- POIRange = els_range:to_poi_range(RefRange),
- Data = #{poi => WrappingPOI},
- els_call_hierarchy_item:new(Name, RefUri, POIRange, POIRange, Data).
+ case els_dt_document:wrapping_functions(RefDoc, RefRange) of
+ [WrappingPOI] ->
+ els_dt_document:wrapping_functions(RefDoc, RefRange),
+ Name = els_utils:function_signature(maps:get(id, WrappingPOI)),
+ POIRange = els_range:to_poi_range(RefRange),
+ Data = #{poi => WrappingPOI},
+ [els_call_hierarchy_item:new(Name, RefUri, POIRange, POIRange, Data)];
+ _ ->
+ []
+ end.
-spec application_to_item(uri(), els_poi:poi()) ->
{ok, els_call_hierarchy_item:item()} | {error, not_found}.
From a45c2e698ad2744ffbc0c37c3c5f83f3db52a40a Mon Sep 17 00:00:00 2001
From: Michael Davis
Date: Mon, 1 Aug 2022 03:28:04 -0500
Subject: [PATCH 101/239] DAP: add `expensive` field to scopes type. (#1347)
There's a required `expensive` field on the Scope Type used
to hint to the client whether the variables in the given scope are
expensive to retrieve.
https://microsoft.github.io/debug-adapter-protocol/specification#Types_Scope
---
apps/els_dap/src/els_dap_general_provider.erl | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/apps/els_dap/src/els_dap_general_provider.erl b/apps/els_dap/src/els_dap_general_provider.erl
index c7955ed88..c42349519 100644
--- a/apps/els_dap/src/els_dap_general_provider.erl
+++ b/apps/els_dap/src/els_dap_general_provider.erl
@@ -351,7 +351,8 @@ handle_request(
#{
<<"name">> => <<"Locals">>,
<<"presentationHint">> => <<"locals">>,
- <<"variablesReference">> => Ref
+ <<"variablesReference">> => Ref,
+ <<"expensive">> => false
}
]
},
From 5a4e62ff478d4592d7aa7cde0d51a7b00b570ce3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andreas=20L=C3=B6scher?=
Date: Mon, 1 Aug 2022 09:28:51 +0100
Subject: [PATCH 102/239] [dap] normalize paths in source field (#1351)
Co-authored-by: loscher
---
apps/els_dap/src/els_dap_general_provider.erl | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/apps/els_dap/src/els_dap_general_provider.erl b/apps/els_dap/src/els_dap_general_provider.erl
index c42349519..da3ee1888 100644
--- a/apps/els_dap/src/els_dap_general_provider.erl
+++ b/apps/els_dap/src/els_dap_general_provider.erl
@@ -734,9 +734,10 @@ break_line(Pid, Node) ->
-spec source(atom(), atom()) -> binary().
source(Module, Node) ->
- Source = els_dap_rpc:file(Node, Module),
+ Source0 = els_dap_rpc:file(Node, Module),
+ Source1 = filename:absname(Source0),
els_dap_rpc:clear(Node),
- unicode:characters_to_binary(Source).
+ unicode:characters_to_binary(Source1).
-spec to_pid(pos_integer(), #{thread_id() => thread()}) -> pid().
to_pid(ThreadId, Threads) ->
From eca7e6534481f3d9ca15483ae5fca036da0528b9 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Wed, 3 Aug 2022 10:03:53 +0200
Subject: [PATCH 103/239] Do not respond to notifications in case of internal
errors (#1354)
---
apps/els_lsp/src/els_methods.erl | 33 ++++++++++++++++++--------------
1 file changed, 19 insertions(+), 14 deletions(-)
diff --git a/apps/els_lsp/src/els_methods.erl b/apps/els_lsp/src/els_methods.erl
index 8a0cb4289..08f70f359 100644
--- a/apps/els_lsp/src/els_methods.erl
+++ b/apps/els_lsp/src/els_methods.erl
@@ -73,7 +73,7 @@ dispatch(<<"$/", Method/binary>>, Params, request, State) ->
message => <<"Method not found: ", Method/binary>>
},
{error, Error, State};
-dispatch(Method, Params, _Type, State) ->
+dispatch(Method, Params, MessageType, State) ->
Function = method_to_function_name(Method),
?LOG_DEBUG("Dispatching request [method=~p] [params=~p]", [Method, Params]),
try
@@ -86,19 +86,24 @@ dispatch(Method, Params, _Type, State) ->
"Internal [type=~p] [error=~p] [stack=~p]",
[Type, Reason, Stack]
),
- Error = #{
- type => Type,
- reason => Reason,
- stack => Stack,
- method => Method,
- params => Params
- },
- ErrorMsg = els_utils:to_binary(lists:flatten(io_lib:format("~p", [Error]))),
- ErrorResponse = #{
- code => ?ERR_INTERNAL_ERROR,
- message => <<"Internal Error: ", ErrorMsg/binary>>
- },
- {error, ErrorResponse, State}
+ case MessageType of
+ request ->
+ Error = #{
+ type => Type,
+ reason => Reason,
+ stack => Stack,
+ method => Method,
+ params => Params
+ },
+ ErrorMsg = els_utils:to_binary(lists:flatten(io_lib:format("~p", [Error]))),
+ ErrorResponse = #{
+ code => ?ERR_INTERNAL_ERROR,
+ message => <<"Internal Error: ", ErrorMsg/binary>>
+ },
+ {error, ErrorResponse, State};
+ notification ->
+ {noresponse, State}
+ end
end.
-spec do_dispatch(atom(), params(), els_server:state()) -> result().
From 494bc0e6be7081627a9feac8fb2f176386d9ab53 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Fri, 5 Aug 2022 10:22:31 +0200
Subject: [PATCH 104/239] Add support for eqwalizer diagnostics (#1356)
---
.../src/diagnostics_eqwalizer.erl | 7 ++
apps/els_lsp/src/els_diagnostics.erl | 3 +-
.../els_lsp/src/els_eqwalizer_diagnostics.erl | 110 ++++++++++++++++++
apps/els_lsp/test/els_diagnostics_SUITE.erl | 55 +++++++++
elvis.config | 12 +-
rebar.config | 12 +-
rebar.lock | 5 +
7 files changed, 196 insertions(+), 8 deletions(-)
create mode 100644 apps/els_lsp/priv/code_navigation/src/diagnostics_eqwalizer.erl
create mode 100644 apps/els_lsp/src/els_eqwalizer_diagnostics.erl
diff --git a/apps/els_lsp/priv/code_navigation/src/diagnostics_eqwalizer.erl b/apps/els_lsp/priv/code_navigation/src/diagnostics_eqwalizer.erl
new file mode 100644
index 000000000..15aa112af
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/diagnostics_eqwalizer.erl
@@ -0,0 +1,7 @@
+-module(diagnostics_eqwalizer).
+
+-export([ main/0] ).
+
+-spec main() -> ok.
+main() ->
+ not_ok.
diff --git a/apps/els_lsp/src/els_diagnostics.erl b/apps/els_lsp/src/els_diagnostics.erl
index 2c5c500aa..c54b6b7ad 100644
--- a/apps/els_lsp/src/els_diagnostics.erl
+++ b/apps/els_lsp/src/els_diagnostics.erl
@@ -76,7 +76,8 @@ available_diagnostics() ->
<<"unused_includes">>,
<<"unused_macros">>,
<<"unused_record_fields">>,
- <<"refactorerl">>
+ <<"refactorerl">>,
+ <<"eqwalizer">>
].
-spec default_diagnostics() -> [diagnostic_id()].
diff --git a/apps/els_lsp/src/els_eqwalizer_diagnostics.erl b/apps/els_lsp/src/els_eqwalizer_diagnostics.erl
new file mode 100644
index 000000000..172d56cc2
--- /dev/null
+++ b/apps/els_lsp/src/els_eqwalizer_diagnostics.erl
@@ -0,0 +1,110 @@
+%%==============================================================================
+%% EqWAlizer diagnostics
+%%==============================================================================
+-module(els_eqwalizer_diagnostics).
+
+%%==============================================================================
+%% Behaviours
+%%==============================================================================
+-behaviour(els_diagnostics).
+
+%%==============================================================================
+%% Exports
+%%==============================================================================
+-export([
+ is_default/0,
+ run/1,
+ source/0
+]).
+
+%% Exported to ease mocking during tests
+-export([eqwalize/2]).
+
+%%==============================================================================
+%% Includes
+%%==============================================================================
+-include("els_lsp.hrl").
+-include_lib("kernel/include/logger.hrl").
+
+%%==============================================================================
+%% Callback Functions
+%%==============================================================================
+
+-spec is_default() -> false.
+is_default() ->
+ false.
+
+-spec run(uri()) -> [els_diagnostics:diagnostic()].
+run(Uri) ->
+ case filename:extension(Uri) of
+ <<".erl">> ->
+ Project = els_uri:path(els_config:get(root_uri)),
+ Module = els_uri:module(Uri),
+ %% Fully qualified call to ensure it's mockable
+ lists:filtermap(fun make_diagnostic/1, ?MODULE:eqwalize(Project, Module));
+ _ ->
+ []
+ end.
+
+-spec source() -> binary().
+source() ->
+ <<"EqWAlizer">>.
+
+%%==============================================================================
+%% Internal Functions
+%%==============================================================================
+
+-spec eqwalize(binary(), atom()) -> [string()].
+eqwalize(Project, Module) ->
+ Cmd = lists:flatten(
+ io_lib:format("elp eqwalize ~p --format json-lsp --project ~s", [Module, Project])
+ ),
+ string:split(string:trim(os:cmd(Cmd)), "\n", all).
+
+-spec make_diagnostic(binary()) -> {true, els_diagnostics:diagnostic()} | false.
+make_diagnostic(Message) ->
+ try jsx:decode(els_utils:to_binary(Message)) of
+ #{
+ <<"relative_path">> := _RelativePath,
+ <<"diagnostic">> :=
+ #{
+ <<"severity">> := Severity,
+ <<"message">> := Description,
+ <<"range">> :=
+ #{
+ <<"start">> :=
+ #{
+ <<"line">> := FromLine,
+ <<"character">> := FromChar
+ },
+ <<"end">> :=
+ #{
+ <<"line">> := ToLine,
+ <<"character">> := ToChar
+ }
+ }
+ }
+ } ->
+ Range = els_protocol:range(#{
+ from => {FromLine + 1, FromChar + 1},
+ to => {ToLine + 1, ToChar + 1}
+ }),
+ Diagnostic = els_diagnostics:make_diagnostic(
+ Range,
+ Description,
+ Severity,
+ source()
+ ),
+ {true, Diagnostic};
+ DecodedMessage ->
+ ?LOG_WARNING("Unrecognized Eqwalizer diagnostic (~p)", [
+ DecodedMessage
+ ]),
+ false
+ catch
+ C:E:St ->
+ ?LOG_WARNING("Issue while running Eqwalizer (~p:~p:~p) for message ~p", [
+ C, E, St, Message
+ ]),
+ false
+ end.
diff --git a/apps/els_lsp/test/els_diagnostics_SUITE.erl b/apps/els_lsp/test/els_diagnostics_SUITE.erl
index 524804c69..5e048ac9a 100644
--- a/apps/els_lsp/test/els_diagnostics_SUITE.erl
+++ b/apps/els_lsp/test/els_diagnostics_SUITE.erl
@@ -47,6 +47,7 @@
unused_macros_refactorerl/1,
unused_record_fields/1,
gradualizer/1,
+ eqwalizer/1,
module_name_check/1,
module_name_check_whitespace/1,
edoc_main/1,
@@ -142,6 +143,41 @@ init_per_testcase(TestCase, Config) when TestCase =:= gradualizer ->
meck:expect(els_gradualizer_diagnostics, is_default, 0, true),
els_mock_diagnostics:setup(),
els_test_utils:init_per_testcase(TestCase, Config);
+init_per_testcase(TestCase, Config) when TestCase =:= eqwalizer ->
+ meck:new(els_eqwalizer_diagnostics, [passthrough, no_link]),
+ meck:expect(els_eqwalizer_diagnostics, is_default, 0, true),
+ Diagnostics = [
+ els_utils:to_list(
+ jsx:encode(#{
+ <<"diagnostic">> =>
+ #{
+ <<"code">> => <<"eqwalizer">>,
+ <<"message">> =>
+ <<"Expected: 'ok'\nGot : 'not_ok'\n">>,
+ <<"range">> =>
+ #{
+ <<"end">> =>
+ #{
+ <<"character">> => 10,
+ <<"line">> => 6
+ },
+ <<"start">> =>
+ #{
+ <<"character">> => 4,
+ <<"line">> => 6
+ }
+ },
+ <<"severity">> => 2,
+ <<"source">> => <<"elp">>
+ },
+ <<"relative_path">> =>
+ <<"src/diagnostics_eqwalizer.erl">>
+ })
+ )
+ ],
+ meck:expect(els_eqwalizer_diagnostics, eqwalize, 2, Diagnostics),
+ els_mock_diagnostics:setup(),
+ els_test_utils:init_per_testcase(TestCase, Config);
init_per_testcase(TestCase, Config) when
TestCase =:= edoc_main;
TestCase =:= edoc_skip_app_src;
@@ -209,6 +245,11 @@ end_per_testcase(TestCase, Config) when TestCase =:= gradualizer ->
els_test_utils:end_per_testcase(TestCase, Config),
els_mock_diagnostics:teardown(),
ok;
+end_per_testcase(TestCase, Config) when TestCase =:= eqwalizer ->
+ meck:unload(els_eqwalizer_diagnostics),
+ els_test_utils:end_per_testcase(TestCase, Config),
+ els_mock_diagnostics:teardown(),
+ ok;
end_per_testcase(TestCase, Config) when
TestCase =:= edoc_main;
TestCase =:= edoc_skip_app_src;
@@ -893,6 +934,20 @@ gradualizer(_Config) ->
Hints = [],
els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+-spec eqwalizer(config()) -> ok.
+eqwalizer(_Config) ->
+ Path = src_path("diagnostics_eqwalizer.erl"),
+ Source = <<"EqWAlizer">>,
+ Errors = [],
+ Warnings = [
+ #{
+ message => <<"Expected: 'ok'\nGot : 'not_ok'\n">>,
+ range => {{6, 4}, {6, 10}}
+ }
+ ],
+ Hints = [],
+ els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).
+
-spec module_name_check(config()) -> ok.
module_name_check(_Config) ->
Path = src_path("diagnostics_module_name_check.erl"),
diff --git a/elvis.config b/elvis.config
index 1fde3b598..aa077b578 100644
--- a/elvis.config
+++ b/elvis.config
@@ -96,11 +96,13 @@
filter => "Makefile",
ruleset => makefiles
},
- #{
- dirs => ["."],
- filter => "rebar.config",
- ruleset => rebar_config
- },
+ %% Commented out due to:
+ %% Error: 'function_clause' while applying rule 'protocol_for_deps_rebar'.
+ %% #{
+ %% dirs => ["."],
+ %% filter => "rebar.config",
+ %% ruleset => rebar_config
+ %% },
#{
dirs => ["."],
filter => "elvis.config",
diff --git a/rebar.config b/rebar.config
index a2a9dbf88..1fdf4c2d7 100644
--- a/rebar.config
+++ b/rebar.config
@@ -23,7 +23,10 @@
{ephemeral, "2.0.4"},
{tdiff, "0.1.2"},
{uuid, "2.0.1", {pkg, uuid_erl}},
- {gradualizer, {git, "https://github.com/josefs/Gradualizer.git", {ref, "6e89b4e"}}}
+ {gradualizer, {git, "https://github.com/josefs/Gradualizer.git", {ref, "6e89b4e"}}},
+ {eqwalizer_support,
+ {git_subdir, "https://github.com/whatsapp/eqwalizer.git", {branch, "main"},
+ "eqwalizer_support"}}
]}.
{shell, [{apps, [els_lsp]}]}.
@@ -35,7 +38,12 @@
{rebar3_bsp, {git, "https://github.com/erlang-ls/rebar3_bsp.git", {ref, "master"}}}
]}.
-{project_plugins, [erlfmt]}.
+{project_plugins, [
+ erlfmt,
+ {eqwalizer_rebar3,
+ {git_subdir, "https://github.com/whatsapp/eqwalizer.git", {branch, "main"},
+ "eqwalizer_rebar3"}}
+]}.
{minimum_otp_vsn, "22.0"}.
diff --git a/rebar.lock b/rebar.lock
index a5e6d6aed..56d251a3f 100644
--- a/rebar.lock
+++ b/rebar.lock
@@ -3,6 +3,11 @@
{<<"docsh">>,{pkg,<<"docsh">>,<<"0.7.2">>},0},
{<<"elvis_core">>,{pkg,<<"elvis_core">>,<<"1.3.1">>},0},
{<<"ephemeral">>,{pkg,<<"ephemeral">>,<<"2.0.4">>},0},
+ {<<"eqwalizer_support">>,
+ {git_subdir,"https://github.com/whatsapp/eqwalizer.git",
+ {ref,"c3c3b284110dcacc0d2a3cb73875d5b5341b8dc2"},
+ "eqwalizer_support"},
+ 0},
{<<"erlfmt">>,
{git,"https://github.com/gomoripeti/erlfmt.git",
{ref,"d4422d1fd79a73ef534c2bcbe5b5da4da5338833"}},
From b653ef8a0a3291121c9db32761a1f57a7e3cd381 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Mon, 8 Aug 2022 10:04:09 +0200
Subject: [PATCH 105/239] Ensure to pass an absolute path to the
`els_uri:uri/1` function (#1357)
The `compile:file/1` function can return a relative path depending on
the directory the emulator is started on. This corner case was
resulting in occasional crashes for the users which prevented
diagnostics to appear if an included file contained errors.
---
apps/els_lsp/src/els_compiler_diagnostics.erl | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/apps/els_lsp/src/els_compiler_diagnostics.erl b/apps/els_lsp/src/els_compiler_diagnostics.erl
index e3b3c0c34..b86283089 100644
--- a/apps/els_lsp/src/els_compiler_diagnostics.erl
+++ b/apps/els_lsp/src/els_compiler_diagnostics.erl
@@ -680,18 +680,23 @@ inclusion_range(IncludePath, Document, include) ->
[Range || #{id := Id, range := Range} <- POIs, Id =:= IncludeId];
inclusion_range(IncludePath, Document, include_lib) ->
POIs = els_dt_document:pois(Document, [include_lib]),
- IncludeId = els_utils:include_lib_id(IncludePath),
+ IncludeId = els_utils:include_lib_id(absolute_path(IncludePath)),
[Range || #{id := Id, range := Range} <- POIs, Id =:= IncludeId];
inclusion_range(IncludePath, Document, behaviour) ->
POIs = els_dt_document:pois(Document, [behaviour]),
- BehaviourId = els_uri:module(els_uri:uri(els_utils:to_binary(IncludePath))),
+ BehaviourId = els_uri:module(els_uri:uri(els_utils:to_binary(absolute_path(IncludePath)))),
[Range || #{id := Id, range := Range} <- POIs, Id =:= BehaviourId];
inclusion_range(IncludePath, Document, parse_transform) ->
POIs = els_dt_document:pois(Document, [parse_transform]),
ParseTransformId =
- els_uri:module(els_uri:uri(els_utils:to_binary(IncludePath))),
+ els_uri:module(els_uri:uri(els_utils:to_binary(absolute_path(IncludePath)))),
[Range || #{id := Id, range := Range} <- POIs, Id =:= ParseTransformId].
+-spec absolute_path(string()) -> string().
+absolute_path(Path) ->
+ {ok, Cwd} = file:get_cwd(),
+ filename:join(Cwd, Path).
+
-spec macro_options() -> [macro_option()].
macro_options() ->
Macros = els_config:get(macros),
From a8c7d162a2fcb7d3b2a54241a548eac548c0ffb5 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Mon, 8 Aug 2022 11:46:59 +0200
Subject: [PATCH 106/239] Remove eqwalizer as dependency (#1358)
Temporarily remove eqwalizer as a project dependency, since the
git_subdir method breaks our internal upgrade scripts.
This does not affect eqwalizer diagnostics, which are still available.
---
rebar.config | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/rebar.config b/rebar.config
index 1fdf4c2d7..1bdc48e6c 100644
--- a/rebar.config
+++ b/rebar.config
@@ -23,10 +23,7 @@
{ephemeral, "2.0.4"},
{tdiff, "0.1.2"},
{uuid, "2.0.1", {pkg, uuid_erl}},
- {gradualizer, {git, "https://github.com/josefs/Gradualizer.git", {ref, "6e89b4e"}}},
- {eqwalizer_support,
- {git_subdir, "https://github.com/whatsapp/eqwalizer.git", {branch, "main"},
- "eqwalizer_support"}}
+ {gradualizer, {git, "https://github.com/josefs/Gradualizer.git", {ref, "6e89b4e"}}}
]}.
{shell, [{apps, [els_lsp]}]}.
@@ -39,10 +36,7 @@
]}.
{project_plugins, [
- erlfmt,
- {eqwalizer_rebar3,
- {git_subdir, "https://github.com/whatsapp/eqwalizer.git", {branch, "main"},
- "eqwalizer_rebar3"}}
+ erlfmt
]}.
{minimum_otp_vsn, "22.0"}.
From 1d8500819002fa1a2d1969619dbbc99ca91409de Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Mon, 8 Aug 2022 12:59:49 +0200
Subject: [PATCH 107/239] Remove eqwalizer_support from rebar.lock file (#1359)
---
rebar.lock | 5 -----
1 file changed, 5 deletions(-)
diff --git a/rebar.lock b/rebar.lock
index 56d251a3f..a5e6d6aed 100644
--- a/rebar.lock
+++ b/rebar.lock
@@ -3,11 +3,6 @@
{<<"docsh">>,{pkg,<<"docsh">>,<<"0.7.2">>},0},
{<<"elvis_core">>,{pkg,<<"elvis_core">>,<<"1.3.1">>},0},
{<<"ephemeral">>,{pkg,<<"ephemeral">>,<<"2.0.4">>},0},
- {<<"eqwalizer_support">>,
- {git_subdir,"https://github.com/whatsapp/eqwalizer.git",
- {ref,"c3c3b284110dcacc0d2a3cb73875d5b5341b8dc2"},
- "eqwalizer_support"},
- 0},
{<<"erlfmt">>,
{git,"https://github.com/gomoripeti/erlfmt.git",
{ref,"d4422d1fd79a73ef534c2bcbe5b5da4da5338833"}},
From a2e258231772069f321b1d076132e4099d642283 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Tue, 23 Aug 2022 11:31:50 +0200
Subject: [PATCH 108/239] Use the wrapping range for symbols, when available
(#1368)
This currently affects only functions. The major visible effect is
that now the function name will be shown in the IDE breadcrumbs
when the user visits any line contained in a function, rather than
only when hovering the function name itself.
---
apps/els_core/src/els_poi.erl | 12 +++--
.../test/els_document_symbol_SUITE.erl | 54 +++++++++----------
2 files changed, 36 insertions(+), 30 deletions(-)
diff --git a/apps/els_core/src/els_poi.erl b/apps/els_core/src/els_poi.erl
index dd64a537f..359f95967 100644
--- a/apps/els_core/src/els_poi.erl
+++ b/apps/els_core/src/els_poi.erl
@@ -15,7 +15,8 @@
label/1,
symbol_kind/1,
to_symbol/2,
- folding_range/1
+ folding_range/1,
+ symbol_range/1
]).
%%==============================================================================
@@ -126,13 +127,12 @@ symbol_kind(#{kind := Kind}) ->
-spec to_symbol(uri(), els_poi:poi()) -> symbol_information().
to_symbol(Uri, POI) ->
- #{range := Range} = POI,
#{
name => label(POI),
kind => symbol_kind(POI),
location => #{
uri => Uri,
- range => els_protocol:range(Range)
+ range => els_protocol:range(symbol_range(POI))
}
}.
@@ -140,6 +140,12 @@ to_symbol(Uri, POI) ->
folding_range(#{data := #{folding_range := Range}}) ->
Range.
+-spec symbol_range(els_poi:poi()) -> poi_range().
+symbol_range(#{data := #{wrapping_range := WrappingRange}}) ->
+ WrappingRange;
+symbol_range(#{range := Range}) ->
+ Range.
+
%%==============================================================================
%% Internal Functions
%%==============================================================================
diff --git a/apps/els_lsp/test/els_document_symbol_SUITE.erl b/apps/els_lsp/test/els_document_symbol_SUITE.erl
index 2c53ee087..46bd8b50d 100644
--- a/apps/els_lsp/test/els_document_symbol_SUITE.erl
+++ b/apps/els_lsp/test/els_document_symbol_SUITE.erl
@@ -148,33 +148,33 @@ expected_types(Uri) ->
functions() ->
[
- {<<"function_a/0">>, {20, 0}, {20, 10}},
- {<<"function_b/0">>, {24, 0}, {24, 10}},
- {<<"callback_a/0">>, {27, 0}, {27, 10}},
- {<<"function_c/0">>, {30, 0}, {30, 10}},
- {<<"function_d/0">>, {38, 0}, {38, 10}},
- {<<"function_e/0">>, {41, 0}, {41, 10}},
- {<<"function_f/0">>, {46, 0}, {46, 10}},
- {<<"function_g/1">>, {49, 0}, {49, 10}},
- {<<"function_h/0">>, {55, 0}, {55, 10}},
- {<<"function_i/0">>, {59, 0}, {59, 10}},
- {<<"function_i/0">>, {61, 0}, {61, 10}},
- {<<"function_j/0">>, {66, 0}, {66, 10}},
- {<<"function_k/0">>, {73, 0}, {73, 10}},
- {<<"function_l/2">>, {78, 0}, {78, 10}},
- {<<"function_m/1">>, {83, 0}, {83, 10}},
- {<<"function_n/0">>, {88, 0}, {88, 10}},
- {<<"function_o/0">>, {92, 0}, {92, 10}},
- {<<"PascalCaseFunction/1">>, {97, 0}, {97, 20}},
- {<<"function_p/1">>, {102, 0}, {102, 10}},
- {<<"function_q/0">>, {113, 0}, {113, 10}},
- {<<"macro_b/2">>, {119, 0}, {119, 7}},
- {<<"function_mb/0">>, {122, 0}, {122, 11}},
- {<<"code_navigation/0">>, {125, 0}, {125, 15}},
- {<<"code_navigation/1">>, {127, 0}, {127, 15}},
- {<<"multiple_instances_same_file/0">>, {129, 0}, {129, 28}},
- {<<"code_navigation_extra/3">>, {131, 0}, {131, 21}},
- {<<"multiple_instances_diff_file/0">>, {133, 0}, {133, 28}}
+ {<<"function_a/0">>, {20, 0}, {23, -1}},
+ {<<"function_b/0">>, {24, 0}, {26, -1}},
+ {<<"callback_a/0">>, {27, 0}, {29, -1}},
+ {<<"function_c/0">>, {30, 0}, {35, -1}},
+ {<<"function_d/0">>, {38, 0}, {40, -1}},
+ {<<"function_e/0">>, {41, 0}, {43, -1}},
+ {<<"function_f/0">>, {46, 0}, {48, -1}},
+ {<<"function_g/1">>, {49, 0}, {53, -1}},
+ {<<"function_h/0">>, {55, 0}, {57, -1}},
+ {<<"function_i/0">>, {59, 0}, {60, -1}},
+ {<<"function_i/0">>, {61, 0}, {62, -1}},
+ {<<"function_j/0">>, {66, 0}, {68, -1}},
+ {<<"function_k/0">>, {73, 0}, {76, -1}},
+ {<<"function_l/2">>, {78, 0}, {81, -1}},
+ {<<"function_m/1">>, {83, 0}, {86, -1}},
+ {<<"function_n/0">>, {88, 0}, {90, -1}},
+ {<<"function_o/0">>, {92, 0}, {94, -1}},
+ {<<"PascalCaseFunction/1">>, {97, 0}, {101, -1}},
+ {<<"function_p/1">>, {102, 0}, {108, -1}},
+ {<<"function_q/0">>, {113, 0}, {116, -1}},
+ {<<"macro_b/2">>, {119, 0}, {121, -1}},
+ {<<"function_mb/0">>, {122, 0}, {124, -1}},
+ {<<"code_navigation/0">>, {125, 0}, {126, -1}},
+ {<<"code_navigation/1">>, {127, 0}, {128, -1}},
+ {<<"multiple_instances_same_file/0">>, {129, 0}, {130, -1}},
+ {<<"code_navigation_extra/3">>, {131, 0}, {132, -1}},
+ {<<"multiple_instances_diff_file/0">>, {133, 0}, {134, -1}}
].
macros() ->
From 6ec94072f5b9bb984da1026f9ba0efaa072271b7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kristoffer=20Br=C3=A5nemyr?=
Date: Tue, 23 Aug 2022 13:13:23 +0200
Subject: [PATCH 109/239] Send end progress when request is cancelled. (#1366)
* Send end progress when request is cancelled.
* Fix formatting
---
apps/els_lsp/src/els_background_job.erl | 34 ++++++++++++++++--------
apps/els_lsp/src/els_server.erl | 14 ++++++++--
apps/els_lsp/test/els_progress_SUITE.erl | 24 ++++++++++++++++-
3 files changed, 58 insertions(+), 14 deletions(-)
diff --git a/apps/els_lsp/src/els_background_job.erl b/apps/els_lsp/src/els_background_job.erl
index c761ddae5..dce29362e 100644
--- a/apps/els_lsp/src/els_background_job.erl
+++ b/apps/els_lsp/src/els_background_job.erl
@@ -155,6 +155,8 @@ handle_cast(_Request, State) ->
-spec handle_info(any(), any()) ->
{noreply, state()}.
+handle_info({exec, InternalState}, State) ->
+ handle_info(exec, State#{internal_state => InternalState});
handle_info(exec, State) ->
#{
config := #{entries := Entries, task := Task} = Config,
@@ -171,22 +173,32 @@ handle_info(exec, State) ->
notify_end(Token, Total, ProgressEnabled),
{stop, normal, State};
[Entry | Rest] ->
- NewInternalState = Task(Entry, InternalState),
- notify_report(
- Token,
- Current,
- Step,
- Total,
- ProgressEnabled,
- ShowPercentages
+ MainPid = self(),
+ %% Run the task in a separate process so main process
+ %% is not blocked from receiving messages, needed for stopping
+ %% job.
+ spawn_link(
+ fun() ->
+ NewInternalState = Task(Entry, InternalState),
+ notify_report(
+ Token,
+ Current,
+ Step,
+ Total,
+ ProgressEnabled,
+ ShowPercentages
+ ),
+ MainPid ! {exec, NewInternalState}
+ end
),
- self() ! exec,
{noreply, State#{
config => Config#{entries => Rest},
- current => Current + 1,
- internal_state => NewInternalState
+ current => Current + 1
}}
end;
+%% Allow the terminate function to be called if the spawned child processes dies
+handle_info({'EXIT', _Sender, Reason}, State) when Reason /= normal ->
+ {stop, Reason, State};
handle_info(_Request, State) ->
{noreply, State}.
diff --git a/apps/els_lsp/src/els_server.erl b/apps/els_lsp/src/els_server.erl
index e87ce12d1..cd743075c 100644
--- a/apps/els_lsp/src/els_server.erl
+++ b/apps/els_lsp/src/els_server.erl
@@ -230,6 +230,16 @@ handle_request(
{RequestId, Job} when RequestId =:= Id ->
?LOG_DEBUG("[SERVER] Cancelling request [id=~p] [job=~p]", [Id, Job]),
els_background_job:stop(Job),
+ Error = #{
+ code => ?ERR_REQUEST_CANCELLED,
+ message => <<"Request was cancelled">>
+ },
+ ErrorResponse = els_protocol:error(RequestId, Error),
+ ?LOG_DEBUG(
+ "[SERVER] Sending error response [response=~p]",
+ [ErrorResponse]
+ ),
+ send(ErrorResponse, State0),
State0#{
pending => lists:keydelete(Id, 1, Pending),
in_progress => lists:keydelete(Job, 2, InProgress)
@@ -272,8 +282,8 @@ handle_request(
{async, Uri, BackgroundJob, State} ->
RequestId = maps:get(<<"id">>, Request),
?LOG_DEBUG(
- "[SERVER] Suspending response [background_job=~p]",
- [BackgroundJob]
+ "[SERVER] Suspending response [background_job=~p id=~p]",
+ [BackgroundJob, RequestId]
),
NewPending = [{RequestId, BackgroundJob} | Pending],
State#{
diff --git a/apps/els_lsp/test/els_progress_SUITE.erl b/apps/els_lsp/test/els_progress_SUITE.erl
index d8f9004f6..79ea69c95 100644
--- a/apps/els_lsp/test/els_progress_SUITE.erl
+++ b/apps/els_lsp/test/els_progress_SUITE.erl
@@ -20,7 +20,8 @@
%%==============================================================================
-export([
sample_job/1,
- failing_job/1
+ failing_job/1,
+ stop_job/1
]).
%%==============================================================================
@@ -63,6 +64,16 @@ init_per_testcase(sample_job = TestCase, Config) ->
init_per_testcase(failing_job = TestCase, Config) ->
Task = fun(_, _) -> exit(fail) end,
setup_mocks(Task),
+ [{task, Task} | els_test_utils:init_per_testcase(TestCase, Config)];
+init_per_testcase(stop_job = TestCase, Config) ->
+ Task = fun(_, _) ->
+ sample_job:task_called(),
+ timer:sleep(timer:seconds(100))
+ end,
+ setup_mocks(Task),
+ %% Bit of a hack, because meck only count history after mocked function returns it seems,
+ %% and the above function will not return.
+ meck:expect(sample_job, task_called, fun() -> ok end),
[{task, Task} | els_test_utils:init_per_testcase(TestCase, Config)].
-spec end_per_testcase(atom(), config()) -> ok.
@@ -92,6 +103,17 @@ failing_job(_Config) ->
?assertEqual(1, meck:num_calls(sample_job, on_error, '_')),
ok.
+-spec stop_job(config()) -> ok.
+stop_job(_Config) ->
+ {ok, Pid} = new_background_job(),
+ meck:wait(sample_job, task_called, '_', 5000),
+ els_background_job:stop(Pid),
+ wait_for_completion(Pid),
+ ?assertEqual(1, meck:num_calls(sample_job, task_called, '_')),
+ ?assertEqual(0, meck:num_calls(sample_job, on_complete, '_')),
+ ?assertEqual(1, meck:num_calls(sample_job, on_error, '_')),
+ ok.
+
%%==============================================================================
%% Internal Functions
%%==============================================================================
From 55a3854c17050ce48d5f5a88c863df267ac3025c Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Mon, 29 Aug 2022 09:13:20 +0200
Subject: [PATCH 110/239] Use explicit symbol range for document symbols
(#1370)
The previous implementation relied on the wrapping
range, which could contain zeros as column numbers. When transferring
the wrapping range via the LSP protocol, this could produce negative
numbers which are supported in some editors (eg Emacs) but not in
others (eg VS Code).
An alternative solution could have been refactor the wrapping range
handling, but for the time being - and considering symbol ranges are
limited to functions - I prefer to treat the two entities separately
for simplicity and to minimize risks.
---
apps/els_core/src/els_poi.erl | 4 +-
apps/els_lsp/src/els_parser.erl | 6 ++-
.../els_lsp/test/els_call_hierarchy_SUITE.erl | 9 ++++
.../test/els_document_symbol_SUITE.erl | 54 +++++++++----------
4 files changed, 43 insertions(+), 30 deletions(-)
diff --git a/apps/els_core/src/els_poi.erl b/apps/els_core/src/els_poi.erl
index 359f95967..8ffbca9d0 100644
--- a/apps/els_core/src/els_poi.erl
+++ b/apps/els_core/src/els_poi.erl
@@ -141,8 +141,8 @@ folding_range(#{data := #{folding_range := Range}}) ->
Range.
-spec symbol_range(els_poi:poi()) -> poi_range().
-symbol_range(#{data := #{wrapping_range := WrappingRange}}) ->
- WrappingRange;
+symbol_range(#{data := #{symbol_range := SymbolRange}}) ->
+ SymbolRange;
symbol_range(#{range := Range}) ->
Range.
diff --git a/apps/els_lsp/src/els_parser.erl b/apps/els_lsp/src/els_parser.erl
index d88562508..0bc454014 100644
--- a/apps/els_lsp/src/els_parser.erl
+++ b/apps/els_lsp/src/els_parser.erl
@@ -621,7 +621,7 @@ function(Tree) ->
erl_syntax:type(Clause) =:= clause
],
{StartLine, StartColumn} = get_start_location(Tree),
- {EndLine, _EndColumn} = get_end_location(Tree),
+ {EndLine, EndColumn} = get_end_location(Tree),
FoldingRange = exceeds_one_line(StartLine, EndLine),
FunctionPOI = poi(
erl_syntax:get_pos(FunName),
@@ -633,6 +633,10 @@ function(Tree) ->
from => {StartLine, StartColumn},
to => {EndLine + 1, 0}
},
+ symbol_range => #{
+ from => {StartLine, StartColumn},
+ to => {EndLine, EndColumn}
+ },
folding_range => FoldingRange
}
),
diff --git a/apps/els_lsp/test/els_call_hierarchy_SUITE.erl b/apps/els_lsp/test/els_call_hierarchy_SUITE.erl
index 26c359a25..67a29c0e4 100644
--- a/apps/els_lsp/test/els_call_hierarchy_SUITE.erl
+++ b/apps/els_lsp/test/els_call_hierarchy_SUITE.erl
@@ -76,6 +76,7 @@ incoming_calls(Config) ->
from => {7, 1},
to => {17, 0}
},
+ symbol_range => #{from => {7, 1}, to => {16, 19}},
folding_range => #{
from => {7, ?END_OF_LINE},
to => {16, ?END_OF_LINE}
@@ -126,6 +127,8 @@ incoming_calls(Config) ->
from => {7, 1},
to => {14, 0}
},
+
+ symbol_range => #{from => {7, 1}, to => {13, 19}},
folding_range =>
#{
from => {7, ?END_OF_LINE},
@@ -177,6 +180,7 @@ incoming_calls(Config) ->
from => {7, 1},
to => {17, 0}
},
+ symbol_range => #{from => {7, 1}, to => {16, 19}},
folding_range =>
#{
from => {7, ?END_OF_LINE},
@@ -246,6 +250,7 @@ outgoing_calls(Config) ->
from => {7, 1},
to => {17, 0}
},
+ symbol_range => #{from => {7, 1}, to => {16, 19}},
folding_range => #{
from => {7, ?END_OF_LINE},
to => {16, ?END_OF_LINE}
@@ -292,6 +297,7 @@ outgoing_calls(Config) ->
from => {7, 1},
to => {17, 0}
},
+ symbol_range => #{from => {7, 1}, to => {16, 19}},
folding_range => #{
from => {7, ?END_OF_LINE},
to => {16, ?END_OF_LINE}
@@ -315,6 +321,7 @@ outgoing_calls(Config) ->
from => {18, 1},
to => {20, 0}
},
+ symbol_range => #{from => {18, 1}, to => {19, 6}},
folding_range => #{
from => {18, ?END_OF_LINE},
to => {19, ?END_OF_LINE}
@@ -338,6 +345,7 @@ outgoing_calls(Config) ->
from => {7, 1},
to => {14, 0}
},
+ symbol_range => #{from => {7, 1}, to => {13, 19}},
folding_range => #{
from => {7, ?END_OF_LINE},
to => {13, ?END_OF_LINE}
@@ -361,6 +369,7 @@ outgoing_calls(Config) ->
from => {18, 1},
to => {20, 0}
},
+ symbol_range => #{from => {18, 1}, to => {19, 6}},
folding_range => #{
from => {18, ?END_OF_LINE},
to => {19, ?END_OF_LINE}
diff --git a/apps/els_lsp/test/els_document_symbol_SUITE.erl b/apps/els_lsp/test/els_document_symbol_SUITE.erl
index 46bd8b50d..4d02c99d0 100644
--- a/apps/els_lsp/test/els_document_symbol_SUITE.erl
+++ b/apps/els_lsp/test/els_document_symbol_SUITE.erl
@@ -148,33 +148,33 @@ expected_types(Uri) ->
functions() ->
[
- {<<"function_a/0">>, {20, 0}, {23, -1}},
- {<<"function_b/0">>, {24, 0}, {26, -1}},
- {<<"callback_a/0">>, {27, 0}, {29, -1}},
- {<<"function_c/0">>, {30, 0}, {35, -1}},
- {<<"function_d/0">>, {38, 0}, {40, -1}},
- {<<"function_e/0">>, {41, 0}, {43, -1}},
- {<<"function_f/0">>, {46, 0}, {48, -1}},
- {<<"function_g/1">>, {49, 0}, {53, -1}},
- {<<"function_h/0">>, {55, 0}, {57, -1}},
- {<<"function_i/0">>, {59, 0}, {60, -1}},
- {<<"function_i/0">>, {61, 0}, {62, -1}},
- {<<"function_j/0">>, {66, 0}, {68, -1}},
- {<<"function_k/0">>, {73, 0}, {76, -1}},
- {<<"function_l/2">>, {78, 0}, {81, -1}},
- {<<"function_m/1">>, {83, 0}, {86, -1}},
- {<<"function_n/0">>, {88, 0}, {90, -1}},
- {<<"function_o/0">>, {92, 0}, {94, -1}},
- {<<"PascalCaseFunction/1">>, {97, 0}, {101, -1}},
- {<<"function_p/1">>, {102, 0}, {108, -1}},
- {<<"function_q/0">>, {113, 0}, {116, -1}},
- {<<"macro_b/2">>, {119, 0}, {121, -1}},
- {<<"function_mb/0">>, {122, 0}, {124, -1}},
- {<<"code_navigation/0">>, {125, 0}, {126, -1}},
- {<<"code_navigation/1">>, {127, 0}, {128, -1}},
- {<<"multiple_instances_same_file/0">>, {129, 0}, {130, -1}},
- {<<"code_navigation_extra/3">>, {131, 0}, {132, -1}},
- {<<"multiple_instances_diff_file/0">>, {133, 0}, {134, -1}}
+ {<<"function_a/0">>, {20, 0}, {22, 14}},
+ {<<"function_b/0">>, {24, 0}, {25, 11}},
+ {<<"callback_a/0">>, {27, 0}, {28, 5}},
+ {<<"function_c/0">>, {30, 0}, {34, 20}},
+ {<<"function_d/0">>, {38, 0}, {39, 14}},
+ {<<"function_e/0">>, {41, 0}, {42, 25}},
+ {<<"function_f/0">>, {46, 0}, {47, 11}},
+ {<<"function_g/1">>, {49, 0}, {52, 70}},
+ {<<"function_h/0">>, {55, 0}, {56, 15}},
+ {<<"function_i/0">>, {59, 0}, {59, 20}},
+ {<<"function_i/0">>, {61, 0}, {61, 20}},
+ {<<"function_j/0">>, {66, 0}, {67, 5}},
+ {<<"function_k/0">>, {73, 0}, {75, 13}},
+ {<<"function_l/2">>, {78, 0}, {80, 6}},
+ {<<"function_m/1">>, {83, 0}, {85, 36}},
+ {<<"function_n/0">>, {88, 0}, {89, 8}},
+ {<<"function_o/0">>, {92, 0}, {93, 18}},
+ {<<"PascalCaseFunction/1">>, {97, 0}, {100, 78}},
+ {<<"function_p/1">>, {102, 0}, {107, 13}},
+ {<<"function_q/0">>, {113, 0}, {115, 47}},
+ {<<"macro_b/2">>, {119, 0}, {120, 5}},
+ {<<"function_mb/0">>, {122, 0}, {123, 17}},
+ {<<"code_navigation/0">>, {125, 0}, {125, 37}},
+ {<<"code_navigation/1">>, {127, 0}, {127, 24}},
+ {<<"multiple_instances_same_file/0">>, {129, 0}, {129, 74}},
+ {<<"code_navigation_extra/3">>, {131, 0}, {131, 67}},
+ {<<"multiple_instances_diff_file/0">>, {133, 0}, {133, 56}}
].
macros() ->
From 4afd02260026295d9b8de7fda022e24eb2850089 Mon Sep 17 00:00:00 2001
From: Zach Lankton
Date: Thu, 8 Sep 2022 03:40:44 -0400
Subject: [PATCH 111/239] [1371] Add Custom Hostname and Domain Options (#1372)
When using `longnames` in projects that don't
adhere to the host provided hostname and domain
it is useful to be able to override those via the
`erlang_ls.config` file. The code in this commit
provides these options as well as cleans up
some related duplicated code.
This code also fixes a small bug that would leave
a trailing dot `.` if a domain is not defined.
Tests have been updated to reflect these changes and
new tests have been created to test the new options.
---
apps/els_core/src/els_config_runtime.erl | 26 ++++++++-
apps/els_core/src/els_distribution_server.erl | 11 +---
apps/els_core/src/els_utils.erl | 9 ++--
apps/els_dap/src/els_dap_general_provider.erl | 32 +++--------
apps/els_lsp/test/els_diagnostics_SUITE.erl | 54 +++++++++++++++----
5 files changed, 84 insertions(+), 48 deletions(-)
diff --git a/apps/els_core/src/els_config_runtime.erl b/apps/els_core/src/els_config_runtime.erl
index 34dcbf5a5..bb3abfb8d 100644
--- a/apps/els_core/src/els_config_runtime.erl
+++ b/apps/els_core/src/els_config_runtime.erl
@@ -8,6 +8,8 @@
%% Getters
-export([
get_node_name/0,
+ get_hostname/0,
+ get_domain/0,
get_otp_path/0,
get_start_cmd/0,
get_start_args/0,
@@ -20,6 +22,8 @@
-spec default_config() -> config().
default_config() ->
#{
+ "hostname" => default_hostname(),
+ "domain" => default_domain(),
"node_name" => default_node_name(),
"otp_path" => default_otp_path(),
"start_cmd" => default_start_cmd(),
@@ -31,6 +35,17 @@ get_node_name() ->
Value = maps:get("node_name", els_config:get(runtime), default_node_name()),
els_utils:compose_node_name(Value, get_name_type()).
+-spec get_hostname() -> string().
+get_hostname() ->
+ case els_config:get(runtime) of
+ undefined -> default_hostname();
+ Runtime -> maps:get("hostname", Runtime, default_hostname())
+ end.
+
+-spec get_domain() -> string().
+get_domain() ->
+ maps:get("domain", els_config:get(runtime), default_domain()).
+
-spec get_otp_path() -> string().
get_otp_path() ->
maps:get("otp_path", els_config:get(runtime), default_otp_path()).
@@ -65,12 +80,21 @@ get_cookie() ->
-spec default_node_name() -> string().
default_node_name() ->
RootUri = els_config:get(root_uri),
- {ok, Hostname} = inet:gethostname(),
+ Hostname = get_hostname(),
NodeName = els_distribution_server:normalize_node_name(
filename:basename(els_uri:path(RootUri))
),
NodeName ++ "@" ++ Hostname.
+-spec default_hostname() -> string().
+default_hostname() ->
+ {ok, Hostname} = inet:gethostname(),
+ Hostname.
+
+-spec default_domain() -> string().
+default_domain() ->
+ proplists:get_value(domain, inet:get_rc(), "").
+
-spec default_otp_path() -> string().
default_otp_path() ->
filename:dirname(filename:dirname(code:root_dir())).
diff --git a/apps/els_core/src/els_distribution_server.erl b/apps/els_core/src/els_distribution_server.erl
index 6b30da914..97efca4ba 100644
--- a/apps/els_core/src/els_distribution_server.erl
+++ b/apps/els_core/src/els_distribution_server.erl
@@ -18,7 +18,6 @@
rpc_call/3,
rpc_call/4,
node_name/2,
- node_name/3,
normalize_node_name/1
]).
@@ -223,15 +222,7 @@ node_name(Prefix, Name0) ->
Name = normalize_node_name(Name0),
Int = erlang:phash2(erlang:timestamp()),
Id = lists:flatten(io_lib:format("~s_~s_~p", [Prefix, Name, Int])),
- {ok, HostName} = inet:gethostname(),
- node_name(Id, HostName, els_config_runtime:get_name_type()).
-
--spec node_name(string(), string(), 'longnames' | 'shortnames') -> atom().
-node_name(Id, HostName, shortnames) ->
- list_to_atom(Id ++ "@" ++ HostName);
-node_name(Id, HostName, longnames) ->
- Domain = proplists:get_value(domain, inet:get_rc(), ""),
- list_to_atom(Id ++ "@" ++ HostName ++ "." ++ Domain).
+ els_utils:compose_node_name(Id, els_config_runtime:get_name_type()).
-spec normalize_node_name(string() | binary()) -> string().
normalize_node_name(Name) ->
diff --git a/apps/els_core/src/els_utils.erl b/apps/els_core/src/els_utils.erl
index edb562436..242ee2589 100644
--- a/apps/els_core/src/els_utils.erl
+++ b/apps/els_core/src/els_utils.erl
@@ -453,15 +453,18 @@ compose_node_name(Name, Type) ->
true ->
Name;
_ ->
- {ok, HostName} = inet:gethostname(),
+ HostName = els_config_runtime:get_hostname(),
Name ++ [$@ | HostName]
end,
case Type of
shortnames ->
list_to_atom(NodeName);
longnames ->
- Domain = proplists:get_value(domain, inet:get_rc(), ""),
- list_to_atom(NodeName ++ "." ++ Domain)
+ Domain = els_config_runtime:get_domain(),
+ case Domain of
+ "" -> list_to_atom(NodeName);
+ _ -> list_to_atom(NodeName ++ "." ++ Domain)
+ end
end.
%% @doc Given an MFA or a FA, return a printable version of the
diff --git a/apps/els_dap/src/els_dap_general_provider.erl b/apps/els_dap/src/els_dap_general_provider.erl
index da3ee1888..46a9b0878 100644
--- a/apps/els_dap/src/els_dap_general_provider.erl
+++ b/apps/els_dap/src/els_dap_general_provider.erl
@@ -1023,22 +1023,6 @@ safe_eval(ProjectNode, Debugged, Expression, Update) ->
end,
Return.
--spec check_project_node_name(binary(), boolean()) -> atom().
-check_project_node_name(ProjectNode, false) ->
- binary_to_atom(ProjectNode, utf8);
-check_project_node_name(ProjectNode, true) ->
- case binary:match(ProjectNode, <<"@">>) of
- nomatch ->
- {ok, HostName} = inet:gethostname(),
- BinHostName = list_to_binary(HostName),
- DomainStr = proplists:get_value(domain, inet:get_rc(), ""),
- Domain = list_to_binary(DomainStr),
- BinName = <>,
- binary_to_atom(BinName, utf8);
- _ ->
- binary_to_atom(ProjectNode, utf8)
- end.
-
-spec start_distribution(map()) -> {ok, map()} | {error, any()}.
start_distribution(Params) ->
#{<<"cwd">> := Cwd} = Params,
@@ -1062,10 +1046,6 @@ start_distribution(Params) ->
<<"cookie">> := ConfCookie,
<<"use_long_names">> := UseLongNames
} = Config,
- ConfProjectNode = check_project_node_name(RawProjectNode, UseLongNames),
- ?LOG_INFO("Configured Project Node Name: ~p", [ConfProjectNode]),
- Cookie = binary_to_atom(ConfCookie, utf8),
-
NameType =
case UseLongNames of
true ->
@@ -1073,12 +1053,14 @@ start_distribution(Params) ->
false ->
shortnames
end,
+
+ ConfProjectNode0 = binary_to_list(RawProjectNode),
+ ConfProjectNode = els_utils:compose_node_name(ConfProjectNode0, NameType),
+ ?LOG_INFO("Configured Project Node Name: ~p", [ConfProjectNode]),
+ Cookie = binary_to_atom(ConfCookie, utf8),
+
%% start distribution
- Prefix = <<"erlang_ls_dap">>,
- Int = erlang:phash2(erlang:timestamp()),
- Id = lists:flatten(io_lib:format("~s_~s_~p", [Prefix, Name, Int])),
- {ok, HostName} = inet:gethostname(),
- LocalNode = els_distribution_server:node_name(Id, HostName, NameType),
+ LocalNode = els_distribution_server:node_name(<<"erlang_ls_dap">>, Name),
case
els_distribution_server:start_distribution(
LocalNode,
diff --git a/apps/els_lsp/test/els_diagnostics_SUITE.erl b/apps/els_lsp/test/els_diagnostics_SUITE.erl
index 5e048ac9a..618e769cd 100644
--- a/apps/els_lsp/test/els_diagnostics_SUITE.erl
+++ b/apps/els_lsp/test/els_diagnostics_SUITE.erl
@@ -28,6 +28,8 @@
compiler_telemetry/1,
code_path_extra_dirs/1,
use_long_names/1,
+ use_long_names_no_domain/1,
+ use_long_names_custom_hostname/1,
epp_with_nonexistent_macro/1,
code_reload/1,
code_reload_sticky_mod/1,
@@ -120,15 +122,20 @@ init_per_testcase(code_path_extra_dirs, Config) ->
els_mock_diagnostics:setup(),
els_test_utils:init_per_testcase(code_path_extra_dirs, Config);
init_per_testcase(use_long_names, Config) ->
- meck:new(yamerl, [passthrough, no_link]),
+ Content =
+ <<"runtime:\n", " use_long_names: true\n", " cookie: mycookie\n",
+ " node_name: my_node\n", " domain: test.local">>,
+ init_long_names_config(Content, Config);
+init_per_testcase(use_long_names_no_domain, Config) ->
Content =
<<"runtime:\n", " use_long_names: true\n", " cookie: mycookie\n",
" node_name: my_node\n">>,
- meck:expect(yamerl, decode_file, 2, fun(_, Opts) ->
- yamerl:decode(Content, Opts)
- end),
- els_mock_diagnostics:setup(),
- els_test_utils:init_per_testcase(code_path_extra_dirs, Config);
+ init_long_names_config(Content, Config);
+init_per_testcase(use_long_names_custom_hostname, Config) ->
+ Content =
+ <<"runtime:\n", " use_long_names: true\n", " cookie: mycookie\n",
+ " node_name: my_node\n", " hostname: 127.0.0.1">>,
+ init_long_names_config(Content, Config);
init_per_testcase(exclude_unused_includes = TestCase, Config) ->
els_mock_diagnostics:setup(),
NewConfig = els_test_utils:init_per_testcase(TestCase, Config),
@@ -224,7 +231,9 @@ end_per_testcase(TestCase, Config) when
ok;
end_per_testcase(TestCase, Config) when
TestCase =:= code_path_extra_dirs orelse
- TestCase =:= use_long_names
+ TestCase =:= use_long_names orelse
+ TestCase =:= use_long_names_no_domain orelse
+ TestCase =:= use_long_names_custom_hostname
->
meck:unload(yamerl),
els_test_utils:end_per_testcase(code_path_extra_dirs, Config),
@@ -271,6 +280,15 @@ end_per_testcase(TestCase, Config) ->
els_mock_diagnostics:teardown(),
ok.
+-spec init_long_names_config(binary(), config()) -> config().
+init_long_names_config(Content, Config) ->
+ meck:new(yamerl, [passthrough, no_link]),
+ meck:expect(yamerl, decode_file, 2, fun(_, Opts) ->
+ yamerl:decode(Content, Opts)
+ end),
+ els_mock_diagnostics:setup(),
+ els_test_utils:init_per_testcase(code_path_extra_dirs, Config).
+
% RefactorErl
%%==============================================================================
@@ -626,12 +644,30 @@ code_path_extra_dirs(_Config) ->
-spec use_long_names(config()) -> ok.
use_long_names(_Config) ->
- {ok, HostName} = inet:gethostname(),
+ HostName = els_config_runtime:get_hostname(),
NodeName =
"my_node@" ++
HostName ++ "." ++
- proplists:get_value(domain, inet:get_rc(), ""),
+ els_config_runtime:get_domain(),
+ Node = list_to_atom(NodeName),
+ ?assertMatch(Node, els_config_runtime:get_node_name()),
+ ok.
+
+-spec use_long_names_no_domain(config()) -> ok.
+use_long_names_no_domain(_Config) ->
+ HostName = els_config_runtime:get_hostname(),
+ NodeName =
+ "my_node@" ++ HostName,
+ Node = list_to_atom(NodeName),
+ ?assertMatch(Node, els_config_runtime:get_node_name()),
+ ok.
+
+-spec use_long_names_custom_hostname(config()) -> ok.
+use_long_names_custom_hostname(_Config) ->
+ HostName = els_config_runtime:get_hostname(),
+ NodeName = "my_node@127.0.0.1",
Node = list_to_atom(NodeName),
+ ?assertMatch(HostName, "127.0.0.1"),
?assertMatch(Node, els_config_runtime:get_node_name()),
ok.
From f1525fc5413d469c8892384f08a458d0f6fc2879 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andreas=20L=C3=B6scher?=
Date: Thu, 15 Sep 2022 18:13:36 +0100
Subject: [PATCH 112/239] [dap] support non-verified breakpoints (#1374)
* [dap] support non-verified breakpoints in-case the module isn't available in the debugged node
* fix typos and addressed comments
Co-authored-by: loscher
---
apps/els_dap/src/els_dap_breakpoints.erl | 35 ++++--
apps/els_dap/src/els_dap_general_provider.erl | 113 +++++++++++-------
apps/els_dap/src/els_dap_rpc.erl | 7 ++
.../test/els_dap_general_provider_SUITE.erl | 21 +++-
4 files changed, 121 insertions(+), 55 deletions(-)
diff --git a/apps/els_dap/src/els_dap_breakpoints.erl b/apps/els_dap/src/els_dap_breakpoints.erl
index fb2b7c741..efdc2b3e9 100644
--- a/apps/els_dap/src/els_dap_breakpoints.erl
+++ b/apps/els_dap/src/els_dap_breakpoints.erl
@@ -3,8 +3,8 @@
build_source_breakpoints/1,
get_function_breaks/2,
get_line_breaks/2,
- do_line_breakpoints/4,
- do_function_breaks/4,
+ do_line_breakpoints/5,
+ do_function_breaks/5,
type/3
]).
@@ -108,21 +108,27 @@ get_function_breaks(Module, Breaks) ->
get_line_breaks(Module, Breaks) ->
case Breaks of
#{Module := #{line := Lines}} -> Lines;
- _ -> []
+ _ -> #{}
end.
-spec do_line_breakpoints(
node(),
module(),
#{line() => line_breaks()},
- breakpoints()
+ breakpoints(),
+ boolean()
) ->
breakpoints().
-do_line_breakpoints(Node, Module, LineBreakPoints, Breaks) ->
- maps:map(
- fun(Line, _) -> els_dap_rpc:break(Node, Module, Line) end,
- LineBreakPoints
- ),
+do_line_breakpoints(Node, Module, LineBreakPoints, Breaks, Set) ->
+ case Set of
+ true ->
+ maps:map(
+ fun(Line, _) -> els_dap_rpc:break(Node, Module, Line) end,
+ LineBreakPoints
+ );
+ false ->
+ ok
+ end,
case Breaks of
#{Module := ModBreaks} ->
Breaks#{Module => ModBreaks#{line => LineBreakPoints}};
@@ -130,10 +136,15 @@ do_line_breakpoints(Node, Module, LineBreakPoints, Breaks) ->
Breaks#{Module => #{line => LineBreakPoints, function => []}}
end.
--spec do_function_breaks(node(), module(), [function_break()], breakpoints()) ->
+-spec do_function_breaks(node(), module(), [function_break()], breakpoints(), boolean()) ->
breakpoints().
-do_function_breaks(Node, Module, FBreaks, Breaks) ->
- [els_dap_rpc:break_in(Node, Module, Func, Arity) || {Func, Arity} <- FBreaks],
+do_function_breaks(Node, Module, FBreaks, Breaks, Set) ->
+ case Set of
+ true ->
+ [els_dap_rpc:break_in(Node, Module, Func, Arity) || {Func, Arity} <- FBreaks];
+ false ->
+ ok
+ end,
case Breaks of
#{Module := ModBreaks} ->
Breaks#{Module => ModBreaks#{function => FBreaks}};
diff --git a/apps/els_dap/src/els_dap_general_provider.erl b/apps/els_dap/src/els_dap_general_provider.erl
index 46a9b0878..469a5f9bf 100644
--- a/apps/els_dap/src/els_dap_general_provider.erl
+++ b/apps/els_dap/src/els_dap_general_provider.erl
@@ -192,20 +192,20 @@ handle_request(
ensure_connected(ProjectNode, Timeout),
{Module, LineBreaks} = els_dap_breakpoints:build_source_breakpoints(Params),
- {module, Module} = els_dap_rpc:i(ProjectNode, Module),
+ {IsModuleAvailable, Message} = maybe_interpret_and_clear_module(ProjectNode, Module),
- %% purge all breakpoints from the module
- els_dap_rpc:no_break(ProjectNode, Module),
Breakpoints1 =
els_dap_breakpoints:do_line_breakpoints(
ProjectNode,
Module,
LineBreaks,
- Breakpoints0
+ Breakpoints0,
+ IsModuleAvailable
),
+
BreakpointsRsps = [
- #{<<"verified">> => true, <<"line">> => Line}
- || {{_, Line}, _} <- els_dap_rpc:all_breaks(ProjectNode, Module)
+ #{<<"verified">> => IsModuleAvailable, <<"line">> => Line, message => Message}
+ || Line <- maps:keys(LineBreaks)
],
FunctionBreaks =
@@ -215,7 +215,8 @@ handle_request(
ProjectNode,
Module,
FunctionBreaks,
- Breakpoints1
+ Breakpoints1,
+ IsModuleAvailable
),
{#{<<"breakpoints">> => BreakpointsRsps}, State#{breakpoints => Breakpoints2}};
@@ -232,11 +233,7 @@ handle_request(
ensure_connected(ProjectNode, Timeout),
FunctionBreakPoints = maps:get(<<"breakpoints">>, Params, []),
MFAs = [
- begin
- Spec = {Mod, _, _} = parse_mfa(MFA),
- els_dap_rpc:i(ProjectNode, Mod),
- Spec
- end
+ parse_mfa(MFA)
|| #{<<"name">> := MFA, <<"enabled">> := Enabled} <- FunctionBreakPoints,
Enabled andalso parse_mfa(MFA) =/= error
],
@@ -252,54 +249,64 @@ handle_request(
MFAs
),
+ %% we need to really purge all break points here
els_dap_rpc:no_break(ProjectNode),
- Breakpoints1 = maps:fold(
- fun(Mod, Breaks, Acc) ->
- Acc#{Mod => Breaks#{function => []}}
- end,
- #{},
- Breakpoints0
- ),
- Breakpoints2 = maps:fold(
- fun(Module, FunctionBreaks, Acc) ->
- els_dap_breakpoints:do_function_breaks(
- ProjectNode,
- Module,
- FunctionBreaks,
- Acc
- )
- end,
- Breakpoints1,
- ModFuncBreaks
- ),
+ {Breakpoints1, VerifiedMessage} =
+ maps:fold(
+ fun(Module, FunctionBreaks, {AccBP, AccVerified}) ->
+ {IsModuleAvailable, Message} =
+ maybe_interpret_and_clear_module(ProjectNode, Module),
+ {
+ els_dap_breakpoints:do_function_breaks(
+ ProjectNode,
+ Module,
+ FunctionBreaks,
+ AccBP#{
+ Module => #{function => []}
+ },
+ IsModuleAvailable
+ ),
+ AccVerified#{Module => {IsModuleAvailable, Message}}
+ }
+ end,
+ {Breakpoints0, #{}},
+ ModFuncBreaks
+ ),
+
BreakpointsRsps = [
#{
- <<"verified">> => true,
- <<"line">> => Line,
- <<"source">> => #{<<"path">> => source(Module, ProjectNode)}
+ <<"verified">> => Verified,
+ <<"message">> => Message,
+ <<"source">> => #{
+ <<"path">> =>
+ case Verified of
+ true -> source(Module, ProjectNode);
+ false -> <<"">>
+ end
+ }
}
- || {{Module, Line}, [Status, _, _, _]} <-
- els_dap_rpc:all_breaks(ProjectNode),
- Status =:= active
+ || {Module, {Verified, Message}} <- maps:to_list(VerifiedMessage)
],
%% replay line breaks
- Breakpoints3 = maps:fold(
+ Breakpoints2 = maps:fold(
fun(Module, _, Acc) ->
+ Set = true =:= els_dap_rpc:interpretable(ProjectNode, Module),
Lines = els_dap_breakpoints:get_line_breaks(Module, Acc),
els_dap_breakpoints:do_line_breakpoints(
ProjectNode,
Module,
Lines,
- Acc
+ Acc,
+ Set
)
end,
- Breakpoints2,
- Breakpoints2
+ Breakpoints1,
+ Breakpoints1
),
- {#{<<"breakpoints">> => BreakpointsRsps}, State#{breakpoints => Breakpoints3}};
+ {#{<<"breakpoints">> => BreakpointsRsps}, State#{breakpoints => Breakpoints2}};
handle_request({<<"threads">>, _Params}, #{threads := Threads0} = State) ->
Threads =
[
@@ -1084,3 +1091,25 @@ distribution_error(Error) ->
io_lib:format("Could not start Erlang distribution. ~p", [Error])
)
).
+
+-spec maybe_interpret_and_clear_module(node(), module()) -> {boolean(), binary()}.
+maybe_interpret_and_clear_module(ProjectNode, Module) ->
+ case els_dap_rpc:interpretable(ProjectNode, Module) of
+ true ->
+ {module, Module} = els_dap_rpc:i(ProjectNode, Module),
+
+ %% purge all breakpoints from the module
+ els_dap_rpc:no_break(ProjectNode, Module),
+ {true, <<"">>};
+ {error, Reason} ->
+ Msg = unicode:characters_to_binary(
+ io_lib:format(
+ <<
+ "module not available (~p) in the debugged node, "
+ "reset the breakpoint when the module is availalbe"
+ >>,
+ [Reason]
+ )
+ ),
+ {false, Msg}
+ end.
diff --git a/apps/els_dap/src/els_dap_rpc.erl b/apps/els_dap/src/els_dap_rpc.erl
index 91027e45c..da342e47e 100644
--- a/apps/els_dap/src/els_dap_rpc.erl
+++ b/apps/els_dap/src/els_dap_rpc.erl
@@ -15,6 +15,7 @@
get_meta/2,
halt/1,
i/2,
+ interpretable/2,
load_binary/4,
meta/4,
meta_eval/3,
@@ -106,6 +107,12 @@ halt(Node) ->
i(Node, Module) ->
rpc:call(Node, int, i, [Module]).
+-spec interpretable(node(), module() | string()) ->
+ true
+ | {error, no_src | no_beam | no_debug_info | badarg | {app, kernel | stdlib | gs | debugger}}.
+interpretable(Node, AbsModule) ->
+ rpc:call(Node, int, interpretable, [AbsModule]).
+
-spec load_binary(node(), module(), string(), binary()) -> any().
load_binary(Node, Module, File, Bin) ->
rpc:call(Node, code, load_binary, [Module, File, Bin]).
diff --git a/apps/els_dap/test/els_dap_general_provider_SUITE.erl b/apps/els_dap/test/els_dap_general_provider_SUITE.erl
index bf0fb8d6c..80851a13c 100644
--- a/apps/els_dap/test/els_dap_general_provider_SUITE.erl
+++ b/apps/els_dap/test/els_dap_general_provider_SUITE.erl
@@ -478,6 +478,16 @@ breakpoints(Config) ->
)
),
?assertMatch([{{els_dap_test_module, 9}, _}], els_dap_rpc:all_breaks(Node)),
+
+ els_dap_provider:handle_request(
+ Provider,
+ request_set_breakpoints(
+ path_to_test_module(DataDir, i_dont_exist),
+ [42]
+ )
+ ),
+ ?assertMatch([{{els_dap_test_module, 9}, _}], els_dap_rpc:all_breaks(Node)),
+
els_dap_provider:handle_request(
Provider,
request_set_function_breakpoints([<<"els_dap_test_module:entry/1">>])
@@ -508,7 +518,16 @@ breakpoints(Config) ->
Provider,
request_set_function_breakpoints([])
),
- ?assertMatch([{{els_dap_test_module, 9}, _}], els_dap_rpc:all_breaks(Node)),
+ ?assertMatch(
+ [{{els_dap_test_module, 9}, _}], els_dap_rpc:all_breaks(Node)
+ ),
+ els_dap_provider:handle_request(
+ Provider,
+ request_set_function_breakpoints([<<"i_dont:exist/42">>])
+ ),
+ ?assertMatch(
+ [{{els_dap_test_module, 9}, _}], els_dap_rpc:all_breaks(Node)
+ ),
ok.
-spec project_node_exit(config()) -> ok.
From d5adfbd4ca735bc2dc8d0c96b5a3638647f2ca69 Mon Sep 17 00:00:00 2001
From: Roberto Aloi
Date: Mon, 19 Sep 2022 10:21:56 +0200
Subject: [PATCH 113/239] Find references asynchronously (#1380)
---
apps/els_lsp/src/els_definition_provider.erl | 2 +-
apps/els_lsp/src/els_methods.erl | 14 +++++----
apps/els_lsp/src/els_references_provider.erl | 31 ++++++++++++++++++--
3 files changed, 37 insertions(+), 10 deletions(-)
diff --git a/apps/els_lsp/src/els_definition_provider.erl b/apps/els_lsp/src/els_definition_provider.erl
index f28dba876..2a1855c2c 100644
--- a/apps/els_lsp/src/els_definition_provider.erl
+++ b/apps/els_lsp/src/els_definition_provider.erl
@@ -11,7 +11,7 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec handle_request(any()) -> {response, any()}.
+-spec handle_request(any()) -> {response, any()} | {async, uri(), pid()}.
handle_request({definition, Params}) ->
#{
<<"position">> := #{
diff --git a/apps/els_lsp/src/els_methods.erl b/apps/els_lsp/src/els_methods.erl
index 08f70f359..65f1eb917 100644
--- a/apps/els_lsp/src/els_methods.erl
+++ b/apps/els_lsp/src/els_methods.erl
@@ -306,9 +306,12 @@ completionitem_resolve(Params, State) ->
-spec textdocument_definition(params(), els_server:state()) -> result().
textdocument_definition(Params, State) ->
Provider = els_definition_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {definition, Params}),
- {response, Response, State}.
+ case els_provider:handle_request(Provider, {definition, Params}) of
+ {response, Response} ->
+ {response, Response, State};
+ {async, Uri, Job} ->
+ {async, Uri, Job, State}
+ end.
%%==============================================================================
%% textDocument/references
@@ -317,9 +320,8 @@ textdocument_definition(Params, State) ->
-spec textdocument_references(params(), els_server:state()) -> result().
textdocument_references(Params, State) ->
Provider = els_references_provider,
- {response, Response} =
- els_provider:handle_request(Provider, {references, Params}),
- {response, Response, State}.
+ {async, Uri, Job} = els_provider:handle_request(Provider, {references, Params}),
+ {async, Uri, Job, State}.
%%==============================================================================
%% textDocument/documentHightlight
diff --git a/apps/els_lsp/src/els_references_provider.erl b/apps/els_lsp/src/els_references_provider.erl
index e7c164a8f..c131d7929 100644
--- a/apps/els_lsp/src/els_references_provider.erl
+++ b/apps/els_lsp/src/els_references_provider.erl
@@ -17,6 +17,7 @@
%% Includes
%%==============================================================================
-include("els_lsp.hrl").
+-include_lib("kernel/include/logger.hrl").
%%==============================================================================
%% Types
@@ -25,7 +26,7 @@
%%==============================================================================
%% els_provider functions
%%==============================================================================
--spec handle_request(any()) -> {response, [location()] | null}.
+-spec handle_request(any()) -> {async, uri(), pid()}.
handle_request({references, Params}) ->
#{
<<"position">> := #{
@@ -34,6 +35,30 @@ handle_request({references, Params}) ->
},
<<"textDocument">> := #{<<"uri">> := Uri}
} = Params,
+ ?LOG_DEBUG(
+ "Starting references job " "[uri=~p, line=~p, character=~p]",
+ [Uri, Line, Character]
+ ),
+ Job = run_references_job(Uri, Line, Character),
+ {async, Uri, Job}.
+
+-spec run_references_job(uri(), line(), column()) -> pid().
+run_references_job(Uri, Line, Character) ->
+ Config = #{
+ task => fun get_references/2,
+ entries => [{Uri, Line, Character}],
+ title => <<"References">>,
+ on_complete =>
+ fun(ReferencesResp) ->
+ els_server ! {result, ReferencesResp, self()},
+ ok
+ end
+ },
+ {ok, Pid} = els_background_job:new(Config),
+ Pid.
+
+-spec get_references({uri(), integer(), integer()}, _) -> null | [location()].
+get_references({Uri, Line, Character}, _) ->
{ok, Document} = els_utils:lookup_document(Uri),
Refs =
case els_dt_document:get_element_at_pos(Document, Line + 1, Character + 1) of
@@ -41,8 +66,8 @@ handle_request({references, Params}) ->
[] -> []
end,
case Refs of
- [] -> {response, null};
- Rs -> {response, Rs}
+ [] -> null;
+ Rs -> Rs
end.
%%==============================================================================
From f29bc459615d0bd7a711016d6d39acde4f418e58 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Thu, 22 Sep 2022 17:26:48 +0200
Subject: [PATCH 114/239] [#1290] Fix error when parsing record containing a
comment (#1382)
---
apps/els_lsp/src/els_parser.erl | 8 ++++++--
apps/els_lsp/test/els_parser_SUITE.erl | 13 ++++++++++++-
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/apps/els_lsp/src/els_parser.erl b/apps/els_lsp/src/els_parser.erl
index 0bc454014..c460cf616 100644
--- a/apps/els_lsp/src/els_parser.erl
+++ b/apps/els_lsp/src/els_parser.erl
@@ -851,7 +851,9 @@ record_field_name(FieldNode, Record, Kind) ->
record_field ->
erl_syntax:record_field_name(FieldNode);
record_type_field ->
- erl_syntax:record_type_field_name(FieldNode)
+ erl_syntax:record_type_field_name(FieldNode);
+ comment ->
+ undefined
end,
case is_atom_node(NameNode) of
{true, NameAtom} ->
@@ -968,7 +970,9 @@ macro_name(Tree) ->
macro_name(Name, none) -> node_name(Name);
macro_name(Name, Args) -> {node_name(Name), length(Args)}.
--spec is_atom_node(tree()) -> {true, atom()} | false.
+-spec is_atom_node(tree() | undefined) -> {true, atom()} | false.
+is_atom_node(undefined) ->
+ false;
is_atom_node(Tree) ->
case erl_syntax:type(Tree) of
atom ->
diff --git a/apps/els_lsp/test/els_parser_SUITE.erl b/apps/els_lsp/test/els_parser_SUITE.erl
index e7eb61527..52e783266 100644
--- a/apps/els_lsp/test/els_parser_SUITE.erl
+++ b/apps/els_lsp/test/els_parser_SUITE.erl
@@ -32,7 +32,8 @@
record_def_recursive/1,
var_in_application/1,
unicode_clause_pattern/1,
- latin1_source_code/1
+ latin1_source_code/1,
+ record_comment/1
]).
%%==============================================================================
@@ -452,6 +453,16 @@ latin1_source_code(_Config) ->
),
ok.
+%% Issue #1290 Parsing error
+-spec record_comment(config()) -> ok.
+record_comment(_Config) ->
+ Text = <<"#my_record{\n%% TODO\n}.">>,
+ ?assertMatch(
+ [#{id := my_record}],
+ parse_find_pois(Text, record_expr)
+ ),
+ ok.
+
%%==============================================================================
%% Helper functions
%%==============================================================================
From 4c5e65e52f0534dd4a8b92c678e590de39f76897 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5kan=20Nilsson?=
Date: Mon, 3 Oct 2022 13:24:27 +0200
Subject: [PATCH 115/239] [#1360] Support completion of record fields inside
#record{} (#1381)
* Automatic triggering when cursor is at #record{| and #record{... , |
* Invoked completion on record field inside record #record{field|
---
apps/els_core/src/els_text.erl | 12 ++
apps/els_core/src/els_utils.erl | 16 ++
.../src/completion_records.erl | 10 ++
apps/els_lsp/src/els_completion_provider.erl | 146 ++++++++++++++++--
apps/els_lsp/src/els_scope.erl | 7 +-
apps/els_lsp/test/els_completion_SUITE.erl | 100 ++++++++++++
6 files changed, 278 insertions(+), 13 deletions(-)
create mode 100644 apps/els_lsp/priv/code_navigation/src/completion_records.erl
diff --git a/apps/els_core/src/els_text.erl b/apps/els_core/src/els_text.erl
index 0d30da9fd..98b2120ae 100644
--- a/apps/els_core/src/els_text.erl
+++ b/apps/els_core/src/els_text.erl
@@ -12,6 +12,7 @@
tokens/1,
apply_edits/2
]).
+-export([strip_comments/1]).
-export_type([edit/0]).
@@ -132,6 +133,17 @@ ensure_string(Text) when is_binary(Text) ->
ensure_string(Text) ->
Text.
+-spec strip_comments(binary()) -> binary().
+strip_comments(Text) ->
+ lines_to_bin(
+ lists:map(
+ fun(Line) ->
+ hd(string:split(Line, "%"))
+ end,
+ bin_to_lines(Text)
+ )
+ ).
+
%%==============================================================================
%% Internal functions
%%==============================================================================
diff --git a/apps/els_core/src/els_utils.erl b/apps/els_core/src/els_utils.erl
index 242ee2589..c96aa80ea 100644
--- a/apps/els_core/src/els_utils.erl
+++ b/apps/els_core/src/els_utils.erl
@@ -22,6 +22,7 @@
base64_encode_term/1,
base64_decode_term/1,
levenshtein_distance/2,
+ camel_case/1,
jaro_distance/2,
is_windows/0,
system_tmp_dir/0
@@ -487,6 +488,13 @@ base64_encode_term(Term) ->
base64_decode_term(Base64) ->
binary_to_term(base64:decode(Base64)).
+-spec camel_case(binary() | string()) -> binary().
+camel_case(Str0) ->
+ %% Remove ''
+ Str = string:trim(Str0, both, "'"),
+ Words = [string:titlecase(Word) || Word <- string:lexemes(Str, "_")],
+ iolist_to_binary(Words).
+
-spec levenshtein_distance(binary(), binary()) -> integer().
levenshtein_distance(S, T) ->
{Distance, _} = levenshtein_distance(to_list(S), to_list(T), #{}),
@@ -717,4 +725,12 @@ jaro_distance_test_() ->
)
].
+camel_case_test() ->
+ ?assertEqual(<<"">>, camel_case(<<"">>)),
+ ?assertEqual(<<"F">>, camel_case(<<"f">>)),
+ ?assertEqual(<<"Foo">>, camel_case(<<"foo">>)),
+ ?assertEqual(<<"FooBar">>, camel_case(<<"foo_bar">>)),
+ ?assertEqual(<<"FooBarBaz">>, camel_case(<<"foo_bar_baz">>)),
+ ?assertEqual(<<"FooBarBaz">>, camel_case(<<"'foo_bar_baz'">>)).
+
-endif.
diff --git a/apps/els_lsp/priv/code_navigation/src/completion_records.erl b/apps/els_lsp/priv/code_navigation/src/completion_records.erl
new file mode 100644
index 000000000..4fe4ad6a9
--- /dev/null
+++ b/apps/els_lsp/priv/code_navigation/src/completion_records.erl
@@ -0,0 +1,10 @@
+-module(completion_records).
+
+-record(record_a, {field_a, field_b, 'Field C'}).
+-record(record_b, {field_x, field_y}).
+
+function_a(#record_a{field_a = a, field_b = b}) ->
+ #record_b{field_x = #record_a{},
+ %% #record_a{
+ field_y = y},
+ {}.
diff --git a/apps/els_lsp/src/els_completion_provider.erl b/apps/els_lsp/src/els_completion_provider.erl
index 3f32a1e43..ed4e9e8b4 100644
--- a/apps/els_lsp/src/els_completion_provider.erl
+++ b/apps/els_lsp/src/els_completion_provider.erl
@@ -31,7 +31,16 @@
%%==============================================================================
-spec trigger_characters() -> [binary()].
trigger_characters() ->
- [<<":">>, <<"#">>, <<"?">>, <<".">>, <<"-">>, <<"\"">>].
+ [
+ <<":">>,
+ <<"#">>,
+ <<"?">>,
+ <<".">>,
+ <<"-">>,
+ <<"\"">>,
+ <<"{">>,
+ <<" ">>
+ ].
-spec handle_request(els_provider:provider_request()) -> {response, any()}.
handle_request({completion, Params}) ->
@@ -156,6 +165,28 @@ find_completions(
#{trigger := <<"#">>, document := Document}
) ->
definitions(Document, record);
+find_completions(
+ Prefix,
+ ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ #{trigger := <<"{">>, document := Document}
+) ->
+ case lists:reverse(els_text:tokens(string:trim(Prefix))) of
+ [{atom, _, Name} | _] ->
+ record_fields_with_var(Document, Name);
+ _ ->
+ []
+ end;
+find_completions(
+ Prefix,
+ ?COMPLETION_TRIGGER_KIND_CHARACTER,
+ #{trigger := <<" ">>} = Opts
+) ->
+ case lists:reverse(els_text:tokens(string:trim(Prefix))) of
+ [{',', _} | _] = Tokens ->
+ complete_record_field(Opts, Tokens);
+ _ ->
+ []
+ end;
find_completions(
<<"-include_lib(">>,
?COMPLETION_TRIGGER_KIND_CHARACTER,
@@ -186,7 +217,7 @@ find_completions(
document := Document,
line := Line,
column := Column
- }
+ } = Opts
) when
TriggerKind =:= ?COMPLETION_TRIGGER_KIND_INVOKED;
TriggerKind =:= ?COMPLETION_TRIGGER_KIND_FOR_INCOMPLETE_COMPLETIONS
@@ -224,6 +255,9 @@ find_completions(
%% Check for "[...] #anything"
[_, {'#', _} | _] ->
definitions(Document, record);
+ %% Check for "[...] #anything{"
+ [{'{', _}, {atom, _, RecordName}, {'#', _} | _] ->
+ record_fields_with_var(Document, RecordName);
%% Check for "[...] Variable"
[{var, _, _} | _] ->
variables(Document);
@@ -260,7 +294,7 @@ find_completions(
bifs(function, ExportFormat = true) ++
definitions(Document, function, ExportFormat = true);
%% Check for "[...] atom"
- [{atom, _, Name} | _] ->
+ [{atom, _, Name} | _] = Tokens ->
NameBinary = atom_to_binary(Name, utf8),
{ExportFormat, POIKind} = completion_context(Document, Line, Column),
case ExportFormat of
@@ -268,13 +302,18 @@ find_completions(
%% Only complete unexported definitions when in export
unexported_definitions(Document, POIKind);
false ->
- keywords() ++
- bifs(POIKind, ExportFormat) ++
- atoms(Document, NameBinary) ++
- all_record_fields(Document, NameBinary) ++
- modules(NameBinary) ++
- definitions(Document, POIKind, ExportFormat) ++
- els_snippets_server:snippets()
+ case complete_record_field(Opts, Tokens) of
+ [] ->
+ keywords() ++
+ bifs(POIKind, ExportFormat) ++
+ atoms(Document, NameBinary) ++
+ all_record_fields(Document, NameBinary) ++
+ modules(NameBinary) ++
+ definitions(Document, POIKind, ExportFormat) ++
+ els_snippets_server:snippets();
+ RecordFields ->
+ RecordFields
+ end
end;
Tokens ->
?LOG_DEBUG(
@@ -286,6 +325,51 @@ find_completions(
find_completions(_Prefix, _TriggerKind, _Opts) ->
[].
+-spec complete_record_field(map(), list()) -> items().
+complete_record_field(_Opts, [{atom, _, _}, {'=', _} | _]) ->
+ [];
+complete_record_field(
+ #{document := Document, line := Line, column := Col},
+ _Tokens
+) ->
+ complete_record_field(Document, {Line, Col}, <<"key=val}.">>).
+
+-spec complete_record_field(map(), pos(), binary()) -> items().
+complete_record_field(#{text := Text} = Document, Pos, Suffix) ->
+ T0 = els_text:range(Text, {1, 1}, Pos),
+ POIs = els_dt_document:pois(Document, [function]),
+ Line =
+ case els_scope:pois_before(POIs, #{from => Pos, to => Pos}) of
+ [#{range := #{to := {L, _}}} | _] ->
+ L;
+ _ ->
+ %% No function before
+ 1
+ end,
+ %% Just look at lines after last function
+ {_, T} = els_text:split_at_line(T0, Line),
+ case parse_record(els_text:strip_comments(T), Suffix) of
+ {ok, Id} ->
+ record_fields_with_var(Document, Id);
+ error ->
+ []
+ end.
+
+-spec parse_record(binary(), binary()) -> {ok, els_poi:poi_id()} | error.
+parse_record(Text, Suffix) ->
+ case string:split(Text, <<"#">>, trailing) of
+ [_] ->
+ error;
+ [Left, Right] ->
+ Str = <<"#", Right/binary, Suffix/binary>>,
+ case els_parser:parse(Str) of
+ {ok, [#{kind := record_expr, id := Id} | _]} ->
+ {ok, Id};
+ _ ->
+ parse_record(Left, Str)
+ end
+ end.
+
%%=============================================================================
%% Attributes
%%=============================================================================
@@ -685,6 +769,30 @@ record_fields(Document, RecordName) ->
]
end.
+-spec record_fields_with_var(els_dt_document:item(), atom()) -> [map()].
+record_fields_with_var(Document, RecordName) ->
+ case find_record_definition(Document, RecordName) of
+ [] ->
+ [];
+ POIs ->
+ [#{data := #{field_list := Fields}} | _] = els_poi:sort(POIs),
+ [
+ #{
+ label => atom_to_label(Name),
+ kind => ?COMPLETION_ITEM_KIND_FIELD,
+ insertText => format_record_field_with_var(Name),
+ insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET
+ }
+ || Name <- Fields
+ ]
+ end.
+
+-spec format_record_field_with_var(atom()) -> binary().
+format_record_field_with_var(Name) ->
+ Label = atom_to_label(Name),
+ Var = els_utils:camel_case(Label),
+ <