Skip to content

Commit

Permalink
stdlib: support paging inside expand area of the shell
Browse files Browse the repository at this point in the history
for search, autocompletion and help results
  • Loading branch information
frazze-jobb committed Oct 30, 2023
1 parent 88bf10f commit 16554f9
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 98 deletions.
121 changes: 64 additions & 57 deletions lib/kernel/src/group.erl
Original file line number Diff line number Diff line change
Expand Up @@ -601,15 +601,16 @@ get_line1({open_editor, _Cs, Cont, Rs}, Drv, Shell, Ls0, Encoding) ->
get_line1(edlin:edit_line(Cs1, NewCont), Drv, Shell, Ls0, Encoding)
end;
%% Move Up, Down in History: Ctrl+P, Ctrl+N
get_line1({history_up,Cs,Cont,Rs}, Drv, Shell, Ls0, Encoding) ->
get_line1({history_up,Cs,{_,_,_,Mode0}=Cont,Rs}, Drv, Shell, Ls0, Encoding) ->
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()),
{more_chars,Ncont,Nrs} = edlin:start(edlin:prompt(Cont)),
{more_chars,{A,B,C,_},Nrs} = edlin:start(edlin:prompt(Cont)),
Ncont = {A,B,C,Mode0},
send_drv_reqs(Drv, Nrs),
get_line1(
edlin:edit_line1(
Expand All @@ -618,15 +619,16 @@ get_line1({history_up,Cs,Cont,Rs}, Drv, Shell, Ls0, Encoding) ->
Ncont),
Drv, Shell, Ls, Encoding)
end;
get_line1({history_down,Cs,Cont,Rs}, Drv, Shell, Ls0, Encoding) ->
get_line1({history_down,Cs,{_,_,_,Mode0}=Cont,Rs}, Drv, Shell, Ls0, Encoding) ->
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()),
{more_chars,Ncont,Nrs} = edlin:start(edlin:prompt(Cont)),
{more_chars,{A,B,C,_},Nrs} = edlin:start(edlin:prompt(Cont)),
Ncont = {A,B,C,Mode0},
send_drv_reqs(Drv, Nrs),
get_line1(edlin:edit_line1(string:to_graphemes(lists:sublist(Lcs,
1,
Expand All @@ -652,8 +654,28 @@ get_line1({search,Cs,Cont,Rs}, Drv, Shell, Ls, Encoding) ->
%% prompt ('N>') and substitute it with the search prompt.
put(search_quit_prompt, Cont),
Pbs = prompt_bytes("\033[;1;4msearch:\033[0m ", Encoding),
{more_chars,Ncont,_Nrs} = edlin:start(Pbs, {search,none}),
send_drv_reqs(Drv, edlin:erase_line()),
{more_chars,Ncont,Nrs} = edlin:start(Pbs, {search,none}),
send_drv_reqs(Drv, Nrs),
get_line1(edlin:edit_line1(Cs, Ncont), Drv, Shell, Ls, Encoding);
get_line1({help, Before, Cs0, Cont, Rs}, Drv, Shell, Ls0, Encoding) ->
send_drv_reqs(Drv, Rs),
{_,Word,_} = edlin:over_word(Before, [], 0),
Docs = case edlin_context:get_context(Before) of
{function, Mod} when Word =/= [] -> try
c:h1(list_to_atom(Mod), list_to_atom(Word))
catch _:_ ->
c:h1(list_to_atom(Mod))
end;
{function, Mod} -> c:h1(list_to_atom(Mod));
{function, Mod, Fun, _Args, _Unfinished, _Nesting} -> c:h1(list_to_atom(Mod), list_to_atom(Fun));
_ -> ""
end,
case Docs of
{error, _} -> send_drv(Drv, beep);
_ -> send_drv(Drv, {put_expand, unicode, ["\n",unicode:characters_to_binary(string:trim(Docs, both))]})
end,
get_line1(edlin:edit_line(Cs0, Cont), 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),
Expand All @@ -677,32 +699,8 @@ get_line1({Expand, Before, Cs0, Cont,Rs}, Drv, Shell, Ls0, Encoding)
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_reqs(Drv, [{put_chars, unicode, NlMatchStr}]),
[$\e, $l | Cs1]
end
end;
send_drv(Drv, {put_expand, unicode, NlMatchStr}),
Cs1;
false ->
send_drv(Drv, {put_chars, unicode, NlMatchStr}),
[$\e, $l | Cs1]
Expand Down Expand Up @@ -750,42 +748,51 @@ get_line1({search_cancel,_Cs,_,Rs}, Drv, Shell, Ls, Encoding) ->
send_drv_reqs(Drv, edlin:redraw_line(NCont)),
get_line1({more_chars, NCont, []}, Drv, Shell, Ls, Encoding);
%% Search mode is entered.
get_line1({What,{line,Prompt,{_,{RevCmd0,_},_},{search, none}},_Rs},
get_line1({What,{line,Prompt,{_,{RevCmd0,_},_},{search, none}}=Cont0,_Rs},
Drv, Shell, Ls0, Encoding) ->
%% Figure out search direction. ^S and ^R are returned through edlin
%% whenever we received a search while being already in search mode.
OldSearch = get(search),
{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}
_ when RevCmd0 =/= OldSearch -> % new search, rewind stack for a proper search.
{fun search_up_stack/2, new_stack(get_lines(Ls0)), RevCmd0};
_ ->
{skip, Ls0, RevCmd0}
end,
put(search, RevCmd),
Cmd = lists:reverse(RevCmd),
{Ls, NewStack} = case Search(Ls1, Cmd) of
{none, Ls2} ->
send_drv(Drv, beep),
put(search_result, []),
send_drv(Drv, delete_line),
send_drv(Drv, {insert_chars, unicode, unicode:characters_to_binary(Prompt++Cmd)}),
{Ls2, {[],{RevCmd, []},[]}};
{Line, Ls2} -> % found. Complete the output edlin couldn't have done.
Lines = string:split(string:to_graphemes(Line), "\n", all),
Output = if length(Lines) > 5 ->
[A,B,C,D,E|_]=Lines,
(["\n " ++ Line1 || Line1 <- [A,B,C,D,E]] ++
[io_lib:format("~n ... (~w lines omitted)",[length(Lines)-5])]);
true -> ["\n " ++ Line1 || Line1 <- Lines]
end,
put(search_result, Lines),
send_drv(Drv, delete_line),
send_drv(Drv, {insert_chars, unicode, unicode:characters_to_binary(Prompt++Cmd)}),
send_drv(Drv, {put_expand_no_trim, unicode, unicode:characters_to_binary(Output)}),
{Ls2, {[],{RevCmd, []},[]}}
end,
Cont = {line,Prompt,NewStack,{search, none}},
more_data(What, Cont, Drv, Shell, Ls, Encoding);
if Search =:= skip ->
send_drv_reqs(Drv, _Rs),
more_data(What, Cont0, Drv, Shell, Ls0, Encoding);
true ->
{Ls, NewStack} = case Search(Ls1, Cmd) of
{none, Ls2} ->
send_drv(Drv, beep),
put(search_result, []),
send_drv(Drv, delete_line),
send_drv(Drv, {insert_chars, unicode, unicode:characters_to_binary(Prompt++Cmd)}),
{Ls2, {[],{RevCmd, []},[]}};
{Line, Ls2} -> % found. Complete the output edlin couldn't have done.
Lines = string:split(string:to_graphemes(Line), "\n", all),
Output = if length(Lines) > 5 ->
[A,B,C,D,E|_]=Lines,
(["\n " ++ Line1 || Line1 <- [A,B,C,D,E]] ++
[io_lib:format("~n ... (~w lines omitted)",[length(Lines)-5])]);
true -> ["\n " ++ Line1 || Line1 <- Lines]
end,
put(search_result, Lines),
send_drv(Drv, delete_line),
send_drv(Drv, {insert_chars, unicode, unicode:characters_to_binary(Prompt++Cmd)}),
send_drv(Drv, {put_expand_no_trim, unicode, unicode:characters_to_binary(Output)}),
{Ls2, {[],{RevCmd, []},[]}}
end,
Cont = {line,Prompt,NewStack,{search, none}},
more_data(What, Cont, Drv, Shell, Ls, Encoding)
end;
get_line1({What,Cont0,Rs}, Drv, Shell, Ls, Encoding) ->
send_drv_reqs(Drv, Rs),
more_data(What, Cont0, Drv, Shell, Ls, Encoding).
Expand Down
49 changes: 43 additions & 6 deletions lib/kernel/src/prim_tty.erl
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
buffer_before = [], %% Current line before cursor in reverse
buffer_after = [], %% Current line after cursor not in reverse
buffer_expand, %% Characters in expand buffer
buffer_expand_row = 1,
cols = 80,
rows = 24,
xn = false,
Expand Down Expand Up @@ -593,7 +594,7 @@ handle_request(State, redraw_prompt) ->
{ClearLine, _} = handle_request(State, delete_line),
{Redraw, NewState} = handle_request(State, redraw_prompt_pre_deleted),
{[ClearLine, Redraw], NewState};
handle_request(State = #state{unicode = U, cols = W}, redraw_prompt_pre_deleted) ->
handle_request(State = #state{unicode = U, cols = W, rows = R}, redraw_prompt_pre_deleted) ->
{Movement, TextInView, EverythingFitsInView} = in_view(State),
{_, NewPrompt} = handle_request(State, new_prompt),
{Redraw, RedrawState} = insert_buf(NewPrompt, unicode:characters_to_binary(TextInView)),
Expand All @@ -607,17 +608,53 @@ handle_request(State = #state{unicode = U, cols = W}, redraw_prompt_pre_deleted)
true when Last =/= [] -> cols(Last, U);
_ -> cols(State#state.buffer_before, U) + cols(State#state.buffer_after,U)
end,
{ExpandBuffer, NewState} = insert_buf(RedrawState#state{ buffer_expand = [] }, iolist_to_binary(BufferExpand)),
ERow = State#state.buffer_expand_row,

BufferExpandLines = string:split(erlang:binary_to_list(BufferExpand), "\n", all),
InputRows = (cols_multiline([State#state.buffer_before ++ State#state.buffer_after], W, U) div W),
ExpandRows = (cols_multiline(BufferExpandLines, W, U) div W),
BufferExpand1 = case ExpandRows > (R-InputRows) of
true -> StatusLine = io_lib:format("\e[37;46mrows ~w to ~w of ~w\e[0m", [ERow, ERow + R-1-InputRows, ExpandRows]),
Cols1 = max(0,W*(R-1-InputRows)),
Cols0 = max(0,W*(ERow-1)),
{_, _, BufferExpandLinesInViewStart, {_, BEStartIVHalf}} = split_cols_multiline(Cols0, BufferExpandLines, U, W),
{_, BufferExpandLinesInViewRev, _, {BEIVHalf, _}} = split_cols_multiline(Cols1, BufferExpandLinesInViewStart++[BEStartIVHalf], U, W),
BEIVHalf1 = case BEIVHalf of [] -> [];
_ -> [BEIVHalf]
end,
ExpandInView = lists:reverse(BEIVHalf1++BufferExpandLinesInViewRev),
["\r\n",lists:join("\n", ExpandInView ++ [StatusLine])];
false ->
["\r\n",BufferExpand]
end,
{ExpandBuffer, NewState} = insert_buf(RedrawState#state{ buffer_expand = [] }, iolist_to_binary(BufferExpand1)),
BECols = cols(W, End, NewState#state.buffer_expand, U),
MoveToEnd = move_cursor(RedrawState, BECols, End),
{[encode(Redraw,U),encode(ExpandBuffer, U), MoveToEnd, Movement], RedrawState}
end,
{Output, State};
handle_request(State = #state{ buffer_expand = Expand, buffer_expand_row = ERow, cols = W, rows = WindowRows, unicode = U}, {move_expand, N}) ->
%% Get number of Lines in terminal window
BufferExpandLines = case Expand of
undefined -> [];
_ -> string:split(erlang:binary_to_list(Expand), "\n", all)
end,
ExpandRows = (cols_multiline(BufferExpandLines, W, U) div W),
InputRows = (cols_multiline([State#state.buffer_before ++ State#state.buffer_after], W, U) div W),
ERow1 = if ExpandRows > WindowRows-InputRows -> %% We need to page expand rows
min(ExpandRows-(WindowRows-InputRows-1),max(1,ERow + N));
true -> 1 %% No need to page expand rows
end,
if ERow =:= ERow1 -> %% We don't need to do anything
{[], State};
true ->
handle_request(State#state{buffer_expand_row = ERow1}, redraw_prompt)
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 ->
{Redraw, NoExpandState} = handle_request(State#state{ buffer_expand = undefined }, redraw_prompt),
{Output, NewState} = handle_request(NoExpandState#state{ buffer_expand = undefined }, Request),
{Redraw, NoExpandState} = handle_request(State#state{ buffer_expand = undefined, buffer_expand_row = 1 }, redraw_prompt),
{Output, NewState} = handle_request(NoExpandState#state{ buffer_expand = undefined, buffer_expand_row = 1 }, Request),
{[encode(Redraw, U), encode(Output, U)], NewState};
handle_request(State, new_prompt) ->
{"", State#state{buffer_before = [],
Expand All @@ -629,7 +666,7 @@ handle_request(State, {expand, Expand}) ->
handle_request(State#state{buffer_expand = Expand}, redraw_prompt);
handle_request(State, {expand_with_trim, Binary}) ->
handle_request(State,
{expand, iolist_to_binary(["\r\n",string:trim(Binary, both)])});
{expand, iolist_to_binary([string:trim(Binary, both)])});
%% putc prints Binary and overwrites any existing characters
handle_request(State = #state{ unicode = U }, {putc, Binary}) ->
%% Todo should handle invalid unicode?
Expand Down Expand Up @@ -917,7 +954,7 @@ in_view(#state{lines_after = LinesAfter, buffer_before = Bef, buffer_after = Aft
InputAfterRows = (cols_multiline(LinesAfter, W, U) div W),
%% Dont print lines after if we have expansion rows
SumRows = InputBeforeRows+ InputRows + ExpandRows + InputAfterRows,
if SumRows > R ->
if SumRows > R ->
RowsLeftAfterInputRows = R - InputRows,
RowsLeftAfterExpandRows = RowsLeftAfterInputRows - ExpandRows,
RowsLeftAfterInputBeforeRows = RowsLeftAfterExpandRows - InputBeforeRows,
Expand Down
3 changes: 3 additions & 0 deletions lib/kernel/src/user_drv.erl
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
%% Put text in expansion area
{put_expand, unicode, binary()} |
{put_expand_no_trim, unicode, binary()} |
{move_expand, -32768..32767} |
%% Move the cursor X characters left or right (negative is left)
{move_rel, -32768..32767} |
%% Move the cursor Y rows up or down (negative is up)
Expand Down Expand Up @@ -793,6 +794,8 @@ io_request({put_expand, unicode, Chars}, TTY) ->
write(prim_tty:handle_request(TTY, {expand_with_trim, unicode:characters_to_binary(Chars)}));
io_request({put_expand_no_trim, unicode, Chars}, TTY) ->
write(prim_tty:handle_request(TTY, {expand, unicode:characters_to_binary(Chars)}));
io_request({move_expand, N}, TTY) ->
write(prim_tty:handle_request(TTY, {move_expand, N}));
io_request({move_rel, N}, TTY) ->
write(prim_tty:handle_request(TTY, {move, N}));
io_request({move_line, R}, TTY) ->
Expand Down
34 changes: 33 additions & 1 deletion lib/stdlib/src/c.erl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
lc_batch/0, lc_batch/1,
i/3,pid/3,m/0,m/1,mm/0,lm/0,
bt/1, q/0,
h/1,h/2,h/3,ht/1,ht/2,ht/3,hcb/1,hcb/2,hcb/3,
h/1,h/2,h/3,h1/1,h1/2,h1/3,ht/1,ht/2,ht/3,hcb/1,hcb/2,hcb/3,
erlangrc/0,erlangrc/1,bi/1, flush/0, regs/0, uptime/0,
nregs/0,pwd/0,ls/0,ls/1,cd/1,memory/1,memory/0, xm/1]).

Expand Down Expand Up @@ -194,6 +194,38 @@ h(Module,Function,Arity) ->
Error ->
Error
end.
-spec h1(module()) -> h_return().
h1(Module) ->
case code:get_doc(Module) of
{ok, #docs_v1{ format = Format } = Docs} when ?RENDERABLE_FORMAT(Format) ->
shell_docs:render(Module, Docs);
{ok, #docs_v1{ format = Enc }} ->
{error, {unknown_format, Enc}};
Error ->
Error
end.

-spec h1(module(),function()) -> hf_return().
h1(Module,Function) ->
case code:get_doc(Module) of
{ok, #docs_v1{ format = Format } = Docs} when ?RENDERABLE_FORMAT(Format) ->
shell_docs:render(Module, Function, Docs);
{ok, #docs_v1{ format = Enc }} ->
{error, {unknown_format, Enc}};
Error ->
Error
end.

-spec h1(module(),function(),arity()) -> hf_return().
h1(Module,Function,Arity) ->
case code:get_doc(Module) of
{ok, #docs_v1{ format = Format } = Docs} when ?RENDERABLE_FORMAT(Format) ->
shell_docs:render(Module, Function, Arity, Docs);
{ok, #docs_v1{ format = Enc }} ->
{error, {unknown_format, Enc}};
Error ->
Error
end.

-spec ht(module()) -> h_return().
ht(Module) ->
Expand Down
Loading

0 comments on commit 16554f9

Please sign in to comment.