Skip to content

Commit c257f3a

Browse files
committed
Introduce browse code actions
* Browse elvis warnings * Browse compiler errors * Browse functions and types in otp docs or hex docs
1 parent ed1daaa commit c257f3a

10 files changed

+620
-36
lines changed

apps/els_core/include/els_core.hrl

+2
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,8 @@
581581
%%------------------------------------------------------------------------------
582582

583583
-define(CODE_ACTION_KIND_QUICKFIX, <<"quickfix">>).
584+
-define(CODE_ACTION_KIND_BROWSE, <<"browse">>).
585+
584586
-type code_action_kind() :: binary().
585587

586588
-type code_action_context() :: #{

apps/els_core/src/els_config.erl

+11-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
initialize/4,
88
get/1,
99
set/2,
10-
start_link/0
10+
start_link/0,
11+
is_dep/1
1112
]).
1213

1314
%% gen_server callbacks
@@ -584,6 +585,15 @@ expand_var(Bin, [{Var, Value} | RestEnv]) ->
584585
[Value, RestBin]
585586
end.
586587

588+
-spec is_dep(string()) -> boolean().
589+
is_dep(Path) ->
590+
lists:any(
591+
fun(DepPath) ->
592+
lists:prefix(DepPath, Path)
593+
end,
594+
els_config:get(deps_paths)
595+
).
596+
587597
-spec get_env() -> [{string(), string()}].
588598
-if(?OTP_RELEASE >= 24).
589599
get_env() ->

apps/els_core/src/els_uri.erl

+17-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
-export([
1212
module/1,
1313
path/1,
14-
uri/1
14+
uri/1,
15+
app/1
1516
]).
1617

1718
%%==============================================================================
@@ -26,6 +27,21 @@
2627
%%==============================================================================
2728
-include("els_core.hrl").
2829

30+
-spec app(uri() | [binary()]) -> {ok, atom()} | error.
31+
app(Uri) when is_binary(Uri) ->
32+
app(lists:reverse(filename:split(path(Uri))));
33+
app([]) ->
34+
error;
35+
app([_File, <<"src">>, AppBin0 | _]) ->
36+
case binary:split(AppBin0, <<"-">>) of
37+
[AppBin, _Vsn] ->
38+
{ok, binary_to_atom(AppBin)};
39+
[AppBin] ->
40+
{ok, binary_to_atom(AppBin)}
41+
end;
42+
app([_ | Rest]) ->
43+
app(Rest).
44+
2945
-spec module(uri()) -> atom().
3046
module(Uri) ->
3147
binary_to_atom(filename:basename(path(Uri), <<".erl">>), utf8).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-module(code_action_browse_docs).
2+
3+
-spec function_a(file:filename()) -> pid().
4+
function_e(L) ->
5+
lists:sort(L),
6+
self().
7+
8+
-spec function_b() -> my_dep_mod:my_type().
9+
function_f() ->
10+
my_dep_mod:my_function().

apps/els_lsp/src/els_code_action_provider.erl

+34-30
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ code_actions(Uri, Range, #{<<"diagnostics">> := Diagnostics}) ->
3434
lists:flatten([make_code_actions(Uri, D) || D <- Diagnostics]) ++
3535
wrangler_handler:get_code_actions(Uri, Range) ++
3636
els_code_actions:extract_function(Uri, Range) ++
37-
els_code_actions:bump_variables(Uri, Range)
37+
els_code_actions:bump_variables(Uri, Range) ++
38+
els_code_actions:browse_docs(Uri, Range)
3839
).
3940

