%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(edlin).

%% A simple Emacs-like line editor.
%% 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/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]).

-import(lists, [reverse/1, reverse/2]).

-export([over_word/3]).


%% A Continuation has the structure:
%%	{line,Prompt,CurrentLine,EditPrefix}

%% init()
%%  Initialise the line editor. This must be done once per process using
%%  the editor.

init() ->
    put(kill_buffer, []).

init(Pid) ->
    %% copy the kill_buffer from the process Pid
    CopiedKillBuf =
	case erlang:process_info(Pid, dictionary) of
	    {dictionary,Dict} ->
		case proplists:get_value(kill_buffer, Dict) of
		    undefined -> [];
		    Buf       -> Buf
		end;
	    undefined ->
		[]
	end,
    put(kill_buffer, CopiedKillBuf).

%% start(Prompt)
%% edit(Characters, Continuation)
%%  Return
%%	{done,Line,Rest,Requests}
%%	{more_chars,Cont,Requests}
%%	{blink,Cont,Requests}
%%	{undefined,Char,Rest,Cont,Requests}

start(Pbs) ->
    start(Pbs, none).

%% Only two modes used: 'none' and 'search'. Other modes can be
%% handled inline through specific character handling.
start(Pbs, Mode) ->
    {more_chars,{line,Pbs,{[],[]},Mode},[{put_chars,unicode,Pbs}]}.

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}}) ->
    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, {Bef,Aft}, Prefix, Rs0) ->
    case key_map(C, Prefix) of
	meta ->
	    edit(Cs, P, {Bef,Aft}, meta, Rs0);
        meta_o ->
            edit(Cs, P, {Bef,Aft}, meta_o, Rs0);
        meta_csi ->
            edit(Cs, P, {Bef,Aft}, meta_csi, Rs0);
        meta_meta ->
            edit(Cs, P, {Bef,Aft}, meta_meta, Rs0);
        {csi, _} = Csi ->
            edit(Cs, P, {Bef,Aft}, Csi, Rs0);
	meta_left_sq_bracket ->
	    edit(Cs, P, {Bef,Aft}, meta_left_sq_bracket, Rs0);
	search_meta ->
	    edit(Cs, P, {Bef,Aft}, search_meta, Rs0);
	search_meta_left_sq_bracket ->
	    edit(Cs, P, {Bef,Aft}, search_meta_left_sq_bracket, Rs0);
	ctlx ->
	    edit(Cs, P, {Bef,Aft}, ctlx, Rs0);
	new_line ->
	    {done, get_line(Bef, Aft ++ "\n"), Cs,
	     reverse(Rs0, [{move_rel,cp_len(Aft)},{put_chars,unicode,"\n"}])};
	redraw_line ->
	    Rs1 = erase(P, Bef, Aft, Rs0),
	    Rs = redraw(P, Bef, Aft, Rs1),
	    edit(Cs, P, {Bef,Aft}, none, Rs);
	tab_expand ->
	    {expand, Bef, Cs,
	     {line, P, {Bef, Aft}, none},
	     reverse(Rs0)};

%% 	tab ->
%% 	    %% Always redraw the line since expand/1 might have printed
%% 	    %% possible expansions.
%% 	    case expand(Bef) of
%% 		{yes,Str} ->
%% 		    edit([redraw_line|
%% 			  (Str ++ Cs)], P, {Bef,Aft}, none, Rs0);
%% 		no ->
%% 		    %% don't beep if there's only whitespace before
%% 		    %% us - user may have pasted in a lot of indented stuff.
%% 		    case whitespace_only(Bef) of
%% 			false ->
%% 			    edit([redraw_line|Cs], P, {Bef,Aft}, none,
%% 				 [beep|Rs0]);
%% 			true ->
%% 			    edit([redraw_line|Cs], P, {Bef,Aft}, none, [Rs0])
%% 		    end
%% 	    end;
	{undefined,C} ->
	    {undefined,{none,Prefix,C},Cs,{line,P,{Bef,Aft},none},
	     reverse(Rs0)};
	Op ->
	    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)};
