Skip to content

Commit

Permalink
[lsp] implement a no-op LSP server
Browse files Browse the repository at this point in the history
  • Loading branch information
vladdu committed Mar 17, 2017
1 parent 5ced1ae commit 41ac5ea
Show file tree
Hide file tree
Showing 18 changed files with 1,148 additions and 50 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ TEST-*.*




server/erlide_ide server/erlide_ide
*.crashdump
2 changes: 1 addition & 1 deletion build_utils.sh
Original file line number Original file line Diff line number Diff line change
@@ -1,6 +1,6 @@
#! /bin/bash -e #! /bin/bash -e


declare -A OTP_VSNS=( ["17"]="17.5" ["18"]="18.3" ["19"]="19.2") declare -A OTP_VSNS=( ["17"]="17.5" ["18"]="18.3" ["19"]="19.3")


build_project() { build_project() {
REBAR=$1 REBAR=$1
Expand Down
157 changes: 157 additions & 0 deletions ide/apps/erlide_ide/src/cancellable_worker.erl
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,157 @@
-module(cancellable_worker).

-behaviour(gen_server).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

%% ====================================================================
%% API functions
%% ====================================================================

-export([
start/1,
start/3,
cancel/1,
cancel/2,
yield/1,
yield/2,
check/1
]).

%% Implement a worker process that can be cancelled and then may return a
%% partial answer.
%% The function doing the actual work takes as argument a Reporter function to
%% use to report results:
%% - Reporter(partial, Value) for a partial result
%% - Reporter(final, Value) for the whole result (if partial results are not
%% possible); do not report this after any partial values
%% If partial results are sent, they are aggregated in a list, which is returned
start(WorkerFun) ->
gen_server:start(?MODULE, WorkerFun, []).

start(Module, Function, Args) ->
start(fun() -> apply(Module, Function, Args) end).

%% Check/1 checks if there are any answers from the worker. It can return
%% - {partial, Values} : the list of all currently reported values
%% - {final, Value} : the final result
%% - {error, {Value1, Value2}} : unexpected 'final' Value2 reported (either
%% after another 'final' or after 'partial's Value1)
check(MonPid) when is_pid(MonPid) ->
case is_process_alive(MonPid) of
true ->
gen_server:call(MonPid, check);
false ->
{error, noproc}
end.

%% Cancels the worker and returns the current results.
cancel(MonPid) when is_pid(MonPid) ->
case is_process_alive(MonPid) of
true ->
io:format("*** ~p~n", [{MonPid}]),
catch gen_server:call(MonPid, cancel);
false ->
{error, noproc}
end.
cancel(MonPid, Timeout) when is_pid(MonPid) ->
gen_server:call(MonPid, cancel, Timeout).

%% Wait until the the worker has finished and return the final result.
%% TODO don't return partial/final
yield(MonPid) when is_pid(MonPid) ->
gen_server:call(MonPid, yield).
yield(MonPid, Timeout) when is_pid(MonPid) ->
gen_server:call(MonPid, yield, Timeout).

%% ====================================================================
%% Behavioural functions
%% ====================================================================
-record(state, {
worker_pid,
results = {partial, undefined},
yielding = false
}).

init(WorkerFun) ->
process_flag(trap_exit, true),
Monitor = self(),
Report = fun(partial, V) -> gen_server:cast(Monitor, {partial, V});
(final, V) -> gen_server:cast(Monitor, {final, V})
end,
{WrkPid, _Ref} = spawn_monitor(fun() ->
WorkerFun(Report)
end),
{ok, #state{worker_pid=WrkPid}}.


handle_call(check, _From, State=#state{results=Results, worker_pid=Pid}) when is_pid(Pid) ->
Reply = adjust(Results),
{reply, Reply, State};
handle_call(check, _From, State=#state{results=Results}) ->
{_, Reply} = adjust(Results),
{reply, {final, Reply}, State};
handle_call(cancel, _From, State=#state{results=Results, worker_pid=Pid}) when is_pid(Pid) ->
exit(Pid, kill),
{_, Reply} = adjust(Results),
{reply, {ok, Reply}, State};
handle_call(cancel, _From, State=#state{results=Results}) ->
{_, Reply} = adjust(Results),
{reply, {ok, Reply}, State};
handle_call(yield, _From, State=#state{worker_pid=false, results=Results}) ->
{_, Reply} = adjust(Results),
{stop, normal, {ok, Reply}, State};
handle_call(yield, From, State) ->
{noreply, State#state{yielding=From}};
handle_call(Request, _From, State) ->
io:format("HUH???... ~p~n", [Request]),
Reply = {error, {unknown, Request}},
{reply, Reply, State}.

handle_cast(V, State=#state{results=Results}) ->
NewResults = merge_result(V, Results),
{noreply, State#state{results=NewResults}};
handle_cast(_Msg, State) ->
{noreply, State}.

handle_info({'DOWN', _, process, Pid, _Reason},
State=#state{worker_pid=Pid,
yielding=From,
results=Results}) when From /= false ->

{_, Reply} = adjust(Results),
gen_server:reply(From, {ok, Reply}),
{noreply, State#state{worker_pid=false}};
handle_info({'DOWN', _, process, Pid, _Reason}, State=#state{worker_pid=Pid}) ->
{noreply, State#state{worker_pid=false}};
handle_info(_Info, State) ->
io:format("@@@ ~p~n", [_Info]),
{noreply, State}.

terminate(_Reason, _State) ->
ok.

code_change(_OldVsn, State, _Extra) ->
{ok, State}.

%% ====================================================================
%% Internal functions
%% ====================================================================

adjust({K, Results}=Arg) ->
if is_list(Results) ->
{K, lists:reverse(Results)};
true ->
Arg
end.

merge_result({final, V}, {partial, undefined}) ->
{final, V};
merge_result({partial, V}, {partial, undefined}) ->
{partial, [V]};
merge_result({final, V}, {partial, R}) ->
{final, [V|R]};
merge_result({partial, V}, {partial, R}) ->
{partial, [V|R]};
merge_result(_V, R) ->
R.
94 changes: 94 additions & 0 deletions ide/apps/erlide_ide/src/erlide_doc_server.erl
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,94 @@
%%%

-module(erlide_doc_server).

-export([
get_raw_documentation/3,
get_documentation/3
]).

-type root() ::
{'lib', string()} |
{'app', string()}.
-type configuration() ::
#{
'roots' => [root()]
}.

-type doc_ref() ::
{'application', atom()} |
{'module', atom()} |
{'function', {atom(), atom(), integer()}} |
{'macro', {atom(), atom(), integer()}} |
{'record', {atom(), atom(), integer()}} |
{'behaviour', atom()} |
{'type', {atom(), atom(), integer()}}.

-type doc_tree() :: any().

-type doc_result() :: {'ok', [{atom(), doc_tree()}]} | {'error', any()}.
-type raw_doc_result() :: {'ok', [{atom(), iolist()}]} | {'error', any()}.

-type provider() :: fun((doc_ref()) -> {'ok', {atom(), iolist()}} | {'error', any()}).

-spec get_documentation(configuration(), doc_ref(), [provider()]) -> doc_result().
get_documentation(Config, Ref, Providers) ->
RawDocs = get_raw_documentation(Config, Ref, Providers),
convert(Config, Providers, RawDocs).

-spec get_raw_documentation(configuration(), doc_ref(), [provider()]) -> raw_doc_result().
get_raw_documentation(Config, Ref, Providers) ->
traverse(Config, Providers, Ref).

%% ====================================================================
%% Internal functions
%% ====================================================================

traverse(Config, L, Args) ->
traverse(Config, L, Args, [], []).

traverse(_Config, [], _Args, Result, []) ->
{ok, lists:reverse(Result)};
traverse(_Config, [], _Args, Result, Errs) ->
{error, {lists:reverse(Errs), lists:reverse(Result)}};
traverse(Config, [M|T], Args, Result, Err) ->
case catch apply(M, Args) of
{error, E} ->
traverse(Config, T, Args, Result, [E|Err]);
{'EXIT', E} ->
traverse(Config, T, Args, Result, [E|Err]);
{ok, V} ->
traverse(Config, T, Args, [V|Result], Err);
V ->
traverse(Config, T, Args, [V|Result], Err)
end.

convert(Config, Providers, {ok, Docs}) ->
{ok, convert1(Config, Providers, Docs, [])};
convert(Config, Providers, {error, Errors, Docs}) ->
{error, Errors, convert1(Config, Providers, Docs, [])}.

convert1(_Config, _Providers, [], Result) ->
lists:reverse(Result);
convert1(Config, Providers, [{M, D}|T], Result) ->
case lists:keyfind(M, 1, Providers) of
{M, P} ->
V = P:convert(D),
convert1(Config, Providers, T, [V|Result]);
false ->
convert1(Config, Providers, T, Result)
end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-include_lib("eunit/include/eunit.hrl").

traverse_test_() ->
[
?_assertEqual({ok, [[1,2]]},traverse(#{}, [fun lists:seq/2], [1,2])),
?_assertEqual({ok, []},traverse(#{}, [], [])),
?_assertEqual({ok, []},traverse(#{}, [], [])),
?_assertEqual({ok, []},traverse(#{}, [], [])),
?_assertEqual({ok, []},traverse(#{}, [], [])),
?_assertEqual({ok, []},traverse(#{}, [], []))
].
12 changes: 12 additions & 0 deletions ide/apps/erlide_ide/src/erlide_edoc_doc_provider.erl
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,12 @@
-module(erlide_edoc_doc_provider).

-export([
get_documentation/1
]).

get_documentation({Kind, Ref}) ->
R = lists:flatten(io_lib:format("EDOC:: ~p ~p", [Kind, Ref])),
{ok, {edoc, R}};
get_documentation(Arg) ->
{error, {badarg, Arg}}.

2 changes: 1 addition & 1 deletion ide/apps/erlide_ide/src/erlide_ide.app.src
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{vsn, "0.113.0"}, {vsn, "0.113.0"},
{erlide_context, ide}, {erlide_context, ide},
{registered, []}, {registered, []},
{applications, [kernel, stdlib, erlide_common, erlide_server]}, {applications, [kernel, stdlib, erlide_common, erlide_server, jsx]},
{env, []}, {env, []},
{mod, {erlide_ide, []}} {mod, {erlide_ide, []}}
]} ]}
Expand Down
8 changes: 5 additions & 3 deletions ide/apps/erlide_ide/src/erlide_ide.erl
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
-behaviour(application). -behaviour(application).
-export([start/2, stop/1]). -export([start/2, stop/1]).


main(_) -> main(Args) ->
io:format("Start erlide server. ...Not implemented yet... ~n"), io:format("Start erlide server. ~p~n", [Args]),
application:set_env(erlide_ide, main_args, Args, [{persistent, true}]),
_R = application:ensure_all_started(erlide_ide), _R = application:ensure_all_started(erlide_ide),
receive stop -> ok end, receive stop -> ok end,
ok. ok.


start(_Type, _StartArgs) -> start(_Type, _StartArgs) ->
io:format("Start app ~n"), io:format("Start app ~p~n", [application:get_all_env()]),

erlide_ide_sup:start_link(). erlide_ide_sup:start_link().


stop(_State) -> stop(_State) ->
Expand Down
Loading

0 comments on commit 41ac5ea

Please sign in to comment.