4041
-spec make_code_actions(uri(), map()) -> [map()].
@@ -43,35 +44,38 @@ make_code_actions(
4344
#{<<"message">> := Message, <<"range">> := Range} = Diagnostic
4445
) ->
4546
Data = maps:get(<<"data">>, Diagnostic, <<>>),
46-
make_code_actions(
47-
[
48-
{"function (.*) is unused", fun els_code_actions:export_function/4},
49-
{"variable '(.*)' is unused", fun els_code_actions:ignore_variable/4},
50-
{"variable '(.*)' is unbound", fun els_code_actions:suggest_variable/4},
51-
{"undefined macro '(.*)'", fun els_code_actions:add_include_lib_macro/4},
52-
{"undefined macro '(.*)'", fun els_code_actions:define_macro/4},
53-
{"undefined macro '(.*)'", fun els_code_actions:suggest_macro/4},
54-
{"record (.*) undefined", fun els_code_actions:add_include_lib_record/4},
55-
{"record (.*) undefined", fun els_code_actions:define_record/4},
56-
{"record (.*) undefined", fun els_code_actions:suggest_record/4},
57-
{"field (.*) undefined in record (.*)", fun els_code_actions:suggest_record_field/4},
58-
{"Module name '(.*)' does not match file name '(.*)'",
59-
fun els_code_actions:fix_module_name/4},
60-
{"Unused macro: (.*)", fun els_code_actions:remove_macro/4},
61-
{"function (.*) undefined", fun els_code_actions:create_function/4},
62-
{"function (.*) undefined", fun els_code_actions:suggest_function/4},
63-
{"Cannot find definition for function (.*)", fun els_code_actions:suggest_function/4},
64-
{"Cannot find module (.*)", fun els_code_actions:suggest_module/4},
65-
{"Unused file: (.*)", fun els_code_actions:remove_unused/4},
66-
{"Atom typo\\? Did you mean: (.*)", fun els_code_actions:fix_atom_typo/4},
67-
{"undefined callback function (.*) \\\(behaviour '(.*)'\\\)",
68-
fun els_code_actions:undefined_callback/4}
69-
],
70-
Uri,
71-
Range,
72-
Data,
73-
Message
74-
).
47+
els_code_actions:browse_error(Diagnostic) ++
48+
make_code_actions(
49+
[
50+
{"function (.*) is unused", fun els_code_actions:export_function/4},
51+
{"variable '(.*)' is unused", fun els_code_actions:ignore_variable/4},
52+
{"variable '(.*)' is unbound", fun els_code_actions:suggest_variable/4},
53+
{"undefined macro '(.*)'", fun els_code_actions:add_include_lib_macro/4},
54+
{"undefined macro '(.*)'", fun els_code_actions:define_macro/4},
55+
{"undefined macro '(.*)'", fun els_code_actions:suggest_macro/4},
56+
{"record (.*) undefined", fun els_code_actions:add_include_lib_record/4},
57+
{"record (.*) undefined", fun els_code_actions:define_record/4},
58+
{"record (.*) undefined", fun els_code_actions:suggest_record/4},
59+
{"field (.*) undefined in record (.*)",
60+
fun els_code_actions:suggest_record_field/4},
61+
{"Module name '(.*)' does not match file name '(.*)'",
62+
fun els_code_actions:fix_module_name/4},
63+
{"Unused macro: (.*)", fun els_code_actions:remove_macro/4},
64+
{"function (.*) undefined", fun els_code_actions:create_function/4},
65+
{"function (.*) undefined", fun els_code_actions:suggest_function/4},
66+
{"Cannot find definition for function (.*)",
67+
fun els_code_actions:suggest_function/4},
68+
{"Cannot find module (.*)", fun els_code_actions:suggest_module/4},
69+
{"Unused file: (.*)", fun els_code_actions:remove_unused/4},
70+
{"Atom typo\\? Did you mean: (.*)", fun els_code_actions:fix_atom_typo/4},
71+
{"undefined callback function (.*) \\\(behaviour '(.*)'\\\)",
72+
fun els_code_actions:undefined_callback/4}
73+
],
74+
Uri,
75+
Range,
76+
Data,
77+
Message
78+
).
7579

7680
-spec make_code_actions([{string(), Fun}], uri(), range(), binary(), binary()) ->
7781
[map()]

apps/els_lsp/src/els_code_actions.erl

+122-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
suggest_record_field/4,
2020
suggest_function/4,
2121
suggest_module/4,
22-
bump_variables/2
22+
bump_variables/2,
23+
browse_error/1,
24+
browse_docs/2
2325
]).
2426

2527
-include("els_lsp.hrl").
@@ -577,6 +579,125 @@ undefined_callback(Uri, _Range, _Data, [_Function, Behaviour]) ->
577579
}
578580
].
579581

