Skip to content

Commit

Permalink
Add docs memoization
Browse files Browse the repository at this point in the history
  • Loading branch information
jdamanalo committed Jan 30, 2023
1 parent 1a66e87 commit 0e7577f
Show file tree
Hide file tree
Showing 12 changed files with 320 additions and 18 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 @@ -152,6 +152,7 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->

RefactorErl = maps:get("refactorerl", Config, notconfigured),
Providers = maps:get("providers", Config, #{}),
DocsMemo = maps:get("docs_memo", Config, false),

%% Initialize and start Wrangler
case maps:get("wrangler", Config, notconfigured) of
Expand Down Expand Up @@ -205,6 +206,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
36 changes: 33 additions & 3 deletions apps/els_lsp/src/els_docs.erl
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,21 @@ docs(_M, _POI) ->
-spec function_docs(application_type(), atom(), atom(), non_neg_integer()) ->
[els_markup_content:doc_entry()].
function_docs(Type, M, F, A) ->
function_docs(Type, M, F, A, els_config:get(docs_memo)).

-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) ->
case eep48_docs(function, M, F, A) of
{ok, Docs} ->
[{text, Docs}];
Expand All @@ -125,7 +140,22 @@ function_docs(Type, M, F, A) ->

-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) ->
type_docs(Type, M, F, A, els_config:get(docs_memo)).

-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) ->
case eep48_docs(type, M, F, A) of
{ok, Docs} ->
[{text, Docs}];
Expand Down Expand Up @@ -284,8 +314,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
126 changes: 126 additions & 0 deletions apps/els_lsp/test/els_docs_SUITE.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
-module(els_docs_SUITE).

-include("els_lsp.hrl").

%% CT Callbacks
-export([
init_per_suite/1,
end_per_suite/1,
init_per_testcase/2,
end_per_testcase/2,
all/0
]).

%% Test cases
-export([
memo_docs_true/1,
memo_docs_false/1,
invalidate/1
]).

%%==============================================================================
%% Includes
%%==============================================================================
-include_lib("common_test/include/ct.hrl").
-include_lib("stdlib/include/assert.hrl").

%%==============================================================================
%% Types
%%==============================================================================
-type config() :: [{atom(), any()}].

%%==============================================================================
%% CT Callbacks
%%==============================================================================
-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 memo_docs_true(config()) -> ok.
memo_docs_true(Config) ->
RootUri = els_test_utils:root_uri(),
DataDir = ?config(data_dir, Config),
ConfigPath = filename:join(DataDir, "docs_memo_true.config"),
InitOpts = #{<<"erlang">> => #{<<"config_path">> => ConfigPath}},
els_client:initialize(RootUri, InitOpts),

%% Function
MFACT1 = {M1 = docs_memo, F1 = function, A1 = 0, 'local', function},
Expected1 = els_docs:function_docs('local', M1, F1, A1),
{ok, [#{entries := Result1}]} = els_docs_memo:lookup(MFACT1),
?assertEqual(Expected1, Result1),

%% Type
MFACT2 = {M2 = docs_memo, F2 = type, A2 = 0, 'local', type},
Expected2 = els_docs:type_docs('local', M2, F2, A2),
{ok, [#{entries := Result2}]} = els_docs_memo:lookup(MFACT2),
?assertEqual(Expected2, Result2),

ok.

-spec memo_docs_false(config()) -> ok.
memo_docs_false(Config) ->
RootUri = els_test_utils:root_uri(),
DataDir = ?config(data_dir, Config),
ConfigPath = filename:join(DataDir, "docs_memo_false.config"),
InitOpts = #{<<"erlang">> => #{<<"config_path">> => ConfigPath}},
els_client:initialize(RootUri, InitOpts),

%% Function
MFACT1 = {M1 = docs_memo, F1 = function, A1 = 0, 'local', function},
els_docs:function_docs('local', M1, F1, A1),
{ok, []} = els_docs_memo:lookup(MFACT1),

%% Type
MFACT2 = {M2 = docs_memo, F2 = type, A2 = 0, 'local', type},
els_docs:type_docs('local', M2, F2, A2),
{ok, []} = els_docs_memo:lookup(MFACT2),

ok.

-spec invalidate(config()) -> ok.
invalidate(Config) ->
Uri = ?config(docs_memo_uri, Config),
RootUri = els_test_utils:root_uri(),
DataDir = ?config(data_dir, Config),
ConfigPath = filename:join(DataDir, "docs_memo_true.config"),
InitOpts = #{<<"erlang">> => #{<<"config_path">> => ConfigPath}},
els_client:initialize(RootUri, InitOpts),
meck:new(els_text_synchronization, [passthrough]),

MFACT = {M = docs_memo, F = function, A = 0, 'local', function},

%% Did save
els_docs:function_docs('local', M, F, A),
{ok, [_]} = els_docs_memo:lookup(MFACT),
ok = els_client:did_save(Uri),
els_test_utils:wait_until_mock_called(els_text_synchronization, did_save),
{ok, []} = els_docs_memo:lookup(MFACT),

%% Did change watched files
els_docs:function_docs('local', M, F, A),
{ok, [_]} = els_docs_memo:lookup(MFACT),
ok = els_client:did_change_watched_files([{Uri, ?FILE_CHANGE_TYPE_CHANGED}]),
els_test_utils:wait_until_mock_called(els_text_synchronization, did_change_watched_files),
{ok, []} = els_docs_memo:lookup(MFACT),

ok.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
docs_memo: false
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
docs_memo: true
Loading

0 comments on commit 0e7577f

Please sign in to comment.