summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukas Larsson <lukas@erlang.org>2022-05-09 17:00:06 +0200
committerLukas Larsson <lukas@erlang.org>2022-10-04 14:20:40 +0200
commit81562f74ad1c2d7f2a814935976f0a8e98003687 (patch)
treee6604a210a0c9d970658afdc98640d10d6eda2f2
parent80225287e0ad19e4d47df57010af688448c42549 (diff)
downloaderlang-81562f74ad1c2d7f2a814935976f0a8e98003687.tar.gz
kernel: Add support for expand after current line
-rw-r--r--erts/emulator/beam/break.c10
-rw-r--r--lib/kernel/src/group.erl56
-rw-r--r--lib/kernel/src/prim_tty.erl62
-rw-r--r--lib/kernel/src/user_drv.erl24
-rw-r--r--lib/kernel/test/interactive_shell_SUITE.erl120
-rw-r--r--lib/ssh/src/ssh_cli.erl10
-rw-r--r--lib/stdlib/doc/src/stdlib_app.xml5
-rw-r--r--lib/stdlib/src/edlin.erl14
-rw-r--r--lib/stdlib/src/edlin_expand.erl9
9 files changed, 267 insertions, 43 deletions
diff --git a/erts/emulator/beam/break.c b/erts/emulator/beam/break.c
index dff270c60f..d7d511e982 100644
--- a/erts/emulator/beam/break.c
+++ b/erts/emulator/beam/break.c
@@ -574,6 +574,7 @@ do_break(void)
{
const char *helpstring = "BREAK: (a)bort (A)bort with dump (c)ontinue (p)roc info (i)nfo\n"
" (l)oaded (v)ersion (k)ill (D)b-tables (d)istribution\n";
+ char *clearscreen = "\E[J";
int i;
#ifdef __WIN32__
char *mode; /* enough for storing "window" */
@@ -588,7 +589,14 @@ do_break(void)
ASSERT(erts_thr_progress_is_blocking());
- erts_printf("\n%s", helpstring);
+ /* If we are writing to something known to be a tty we clear the screen
+ after doing newline as the shell tab completion may have written
+ things there. */
+ if (!isatty(fileno(stdin)) || !isatty(fileno(stdout))) {
+ clearscreen = "";
+ }
+
+ erts_printf("\n%s%s", clearscreen, helpstring);
while (1) {
if ((i = sys_get_key(0)) <= 0)
diff --git a/lib/kernel/src/group.erl b/lib/kernel/src/group.erl
index ae568e66ff..0fd66d29cb 100644
--- a/lib/kernel/src/group.erl
+++ b/lib/kernel/src/group.erl
@@ -47,6 +47,7 @@ server(Ancestors, Drv, Shell, Options) ->
end,
put(expand_fun,ExpandFun),
put(echo, proplists:get_value(echo, Options, true)),
+ put(expand_below, proplists:get_value(expand_below, Options, true)),
server_loop(Drv, start_shell(Shell), []).
@@ -621,7 +622,8 @@ get_line1({undefined,{_A,Mode,Char},Cs,Cont,Rs}, Drv, Shell, Ls, 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) ->
+get_line1({Expand, Before, Cs0, Cont,Rs}, Drv, Shell, Ls0, Encoding)
+ when Expand =:= expand; Expand =:= expand_full ->
send_drv_reqs(Drv, Rs),
ExpandFun = get(expand_fun),
{Found, CompleteChars, Matches} = ExpandFun(Before, []),
@@ -633,19 +635,47 @@ get_line1({expand, Before, Cs0, Cont,Rs}, Drv, Shell, Ls0, Encoding) ->
Cs1 = append(CompleteChars, Cs0, Encoding),
MatchStr = case Matches of
- [] -> [];
- _ -> edlin_expand:format_matches(Matches, Width)
- end,
+ [] -> [];
+ _ -> edlin_expand:format_matches(Matches, Width)
+ end,
Cs = case {Cs1, MatchStr} of
- {_,[]} -> Cs1;
- {[],_} ->
- send_drv(Drv, {put_chars, unicode, unicode:characters_to_binary("\n"++MatchStr,unicode)}),
- [$\^L | Cs1];
- {_,[_SingleMatch]} -> Cs1;
- _ ->
- send_drv(Drv, {put_chars, unicode, unicode:characters_to_binary("\n"++MatchStr,unicode)}),
- [$\^L | Cs1]
- end,
+ {_, []} -> Cs1;
+ {Cs1, [_SingleMatch]} when Cs1 =/= [] -> Cs1;
+ _ ->
+ NlMatchStr = unicode:characters_to_binary("\n"++MatchStr),
+ case get(expand_below) of
+ true ->
+ Lines = string:split(string:trim(MatchStr), "\n", all),
+ NoLines = length(Lines),
+ if NoLines > 5, Expand =:= expand ->
+ %% Only show 5 lines to start with
+ [L1,L2,L3,L4,L5|_] = Lines,
+ String = lists:join(
+ $\n,
+ [L1,L2,L3,L4,L5,
+ io_lib:format("Press tab to see all ~p expansions",
+ [edlin_expand:number_matches(Matches)])]),
+ send_drv(Drv, {put_expand, unicode,
+ unicode:characters_to_binary(String)}),
+ Cs1;
+ true ->
+ case get_tty_geometry(Drv) of
+ {_, Rows} when Rows > NoLines ->
+ %% If all lines fit on screen, we expand below
+ send_drv(Drv, {put_expand, unicode, NlMatchStr}),
+ Cs1;
+ _ ->
+ %% If there are more results than fit on
+ %% screen we expand above
+ send_drv(Drv, {put_chars, unicode, NlMatchStr}),
+ [$\^L | Cs1]
+ end
+ end;
+ false ->
+ send_drv(Drv, {put_chars, unicode, NlMatchStr}),
+ [$\^L | Cs1]
+ end
+ 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),
diff --git a/lib/kernel/src/prim_tty.erl b/lib/kernel/src/prim_tty.erl
index 6f984feba9..2b144d4b9e 100644
--- a/lib/kernel/src/prim_tty.erl
+++ b/lib/kernel/src/prim_tty.erl
@@ -138,6 +138,7 @@
unicode,
buffer_before = [], %% Current line before cursor in reverse
buffer_after = [], %% Current line after cursor not in reverse
+ buffer_expand, %% Characters in expand buffer
cols = 80,
rows = 24,
xn = false,
@@ -147,6 +148,7 @@
right = <<"\e[C">>,
%% Tab to next 8 column windows is "\e[1I", for unix "ta" termcap
tab = <<"\e[1I">>,
+ delete_after_cursor = <<"\e[J">>,
insert = false,
delete = false,
position = <<"\e[6n">>, %% "u7" on my Linux
@@ -164,6 +166,7 @@
}.
-type request() ::
{putc, unicode:unicode_binary()} |
+ {expand, unicode:unicode_binary()} |
{insert, unicode:unicode_binary()} |
{delete, integer()} |
{move, integer()} |
@@ -319,6 +322,16 @@ init(State, {unix,_}) ->
false -> (#state{})#state.position
end,
+ %% According to the manual this should only be issued when the cursor
+ %% is at position 0, but until we encounter such a console we keep things
+ %% simple and issue this with the cursor anywhere
+ DeleteAfter = case tgetstr("cd") of
+ {ok, DA} ->
+ DA;
+ false ->
+ (#state{})#state.delete_after_cursor
+ end,
+
State#state{
cols = Cols,
xn = tgetflag("xn"),
@@ -329,8 +342,9 @@ init(State, {unix,_}) ->
insert = Insert,
delete = Delete,
tab = Tab,
- position = Position
- };
+ position = Position,
+ delete_after_cursor = DeleteAfter
+ };
init(State, {win32, _}) ->
State#state{
%% position = false,
@@ -513,6 +527,26 @@ handle_request(State = #state{ options = #{ tty := false } }, Request) ->
_Ignore ->
{<<>>, State}
end;
+%% Clear the expand buffer after the cursor when we handle any request.
+handle_request(State = #state{ buffer_expand = Expand, unicode = U }, Request)
+ when Expand =/= undefined ->
+ BBCols = cols(State#state.buffer_before, U),
+ BACols = cols(State#state.buffer_after, U),
+ ClearExpand = [move_cursor(State, BBCols, BBCols + BACols),
+ State#state.delete_after_cursor,
+ move_cursor(State, BBCols + BACols, BBCols)],
+ {Output, NewState} = handle_request(State#state{ buffer_expand = undefined }, Request),
+ {[ClearExpand, encode(Output, U)], NewState};
+%% Print characters in the expandbuffer after the cursor
+handle_request(State = #state{ unicode = U }, {expand, Binary}) ->
+ BBCols = cols(State#state.buffer_before, U),
+ BACols = cols(State#state.buffer_after, U),
+ Expand = iolist_to_binary(["\r\n",string:trim(Binary, both)]),
+ MoveToEnd = move_cursor(State, BBCols, BBCols + BACols),
+ {ExpandBuffer, NewState} = insert_buf(State#state{ buffer_expand = [] }, Expand),
+ BECols = cols(NewState#state.cols, BBCols + BACols, NewState#state.buffer_expand, U),
+ MoveToOrig = move_cursor(State, BECols, BBCols),
+ {[MoveToEnd, encode(ExpandBuffer, U), MoveToOrig], NewState};
%% putc prints Binary and overwrites any existing characters
handle_request(State = #state{ unicode = U }, {putc, Binary}) ->
%% Todo should handle invalid unicode?
@@ -650,6 +684,13 @@ cols([SkipSeq | T], Unicode) when is_binary(SkipSeq) ->
%% so we skip that
cols(T, Unicode).
+cols(_ColsPerLine, CurrCols, [], _Unicode) ->
+ CurrCols;
+cols(ColsPerLine, CurrCols, ["\r\n" | T], Unicode) ->
+ CurrCols + (ColsPerLine - CurrCols) + cols(ColsPerLine, 0, T, Unicode);
+cols(ColsPerLine, CurrCols, [H | T], Unicode) ->
+ cols(ColsPerLine, CurrCols + cols([H], Unicode), T, Unicode).
+
update_geometry(State) ->
case tty_window_size(State#state.tty) of
{ok, {Cols, Rows}} when Cols > 0 ->
@@ -738,6 +779,10 @@ insert_buf(State, Binary) when is_binary(Binary) ->
insert_buf(State, Binary, [], []).
insert_buf(State, Bin, LineAcc, Acc) ->
case string:next_grapheme(Bin) of
+ [] when State#state.buffer_expand =/= undefined ->
+ Expand = lists:reverse(LineAcc),
+ {[Acc, characters_to_output(Expand)],
+ State#state{ buffer_expand = characters_to_buffer(Expand) }};
[] ->
NewBB = characters_to_buffer(LineAcc) ++ State#state.buffer_before,
NewState = State#state{ buffer_before = NewBB },
@@ -769,12 +814,17 @@ insert_buf(State, Bin, LineAcc, Acc) ->
true ->
<<$\r>>
end,
- insert_buf(State#state{ buffer_before = [], buffer_after = [] }, Rest, [],
- [Acc, [characters_to_output(lists:reverse(LineAcc)), Tail]]);
+ if State#state.buffer_expand =:= undefined ->
+ insert_buf(State#state{ buffer_before = [], buffer_after = [] }, Rest, [],
+ [Acc, [characters_to_output(lists:reverse(LineAcc)), Tail]]);
+ true ->
+ insert_buf(State, Rest, [binary_to_list(Tail) | LineAcc], Acc)
+ end;
[Cluster | Rest] when is_list(Cluster) ->
insert_buf(State, Rest, [Cluster | LineAcc], Acc);
%% We have gotten a code point that may be part of the previous grapheme cluster.
- [Char | Rest] when Char >= 128, LineAcc =:= [], State#state.buffer_before =/= [] ->
+ [Char | Rest] when Char >= 128, LineAcc =:= [], State#state.buffer_before =/= [],
+ State#state.buffer_expand =:= undefined ->
[PrevChar | BB] = State#state.buffer_before,
case string:next_grapheme([PrevChar | Bin]) of
[PrevChar | _] ->
@@ -863,7 +913,7 @@ encode(UnicodeChars, false) ->
-ifdef(debug).
dbg(_) ->
ok.
--endif
+-endif.
%% Nif functions
-spec isatty(stdin | stdout | stderr) -> boolean() | ebadf.
diff --git a/lib/kernel/src/user_drv.erl b/lib/kernel/src/user_drv.erl
index cf88d72a34..1a17409e6b 100644
--- a/lib/kernel/src/user_drv.erl
+++ b/lib/kernel/src/user_drv.erl
@@ -287,7 +287,7 @@ init_remote_shell(State, Node, {M, F, A}) ->
Group = group:start(self(), RShell,
[{echo,State#state.shell_started =:= new}] ++
- remsh_opts(RemoteNode)),
+ group_opts(RemoteNode)),
Gr = gr_add_cur(State#state.groups, Group, RShell),
@@ -311,7 +311,7 @@ init_local_shell(State, InitialShell) ->
Gr = gr_add_cur(State#state.groups,
group:start(self(), InitialShell,
- [{echo,State#state.shell_started =:= new}]),
+ group_opts() ++ [{echo,State#state.shell_started =:= new}]),
InitialShell),
init_shell(State#state{ groups = Gr }, [Slogan,$\n]).
@@ -692,7 +692,7 @@ switch_cmd(r, Gr0) ->
case is_alive() of
true ->
Node = pool:get_node(),
- Pid = group:start(self(), {Node,shell,start,[]}, remsh_opts(Node)),
+ Pid = group:start(self(), {Node,shell,start,[]}, group_opts(Node)),
Gr = gr_add_cur(Gr0, Pid, {Node,shell,start,[]}),
{retry, [], Gr};
false ->
@@ -703,7 +703,7 @@ switch_cmd({r, Node}, Gr) when is_atom(Node)->
switch_cmd({r,Node,Shell}, Gr0) when is_atom(Node), is_atom(Shell) ->
case is_alive() of
true ->
- Pid = group:start(self(), {Node,Shell,start,[]}, remsh_opts(Node)),
+ Pid = group:start(self(), {Node,Shell,start,[]}, group_opts(Node)),
Gr = gr_add_cur(Gr0, Pid, {Node,Shell,start,[]}),
{retry, [], Gr};
false ->
@@ -743,13 +743,17 @@ list_commands() ->
QuitReq ++
[{put_chars, unicode,<<" ? | h - this message\n">>}].
-remsh_opts(Node) ->
+group_opts(Node) ->
VersionString = erpc:call(Node, erlang, system_info, [otp_release]),
Version = list_to_integer(VersionString),
- case Version > 25 of
- true -> [{expand_fun,fun(B, Opts)-> erpc:call(Node,edlin_expand,expand,[B, Opts]) end}];
- false -> [{expand_fun,fun(B, _)-> erpc:call(Node,edlin_expand,expand,[B]) end}]
- end.
+ ExpandFun =
+ case Version > 25 of
+ true -> [{expand_fun,fun(B, Opts)-> erpc:call(Node,edlin_expand,expand,[B, Opts]) end}];
+ false -> [{expand_fun,fun(B, _)-> erpc:call(Node,edlin_expand,expand,[B]) end}]
+ end,
+ group_opts() ++ ExpandFun.
+group_opts() ->
+ [{expand_below, application:get_env(stdlib, shell_expand_location, below) =:= below}].
-spec io_request(request(), prim_tty:state()) -> {noreply, prim_tty:state()} |
{term(), reference(), prim_tty:state()}.
@@ -761,6 +765,8 @@ io_request({put_chars_sync, unicode, Chars, Reply}, TTY) ->
{Output, NewTTY} = prim_tty:handle_request(TTY, {putc, unicode:characters_to_binary(Chars)}),
{ok, MonitorRef} = prim_tty:write(NewTTY, Output, self()),
{Reply, MonitorRef, NewTTY};
+io_request({put_expand, unicode, Chars}, TTY) ->
+ write(prim_tty:handle_request(TTY, {expand, unicode:characters_to_binary(Chars)}));
io_request({move_rel, N}, TTY) ->
write(prim_tty:handle_request(TTY, {move, N}));
io_request({insert_chars, unicode, Chars}, TTY) ->
diff --git a/lib/kernel/test/interactive_shell_SUITE.erl b/lib/kernel/test/interactive_shell_SUITE.erl
index a87983b675..544c285412 100644
--- a/lib/kernel/test/interactive_shell_SUITE.erl
+++ b/lib/kernel/test/interactive_shell_SUITE.erl
@@ -53,6 +53,8 @@
shell_invalid_ansi/1, shell_suspend/1, shell_full_queue/1,
shell_unicode_wrap/1, shell_delete_unicode_wrap/1,
shell_delete_unicode_not_at_cursor_wrap/1,
+ shell_expand_location_above/1,
+ shell_expand_location_below/1,
shell_update_window_unicode_wrap/1,
shell_standard_error_nlcr/1,
remsh_basic/1, remsh_error/1, remsh_longnames/1, remsh_no_epmd/1,
@@ -126,7 +128,9 @@ groups() ->
shell_transpose, shell_search, shell_insert,
shell_update_window, shell_huge_input,
shell_support_ansi_input,
- shell_standard_error_nlcr]}
+ shell_standard_error_nlcr,
+ shell_expand_location_above,
+ shell_expand_location_below]}
].
init_per_suite(Config) ->
@@ -872,6 +876,108 @@ shell_support_ansi_input(Config) ->
ok
end.
+shell_expand_location_below(Config) ->
+
+ Term = start_tty(Config),
+
+ {Rows, _} = get_location(Term),
+
+ NumFunctions = lists:seq(0, Rows*2),
+ FunctionName = "a_long_function_name",
+
+ Module = lists:flatten(
+ ["-module(long_module).\n",
+ "-export([",lists:join($,,[io_lib:format("~s~p/0",[FunctionName,I]) || I <- NumFunctions]),"]).\n\n",
+ [io_lib:format("~s~p() -> ok.\n",[FunctionName,I]) || I <- NumFunctions]]),
+
+ DoScan = fun F([]) ->
+ [];
+ F(Str) ->
+ {done,{ok,T,_},C} = erl_scan:tokens([],Str,0),
+ [ T | F(C) ]
+ end,
+ Forms = [ begin {ok,Y} = erl_parse:parse_form(X),Y end || X <- DoScan(Module) ],
+ {ok,_,Bin} = compile:forms(Forms, [debug_info]),
+
+ try
+ tmux(["resize-window -t ",tty_name(Term)," -x 80"]),
+
+ %% First check that basic completion works
+ send_stdin(Term, "escript:"),
+ send_stdin(Term, "\t"),
+ %% Cursor at correct place
+ check_location(Term, {-3, width("escript:")}),
+ %% Nothing after the start( completion
+ check_content(Term, "start\\($"),
+
+ %% Check that completion is cleared when we type
+ send_stdin(Term, "s"),
+ check_location(Term, {-3, width("escript:s")}),
+ check_content(Term, "escript:s$"),
+
+ %% Check that completion works when in the middle of a term
+ send_tty(Term, "Home"),
+ send_tty(Term, "["),
+ send_tty(Term, "End"),
+ send_tty(Term, ", test_after]"),
+ [send_tty(Term, "Left") || _ <- ", test_after]"],
+ send_stdin(Term, "\t"),
+ check_location(Term, {-3, width("[escript:s")}),
+ check_content(Term, "script_name\\([ ]+start\\($"),
+ send_tty(Term, "End"),
+ send_stdin(Term, ".\n"),
+
+ %% Check that we behave as we should with very long completions
+ rpc(Term, fun() ->
+ {module, long_module} = code:load_binary(long_module, "long_module.beam", Bin)
+ end),
+ check_content(Term, "3>"),
+ send_stdin(Term, "long_module:" ++ FunctionName),
+ send_stdin(Term, "\t"),
+ %% Check that correct text is printed below expansion
+ check_content(Term, io_lib:format("Press tab to see all ~p expansions",
+ [length(NumFunctions)])),
+ send_stdin(Term, "\t"),
+ %% The expansion does not fit on screen, verify that
+ %% expand above mode is used
+ check_content(fun() -> get_content(Term, "-S -5") end,
+ "3> long_module:" ++ FunctionName ++ "\nfunctions"),
+ check_content(Term, "3> long_module:" ++ FunctionName ++ "$"),
+
+ %% We resize the terminal to make everything fit and test that
+ %% expand below mode is used
+ tmux(["resize-window -t ", tty_name(Term), " -y ", integer_to_list(Rows+10)]),
+ timer:sleep(1000), %% Sleep to make sure window has resized
+ send_stdin(Term, "\t\t"),
+ check_content(Term, "3> long_module:" ++ FunctionName ++ "\nfunctions(\n|.)*a_long_function_name99\\($"),
+ ok
+ after
+ stop_tty(Term),
+ ok
+ end.
+
+shell_expand_location_above(Config) ->
+
+ Term = start_tty([{args,["-stdlib","shell_expand_location","above"]}|Config]),
+
+ try
+ tmux(["resize-window -t ",tty_name(Term)," -x 80"]),
+ send_stdin(Term, "escript:"),
+ send_stdin(Term, "\t"),
+ check_location(Term, {0, width("escript:")}),
+ check_content(Term, "start\\(\n"),
+ check_content(Term, "escript:$"),
+ send_stdin(Term, "s"),
+ send_stdin(Term, "\t"),
+ check_location(Term, {0, width("escript:s")}),
+ check_content(Term, "\nscript_name\\([ ]+start\\(\n"),
+ check_content(Term, "escript:s$"),
+ ok
+ after
+ stop_tty(Term),
+ ok
+ end.
+
%% Test the we can handle invalid ansi escape chars.
%% tmux cannot handle this... so we test this using to_erl
shell_invalid_ansi(_Config) ->
@@ -1017,9 +1123,9 @@ shell_standard_error_nlcr(Config) ->
try
rpc(Term, io, format, [standard_error,"test~ntest~ntest", []]),
check_content(Term, "test\ntest\ntest$"),
- rpc(Term, erlang, apply, [fun() -> shell:start_interactive(),
- io:format(standard_error,"test~ntest~ntest", [])
- end, []]),
+ rpc(Term, fun() -> shell:start_interactive(),
+ io:format(standard_error,"test~ntest~ntest", [])
+ end),
check_content(Term, "test\ntest\ntest(\n|.)*test\ntest\ntest")
after
stop_tty(Term)
@@ -1253,6 +1359,8 @@ tmux([Cmd|_] = Command) when is_list(Cmd) ->
tmux(Command) ->
string:trim(os:cmd(["tmux ",Command])).
+rpc(#tmux{ node = N }, Fun) ->
+ erpc:call(N, Fun).
rpc(#tmux{ node = N }, M, F, A) ->
erpc:call(N, M, F, A).
@@ -1265,7 +1373,7 @@ setup_tty(Config) ->
["-env",Key,Value]
end, proplists:get_value(env,Config,[])),
- Args = proplists:get_value(args, Config, []),
+ ExtraArgs = proplists:get_value(args,Config,[]),
ExecArgs = case os:getenv("TMUX_DEBUG") of
"strace" ->
@@ -1294,7 +1402,7 @@ setup_tty(Config) ->
"-kernel","shell_history","disabled",
"-kernel","prevent_overlapping_partitions","false",
"-eval","shell:prompt_func({interactive_shell_SUITE,prompt})."
- ] ++ Envs ++ Args,
+ ] ++ Envs ++ ExtraArgs,
detached => false
},
diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl
index 43237b6141..45c1b069a9 100644
--- a/lib/ssh/src/ssh_cli.erl
+++ b/lib/ssh/src/ssh_cli.erl
@@ -670,7 +670,9 @@ start_shell(ConnectionHandler, State) ->
{_,_,_} = Shell ->
Shell
end,
- State#state{group = group:start(self(), ShellSpawner, [{echo, get_echo(State#state.pty)}]),
+ State#state{group = group:start(self(), ShellSpawner,
+ [{expand_below, false},
+ {echo, get_echo(State#state.pty)}]),
buf = empty_buf()}.
%%--------------------------------------------------------------------
@@ -691,7 +693,8 @@ start_exec_shell(ConnectionHandler, Cmd, State) ->
{M,F,A} ->
{M, F, A++[Cmd]}
end,
- State#state{group = group:start(self(), ExecShellSpawner, [{echo,false}]),
+ State#state{group = group:start(self(), ExecShellSpawner, [{expand_below, false},
+ {echo,false}]),
buf = empty_buf()}.
%%--------------------------------------------------------------------
@@ -775,7 +778,8 @@ exec_in_self_group(ConnectionHandler, ChannelId, WantReply, State, Fun) ->
end
end)
end,
- {ok, State#state{group = group:start(self(), Exec, [{echo,false}]),
+ {ok, State#state{group = group:start(self(), Exec, [{expand_below, false},
+ {echo,false}]),
buf = empty_buf()}}.
diff --git a/lib/stdlib/doc/src/stdlib_app.xml b/lib/stdlib/doc/src/stdlib_app.xml
index b4d0c81c96..e0f50292d6 100644
--- a/lib/stdlib/doc/src/stdlib_app.xml
+++ b/lib/stdlib/doc/src/stdlib_app.xml
@@ -57,6 +57,11 @@
<p>Can be used to set the exception handling of the evaluator process of
Erlang shell.</p>
</item>
+ <tag><marker id="shell_expand_location"/><c>shell_expand_location = above | below</c></tag>
+ <item>
+ <p>Sets where the tab expansion text should appear in the shell.
+ The default is <c>below</c>.</p>
+ </item>
<tag><marker id="shell_history_length"/><c>shell_history_length = integer() >= 0</c></tag>
<item>
<p>Can be used to determine how many commands are saved by the Erlang
diff --git a/lib/stdlib/src/edlin.erl b/lib/stdlib/src/edlin.erl
index 00124bd0b2..6f054d63c0 100644
--- a/lib/stdlib/src/edlin.erl
+++ b/lib/stdlib/src/edlin.erl
@@ -116,10 +116,14 @@ edit([C|Cs], P, {Bef,Aft}, Prefix, Rs0) ->
Rs1 = erase(P, Bef, Aft, Rs0),
Rs = redraw(P, Bef, Aft, Rs1),
edit(Cs, P, {Bef,Aft}, none, Rs);
- tab_expand ->
- {expand, Bef, Cs,
- {line, P, {Bef, Aft}, none},
- reverse(Rs0)};
+ tab_expand ->
+ {expand, Bef, Cs,
+ {line, P, {Bef, Aft}, tab_expand},
+ reverse(Rs0)};
+ tab_expand_full ->
+ {expand_full, Bef, Cs,
+ {line, P, {Bef, Aft}, tab_expand},
+ reverse(Rs0)};
{undefined,C} ->
{undefined,{none,Prefix,C},Cs,{line,P,{Bef,Aft},none},
reverse(Rs0)};
@@ -172,6 +176,8 @@ key_map($\^E, none) -> end_of_line;
key_map($\^F, none) -> forward_char;
key_map($\^H, none) -> backward_delete_char;
key_map($\t, none) -> tab_expand;
+key_map($\t, tab_expand) -> tab_expand_full;
+key_map(C, tab_expand) -> key_map(C, none);
key_map($\^K, none) -> kill_line;
key_map($\^L, none) -> redraw_line;
key_map($\n, none) -> new_line;
diff --git a/lib/stdlib/src/edlin_expand.erl b/lib/stdlib/src/edlin_expand.erl
index cd386bc554..e254de11cf 100644
--- a/lib/stdlib/src/edlin_expand.erl
+++ b/lib/stdlib/src/edlin_expand.erl
@@ -22,7 +22,7 @@
%% filepaths, variable binding, record names, function parameter values,
%% record fields and map keys and record field values.
-include_lib("kernel/include/eep48.hrl").
--export([expand/1, expand/2, expand/3, format_matches/2, get_exports/1,
+-export([expand/1, expand/2, expand/3, format_matches/2, number_matches/1, get_exports/1,
shell_default_or_bif/1, bif/1, over_word/1]).
-export([is_type/3, match_arguments1/3]).
-record(shell_state,{
@@ -1131,6 +1131,13 @@ field_width([], W, LL) when W < LL ->
field_width([], _, LL) ->
LL.
+number_matches([#{ elems := Matches }|T]) ->
+ number_matches(Matches) + number_matches(T);
+number_matches([_|T]) ->
+ 1 + number_matches(T);
+number_matches([]) ->
+ 0.
+
%% Strings are handled naively, but it should be OK here.
longest_common_head([]) ->
no;