diff --git a/lib/kernel/src/group.erl b/lib/kernel/src/group.erl index 83cc5ffcf51c..f45eb4938117 100644 --- a/lib/kernel/src/group.erl +++ b/lib/kernel/src/group.erl @@ -601,7 +601,7 @@ 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} -> @@ -609,7 +609,8 @@ get_line1({history_up,Cs,Cont,Rs}, Drv, Shell, Ls0, Encoding) -> 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( @@ -618,7 +619,7 @@ 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} -> @@ -626,7 +627,8 @@ get_line1({history_down,Cs,Cont,Rs}, Drv, Shell, Ls0, Encoding) -> 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, @@ -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), @@ -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] @@ -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). diff --git a/lib/kernel/src/prim_tty.erl b/lib/kernel/src/prim_tty.erl index 5b402c0236e6..7dd47da6e775 100644 --- a/lib/kernel/src/prim_tty.erl +++ b/lib/kernel/src/prim_tty.erl @@ -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, @@ -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)), @@ -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 = [], @@ -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? @@ -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, diff --git a/lib/kernel/src/user_drv.erl b/lib/kernel/src/user_drv.erl index 5017235d3f5a..741b1ea33830 100644 --- a/lib/kernel/src/user_drv.erl +++ b/lib/kernel/src/user_drv.erl @@ -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) @@ -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) -> diff --git a/lib/stdlib/src/c.erl b/lib/stdlib/src/c.erl index 88573d1df11b..f7d760ee2c0e 100644 --- a/lib/stdlib/src/c.erl +++ b/lib/stdlib/src/c.erl @@ -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]). @@ -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) -> diff --git a/lib/stdlib/src/edlin.erl b/lib/stdlib/src/edlin.erl index 431a3dc0ba06..a7ab5c7b4f23 100644 --- a/lib/stdlib/src/edlin.erl +++ b/lib/stdlib/src/edlin.erl @@ -26,7 +26,6 @@ -export([erase_line/0,erase_inp/1,redraw_line/1]). -export([length_before/1,length_after/1,prompt/1]). -export([current_line/1, current_chars/1]). - -export([edit_line1/2]). -export([inverted_space_prompt/1]). -export([keymap/0]). @@ -115,15 +114,19 @@ edit(eof, _, {_,{Bef,Aft0},LA} = L, _, Rs) -> _ -> Aft0 end, {done,L,[],reverse(Rs, [{move_combo,-cp_len(Bef), length(LA), cp_len(Aft1)}])}; -edit(Buf, P, {LB, {Bef,Aft}, LA}=MultiLine, {ShellMode, EscapePrefix}, Rs0) -> +edit(Buf, P, {LB, {Bef,Aft}, LA}=MultiLine, {ShellMode1, EscapePrefix}, Rs0) -> + {ShellMode, NextMode} = case ShellMode1 of + {_, _}=M -> M; + Mode -> {Mode, Mode} + end, case edlin_key:get_valid_escape_key(Buf, EscapePrefix) of {escape_prefix, EscapePrefix1} -> case ShellMode of tab_expand -> edit(Buf, P, MultiLine, {normal, none}, Rs0); - _ -> edit([], P, MultiLine, {ShellMode, EscapePrefix1}, Rs0) + _ -> edit([], P, MultiLine, {NextMode, EscapePrefix1}, Rs0) end; {invalid, _I, Rest} -> - edit(Rest, P, MultiLine, {ShellMode, none}, Rs0); + edit(Rest, P, MultiLine, {NextMode, none}, Rs0); {insert, C1, Cs2} -> %% If its a printable character %% we could in theory override it in the keymap, @@ -138,7 +141,8 @@ edit(Buf, P, {LB, {Bef,Aft}, LA}=MultiLine, {ShellMode, EscapePrefix}, Rs0) -> normal -> {insert, C1}; search when $\s =< C1 ->{insert_search, C1}; search -> search_quit; - tab_expand -> tab_expand_quit + tab_expand -> tab_expand_quit; + help -> tab_expand_quit end, case Op of tab_expand_quit -> @@ -149,10 +153,10 @@ edit(Buf, P, {LB, {Bef,Aft}, LA}=MultiLine, {ShellMode, EscapePrefix}, Rs0) -> case do_op(Op, MultiLine, Rs0) of {blink,N,MultiLine1,Rs} -> edit(Cs2, P, MultiLine1, {blink,N}, Rs); - {redraw, MultiLine1, Rs} -> - edit(Cs2, P, MultiLine1, {ShellMode, none}, redraw(P, MultiLine1, Rs)); - {MultiLine1,Rs} -> - edit(Cs2, P, MultiLine1, {ShellMode, none}, Rs) + {redraw, {_LB1, {_Bef1, _Aft1}, _LA1}=MultiLine1, Rs} -> + edit(Cs2, P, MultiLine1, {NextMode, none}, redraw(P, MultiLine1, Rs)); + {{_LB1, {_Bef1, _Aft1}, _LA1}=MultiLine1,Rs} -> + edit(Cs2, P, MultiLine1, {NextMode, none}, redraw(P, MultiLine1, Rs)) end end; {key, Key, Cs} -> @@ -166,18 +170,24 @@ edit(Buf, P, {LB, {Bef,Aft}, LA}=MultiLine, {ShellMode, EscapePrefix}, Rs0) -> end; {ok, Value0} -> Value0 end, + Cont = {line,P,MultiLine,{NextMode, none}}, + %erlang:display({edit_value, {Key, Value}}), case Value of + {mode, Mode1} -> + %erlang:display({mode, {Mode1,ShellMode}, Buf}), + edit(Buf, P, MultiLine, {{Mode1, ShellMode}, none}, Rs0); none -> edit(Cs, P, MultiLine, {normal,none}, Rs0); - search -> {search,Cs,{line,P,MultiLine,{normal, none}},reverse(Rs0)}; - search_found -> {search_found,Cs,{line,P,MultiLine,{normal, none}},reverse(Rs0)}; - search_cancel -> {search_cancel,Cs,{line,P,MultiLine,{normal, none}},reverse(Rs0)}; - search_quit -> {search_quit,Cs,{line,P,MultiLine,{normal, none}},reverse(Rs0)}; - open_editor -> {open_editor,Cs,{line,P,MultiLine,{normal, none}},reverse(Rs0)}; - history_up -> {history_up,Cs,{line,P,MultiLine,{normal, none}},reverse(Rs0)}; - history_down -> {history_down,Cs,{line,P,MultiLine,{normal, none}},reverse(Rs0)}; + search -> {search,Cs,Cont,reverse(Rs0)}; + search_found -> {search_found,Cs,Cont,reverse(Rs0)}; + search_cancel -> {search_cancel,Cs,Cont,reverse(Rs0)}; + search_quit -> {search_quit,Cs,Cont,reverse(Rs0)}; + format_expression -> {format_expression,Cs,Cont,reverse(Rs0)}; + open_editor -> {open_editor,Cs,Cont,reverse(Rs0)}; + history_up -> {history_up,Cs,Cont,reverse(Rs0)}; + history_down -> {history_down,Cs,Cont,reverse(Rs0)}; new_line -> MultiLine1 = {[lists:reverse(Bef)|LB],{[],Aft},LA}, - edit(Cs, P, MultiLine1, {normal, none}, reverse(redraw(P, MultiLine1, Rs0))); + edit(Cs, P, MultiLine1, {NextMode, none}, reverse(redraw(P, MultiLine1, Rs0))); new_line_finish -> % Move to end {{LB1,{Bef1,[]},[]}, Rs1} = do_op(end_of_expression, MultiLine, Rs0), @@ -185,10 +195,13 @@ edit(Buf, P, {LB, {Bef,Aft}, LA}=MultiLine, {ShellMode, EscapePrefix}, Rs0) -> redraw_line -> Rs1 = erase_line(Rs0), Rs = redraw(P, MultiLine, Rs1), - edit(Cs, P, MultiLine, {normal, none}, Rs); + edit(Cs, P, MultiLine, {NextMode, none}, Rs); clear -> Rs = redraw(P, MultiLine, [clear|Rs0]), - edit(Cs, P, MultiLine, {normal, none}, Rs); + edit(Cs, P, MultiLine, {NextMode, none}, Rs); + help -> + {help, chars_before(MultiLine), Cs,{line, P, MultiLine, {help, none}}, + reverse(Rs0)}; tab_expand -> {expand, chars_before(MultiLine), Cs, {line, P, MultiLine, {tab_expand, none}}, @@ -210,9 +223,9 @@ edit(Buf, P, {LB, {Bef,Aft}, LA}=MultiLine, {ShellMode, EscapePrefix}, Rs0) -> {blink,N,MultiLine1,Rs} -> edit(Cs, P, MultiLine1, {blink,N}, Rs); {redraw, MultiLine1, Rs} -> - edit(Cs, P, MultiLine1, {normal, none}, redraw(P, MultiLine1, Rs)); + edit(Cs, P, MultiLine1, {NextMode, none}, redraw(P, MultiLine1, Rs)); {MultiLine1,Rs} -> - edit(Cs, P, MultiLine1, {ShellMode, none}, Rs) + edit(Cs, P, MultiLine1, {NextMode, none}, Rs) end end end. @@ -247,6 +260,22 @@ do_op({insert,C}, {LB,{[Bef|Bef0], Aft},LA}, Rs) -> %% search: $TERMS %% $ResultLine1 %% $ResultLine2 +do_op(move_expand_up, Cont, Rs) -> + {Cont, [{move_expand, -1}|Rs]}; +do_op(move_expand_down, Cont, Rs) -> + {Cont, [{move_expand, 1}|Rs]}; +do_op({search,move_expand_up}, Cont, Rs) -> + {Cont, [{move_expand, -1}|Rs]}; +do_op({search,move_expand_down}, Cont, Rs) -> + {Cont, [{move_expand, 1}|Rs]}; +do_op(scroll_expand_up, Cont, Rs) -> + {Cont, [{move_expand, -5}|Rs]}; +do_op(scroll_expand_down, Cont, Rs) -> + {Cont, [{move_expand, 5}|Rs]}; +do_op({search,scroll_expand_up}, Cont, Rs) -> + {Cont, [{move_expand, -5}|Rs]}; +do_op({search,scroll_expand_down}, Cont, Rs) -> + {Cont, [{move_expand, 5}|Rs]}; do_op({insert_search, C}, {LB,{Bef, []},LA}, Rs) -> {{LB, {[C|Bef],[]}, LA}, [{insert_chars, unicode, [C]}, delete_after_cursor | Rs]}; @@ -257,9 +286,9 @@ do_op({insert_search, C}, {LB,{Bef, _Aft},LA}, Rs) -> do_op({search, backward_delete_char}, {LB,{[_|Bef], Aft},LA}, Rs) -> Offset= cp_len(Aft)+1, {{LB, {Bef,Aft}, LA}, - [{insert_chars, unicode, Aft}, {delete_chars,-Offset}|Rs]}; + [redraw, {insert_chars, unicode, Aft}, {delete_chars,-Offset}|Rs]}; do_op({search, backward_delete_char}, {LB,{[], Aft},LA}, Rs) -> - {{LB, {[],Aft}, LA}, [{insert_chars, unicode, Aft}, {delete_chars,-cp_len(Aft)}|Rs]}; + {redraw, {LB, {[],Aft}, LA}, [{insert_chars, unicode, Aft}, {delete_chars,-cp_len(Aft)}|Rs]}; do_op({search, skip_up}, {_,{Bef, Aft},_}, Rs) -> Offset= cp_len(Aft), {{[],{[$\^R|Bef],Aft},[]}, % we insert ^R as a flag to whoever called us @@ -287,7 +316,7 @@ do_op(backward_delete_char, {[PrevLine|LB],{[], Aft},LA}, Rs) -> NewLine = {LB, {lists:reverse(PrevLine), Aft}, LA}, {redraw, NewLine,Rs}; do_op(backward_delete_char, {LB,{[GC|Bef], Aft},LA}, Rs) -> - {{LB, {Bef,Aft}, LA},[{delete_chars,-gc_len(GC)}|Rs]}; + {redraw, {LB, {Bef,Aft}, LA},[{delete_chars,-gc_len(GC)}|Rs]}; do_op(forward_delete_word, {LB,{Bef, []},[NextLine|LA]}, Rs) -> NewLine = {LB, {Bef, NextLine}, LA}, {redraw, NewLine, Rs}; @@ -295,7 +324,7 @@ do_op(forward_delete_word, {LB,{Bef, Aft0},LA}, Rs) -> {Aft1,Kill0,N0} = over_non_word(Aft0, [], 0), {Aft,Kill,N} = over_word(Aft1, Kill0, N0), put(kill_buffer, reverse(Kill)), - {{LB, {Bef,Aft}, LA},[{delete_chars,N}|Rs]}; + {redraw, {LB, {Bef,Aft}, LA},[{delete_chars,N}|Rs]}; do_op(backward_delete_word, {[PrevLine|LB],{[], Aft},LA}, Rs) -> NewLine = {LB, {lists:reverse(PrevLine), Aft}, LA}, {redraw, NewLine,Rs}; @@ -340,20 +369,20 @@ do_op(transpose_word, {LB,{Bef0, Aft0},LA}, Rs) -> {Bef3,Word1,B2} = over_word(Bef2, [], B1), {Bef3, Word2B++reverse(Word2A)++NonWord++Word1, Aft1, B2} end, - {{LB, {reverse(TransposedWords)++Bef, Aft}, LA},[{insert_chars_over, unicode, TransposedWords}, {move_rel, -N}|Rs]}; + {redraw,{LB, {reverse(TransposedWords)++Bef, Aft}, LA},[{insert_chars_over, unicode, TransposedWords}, {move_rel, -N}|Rs]}; do_op(kill_word, {LB,{Bef, Aft0},LA}, Rs) -> {Aft1,Kill0,N0} = over_non_word(Aft0, [], 0), {Aft,Kill,N} = over_word(Aft1, Kill0, N0), put(kill_buffer, reverse(Kill)), - {{LB, {Bef,Aft}, LA},[{delete_chars,N}|Rs]}; + {redraw,{LB, {Bef,Aft}, LA},[{delete_chars,N}|Rs]}; do_op(backward_kill_word, {LB,{Bef0, Aft},LA}, Rs) -> {Bef1,Kill0,N0} = over_non_word(Bef0, [], 0), {Bef,Kill,N} = over_word(Bef1, Kill0, N0), put(kill_buffer, Kill), - {{LB,{Bef,Aft},LA},[{delete_chars,-N}|Rs]}; + {redraw,{LB,{Bef,Aft},LA},[{delete_chars,-N}|Rs]}; do_op(kill_line, {LB, {Bef, Aft}, LA}, Rs) -> put(kill_buffer, Aft), - {{LB, {Bef,[]}, LA},[{delete_chars,cp_len(Aft)}|Rs]}; + {redraw,{LB, {Bef,[]}, LA},[{delete_chars,cp_len(Aft)}|Rs]}; do_op(clear_line, _, Rs) -> {redraw, {[], {[],[]},[]}, Rs}; do_op(yank, {LB,{Bef, []},LA}, Rs) -> @@ -361,7 +390,7 @@ do_op(yank, {LB,{Bef, []},LA}, Rs) -> {{LB, {reverse(Kill, Bef),[]}, LA},[{insert_chars, unicode,Kill}|Rs]}; do_op(yank, {LB,{Bef, Aft},LA}, Rs) -> Kill = get(kill_buffer), - {{LB, {reverse(Kill, Bef),Aft}, LA},[{insert_chars, unicode,Kill}|Rs]}; + {redraw,{LB, {reverse(Kill, Bef),Aft}, LA},[{insert_chars, unicode,Kill}|Rs]}; do_op(forward_line, {_,_,[]} = MultiLine, Rs) -> {MultiLine, Rs}; do_op(forward_line, {LB,{Bef, Aft},[AL|LA]}, Rs) -> diff --git a/lib/stdlib/src/edlin_context.erl b/lib/stdlib/src/edlin_context.erl index 589bac4a02e1..7828644a2203 100644 --- a/lib/stdlib/src/edlin_context.erl +++ b/lib/stdlib/src/edlin_context.erl @@ -65,6 +65,7 @@ | {fun_, Mod, Fun} %% cursor is in a fun mod:fun statement | {new_fun, Unfinished} | {function} + | {function, Mod} | {function, Mod, Fun, Args, Unfinished, Nesting} | {map, Binding, Keys} | {map_or_record} @@ -211,7 +212,8 @@ get_context([$:|Bef2], _) -> {Bef3, Mod} = edlin_expand:over_word(Bef2), case edlin_expand:over_word(Bef3) of {_, "fun"} -> {fun_, Mod}; - _ -> {function} + _ when Mod =:= [] -> {function}; + _ -> {function, Mod} end; get_context([$/|Bef1], _) -> {Bef2, Fun} = edlin_expand:over_word(Bef1), @@ -590,7 +592,7 @@ over_to_opening_paren([CC|Stack], [CC|Bef], Word) -> %% Nested parenthesis of sa over_to_opening_paren(Stack, [Q,NEC|Bef], Word) when Q == $"; Q == $', NEC /= $$, NEC /= $\\ -> %% Consume the whole quoted text, it may contain parenthesis which %% would have confused us. - {Bef1, QuotedWord} = over_to_opening_quote(Q, Bef), + {Bef1, QuotedWord} = over_to_opening_quote(Q, [NEC|Bef]), over_to_opening_paren(Stack, Bef1, QuotedWord ++ Word); over_to_opening_paren(CC, [C|Bef], Word) -> over_to_opening_paren(CC, Bef, [C|Word]). diff --git a/lib/stdlib/src/edlin_key.erl b/lib/stdlib/src/edlin_key.erl index 7a9b0eaa2748..e5decc1e5e5b 100644 --- a/lib/stdlib/src/edlin_key.erl +++ b/lib/stdlib/src/edlin_key.erl @@ -113,7 +113,7 @@ get_valid_escape_key(Rest, Acc) -> {invalid, Acc, Rest}. merge(KeyMap) -> - merge(KeyMap, [normal, search, tab_expand], key_map()). + merge(KeyMap, [normal, search, tab_expand, help], key_map()). merge(_, [], KeyMap) -> KeyMap; merge(InputKeyMap, [Mode|ShellModes], KeyMap) -> @@ -152,6 +152,12 @@ merge(InputKeyMap, [Mode|ShellModes], KeyMap) -> key_map() -> #{ normal => normal_map(), search => #{ + "\^[OA" => move_expand_up, + "\^[[A" => move_expand_up, + "\^[OB" => move_expand_down, + "\^[[B" => move_expand_down, + "\^[[6~" => scroll_expand_down, + "\^[[5~" => scroll_expand_up, "\^R" => skip_up, "\^S" => skip_down, "\^[C" => search_cancel, @@ -164,8 +170,23 @@ key_map() -> #{ %% # everything else should exit search mode and edit the search result (search_quit), }, tab_expand => #{ + "\^[OA" => move_expand_up, + "\^[[A" => move_expand_up, + "\^[OB" => move_expand_down, + "\^[[B" => move_expand_down, + "\^[[6~" => scroll_expand_down, + "\^[[5~" => scroll_expand_up, "\t" => tab_expand_full, default => tab_expand_quit %% go to normal mode and evaluate key input again + }, + help => #{ + "\^[OA" => move_expand_up, + "\^[[A" => move_expand_up, + "\^[OB" => move_expand_down, + "\^[[B" => move_expand_down, + "\^[[6~" => scroll_expand_down, + "\^[[5~" => scroll_expand_up, + default => tab_expand_quit %% go to normal mode and evaluate key input again } }. @@ -214,6 +235,7 @@ normal_map() -> "\^[d" => kill_word, "\^[F" => forward_word, "\^[f" => forward_word, + "\^[h" => help, "\^[L" => redraw_line, "\^[l" => redraw_line, "\^[o" => open_editor, @@ -292,16 +314,22 @@ valid_functions() -> forward_delete_word, %% Delete the characters until the closest non-word character forward_line, %% Move forward one line forward_word, %% Move forward one word + help, %% Open up a pager with help for function or module closest to the cursor history_down, %% Move to the next item in the history history_up, %% Move to the previous item in the history - %%jcl_menu, + %%jcl_menu, %% TODO: let all keys fall through here, and then send it back to user_drv that it should enter + %% a new mode kill_line, %% Delete all characters from the cursor to the end of the line and save them in the kill buffer kill_word, %% Delete the word behind the cursor and save it in the kill buffer + move_expand_up, %% Move up one line in the expand area e.g. help or tab completion pager + move_expand_down, %% Move down one line in the expand area e.g. help or tab completion pager new_line_finish, %% Add a newline at the end of the line and try to evaluate the current expression new_line, %% Add a newline at the cursor position none, %% Do nothing open_editor, %% Open the current line in an editor i.e. EDITOR=code -w redraw_line, %% Redraw the current line + scroll_expand_up, %% Scroll up five lines in the expand area e.g. help or tab completion pager + scroll_expand_down, %% Scroll down five lines in the expand area e.g. help or tab completion pager search_cancel, %% Cancel the current search search_found, %% Accept the current search result and submit it search_quit, %% Accept the current search result, but edit it before submitting