From 18468e69e463b69117ce1423ff9c90aad4e60eeb Mon Sep 17 00:00:00 2001 From: Frank Hunleth Date: Mon, 5 Jun 2023 14:51:35 -0400 Subject: [PATCH] erlang: revert multiline editing for OTP 26.0 --- .../26.0/0003-Revert-multiline-editing.patch | 2185 +++++++++++++++++ 1 file changed, 2185 insertions(+) create mode 100644 package/erlang/26.0/0003-Revert-multiline-editing.patch diff --git a/package/erlang/26.0/0003-Revert-multiline-editing.patch b/package/erlang/26.0/0003-Revert-multiline-editing.patch new file mode 100644 index 0000000000..4fc0415fa6 --- /dev/null +++ b/package/erlang/26.0/0003-Revert-multiline-editing.patch @@ -0,0 +1,2185 @@ +From 19fe6e1ba9b679c0903fb6815800ffa9787050aa Mon Sep 17 00:00:00 2001 +From: Frank Hunleth +Date: Mon, 5 Jun 2023 08:57:03 -0400 +Subject: [PATCH] Revert multiline editing + +This reverts commit 16139b6a35944583b4f1ef05c7698bfe2f771c7a, reversing +changes made to 8305dd5e2b962ada3403a6fc0c600367afdfec5e. +--- + lib/kernel/src/group.erl | 348 +++++------ + lib/kernel/src/prim_tty.erl | 380 ++---------- + lib/kernel/src/user_drv.erl | 48 +- + lib/kernel/test/interactive_shell_SUITE.erl | 150 +---- + lib/stdlib/src/edlin.erl | 602 ++++++++------------ + 5 files changed, 487 insertions(+), 1041 deletions(-) + +diff --git a/lib/kernel/src/group.erl b/lib/kernel/src/group.erl +index ca7251d7ec..311c2f3c5e 100644 +--- a/lib/kernel/src/group.erl ++++ b/lib/kernel/src/group.erl +@@ -483,56 +483,50 @@ 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); ++ 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, Buf, start, Encoding). + +-get_chars_loop(Pbs, M, F, Xa, Drv, Shell, Buf0, State, LineCont0, Encoding) -> ++get_chars_loop(Pbs, M, F, Xa, Drv, Shell, Buf0, State, Encoding) -> + Result = case get(echo) of +- true -> +- get_line(Buf0, Pbs, LineCont0, 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, ++ 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,LineCont1,Buf} -> +- get_chars_apply(Pbs, M, F, Xa, Drv, Shell, Buf, State, LineCont1, Encoding); +- +- interrupted -> +- {error,{error,interrupted},[]}; +- terminated -> +- {exit,terminated} ++ {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, LineCont, Encoding) -> +- %% multi line support means that we should not keep the state +- %% but we need to keep it for oldshell mode +- {State, Line} = case get(echo) of +- true -> {start, edlin:current_line(LineCont)}; +- false -> {State0, LineCont} +- end, +- case catch M:F(State, cast(Line,get(read_mode), Encoding), Encoding, Xa) of ++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,eof} -> + {ok,Result,eof}; + {stop,Result,Rest} -> +- _ = case {M,F} of +- {io_lib, get_until} -> +- save_line_buffer(string:trim(Line, both)++"\n", get_lines(new_stack(get(line_buffer)))); +- _ -> +- skip +- end, +- {ok,Result,append(Rest, Buf, Encoding)}; ++ case {M,F} of ++ {io_lib, get_until} -> ++ save_line_buffer(Line, get_lines(new_stack(get(line_buffer)))), ++ {ok,Result,append(Rest, Buf, Encoding)}; ++ _ -> ++ {ok,Result,append(Rest, Buf, Encoding)} ++ end; + {'EXIT',_} -> + {error,{error,err_func(M, F, Xa)},[]}; + State1 -> +- get_chars_loop(Pbs, M, F, Xa, Drv, Shell, Buf, State1, LineCont, Encoding) ++ save_line_buffer(Line, get_lines(new_stack(get(line_buffer)))), ++ 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) -> +@@ -564,73 +558,65 @@ err_func(_, F, _) -> + %% Get a line with eventual line editing. Handle other io requests + %% while getting line. + %% Returns: +-%% {done,LineChars,RestChars} +-%% interrupted +-get_line(Chars, Pbs, Cont, Drv, Shell, Encoding) -> +- {more_chars,Cont1,Rs} = case Cont of +- [] -> edlin:start(Pbs); +- _ -> edlin:start(Pbs, Cont) +- end, ++%% {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, Cont1), Drv, Shell, new_stack(get(line_buffer)), +- Encoding). ++ get_line1(edlin:edit_line(Chars, Cont), Drv, Shell, new_stack(get(line_buffer)), ++ Encoding). + +-get_line1({done, Cont, Rest, Rs}, Drv, _Shell, _Ls, _Encoding) -> ++get_line1({done,Line,Rest,Rs}, Drv, _Shell, _Ls, _Encoding) -> + send_drv_reqs(Drv, Rs), +- {done, Cont, Rest}; ++ {done,Line,Rest}; + get_line1({undefined,{_A, Mode, Char}, _Cs, Cont, Rs}, Drv, Shell, Ls0, Encoding) +- when Mode =:= none, Char =:= $\^O; +- Mode =:= meta, Char =:= $o -> ++ when ((Mode =:= none) and (Char =:= $\^O)) -> + send_drv_reqs(Drv, Rs), +- Buffer = edlin:current_line(Cont), ++ Buffer = edlin:current_chars(Cont), + send_drv(Drv, {open_editor, Buffer}), + receive + {Drv, {editor_data, Cs}} -> +- send_drv_reqs(Drv, edlin:erase_line()), ++ send_drv_reqs(Drv, edlin:erase_line(Cont)), + {more_chars,NewCont,NewRs} = edlin:start(edlin:prompt(Cont)), + send_drv_reqs(Drv, NewRs), + get_line1(edlin:edit_line(Cs, NewCont), Drv, Shell, Ls0, Encoding) + end; +-%% Move Up, Down in History: Ctrl+P, Ctrl+N + get_line1({undefined,{_A,Mode,Char},Cs,Cont,Rs}, Drv, Shell, Ls0, Encoding) +- when Mode =:= none, Char =:= $\^P; +- Mode =:= meta_left_sq_bracket, Char =:= $A -> ++ 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()), +- {more_chars,Ncont,Nrs} = edlin:start(edlin:prompt(Cont)), +- send_drv_reqs(Drv, Nrs), +- get_line1(edlin:edit_line1(string:to_graphemes(lists:sublist(Lcs, +- 1, +- length(Lcs)-1)), +- Ncont), +- Drv, +- Shell, +- Ls, Encoding) ++ {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, Char =:= $\^N; +- Mode =:= meta_left_sq_bracket, Char =:= $B -> ++ 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()), +- {more_chars,Ncont,Nrs} = edlin:start(edlin:prompt(Cont)), +- send_drv_reqs(Drv, Nrs), +- get_line1(edlin:edit_line1(string:to_graphemes(lists:sublist(Lcs, +- 1, +- length(Lcs)-1)), +- Ncont), +- Drv, +- Shell, +- Ls, Encoding) ++ {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 +@@ -643,13 +629,15 @@ get_line1({undefined,{_A,Mode,Char},Cs,Cont,Rs}, Drv, Shell, Ls0, Encoding) + %% 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, Char =:= $\^R -> ++ 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. +- put(search_quit_prompt, Cont), +- Pbs = prompt_bytes("\033[;1;4msearch:\033[0m ", Encoding), +- {more_chars,Ncont,_Nrs} = edlin:start(Pbs, search), ++ 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) + when Expand =:= expand; Expand =:= expand_full -> +@@ -696,98 +684,66 @@ get_line1({Expand, Before, Cs0, Cont,Rs}, Drv, Shell, Ls0, Encoding) + _ -> + %% If there are more results than fit on + %% screen we expand above +- send_drv_reqs(Drv, [{put_chars_keep_state, unicode, NlMatchStr},redraw_prompt]), ++ send_drv(Drv, {put_chars, unicode, NlMatchStr}), + [$\e, $l | Cs1] + end + end; + false -> +- send_drv(Drv, {put_chars_keep_state, unicode, NlMatchStr}), ++ send_drv(Drv, {put_chars, unicode, NlMatchStr}), + [$\e, $l | Cs1] + end + end, + get_line1(edlin:edit_line(Cs, Cont), Drv, Shell, Ls0, Encoding); +-get_line1({undefined, {_, search_quit, _}, _Cs, _Cont={line, P, Line, none}, Rs}, Drv, Shell, Ls, Encoding) -> +- get_line1({more_chars, {line, P, Line, search_quit}, Rs}, Drv, Shell, Ls, 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,{line,_,_Drv,search_found},Rs}, Drv, Shell, Ls0, Encoding) -> +- SearchResult = get(search_result), +- LineCont = case SearchResult of +- [] -> {[],{[],[]},[]}; +- _ -> [Last| LB] = lists:reverse(SearchResult), +- {LB, {lists:reverse(Last),[]},[]} +- end, +- Prompt = edlin:prompt(get(search_quit_prompt)), +- send_drv_reqs(Drv, Rs), +- send_drv_reqs(Drv, edlin:erase_line()), +- send_drv_reqs(Drv, edlin:redraw_line({line, Prompt, LineCont, none})), +- put(search_result, []), +- %% TODO, do even need to save it, won't it be saved by handling {done...}? +- Ls = save_line(new_stack(get_lines(Ls0)), edlin:current_line({line, edlin:prompt(get(search_quit_prompt)), LineCont, none})), +- get_line1({done, LineCont, "\n", Rs}, Drv, Shell, Ls, Encoding); ++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,{line,_,_,search_quit},Rs}, Drv, Shell, Ls, Encoding) -> ++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 edlin:prompt(get(search_quit_prompt)) of +- Prompt -> % redraw the line and keep going with the same stack position +- SearchResult = get(search_result), +- L = case SearchResult of +- [] -> {[],{[],[]},[]}; +- _ -> [Last|LB] = lists:reverse(SearchResult), +- {LB, {lists:reverse(Last), []}, []} +- end, +- NCont = {line,Prompt,L,none}, +- put(search_result, []), +- send_drv_reqs(Drv, [delete_line|Rs]), +- send_drv_reqs(Drv, edlin:redraw_line(NCont)), +- get_line1({What, NCont ,[]}, Drv, Shell, pad_stack(Ls), Encoding) ++ 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; +-get_line1({What,_Cont={line,_,_,search_cancel},Rs}, Drv, Shell, Ls, Encoding) -> +- NCont = get(search_quit_prompt), +- put(search_result, []), +- send_drv_reqs(Drv, [delete_line|Rs]), +- send_drv_reqs(Drv, edlin:redraw_line(NCont)), +- get_line1({What, NCont, []}, Drv, Shell, Ls, Encoding); + %% Search mode is entered. +-get_line1({What,{line,Prompt,{_,{RevCmd0,_},_},search},_Rs}, +- Drv, Shell, Ls0, Encoding) -> ++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, ++ [$\^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), +- put(search_result, []), +- send_drv(Drv, delete_line), +- send_drv(Drv, {put_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, {put_chars, unicode, unicode:characters_to_binary(Prompt++Cmd)}), +- send_drv(Drv, {put_expand_no_trim, unicode, unicode:characters_to_binary(Output)}), +- {Ls2, {[],{RevCmd, []},[]}} +- end, ++ {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) -> +@@ -796,32 +752,29 @@ get_line1({What,Cont0,Rs}, Drv, Shell, Ls, Encoding) -> + + more_data(What, Cont0, Drv, Shell, Ls, Encoding) -> + receive +- {Drv, activate} -> +- send_drv_reqs(Drv, edlin:redraw_line(Cont0)), +- more_data(What, Cont0, Drv, Shell, Ls, Encoding); +- {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()), +- 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); ++ {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) ++ {'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) ++ get_line_timeout(What)-> ++ get_line1(edlin:edit_line([], Cont0), Drv, Shell, Ls, Encoding) + end. + + get_line_echo_off(Chars, Pbs, Drv, Shell) -> +@@ -861,21 +814,21 @@ get_chars_echo_off1(Drv, Shell) -> + receive + {Drv, {data, Cs}} -> + Cs; +- {Drv, eof} -> ++ {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); ++ {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) ++ {'EXIT',Drv,interrupt} -> ++ interrupted; ++ {'EXIT',Drv,_} -> ++ terminated; ++ {'EXIT',Shell,R} -> ++ exit(R) + end. + + %% We support line editing for the ICANON mode except the following +@@ -956,8 +909,8 @@ get_all_lines({stack, U, {}, []}) -> + U; + get_all_lines({stack, U, {}, D}) -> + case lists:reverse(D, U) of +- ["\n"|Lines] -> Lines; +- Lines -> Lines ++ ["\n"|Lines] -> Lines; ++ Lines -> Lines + end; + get_all_lines({stack, U, L, D}) -> + get_all_lines({stack, U, {}, [L|D]}). +@@ -981,22 +934,22 @@ save_line_buffer(Lines) -> + + search_up_stack(Stack, Substr) -> + case up_stack(Stack) of +- {none,NewStack} -> {none,NewStack}; +- {L, NewStack} -> ++ {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 + 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 ++ {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. + + +@@ -1010,15 +963,16 @@ get_password1({Chars,[]}, Drv, Shell) -> + {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); ++ 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); ++ get_password1({Chars, []},Drv, Shell); + {'EXIT',Drv,interrupt} -> + interrupted; + {'EXIT',Drv,_} -> +diff --git a/lib/kernel/src/prim_tty.erl b/lib/kernel/src/prim_tty.erl +index a03e746cc9..4ef10c752d 100644 +--- a/lib/kernel/src/prim_tty.erl ++++ b/lib/kernel/src/prim_tty.erl +@@ -136,8 +136,6 @@ + writer, + options, + unicode, +- lines_before = [], %% All lines before the current line in reverse order +- lines_after = [], %% All lines after the current line. + buffer_before = [], %% Current line before cursor in reverse + buffer_after = [], %% Current line after cursor not in reverse + buffer_expand, %% Characters in expand buffer +@@ -168,20 +166,10 @@ + -type request() :: + {putc_raw, binary()} | + {putc, unicode:unicode_binary()} | +- {putc_keep_state, unicode:unicode_binary()} | + {expand, unicode:unicode_binary()} | +- {expand_with_trim, unicode:unicode_binary()} | + {insert, unicode:unicode_binary()} | + {delete, integer()} | +- delete_after_cursor | +- delete_line | +- redraw_prompt | +- {redraw_prompt, string(), string(), tuple()} | +- redraw_prompt_pre_deleted | +- new_prompt | + {move, integer()} | +- {move_line, integer()} | +- {move_combo, integer(), integer(), integer()} | + clear | + beep. + -opaque state() :: #state{}. +@@ -288,9 +276,9 @@ init(State, {unix,_}) -> + %% See https://www.gnu.org/software/termutils/manual/termcap-1.3/html_mono/termcap.html#SEC23 + %% for a list of all possible termcap capabilities + Clear = case tgetstr("clear") of +- {ok, C} -> C; +- false -> (#state{})#state.clear +- end, ++ {ok, C} -> C; ++ false -> (#state{})#state.clear ++ end, + Cols = case tgetnum("co") of + {ok, Cs} -> Cs; + _ -> (#state{})#state.cols +@@ -333,7 +321,7 @@ init(State, {unix,_}) -> + {ok, <<"\e[6n">> = U7} -> + %% User 7 should contain the codes for getting + %% cursor position. +- %% User 6 should contain how to parse the reply ++ % User 6 should contain how to parse the reply + {ok, <<"\e[%i%d;%dR">>} = tgetstr("u6"), + <<"\e[6n">> = U7; + false -> (#state{})#state.position +@@ -547,62 +535,26 @@ handle_request(State = #state{ options = #{ tty := false } }, Request) -> + _Ignore -> + {<<>>, State} + end; +-handle_request(State, {redraw_prompt, Pbs, Pbs2, {LB, {Bef, Aft}, LA}}) -> +- {ClearLine, Cleared} = handle_request(State, delete_line), +- CL = lists:reverse(Bef,Aft), +- Text = Pbs ++ lists:flatten(lists:join("\n"++Pbs2, lists:reverse(LB)++[CL|LA])), +- Moves = if LA /= [] -> +- [Last|_] = lists:reverse(LA), +- {move_combo, -logical(Last), length(LA), logical(Bef)}; +- true -> +- {move, -logical(Aft)} +- end, +- {_, InsertedText} = handle_request(Cleared, {insert, unicode:characters_to_binary(Text)}), +- {_, Moved} = handle_request(InsertedText, Moves), +- {Redraw, NewState} = handle_request(Moved, redraw_prompt_pre_deleted), +- {[ClearLine, Redraw], NewState}; +-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) -> +- {Movement, TextInView} = in_view(State), +- {_, NewPrompt} = handle_request(State, new_prompt), +- {Redraw, RedrawState} = insert_buf(NewPrompt#state{xn = false}, unicode:characters_to_binary(TextInView)), +- {Output, _} = case State#state.buffer_expand of +- undefined -> +- {[encode(Redraw, U), xnfix(RedrawState, RedrawState#state.buffer_before), Movement], RedrawState}; +- BufferExpand -> +- BBCols = cols(State#state.buffer_before, U), +- End = BBCols + cols(State#state.buffer_after,U), +- {ExpandBuffer, NewState} = insert_buf(RedrawState#state{ buffer_expand = [] }, iolist_to_binary(BufferExpand)), +- 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}; + %% Clear the expand buffer after the cursor when we handle any request. +-handle_request(State = #state{ buffer_expand = Expand, unicode = U}, 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), +- {[encode(Redraw, U), encode(Output, U)], NewState}; +-handle_request(State, new_prompt) -> +- {"", State#state{buffer_before = [], +- buffer_after = [], +- lines_before = [], +- lines_after = []}}; ++ 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, {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)])}); +-%% putc_keep_state prints Binary and keeps the current prompt unchanged +-handle_request(State = #state{ unicode = U }, {putc_keep_state, Binary}) -> +- {PutBuffer, _NewState} = insert_buf(State, Binary), +- {encode(PutBuffer, U), State}; ++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? +@@ -611,136 +563,36 @@ handle_request(State = #state{ unicode = U }, {putc, Binary}) -> + {encode(PutBuffer, U), NewState}; + true -> + %% Delete any overwritten characters after current the cursor +- OldLength = logical(State#state.buffer_before) + lists:sum([logical(L) || L <- State#state.lines_before]), +- NewLength = logical(NewState#state.buffer_before) + lists:sum([logical(L) || L <- NewState#state.lines_before]), ++ OldLength = logical(State#state.buffer_before), ++ NewLength = logical(NewState#state.buffer_before), + {_, _, _, NewBA} = split(NewLength - OldLength, NewState#state.buffer_after, U), + {encode(PutBuffer, U), NewState#state{ buffer_after = NewBA }} + end; + handle_request(State, {putc_raw, Binary}) -> + handle_request(State, {putc, unicode:characters_to_binary(Binary, latin1)}); +-handle_request(State = #state{}, delete_after_cursor) -> +- {[State#state.delete_after_cursor], +- State#state{buffer_after = [], +- lines_after = []}}; +-handle_request(State = #state{unicode = U, cols = W, buffer_before = Bef, +- lines_before = LinesBefore, +- lines_after = _LinesAfter}, delete_line) -> +- MoveToBeg = move_cursor(State, cols_multiline(Bef, LinesBefore, W, U), 0), +- {[MoveToBeg, State#state.delete_after_cursor], +- State#state{buffer_before = [], +- buffer_after = [], +- lines_before = [], +- lines_after = []}}; +-handle_request(State = #state{ unicode = U, cols = W }, {delete, N}) when N > 0 -> ++handle_request(State = #state{ unicode = U }, {delete, N}) when N > 0 -> + {_DelNum, DelCols, _, NewBA} = split(N, State#state.buffer_after, U), + BBCols = cols(State#state.buffer_before, U), +- BACols = cols(State#state.buffer_after, U), + NewBACols = cols(NewBA, U), +- Output = [encode(NewBA, U), +- lists:duplicate(DelCols, $\s), +- xnfix(State, BBCols + NewBACols + DelCols), +- move_cursor(State, +- BBCols + NewBACols + DelCols, +- BBCols)], +- NewState0 = State#state{ buffer_after = NewBA }, +- if State#state.lines_after =/= [], (BBCols + BACols-N) rem W =:= 0 -> +- {Delete, _} = handle_request(State, delete_line), +- {Redraw, NewState1} = handle_request(NewState0, redraw_prompt_pre_deleted), +- {[Delete, Redraw], NewState1}; +- true -> +- {Output, NewState0} +- end; +-handle_request(State = #state{ unicode = U, cols = W }, {delete, N}) when N < 0 -> ++ {[encode(NewBA, U), ++ lists:duplicate(DelCols, $\s), ++ xnfix(State, BBCols + NewBACols + DelCols), ++ move_cursor(State, ++ BBCols + NewBACols + DelCols, ++ BBCols)], ++ State#state{ buffer_after = NewBA }}; ++handle_request(State = #state{ unicode = U }, {delete, N}) when N < 0 -> + {_DelNum, DelCols, _, NewBB} = split(-N, State#state.buffer_before, U), +- BBCols = cols(State#state.buffer_before, U), +- BACols = cols(State#state.buffer_after, U), + NewBBCols = cols(NewBB, U), +- Output = [move_cursor(State, NewBBCols + DelCols, NewBBCols), +- encode(State#state.buffer_after,U), +- lists:duplicate(DelCols, $\s), +- xnfix(State, NewBBCols + BACols + DelCols), +- move_cursor(State, NewBBCols + BACols + DelCols, NewBBCols)], +- NewState0 = State#state{ buffer_before = NewBB }, +- if State#state.lines_after =/= [], (BBCols+BACols+N) rem W =:= 0 -> +- {Delete, _} = handle_request(State, delete_line), +- {Redraw, NewState1} = handle_request(NewState0, redraw_prompt_pre_deleted), +- {[Delete, Redraw], NewState1}; +- true -> +- {Output, NewState0} +- end; ++ BACols = cols(State#state.buffer_after, U), ++ {[move_cursor(State, NewBBCols + DelCols, NewBBCols), ++ encode(State#state.buffer_after,U), ++ lists:duplicate(DelCols, $\s), ++ xnfix(State, NewBBCols + BACols + DelCols), ++ move_cursor(State, NewBBCols + BACols + DelCols, NewBBCols)], ++ State#state{ buffer_before = NewBB } }; + handle_request(State, {delete, 0}) -> + {"",State}; +-%% {move_combo, before_line_movement, line_movement, after_line_movement} +-%% Many of the move operations comes in threes, this is a helper to make +-%% movement a little bit easier. We move to the beginning of +-%% the line before switching line and then move to the right column on +-%% the next line. +-handle_request(State, {move_combo, V1, L, V2}) -> +- {Moves1, NewState1} = handle_request(State, {move, V1}), +- {Moves2, NewState2} = handle_request(NewState1, {move_line, L}), +- {Moves3, NewState3} = handle_request(NewState2, {move, V2}), +- {Moves1 ++ Moves2 ++ Moves3, NewState3}; +-handle_request(State = #state{ cols = W, +- rows = R, +- unicode = U, +- buffer_before = Bef, +- buffer_after = Aft, +- lines_before = LinesBefore, +- lines_after = LinesAfter}, +- {move_line, L}) when L < 0, length(LinesBefore) >= -L -> +- {LinesJumped, [B|NewLinesBefore]} = lists:split(-L -1, LinesBefore), +- PrevLinesCols = cols_multiline([B|LinesJumped], W, U), +- N_Cols = min(cols(Bef, U), cols(B, U)), +- {_, _, NewBB, NewBA} = split_cols(N_Cols, B, U), +- Moves = move_cursor(State, PrevLinesCols, 0), +- CL = lists:reverse(Bef,Aft), +- NewLinesAfter = lists:reverse([CL|LinesJumped], LinesAfter), +- NewState = State#state{buffer_before = NewBB, +- buffer_after = NewBA, +- lines_before = NewLinesBefore, +- lines_after = NewLinesAfter}, +- RowsInView = cols_multiline([B,CL|LinesBefore], W, U) div W, +- Output = if +- %% When we move up and the view is "full" +- RowsInView >= R -> +- {Movement, TextInView} = in_view(NewState), +- {ClearLine, Cleared} = handle_request(State, delete_line), +- {Redraw, _} = handle_request(Cleared, {insert, unicode:characters_to_binary(TextInView)}), +- [ClearLine, Redraw, Movement]; +- true -> Moves +- end, +- {Output, NewState}; +-handle_request(State = #state{ cols = W, +- rows = R, +- unicode = U, +- buffer_before = Bef, +- buffer_after = Aft, +- lines_before = LinesBefore, +- lines_after = LinesAfter}, +- {move_line, L}) when L > 0, length(LinesAfter) >= L -> +- {LinesJumped, [A|NewLinesAfter]} = lists:split(L - 1, LinesAfter), +- NextLinesCols = cols_multiline([(Bef++Aft)|LinesJumped], W, U), +- N_Cols = min(cols(Bef, U), cols(A, U)), +- {_, _, NewBB, NewBA} = split_cols(N_Cols, A, U), +- Moves = move_cursor(State, 0, NextLinesCols), +- CL = lists:reverse(Bef, Aft), +- NewLinesBefore = lists:reverse([CL|LinesJumped],LinesBefore), +- NewState = State#state{buffer_before = NewBB, +- buffer_after = NewBA, +- lines_before = NewLinesBefore, +- lines_after = NewLinesAfter}, +- RowsInView = cols_multiline([A|NewLinesBefore], W, U) div W, +- Output = if +- RowsInView >= R -> +- {Movement, TextInView} = in_view(NewState), +- {ClearLine, Cleared} = handle_request(State, delete_line), +- {Redraw, _} = handle_request(Cleared, {insert, unicode:characters_to_binary(TextInView)}), +- [ClearLine, Redraw, Movement]; +- true -> Moves +- end, +- {Output, NewState}; +-handle_request(State, {move_line, _}) -> +- {"", State}; + handle_request(State = #state{ unicode = U }, {move, N}) when N < 0 -> + {_DelNum, DelCols, NewBA, NewBB} = split(-N, State#state.buffer_before, U), + NewBBCols = cols(NewBB, U), +@@ -750,57 +602,29 @@ handle_request(State = #state{ unicode = U }, {move, N}) when N < 0 -> + handle_request(State = #state{ unicode = U }, {move, N}) when N > 0 -> + {_DelNum, DelCols, NewBB, NewBA} = split(N, State#state.buffer_after, U), + BBCols = cols(State#state.buffer_before, U), +- Moves = move_cursor(State, BBCols, BBCols + DelCols), +- {Moves, State#state{ buffer_after = NewBA, +- buffer_before = NewBB ++ State#state.buffer_before} }; ++ {move_cursor(State, BBCols, BBCols + DelCols), ++ State#state{ buffer_after = NewBA, ++ buffer_before = NewBB ++ State#state.buffer_before} }; + handle_request(State, {move, 0}) -> + {"",State}; +-handle_request(State = #state{cols = W, xn = OrigXn, unicode = U,lines_after = LinesAfter}, {insert, Chars}) -> ++handle_request(State = #state{ xn = OrigXn, unicode = U }, {insert, Chars}) -> + {InsertBuffer, NewState0} = insert_buf(State#state{ xn = false }, Chars), +- NewState1 = NewState0#state{ xn = OrigXn }, +- NewBBCols = cols(NewState1#state.buffer_before, U), +- NewBACols = cols(NewState1#state.buffer_after, U), +- Output = [ encode(InsertBuffer, U), +- encode(NewState1#state.buffer_after, U), +- xnfix(State, NewBBCols + NewBACols), +- move_cursor(State, NewBBCols + NewBACols, NewBBCols) ], +- if LinesAfter =:= []; (NewBBCols + NewBACols) rem W =:= 0 -> +- {Output, NewState1}; +- true -> +- {Delete, _} = handle_request(State, delete_line), +- {Redraw, NewState2} = handle_request(NewState1, redraw_prompt_pre_deleted), +- {[Delete, Redraw,""], NewState2} +- end; ++ NewState = NewState0#state{ xn = OrigXn }, ++ BBCols = cols(NewState#state.buffer_before, U), ++ BACols = cols(NewState#state.buffer_after, U), ++ {[ encode(InsertBuffer, U), ++ encode(NewState#state.buffer_after, U), ++ xnfix(State, BBCols + BACols), ++ move_cursor(State, BBCols + BACols, BBCols) ], ++ NewState}; + handle_request(State, beep) -> + {<<7>>, State}; + handle_request(State, clear) -> +- {State#state.clear, State#state{buffer_before = [], +- buffer_after = [], +- lines_before = [], +- lines_after = []}}; ++ {State#state.clear, State}; + handle_request(State, Req) -> + erlang:display({unhandled_request, Req}), + {"", State}. + +-%% Split the buffer after N cols +-%% Returns the number of characters deleted, and the column length (N) +-%% of those characters. +-split_cols(N_Cols, Buff, Unicode) -> +- split_cols(N_Cols, Buff, [], 0, 0, Unicode). +-split_cols(N, [SkipChars | T], Acc, Cnt, Cols, Unicode) when is_binary(SkipChars) -> +- split_cols(N, T, [SkipChars | Acc], Cnt, Cols, Unicode); +-split_cols(0, Buff, Acc, Chars, Cols, _Unicode) -> +- {Chars, Cols, Acc, Buff}; +-split_cols(N, _Buff, _Acc, _Chars, _Cols, _Unicode) when N < 0 -> +- error; +-split_cols(_N, [], Acc, Chars, Cols, _Unicode) -> +- {Chars, Cols, Acc, []}; +-split_cols(N, [Char | T], Acc, Cnt, Cols, Unicode) when is_integer(Char) -> +- split_cols(N - npwcwidth(Char), T, [Char | Acc], Cnt + 1, Cols + npwcwidth(Char, Unicode), Unicode); +-split_cols(N, [Chars | T], Acc, Cnt, Cols, Unicode) when is_list(Chars) -> +- split_cols(N - length(Chars), T, [Chars | Acc], +- Cnt + length(Chars), Cols + cols(Chars, Unicode), Unicode). +- + %% Split the buffer after N logical characters returning + %% the number of real characters deleted and the column length + %% of those characters +@@ -811,7 +635,7 @@ split(0, Buff, Acc, Chars, Cols, _Unicode) -> + ?dbg({?FUNCTION_NAME, {Chars, Cols, Acc, Buff}}), + {Chars, Cols, Acc, Buff}; + split(N, _Buff, _Acc, _Chars, _Cols, _Unicode) when N < 0 -> +- error; ++ ok = N; + split(_N, [], Acc, Chars, Cols, _Unicode) -> + {Chars, Cols, Acc, []}; + split(N, [Char | T], Acc, Cnt, Cols, Unicode) when is_integer(Char) -> +@@ -861,77 +685,6 @@ move(left, #state{ left = Left }, N) -> + move(right, #state{ right = Right }, N) -> + lists:duplicate(N, Right). + +-in_view(#state{lines_after = LinesAfter, buffer_before = Bef, buffer_after = Aft, lines_before = LinesBefore, rows=R, cols=W, unicode=U, buffer_expand = BufferExpand} = State) -> +- BufferExpandLines = case BufferExpand of +- undefined -> []; +- _ -> string:split(erlang:binary_to_list(BufferExpand), "\r\n", all) +- end, +- ExpandRows = (cols_multiline(BufferExpandLines, W, U) div W), +- InputBeforeRows = (cols_multiline(LinesBefore, W, U) div W), +- InputRows = (cols_multiline([Bef ++ Aft], W, U) div W), +- 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 -> +- RowsLeftAfterInputRows = R - InputRows, +- RowsLeftAfterExpandRows = RowsLeftAfterInputRows - ExpandRows, +- RowsLeftAfterInputBeforeRows = RowsLeftAfterExpandRows - InputBeforeRows, +- Cols1 = max(0,W*max(RowsLeftAfterInputBeforeRows, RowsLeftAfterExpandRows)), +- {_, LBAfter, _, {_, LBAHalf}} = split_cols_multiline(Cols1, LinesBefore, U, W), +- LBAfter0 = case LBAHalf of [] -> LBAfter; +- _ -> [LBAHalf|LBAfter] +- end, +- +- RowsLeftAfterInputAfterRows = RowsLeftAfterInputBeforeRows - InputAfterRows, +- LAInViewLines = case BufferExpandLines of +- [] -> +- %% We must remove one line extra, since we may have an xnfix at the end which will +- %% adds one extra line, so for consistency always remove one line +- Cols2 = max(0,W*max(RowsLeftAfterInputAfterRows, RowsLeftAfterInputBeforeRows)-W), +- {_, LABefore, _, {LABHalf, _}} = split_cols_multiline(Cols2, LinesAfter, U, W), +- case LABHalf of [] -> LABefore; +- _ -> [LABHalf|LABefore] +- end; +- _ -> +- [] +- end, +- LAInView = lists:flatten(["\n"++LA||LA<-lists:reverse(LAInViewLines)]), +- LBInView = lists:flatten([LB++"\n"||LB<-LBAfter0]), +- Text = LBInView ++ lists:reverse(Bef,Aft) ++ LAInView, +- Movement = move_cursor(State, +- cols_after_cursor(State#state{lines_after = LAInViewLines++[lists:reverse(Bef, Aft)]}), +- cols(Bef,U)), +- {Movement, Text}; +- +- true -> +- %% Everything fits in the current window, just output everything +- Movement = move_cursor(State, cols_after_cursor(State#state{lines_after = lists:reverse(LinesAfter)++[lists:reverse(Bef, Aft)]}), cols(Bef,U)), +- Text = lists:flatten([LB++"\n"||LB<-lists:reverse(LinesBefore)]) ++ +- lists:reverse(Bef,Aft) ++ lists:flatten(["\n"++LA||LA<-LinesAfter]), +- {Movement, Text} +- end. +-cols_after_cursor(#state{lines_after=[LAST|LinesAfter],cols=W, unicode=U}) -> +- cols_multiline(LAST, LinesAfter, W, U). +-split_cols_multiline(Cols, Lines, U, W) -> +- split_cols_multiline(Cols, Lines, U, W, 0, []). +-split_cols_multiline(0, Lines, _U, _W, ColsAcc, AccBefore) -> +- {ColsAcc, AccBefore, Lines, {[],[]}}; +-split_cols_multiline(_Cols, [], _U, _W, ColsAcc, AccBefore) -> +- {ColsAcc, AccBefore, [], {[],[]}}; +-split_cols_multiline(Cols, [L|Lines], U, W, ColsAcc, AccBefore) -> +- case cols(L, U) > Cols of +- true -> +- {_, _, LB, LA} = split_cols(Cols, L, U), +- {ColsAcc+Cols, AccBefore, Lines, {lists:reverse(LB), LA}}; +- _ -> +- Cols2 = (((cols(L,U)-1) div W)*W+W), +- split_cols_multiline(Cols-Cols2, Lines, U, W, ColsAcc+Cols2, [L|AccBefore]) +- end. +-cols_multiline(Lines, W, U) -> +- cols_multiline("", Lines, W, U). +-cols_multiline(ExtraCols, Lines, W, U) -> +- cols(ExtraCols, U) + lists:sum([((cols(LB,U)-1) div W)*W + W || LB <- Lines]). +- + cols([],_Unicode) -> + 0; + cols([Char | T], Unicode) when is_integer(Char) -> +@@ -975,7 +728,6 @@ npwcwidthstring(String) -> + _ -> + npwcwidth($\e) + npwcwidthstring(Rest) + end; +- [H|Rest] when is_list(H)-> lists:sum([npwcwidth(A)||A<-H]) + npwcwidthstring(Rest); + [H|Rest] -> npwcwidth(H) + npwcwidthstring(Rest) + end. + +@@ -1027,7 +779,7 @@ characters_to_output(Chars) -> + (Char) -> + Char + end, Chars) +- ) ++ ) + end. + characters_to_buffer(Chars) -> + lists:flatmap( +@@ -1063,12 +815,12 @@ insert_buf(State, Bin, LineAcc, Acc) -> + insert_buf(State, AnsiRest, [{ansi, Ansi} | LineAcc], Acc); + _ -> + insert_buf(State, Rest, [$\e | LineAcc], Acc) +- end; ++ end; + {Ansi, AnsiRest} -> + %% We include the graphics ansi sequences in the + %% buffer that we step over + insert_buf(State, AnsiRest, [Ansi | LineAcc], Acc) +- end; ++ end; + [NLCR | Rest] when NLCR =:= $\n; NLCR =:= $\r -> + Tail = + if NLCR =:= $\n -> +@@ -1077,22 +829,10 @@ insert_buf(State, Bin, LineAcc, Acc) -> + <<$\r>> + end, + if State#state.buffer_expand =:= undefined -> +- CurrentLine = lists:reverse(State#state.buffer_before), +- LinesBefore = State#state.lines_before, +- LinesBefore1 = +- case {CurrentLine, LineAcc} of +- {[], []} -> +- LinesBefore; +- {[],_} -> +- [lists:reverse(LineAcc)|LinesBefore]; +- {_,_} -> +- [CurrentLine++lists:reverse(LineAcc)|LinesBefore] +- end, +- insert_buf(State#state{ buffer_before = [], +- buffer_after = State#state.buffer_after, +- lines_before=LinesBefore1}, +- Rest, [], [Acc, [characters_to_output(lists:reverse(LineAcc)), Tail]]); +- true -> insert_buf(State, Rest, [binary_to_list(Tail) | LineAcc], Acc) ++ 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); +diff --git a/lib/kernel/src/user_drv.erl b/lib/kernel/src/user_drv.erl +index 25ebcbdd68..fc1a4434fb 100644 +--- a/lib/kernel/src/user_drv.erl ++++ b/lib/kernel/src/user_drv.erl +@@ -52,25 +52,14 @@ + %% Same as put_chars/3, but sends Reply to From when the characters are + %% guaranteed to have been written to the terminal + {put_chars_sync, unicode, binary(), {From :: pid(), Reply :: term()}} | +- %% Put text in expansion area +- {put_expand} | +- {put_expand_no_trim} | + %% 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) +- {move_line, -32768..32767} | +- %% Move combo, helper to simplify some move operations +- {move_combo, -32768..32767, -32768..32767, -32768..32767} | + %% Insert characters at current cursor position moving any + %% characters after the cursor. + {insert_chars, unicode, binary()} | + %% Delete X chars before or after the cursor adjusting any test remaining + %% to the right of the cursor. + {delete_chars, -32768..32767} | +- %% Deletes the current prompt and expression +- delete_line | +- %% Delete after the cursor +- delete_after_cursor | + %% Trigger a terminal "bell" + beep | + %% Clears the screen +@@ -78,12 +67,7 @@ + %% Execute multiple request() actions + {requests, [request()]} | + %% Open external editor +- {open_editor, string()} | +- %% Redraws the current prompt and expression +- redraw_prompt | +- {redraw_prompt, string(), string(), tuple()} | +- %% Clears the state, not touching the characters +- new_prompt. ++ {open_editor, string()}. + + -export_type([message/0]). + -export([start/0, start/1, start_shell/0, start_shell/1, whereis_group/0]). +@@ -314,6 +298,7 @@ init_remote_shell(State, Node, {M, F, A}) -> + end. + + init_local_shell(State, InitialShell) -> ++ + Slogan = + case application:get_env( + stdlib, shell_slogan, +@@ -610,9 +595,8 @@ switch_loop(internal, {line, Line}, State) -> + {ok, Groups} -> + Curr = gr_cur_pid(Groups), + put(current_group, Curr), +- Curr ! {self(), activate}, + {next_state, server, +- State#state{ current_group = Curr, groups = Groups }}; ++ State#state{ current_group = Curr, groups = Groups } }; + {retry, Requests} -> + {keep_state, State#state{ tty = io_requests(Requests, State#state.tty) }, + {next_event, internal, line}}; +@@ -633,7 +617,7 @@ switch_loop(internal, {line, Line}, State) -> + end; + switch_loop(info,{ReadHandle,{data,Cs}}, {Cont, #state{ read = ReadHandle } = State}) -> + case edlin:edit_line(unicode:characters_to_list(Cs), Cont) of +- {done,{[Line],_,_},_Rest, Rs} -> ++ {done,Line,_Rest, Rs} -> + {keep_state, State#state{ tty = io_requests(Rs, State#state.tty) }, + {next_event, internal, {line, Line}}}; + {undefined,_Char,MoreCs,NewCont,Rs} -> +@@ -775,18 +759,6 @@ group_opts() -> + {term(), reference(), prim_tty:state()}. + io_request({requests,Rs}, TTY) -> + {noreply, io_requests(Rs, TTY)}; +-io_request(redraw_prompt, TTY) -> +- write(prim_tty:handle_request(TTY, redraw_prompt)); +-io_request({redraw_prompt, Pbs, Pbs2, LineState}, TTY) -> +- write(prim_tty:handle_request(TTY, {redraw_prompt, Pbs, Pbs2, LineState})); +-io_request(new_prompt, TTY) -> +- write(prim_tty:handle_request(TTY, new_prompt)); +-io_request(delete_after_cursor, TTY) -> +- write(prim_tty:handle_request(TTY, delete_after_cursor)); +-io_request(delete_line, TTY) -> +- write(prim_tty:handle_request(TTY, delete_line)); +-io_request({put_chars_keep_state, unicode, Chars}, TTY) -> +- write(prim_tty:handle_request(TTY, {putc_keep_state, unicode:characters_to_binary(Chars)})); + io_request({put_chars, unicode, Chars}, TTY) -> + write(prim_tty:handle_request(TTY, {putc, unicode:characters_to_binary(Chars)})); + io_request({put_chars_sync, unicode, Chars, Reply}, TTY) -> +@@ -798,15 +770,9 @@ io_request({put_chars_sync, latin1, Chars, Reply}, TTY) -> + {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_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_rel, N}, TTY) -> + write(prim_tty:handle_request(TTY, {move, N})); +-io_request({move_line, R}, TTY) -> +- write(prim_tty:handle_request(TTY, {move_line, R})); +-io_request({move_combo, V1, R, V2}, TTY) -> +- write(prim_tty:handle_request(TTY, {move_combo, V1, R, V2})); + io_request({insert_chars, unicode, Chars}, TTY) -> + write(prim_tty:handle_request(TTY, {insert, unicode:characters_to_binary(Chars)})); + io_request({delete_chars, N}, TTY) -> +@@ -824,12 +790,6 @@ io_requests([{insert_chars, unicode, C1},{insert_chars, unicode, C2}|Rs], TTY) - + io_requests([{insert_chars, unicode, [C1,C2]}|Rs], TTY); + io_requests([{put_chars, unicode, C1},{put_chars, unicode, C2}|Rs], TTY) -> + io_requests([{put_chars, unicode, [C1,C2]}|Rs], TTY); +-io_requests([{move_rel, N}, {move_line, R}, {move_rel, M}|Rs], TTY) -> +- io_requests([{move_combo, N, R, M}|Rs], TTY); +-io_requests([{move_rel, N}, {move_line, R}|Rs], TTY) -> +- io_requests([{move_combo, N, R, 0}|Rs], TTY); +-io_requests([{move_line, R}, {move_rel, M}|Rs], TTY) -> +- io_requests([{move_combo, 0, R, M}|Rs], TTY); + io_requests([R|Rs], TTY) -> + {noreply, NewTTY} = io_request(R, TTY), + io_requests(Rs, NewTTY); +diff --git a/lib/kernel/test/interactive_shell_SUITE.erl b/lib/kernel/test/interactive_shell_SUITE.erl +index b413800661..04f4143ea9 100644 +--- a/lib/kernel/test/interactive_shell_SUITE.erl ++++ b/lib/kernel/test/interactive_shell_SUITE.erl +@@ -46,9 +46,9 @@ + shell_history_custom/1, shell_history_custom_errors/1, + job_control_remote_noshell/1,ctrl_keys/1, + get_columns_and_rows_escript/1, +- shell_navigation/1, shell_multiline_navigation/1, shell_xnfix/1, shell_delete/1, ++ shell_navigation/1, shell_xnfix/1, shell_delete/1, + shell_transpose/1, shell_search/1, shell_insert/1, +- shell_update_window/1, shell_small_window_multiline_navigation/1, shell_huge_input/1, ++ shell_update_window/1, shell_huge_input/1, + shell_invalid_unicode/1, shell_support_ansi_input/1, + shell_invalid_ansi/1, shell_suspend/1, shell_full_queue/1, + shell_unicode_wrap/1, shell_delete_unicode_wrap/1, +@@ -66,7 +66,7 @@ + -export([load/0, add/1]). + %% For custom prompt testing + -export([prompt/1]). +--record(tmux, {peer, node, name, orig_location }). ++ + suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap,{minutes,3}}]. +@@ -124,9 +124,9 @@ groups() -> + ]}, + {tty_latin1,[],[{group,tty_tests}]}, + {tty_tests, [parallel], +- [shell_navigation, shell_multiline_navigation, shell_xnfix, shell_delete, ++ [shell_navigation, shell_xnfix, shell_delete, + shell_transpose, shell_search, shell_insert, +- shell_update_window, shell_small_window_multiline_navigation, shell_huge_input, ++ shell_update_window, shell_huge_input, + shell_support_ansi_input, + shell_standard_error_nlcr, + shell_expand_location_above, +@@ -404,68 +404,7 @@ shell_navigation(Config) -> + after + stop_tty(Term) + end. +-shell_multiline_navigation(Config) -> +- Term = start_tty(Config), + +- try +- [begin +- check_location(Term, {0, 0}), +- send_tty(Term,"{aaa,"), +- check_location(Term, {0,width("{aaa,")}), +- send_tty(Term,"\n'b"++U++"b',"), +- check_location(Term, {0, width("'b"++U++"b',")}), +- send_tty(Term,"\nccc}"), +- check_location(Term, {-2, 0}), %% Check that cursor jump backward (blink) +- timer:sleep(1000), %% Wait for cursor to jump back +- check_location(Term, {0, width("ccc}")}), +- send_tty(Term,"Home"), +- check_location(Term, {0, 0}), +- send_tty(Term,"End"), +- check_location(Term, {0, width("ccc}")}), +- send_tty(Term,"Left"), +- check_location(Term, {0, width("ccc")}), +- send_tty(Term,"C-Left"), +- check_location(Term, {0, 0}), +- send_tty(Term,"C-Left"), +- check_location(Term, {-1, width("'b"++U++"b',")}), +- send_tty(Term,"C-Left"), +- check_location(Term, {-1, 0}), +- %send_tty(Term,"C-Left"), +- %check_location(Term, {-1, 0}), +- %send_tty(Term,"C-Right"), +- %check_location(Term, {-1, 1}), +- send_tty(Term,"C-Right"), +- check_location(Term, {-1, width("'b"++U++"b'")}), +- send_tty(Term,"C-Up"), +- check_location(Term, {-2, width("{aaa,")}), +- send_tty(Term,"C-Down"), +- send_tty(Term,"C-Down"), +- check_location(Term, {0, width("ccc}")}), +- send_tty(Term,"Left"), +- send_tty(Term,"C-Up"), +- check_location(Term, {-1, width("'b"++U)}), +- send_tty(Term,"M-<"), +- check_location(Term, {-2, 0}), +- send_tty(Term,"M->"), +- send_tty(Term,"Left"), +- check_location(Term, {0,width("ccc")}), +- send_tty(Term,"Enter"), +- send_tty(Term,"Right"), +- check_location(Term, {0,0}), +- send_tty(Term,"C-h"), % Backspace +- check_location(Term, {-1,width("ccc}")}), +- send_tty(Term,"Left"), +- send_tty(Term,"M-Enter"), +- send_tty(Term,"Right"), +- check_location(Term, {0,1}), +- send_tty(Term,"M-c"), +- check_location(Term, {-3,0}), +- send_tty(Term,"{'"++U++"',\n\n\nworks}.\n") +- end || U <- hard_unicode()], +- ok +- after +- stop_tty(Term) +- end. + shell_clear(Config) -> + + Term = start_tty(Config), +@@ -788,7 +727,9 @@ shell_transpose(Config) -> + end. + + shell_search(C) -> ++ + Term = start_tty(C), ++ {_Row, Cols} = get_location(Term), + + try + send_tty(Term,"a"), +@@ -802,22 +743,20 @@ shell_search(C) -> + send_tty(Term,"Enter"), + check_location(Term, {0, 0}), + send_tty(Term,"C-r"), +- check_content(Term, "search:\\s*\n\\s*'a😀'."), ++ check_location(Term, {0, - Cols + width(C, "(search)`': 'a😀'.") }), + send_tty(Term,"C-a"), +- check_location(Term, {-1, width(C, "'a😀'.")}), ++ check_location(Term, {0, width(C, "'a😀'.")}), + send_tty(Term,"Enter"), + send_tty(Term,"C-r"), +- check_content(Term, "search:\\s*\n\\s*'a😀'."), ++ check_location(Term, {0, - Cols + width(C, "(search)`': 'a😀'.") }), + send_tty(Term,"a"), +- check_content(Term, "search: a\\s*\n\\s*'a😀'."), ++ check_location(Term, {0, - Cols + width(C, "(search)`a': 'a😀'.") }), + send_tty(Term,"C-r"), +- check_content(Term, "search: a\\s*\n\\s*a."), ++ check_location(Term, {0, - Cols + width(C, "(search)`a': a.") }), + send_tty(Term,"BSpace"), +- check_content(Term, "search:\\s*\n\\s*'a😀'."), ++ check_location(Term, {0, - Cols + width(C, "(search)`': 'a😀'.") }), + send_tty(Term,"BSpace"), +- check_content(Term, "search:\\s*\n\\s*'a😀'."), +- send_tty(Term,"M-c"), +- check_location(Term, {-1, 0}), ++ check_location(Term, {0, - Cols + width(C, "(search)`': 'a😀'.") }), + ok + after + stop_tty(Term), +@@ -872,49 +811,7 @@ shell_update_window(Config) -> + after + stop_tty(Term) + end. +-shell_small_window_multiline_navigation(Config) -> +- Term0 = start_tty(Config), +- tmux(["resize-window -t ",tty_name(Term0)," -x ",30, " -y ", 6]), +- {Row, Col} = get_location(Term0), +- Term = Term0#tmux{orig_location = {Row, Col}}, +- Text = ("xbcdefghijklmabcdefghijklm\n"++ +- "abcdefghijkl\n"++ +- "abcdefghijklmabcdefghijklm\n"++ +- "abcdefghijklmabcdefghijklx"), +- try +- send_tty(Term,Text), +- check_location(Term, {0, -4}), +- send_tty(Term,"Home"), +- check_location(Term, {-1, 0}), +- send_tty(Term, "C-Up"), +- check_location(Term, {-2, 0}), +- send_tty(Term, "C-Down"), +- check_location(Term, {-1, 0}), +- send_tty(Term, "Left"), +- check_location(Term, {-1, -4}), +- send_tty(Term, "Right"), +- check_location(Term, {-1, 0}), +- send_tty(Term, "\e[1;4A"), +- check_location(Term, {-5, 0}), +- check_content(Term,"xbc"), +- send_tty(Term, "\e[1;4B"), +- check_location(Term, {0, -4}), +- check_content(Term,"klx"), +- send_tty(Term, " sets:is_e\t"), +- check_content(Term,"is_element"), +- check_content(Term,"is_empty"), +- check_location(Term, {-3, 6}), +- send_tty(Term, "C-Up"), +- send_tty(Term,"Home"), +- check_location(Term, {-2, 0}), +- send_tty(Term, "sets:is_e\t"), +- check_content(Term,"is_element"), +- check_content(Term,"is_empty"), +- check_location(Term, {-4, 9}), +- ok +- after +- stop_tty(Term) +- end. ++ + shell_huge_input(Config) -> + Term = start_tty(Config), + +@@ -1071,7 +968,7 @@ shell_expand_location_below(Config) -> + 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 -7") end, ++ check_content(fun() -> get_content(Term, "-S -5") end, + "3> long_module:" ++ FunctionName ++ "\nfunctions"), + check_content(Term, "3> long_module:" ++ FunctionName ++ "$"), + +@@ -1166,19 +1063,18 @@ external_editor(Config) -> + tmux(["resize-window -t ",tty_name(Term)," -x 80"]), + send_tty(Term,"os:putenv(\"EDITOR\",\"nano\").\n"), + send_tty(Term, "\"some text with\nnewline in it\""), +- check_content(Term,"3> \"some text with\\s*\n.+\\s*newline in it\""), ++ check_content(Term,"3> \"some text with\\s*\n.+3>\\s*newline in it\""), + send_tty(Term, "C-O"), + check_content(Term,"GNU nano [\\d.]+"), +- check_content(Term,"\"some text with\\s*\n\\s*newline in it\""), +- send_tty(Term, "Right"), ++ check_content(Term,"newline in it\""), + send_tty(Term, "still"), + send_tty(Term, "Enter"), + send_tty(Term, "C-O"), %% save in nano + send_tty(Term, "Enter"), + send_tty(Term, "C-X"), %% quit in nano +- check_content(Term,"3> \"still\\s*\n\\s*.+\\s*some text with\\s*\n.+\\s*newline in it\""), ++ check_content(Term,"still\n.+3> newline in it\""), + send_tty(Term,".\n"), +- check_content(Term,"\\Q\"still\\nsome text with\\nnewline in it\"\\E"), ++ check_content(Term,"\\Q\"some text with\\nstill\\nnewline in it\"\\E"), + ok + after + stop_tty(Term), +@@ -1484,6 +1380,8 @@ npwcwidth(CP) -> + end + end. + ++-record(tmux, {peer, node, name, orig_location }). ++ + tmux([Cmd|_] = Command) when is_list(Cmd) -> + tmux(lists:concat(Command)); + tmux(Command) -> +@@ -2227,13 +2125,15 @@ test_remote_job_control(Node) -> + {expect, "Unknown job"}, + {expect, " --> $"}, + {putline, "c 1"}, ++ {expect, "\r\n"}, ++ {putline, ""}, + {expect, "\\Q("++RemNode++"@\\E[^)]*\\)[12]> $"}, + {putdata, "\^g"}, + {expect, " --> $"}, + {putline, "j"}, + {expect, "1[*] {shell,start,\\[init]}"}, + {putline, "c"}, +- {expect, "\\Q("++RemNode++"@\\E[^)]*\\)[123]> $"}, ++ {expect, "\r\n"}, + {sleep, 100}, + {putline, "35."}, + {expect, "\\Q("++RemNode++"@\\E[^)]*\\)[123]> $"} +diff --git a/lib/stdlib/src/edlin.erl b/lib/stdlib/src/edlin.erl +index 88283a54c9..b015479b9f 100644 +--- a/lib/stdlib/src/edlin.erl ++++ b/lib/stdlib/src/edlin.erl +@@ -23,9 +23,10 @@ + %% About Latin-1 characters: see the beginning of erl_scan.erl. + + -export([init/0,init/1,start/1,start/2,edit_line/2,prefix_arg/1]). +--export([erase_line/0,erase_inp/1,redraw_line/1]). ++-export([erase_line/1,erase_inp/1,redraw_line/1]). + -export([length_before/1,length_after/1,prompt/1]). + -export([current_line/1, current_chars/1]). ++%%-export([expand/1]). + + -export([edit_line1/2]). + +@@ -71,105 +72,80 @@ start(Pbs) -> + + %% Only two modes used: 'none' and 'search'. Other modes can be + %% handled inline through specific character handling. +-start(Pbs, {_,{_,_},_}=Cont) -> +- Rs1 = erase_line(), +- Rs2 = redraw(Pbs, Cont, Rs1), +- Rs3 = reverse(Rs2), +- {more_chars,{line,Pbs,Cont,none},Rs3}; +- + start(Pbs, Mode) -> +- {more_chars,{line,Pbs,{[],{[],[]},[]},Mode},[new_prompt, {put_chars,unicode,Pbs}]}. ++ {more_chars,{line,Pbs,{[],[]},Mode},[{put_chars,unicode,Pbs}]}. + +-edit_line(Cs, {line,P,L,{blink,N_Rs}}) -> +- edit(Cs, P, L, none, N_Rs); ++edit_line(Cs, {line,P,L,{blink,N}}) -> ++ edit(Cs, P, L, none, [{move_rel,N}]); + edit_line(Cs, {line,P,L,M}) -> + edit(Cs, P, L, M, []). + +-edit_line1(Cs, {line,P,L,{blink,N_Rs}}) -> +- edit(Cs, P, L, none, N_Rs); +-edit_line1(Cs, {line,P,{B,{[],[]},A},none}) -> +- [CurrentLine|Lines] = [string:to_graphemes(Line) || Line <- reverse(string:split(Cs, "\n",all))], +- Cont = {Lines ++ B,{reverse(CurrentLine),[]},A}, +- Rs = reverse(redraw(P, Cont, [])), +- %erlang:display({P, Cont, Cs, CurrentLine}), +- {more_chars, {line,P,Cont,none},[delete_line|Rs]}; ++edit_line1(Cs, {line,P,L,{blink,N}}) -> ++ edit(Cs, P, L, none, [{move_rel,N}]); ++edit_line1(Cs, {line,P,{[],[]},none}) -> ++ {more_chars, {line,P,{string:reverse(Cs),[]},none},[{put_chars, unicode, Cs}]}; + edit_line1(Cs, {line,P,L,M}) -> + edit(Cs, P, L, M, []). + + edit([C|Cs], P, Line, {blink,_}, [_|Rs]) -> %Remove blink here + edit([C|Cs], P, Line, none, Rs); +-edit([C|Cs], P, {LB, {Bef,Aft}, LA}=MultiLine, Prefix, Rs0) -> ++edit([C|Cs], P, {Bef,Aft}, Prefix, Rs0) -> + case key_map(C, Prefix) of + meta -> +- edit(Cs, P, MultiLine, meta, Rs0); ++ edit(Cs, P, {Bef,Aft}, meta, Rs0); + meta_o -> +- edit(Cs, P, MultiLine, meta_o, Rs0); ++ edit(Cs, P, {Bef,Aft}, meta_o, Rs0); + meta_csi -> +- edit(Cs, P, MultiLine, meta_csi, Rs0); ++ edit(Cs, P, {Bef,Aft}, meta_csi, Rs0); + meta_meta -> +- edit(Cs, P, MultiLine, meta_meta, Rs0); ++ edit(Cs, P, {Bef,Aft}, meta_meta, Rs0); + {csi, _} = Csi -> +- edit(Cs, P, MultiLine, Csi, Rs0); ++ edit(Cs, P, {Bef,Aft}, Csi, Rs0); + meta_left_sq_bracket -> +- edit(Cs, P, MultiLine, meta_left_sq_bracket, Rs0); ++ edit(Cs, P, {Bef,Aft}, meta_left_sq_bracket, Rs0); + search_meta -> +- edit(Cs, P, MultiLine, search_meta, Rs0); ++ edit(Cs, P, {Bef,Aft}, search_meta, Rs0); + search_meta_left_sq_bracket -> +- edit(Cs, P, MultiLine, search_meta_left_sq_bracket, Rs0); ++ edit(Cs, P, {Bef,Aft}, search_meta_left_sq_bracket, Rs0); + ctlx -> +- edit(Cs, P, MultiLine, ctlx, Rs0); ++ edit(Cs, P, {Bef,Aft}, ctlx, Rs0); + new_line -> +- case Bef of +- [] -> edit(Cs, P, MultiLine, none, Rs0); +- _ -> MultiLine1 = {[lists:reverse(Bef)|LB],{[],Aft},LA}, +- edit(Cs, P, MultiLine1, none, redraw(P, MultiLine1, Rs0)) +- end; +- new_line_finish -> +- [Last|LAR]=LA1 = lists:reverse([lists:reverse(Bef,Aft)|LA]), +- MultiLine1 = {LA1 ++ LB,{[],[]},[]}, +- % Move to end and redraw +- Rs1 = redraw(P, {LAR ++ LB, {lists:reverse(Last), []},[]}, Rs0), +- {done, MultiLine1, Cs, reverse(Rs1, [{insert_chars, unicode, "\n"}])}; ++ {done, get_line(Bef, Aft ++ "\n"), Cs, ++ reverse(Rs0, [{move_rel,cp_len(Aft)},{put_chars,unicode,"\n"}])}; + redraw_line -> +- Rs1 = erase_line(Rs0), +- Rs = redraw(P, MultiLine, Rs1), +- edit(Cs, P, MultiLine, none, Rs); ++ Rs1 = erase(P, Bef, Aft, Rs0), ++ Rs = redraw(P, Bef, Aft, Rs1), ++ edit(Cs, P, {Bef,Aft}, none, Rs); + clear -> +- Rs = redraw(P, MultiLine, [clear|Rs0]), +- edit(Cs, P, MultiLine, none, Rs); +- tab_expand -> +- {expand, chars_before(MultiLine), Cs, +- {line, P, MultiLine, tab_expand}, +- reverse(Rs0)}; ++ Rs = redraw(P, Bef, Aft, [clear | Rs0]), ++ edit(Cs, P, {Bef,Aft}, none, Rs); ++ tab_expand -> ++ {expand, Bef, Cs, ++ {line, P, {Bef, Aft}, tab_expand}, ++ reverse(Rs0)}; + tab_expand_full -> +- {expand_full, chars_before(MultiLine), Cs, +- {line, P, MultiLine, tab_expand}, +- reverse(Rs0)}; ++ {expand_full, Bef, Cs, ++ {line, P, {Bef, Aft}, tab_expand}, ++ reverse(Rs0)}; + {undefined,C} -> +- {undefined,{none,Prefix,C},Cs,{line,P,MultiLine,none}, ++ {undefined,{none,Prefix,C},Cs,{line,P,{Bef,Aft},none}, + reverse(Rs0)}; + Op -> +- case do_op(Op, MultiLine, Rs0) of +- {blink,N,MultiLine1,Rs} -> +- edit(Cs, P, MultiLine1, {blink,N}, Rs); +- {redraw, MultiLine1, Rs} -> +- edit(Cs, P, MultiLine1, none, redraw(P, MultiLine1, Rs)); +- {MultiLine1, Rs, Mode} -> % allow custom modes from do_op +- edit(Cs, P, MultiLine1, Mode, Rs); +- {MultiLine1,Rs} -> +- edit(Cs, P, MultiLine1, none, Rs) ++ case do_op(Op, Bef, Aft, Rs0) of ++ {blink,N,Line,Rs} -> ++ edit(Cs, P, Line, {blink,N}, Rs); ++ {Line, Rs, Mode} -> % allow custom modes from do_op ++ edit(Cs, P, Line, Mode, Rs); ++ {Line,Rs} -> ++ edit(Cs, P, Line, none, Rs) + end + end; + edit([], P, L, {blink,N}, Rs) -> +- {blink,{line,P,L, {blink,N}},reverse(Rs)}; ++ {blink,{line,P,L,{blink,N}},reverse(Rs)}; + edit([], P, L, Prefix, Rs) -> + {more_chars,{line,P,L,Prefix},reverse(Rs)}; +-edit(eof, _, {_,{Bef,Aft0},LA} = L, _, Rs) -> +- Aft1 = case LA of +- [Last|_] -> Last; +- _ -> Aft0 +- end, +- {done,L,[],reverse(Rs, [{move_combo,-cp_len(Bef), length(LA), cp_len(Aft1)}])}. ++edit(eof, _, {Bef,Aft}, _, Rs) -> ++ {done,get_line(Bef, Aft),[],reverse(Rs, [{move_rel,cp_len(Aft)}])}. + + %% %% Assumes that arg is a string + %% %% Horizontal whitespace only. +@@ -207,8 +183,8 @@ 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) -> clear; +-key_map($\n, none) -> new_line_finish; +-key_map($\r, none) -> new_line_finish; ++key_map($\n, none) -> new_line; ++key_map($\r, none) -> new_line; + key_map($\^T, none) -> transpose_char; + key_map($\^U, none) -> ctlu; + key_map($\^], none) -> auto_blink; +@@ -232,16 +208,11 @@ key_map($L, meta) -> redraw_line; + key_map($T, meta) -> transpose_word; + key_map($Y, meta) -> yank_pop; + key_map($b, meta) -> backward_word; +-key_map($c, meta) -> clear_line; + key_map($d, meta) -> kill_word; + key_map($f, meta) -> forward_word; + key_map($l, meta) -> redraw_line; + key_map($t, meta) -> transpose_word; + key_map($y, meta) -> yank_pop; +-key_map($<, meta) -> beginning_of_expression; +-key_map($>, meta) -> end_of_expression; +-key_map($\n, meta) -> new_line; +-key_map($\r, meta) -> new_line; + key_map($O, meta) -> meta_o; + key_map($H, meta_o) -> beginning_of_line; + key_map($F, meta_o) -> end_of_line; +@@ -252,7 +223,7 @@ key_map($H, meta_left_sq_bracket) -> beginning_of_line; + key_map($F, meta_left_sq_bracket) -> end_of_line; + key_map($D, meta_left_sq_bracket) -> backward_char; + key_map($C, meta_left_sq_bracket) -> forward_char; +-% support a few + combinations... ++% support a few + combinations... + % - forward: \e\e[C, \e[5C, \e[1;5C + % - backward: \e\e[D, \e[5D, \e[1;5D + key_map($\e, meta) -> meta_meta; +@@ -261,31 +232,14 @@ key_map($C, meta_csi) -> forward_word; + key_map($D, meta_csi) -> backward_word; + key_map($1, meta_left_sq_bracket) -> {csi, "1"}; + key_map($3, meta_left_sq_bracket) -> {csi, "3"}; +-key_map($C, {csi, "3"}) -> forward_word; +-key_map($D, {csi, "3"}) -> backward_word; +-key_map($~, {csi, "3"}) -> forward_delete_char; + key_map($5, meta_left_sq_bracket) -> {csi, "5"}; +-key_map($C, {csi, "5"}) -> forward_word; +-key_map($D, {csi, "5"}) -> backward_word; +-key_map($;, {csi, "1"}) -> {csi, "1;"}; +-key_map($3, {csi, "1;"}) -> {csi, "1;3"}; +-key_map($C, {csi, "1;3"}) -> forward_word; +-key_map($D, {csi, "1;3"}) -> backward_word; +-key_map($A, {csi, "1;3"}) -> backward_line; +-key_map($B, {csi, "1;3"}) -> forward_line; +-key_map($4, {csi, "1;"}) -> {csi, "1;4"}; +-key_map($A, {csi, "1;4"}) -> beginning_of_expression; +-key_map($B, {csi, "1;4"}) -> end_of_expression; + key_map($5, {csi, "1;"}) -> {csi, "1;5"}; ++key_map($~, {csi, "3"}) -> forward_delete_char; ++key_map($C, {csi, "5"}) -> forward_word; + key_map($C, {csi, "1;5"}) -> forward_word; ++key_map($D, {csi, "5"}) -> backward_word; + key_map($D, {csi, "1;5"}) -> backward_word; +-key_map($A, {csi, "1;5"}) -> backward_line; +-key_map($B, {csi, "1;5"}) -> forward_line; +- +- +- +- +- ++key_map($;, {csi, "1"}) -> {csi, "1;"}; + key_map(C, none) when C >= $\s -> + {insert,C}; + %% for search, we need smarter line handling and so +@@ -310,8 +264,6 @@ key_map($\^], search) -> {search, search_quit}; + key_map($\^X, search) -> {search, search_quit}; + key_map($\^Y, search) -> {search, search_quit}; + key_map($\e, search) -> search_meta; +-key_map($c, search_meta) -> {search, search_cancel}; +-key_map($C, search_meta) -> {search, search_cancel}; + key_map($[, search_meta) -> search_meta_left_sq_bracket; + key_map(_, search_meta) -> {search, search_quit}; + key_map(_C, search_meta_left_sq_bracket) -> {search, search_quit}; +@@ -320,19 +272,19 @@ key_map(C, _) -> {undefined,C}. + + %% do_op(Action, Before, After, Requests) + %% Before and After are of lists of type string:grapheme_cluster() +-do_op({insert,C}, {LB,{[],[]},LA}, Rs) -> +- {{LB,{[C],[]},LA},[{insert_chars, unicode,[C]}|Rs]}; +-do_op({insert,C}, {LB,{[Bef|Bef0], []},LA}, Rs) -> ++do_op({insert,C}, [], [], Rs) -> ++ {{[C],[]},[{put_chars, unicode,[C]}|Rs]}; ++do_op({insert,C}, [Bef|Bef0], [], Rs) -> + case string:to_graphemes([Bef,C]) of +- [GC] -> {{LB,{[GC|Bef0],[]},LA},[{insert_chars, unicode,[C]}|Rs]}; +- _ -> {{LB,{[C,Bef|Bef0],[]},LA},[{insert_chars, unicode,[C]}|Rs]} ++ [GC] -> {{[GC|Bef0],[]},[{put_chars, unicode,[C]}|Rs]}; ++ _ -> {{[C,Bef|Bef0],[]},[{put_chars, unicode,[C]}|Rs]} + end; +-do_op({insert,C}, {LB,{[], Aft},LA}, Rs) -> +- {{LB,{[C],Aft},LA},[{insert_chars, unicode,[C]}|Rs]}; +-do_op({insert,C}, {LB,{[Bef|Bef0], Aft},LA}, Rs) -> ++do_op({insert,C}, [], Aft, Rs) -> ++ {{[C],Aft},[{insert_chars, unicode,[C]}|Rs]}; ++do_op({insert,C}, [Bef|Bef0], Aft, Rs) -> + case string:to_graphemes([Bef,C]) of +- [GC] -> {{LB,{[GC|Bef0],Aft},LA},[{insert_chars, unicode,[C]}|Rs]}; +- _ -> {{LB,{[C,Bef|Bef0],Aft},LA},[{insert_chars, unicode,[C]}|Rs]} ++ [GC] -> {{[GC|Bef0],Aft},[{insert_chars, unicode,[C]}|Rs]}; ++ _ -> {{[C,Bef|Bef0],Aft},[{insert_chars, unicode,[C]}|Rs]} + end; + %% Search mode prompt always looks like (search)`$TERMS': $RESULT. + %% the {insert_search, _} handlings allow to share this implementation +@@ -343,168 +295,127 @@ do_op({insert,C}, {LB,{[Bef|Bef0], Aft},LA}, Rs) -> + %% search mode), we can use the Bef and Aft variables to hold each + %% part of the line. Bef takes charge of "(search)`$TERMS" and Aft + %% takes charge of "': $RESULT". +-%% +-%% Since multiline support the search mode prompt always looks like: +-%% search: $TERMS +-%% $ResultLine1 +-%% $ResultLine2 +-do_op({insert_search, C}, {LB,{Bef, []},LA}, Rs) -> +- {{LB, {[C|Bef],[]}, LA}, +- [{insert_chars, unicode, [C]}, delete_after_cursor | Rs], search}; +-do_op({insert_search, C}, {LB,{Bef, _Aft},LA}, Rs) -> +- {{LB, {[C|Bef],[]}, LA}, +- [{insert_chars, unicode, [C]}, delete_after_cursor | Rs], ++do_op({insert_search, C}, Bef, [], Rs) -> ++ Aft="': ", ++ {{[C|Bef],Aft}, ++ [{insert_chars, unicode, [C]++Aft}, {delete_chars,-3} | Rs], ++ search}; ++do_op({insert_search, C}, Bef, Aft, Rs) -> ++ Offset= cp_len(Aft), ++ NAft = "': ", ++ {{[C|Bef],NAft}, ++ [{insert_chars, unicode, [C]++NAft}, {delete_chars,-Offset} | Rs], + search}; +-do_op({search, backward_delete_char}, {LB,{[_|Bef], Aft},LA}, Rs) -> ++do_op({search, backward_delete_char}, [_|Bef], Aft, Rs) -> + Offset= cp_len(Aft)+1, +- {{LB, {Bef,Aft}, LA}, +- [{insert_chars, unicode, Aft}, {delete_chars,-Offset}|Rs], ++ NAft = "': ", ++ {{Bef,NAft}, ++ [{insert_chars, unicode, NAft}, {delete_chars,-Offset}|Rs], + search}; +-do_op({search, backward_delete_char}, {LB,{[], Aft},LA}, Rs) -> +- {{LB, {[],Aft}, LA}, [{insert_chars, unicode, Aft}, {delete_chars,-cp_len(Aft)}|Rs], search}; +-do_op({search, skip_up}, {_,{Bef, Aft},_}, Rs) -> ++do_op({search, backward_delete_char}, [], Aft, Rs) -> ++ NAft="': ", ++ {{[],NAft}, [{insert_chars, unicode, NAft}, {delete_chars,-cp_len(Aft)}|Rs], search}; ++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 +- [{insert_chars, unicode, Aft}, {delete_chars,-Offset}|Rs], ++ NAft = "': ", ++ {{[$\^R|Bef],NAft}, % we insert ^R as a flag to whoever called us ++ [{insert_chars, unicode, NAft}, {delete_chars,-Offset}|Rs], + search}; +-do_op({search, skip_down}, {_,{Bef, Aft},_LA}, Rs) -> ++do_op({search, skip_down}, Bef, Aft, Rs) -> + Offset= cp_len(Aft), +- {{[],{[$\^S|Bef],Aft},[]}, % we insert ^S as a flag to whoever called us +- [{insert_chars, unicode, Aft}, {delete_chars,-Offset}|Rs], ++ NAft = "': ", ++ {{[$\^S|Bef],NAft}, % we insert ^S as a flag to whoever called us ++ [{insert_chars, unicode, NAft}, {delete_chars,-Offset}|Rs], + search}; +-do_op({search, search_found}, {_,{_Bef, Aft},LA}, Rs) -> +- {{[],{[],Aft},LA}, Rs, search_found}; +-do_op({search, search_quit}, {_,{_Bef, Aft},LA}, Rs) -> +- {{[],{[],Aft},LA}, Rs, search_quit}; +-do_op({search, search_cancel}, _, Rs) -> +- {{[],{[],[]},[]}, Rs, search_cancel}; ++do_op({search, search_found}, _Bef, Aft, Rs) -> ++ "': "++NAft = Aft, ++ {{[],NAft}, ++ [{put_chars, unicode, "\n"}, {move_rel,-cp_len(Aft)} | Rs], ++ search_found}; ++do_op({search, search_quit}, _Bef, Aft, Rs) -> ++ "': "++NAft = Aft, ++ {{[],NAft}, ++ [{put_chars, unicode, "\n"}, {move_rel,-cp_len(Aft)} | Rs], ++ search_quit}; + %% do blink after $$ +-do_op({blink,C,M}, {_,{[$$,$$|_], _},_} = MultiLine, Rs) -> +- blink(over_paren(chars_before(MultiLine), C, M), C, MultiLine, Rs); ++do_op({blink,C,M}, Bef=[$$,$$|_], Aft, Rs) -> ++ N = over_paren(Bef, C, M), ++ {blink,N+1,{[C|Bef],Aft},[{move_rel,-(N+1)},{insert_chars, unicode,[C]}|Rs]}; + %% don't blink after a $ +-do_op({blink,C,_}, {_,{[$$|_], _},_} = MultiLine, Rs) -> +- do_op({insert,C}, MultiLine, Rs); +-do_op({blink,C,M}, MultiLine, Rs) -> +- blink(over_paren(chars_before(MultiLine), C, M), C, MultiLine, Rs); +-do_op(auto_blink, MultiLine, Rs) -> +- blink(over_paren_auto(chars_before(MultiLine)), MultiLine, Rs); +-do_op(forward_delete_char, {LB,{Bef, []},[NextLine|LA]}, Rs) -> +- NewLine = {LB, {Bef, NextLine}, LA}, +- {redraw, NewLine, Rs}; +-do_op(forward_delete_char, {LB,{Bef, [GC|Aft]},LA}, Rs) -> +- {{LB, {Bef,Aft}, LA},[{delete_chars,gc_len(GC)}|Rs]}; +-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]}; +-do_op(transpose_char, {LB,{[C1,C2|Bef], []},LA}, Rs) -> ++do_op({blink,C,_}, Bef=[$$|_], Aft, Rs) -> ++ do_op({insert,C}, Bef, Aft, Rs); ++do_op({blink,C,M}, Bef, Aft, Rs) -> ++ case over_paren(Bef, C, M) of ++ beep -> ++ {{[C|Bef], Aft}, [beep,{insert_chars, unicode, [C]}|Rs]}; ++ N -> {blink,N+1,{[C|Bef],Aft}, ++ [{move_rel,-(N+1)},{insert_chars, unicode,[C]}|Rs]} ++ end; ++do_op(auto_blink, Bef, Aft, Rs) -> ++ case over_paren_auto(Bef) of ++ {N, Paren} -> ++ {blink,N+1, ++ {[Paren|Bef], Aft},[{move_rel,-(N+1)},{insert_chars, unicode,[Paren]}|Rs]}; ++ % N is likely 0 ++ N -> {blink,N+1,{Bef,Aft}, ++ [{move_rel,-(N+1)}|Rs]} ++ end; ++do_op(forward_delete_char, Bef, [GC|Aft], Rs) -> ++ {{Bef,Aft},[{delete_chars,gc_len(GC)}|Rs]}; ++do_op(backward_delete_char, [GC|Bef], Aft, Rs) -> ++ {{Bef,Aft},[{delete_chars,-gc_len(GC)}|Rs]}; ++do_op(transpose_char, [C1,C2|Bef], [], Rs) -> + Len = gc_len(C1)+gc_len(C2), +- {{LB, {[C2,C1|Bef],[]}, LA},[{put_chars, unicode,[C1,C2]},{move_rel,-Len}|Rs]}; +-do_op(transpose_char, {LB,{[C2|Bef], [C1|Aft]},LA}, Rs) -> ++ {{[C2,C1|Bef],[]},[{put_chars, unicode,[C1,C2]},{move_rel,-Len}|Rs]}; ++do_op(transpose_char, [C2|Bef], [C1|Aft], Rs) -> + Len = gc_len(C2), +- {{LB, {[C2,C1|Bef],Aft}, LA},[{put_chars, unicode,[C1,C2]},{move_rel,-Len}|Rs]}; +-do_op(kill_word, {LB,{Bef, Aft0},LA}, Rs) -> ++ {{[C2,C1|Bef],Aft},[{put_chars, unicode,[C1,C2]},{move_rel,-Len}|Rs]}; ++do_op(kill_word, Bef, Aft0, 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]}; +-do_op(backward_kill_word, {LB,{Bef0, Aft},LA}, Rs) -> ++ {{Bef,Aft},[{delete_chars,N}|Rs]}; ++do_op(backward_kill_word, Bef0, Aft, 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]}; +-do_op(kill_line, {LB, {Bef, Aft}, LA}, Rs) -> ++ {{Bef,Aft},[{delete_chars,-N}|Rs]}; ++do_op(kill_line, Bef, Aft, Rs) -> + put(kill_buffer, Aft), +- {{LB, {Bef,[]}, LA},[{delete_chars,cp_len(Aft)}|Rs]}; +-do_op(clear_line, _, Rs) -> +- {redraw, {[], {[],[]},[]}, Rs}; +-do_op(yank, {LB,{Bef, []},LA}, Rs) -> ++ {{Bef,[]},[{delete_chars,cp_len(Aft)}|Rs]}; ++do_op(yank, Bef, [], Rs) -> + Kill = get(kill_buffer), +- {{LB, {reverse(Kill, Bef),[]}, LA},[{put_chars, unicode,Kill}|Rs]}; +-do_op(yank, {LB,{Bef, Aft},LA}, Rs) -> ++ {{reverse(Kill, Bef),[]},[{put_chars, unicode,Kill}|Rs]}; ++do_op(yank, Bef, Aft, Rs) -> + Kill = get(kill_buffer), +- {{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) -> +- CL = lists:reverse(Bef, Aft), +- CursorPos = min(length(Bef), length(AL)), +- {Bef1, Aft1} = lists:split(CursorPos, AL), +- {{[CL|LB], {lists:reverse(Bef1), Aft1}, LA}, [{move_combo, -cp_len(Bef), 1, cp_len(Bef1)}|Rs]}; +-do_op(backward_line, {[], _, _} = MultiLine, Rs) -> +- {MultiLine, Rs}; +-do_op(backward_line, {[BL|LB],{Bef, Aft},LA}, Rs) -> +- CL = lists:reverse(Bef, Aft), +- CursorPos = min(length(Bef), length(BL)), +- {Bef1, Aft1} = lists:split(CursorPos, BL), +- {{LB, {lists:reverse(Bef1), Aft1}, [CL|LA]},[{move_combo, -cp_len(Bef), -1, cp_len(Bef1)}|Rs]}; +-do_op(forward_char, {LB,{Bef, []}, [AL|LA]}, Rs) -> +- {{[lists:reverse(Bef)|LB],{[], string:to_graphemes(AL)}, LA}, [{move_combo, -cp_len(Bef), 1, 0}|Rs]}; +-do_op(forward_char, {LB,{Bef, [C|Aft]},LA}, Rs) -> +- {{LB,{[C|Bef],Aft},LA},[{move_rel,gc_len(C)}|Rs]}; +-do_op(backward_char, {[BL|LB],{[], Aft},LA}, Rs) -> +- {{LB,{lists:reverse(string:to_graphemes(BL)), []}, [Aft|LA]}, [{move_combo, 0, -1, cp_len(BL)}|Rs]}; +-do_op(backward_char, {LB,{[C|Bef], Aft},LA}, Rs) -> +- {{LB, {Bef,[C|Aft]}, LA},[{move_rel,-gc_len(C)}|Rs]}; +-do_op(forward_word, {LB,{Bef0, []},[NextLine|LA]}, Rs) -> +- {{[reverse(Bef0)|LB], {[], NextLine}, LA},[{move_combo, -cp_len(Bef0), 1, 0}|Rs]}; +-do_op(forward_word, {LB,{Bef0, Aft0},LA}, Rs) -> ++ {{reverse(Kill, Bef),Aft},[{insert_chars, unicode,Kill}|Rs]}; ++do_op(forward_char, Bef, [C|Aft], Rs) -> ++ {{[C|Bef],Aft},[{move_rel,gc_len(C)}|Rs]}; ++do_op(backward_char, [C|Bef], Aft, Rs) -> ++ {{Bef,[C|Aft]},[{move_rel,-gc_len(C)}|Rs]}; ++do_op(forward_word, Bef0, Aft0, Rs) -> + {Aft1,Bef1,N0} = over_non_word(Aft0, Bef0, 0), +- {Aft, Bef, N} = over_word(Aft1, Bef1, N0), +- {{LB, {Bef,Aft}, LA},[{move_rel,N}|Rs]}; +-do_op(backward_word, {[PrevLine|LB],{[], Aft0},LA}, Rs) -> +- {{LB, {reverse(PrevLine), []}, [Aft0|LA]},[{move_combo, 0, -1, cp_len(PrevLine)}|Rs]}; +-do_op(backward_word, {LB,{Bef0, Aft0},LA}, Rs) -> ++ {Aft,Bef,N} = over_word(Aft1, Bef1, N0), ++ {{Bef,Aft},[{move_rel,N}|Rs]}; ++do_op(backward_word, Bef0, Aft0, Rs) -> + {Bef1,Aft1,N0} = over_non_word(Bef0, Aft0, 0), + {Bef,Aft,N} = over_word(Bef1, Aft1, N0), +- {{LB, {Bef,Aft}, LA},[{move_rel,-N}|Rs]}; +-do_op(beginning_of_expression, {[],{[], Aft},LA}, Rs) -> +- {{[], {[],Aft}, LA},Rs}; +-do_op(beginning_of_expression, {LB,{Bef, Aft},LA}, Rs) -> +- [First|Rest] = lists:reverse(LB) ++ [lists:reverse(Bef, Aft)], +- {{[], {[],First}, Rest ++ LA},[{move_combo, -cp_len(Bef), -length(LB), 0}|Rs]}; +-do_op(end_of_expression, {LB,{Bef, []},[]}, Rs) -> +- {{LB, {Bef,[]}, []},Rs}; +-do_op(end_of_expression, {LB,{Bef, Aft},LA}, Rs) -> +- [Last|Rest] = lists:reverse(LA) ++ [lists:reverse(Bef, Aft)], +- {{LB ++ Rest, {lists:reverse(Last),[]}, []},[{move_combo, -cp_len(Bef), length(LA), cp_len(Last)}|Rs]}; +-do_op(beginning_of_line, {LB,{[_|_]=Bef, Aft},LA}, Rs) -> +- {{LB, {[],reverse(Bef, Aft)}, LA},[{move_rel,-(cp_len(Bef))}|Rs]}; +-do_op(beginning_of_line, {LB,{[], Aft},LA}, Rs) -> +- {{LB, {[],Aft}, LA},Rs}; +-do_op(end_of_line, {LB,{Bef, [_|_]=Aft},LA}, Rs) -> +- {{LB, {reverse(Aft, Bef),[]}, LA},[{move_rel,cp_len(Aft)}|Rs]}; +-do_op(end_of_line, {LB,{Bef, []},LA}, Rs) -> +- {{LB, {Bef,[]}, LA},Rs}; +-do_op(ctlu, {LB,{Bef, Aft},LA}, Rs) -> ++ {{Bef,Aft},[{move_rel,-N}|Rs]}; ++do_op(beginning_of_line, [_|_]=Bef, Aft, Rs) -> ++ {{[],reverse(Bef, Aft)},[{move_rel,-(cp_len(Bef))}|Rs]}; ++do_op(beginning_of_line, [], Aft, Rs) -> ++ {{[],Aft},Rs}; ++do_op(end_of_line, Bef, [_|_]=Aft, Rs) -> ++ {{reverse(Aft, Bef),[]},[{move_rel,cp_len(Aft)}|Rs]}; ++do_op(end_of_line, Bef, [], Rs) -> ++ {{Bef,[]},Rs}; ++do_op(ctlu, Bef, Aft, Rs) -> + put(kill_buffer, reverse(Bef)), +- {{LB, {[], Aft}, LA}, [{delete_chars, -cp_len(Bef)} | Rs]}; +-do_op(beep, {LB,{Bef, Aft},LA}, Rs) -> +- {{LB,{Bef,Aft},LA},[beep|Rs]}; +-do_op(_, {LB,{Bef, Aft},LA}, Rs) -> +- {{LB,{Bef,Aft},LA},[beep|Rs]}. +- +-blink(beep, C, {LB, {Bef, Aft}, LA}, Rs) -> +- {{LB,{[C|Bef], Aft},LA}, [beep,{insert_chars, unicode, [C]}|Rs]}; +-blink({N, R}, C, MultiLine, Rs) -> +- blink({N, R, C}, MultiLine, Rs). +-%% same line +-blink(beep, {LB,{Bef, Aft},LA}, Rs) -> +- {{LB,{Bef, Aft},LA}, [beep|Rs]}; +-blink({N, 0, Paren}, {LB, {Bef, Aft}, LA}, Rs) -> +- MoveBackToParen = {move_rel,-N-1}, +- MoveForwardToParen = {move_rel, N+1}, +- {blink,[MoveForwardToParen],{LB,{[Paren|Bef],Aft},LA}, +- [MoveBackToParen,{insert_chars, unicode,[Paren]}|Rs]}; +-%% multiline +-blink({N, R, Paren}, {LB,{Bef, Aft},LA}, Rs) -> +- LengthToClosingParen = cp_len([Paren|Bef]), +- LengthOpeningParen = cp_len(lists:nth(R,LB)) - N - 1, +- MoveToOpeningParen = {move_combo, -LengthToClosingParen, -R, LengthOpeningParen}, +- MoveToClosingParen = {move_combo, -LengthOpeningParen, R, LengthToClosingParen+1}, +- {blink,[MoveToClosingParen],{LB,{[Paren|Bef],Aft},LA}, +- [MoveToOpeningParen,{insert_chars, unicode,[Paren]}|Rs]}. ++ {{[], Aft}, [{delete_chars, -cp_len(Bef)} | Rs]}; ++do_op(beep, Bef, Aft, Rs) -> ++ {{Bef,Aft},[beep|Rs]}; ++do_op(_, Bef, Aft, Rs) -> ++ {{Bef,Aft},[beep|Rs]}. + + %% over_word(Chars, InitialStack, InitialCount) -> + %% {RemainingChars,CharStack,Count} +@@ -512,6 +423,8 @@ blink({N, R, Paren}, {LB,{Bef, Aft},LA}, Rs) -> + %% {RemainingChars,CharStack,Count} + %% Step over word/non-word characters pushing the stepped over ones on + %% the stack. ++ ++ + over_word(Cs, Stack, N) -> + L = length([1 || $\' <- Cs]), + case L rem 2 of +@@ -572,84 +485,80 @@ word_char(_) -> false. + %% do proper parentheses matching check. Paren has NOT been added. + + over_paren(Chars, Paren, Match) -> +- over_paren(Chars, Paren, Match, 1, 1, 0, []). +- +- +-over_paren([C,$$,$$|Cs], Paren, Match, D, N, R, L) -> +- over_paren([C|Cs], Paren, Match, D, N+2, R, L); +-over_paren([GC,$$|Cs], Paren, Match, D, N, R, L) -> +- over_paren(Cs, Paren, Match, D, N+1+gc_len(GC), R, L); +-over_paren([$\n|Cs], Paren, Match, D, _N, R, L) -> +- over_paren(Cs, Paren, Match, D, 0, R+1, L); +-over_paren([Match|_], _Paren, Match, 1, N, R, _) -> +- {N, R}; +-over_paren([Match|Cs], Paren, Match, D, N, R, [Match|L]) -> +- over_paren(Cs, Paren, Match, D-1, N+1, R, L); +-over_paren([Paren|Cs], Paren, Match, D, N, R, L) -> +- over_paren(Cs, Paren, Match, D+1, N+1, R, [Match|L]); +- +-over_paren([$)|Cs], Paren, Match, D, N, R, L) -> +- over_paren(Cs, Paren, Match, D, N+1, R, [$(|L]); +-over_paren([$]|Cs], Paren, Match, D, N, R, L) -> +- over_paren(Cs, Paren, Match, D, N+1, R, [$[|L]); +-over_paren([$}|Cs], Paren, Match, D, N, R, L) -> +- over_paren(Cs, Paren, Match, D, N+1, R, [${|L]); +- +-over_paren([$(|Cs], Paren, Match, D, N, R, [$(|L]) -> +- over_paren(Cs, Paren, Match, D, N+1, R, L); +-over_paren([$[|Cs], Paren, Match, D, N, R, [$[|L]) -> +- over_paren(Cs, Paren, Match, D, N+1, R, L); +-over_paren([${|Cs], Paren, Match, D, N, R, [${|L]) -> +- over_paren(Cs, Paren, Match, D, N+1, R, L); +- +-over_paren([$(|_], _, _, _, _, _, _) -> ++ over_paren(Chars, Paren, Match, 1, 1, []). ++ ++ ++over_paren([C,$$,$$|Cs], Paren, Match, D, N, L) -> ++ over_paren([C|Cs], Paren, Match, D, N+2, L); ++over_paren([GC,$$|Cs], Paren, Match, D, N, L) -> ++ over_paren(Cs, Paren, Match, D, N+1+gc_len(GC), L); ++over_paren([Match|_], _Paren, Match, 1, N, _) -> ++ N; ++over_paren([Match|Cs], Paren, Match, D, N, [Match|L]) -> ++ over_paren(Cs, Paren, Match, D-1, N+1, L); ++over_paren([Paren|Cs], Paren, Match, D, N, L) -> ++ over_paren(Cs, Paren, Match, D+1, N+1, [Match|L]); ++ ++over_paren([$)|Cs], Paren, Match, D, N, L) -> ++ over_paren(Cs, Paren, Match, D, N+1, [$(|L]); ++over_paren([$]|Cs], Paren, Match, D, N, L) -> ++ over_paren(Cs, Paren, Match, D, N+1, [$[|L]); ++over_paren([$}|Cs], Paren, Match, D, N, L) -> ++ over_paren(Cs, Paren, Match, D, N+1, [${|L]); ++ ++over_paren([$(|Cs], Paren, Match, D, N, [$(|L]) -> ++ over_paren(Cs, Paren, Match, D, N+1, L); ++over_paren([$[|Cs], Paren, Match, D, N, [$[|L]) -> ++ over_paren(Cs, Paren, Match, D, N+1, L); ++over_paren([${|Cs], Paren, Match, D, N, [${|L]) -> ++ over_paren(Cs, Paren, Match, D, N+1, L); ++ ++over_paren([$(|_], _, _, _, _, _) -> + beep; +-over_paren([$[|_], _, _, _, _, _, _) -> ++over_paren([$[|_], _, _, _, _, _) -> + beep; +-over_paren([${|_], _, _, _, _, _, _) -> ++over_paren([${|_], _, _, _, _, _) -> + beep; + +-over_paren([GC|Cs], Paren, Match, D, N, R, L) -> +- over_paren(Cs, Paren, Match, D, N+gc_len(GC), R, L); +-over_paren([], _, _, _, _, _, _) -> +- beep. ++over_paren([GC|Cs], Paren, Match, D, N, L) -> ++ over_paren(Cs, Paren, Match, D, N+gc_len(GC), L); ++over_paren([], _, _, _, _, _) -> ++ 0. + + over_paren_auto(Chars) -> +- over_paren_auto(Chars, 1, 1, 0, []). +- +- +-over_paren_auto([C,$$,$$|Cs], D, N, R, L) -> +- over_paren_auto([C|Cs], D, N+2, R, L); +-over_paren_auto([GC,$$|Cs], D, N, R, L) -> +- over_paren_auto(Cs, D, N+1+gc_len(GC), R, L); +-over_paren_auto([$\n|Cs], D, _N, R, L) -> +- over_paren_auto(Cs, D, 0, R+1, L); +- +-over_paren_auto([$(|_], _, N, R, []) -> +- {N, R, $)}; +-over_paren_auto([$[|_], _, N, R, []) -> +- {N, R, $]}; +-over_paren_auto([${|_], _, N, R, []) -> +- {N, R, $}}; +- +-over_paren_auto([$)|Cs], D, N, R, L) -> +- over_paren_auto(Cs, D, N+1, R, [$(|L]); +-over_paren_auto([$]|Cs], D, N, R, L) -> +- over_paren_auto(Cs, D, N+1, R, [$[|L]); +-over_paren_auto([$}|Cs], D, N, R, L) -> +- over_paren_auto(Cs, D, N+1, R, [${|L]); +- +-over_paren_auto([$(|Cs], D, N, R, [$(|L]) -> +- over_paren_auto(Cs, D, N+1, R, L); +-over_paren_auto([$[|Cs], D, N, R, [$[|L]) -> +- over_paren_auto(Cs, D, N+1, R, L); +-over_paren_auto([${|Cs], D, N, R, [${|L]) -> +- over_paren_auto(Cs, D, N+1, R, L); +- +-over_paren_auto([GC|Cs], D, N, R, L) -> +- over_paren_auto(Cs, D, N+gc_len(GC), R, L); +-over_paren_auto([], _, _, _, _) -> +- beep. ++ over_paren_auto(Chars, 1, 1, []). ++ ++ ++over_paren_auto([C,$$,$$|Cs], D, N, L) -> ++ over_paren_auto([C|Cs], D, N+2, L); ++over_paren_auto([GC,$$|Cs], D, N, L) -> ++ over_paren_auto(Cs, D, N+1+gc_len(GC), L); ++ ++over_paren_auto([$(|_], _, N, []) -> ++ {N, $)}; ++over_paren_auto([$[|_], _, N, []) -> ++ {N, $]}; ++over_paren_auto([${|_], _, N, []) -> ++ {N, $}}; ++ ++over_paren_auto([$)|Cs], D, N, L) -> ++ over_paren_auto(Cs, D, N+1, [$(|L]); ++over_paren_auto([$]|Cs], D, N, L) -> ++ over_paren_auto(Cs, D, N+1, [$[|L]); ++over_paren_auto([$}|Cs], D, N, L) -> ++ over_paren_auto(Cs, D, N+1, [${|L]); ++ ++over_paren_auto([$(|Cs], D, N, [$(|L]) -> ++ over_paren_auto(Cs, D, N+1, L); ++over_paren_auto([$[|Cs], D, N, [$[|L]) -> ++ over_paren_auto(Cs, D, N+1, L); ++over_paren_auto([${|Cs], D, N, [${|L]) -> ++ over_paren_auto(Cs, D, N+1, L); ++ ++over_paren_auto([GC|Cs], D, N, L) -> ++ over_paren_auto(Cs, D, N+gc_len(GC), L); ++over_paren_auto([], _, _, _) -> ++ 0. + + %% erase_line(Line) + %% erase_inp(Line) +@@ -658,57 +567,40 @@ over_paren_auto([], _, _, _, _) -> + %% length_after(Line) + %% prompt(Line) + %% current_line(Line) +-%% current_chars(Line) + %% Various functions for accessing bits of a line. + +-erase_line() -> +- [delete_line]. +- +-erase_inp({line,_, L,_}) -> +- reverse(erase([], L, [])). ++erase_line({line,Pbs,{Bef,Aft},_}) -> ++ reverse(erase(Pbs, Bef, Aft, [])). + +-erase_line(Rs) -> +- [delete_line|Rs]. ++erase_inp({line,_,{Bef,Aft},_}) -> ++ reverse(erase([], Bef, Aft, [])). + +-erase(Pbs, {_,{Bef, Aft},_}, Rs) -> ++erase(Pbs, Bef, Aft, Rs) -> + [{delete_chars,-cp_len(Pbs)-cp_len(Bef)},{delete_chars,cp_len(Aft)}|Rs]. + +-redraw_line({line, Pbs, L,_}) -> +- redraw(Pbs, L, []). +- +-multi_line_prompt(Pbs) -> +- lists:duplicate(max(0,prim_tty:npwcwidthstring(Pbs)-3), $ )++".. ". ++redraw_line({line,Pbs,{Bef,Aft},_}) -> ++ reverse(redraw(Pbs, Bef, Aft, [])). + +-redraw(Pbs, {_,{_,_},_}=L, Rs) -> +- [{redraw_prompt, Pbs, multi_line_prompt(Pbs), L} |Rs]. ++redraw(Pbs, Bef, Aft, Rs) -> ++ [{move_rel,-cp_len(Aft)},{put_chars, unicode,reverse(Bef, Aft)},{put_chars, unicode,Pbs}|Rs]. + +-chars_before({[],{Bef,_},_}) -> +- Bef; +-chars_before({LB,{Bef,_},_}) -> +- lists:flatten(lists:join($\n, [Bef| [reverse(Line)|| Line <- LB]])). +- +-length_before({line,Pbs,{_,{Bef,_Aft},_},_}) -> ++length_before({line,Pbs,{Bef,_Aft},_}) -> + cp_len(Pbs) + cp_len(Bef). + +-length_after({line,_,{_,{_Bef,Aft},_},_}) -> ++length_after({line,_,{_Bef,Aft},_}) -> + cp_len(Aft). + + prompt({line,Pbs,_,_}) -> + Pbs. + +-current_chars({line,_,MultiLine,_}) -> +- current_line(MultiLine). +-current_line({line,_,MultiLine,_}) -> +- current_line(MultiLine) ++ "\n"; +-%% Convert a multiline tuple into a string with new lines +-current_line({LinesBefore, {Before, After}, LinesAfter}) -> +- CurrentLine = lists:reverse(Before, After), +- unicode:characters_to_list(lists:flatten( +- lists:filter( +- fun (X) -> +- X /= [] +- end, +- lists:join($\n, lists:reverse(LinesBefore) ++ [CurrentLine] ++ LinesAfter)))). ++current_line({line,_,{Bef, Aft},_}) -> ++ get_line(Bef, Aft ++ "\n"). ++ ++current_chars({line,_,{Bef,Aft},_}) -> ++ get_line(Bef, Aft). ++ ++get_line(Bef, Aft) -> ++ unicode:characters_to_list(reverse(Bef, Aft)). + + %% Grapheme length in codepoints + gc_len(CP) when is_integer(CP) -> 1; +-- +2.34.1 + -- 2.34.1