Skip to content

Commit

Permalink
Add docs memoization
Browse files Browse the repository at this point in the history
  • Loading branch information
jdamanalo committed Dec 22, 2023
1 parent 3e54aae commit c545047
Show file tree
Hide file tree
Showing 12 changed files with 347 additions and 45 deletions.
2 changes: 2 additions & 0 deletions apps/els_core/src/els_config.erl
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
RefactorErl = maps:get("refactorerl", Config, notconfigured),
Providers = maps:get("providers", Config, #{}),
EdocParseEnabled = maps:get("edoc_parse_enabled", Config, true),
DocsMemo = maps:get("docs_memo", Config, false),

%% Initialize and start Wrangler
case maps:get("wrangler", Config, notconfigured) of
Expand Down Expand Up @@ -220,6 +221,7 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
ok = set(plt_path, DialyzerPltPath),
ok = set(code_reload, CodeReload),
ok = set(providers, Providers),
ok = set(docs_memo, DocsMemo),
?LOG_INFO("Config=~p", [Config]),
ok = set(
runtime,
Expand Down
6 changes: 6 additions & 0 deletions apps/els_lsp/priv/code_navigation/src/docs_memo.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-module(docs_memo).

-type type() -> any().

-spec function() -> ok.
function() -> ok.
3 changes: 2 additions & 1 deletion apps/els_lsp/src/els_db.erl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ tables() ->
els_dt_document,
els_dt_document_index,
els_dt_references,
els_dt_signatures
els_dt_signatures,
els_docs_memo
].

-spec delete(atom(), any()) -> ok.
Expand Down
90 changes: 60 additions & 30 deletions apps/els_lsp/src/els_docs.erl
Original file line number Diff line number Diff line change
Expand Up @@ -105,26 +105,7 @@ docs(_M, _POI) ->
function_docs(Type, M, F, A) ->
case edoc_parse_enabled() of
true ->
%% call via ?MODULE to enable mocking in tests
case ?MODULE:eep48_docs(function, M, F, A) of
{ok, Docs} ->
[{text, Docs}];
{error, not_available} ->
%% 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)
],
case lists:append(L) of
[] ->
[Sig];
Docs ->
[Sig, {text, "---"} | Docs]
end
end;
function_docs(Type, M, F, A, els_config:get(docs_memo));
false ->
Sig = {h2, signature(Type, M, F, A)},
L = [
Expand All @@ -139,22 +120,71 @@ function_docs(Type, M, F, A) ->
end
end.

-spec function_docs(application_type(), atom(), atom(), non_neg_integer(), boolean()) ->
[els_markup_content:doc_entry()].
function_docs(Type, M, F, A, true = _DocsMemo) ->
MFACT = {M, F, A, Type, function},
case els_docs_memo:lookup(MFACT) of
{ok, [#{entries := Entries}]} ->
Entries;
{ok, []} ->
Entries = function_docs(Type, M, F, A, false),
ok = els_docs_memo:insert(#{mfact => MFACT, entries => Entries}),
Entries
end;
function_docs(Type, M, F, A, false = _DocsMemo) ->
%% call via ?MODULE to enable mocking in tests
case ?MODULE:eep48_docs(function, M, F, A) of
{ok, Docs} ->
[{text, Docs}];
{error, not_available} ->
%% 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)
],
case lists:append(L) of
[] ->
[Sig];
Docs ->
[Sig, {text, "---"} | Docs]
end
end.

-spec type_docs(application_type(), atom(), atom(), non_neg_integer()) ->
[els_markup_content:doc_entry()].
type_docs(_Type, M, F, A) ->
type_docs(Type, M, F, A) ->
case edoc_parse_enabled() of
true ->
%% call via ?MODULE to enable mocking in tests
case ?MODULE:eep48_docs(type, M, F, A) of
{ok, Docs} ->
[{text, Docs}];
{error, not_available} ->
type(M, F, A)
end;
type_docs(Type, M, F, A, els_config:get(docs_memo));
false ->
type(M, F, A)
end.

-spec type_docs(application_type(), atom(), atom(), non_neg_integer(), boolean()) ->
[els_markup_content:doc_entry()].
type_docs(Type, M, F, A, true = _DocsMemo) ->
MFACT = {M, F, A, Type, type},
case els_docs_memo:lookup(MFACT) of
{ok, [#{entries := Entries}]} ->
Entries;
{ok, []} ->
Entries = type_docs(Type, M, F, A, false),
ok = els_docs_memo:insert(#{mfact => MFACT, entries => Entries}),
Entries
end;
type_docs(_Type, M, F, A, false = _DocsMemo) ->
%% call via ?MODULE to enable mocking in tests
case ?MODULE:eep48_docs(type, M, F, A) of
{ok, Docs} ->
[{text, Docs}];
{error, not_available} ->
type(M, F, A)
end.

-spec get_valuetext(uri(), map()) -> list().
get_valuetext(DefUri, #{from := From, to := To}) ->
{ok, #{text := Text}} = els_utils:lookup_document(DefUri),
Expand Down Expand Up @@ -306,8 +336,8 @@ get_edoc_chunk(M, Uri) ->
error
end.
-else.
-dialyzer({no_match, function_docs/4}).
-dialyzer({no_match, type_docs/4}).
-dialyzer({no_match, function_docs/5}).
-dialyzer({no_match, type_docs/5}).
-spec eep48_docs(function | type, atom(), atom(), non_neg_integer()) ->
{error, not_available}.
eep48_docs(_Type, _M, _F, _A) ->
Expand Down
106 changes: 106 additions & 0 deletions apps/els_lsp/src/els_docs_memo.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
%%==============================================================================
%% The 'docs' table
%%==============================================================================

-module(els_docs_memo).

%%==============================================================================
%% Behaviour els_db_table
%%==============================================================================

-behaviour(els_db_table).
-export([
name/0,
opts/0
]).

%%==============================================================================
%% API
%%==============================================================================

-export([
insert/1,
lookup/1,
delete_by_uri/1
]).

%%==============================================================================
%% Includes
%%==============================================================================
-include("els_lsp.hrl").
-include_lib("kernel/include/logger.hrl").
-include_lib("stdlib/include/ms_transform.hrl").

%%==============================================================================
%% Item Definition
%%==============================================================================

-record(els_docs_memo, {
mfact :: mfact() | '_' | {atom(), '_', '_', call_type() | '_', function | type | '_'},
entries :: [els_markup_content:doc_entry()] | '_'
}).
-type call_type() :: 'local' | 'remote'.
-type mfact() :: {module(), atom(), arity(), call_type(), function | type}.
-type els_docs_memo() :: #els_docs_memo{}.
-type item() :: #{
mfact := mfact(),
entries := [els_markup_content:doc_entry()]
}.
-export_type([item/0]).

%%==============================================================================
%% Callbacks for the els_db_table Behaviour
%%==============================================================================

-spec name() -> atom().
name() -> ?MODULE.

-spec opts() -> proplists:proplist().
opts() ->
[ordered_set].

%%==============================================================================
%% API
%%==============================================================================

-spec from_item(item()) -> els_docs_memo().
from_item(#{
mfact := MFACT,
entries := Entries
}) ->
#els_docs_memo{
mfact = MFACT,
entries = Entries
}.

-spec to_item(els_docs_memo()) -> item().
to_item(#els_docs_memo{
mfact = MFACT,
entries = Entries
}) ->
#{
mfact => MFACT,
entries => Entries
}.

-spec insert(item()) -> ok | {error, any()}.
insert(Map) when is_map(Map) ->
Record = from_item(Map),
els_db:write(name(), Record).

-spec lookup(mfact()) -> {ok, [item()]}.
lookup({M, _F, _A, _C, _T} = MFACT) ->
{ok, _Uris} = els_utils:find_modules(M),
{ok, Items} = els_db:lookup(name(), MFACT),
{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_docs_memo{mfact = {Module, '_', '_', '_', '_'}, _ = '_'},
ok = els_db:match_delete(name(), Pattern);
_ ->
ok
end.
28 changes: 16 additions & 12 deletions apps/els_lsp/src/els_hover_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ handle_request({hover, Params}) ->

-spec run_hover_job(uri(), line(), column()) -> pid().
run_hover_job(Uri, Line, Character) ->
{ok, Doc} = els_utils:lookup_document(Uri),
POIs = els_dt_document:get_element_at_pos(Doc, Line + 1, Character + 1),
Config = #{
task => fun get_docs/2,
entries => [{Uri, Line, Character}],
entries => [{Uri, POIs}],
title => <<"Hover">>,
on_complete =>
fun(HoverResp) ->
Expand All @@ -54,19 +56,21 @@ run_hover_job(Uri, Line, Character) ->
{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).
-spec get_docs({uri(), [els_poi:poi()]}, undefined) -> map() | null.
get_docs({Uri, POIs}, _) ->
Pid = self(),
spawn(fun() -> do_get_docs(Uri, POIs, Pid) end),
receive
HoverResp -> HoverResp
end.

-spec do_get_docs(uri(), [els_poi:poi()]) -> map() | null.
do_get_docs(_Uri, []) ->
null;
do_get_docs(Uri, [POI | Rest]) ->
-spec do_get_docs(uri(), [els_poi:poi()], pid()) -> map() | null.
do_get_docs(_Uri, [], Pid) ->
Pid ! null;
do_get_docs(Uri, [POI | Rest], Pid) ->
case els_docs:docs(Uri, POI) of
[] ->
do_get_docs(Uri, Rest);
do_get_docs(Uri, Rest, Pid);
Entries ->
#{contents => els_markup_content:new(Entries)}
Pid ! #{contents => els_markup_content:new(Entries)}
end.
6 changes: 5 additions & 1 deletion apps/els_lsp/src/els_text_synchronization.erl
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ did_open(Params) ->
ok.

-spec did_save(map()) -> ok.
did_save(_Params) ->
did_save(Params) ->
#{<<"textDocument">> := #{<<"uri">> := Uri}} = Params,
els_docs_memo:delete_by_uri(Uri),
ok.

-spec did_change_watched_files(map()) -> ok.
Expand Down Expand Up @@ -93,8 +95,10 @@ handle_file_change(Uri, Type) when
Type =:= ?FILE_CHANGE_TYPE_CREATED;
Type =:= ?FILE_CHANGE_TYPE_CHANGED
->
els_docs_memo:delete_by_uri(Uri),
reload_from_disk(Uri);
handle_file_change(Uri, Type) when Type =:= ?FILE_CHANGE_TYPE_DELETED ->
els_docs_memo:delete_by_uri(Uri),
els_indexing:remove(Uri).

-spec reload_from_disk(uri()) -> ok.
Expand Down
Loading

0 comments on commit c545047

Please sign in to comment.