%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(group).
%% A group leader process for user io.
-export([start/2, start/3, server/3]).
-export([interfaces/1]).
start(Drv, Shell) ->
start(Drv, Shell, []).
start(Drv, Shell, Options) ->
spawn_link(group, server, [Drv, Shell, Options]).
server(Drv, Shell, Options) ->
process_flag(trap_exit, true),
edlin:init(),
put(line_buffer, proplists:get_value(line_buffer, Options, group_history:load())),
put(read_mode, list),
put(user_drv, Drv),
put(expand_fun,
proplists:get_value(expand_fun, Options,
fun(B) -> edlin_expand:expand(B) end)),
put(echo, proplists:get_value(echo, Options, true)),
start_shell(Shell),
server_loop(Drv, get(shell), []).
%% Return the pid of user_drv and the shell process.
%% Note: We can't ask the group process for this info since it
%% may be busy waiting for data from the driver.
interfaces(Group) ->
case process_info(Group, dictionary) of
{dictionary,Dict} ->
get_pids(Dict, [], false);
_ ->
[]
end.
get_pids([Drv = {user_drv,_} | Rest], Found, _) ->
get_pids(Rest, [Drv | Found], true);
get_pids([Sh = {shell,_} | Rest], Found, Active) ->
get_pids(Rest, [Sh | Found], Active);
get_pids([_ | Rest], Found, Active) ->
get_pids(Rest, Found, Active);
get_pids([], Found, true) ->
Found;
get_pids([], _Found, false) ->
[].
%% start_shell(Shell)
%% Spawn a shell with its group_leader from the beginning set to ourselves.
%% If Shell a pid the set its group_leader.
start_shell({Mod,Func,Args}) ->
start_shell1(Mod, Func, Args);
start_shell({Node,Mod,Func,Args}) ->
start_shell1(net, call, [Node,Mod,Func,Args]);
start_shell(Shell) when is_atom(Shell) ->
start_shell1(Shell, start, []);
start_shell(Shell) when is_function(Shell) ->
start_shell1(Shell);
start_shell(Shell) when is_pid(Shell) ->
group_leader(self(), Shell), % we are the shells group leader
link(Shell), % we're linked to it.
put(shell, Shell);
start_shell(_Shell) ->
ok.
start_shell1(M, F, Args) ->
G = group_leader(),
group_leader(self(), self()),
case catch apply(M, F, Args) of
Shell when is_pid(Shell) ->
group_leader(G, self()),
link(Shell), % we're linked to it.
put(shell, Shell);
Error -> % start failure
exit(Error) % let the group process crash
end.
start_shell1(Fun) ->
G = group_leader(),
group_leader(self(), self()),
case catch Fun() of
Shell when is_pid(Shell) ->
group_leader(G, self()),
link(Shell), % we're linked to it.
put(shell, Shell);
Error -> % start failure
exit(Error) % let the group process crash
end.
server_loop(Drv, Shell, Buf0) ->
receive
{io_request,From,ReplyAs,Req} when is_pid(From) ->
%% This io_request may cause a transition to a couple of
%% selective receive loops elsewhere in this module.
Buf = io_request(Req, From, ReplyAs, Drv, Shell, Buf0),
server_loop(Drv, Shell, Buf);
{reply,{{From,ReplyAs},Reply}} ->
io_reply(From, ReplyAs, Reply),
server_loop(Drv, Shell, Buf0);
{driver_id,ReplyTo} ->
ReplyTo ! {self(),driver_id,Drv},
server_loop(Drv, Shell, Buf0);
{Drv, echo, Bool} ->
put(echo, Bool),
server_loop(Drv, Shell, Buf0);
{'EXIT',Drv,interrupt} ->
%% Send interrupt to the shell.
exit_shell(interrupt),
server_loop(Drv, Shell, Buf0);
{'EXIT',Drv,R} ->
exit(R);
{'EXIT',Shell,R} ->
exit(R);
%% We want to throw away any term that we don't handle (standard
%% practice in receive loops), but not any {Drv,_} tuples which are
%% handled in io_request/6.
NotDrvTuple when (not is_tuple(NotDrvTuple)) orelse
(tuple_size(NotDrvTuple) =/= 2) orelse
(element(1, NotDrvTuple) =/= Drv) ->
%% Ignore this unknown message.
server_loop(Drv, Shell, Buf0)
end.
exit_shell(Reason) ->
case get(shell) of
undefined -> true;
Pid -> exit(Pid, Reason)
end.
get_tty_geometry(Drv) ->
Drv ! {self(),tty_geometry},
receive
{Drv,tty_geometry,Geometry} ->
Geometry
after 2000 ->
timeout
end.
get_unicode_state(Drv) ->
Drv ! {self(),get_unicode_state},
receive
{Drv,get_unicode_state,UniState} ->
UniState;
{Drv,get_unicode_state,error} ->
{error, internal}
after 2000 ->
{error,timeout}
end.
set_unicode_state(Drv,Bool) ->
Drv ! {self(),set_unicode_state,Bool},
receive
{Drv,set_unicode_state,_OldUniState} ->
ok
after 2000 ->
timeout
end.
io_request(Req, From, ReplyAs, Drv, Shell, Buf0) ->
case io_request(Req, Drv, Shell, {From,ReplyAs}, Buf0) of
{ok,Reply,Buf} ->
io_reply(From, ReplyAs, Reply),
Buf;
{noreply,Buf} ->
%% We expect a {reply,_} message from the Drv when request is done
Buf;
{error,Reply,Buf} ->
io_reply(From, ReplyAs, Reply),
Buf;
{exit,R} ->
%% 'kill' instead of R, since the shell is not always in
%% a state where it is ready to handle a termination
%% message.
exit_shell(kill),
exit(R)
end.
%% Put_chars, unicode is the normal message, characters are always in
%%standard unicode
%% format.
%% You might be tempted to send binaries unchecked, but the driver
%% expects unicode, so that is what we should send...
%% io_request({put_chars,unicode,Binary}, Drv, Buf) when is_binary(Binary) ->
%% send_drv(Drv, {put_chars,Binary}),
%% {ok,ok,Buf};
%%
%% These put requests have to be synchronous to the driver as otherwise
%% there is no guarantee that the data has actually been printed.
io_request({put_chars,unicode,Chars}, Drv, _Shell, From, Buf) ->
case catch unicode:characters_to_binary(Chars,utf8) of
Binary when is_binary(Binary) ->
send_drv(Drv, {put_chars_sync, unicode, Binary, {From,ok}}),
{noreply,Buf};
_ ->
{error,{error,{put_chars, unicode,Chars}},Buf}
end;
io_request({put_chars,unicode,M,F,As}, Drv, _Shell, From, Buf) ->
case catch apply(M, F, As) of
Binary when is_binary(Binary) ->
send_drv(Drv, {put_chars_sync, unicode, Binary, {From,ok}}),
{noreply,Buf};
Chars ->
case catch unicode:characters_to_binary(Chars,utf8) of
B when is_binary(B) ->
send_drv(Drv, {put_chars_sync, unicode, B, {From,ok}}),
{noreply,Buf};
_ ->
{error,{error,F},Buf}
end
end;
io_request({put_chars,latin1,Binary}, Drv, _Shell, From, Buf) when is_binary(Binary) ->
send_drv(Drv, {put_chars_sync, unicode,
unicode:characters_to_binary(Binary,latin1),
{From,ok}}),
{noreply,Buf};
io_request({put_chars,latin1,Chars}, Drv, _Shell, From, Buf) ->
case catch unicode:characters_to_binary(Chars,latin1) of
Binary when is_binary(Binary) ->
send_drv(Drv, {put_chars_sync, unicode, Binary, {From,ok}}),
{noreply,Buf};
_ ->
{error,{error,{put_chars,latin1,Chars}},Buf}
end;
io_request({put_chars,latin1,M,F,As}, Drv, _Shell, From, Buf) ->
case catch apply(M, F, As) of
Binary when is_binary(Binary) ->
send_drv(Drv, {put_chars_sync, unicode,
unicode:characters_to_binary(Binary,latin1),
{From,ok}}),
{noreply,Buf};
Chars ->
case catch unicode:characters_to_binary(Chars,latin1) of
B when is_binary(B) ->
send_drv(Drv, {put_chars_sync, unicode, B, {From,ok}}),
{noreply,Buf};
_ ->
{error,{error,F},Buf}
end
end;
io_request({get_chars,Encoding,Prompt,N}, Drv, Shell, _From, Buf) ->
get_chars_n(Prompt, io_lib, collect_chars, N, Drv, Shell, Buf, Encoding);
io_request({get_line,Encoding,Prompt}, Drv, Shell, _From, Buf) ->
get_chars_line(Prompt, io_lib, collect_line, [], Drv, Shell, Buf, Encoding);
io_request({get_until,Encoding, Prompt,M,F,As}, Drv, Shell, _From, Buf) ->
get_chars_line(Prompt, io_lib, get_until, {M,F,As}, Drv, Shell, Buf, Encoding);
io_request({get_password,_Encoding},Drv,Shell,_From,Buf) ->
get_password_chars(Drv, Shell, Buf);
io_request({setopts,Opts}, Drv, _Shell, _From, Buf) when is_list(Opts) ->
setopts(Opts, Drv, Buf);
io_request(getopts, Drv, _Shell, _From, Buf) ->
getopts(Drv, Buf);
io_request({requests,Reqs}, Drv, Shell, From, Buf) ->
io_requests(Reqs, {ok,ok,Buf}, From, Drv, Shell);
%% New in R12
io_request({get_geometry,columns},Drv,_Shell,_From,Buf) ->
case get_tty_geometry(Drv) of
{W,_H} ->
{ok,W,Buf};
_ ->
{error,{error,enotsup},Buf}
end;
io_request({get_geometry,rows},Drv,_Shell,_From,Buf) ->
case get_tty_geometry(Drv) of
{_W,H} ->
{ok,H,Buf};
_ ->
{error,{error,enotsup},Buf}
end;
%% BC with pre-R13
io_request({put_chars,Chars}, Drv, Shell, From, Buf) ->
io_request({put_chars,latin1,Chars}, Drv, Shell, From, Buf);
io_request({put_chars,M,F,As}, Drv, Shell, From, Buf) ->
io_request({put_chars,latin1,M,F,As}, Drv, Shell, From, Buf);
io_request({get_chars,Prompt,N}, Drv, Shell, From, Buf) ->
io_request({get_chars,latin1,Prompt,N}, Drv, Shell, From, Buf);
io_request({get_line,Prompt}, Drv, Shell, From, Buf) ->
io_request({get_line,latin1,Prompt}, Drv, Shell, From, Buf);
io_request({get_until, Prompt,M,F,As}, Drv, Shell, From, Buf) ->
io_request({get_until,latin1, Prompt,M,F,As}, Drv, Shell, From, Buf);
io_request(get_password,Drv,Shell,From,Buf) ->
io_request({get_password,latin1},Drv,Shell,From,Buf);
io_request(_, _Drv, _Shell, _From, Buf) ->
{error,{error,request},Buf}.
%% Status = io_requests(RequestList, PrevStat, From, Drv, Shell)
%% Process a list of output requests as long as
%% the previous status is 'ok' or noreply.
%%
%% We use undefined as the From for all but the last request
%% in order to discards acknowledgements from those requests.
%%
io_requests([R|Rs], {noreply,Buf}, From, Drv, Shell) ->
ReqFrom = if Rs =:= [] -> From; true -> undefined end,
io_requests(Rs, io_request(R, Drv, Shell, ReqFrom, Buf), From, Drv, Shell);
io_requests([R|Rs], {ok,ok,Buf}, From, Drv, Shell) ->
ReqFrom = if Rs =:= [] -> From; true -> undefined end,
io_requests(Rs, io_request(R, Drv, Shell, ReqFrom, Buf), From, Drv, Shell);
io_requests([_|_], Error, _From, _Drv, _Shell) ->
Error;
io_requests([], Stat, _From, _, _Shell) ->
Stat.
%% io_reply(From, ReplyAs, Reply)
%% The function for sending i/o command acknowledgement.
%% The ACK contains the return value.
io_reply(undefined, _ReplyAs, _Reply) ->
%% Ignore these replies as they are generated from io_requests/5.
ok;
io_reply(From, ReplyAs, Reply) ->
From ! {io_reply,ReplyAs,Reply},
ok.
%% send_drv(Drv, Message)
%% send_drv_reqs(Drv, Requests)
send_drv(Drv, Msg) ->
Drv ! {self(),Msg},
ok.
send_drv_reqs(_Drv, []) -> ok;
send_drv_reqs(Drv, Rs) ->
send_drv(Drv, {requests,Rs}).
expand_encoding([]) ->
[];
expand_encoding([latin1 | T]) ->
[{encoding,latin1} | expand_encoding(T)];
expand_encoding([unicode | T]) ->
[{encoding,unicode} | expand_encoding(T)];
expand_encoding([H|T]) ->
[H|expand_encoding(T)].
%% setopts
setopts(Opts0,Drv,Buf) ->
Opts = proplists:unfold(
proplists:substitute_negations(
[{list,binary}],
expand_encoding(Opts0))),
case check_valid_opts(Opts) of
true ->
do_setopts(Opts,Drv,Buf);
false ->
{error,{error,enotsup},Buf}
end.
check_valid_opts([]) ->
true;
check_valid_opts([{binary,_}|T]) ->
check_valid_opts(T);
check_valid_opts([{encoding,Valid}|T]) when Valid =:= unicode; Valid =:= utf8; Valid =:= latin1 ->
check_valid_opts(T);
check_valid_opts([{echo,_}|T]) ->
check_valid_opts(T);
check_valid_opts([{expand_fun,_}|T]) ->
check_valid_opts(T);
check_valid_opts(_) ->
false.
do_setopts(Opts, Drv, Buf) ->
put(expand_fun, proplists:get_value(expand_fun, Opts, get(expand_fun))),
put(echo, proplists:get_value(echo, Opts, get(echo))),
case proplists:get_value(encoding,Opts) of
Valid when Valid =:= unicode; Valid =:= utf8 ->
set_unicode_state(Drv,true);
latin1 ->
set_unicode_state(Drv,false);
_ ->
ok
end,
case proplists:get_value(binary, Opts, case get(read_mode) of
binary -> true;
_ -> false
end) of
true ->
put(read_mode, binary),
{ok,ok,Buf};
false ->
put(read_mode, list),
{ok,ok,Buf};
_ ->
{ok,ok,Buf}
end.
getopts(Drv,Buf) ->
Exp = {expand_fun, case get(expand_fun) of
Func when is_function(Func) ->
Func;
_ ->
false
end},
Echo = {echo, case get(echo) of
Bool when Bool =:= true; Bool =:= false ->
Bool;
_ ->
false
end},
Bin = {binary, case get(read_mode) of
binary ->
true;
_ ->
false
end},
Uni = {encoding, case get_unicode_state(Drv) of
true -> unicode;
_ -> latin1
end},
{ok,[Exp,Echo,Bin,Uni],Buf}.
%% get_chars_*(Prompt, Module, Function, XtraArgument, Drv, Buffer)
%% Gets characters from the input Drv until as the applied function
%% returns {stop,Result,Rest}. Does not block output until input has been
%% received.
%% Returns:
%% {Result,NewSaveBuffer}
%% {error,What,NewSaveBuffer}
get_password_chars(Drv,Shell,Buf) ->
case get_password_line(Buf, Drv, Shell) of
{done, Line, Buf1} ->
{ok, Line, Buf1};
interrupted ->
{error, {error, interrupted}, []};
terminated ->
{exit, terminated}
end.
get_chars_n(Prompt, M, F, Xa, Drv, Shell, Buf, Encoding) ->
Pbs = prompt_bytes(Prompt, Encoding),
case get(echo) of
true ->
get_chars_loop(Pbs, M, F, Xa, Drv, Shell, Buf, start, Encoding);
false ->
get_chars_n_loop(Pbs, M, F, Xa, Drv, Shell, Buf, start, Encoding)
end.
get_chars_line(Prompt, M, F, Xa, Drv, Shell, Buf, Encoding) ->
Pbs = prompt_bytes(Prompt, Encoding),
get_chars_loop(Pbs, M, F, Xa, Drv, Shell, Buf, start, Encoding).
get_chars_loop(Pbs, M, F, Xa, Drv, Shell, Buf0, State, Encoding) ->
Result = case get(echo) of
true ->
get_line(Buf0, Pbs, Drv, Shell, Encoding);
false ->
% get_line_echo_off only deals with lists
% and does not need encoding...
get_line_echo_off(Buf0, Pbs, Drv, Shell)
end,
case Result of
{done,Line,Buf} ->
get_chars_apply(Pbs, M, F, Xa, Drv, Shell, Buf, State, Line, Encoding);
interrupted ->
{error,{error,interrupted},[]};
terminated ->
{exit,terminated}
end.
get_chars_apply(Pbs, M, F, Xa, Drv, Shell, Buf, State0, Line, Encoding) ->
case catch M:F(State0, cast(Line,get(read_mode), Encoding), Encoding, Xa) of
{stop,Result,Rest} ->
{ok,Result,append(Rest, Buf, Encoding)};
{'EXIT',_} ->
{error,{error,err_func(M, F, Xa)},[]};
State1 ->
get_chars_loop(Pbs, M, F, Xa, Drv, Shell, Buf, State1, Encoding)
end.
get_chars_n_loop(Pbs, M, F, Xa, Drv, Shell, Buf0, State, Encoding) ->
try M:F(State, cast(Buf0, get(read_mode), Encoding), Encoding, Xa) of
{stop,Result,Rest} ->
{ok, Result, Rest};
State1 ->
case get_chars_echo_off(Pbs, Drv, Shell) of
interrupted ->
{error,{error,interrupted},[]};
terminated ->
{exit,terminated};
Buf ->
get_chars_n_loop(Pbs, M, F, Xa, Drv, Shell, Buf, State1, Encoding)
end
catch _:_ ->
{error,{error,err_func(M, F, Xa)},[]}
end.
%% Convert error code to make it look as before
err_func(io_lib, get_until, {_,F,_}) ->
F;
err_func(_, F, _) ->
F.
%% get_line(Chars, PromptBytes, Drv)
%% Get a line with eventual line editing. Handle other io requests
%% while getting line.
%% Returns:
%% {done,LineChars,RestChars}
%% interrupted
get_line(Chars, Pbs, Drv, Shell, Encoding) ->
{more_chars,Cont,Rs} = edlin:start(Pbs),
send_drv_reqs(Drv, Rs),
get_line1(edlin:edit_line(Chars, Cont), Drv, Shell, new_stack(get(line_buffer)),
Encoding).
get_line1({done,Line,Rest,Rs}, Drv, _Shell, Ls, _Encoding) ->
send_drv_reqs(Drv, Rs),
save_line_buffer(Line, get_lines(Ls)),
{done,Line,Rest};
get_line1({undefined,{_A,Mode,Char},Cs,Cont,Rs}, Drv, Shell, Ls0, Encoding)
when ((Mode =:= none) and (Char =:= $\^P))
or ((Mode =:= meta_left_sq_bracket) and (Char =:= $A)) ->
send_drv_reqs(Drv, Rs),
case up_stack(save_line(Ls0, edlin:current_line(Cont))) of
{none,_Ls} ->
send_drv(Drv, beep),
get_line1(edlin:edit_line(Cs, Cont), Drv, Shell, Ls0, Encoding);
{Lcs,Ls} ->
send_drv_reqs(Drv, edlin:erase_line(Cont)),
{more_chars,Ncont,Nrs} = edlin:start(edlin:prompt(Cont)),
send_drv_reqs(Drv, Nrs),
get_line1(edlin:edit_line1(lists:sublist(Lcs, 1, length(Lcs)-1),
Ncont),
Drv,
Shell,
Ls, Encoding)
end;
get_line1({undefined,{_A,Mode,Char},Cs,Cont,Rs}, Drv, Shell, Ls0, Encoding)
when ((Mode =:= none) and (Char =:= $\^N))
or ((Mode =:= meta_left_sq_bracket) and (Char =:= $B)) ->
send_drv_reqs(Drv, Rs),
case down_stack(save_line(Ls0, edlin:current_line(Cont))) of
{none,_Ls} ->
send_drv(Drv, beep),
get_line1(edlin:edit_line(Cs, Cont), Drv, Shell, Ls0, Encoding);
{Lcs,Ls} ->
send_drv_reqs(Drv, edlin:erase_line(Cont)),
{more_chars,Ncont,Nrs} = edlin:start(edlin:prompt(Cont)),
send_drv_reqs(Drv, Nrs),
get_line1(edlin:edit_line1(lists:sublist(Lcs, 1, length(Lcs)-1),
Ncont),
Drv,
Shell,
Ls, Encoding)
end;
%% ^R = backward search, ^S = forward search.
%% Search is tricky to implement and does a lot of back-and-forth
%% work with edlin.erl (from stdlib). Edlin takes care of writing
%% and handling lines and escape characters to get out of search,
%% whereas this module does the actual searching and appending to lines.
%% Erlang's shell wasn't exactly meant to traverse the wall between
%% line and line stack, so we at least restrict it by introducing
%% new modes: search, search_quit, search_found. These are added to
%% the regular ones (none, meta_left_sq_bracket) and handle special
%% cases of history search.
get_line1({undefined,{_A,Mode,Char},Cs,Cont,Rs}, Drv, Shell, Ls, Encoding)
when ((Mode =:= none) and (Char =:= $\^R)) ->
send_drv_reqs(Drv, Rs),
%% drop current line, move to search mode. We store the current
%% prompt ('N>') and substitute it with the search prompt.
send_drv_reqs(Drv, edlin:erase_line(Cont)),
put(search_quit_prompt, edlin:prompt(Cont)),
Pbs = prompt_bytes("(search)`': ", Encoding),
{more_chars,Ncont,Nrs} = edlin:start(Pbs, search),
send_drv_reqs(Drv, Nrs),
get_line1(edlin:edit_line1(Cs, Ncont), Drv, Shell, Ls, Encoding);
get_line1({expand, Before, Cs0, Cont,Rs}, Drv, Shell, Ls0, Encoding) ->
send_drv_reqs(Drv, Rs),
ExpandFun = get(expand_fun),
{Found, Add, Matches} = ExpandFun(Before),
case Found of
no -> send_drv(Drv, beep);
yes -> ok
end,
Cs1 = append(Add, Cs0, Encoding), %%XXX:PaN should this always be unicode?
Cs = case Matches of
[] -> Cs1;
_ -> MatchStr = edlin_expand:format_matches(Matches),
send_drv(Drv, {put_chars, unicode, unicode:characters_to_binary(MatchStr,unicode)}),
[$\^L | Cs1]
end,
get_line1(edlin:edit_line(Cs, Cont), Drv, Shell, Ls0, Encoding);
get_line1({undefined,_Char,Cs,Cont,Rs}, Drv, Shell, Ls, Encoding) ->
send_drv_reqs(Drv, Rs),
send_drv(Drv, beep),
get_line1(edlin:edit_line(Cs, Cont), Drv, Shell, Ls, Encoding);
%% The search item was found and accepted (new line entered on the exact
%% result found)
get_line1({_What,Cont={line,_Prompt,_Chars,search_found},Rs}, Drv, Shell, Ls0, Encoding) ->
Line = edlin:current_line(Cont),
%% this may create duplicate entries.
Ls = save_line(new_stack(get_lines(Ls0)), Line),
get_line1({done, Line, "", Rs}, Drv, Shell, Ls, Encoding);
%% The search mode has been exited, but the user wants to remain in line
%% editing mode wherever that was, but editing the search result.
get_line1({What,Cont={line,_Prompt,_Chars,search_quit},Rs}, Drv, Shell, Ls, Encoding) ->
Line = edlin:current_chars(Cont),
%% Load back the old prompt with the correct line number.
case get(search_quit_prompt) of
undefined -> % should not happen. Fallback.
LsFallback = save_line(new_stack(get_lines(Ls)), Line),
get_line1({done, "\n", Line, Rs}, Drv, Shell, LsFallback, Encoding);
Prompt -> % redraw the line and keep going with the same stack position
NCont = {line,Prompt,{lists:reverse(Line),[]},none},
send_drv_reqs(Drv, Rs),
send_drv_reqs(Drv, edlin:erase_line(Cont)),
send_drv_reqs(Drv, edlin:redraw_line(NCont)),
get_line1({What, NCont ,[]}, Drv, Shell, pad_stack(Ls), Encoding)
end;
%% Search mode is entered.
get_line1({What,{line,Prompt,{RevCmd0,_Aft},search},Rs},
Drv, Shell, Ls0, Encoding) ->
send_drv_reqs(Drv, Rs),
%% Figure out search direction. ^S and ^R are returned through edlin
%% whenever we received a search while being already in search mode.
{Search, Ls1, RevCmd} = case RevCmd0 of
[$\^S|RevCmd1] ->
{fun search_down_stack/2, Ls0, RevCmd1};
[$\^R|RevCmd1] ->
{fun search_up_stack/2, Ls0, RevCmd1};
_ -> % new search, rewind stack for a proper search.
{fun search_up_stack/2, new_stack(get_lines(Ls0)), RevCmd0}
end,
Cmd = lists:reverse(RevCmd),
{Ls, NewStack} = case Search(Ls1, Cmd) of
{none, Ls2} ->
send_drv(Drv, beep),
{Ls2, {RevCmd, "': "}};
{Line, Ls2} -> % found. Complete the output edlin couldn't have done.
send_drv_reqs(Drv, [{put_chars, Encoding, Line}]),
{Ls2, {RevCmd, "': "++Line}}
end,
Cont = {line,Prompt,NewStack,search},
more_data(What, Cont, Drv, Shell, Ls, Encoding);
get_line1({What,Cont0,Rs}, Drv, Shell, Ls, Encoding) ->
send_drv_reqs(Drv, Rs),
more_data(What, Cont0, Drv, Shell, Ls, Encoding).
more_data(What, Cont0, Drv, Shell, Ls, Encoding) ->
receive
{Drv,{data,Cs}} ->
get_line1(edlin:edit_line(Cs, Cont0), Drv, Shell, Ls, Encoding);
{Drv,eof} ->
get_line1(edlin:edit_line(eof, Cont0), Drv, Shell, Ls, Encoding);
{io_request,From,ReplyAs,Req} when is_pid(From) ->
{more_chars,Cont,_More} = edlin:edit_line([], Cont0),
send_drv_reqs(Drv, edlin:erase_line(Cont)),
io_request(Req, From, ReplyAs, Drv, Shell, []), %WRONG!!!
send_drv_reqs(Drv, edlin:redraw_line(Cont)),
get_line1({more_chars,Cont,[]}, Drv, Shell, Ls, Encoding);
{reply,{{From,ReplyAs},Reply}} ->
%% We take care of replies from puts here as well
io_reply(From, ReplyAs, Reply),
more_data(What, Cont0, Drv, Shell, Ls, Encoding);
{'EXIT',Drv,interrupt} ->
interrupted;
{'EXIT',Drv,_} ->
terminated;
{'EXIT',Shell,R} ->
exit(R)
after
get_line_timeout(What)->
get_line1(edlin:edit_line([], Cont0), Drv, Shell, Ls, Encoding)
end.
get_line_echo_off(Chars, Pbs, Drv, Shell) ->
send_drv_reqs(Drv, [{put_chars, unicode,Pbs}]),
get_line_echo_off1(edit_line(Chars,[]), Drv, Shell).
get_line_echo_off1({Chars,[]}, Drv, Shell) ->
receive
{Drv,{data,Cs}} ->
get_line_echo_off1(edit_line(Cs, Chars), Drv, Shell);
{Drv,eof} ->
get_line_echo_off1(edit_line(eof, Chars), Drv, Shell);
{io_request,From,ReplyAs,Req} when is_pid(From) ->
io_request(Req, From, ReplyAs, Drv, Shell, []),
get_line_echo_off1({Chars,[]}, Drv, Shell);
{reply,{{From,ReplyAs},Reply}} when From =/= undefined ->
%% We take care of replies from puts here as well
io_reply(From, ReplyAs, Reply),
get_line_echo_off1({Chars,[]},Drv, Shell);
{'EXIT',Drv,interrupt} ->
interrupted;
{'EXIT',Drv,_} ->
terminated;
{'EXIT',Shell,R} ->
exit(R)
end;
get_line_echo_off1({Chars,Rest}, _Drv, _Shell) ->
{done,lists:reverse(Chars),case Rest of done -> []; _ -> Rest end}.
get_chars_echo_off(Pbs, Drv, Shell) ->
send_drv_reqs(Drv, [{put_chars, unicode,Pbs}]),
get_chars_echo_off1(Drv, Shell).
get_chars_echo_off1(Drv, Shell) ->
receive
{Drv, {data, Cs}} ->
Cs;
{Drv, eof} ->
eof;
{io_request,From,ReplyAs,Req} when is_pid(From) ->
io_request(Req, From, ReplyAs, Drv, Shell, []),
get_chars_echo_off1(Drv, Shell);
{reply,{{From,ReplyAs},Reply}} when From =/= undefined ->
%% We take care of replies from puts here as well
io_reply(From, ReplyAs, Reply),
get_chars_echo_off1(Drv, Shell);
{'EXIT',Drv,interrupt} ->
interrupted;
{'EXIT',Drv,_} ->
terminated;
{'EXIT',Shell,R} ->
exit(R)
end.
%% We support line editing for the ICANON mode except the following
%% line editing characters, which already has another meaning in
%% echo-on mode (See Advanced Programming in the Unix Environment, 2nd ed,
%% Stevens, page 638):
%% - ^u in posix/icanon mode: erase-line, prefix-arg in edlin
%% - ^t in posix/icanon mode: status, transpose-char in edlin
%% - ^d in posix/icanon mode: eof, delete-forward in edlin
%% - ^r in posix/icanon mode: reprint (silly in echo-off mode :-))
%% - ^w in posix/icanon mode: word-erase (produces a beep in edlin)
edit_line(eof, Chars) ->
{Chars,done};
edit_line([],Chars) ->
{Chars,[]};
edit_line([$\r,$\n|Cs],Chars) ->
{[$\n | Chars], remainder_after_nl(Cs)};
edit_line([NL|Cs],Chars) when NL =:= $\r; NL =:= $\n ->
{[$\n | Chars], remainder_after_nl(Cs)};
edit_line([Erase|Cs],[]) when Erase =:= $\177; Erase =:= $\^H ->
edit_line(Cs,[]);
edit_line([Erase|Cs],[_|Chars]) when Erase =:= $\177; Erase =:= $\^H ->
edit_line(Cs,Chars);
edit_line([Char|Cs],Chars) ->
edit_line(Cs,[Char|Chars]).
remainder_after_nl("") -> done;
remainder_after_nl(Cs) -> Cs.
get_line_timeout(blink) -> 1000;
get_line_timeout(more_chars) -> infinity.
new_stack(Ls) -> {stack,Ls,{},[]}.
up_stack({stack,[L|U],{},D}) ->
{L,{stack,U,L,D}};
up_stack({stack,[],{},D}) ->
{none,{stack,[],{},D}};
up_stack({stack,U,C,D}) ->
up_stack({stack,U,{},[C|D]}).
down_stack({stack,U,{},[L|D]}) ->
{L,{stack,U,L,D}};
down_stack({stack,U,{},[]}) ->
{none,{stack,U,{},[]}};
down_stack({stack,U,C,D}) ->
down_stack({stack,[C|U],{},D}).
save_line({stack, U, {}, []}, Line) ->
{stack, U, {}, [Line]};
save_line({stack, U, _L, D}, Line) ->
{stack, U, Line, D}.
get_lines(Ls) -> get_all_lines(Ls).
%get_lines({stack, U, {}, []}) ->
% U;
%get_lines({stack, U, {}, D}) ->
% tl(lists:reverse(D, U));
%get_lines({stack, U, L, D}) ->
% get_lines({stack, U, {}, [L|D]}).
%% There's a funny behaviour whenever the line stack doesn't have a "\n"
%% at its end -- get_lines() seemed to work on the assumption it *will* be
%% there, but the manipulations done with search history do not require it.
%%
%% It is an assumption because the function was built with either the full
%% stack being on the 'Up' side (we're on the new line) where it isn't
%% stripped. The only other case when it isn't on the 'Up' side is when
%% someone has used the up/down arrows (or ^P and ^N) to navigate lines,
%% in which case, a line with only a \n is stored at the end of the stack
%% (the \n is returned by edlin:current_line/1).
%%
%% get_all_lines works the same as get_lines, but only strips the trailing
%% character if it's a linebreak. Otherwise it's kept the same. This is
%% because traversing the stack due to search history will *not* insert
%% said empty line in the stack at the same time as other commands do,
%% and thus it should not always be stripped unless we know a new line
%% is the last entry.
get_all_lines({stack, U, {}, []}) ->
U;
get_all_lines({stack, U, {}, D}) ->
case lists:reverse(D, U) of
["\n"|Lines] -> Lines;
Lines -> Lines
end;
get_all_lines({stack, U, L, D}) ->
get_all_lines({stack, U, {}, [L|D]}).
%% For the same reason as above, though, we need to expand the stack
%% in some cases to make sure we play nice with up/down arrows. We need
%% to insert newlines, but not always.
pad_stack({stack, U, L, D}) ->
{stack, U, L, D++["\n"]}.
save_line_buffer("\n", Lines) ->
save_line_buffer(Lines);
save_line_buffer(Line, [Line|_Lines]=Lines) ->
save_line_buffer(Lines);
save_line_buffer(Line, Lines) ->
group_history:add(Line),
save_line_buffer([Line|Lines]).
save_line_buffer(Lines) ->
put(line_buffer, Lines).
search_up_stack(Stack, Substr) ->
case up_stack(Stack) of
{none,NewStack} -> {none,NewStack};
{L, NewStack} ->
case string:find(L, Substr) of
nomatch -> search_up_stack(NewStack, Substr);
_ -> {string:trim(L, trailing, "$\n"), NewStack}
end
end.
search_down_stack(Stack, Substr) ->
case down_stack(Stack) of
{none,NewStack} -> {none,NewStack};
{L, NewStack} ->
case string:find(L, Substr) of
nomatch -> search_down_stack(NewStack, Substr);
_ -> {string:trim(L, trailing, "$\n"), NewStack}
end
end.
%% This is get_line without line editing (except for backspace) and
%% without echo.
get_password_line(Chars, Drv, Shell) ->
get_password1(edit_password(Chars,[]),Drv,Shell).
get_password1({Chars,[]}, Drv, Shell) ->
receive
{Drv,{data,Cs}} ->
get_password1(edit_password(Cs,Chars),Drv,Shell);
{io_request,From,ReplyAs,Req} when is_pid(From) ->
%send_drv_reqs(Drv, [{delete_chars, -length(Pbs)}]),
io_request(Req, From, ReplyAs, Drv, Shell, []), %WRONG!!!
%% I guess the reason the above line is wrong is that Buf is
%% set to []. But do we expect anything but plain output?
get_password1({Chars, []}, Drv, Shell);
{reply,{{From,ReplyAs},Reply}} ->
%% We take care of replies from puts here as well
io_reply(From, ReplyAs, Reply),
get_password1({Chars, []},Drv, Shell);
{'EXIT',Drv,interrupt} ->
interrupted;
{'EXIT',Drv,_} ->
terminated;
{'EXIT',Shell,R} ->
exit(R)
end;
get_password1({Chars,Rest},Drv,_Shell) ->
send_drv_reqs(Drv,[{put_chars, unicode, "\n"}]),
{done,lists:reverse(Chars),case Rest of done -> []; _ -> Rest end}.
edit_password([],Chars) ->
{Chars,[]};
edit_password([$\r],Chars) ->
{Chars,done};
edit_password([$\r|Cs],Chars) ->
{Chars,Cs};
edit_password([$\177|Cs],[]) -> %% Being able to erase characters is
edit_password(Cs,[]); %% the least we should offer, but
edit_password([$\177|Cs],[_|Chars]) ->%% is backspace enough?
edit_password(Cs,Chars);
edit_password([Char|Cs],Chars) ->
edit_password(Cs,[Char|Chars]).
%% prompt_bytes(Prompt, Encoding)
%% Return a flat list of characters for the Prompt.
prompt_bytes(Prompt, Encoding) ->
lists:flatten(io_lib:format_prompt(Prompt, Encoding)).
cast(L, binary,latin1) when is_list(L) ->
list_to_binary(L);
cast(L, list, latin1) when is_list(L) ->
binary_to_list(list_to_binary(L)); %% Exception if not bytes
cast(L, binary,unicode) when is_list(L) ->
unicode:characters_to_binary(L,utf8);
cast(Other, _, _) ->
Other.
append(B, L, latin1) when is_binary(B) ->
binary_to_list(B)++L;
append(B, L, unicode) when is_binary(B) ->
unicode:characters_to_list(B,utf8)++L;
append(L1, L2, _) when is_list(L1) ->
L1++L2;
append(_Eof, L, _) ->
L.