edit([], P, L, Prefix, Rs) ->
    {more_chars,{line,P,L,Prefix},reverse(Rs)};
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.
%% whitespace_only([]) ->
%%     true;
%% whitespace_only([C|Rest]) ->
%%     case C of
%% 	$\s ->
%% 	    whitespace_only(Rest);
%% 	$\t ->
%% 	    whitespace_only(Rest);
%% 	_ ->
%% 	    false
%%     end.

%% prefix_arg(Argument)
%%  Take a prefix argument and return its numeric value.

prefix_arg(none) -> 1;
prefix_arg({ctlu,N}) -> N;
prefix_arg(N) -> N.

%% key_map(Char, Prefix)
%%  Map a character and a prefix to an action.

key_map(A, _) when is_atom(A) -> A;		% so we can push keywords
key_map($\^A, none) -> beginning_of_line;
key_map($\^B, none) -> backward_char;
key_map($\^D, none) -> forward_delete_char;
key_map($\^E, none) -> end_of_line;
key_map($\^F, none) -> forward_char;
key_map($\^H, none) -> backward_delete_char;
key_map($\t, none) -> tab_expand;
key_map($\^L, none) -> redraw_line;
key_map($\n, none) -> new_line;
key_map($\^K, none) -> kill_line;
key_map($\r, none) -> new_line;
key_map($\^T, none) -> transpose_char;
key_map($\^U, none) -> ctlu;
key_map($\^], none) -> auto_blink;
key_map($\^X, none) -> ctlx;
key_map($\^Y, none) -> yank;
key_map($\^W, none) -> backward_kill_word;
key_map($\e, none) -> meta;
key_map($), Prefix) when Prefix =/= meta,
                         Prefix =/= search,
                         Prefix =/= search_meta -> {blink,$),$(};
key_map($}, Prefix) when Prefix =/= meta,
                         Prefix =/= search,
                         Prefix =/= search_meta -> {blink,$},${};
key_map($], Prefix) when Prefix =/= meta,
                         Prefix =/= search,
                         Prefix =/= search_meta -> {blink,$],$[};
