diff options
author | Lukas Larsson <lukas@erlang.org> | 2022-05-09 17:00:06 +0200 |
---|---|---|
committer | Lukas Larsson <lukas@erlang.org> | 2022-10-04 14:20:40 +0200 |
commit | 81562f74ad1c2d7f2a814935976f0a8e98003687 (patch) | |
tree | e6604a210a0c9d970658afdc98640d10d6eda2f2 | |
parent | 80225287e0ad19e4d47df57010af688448c42549 (diff) | |
download | erlang-81562f74ad1c2d7f2a814935976f0a8e98003687.tar.gz |
kernel: Add support for expand after current line
-rw-r--r-- | erts/emulator/beam/break.c | 10 | ||||
-rw-r--r-- | lib/kernel/src/group.erl | 56 | ||||
-rw-r--r-- | lib/kernel/src/prim_tty.erl | 62 | ||||
-rw-r--r-- | lib/kernel/src/user_drv.erl | 24 | ||||
-rw-r--r-- | lib/kernel/test/interactive_shell_SUITE.erl | 120 | ||||
-rw-r--r-- | lib/ssh/src/ssh_cli.erl | 10 | ||||
-rw-r--r-- | lib/stdlib/doc/src/stdlib_app.xml | 5 | ||||
-rw-r--r-- | lib/stdlib/src/edlin.erl | 14 | ||||
-rw-r--r-- | lib/stdlib/src/edlin_expand.erl | 9 |
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; |