582+
-spec browse_docs(uri(), range()) -> [map()].
583+
browse_docs(Uri, Range) ->
584+
#{from := {Line, Column}} = els_range:to_poi_range(Range),
585+
{ok, Document} = els_utils:lookup_document(Uri),
586+
POIs = els_dt_document:get_element_at_pos(Document, Line, Column),
587+
lists:flatten([browse_docs(POI) || POI <- POIs]).
588+
589+
-spec browse_docs(els_poi:poi()) -> [map()].
590+
browse_docs(#{id := {M, F, A}, kind := Kind}) when
591+
Kind == application;
592+
Kind == type_application
593+
->
594+
case els_utils:find_module(M) of
595+
{ok, ModUri} ->
596+
case els_uri:app(ModUri) of
597+
{ok, App} ->
598+
DocType = doc_type(ModUri),
599+
make_browse_docs_command(DocType, {M, F, A}, App, Kind);
600+
error ->
601+
[]
602+
end;
603+
{error, not_found} ->
604+
[]
605+
end;
606+
browse_docs(_) ->
607+
[].
608+
609+
-spec doc_type(uri()) -> otp | hex | other.
610+
doc_type(Uri) ->
611+
Path = binary_to_list(els_uri:path(Uri)),
612+
OtpPath = els_config:get(otp_path),
613+
case lists:prefix(OtpPath, Path) of
614+
true ->
615+
otp;
616+
false ->
617+
case els_config:is_dep(Path) of
618+
true ->
619+
hex;
620+
false ->
621+
other
622+
end
623+
end.
624+
625+
-spec make_browse_docs_command(atom(), mfa(), atom(), atom()) ->
626+
[map()].
627+
make_browse_docs_command(other, _MFA, _App, _Kind) ->
628+
[];
629+
make_browse_docs_command(DocType, {M, F, A}, App, Kind) ->
630+
Title = make_browse_docs_title(DocType, {M, F, A}),
631+
[
632+
#{
633+
title => Title,
634+
kind => ?CODE_ACTION_KIND_BROWSE,
635+
command =>
636+
els_command:make_command(
637+
Title,
638+
<<"browse-docs">>,
639+
[
640+
#{
641+
source => DocType,
642+
module => M,
643+
function => F,
644+
arity => A,
645+
app => App,
646+
kind => els_dt_references:kind_to_category(Kind)
647+
}
648+
]
649+
)
650+
}
651+
].
652+
653+
-spec make_browse_docs_title(atom(), mfa()) -> binary().
654+
make_browse_docs_title(otp, {M, F, A}) ->
655+
list_to_binary(io_lib:format("Browse: OTP docs: ~p:~p/~p", [M, F, A]));
656+
make_browse_docs_title(hex, {M, F, A}) ->
657+
list_to_binary(io_lib:format("Browse: Hex docs: ~p:~p/~p", [M, F, A])).
658+
659+
-spec browse_error(map()) -> [map()].
660+
browse_error(#{<<"source">> := <<"Compiler">>, <<"code">> := ErrorCode}) ->
661+
Title = <<"Browse: Erlang Error Index: ", ErrorCode/binary>>,
662+
[
663+
#{
664+
title => Title,
665+
kind => ?CODE_ACTION_KIND_BROWSE,
666+
command =>
667+
els_command:make_command(
668+
Title,
669+
<<"browse-error">>,
670+
[
671+
#{
672+
source => <<"Compiler">>,
673+
code => ErrorCode
674+
}
675+
]
676+
)
677+
}
678+
];
679+
browse_error(#{<<"source">> := <<"Elvis">>, <<"code">> := ErrorCode}) ->
680+
Title = <<"Browse: Elvis rules: ", ErrorCode/binary>>,
681+
[
682+
#{
683+
title => Title,
684+
kind => ?CODE_ACTION_KIND_BROWSE,
685+
command =>
686+
els_command:make_command(
687+
Title,
688+
<<"browse-error">>,
689+
[
690+
#{
691+
source => <<"Elvis">>,
692+
code => ErrorCode
693+
}
694+
]
695+
)
696+
}
697+
];
698+
browse_error(_Diagnostic) ->
699+
[].
700+
580701
-spec ensure_range(els_poi:poi_range(), binary(), [els_poi:poi()]) ->
581702
{ok, els_poi:poi_range()} | error.
582703
ensure_range(#{from := {Line, _}}, SubjectId, POIs) ->

apps/els_lsp/src/els_dt_references.erl

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
find_by/1,
2525
find_by_id/2,
2626
insert/2,
27-
versioned_insert/2
27+
versioned_insert/2,
28+
kind_to_category/1
2829
]).
2930

3031
%%==============================================================================

0 commit comments

Comments
 (0)