Skip to content

Commit

Permalink
Add support for didChangeWatchedFiles (erlang-ls#1247)
Browse files Browse the repository at this point in the history
  • Loading branch information
robertoaloi committed Mar 18, 2022
1 parent 3ef713c commit 2bfcc46
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 15 deletions.
12 changes: 12 additions & 0 deletions apps/els_core/include/els_core.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -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
%%------------------------------------------------------------------------------
Expand Down
31 changes: 22 additions & 9 deletions apps/els_core/src/els_client.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}}).
Expand Down Expand Up @@ -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};
Expand Down Expand Up @@ -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">>.

Expand Down Expand Up @@ -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().
Expand Down
6 changes: 6 additions & 0 deletions apps/els_lsp/priv/code_navigation/src/watched_file_a.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-module(watched_file_a).

-export([ main/0 ]).

main() ->
ok.
6 changes: 6 additions & 0 deletions apps/els_lsp/priv/code_navigation/src/watched_file_b.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-module(watched_file_b).

-export([ main/0 ]).

main() ->
watched_file_a:main().
5 changes: 5 additions & 0 deletions apps/els_lsp/src/els_dt_document.erl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

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

-export([ new/2
Expand Down Expand Up @@ -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),
Expand Down
8 changes: 7 additions & 1 deletion apps/els_lsp/src/els_dt_document_index.erl
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
%% API
%%==============================================================================

-export([new/3]).
-export([ new/3 ]).

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

%%==============================================================================
Expand Down Expand Up @@ -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, _ = '_'},
Expand Down
10 changes: 8 additions & 2 deletions apps/els_lsp/src/els_dt_signatures.erl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

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

%%==============================================================================
Expand All @@ -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{}.

Expand Down Expand Up @@ -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).
5 changes: 2 additions & 3 deletions apps/els_lsp/src/els_methods.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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}.

%%==============================================================================
Expand Down
20 changes: 20 additions & 0 deletions apps/els_lsp/src/els_text_synchronization.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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().
Expand Down Expand Up @@ -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.

Expand All @@ -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)).
90 changes: 90 additions & 0 deletions apps/els_lsp/test/els_references_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,17 @@
, 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
]).

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

%%==============================================================================
%% Types
Expand All @@ -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).

Expand Down Expand Up @@ -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
%%==============================================================================
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-module(watched_file_c).

-export([ main/0 ]).

main() ->
watched_file_a:main().

0 comments on commit 2bfcc46

Please sign in to comment.