key_map($B, meta) -> backward_word;
key_map($D, meta) -> kill_word;
key_map($F, meta) -> forward_word;
key_map($T, meta) -> transpose_word;
key_map($Y, meta) -> yank_pop;
key_map($b, meta) -> backward_word;
key_map($d, meta) -> kill_word;
key_map($f, meta) -> forward_word;
key_map($t, meta) -> transpose_word;
key_map($y, meta) -> yank_pop;
key_map($O, meta) -> meta_o;
key_map($H, meta_o) -> beginning_of_line;
key_map($F, meta_o) -> end_of_line;
key_map($\177, none) -> backward_delete_char;
key_map($\177, meta) -> backward_kill_word;
key_map($[, meta) -> meta_left_sq_bracket;
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 <CTRL>+<CURSOR LEFT|RIGHT> 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;
key_map($[, meta_meta) -> meta_csi;
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($5, meta_left_sq_bracket) -> {csi, "5"};
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($;, {csi, "1"}) -> {csi, "1;"};
key_map(C, none) when C >= $\s ->
    {insert,C};
%% for search, we need smarter line handling and so
%% we cheat a bit on the dispatching, and allow to
%% return a mode.
key_map($\^H, search) -> {search, backward_delete_char};
key_map($\177, search) -> {search, backward_delete_char};
key_map($\^R, search) -> {search, skip_up};
key_map($\^S, search) -> {search, skip_down};
key_map($\n, search) -> {search, search_found};
key_map($\r, search) -> {search, search_found};
key_map($\^A, search) -> {search, search_quit};
key_map($\^B, search) -> {search, search_quit};
key_map($\^D, search) -> {search, search_quit};
key_map($\^E, search) -> {search, search_quit};
key_map($\^F, search) -> {search, search_quit};
key_map($\t,  search) -> {search, search_quit};
key_map($\^L, search) -> {search, search_quit};
key_map($\^T, search) -> {search, search_quit};
key_map($\^U, search) -> {search, search_quit};
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($[,  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};
key_map(C, search) -> {insert_search,C};
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}, [], [], Rs) ->
    {{[C],[]},[{put_chars, unicode,[C]}|Rs]};
do_op({insert,C}, [Bef|Bef0], [], Rs) ->
    case string:to_graphemes([Bef,C]) of
        [GC] -> {{[GC|Bef0],[]},[{put_chars, unicode,[C]}|Rs]};
        _ -> {{[C,Bef|Bef0],[]},[{put_chars, unicode,[C]}|Rs]}
    end;
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] -> {{[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
%% correctly with group.erl. This module provides $TERMS, and group.erl
%% is in charge of providing $RESULT.
%% This require a bit of trickery. Because search disables moving around
%% on the line (left/right arrow keys and other shortcuts that just exit
%% 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".
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}, [_|Bef], Aft, Rs) ->
    Offset= cp_len(Aft)+1,
    NAft = "': ",
    {{Bef,NAft},
     [{insert_chars, unicode, NAft}, {delete_chars,-Offset}|Rs],
     search};
do_op({search, backward_delete_char}, [], _Aft, Rs) ->
    Aft="': ",
    {{[],Aft}, Rs, search};
do_op({search, skip_up}, Bef, Aft, Rs) ->
    Offset= cp_len(Aft),
    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, Rs) ->
    Offset= cp_len(Aft),
    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, 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}, 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,_}, 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),
    {{[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),
    {{[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)),
    {{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),
    {{Bef,Aft},[{delete_chars,-N}|Rs]};
do_op(kill_line, Bef, Aft, Rs) ->
    put(kill_buffer, Aft),
    {{Bef,[]},[{delete_chars,cp_len(Aft)}|Rs]};
do_op(yank, Bef, [], Rs) ->
    Kill = get(kill_buffer),
    {{reverse(Kill, Bef),[]},[{put_chars, unicode,Kill}|Rs]};
do_op(yank, Bef, Aft, Rs) ->
    Kill = get(kill_buffer),
    {{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),
    {{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),
    {{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)),
    {{[], 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}
%% over_non_word(Chars, InitialStack, InitialCount) ->
%%	{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
	0 ->
	    over_word1(Cs, Stack, N);
	1 ->
	    until_quote(Cs, Stack, N)
    end.

until_quote([$\'|Cs], Stack, N) ->
    {Cs, [$\'|Stack], N+1};
until_quote([C|Cs], Stack, N) ->
    until_quote(Cs, [C|Stack], N+gc_len(C)).

over_word1([$\'=C|Cs], Stack, N) ->
    until_quote(Cs, [C|Stack], N+1);
over_word1(Cs, Stack, N) ->
    over_word2(Cs, Stack, N).

over_word2([C|Cs], Stack, N) ->
    case word_char(C) of
	true -> over_word2(Cs, [C|Stack], N+gc_len(C));
	false -> {[C|Cs],Stack,N}
    end;
over_word2([], Stack, N) when is_integer(N) ->
    {[],Stack,N}.

over_non_word([C|Cs], Stack, N) ->
    case word_char(C) of
	true -> {[C|Cs],Stack,N};
	false -> over_non_word(Cs, [C|Stack], N+gc_len(C))
    end;
over_non_word([], Stack, N) ->
    {[],Stack,N}.

word_char(C) when C >= $A, C =< $Z -> true;
word_char(C) when C >= $À, C =< $Þ, C =/= $× -> true;
word_char(C) when C >= $a, C =< $z -> true;
word_char(C) when C >= $ß, C =< $ÿ, C =/= $÷ -> true;
word_char(C) when C >= $0, C =< $9 -> true;
word_char(C) when C =:= $_ -> true;
word_char([_|_]) -> true; %% Is grapheme
word_char(_) -> false.

%% over_white(Chars, InitialStack, InitialCount) ->
%%	{RemainingChars,CharStack,Count}

%% over_white([$\s|Cs], Stack, N) ->
%%     over_white(Cs, [$\s|Stack], N+1);
%% over_white([$\t|Cs], Stack, N) ->
%%     over_white(Cs, [$\t|Stack], N+1);
%% over_white(Cs, Stack, N) ->
%%     {Cs,Stack,N}.

%% over_paren(Chars, Paren, Match)
%% over_paren(Chars, Paren, Match, Depth, N)
%%  Step over parentheses until matching Paren is found at depth 0. Don't
%%  do proper parentheses matching check. Paren has NOT been added.

over_paren(Chars, Paren, Match) ->
    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([$[|_], _, _, _, _, _)  ->
    beep;
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, []).


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)
%% redraw_line(Line)
%% length_before(Line)
%% length_after(Line)
%% prompt(Line)
%% current_line(Line)
%%  Various functions for accessing bits of a line.

erase_line({line,Pbs,{Bef,Aft},_}) ->
    reverse(erase(Pbs, Bef, Aft, [])).

erase_inp({line,_,{Bef,Aft},_}) ->
    reverse(erase([], Bef, Aft, [])).

erase(Pbs, Bef, Aft, Rs) ->
    [{delete_chars,-cp_len(Pbs)-cp_len(Bef)},{delete_chars,cp_len(Aft)}|Rs].

redraw_line({line,Pbs,{Bef,Aft},_}) ->
    reverse(redraw(Pbs, Bef, Aft, [])).

redraw(Pbs, Bef, Aft, Rs) ->
    [{move_rel,-cp_len(Aft)},{put_chars, unicode,reverse(Bef, Aft)},{put_chars, unicode,Pbs}|Rs].

length_before({line,Pbs,{Bef,_Aft},_}) ->
    cp_len(Pbs) + cp_len(Bef).

length_after({line,_,{_Bef,Aft},_}) ->
    cp_len(Aft).

prompt({line,Pbs,_,_}) ->
    Pbs.

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;
gc_len(CPs) when is_list(CPs) -> length(CPs).

%% String length in codepoints
cp_len(Str) ->
    cp_len(Str, 0).

cp_len([GC|R], Len) ->
    cp_len(R, Len + gc_len(GC));
cp_len([], Len) -> Len.

%% %% expand(CurrentBefore) ->
%% %%	{yes,Expansion} | no
%% %%  Try to expand the word before as either a module name or a function
%% %%  name. We can handle white space around the seperating ':' but the
%% %%  function name must be on the same line. CurrentBefore is reversed
%% %%  and over_word/3 reverses the characters it finds. In certain cases
%% %%  possible expansions are printed.

%% expand(Bef0) ->
%%     {Bef1,Word,_} = over_word(Bef0, [], 0),
%%     case over_white(Bef1, [], 0) of
%% 	{[$:|Bef2],_White,_Nwh} ->
%% 	    {Bef3,_White1,_Nwh1} = over_white(Bef2, [], 0),
%% 	    {_,Mod,_Nm} = over_word(Bef3, [], 0),
%% 	    expand_function_name(Mod, Word);
%% 	{_,_,_} ->
%% 	    expand_module_name(Word)
%%     end.

%% expand_module_name(Prefix) ->
%%     match(Prefix, code:all_loaded(), ":").

%% expand_function_name(ModStr, FuncPrefix) ->
%%     Mod = list_to_atom(ModStr),
%%     case erlang:module_loaded(Mod) of
%% 	true ->
%% 	    L = apply(Mod, module_info, []),
%% 	    case lists:keyfind(exports, 1, L) of
%% 		{_, Exports} ->
%% 		    match(FuncPrefix, Exports, "(");
%% 		_ ->
%% 		    no
%% 	    end;
%% 	false ->
%% 	    no
%%     end.

%% match(Prefix, Alts, Extra) ->
%%     Matches = match1(Prefix, Alts),
%%     case longest_common_head([N || {N,_} <- Matches]) of
%% 	{partial, []} ->
%% 	    print_matches(Matches),
%% 	    no;
%% 	{partial, Str} ->
%% 	    case lists:nthtail(length(Prefix), Str) of
%% 		[] ->
%% 		    print_matches(Matches),
%% 		    {yes, []};
%% 		Remain ->
%% 		    {yes, Remain}
%% 	    end;
%% 	{complete, Str} ->
%% 	    {yes, lists:nthtail(length(Prefix), Str) ++ Extra};
%% 	no ->
%% 	    no
%%     end.

%% %% Print the list of names L in multiple columns.
%% print_matches(L) ->
%%     io:nl(),
%%     col_print(lists:sort(L)),
%%     ok.

%% col_print([]) -> ok;
%% col_print(L)  -> col_print(L, field_width(L), 0).

%% col_print(X, Width, Len) when Width + Len > 79 ->
%%     io:nl(),
%%     col_print(X, Width, 0);
%% col_print([{H0,A}|T], Width, Len) ->
%%     H = if
%% 	    %% If the second element is an integer, we assume it's an
%% 	    %% arity, and meant to be printed.
%% 	    integer(A) ->
%% 		H0 ++ "/" ++ integer_to_list(A);
%% 	    true ->
%% 		H0
%% 	end,
%%     io:format("~-*s",[Width,H]),
%%     col_print(T, Width, Len+Width);
%% col_print([], _, _) ->
%%     io:nl().

%% field_width([{H,_}|T]) -> field_width(T, length(H)).

%% field_width([{H,_}|T], W) ->
%%     case length(H) of
%% 	L when L > W -> field_width(T, L);
%% 	_ -> field_width(T, W)
%%     end;
%% field_width([], W) when W < 40 ->
%%     W + 4;
%% field_width([], _) ->
%%     40.

%% match1(Prefix, Alts) ->
%%     match1(Prefix, Alts, []).

%% match1(Prefix, [{H,A}|T], L) ->
%%     case prefix(Prefix, Str = atom_to_list(H)) of
%% 	true ->
%% 	    match1(Prefix, T, [{Str,A}|L]);
%% 	false ->
%% 	    match1(Prefix, T, L)
%%     end;
%% match1(_, [], L) ->
%%     L.

%% longest_common_head([]) ->
%%     no;
%% longest_common_head(LL) ->
%%     longest_common_head(LL, []).

%% longest_common_head([[]|_], L) ->
%%     {partial, reverse(L)};
%% longest_common_head(LL, L) ->
%%     case same_head(LL) of
%% 	true ->
%% 	    [[H|_]|_] = LL,
%% 	    LL1 = all_tails(LL),
%% 	    case all_nil(LL1) of
%% 		false ->
%% 		    longest_common_head(LL1, [H|L]);
%% 		true ->
%% 		    {complete, reverse([H|L])}
%% 	    end;
%% 	false ->
%% 	    {partial, reverse(L)}
%%     end.

%% same_head([[H|_]|T1]) -> same_head(H, T1).

%% same_head(H, [[H|_]|T]) -> same_head(H, T);
%% same_head(_, [])        -> true;
%% same_head(_, _)         -> false.

%% all_tails(LL) -> all_tails(LL, []).

%% all_tails([[_|T]|T1], L) -> all_tails(T1, [T|L]);
%% all_tails([], L)         -> L.

%% all_nil([]) -> true;
%% all_nil([[] | Rest]) -> all_nil(Rest);
%% all_nil(_) -> false.
