%%
%% %CopyrightBegin%
%%
  Copyright Ericsson AB 2005 - 2023 . 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
%%
%%     -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_expand).
  a default expand function for edlin , expanding modules , functions
%% filepaths, variable binding, record names, function parameter values,
%% record fields and map keys and record field values.
-include_lib("kernel/include/eep48.hrl").
-export([expand/1, expand/2, expand/3, format_matches/2, number_matches/1, get_exports/1,
         shell_default_or_bif/1, bif/1, over_word/1]).
-export([is_type/3, match_arguments1/3]).
-record(shell_state,{
                     bindings = [],
                     records = [],
                     functions = []
                    }).

-spec expand(Bef0) -> {Res, Completion, Matches} when
  a line of erlang expressions in reverse
      Res :: 'yes' | 'no',
      Completion :: string(),
      Matches :: [Element] | [Section],
      Element :: {string(), [ElementOption]},
      ElementOption :: {ending, string()},
      Section :: #{title:=string(), elems:=Matches, options:=SectionOption},
      SectionOption :: {highlight_all} %% highlight the whole title
                     | {highlight, string()}  %% highlight this part of the title
                     | {highlight_param, integer()} %% highlight this parameter
                     | {hide, title}          %% hide the title
                     | {hide, result}         %% hide the results
                     | {separator, string()}. %% specify another separator between title and result
expand(Bef0) ->
    expand(Bef0, [{legacy_output, true}]).

-spec expand(Bef0, Opts) -> {Res, Completion, Matches} when
  a line of erlang expressions in reverse
      Opts :: [Option],
      Option :: {legacy_output, boolean()},
      Res :: 'yes' | 'no',
      Completion :: string(),
      Matches :: [Element] | [Section],
      Element :: {string(), [ElementOption]},
      ElementOption :: {ending, string()},
      Section :: #{title:=string(), elems:=Matches, options:=SectionOption},
      SectionOption :: {highlight_all} %% highlight the whole title
                     | {highlight, string()}  %% highlight this part of the title
                     | {highlight_param, integer()} %% highlight this parameter
                     | {hide, title}          %% hide the title
                     | {hide, result}         %% hide the results
                     | {separator, string()}. %% specify another separator between title and result
expand(Bef0, Opts) ->
    ShellState = try
                     shell:get_state()
                 catch
                     _:_ ->
                         %% Running on a shell that does not support get_state()
                         #shell_state{bindings=[],records=[],functions=[]}
                 end,
    expand(Bef0, Opts, ShellState).

%% Only used for testing
expand(Bef0, Opts, #shell_state{bindings = Bs, records = RT, functions = FT}) ->
    LegacyOutput = proplists:get_value(legacy_output, Opts, false),
    {_Bef1, Word} = over_word(Bef0),
    {Res, Expansion, Matches} = case edlin_context:get_context(Bef0) of

                 {string} -> expand_string(Bef0);

                 {binding} -> expand_binding(Word, Bs);

                 {term} -> expand_module_function(Bef0, FT);
                 {term, _, {_, Unfinished}} -> expand_module_function(lists:reverse(Unfinished), FT);
                 {error, _Column} ->
                    {no, [], []};
                 {function} -> expand_module_function(Bef0, FT);
                 {fun_} -> expand_module_function(Bef0, FT);

                 {fun_, Mod} -> expand_function_name(Mod, Word, "/", FT);

                 %% Complete with arity in a 'fun mod:fun/' expression
                 {fun_, Mod, Fun} ->
                    Arities = [integer_to_list(A) || A <- get_arities(Mod, Fun)],
                    match(Word, Arities, "");
                 {new_fun, _ArgsString} -> {no, [], []};
                 %% Suggest type of function parameter
                 %% Complete an unfinished list, tuple or map using type of function parameter
                 {function, Mod, Fun, Args, Unfinished, Nesting} ->
                    Mod2 = case Mod of
                        "user_defined" -> "shell_default";
                        _ -> Mod
                    end,
                    FunExpansion = expand_function_type(Mod2, Fun, Args, Unfinished, Nesting, FT),
                    case Word of
                        [] -> FunExpansion;
                        _ ->
                            ModuleOrBifs = expand_helper(FT, module, Word, ":"),
                            Functions = case Args =/= [] andalso lists:last(Args) of
                                {atom, MaybeMod} -> expand_function_name(MaybeMod, Word, "", FT);
                                _ -> {no, [], []}
                            end,
                            fold_results([FunExpansion] ++ ModuleOrBifs ++ [Functions])
                    end;

                 %% Complete an unfinished key or suggest valid keys of a map binding
                 {map, Binding, Keys} -> expand_map(Word, Bs, Binding, Keys);

                 {map_or_record} ->
                     {[$#|Bef2], _} = over_word(Bef0),
                     {_, Var} = over_word(Bef2),
                     case Bs of
                         [] -> expand_record(Word, RT);
                         _ ->
                             case proplists:get_value(list_to_atom(Var), Bs) of
                                 undefined ->
                                     expand_record(Word, RT);
                                 Map when is_map(Map) -> {yes, "{", []};
                                 RecordTuple when is_tuple(RecordTuple), tuple_size(RecordTuple) > 0 ->
                                     Atom = erlang:element(1, RecordTuple),
                                     case (is_atom(Atom) andalso lists:keysearch(Atom, 1, RT)) of
                                        {value, {Atom, _}} -> match(Word, [Atom], "{");
                                        _ -> {no, [], []}
                                     end;
                                 _ -> {no, [], []}
                             end
                     end;

                 {record} -> expand_record(Word, RT);

                 {record, Record, Fields, FieldToComplete, Args, Unfinished, Nestings} ->
                     RecordExpansion = expand_record_fields(FieldToComplete, Unfinished, Record, Fields, RT, Args, Nestings, FT),
                     case Word of
                         [] -> RecordExpansion;
                         _ ->
                             ModuleOrBifs = expand_helper(FT, module,Word,":"),
                             fold_results([RecordExpansion] ++ ModuleOrBifs)
                     end;
                 _ -> {no, [], []}

             end,
    Matches1 = case {Res,number_matches(Matches)} of
        {yes, 1} -> [];
        _ -> Matches
    end,
    case LegacyOutput of
        true -> {Res, Expansion, to_legacy_format(Matches1)};
        false -> {Res, Expansion, Matches1}
    end.
expand_map(_, [], _, _) ->
    {no, [], []};
expand_map(Word, Bs, Binding, Keys) ->
    case proplists:get_value(list_to_atom(Binding), Bs) of
        Map when is_map(Map) ->
            K1 = sets:from_list(maps:keys(Map)),
            K2 = sets:subtract(K1, sets:from_list([list_to_atom(K) || K <- Keys])),
            match(Word, sets:to_list(K2), "=>");
        _ -> {no, [], []}
    end.

over_word(Bef) ->
    {Bef1,_,_} = over_white(Bef, [], 0),
    {Bef2, Word, _} = edlin:over_word(Bef1, [], 0),
    {Bef2, Word}.


expand_binding(Prefix, Bindings) ->
    Alts = [strip_quotes(K) || {K,_} <- Bindings],
    case match(Prefix, Alts, "") of
        {_Res,_Expansion,[]}=M -> M;
        {Res, Expansion, Matches} -> {Res,Expansion,[#{title=>"bindings", elems=>Matches, options=>[highlight_all]}]}
    end.

expand_record(Prefix, RT) ->
    Alts = [Name || {Name, _} <- RT],
    case match(Prefix, Alts, "{") of
        {_Res,_Expansion,[]}=M -> M;
        {Res, Expansion, Matches} -> {Res,Expansion,[#{title=>"records", elems=>Matches, options=>[highlight_all]}]}
    end.

expand_record_fields(FieldToComplete, Word, Record, Fields, RT, _Args, Nestings, FT) ->
    Record2 = list_to_atom(Record),
    FieldSet2 = sets:from_list([list_to_atom(F) || F <- Fields]),
    FieldToComplete2 = list_to_atom(FieldToComplete),
    Word1 = case Word of
                {_, Word2} -> Word2;
                [] -> []
            end,
    case [RecordSpec || {Record3, RecordSpec} <- RT, Record2 =:= Record3] of
        [RecordType|_] ->
            case sets:is_element(FieldToComplete2, FieldSet2) of
                true ->
                    expand_record_field_content(FieldToComplete2, RecordType, Word1, Nestings, FT);
                false ->
                    expand_record_field_name(Record2, FieldSet2, RecordType, Word1)
            end;
        _ ->
            {no, [], []}
    end.

expand_record_field_name(Record, Fields, RecordType, Word) ->
    RecordFieldsList = extract_record_fields(Record, RecordType),
    RecordFieldsSet = sets:from_list(RecordFieldsList),
    RecordFields = sets:subtract(RecordFieldsSet, Fields),
    Alts = sets:to_list(RecordFields),
    case match(Word, Alts, "=") of
        {_Res,_Expansion,[]}=M -> M;
        {Res, Expansion, Matches} -> {Res,Expansion,[#{title=>"fields", elems=>Matches, options=>[highlight_all]}]}
    end.

expand_record_field_content(Field,
                            {attribute, _, record,
                             {_Record, FieldTypes}}, Word, Nestings, FT) ->
    FieldTypesFiltered = [Type1 || {typed_record_field, {record_field, _, {_,_, F}}, Type1} <- FieldTypes, F == Field] ++
        [Type1 || {typed_record_field, {record_field, _, {_,_, F}, _}, Type1} <- FieldTypes, F == Field],
    case FieldTypesFiltered of
        [] -> {no, [], []};
        [Type] ->
            T = edlin_type_suggestion:type_tree(erlang, Type, Nestings, FT),
            Types = edlin_type_suggestion:get_types([], T, Nestings),
            case Nestings of
                [] ->
                    Atoms = edlin_type_suggestion:get_atoms([], T, Nestings),
                    case {Word, match(Word, Atoms, ", ")} of
                        {[],{_Res,_Expansion,_}} -> {_Res, _Expansion, [#{title=>"types", elems=>Types, options=>[{hide, title}]}]};
                        {_,{_Res,_Expansion,[]}=M} -> M;
                        {_,{Res,Expansion,Matches}} -> {Res, Expansion, [#{title=>"matches", elems=>Matches, options=>[highlight_all]}]}
                    end;
                _ ->
                    expand_nesting_content(T, [], Nestings, #{title=>"types", elems=>Types, options=>[{hide, title}]})
            end
    end.

%% Check that the actual type on previous arguments
%% matches with the expected types
%% Since we are not doing any evaluations at this point we
%% don't know if a parenthesis, keyword, var, call or fun returns
%% a value with the wrong type.
match_arguments({function, {{parameters, Ps}, _}, Cs}, As) ->
    match_arguments1(Ps, Cs, As);
match_arguments({{parameters, Ps}, _}, As) ->
    match_arguments1(Ps, [], As).
match_arguments1(_,_,[]) -> true;
%% Just assume that it will evaluate to the correct type.
match_arguments1([_|Ps], Cs, [{parenthesis, _}|As]) ->
    match_arguments1(Ps, Cs, As);
match_arguments1([_|Ps], Cs, [{operation, _}|As]) ->
    match_arguments1(Ps, Cs, As);
match_arguments1([_|Ps], Cs, [{keyword, _}|As]) ->
    match_arguments1(Ps, Cs, As);
match_arguments1([_|Ps], Cs, [{var, _}|As]) ->
    match_arguments1(Ps, Cs, As);
match_arguments1([_|Ps], Cs, [{call, _}|As]) ->
    match_arguments1(Ps, Cs, As);
match_arguments1([_|Ps], Cs, [{fun_, _}|As]) ->
    match_arguments1(Ps, Cs, As);
match_arguments1([P|Ps], Cs, [{atom, [$'|_]=String}|As]) ->
    case edlin_context:odd_quotes($', lists:reverse(String)) of
        true -> false; % we know that the atom is unfinished, and thus cannot match any valid atom
        _ -> case is_type(P, Cs, String) of
                 true -> match_arguments1(Ps, Cs, As);
                 false -> false
             end
    end;
match_arguments1([P|Ps], Cs, [{_, String}|As]) ->
    case is_type(P, Cs, String) of
        true -> match_arguments1(Ps, Cs, As);
        false -> false
    end.

is_type(Type, Cs, String) ->
    {ok, A, _} = erl_scan:string(String++"."),
    Types = [T || T <- edlin_type_suggestion:get_types(Cs, Type, [], [no_print]) ],
    try
        {ok, Term} = erl_parse:parse_term(A),
        case Term of
            Atom when is_atom(Atom) ->
                Atoms = edlin_type_suggestion:get_atoms(Cs, Type, []),
                lists:member(to_list(Atom), Atoms) orelse 
                    lists:member(atom_to_list(Atom), Atoms) orelse
                        find_type(Types, [atom, node, module, 'fun']);
            Tuple when is_tuple(Tuple) -> find_type(Types, [tuple]);
            Map when is_map(Map) -> find_type(Types, [map]);
            Binary when is_binary(Binary) -> find_type(Types, [binary]);
            Float when is_float(Float) -> find_type(Types, [float]);
            Integer when is_integer(Integer) -> check_integer_type(Types, Integer);
            List when is_list(List), length(List) > 0 ->
                find_type(Types, [list, string, nonempty_list,maybe_improper_list, nonempty_improper_list]);
            List when is_list(List) -> find_type(Types, [list, string, maybe_improper_list])
        end
    catch
        _:_ ->
            %% Types not possible to deduce with erl_parse
            % If string contains variables, erl_parse:parse_term will fail, but we
            % consider them valid sooo.. lets replace them with the atom var
            B = [(fun({var, Anno, _}) -> {atom, Anno, var}; (Token) -> Token end)(X) || X <- A],
            try
                {ok, Term2} = erl_parse:parse_term(B),
                case Term2 of
                    Tuple2 when is_tuple(Tuple2) -> find_type(Types, [tuple]);
                    Map2 when is_map(Map2) -> find_type(Types, [map]);
                    Binary2 when is_binary(Binary2) -> find_type(Types, [binary]);
                    List2 when is_list(List2), length(List2) > 0 ->
                        find_type(Types, [list, string, nonempty_list,maybe_improper_list, nonempty_improper_list]);
                    List2 when is_list(List2) -> find_type(Types, [list, string, maybe_improper_list])
                end
            catch
                _:_ ->
                    case A of
                        [{'#',_},{var,_,'Port'},{'<',_},{float,_,_},{'>',_},{dot,_}] -> find_type(Types, [port]);
                        [{'#',_},{var,_,'Ref'},{'<',_},{float,_,_},{'.',_},{float,_,_},{'>',_},{dot,_}] -> find_type(Types, [reference]);
                        [{'fun',_},{'(',_} | _] -> find_type(Types, [parameters, function, 'fun']);
                        [{'#',_},{var,_,'Fun'},{'<',_},{atom,_,erl_eval},{'.',_},{float,_,_},{'>',_}] -> find_type(Types, [parameters, function, 'fun']);
                        [{'<', _}, {float, _, _}, {'.', _}, {integer, _, _}, {'>', _}, {dot, _}] -> find_type(Types, [pid]);
                        [{'#', _}, {atom, _, RecordName},{'{', _}| _] -> find_type(Types, [{record, RecordName}]);
                        _ -> false
                    end
            end
    end.

find_type([],_) -> false;
find_type([any|_], _) -> true; % If we find any then every type is valid
find_type([{type, any, []}|_], _) -> true;
find_type([{{parameters, _},_}|Types], ValidTypes) ->
    case lists:member(parameters, ValidTypes) of
        true -> true;
        false -> find_type(Types, ValidTypes)
    end;
find_type([{record, _}=Type|Types], ValidTypes) ->
    case lists:member(Type, ValidTypes) of
        true -> true;
        false -> find_type(Types, ValidTypes)
    end;
find_type([{Type, _}|Types], ValidTypes) ->
    case lists:member(Type, ValidTypes) of
        true -> true;
        false -> find_type(Types, ValidTypes)
    end;
find_type([{type, Type, _}|Types], ValidTypes) ->
    case lists:member(Type, ValidTypes) of
        true -> true;
        false -> find_type(Types, ValidTypes)
    end;
find_type([{type, Type, _, any}|Types], ValidTypes) ->
    case lists:member(Type, ValidTypes) of
        true -> true;
        false -> find_type(Types, ValidTypes)
    end;
find_type([_|Types], ValidTypes) -> find_type(Types, ValidTypes).

in_range(_, []) -> false;
in_range(Integer, [{type, range, [{integer, Start}, {integer, End}]}|_]) when Start =< Integer, Integer =< End -> true;
in_range(Integer, [_|Types]) -> in_range(Integer, Types).

check_integer_type(Types, Integer) when Integer == 0 -> find_type(Types, [integer, non_neg_integer, arity]) orelse in_range(Integer, Types);
check_integer_type(Types, Integer) when Integer < 0 -> find_type(Types, [integer, neg_integer]) orelse in_range(Integer, Types);
check_integer_type(Types, Integer) when Integer > 0 -> find_type(Types, [integer, non_neg_integer, pos_integer]) orelse in_range(Integer, Types).

add_to_last_nesting(Term, Nesting) ->
    Last = lists:last(Nesting),
    List = lists:droplast(Nesting),
    case Last of
        {tuple, Args, U} ->
            List ++ [{tuple, Args ++ [Term], U}];
        {list, Args, U} ->
            List ++ [{list, Args ++ [Term], U}];
        {map, F, Fs, Args, U} ->
            List ++ [{map, F, Fs, Args ++ [Term], U}]
    end.

close_nesting(Nesting) ->
    Last = lists:last(Nesting),
    case Last of
        {tuple, _Args, _} ->
            "}";
        {list, _Args, _} ->
            "]";
        {map, _F, _Fs, _Args, _} ->
            "}"
    end.
expand_function_parameter_type(Mod, MFA, FunType, Args, Unfinished, Nestings, FT) ->
    TypeTree = edlin_type_suggestion:type_tree(Mod, FunType, Nestings, FT),

    {Parameters, Constraints1} = case TypeTree of
                                     {function, {{parameters, Parameters1},_}, Constraints} ->
                                         {Parameters1, Constraints};
                                     {{parameters, Parameters1},_}=_F ->
                                         {Parameters1, []}
                                 end,
    case match_arguments(TypeTree, Args) of
        false -> {no, [], []};
        true when Parameters == [] -> {yes, ")", [#{title=>MFA, elems=>[")"], options=>[]}]};
        true ->
            Parameter = lists:nth(length(Args)+1, Parameters),
            {T, _Name} = case Parameter of
                            Atom when is_atom(Atom) -> {Atom, atom_to_list(Atom)};
                            {var, Name1}=T1 -> {T1, atom_to_list(Name1)};
                            {ann_type, {var, Name1}, T1} -> {T1, atom_to_list(Name1)};
                            T1 -> {T1, edlin_type_suggestion:print_type(T1, [], [{first_only, true}])}
                        end,
            Ts = edlin_type_suggestion:get_types(Constraints1, T, Nestings),
            Types = case Ts of
                        [] -> [];
                        _  ->
                            SectionTypes = [S || #{}=S <- Ts],
                            Types1 = case [E || {_, _}=E<-Ts] of
                                [] -> SectionTypes;
                                Elems -> 
                                    case SectionTypes of
                                        [] -> Elems;
                                        ST -> [#{title=>"simple types", elems=>Elems, options=>[{hide, title}]}|ST]
                                    end
                            end,
                            
                            [#{title=>"types", elems=>(Types1), options=>[{hide, title}]}]
                    end,
            case Nestings of
                [] -> %% Expand function type
                    case Unfinished of
                        [] ->
                            case T of
                                Atom1 when is_atom(Atom1) ->
                                    CC = case length(Args)+1 < length(Parameters) of
                                             true -> ", ";
                                             false ->")"
                                         end,
                                    {Res, Expansion, Matches} = match([], [Atom1], CC),
                                    case Matches of
                                        [] -> {no, [], []};
                                        _ -> {Res, Expansion, [#{title=>MFA, elems=>[], options=>[{highlight_param, length(Args)+1}]}]}
                                    end;
                                _ when Types == [] ->
                                    {no, [], []};
                                _ ->
                                    {no, [], [#{title=>MFA, elems=>Types, options=>[{highlight_param, length(Args)+1}]}]}
                            end;
                        {_, Word} when is_atom(T) ->
                            CC = case length(Args)+1 < length(Parameters) of
                                     true -> ", ";
                                     false ->")"
                                 end,
                            {Res, Expansion, Matches} = match(Word, [T], CC),
                            case Matches of
                                [] -> {no, [], []};
                                _ -> {Res, Expansion, [#{title=>MFA, elems=>[], options=>[{highlight_param, length(Args)+1}]}]}
                            end;
                        {_, Word} ->
                            {Res, Expansion, Matches} = begin
                                                            CC = case length(Args)+1 < length(Parameters) of
                                                                     true -> ", ";
                                                                     false ->")"
                                                                 end,
                                                            Atoms1 = edlin_type_suggestion:get_atoms(Constraints1, T, Nestings),
                                                            {Res1, Expansion1, Matches1} = match(Word, Atoms1, CC),
                                                            case Matches1 of
                                                                [] ->
                                                                    case match_arguments(TypeTree, Args ++ [Unfinished]) of
                                                                        false -> {Res1, Expansion1, Matches1};
                                                                        true ->
                                                                            {yes, CC, [{CC, []}]}
                                                                    end;
                                                                _ ->
                                                                    {Res1, Expansion1, Matches1}
                                                            end
                                                        end,
                            Match1 = case Matches of
                                         [] -> [];
                                         _  -> Atoms = [#{title=>"atoms", elems=>Matches, options=>[{hide, title}]}],
                                               [#{title=>MFA, elems=>Atoms, options=>[{highlight_param, length(Args)+1}]}]
                                     end,
                            {Res, Expansion,Match1}
                    end;
                _ ->  %% Expand last nesting types
                    expand_nesting_content(T, Constraints1, Nestings, #{title=>MFA, elems=>Types, options=>[{highlight_param, length(Args)+1}]})
            end
    end.
expand_nesting_content(T, Constraints, Nestings, Section) ->
    {NestingType, UnfinishedNestingArg, NestingArgs} = case lists:last(Nestings) of
                                              {tuple, NestingArgs1, Unfinished1} -> {tuple, Unfinished1, NestingArgs1};
                                              {list, NestingArgs1, Unfinished1} -> {list, Unfinished1, NestingArgs1};
                                              {map, _, _, NestingArgs1, Unfinished1} -> {map, Unfinished1, NestingArgs1}
                                          end,
    %% in the case of
    %% erlang:system_info({allocator, ) 
    %% we have a tuple nesting with an atom
    %% this should give us "allocator" in the nestingsargs, and empty unfinished part
    %% but we also know that we have a nesting, if we expect something other than a tuple, we shouldnt print that function
    %% lets call it NestingType
    %% now when that is fixed, how do we filter {allocator_sizes, ...} and others
    Types = [Ts || Ts <- edlin_type_suggestion:get_types(Constraints, T, lists:droplast(Nestings), [no_print]) ],
    case UnfinishedNestingArg of
        [] ->
            case find_type(Types, [NestingType]) of
                true -> 
                    %% if we know had a tuple, {allocator_sizes, } will be allowed
                    %% probably get_arity will return none
                    Nestings2 = add_to_last_nesting({var, "Var"}, Nestings),
                    NestingArities = edlin_type_suggestion:get_arity(Constraints, T, Nestings2),
                    
                    fold_results([begin
                        case NestingArity of
                            none -> {no, [], []};
                            _ -> {no, [], [Section]}
                        end
                    end || NestingArity <- NestingArities]);
                false -> {no, [], []}
            end;
        {_, Word} ->
            Atoms1 = edlin_type_suggestion:get_atoms(Constraints, T, Nestings),
            {Res1, Expansion1, Matches1} = match(Word, Atoms1, ""),
            {Res, Expansion, Matches} = case Matches1 of
                                            [] ->
                                                Nestings2 = add_to_last_nesting(UnfinishedNestingArg, Nestings),
                                                NestingArities = edlin_type_suggestion:get_arity(Constraints, T, Nestings2),
                                                fold_results([begin
                                                                  case NestingArity of
                                                                      none -> {no, [], []};
                                                                      _ when NestingType =:= tuple ->
                                                                          CC = case length(NestingArgs)+1 < NestingArity of
                                                                                   true -> ", ";
                                                                                   false -> close_nesting(Nestings)
                                                                               end,
                                                                          {yes, CC, [{CC, []}]};
                                                                      _ when NestingType =:= list ->
                                                                        {no, [], [{", ", []}, {"]", []}]};
                                                                      _ when NestingType =:= map ->
                                                                        {no, [], [{", ",[]},{"}", []}]};
                                                                      _ -> 
                                                                        {no, [], []}
                                                                  end
                                                              end || NestingArity <- NestingArities]);
                                            [{Word2,_}] ->
                                                Nestings2 = add_to_last_nesting({atom, Word2}, Nestings),
                                                NestingArities = edlin_type_suggestion:get_arity(Constraints, T, Nestings2),
                                                fold_results([begin
                                                                  case NestingArity of
                                                                      none -> {no, [], []};
                                                                      _ when NestingType =:= tuple ->
                                                                          CC = case length(NestingArgs)+1 < NestingArity of
                                                                                   true -> ", ";
                                                                                   false -> close_nesting(Nestings)
                                                                               end,
                                                                          {yes, Expansion1++CC, [{Word2, [{ending, CC}]}]};
                                                                      _ -> 
                                                                        {Res1, Expansion1, Matches1}
                                                                  end
                                                              end || NestingArity <- NestingArities]);
                                            _ -> {Res1, Expansion1, Matches1}
                                        end,
            Match1 = case Matches of
                         [] -> [];
                         _  -> Atoms = [#{title=>"atoms", elems=>Matches, options=>[{hide, title}]}],
                               [Section#{elems:=Atoms}]
                     end,
            {Res, Expansion, Match1}
    end.

extract_record_fields(Record, {attribute,_,record,{Record, Fields}})->
    [X || X <- [extract_record_field(F) || F <- Fields], X /= []];
extract_record_fields(_, _)-> error.
extract_record_field({typed_record_field, {_, _,{atom, _, Field}},_})->
    Field;
extract_record_field({typed_record_field, {_, _,{atom, _, Field}, _},_})->
    Field;
extract_record_field({record_field, _,{atom, _, Field},_})->
    Field;
extract_record_field({record_field, _,{atom, _, Field}})->
    Field;
extract_record_field(_) -> [].

fold_results([]) -> {no, [], []};
fold_results([R|Results]) ->
    lists:foldl(fun fold_completion_result/2, R, Results).

fold_completion_result({yes, Cmp1, Matches1}, {yes, Cmp2, Matches2}) ->
    {_, Cmp} = longest_common_head([Cmp1,Cmp2]),
    case Cmp of
        [] -> {no, [], ordsets:union([Matches1,Matches2])};
        _ -> {yes, Cmp, ordsets:union([Matches1,Matches2])}
    end;
fold_completion_result({yes, Cmp, Matches}, {no, [], []}) ->
    {yes, Cmp, Matches};
fold_completion_result({no, [], []},{yes, Cmp, Matches}) ->
    {yes, Cmp, Matches};
fold_completion_result({_, _, Matches1}, {_, [], Matches2}) ->
    {no, [], ordsets:union([Matches1,Matches2])};
fold_completion_result(A, B) ->
    fold_completion_result(B,A).

expand_function_type(ModStr, FunStr, Args, Unfinished, Nestings, FT) ->
    Mod = list_to_atom(ModStr),
    Fun = list_to_atom(FunStr),
    MinArity = if Unfinished =:= [], length(Args) =:= 0 -> 0;
        true -> length(Args)+1
    end,
    case [A || A <- get_arities(ModStr, FunStr, FT), A >= MinArity] of
        [] -> {no, [], []};
        Arities ->
            {Res, Expansion, Matches} = fold_results([begin
                              FunTypes = edlin_type_suggestion:get_function_type(Mod, Fun, Arity, FT),
                              case FunTypes of
                                  [] -> MFA = print_function_head(ModStr, FunStr, Arity),
                                        case Unfinished of
                                            [] -> {no, [], [#{title=>MFA, elems=>[], options=>[]}]};
                                            _ -> {no, [], []}
                                        end;
                                  _ ->
                                      fold_results([begin
                                                        MFA = print_function_head(ModStr, FunStr, FunType, FT),
                                                        expand_function_parameter_type(Mod, MFA, FunType, Args, Unfinished, Nestings, FT)
                                                    end || FunType <- FunTypes])
                              end
                          end || Arity <- Arities]),
            case Matches of
                [] -> {Res, Expansion, Matches};
                _ -> {Res, Expansion, [#{title=>"typespecs", elems=>Matches, options=>[highlight_all]}]}
            end
    end.

  Behaves like zsh
%% filters all files starting with . unless Word starts with .
%% outputs / on end of folders
expand_filepath(PathPrefix, Word) ->
    Path = case PathPrefix of
               [$/|_] -> PathPrefix;
               _ ->
                   {ok, Cwd} = file:get_cwd(),
                   Cwd ++ "/" ++ PathPrefix
           end,
    ShowHidden = case Word of
                     "." ++ _ -> true;
                     _ -> false
                 end,
    Entries = case file:list_dir(Path) of
                  {ok, E} -> lists:map(
                               fun(X)->
                                       case filelib:is_dir(Path ++ "/" ++ X) of
                                           true -> X ++ "/";
                                           false -> X
                                       end
                               end, [".."|E]);
                  _ -> []
              end,
    EntriesFiltered = [File || File <- Entries,
                               case File of
                                   [$.|_] -> ShowHidden;
                                   _ -> true
                               end],
    case match(Word, EntriesFiltered, []) of
        {yes, Cmp, [Match]} ->
            case filelib:is_dir(Path ++ "/" ++ Word ++ Cmp) of
                true -> {yes, Cmp, [Match]};
                false -> {yes, Cmp ++ "\"", [Match]}
            end;
        X -> X
    end.

shell(Fun) ->
    case shell:local_func(list_to_atom(Fun)) of
        true -> "shell";
        false -> "user_defined"
    end.

shell_default_or_bif(Fun) ->
    case lists:member(list_to_atom(Fun), [E || {E,_}<-get_exports(shell_default)]) of
        true -> "shell_default";
        _ -> bif(Fun)
    end.
bif(Fun) ->
    case lists:member(list_to_atom(Fun), [E || {E,A}<-get_exports(erlang), erl_internal:bif(E,A)]) of
        true -> "erlang";
        _ -> shell(Fun)
    end.

expand_string(Bef0) ->
    case over_filepath(Bef0, []) of
        {_, Filepath} ->
            {Path, File} = split_at_last_slash(Filepath),
            expand_filepath(Path, File);
        _ -> {no, [], []}
    end.
%% Extract a whole filepath
%% Stops as soon as we hit a double quote (")
%% and returns everything it found before stopping.
%% assumes the string is not a filepath if it contains unescaped spaces
over_filepath([],_) -> none;
over_filepath([$", $\\|Bef1], Filepath) -> over_filepath(Bef1, [$" | Filepath]);
over_filepath([$"|Bef1], Filepath) -> {Bef1, Filepath};
over_filepath([$\ ,$\\|Bef1], Filepath) -> over_filepath(Bef1, [$\ |Filepath]);
over_filepath([$\ |_], _) -> none;
over_filepath([C|Bef1], Filepath) ->
    over_filepath(Bef1, [C|Filepath]).
split_at_last_slash(Filepath) ->
    {File, Path} = lists:splitwith(fun(X)->X/=$/ end, lists:reverse(Filepath)),
    {lists:reverse(Path), lists:reverse(File)}.

print_function_head(ModStr, FunStr, Arity) ->
    lists:flatten(ModStr ++ ":" ++ FunStr ++ "/" ++ integer_to_list(Arity)).
print_function_head(ModStr, FunStr, FunType, FT) ->
    lists:flatten(print_function_head_from_type(ModStr, FunStr, FunType, FT)).

print_function_head1(Mod, Fun, Par, _Ret) ->
    Mod++":"++Fun++"("++lists:join(", ",
                                   [case P of
                                        Atom when is_atom(Atom) -> atom_to_list(Atom);
                                        {var, V} -> atom_to_list(V);
                                        {ann_type, {var, V}, _T} -> atom_to_list(V);
                                        T -> edlin_type_suggestion:print_type(T, [], [{first_only, true}])
                                    end || {_N,P} <- lists:enumerate(Par)])++")".
print_function_head_from_type(Mod, Fun, FunType, FT) ->
    case edlin_type_suggestion:type_tree(list_to_atom(Mod), FunType, [], FT) of
        {function, {{parameters, Parameters},{return, Return}}, _} ->
            print_function_head1(Mod, Fun, Parameters, Return);
        {{parameters, Parameters},{return, Return}} ->
            print_function_head1(Mod, Fun, Parameters, Return)
    end.

%% expand_module_function(CurrentBefore, FT) -> {yes, Expansion, Matches} | {no, [], Matches}
%%  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 . ´ ´ ´
%%
%%  The function also handles expansion with "h(" and "ht("" for module and functions.
expand_module_function(Bef0, FT) ->
    {Bef1,Word,_} = edlin:over_word(Bef0, [], 0),
    case over_white(Bef1, [], 0) of
        {[$,|Bef2],_White,_Nwh} ->
            {Bef3,_White1,_Nwh1} = over_white(Bef2, [], 0),
            {Bef4,Mod,_Nm} = edlin:over_word(Bef3, [], 0),
            case expand_function(Bef4) of
                help ->
                    expand_function_name(Mod, Word, ", ", FT);
                help_type ->
                    expand_type_name(Mod, Word, ", ");
                _ ->
                    fold_results(expand_helper(FT, module, Word, ":"))
            end;
        {[$:|Bef2],_White,_Nwh} ->
            {Bef3,_White1,_Nwh1} = over_white(Bef2, [], 0),
            {_,Mod,_Nm} = edlin:over_word(Bef3, [], 0),
            expand_function_name(Mod, Word, "(", FT);
        {[CC, N_Esc|_], _White, _Nwh} when (CC =:= $] orelse CC =:= $) orelse CC =:= $> orelse CC =:= $}
                                            orelse CC =:= $" orelse CC =:= $'),
                                           N_Esc =/= $$, N_Esc =/= $- ->
            {no, [], []};
        {[], _, _} ->
            case Word of
                [] -> {no, [], []}; %fold_results([expand_shell_default(Word), expand_user_defined_functions(FT, Word)]);
                _ -> fold_results(expand_helper(FT, all, Word, ":"))
            end;
        {_,_,_} ->
            case Word of
                [] -> {no, [], []};
                _ ->
            TypeOfExpand = expand_function(Bef1),
            CompleteChar
                = case TypeOfExpand of
                      help -> ", ";
                      help_type -> ", ";
                      _ -> ":"
                  end,
            fold_results(expand_helper(FT, TypeOfExpand, Word, CompleteChar))
        end
    end.
expand_keyword(Word) ->
    Keywords = ["begin", "case", "of", "receive", "after", "maybe", "try", "catch", "throw", "if", "fun", "when", "end"],
    {Res, Expansion, Matches} = match(Word, Keywords, ""),
    case Matches of
        [] -> {no, [], []};
        [{Word, _}] -> {no, [], []}; %% exact match
        _ -> {Res,Expansion,[#{title=>"keywords", elems=>Matches, options=>[highlight_all]}]}
    end.
expand_helper(_, help, Word, CompleteChar) ->
    [expand_module_name(Word, CompleteChar)];
expand_helper(_, help_type, Word, CompleteChar) ->
    [expand_module_name(Word, CompleteChar)];
expand_helper(FT, all, Word, CompleteChar) ->
    [expand_module_name(Word, CompleteChar), expand_bifs(Word), expand_shell_default(Word),
     expand_user_defined_functions(FT, Word), expand_keyword(Word)];
expand_helper(FT, _, Word, CompleteChar) ->
    [expand_module_name(Word, CompleteChar), expand_bifs(Word),
     expand_user_defined_functions(FT, Word), expand_keyword(Word)].
expand_function("("++Str) ->
    case edlin:over_word(Str, [], 0) of
        {_,"h",_} ->
            help;
        {_,"ht",_} ->
            help_type;
        _ ->
            module
    end;
expand_function(_) ->
    module.

expand_bifs(Prefix) ->
    Alts = [EA || {E,A}=EA <- get_exports(erlang), erl_internal:bif(E,A)],
    CC = "(",
    case match(Prefix, Alts, CC) of
        {_Res,_Expansion,[]}=M -> M;
        {Res,Expansion, Matches} -> {Res,Expansion,[#{title=>"bifs", elems=>Matches, options=>[highlight_all]}]}
    end.

expand_shell_default(Prefix) ->
    Alts = get_exports(shell_default) ++ shell:local_func(),
    CC = "(",
    case match(Prefix, Alts, CC) of
        {_Res,_Expansion,[]}=M -> M;
        {Res,Expansion, Matches} -> {Res,Expansion,[#{title=>"commands",elems=>Matches, options=>[highlight_all]}]}
    end.

expand_user_defined_functions(FT, Prefix) ->
    Alts = [{Name, Arity}||{{function, {_, Name, Arity}}, _} <- FT],
    CC = "(",
    case match(Prefix, Alts, CC) of
        {_Res,_Expansion,[]}=M -> M;
        {Res,Expansion, Matches} -> {Res,Expansion,[#{title=>"user_defined", elems=>Matches, options=>[highlight_all]}]}
    end.

expand_module_name("",_) ->
    {no, [], []};
expand_module_name(Prefix,CC) ->
    Alts = [{list_to_atom(M),""} || {M,_,_} <- code:all_available()],
    case match(Prefix, Alts, CC) of
        {_Res,_Expansion,[]}=M -> M;
        {Res,Expansion, Matches} -> {Res,Expansion,[#{title=>"modules", elems=>Matches, options=>[highlight_all]}]}
    end.

get_arities("shell_default"=ModStr, FuncStr, FT) ->
    case [A || {{function, {_, Fun, A}}, _} <- FT, Fun =:= list_to_atom(FuncStr)] of
        [] -> get_arities(ModStr, FuncStr);
        Arities -> Arities
    end;
get_arities(ModStr, FuncStr, _) ->
    get_arities(ModStr, FuncStr).
get_arities(ModStr, FuncStr) ->
    case to_atom(ModStr) of
        {ok, Mod} ->
            Exports = get_exports(Mod),
            lists:sort(
              [A || {H, A} <- Exports, string:equal(FuncStr, flat_write(H))]);
        error ->
            {no, [], []}
    end.

get_exports(Mod) ->
    case erlang:module_loaded(Mod) of
        true ->
            Mod:module_info(exports);
        false ->
            case beam_lib:chunks(code:which(Mod), [exports]) of
                {ok, {Mod, [{exports,E}]}} ->
                    E;
                _ ->
                    []
            end
    end.

expand_function_name(ModStr, FuncPrefix, CompleteChar, FT) ->
    case to_atom(ModStr) of
        {ok, Mod} ->
            Extra = case Mod of
                        shell_default -> [{Name, Arity}||{{function, {_, Name, Arity}}, _} <- FT];
                        _ -> []
                    end,
            Exports = get_exports(Mod) ++ Extra,
            {Res, Expansion, Matches}=Result = match(FuncPrefix, Exports, CompleteChar),
            case Matches of
                [] -> Result;
                _ -> {Res, Expansion, [#{title=>"functions", elems=>Matches, options=>[highlight_all]}]}
            end;
        error ->
            {no, [], []}
    end.

get_module_types(Mod) ->
    case code:get_doc(Mod, #{sources => [debug_info]}) of
        {ok, #docs_v1{ docs = Docs } } ->
            [{T, A} || {{type, T, A},_Anno,_Sig,_Doc,_Meta} <- Docs];
        _ -> {no, [], []}
    end.

expand_type_name(ModStr, TypePrefix, CompleteChar) ->
    case to_atom(ModStr) of
        {ok, Mod} ->
            case get_module_types(Mod) of
                {no, [], []} ->
                    {no, [], []};
                Types ->
                    {Res, Expansion, Matches}=Result = match(TypePrefix, Types, CompleteChar),
                    case Matches of
                        [] -> Result;
                        _ -> {Res, Expansion, [#{title=>"types", elems=>Matches, options=>[highlight_all]}]}
                    end
            end;
        error ->
            {no, [], []}
    end.

to_atom(Str) ->
    case erl_scan:string(Str) of
        {ok, [{atom,_,A}], _} ->
            {ok, A};
        _ ->
            error
    end.

to_list(Atom) ->
    io_lib:write_atom(Atom).

strip_quotes(Atom) ->
    [C || C<-atom_to_list(Atom), C/=$'].

match_preprocess_alt({_,_}=Alt) -> Alt;
match_preprocess_alt(X) -> {X, ""}.

match(Prefix, Alts, Extra0) ->
    Alts2 = [match_preprocess_alt(A) || A <- Alts],
    Len = string:length(Prefix),
    Matches = lists:sort(
                [{S, A} || {H, A} <- Alts2,
                           lists:prefix(Prefix, S=flat_write(H))]),
    Matches2 = lists:usort(
                 case Extra0 of
                     [] -> [{S,[]} || {S,_} <- Matches];
                     _  -> [{S,[{ending, Extra0}]} || {S,_} <- Matches]
                 end),
    case longest_common_head([N || {N, _} <- Matches]) of
        {partial, []} ->
            {no, [], Matches2};
        {partial, Str} ->
            case string:slice(Str, Len) of
                [] ->
                    {yes, [], Matches2};
                Remain ->
                    {yes, Remain, Matches2}
            end;
        {complete, Str} ->
            Extra = case {Extra0,Matches} of
                        {"/",[{Str,N}]} when is_integer(N) -> "/"++integer_to_list(N);
                        {"(",[{Str,0}]} -> "()";
                        {_,_} -> Extra0
                    end,
            {yes, string:slice(Str, Len) ++ Extra, ordsets:from_list(Matches2)};
        no ->
            {no, [], []}
    end.

flat_write(T) when is_atom(T) ->
    lists:flatten(io_lib:fwrite("~tw",[T]));
flat_write(S) ->
    S.

special_sort1([C|A], B) when C == ${ ; C == $. ; C == $# ->
    special_sort1(A, B);
special_sort1(A, [C|B]) when C == ${ ; C == $. ; C == $# ->
    special_sort1(A,B);
special_sort1(A,B) ->
    string:lowercase(A) =< string:lowercase(B).
special_sort(#{title:=A}, #{title:=B}) ->
    special_sort1(A,B);
%% Sections and elemts should not be in the same list
special_sort(#{}, {}) ->
    error;
special_sort({}, #{}) ->
    error;
special_sort({A,_},{B,_}) ->
    special_sort1(A,B);
special_sort(A,B) ->
    special_sort1(A,B).

to_legacy_format([]) -> [];
to_legacy_format([#{title:=Title}|Rest]) when Title =:= "commands"; Title =:= "bifs" ->
    to_legacy_format(Rest);
to_legacy_format([#{title:=Title, elems:=Elems}|Rest])
  when Title =:= "modules"; Title =:= "functions"; Title =:= "bindings";
       Title =:= "user_defined", Title =:= "records"; Title =:= "fields";
       Title =:= "types"; Title =:= "atoms"; Title =:= "matches";
       Title =:= "keywords"; Title =:= "typespecs" ->
    Elems1 = to_legacy_format(Elems),
    Elems1 ++ to_legacy_format(Rest);
to_legacy_format([#{title:=Title, elems:=_Elems}|Rest]) ->
    [Title] ++ to_legacy_format(Rest);
to_legacy_format([{Val, _}|Rest]) ->
    [{Val, ""}] ++ to_legacy_format(Rest).

format_matches([], _LineWidth) -> [];
format_matches([#{}|_]=FF, LineWidth) ->
    %% Group function head that have the exact same Type suggestion
    Groups = maps:groups_from_list(
                fun(#{title:=Title, elems:=T, options:=Opts}) ->
                    Separator = proplists:get_value(separator, Opts, "\n"),
                    case lists:last(string:split(Title++Separator, "\n", all)) of
                        [] -> format_section_matches(T, LineWidth);
  we have chars that compete with the results on the first line
                            Len = length(Chars),
                            format_section_matches(T, LineWidth, Len)
                    end
                end,
               fun(F) ->
                       format_title(F, LineWidth)
               end, FF),
    S = lists:flatten(
      [lists:join("", F)++Matches ||
          {Matches, F}<-lists:sort(fun({_,A},{_,B}) -> A =< B end, maps:to_list(Groups))]),
    lists:flatten(string:trim(S, trailing)++"\n");
format_matches(Elems, LineWidth) ->
    S = format_section_matches1(Elems, LineWidth, 0),
    lists:flatten(string:trim(S, trailing)++"\n").
format_title(#{title:=MFA, options:=Options}, _LineWidth) ->
    case proplists:get_value(hide, Options) of
        title -> "";
        _ ->
            Separator = proplists:get_value(separator, Options, "\n"),
            HighlightAll = proplists:is_defined(highlight_all, Options),
            case HighlightAll of
                true -> "\033[;1;4m"++MFA++"\033[0m"++Separator;
                _ ->
                    HighlightParam = proplists:get_value(highlight_param, Options, false),
                    
                    MFA2 = case HighlightParam of
                        false -> MFA;
                        _ -> 
                            PreviousParams = HighlightParam -1,
                            TuplePattern = "(?:\\{[^\\}]+\\})",
                            AtomVarPattern = "(?:\\w+)",
                            TypePattern="(?:(?:"++AtomVarPattern++":)?(?:"++AtomVarPattern++"\\(\\))(?:\\s[><=]+\\s\\d+)?)",
                            SimplePatterns = "(?:"++TuplePattern++"|"++TypePattern++"|"++AtomVarPattern++")",
                            UnionPattern = "(?:"++SimplePatterns++"(?:\\s\\|\\s"++SimplePatterns++")*)",
                            FunPattern="(?:fun\\(\\(" ++ UnionPattern ++ "\\)\\s*->\\s*" ++ UnionPattern ++ "\\))",
                            ArgPattern3 = "(?:"++FunPattern++"|"++UnionPattern++")",
                            PrevArgs="(?:"++ArgPattern3++",\\s){"++integer_to_list(PreviousParams) ++ "}",
  \\1
                            
\\2
                            NextArgs="(?:,\\s"++ArgPattern3++")*",
  \\3

                            re:replace(MFA, 
                        FunctionHeadStart ++ HighlightArg ++ FunctionHeadEnd,
                                "\\1\033[;1;4m\\2\033[0m\\3",
                            [global, {return, list}, unicode])
                    end,
                    Highlight = proplists:get_value(highlight, Options, false),
                    case Highlight of
                        false -> MFA2;
                        _ -> re:replace(MFA2, "(\\Q"++Highlight++"\\E)", "\033[;1;4m\\1\033[0m", [global, {return, list}, unicode])
                    end ++ Separator
            end
    end;
format_title(_Elems, _LineWidth) ->
    %% not a section, old interface
    %% output empty list
    "".

format_section_matches(LS, LineWidth) -> format_section_matches(LS, LineWidth, 0).
format_section_matches([], _, _) -> "\n";
format_section_matches([#{}|_]=FF, LineWidth, Acc) ->
    Groups = maps:groups_from_list(
        fun(#{title:=Title, elems:=T, options:=Opts}) ->
                Separator = proplists:get_value(separator, Opts, "\n"),
                case lists:last(string:split(Title++Separator, "\n", trailing)) of
                    [] -> format_section_matches(T, LineWidth);
  we have chars that compete with the results on the first line
                        Len = string:length(Chars),
                        format_section_matches(T, LineWidth, Len+Acc)
                end
        end,
        fun(F) ->
                format_title(F, LineWidth)
        end, FF),
    lists:flatten(
        [lists:join("", F)++Matches ||
        {Matches, F}<-lists:sort(fun({_,A},{_,B}) -> A =< B end, maps:to_list(Groups))]);
format_section_matches(Elems, LineWidth, Acc) ->
    format_section_matches1(Elems, LineWidth, Acc).

format_section_matches1([], _, _) -> [];
format_section_matches1(LS, LineWidth, Len) ->
    L = lists:usort(fun special_sort/2, ordsets:to_list(LS)),
    Opt = case Len == 0 of
        true -> [];
        false -> [{title, Len}]
    end,    
    S1 = format_col(Opt ++ L, field_width(Opt ++ L, LineWidth), Len, [], LineWidth, Opt),
    S2 = lists:map(
           fun(Line) ->
                case string:length(Line) of
                    Len1 when Len1 > LineWidth ->
                        string:sub_string(Line, 1, LineWidth-4) ++ "...\n";
                    _ -> Line
                end
           end,S1),
    lists:flatten(string:trim(S2, trailing)++"\n").

format_col(X, Width, Len, Acc, LL, Opt) when Width + Len > LL ->
    format_col(X, Width, 0, ["\n" | Acc], LL, Opt);
format_col([{title,TitleLen}|T], Width, Len, Acc0, LL, Opt) ->
    Acc = [io_lib:format("~-*ts", [Width-TitleLen, ""])|Acc0],
    format_col(T, Width, Len+Width, Acc, LL, Opt);
format_col([A|T], Width, Len, Acc0, LL, _Opt) ->
    {H0, R} = format_val(A),
    Hmax = LL - string:length(R),
    {H, _} =
        case string:length(H0) > Hmax of
            true -> {io_lib:format("~-*ts", [Hmax - 3, H0]) ++ "...", true};
            false -> {H0, false}
        end,
    Acc = [io_lib:format("~-*ts", [Width, H ++ R]) | Acc0],
    format_col(T, Width, Len+Width, Acc, LL, []);
format_col([], _, _, Acc, _LL, _Opt) ->
    lists:reverse(Acc).

format_val({H, L}) when is_list(L) ->
    {H, proplists:get_value(ending, L, "")};
format_val({H, I}) when is_integer(I) ->
    {H, "/"++integer_to_list(I)};
format_val({H, _}) ->
    {H, ""};
format_val(H) ->
    {H, ""}.

field_width(L, LL) -> field_width(L, 0, LL).
field_width([{title, Len}|T], W, LL) ->
    case Len of
        L when L > W -> field_width(T, L, LL);
        _ -> field_width(T, W, LL)
    end;
field_width([H|T], W, LL) ->
    {H1, Ending} = format_val(H),
    case string:length(H1++Ending) of
        L when L > W -> field_width(T, L, LL);
        _ -> field_width(T, W, LL)
    end;
field_width([], W, LL) when W < LL ->
    W + 4;
field_width([], _, LL) ->
    LL.

number_matches([#{ elems := Matches }|T]) ->
    number_matches(Matches) + number_matches(T);
number_matches([_|T]) ->
    1 + number_matches(T);
number_matches([]) ->
    0.

%% Strings are handled naively, but it should be OK here.
longest_common_head([]) ->
    no;
longest_common_head(LL) ->
    longest_common_head(LL, []).

longest_common_head([[]|_], L) ->
    {partial, lists: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, lists:reverse([H|L])}
            end;
        false ->
            {partial, lists: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.

  over_white(Chars , InitialStack , InitialCount ) - >
     { RemainingChars ,  , 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) when is_list(Cs) ->
    {Cs,Stack,N}.


 %CopyrightBegin%


 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

     -2.0

 Unless required by applicable law or agreed to in writing, software
 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%

 filepaths, variable binding, record names, function parameter values,
 record fields and map keys and record field values.
 highlight the whole title
 highlight this part of the title
 highlight this parameter
 hide the title
 hide the results
 specify another separator between title and result
 highlight the whole title
 highlight this part of the title
 highlight this parameter
 hide the title
 hide the results
 specify another separator between title and result
 Running on a shell that does not support get_state()
 Only used for testing
 Complete with arity in a 'fun mod:fun/' expression
 Suggest type of function parameter
 Complete an unfinished list, tuple or map using type of function parameter
 Complete an unfinished key or suggest valid keys of a map binding
 Check that the actual type on previous arguments
 matches with the expected types
 Since we are not doing any evaluations at this point we
 don't know if a parenthesis, keyword, var, call or fun returns
 a value with the wrong type.
 Just assume that it will evaluate to the correct type.
 we know that the atom is unfinished, and thus cannot match any valid atom
 Types not possible to deduce with erl_parse
 If string contains variables, erl_parse:parse_term will fail, but we
 consider them valid sooo.. lets replace them with the atom var
 If we find any then every type is valid
 Expand function type
 Expand last nesting types
 in the case of
 erlang:system_info({allocator, ) 
 we have a tuple nesting with an atom
 this should give us "allocator" in the nestingsargs, and empty unfinished part
 but we also know that we have a nesting, if we expect something other than a tuple, we shouldnt print that function
 lets call it NestingType
 now when that is fixed, how do we filter {allocator_sizes, ...} and others
 if we know had a tuple, {allocator_sizes, } will be allowed
 probably get_arity will return none
 filters all files starting with . unless Word starts with .
 outputs / on end of folders
 Extract a whole filepath
 Stops as soon as we hit a double quote (")
 and returns everything it found before stopping.
 assumes the string is not a filepath if it contains unescaped spaces
 expand_module_function(CurrentBefore, FT) -> {yes, Expansion, Matches} | {no, [], Matches}
  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

  The function also handles expansion with "h(" and "ht("" for module and functions.
fold_results([expand_shell_default(Word), expand_user_defined_functions(FT, Word)]);
 exact match
 Sections and elemts should not be in the same list
 Group function head that have the exact same Type suggestion
 not a section, old interface
 output empty list
 Strings are handled naively, but it should be OK here.
  Copyright Ericsson AB 2005 - 2023 . All Rights Reserved .
  Licensed under the Apache License , Version 2.0 ( the " License " ) ;
  distributed under the License is distributed on an " AS IS " BASIS ,
-module(edlin_expand).
  a default expand function for edlin , expanding modules , functions
-include_lib("kernel/include/eep48.hrl").
-export([expand/1, expand/2, expand/3, format_matches/2, number_matches/1, get_exports/1,
         shell_default_or_bif/1, bif/1, over_word/1]).
-export([is_type/3, match_arguments1/3]).
-record(shell_state,{
                     bindings = [],
                     records = [],
                     functions = []
                    }).

-spec expand(Bef0) -> {Res, Completion, Matches} when
  a line of erlang expressions in reverse
      Res :: 'yes' | 'no',
      Completion :: string(),
      Matches :: [Element] | [Section],
      Element :: {string(), [ElementOption]},
      ElementOption :: {ending, string()},
      Section :: #{title:=string(), elems:=Matches, options:=SectionOption},
expand(Bef0) ->
    expand(Bef0, [{legacy_output, true}]).

-spec expand(Bef0, Opts) -> {Res, Completion, Matches} when
  a line of erlang expressions in reverse
      Opts :: [Option],
      Option :: {legacy_output, boolean()},
      Res :: 'yes' | 'no',
      Completion :: string(),
      Matches :: [Element] | [Section],
      Element :: {string(), [ElementOption]},
      ElementOption :: {ending, string()},
      Section :: #{title:=string(), elems:=Matches, options:=SectionOption},
expand(Bef0, Opts) ->
    ShellState = try
                     shell:get_state()
                 catch
                     _:_ ->
                         #shell_state{bindings=[],records=[],functions=[]}
                 end,
    expand(Bef0, Opts, ShellState).

expand(Bef0, Opts, #shell_state{bindings = Bs, records = RT, functions = FT}) ->
    LegacyOutput = proplists:get_value(legacy_output, Opts, false),
    {_Bef1, Word} = over_word(Bef0),
    {Res, Expansion, Matches} = case edlin_context:get_context(Bef0) of

                 {string} -> expand_string(Bef0);

                 {binding} -> expand_binding(Word, Bs);

                 {term} -> expand_module_function(Bef0, FT);
                 {term, _, {_, Unfinished}} -> expand_module_function(lists:reverse(Unfinished), FT);
                 {error, _Column} ->
                    {no, [], []};
                 {function} -> expand_module_function(Bef0, FT);
                 {fun_} -> expand_module_function(Bef0, FT);

                 {fun_, Mod} -> expand_function_name(Mod, Word, "/", FT);

                 {fun_, Mod, Fun} ->
                    Arities = [integer_to_list(A) || A <- get_arities(Mod, Fun)],
                    match(Word, Arities, "");
                 {new_fun, _ArgsString} -> {no, [], []};
                 {function, Mod, Fun, Args, Unfinished, Nesting} ->
                    Mod2 = case Mod of
                        "user_defined" -> "shell_default";
                        _ -> Mod
                    end,
                    FunExpansion = expand_function_type(Mod2, Fun, Args, Unfinished, Nesting, FT),
                    case Word of
                        [] -> FunExpansion;
                        _ ->
                            ModuleOrBifs = expand_helper(FT, module, Word, ":"),
                            Functions = case Args =/= [] andalso lists:last(Args) of
                                {atom, MaybeMod} -> expand_function_name(MaybeMod, Word, "", FT);
                                _ -> {no, [], []}
                            end,
                            fold_results([FunExpansion] ++ ModuleOrBifs ++ [Functions])
                    end;

                 {map, Binding, Keys} -> expand_map(Word, Bs, Binding, Keys);

                 {map_or_record} ->
                     {[$#|Bef2], _} = over_word(Bef0),
                     {_, Var} = over_word(Bef2),
                     case Bs of
                         [] -> expand_record(Word, RT);
                         _ ->
                             case proplists:get_value(list_to_atom(Var), Bs) of
                                 undefined ->
                                     expand_record(Word, RT);
                                 Map when is_map(Map) -> {yes, "{", []};
                                 RecordTuple when is_tuple(RecordTuple), tuple_size(RecordTuple) > 0 ->
                                     Atom = erlang:element(1, RecordTuple),
                                     case (is_atom(Atom) andalso lists:keysearch(Atom, 1, RT)) of
                                        {value, {Atom, _}} -> match(Word, [Atom], "{");
                                        _ -> {no, [], []}
                                     end;
                                 _ -> {no, [], []}
                             end
                     end;

                 {record} -> expand_record(Word, RT);

                 {record, Record, Fields, FieldToComplete, Args, Unfinished, Nestings} ->
                     RecordExpansion = expand_record_fields(FieldToComplete, Unfinished, Record, Fields, RT, Args, Nestings, FT),
                     case Word of
                         [] -> RecordExpansion;
                         _ ->
                             ModuleOrBifs = expand_helper(FT, module,Word,":"),
                             fold_results([RecordExpansion] ++ ModuleOrBifs)
                     end;
                 _ -> {no, [], []}

             end,
    Matches1 = case {Res,number_matches(Matches)} of
        {yes, 1} -> [];
        _ -> Matches
    end,
    case LegacyOutput of
        true -> {Res, Expansion, to_legacy_format(Matches1)};
        false -> {Res, Expansion, Matches1}
    end.
expand_map(_, [], _, _) ->
    {no, [], []};
expand_map(Word, Bs, Binding, Keys) ->
    case proplists:get_value(list_to_atom(Binding), Bs) of
        Map when is_map(Map) ->
            K1 = sets:from_list(maps:keys(Map)),
            K2 = sets:subtract(K1, sets:from_list([list_to_atom(K) || K <- Keys])),
            match(Word, sets:to_list(K2), "=>");
        _ -> {no, [], []}
    end.

over_word(Bef) ->
    {Bef1,_,_} = over_white(Bef, [], 0),
    {Bef2, Word, _} = edlin:over_word(Bef1, [], 0),
    {Bef2, Word}.


expand_binding(Prefix, Bindings) ->
    Alts = [strip_quotes(K) || {K,_} <- Bindings],
    case match(Prefix, Alts, "") of
        {_Res,_Expansion,[]}=M -> M;
        {Res, Expansion, Matches} -> {Res,Expansion,[#{title=>"bindings", elems=>Matches, options=>[highlight_all]}]}
    end.

expand_record(Prefix, RT) ->
    Alts = [Name || {Name, _} <- RT],
    case match(Prefix, Alts, "{") of
        {_Res,_Expansion,[]}=M -> M;
        {Res, Expansion, Matches} -> {Res,Expansion,[#{title=>"records", elems=>Matches, options=>[highlight_all]}]}
    end.

expand_record_fields(FieldToComplete, Word, Record, Fields, RT, _Args, Nestings, FT) ->
    Record2 = list_to_atom(Record),
    FieldSet2 = sets:from_list([list_to_atom(F) || F <- Fields]),
    FieldToComplete2 = list_to_atom(FieldToComplete),
    Word1 = case Word of
                {_, Word2} -> Word2;
                [] -> []
            end,
    case [RecordSpec || {Record3, RecordSpec} <- RT, Record2 =:= Record3] of
        [RecordType|_] ->
            case sets:is_element(FieldToComplete2, FieldSet2) of
                true ->
                    expand_record_field_content(FieldToComplete2, RecordType, Word1, Nestings, FT);
                false ->
                    expand_record_field_name(Record2, FieldSet2, RecordType, Word1)
            end;
        _ ->
            {no, [], []}
    end.

expand_record_field_name(Record, Fields, RecordType, Word) ->
    RecordFieldsList = extract_record_fields(Record, RecordType),
    RecordFieldsSet = sets:from_list(RecordFieldsList),
    RecordFields = sets:subtract(RecordFieldsSet, Fields),
    Alts = sets:to_list(RecordFields),
    case match(Word, Alts, "=") of
        {_Res,_Expansion,[]}=M -> M;
        {Res, Expansion, Matches} -> {Res,Expansion,[#{title=>"fields", elems=>Matches, options=>[highlight_all]}]}
    end.

expand_record_field_content(Field,
                            {attribute, _, record,
                             {_Record, FieldTypes}}, Word, Nestings, FT) ->
    FieldTypesFiltered = [Type1 || {typed_record_field, {record_field, _, {_,_, F}}, Type1} <- FieldTypes, F == Field] ++
        [Type1 || {typed_record_field, {record_field, _, {_,_, F}, _}, Type1} <- FieldTypes, F == Field],
    case FieldTypesFiltered of
        [] -> {no, [], []};
        [Type] ->
            T = edlin_type_suggestion:type_tree(erlang, Type, Nestings, FT),
            Types = edlin_type_suggestion:get_types([], T, Nestings),
            case Nestings of
                [] ->
                    Atoms = edlin_type_suggestion:get_atoms([], T, Nestings),
                    case {Word, match(Word, Atoms, ", ")} of
                        {[],{_Res,_Expansion,_}} -> {_Res, _Expansion, [#{title=>"types", elems=>Types, options=>[{hide, title}]}]};
                        {_,{_Res,_Expansion,[]}=M} -> M;
                        {_,{Res,Expansion,Matches}} -> {Res, Expansion, [#{title=>"matches", elems=>Matches, options=>[highlight_all]}]}
                    end;
                _ ->
                    expand_nesting_content(T, [], Nestings, #{title=>"types", elems=>Types, options=>[{hide, title}]})
            end
    end.

match_arguments({function, {{parameters, Ps}, _}, Cs}, As) ->
    match_arguments1(Ps, Cs, As);
match_arguments({{parameters, Ps}, _}, As) ->
    match_arguments1(Ps, [], As).
match_arguments1(_,_,[]) -> true;
match_arguments1([_|Ps], Cs, [{parenthesis, _}|As]) ->
    match_arguments1(Ps, Cs, As);
match_arguments1([_|Ps], Cs, [{operation, _}|As]) ->
    match_arguments1(Ps, Cs, As);
match_arguments1([_|Ps], Cs, [{keyword, _}|As]) ->
    match_arguments1(Ps, Cs, As);
match_arguments1([_|Ps], Cs, [{var, _}|As]) ->
    match_arguments1(Ps, Cs, As);
match_arguments1([_|Ps], Cs, [{call, _}|As]) ->
    match_arguments1(Ps, Cs, As);
match_arguments1([_|Ps], Cs, [{fun_, _}|As]) ->
    match_arguments1(Ps, Cs, As);
match_arguments1([P|Ps], Cs, [{atom, [$'|_]=String}|As]) ->
    case edlin_context:odd_quotes($', lists:reverse(String)) of
        _ -> case is_type(P, Cs, String) of
                 true -> match_arguments1(Ps, Cs, As);
                 false -> false
             end
    end;
match_arguments1([P|Ps], Cs, [{_, String}|As]) ->
    case is_type(P, Cs, String) of
        true -> match_arguments1(Ps, Cs, As);
        false -> false
    end.

is_type(Type, Cs, String) ->
    {ok, A, _} = erl_scan:string(String++"."),
    Types = [T || T <- edlin_type_suggestion:get_types(Cs, Type, [], [no_print]) ],
    try
        {ok, Term} = erl_parse:parse_term(A),
        case Term of
            Atom when is_atom(Atom) ->
                Atoms = edlin_type_suggestion:get_atoms(Cs, Type, []),
                lists:member(to_list(Atom), Atoms) orelse 
                    lists:member(atom_to_list(Atom), Atoms) orelse
                        find_type(Types, [atom, node, module, 'fun']);
            Tuple when is_tuple(Tuple) -> find_type(Types, [tuple]);
            Map when is_map(Map) -> find_type(Types, [map]);
            Binary when is_binary(Binary) -> find_type(Types, [binary]);
            Float when is_float(Float) -> find_type(Types, [float]);
            Integer when is_integer(Integer) -> check_integer_type(Types, Integer);
            List when is_list(List), length(List) > 0 ->
                find_type(Types, [list, string, nonempty_list,maybe_improper_list, nonempty_improper_list]);
            List when is_list(List) -> find_type(Types, [list, string, maybe_improper_list])
        end
    catch
        _:_ ->
            B = [(fun({var, Anno, _}) -> {atom, Anno, var}; (Token) -> Token end)(X) || X <- A],
            try
                {ok, Term2} = erl_parse:parse_term(B),
                case Term2 of
                    Tuple2 when is_tuple(Tuple2) -> find_type(Types, [tuple]);
                    Map2 when is_map(Map2) -> find_type(Types, [map]);
                    Binary2 when is_binary(Binary2) -> find_type(Types, [binary]);
                    List2 when is_list(List2), length(List2) > 0 ->
                        find_type(Types, [list, string, nonempty_list,maybe_improper_list, nonempty_improper_list]);
                    List2 when is_list(List2) -> find_type(Types, [list, string, maybe_improper_list])
                end
            catch
                _:_ ->
                    case A of
                        [{'#',_},{var,_,'Port'},{'<',_},{float,_,_},{'>',_},{dot,_}] -> find_type(Types, [port]);
                        [{'#',_},{var,_,'Ref'},{'<',_},{float,_,_},{'.',_},{float,_,_},{'>',_},{dot,_}] -> find_type(Types, [reference]);
                        [{'fun',_},{'(',_} | _] -> find_type(Types, [parameters, function, 'fun']);
                        [{'#',_},{var,_,'Fun'},{'<',_},{atom,_,erl_eval},{'.',_},{float,_,_},{'>',_}] -> find_type(Types, [parameters, function, 'fun']);
                        [{'<', _}, {float, _, _}, {'.', _}, {integer, _, _}, {'>', _}, {dot, _}] -> find_type(Types, [pid]);
                        [{'#', _}, {atom, _, RecordName},{'{', _}| _] -> find_type(Types, [{record, RecordName}]);
                        _ -> false
                    end
            end
    end.

find_type([],_) -> false;
find_type([{type, any, []}|_], _) -> true;
find_type([{{parameters, _},_}|Types], ValidTypes) ->
    case lists:member(parameters, ValidTypes) of
        true -> true;
        false -> find_type(Types, ValidTypes)
    end;
find_type([{record, _}=Type|Types], ValidTypes) ->
    case lists:member(Type, ValidTypes) of
        true -> true;
        false -> find_type(Types, ValidTypes)
    end;
find_type([{Type, _}|Types], ValidTypes) ->
    case lists:member(Type, ValidTypes) of
        true -> true;
        false -> find_type(Types, ValidTypes)
    end;
find_type([{type, Type, _}|Types], ValidTypes) ->
    case lists:member(Type, ValidTypes) of
        true -> true;
        false -> find_type(Types, ValidTypes)
    end;
find_type([{type, Type, _, any}|Types], ValidTypes) ->
    case lists:member(Type, ValidTypes) of
        true -> true;
        false -> find_type(Types, ValidTypes)
    end;
find_type([_|Types], ValidTypes) -> find_type(Types, ValidTypes).

in_range(_, []) -> false;
in_range(Integer, [{type, range, [{integer, Start}, {integer, End}]}|_]) when Start =< Integer, Integer =< End -> true;
in_range(Integer, [_|Types]) -> in_range(Integer, Types).

check_integer_type(Types, Integer) when Integer == 0 -> find_type(Types, [integer, non_neg_integer, arity]) orelse in_range(Integer, Types);
check_integer_type(Types, Integer) when Integer < 0 -> find_type(Types, [integer, neg_integer]) orelse in_range(Integer, Types);
check_integer_type(Types, Integer) when Integer > 0 -> find_type(Types, [integer, non_neg_integer, pos_integer]) orelse in_range(Integer, Types).

add_to_last_nesting(Term, Nesting) ->
    Last = lists:last(Nesting),
    List = lists:droplast(Nesting),
    case Last of
        {tuple, Args, U} ->
            List ++ [{tuple, Args ++ [Term], U}];
        {list, Args, U} ->
            List ++ [{list, Args ++ [Term], U}];
        {map, F, Fs, Args, U} ->
            List ++ [{map, F, Fs, Args ++ [Term], U}]
    end.

close_nesting(Nesting) ->
    Last = lists:last(Nesting),
    case Last of
        {tuple, _Args, _} ->
            "}";
        {list, _Args, _} ->
            "]";
        {map, _F, _Fs, _Args, _} ->
            "}"
    end.
expand_function_parameter_type(Mod, MFA, FunType, Args, Unfinished, Nestings, FT) ->
    TypeTree = edlin_type_suggestion:type_tree(Mod, FunType, Nestings, FT),

    {Parameters, Constraints1} = case TypeTree of
                                     {function, {{parameters, Parameters1},_}, Constraints} ->
                                         {Parameters1, Constraints};
                                     {{parameters, Parameters1},_}=_F ->
                                         {Parameters1, []}
                                 end,
    case match_arguments(TypeTree, Args) of
        false -> {no, [], []};
        true when Parameters == [] -> {yes, ")", [#{title=>MFA, elems=>[")"], options=>[]}]};
        true ->
            Parameter = lists:nth(length(Args)+1, Parameters),
            {T, _Name} = case Parameter of
                            Atom when is_atom(Atom) -> {Atom, atom_to_list(Atom)};
                            {var, Name1}=T1 -> {T1, atom_to_list(Name1)};
                            {ann_type, {var, Name1}, T1} -> {T1, atom_to_list(Name1)};
                            T1 -> {T1, edlin_type_suggestion:print_type(T1, [], [{first_only, true}])}
                        end,
            Ts = edlin_type_suggestion:get_types(Constraints1, T, Nestings),
            Types = case Ts of
                        [] -> [];
                        _  ->
                            SectionTypes = [S || #{}=S <- Ts],
                            Types1 = case [E || {_, _}=E<-Ts] of
                                [] -> SectionTypes;
                                Elems -> 
                                    case SectionTypes of
                                        [] -> Elems;
                                        ST -> [#{title=>"simple types", elems=>Elems, options=>[{hide, title}]}|ST]
                                    end
                            end,
                            
                            [#{title=>"types", elems=>(Types1), options=>[{hide, title}]}]
                    end,
            case Nestings of
                    case Unfinished of
                        [] ->
                            case T of
                                Atom1 when is_atom(Atom1) ->
                                    CC = case length(Args)+1 < length(Parameters) of
                                             true -> ", ";
                                             false ->")"
                                         end,
                                    {Res, Expansion, Matches} = match([], [Atom1], CC),
                                    case Matches of
                                        [] -> {no, [], []};
                                        _ -> {Res, Expansion, [#{title=>MFA, elems=>[], options=>[{highlight_param, length(Args)+1}]}]}
                                    end;
                                _ when Types == [] ->
                                    {no, [], []};
                                _ ->
                                    {no, [], [#{title=>MFA, elems=>Types, options=>[{highlight_param, length(Args)+1}]}]}
                            end;
                        {_, Word} when is_atom(T) ->
                            CC = case length(Args)+1 < length(Parameters) of
                                     true -> ", ";
                                     false ->")"
                                 end,
                            {Res, Expansion, Matches} = match(Word, [T], CC),
                            case Matches of
                                [] -> {no, [], []};
                                _ -> {Res, Expansion, [#{title=>MFA, elems=>[], options=>[{highlight_param, length(Args)+1}]}]}
                            end;
                        {_, Word} ->
                            {Res, Expansion, Matches} = begin
                                                            CC = case length(Args)+1 < length(Parameters) of
                                                                     true -> ", ";
                                                                     false ->")"
                                                                 end,
                                                            Atoms1 = edlin_type_suggestion:get_atoms(Constraints1, T, Nestings),
                                                            {Res1, Expansion1, Matches1} = match(Word, Atoms1, CC),
                                                            case Matches1 of
                                                                [] ->
                                                                    case match_arguments(TypeTree, Args ++ [Unfinished]) of
                                                                        false -> {Res1, Expansion1, Matches1};
                                                                        true ->
                                                                            {yes, CC, [{CC, []}]}
                                                                    end;
                                                                _ ->
                                                                    {Res1, Expansion1, Matches1}
                                                            end
                                                        end,
                            Match1 = case Matches of
                                         [] -> [];
                                         _  -> Atoms = [#{title=>"atoms", elems=>Matches, options=>[{hide, title}]}],
                                               [#{title=>MFA, elems=>Atoms, options=>[{highlight_param, length(Args)+1}]}]
                                     end,
                            {Res, Expansion,Match1}
                    end;
                    expand_nesting_content(T, Constraints1, Nestings, #{title=>MFA, elems=>Types, options=>[{highlight_param, length(Args)+1}]})
            end
    end.
expand_nesting_content(T, Constraints, Nestings, Section) ->
    {NestingType, UnfinishedNestingArg, NestingArgs} = case lists:last(Nestings) of
                                              {tuple, NestingArgs1, Unfinished1} -> {tuple, Unfinished1, NestingArgs1};
                                              {list, NestingArgs1, Unfinished1} -> {list, Unfinished1, NestingArgs1};
                                              {map, _, _, NestingArgs1, Unfinished1} -> {map, Unfinished1, NestingArgs1}
                                          end,
    Types = [Ts || Ts <- edlin_type_suggestion:get_types(Constraints, T, lists:droplast(Nestings), [no_print]) ],
    case UnfinishedNestingArg of
        [] ->
            case find_type(Types, [NestingType]) of
                true -> 
                    Nestings2 = add_to_last_nesting({var, "Var"}, Nestings),
                    NestingArities = edlin_type_suggestion:get_arity(Constraints, T, Nestings2),
                    
                    fold_results([begin
                        case NestingArity of
                            none -> {no, [], []};
                            _ -> {no, [], [Section]}
                        end
                    end || NestingArity <- NestingArities]);
                false -> {no, [], []}
            end;
        {_, Word} ->
            Atoms1 = edlin_type_suggestion:get_atoms(Constraints, T, Nestings),
            {Res1, Expansion1, Matches1} = match(Word, Atoms1, ""),
            {Res, Expansion, Matches} = case Matches1 of
                                            [] ->
                                                Nestings2 = add_to_last_nesting(UnfinishedNestingArg, Nestings),
                                                NestingArities = edlin_type_suggestion:get_arity(Constraints, T, Nestings2),
                                                fold_results([begin
                                                                  case NestingArity of
                                                                      none -> {no, [], []};
                                                                      _ when NestingType =:= tuple ->
                                                                          CC = case length(NestingArgs)+1 < NestingArity of
                                                                                   true -> ", ";
                                                                                   false -> close_nesting(Nestings)
                                                                               end,
                                                                          {yes, CC, [{CC, []}]};
                                                                      _ when NestingType =:= list ->
                                                                        {no, [], [{", ", []}, {"]", []}]};
                                                                      _ when NestingType =:= map ->
                                                                        {no, [], [{", ",[]},{"}", []}]};
                                                                      _ -> 
                                                                        {no, [], []}
                                                                  end
                                                              end || NestingArity <- NestingArities]);
                                            [{Word2,_}] ->
                                                Nestings2 = add_to_last_nesting({atom, Word2}, Nestings),
                                                NestingArities = edlin_type_suggestion:get_arity(Constraints, T, Nestings2),
                                                fold_results([begin
                                                                  case NestingArity of
                                                                      none -> {no, [], []};
                                                                      _ when NestingType =:= tuple ->
                                                                          CC = case length(NestingArgs)+1 < NestingArity of
                                                                                   true -> ", ";
                                                                                   false -> close_nesting(Nestings)
                                                                               end,
                                                                          {yes, Expansion1++CC, [{Word2, [{ending, CC}]}]};
                                                                      _ -> 
                                                                        {Res1, Expansion1, Matches1}
                                                                  end
                                                              end || NestingArity <- NestingArities]);
                                            _ -> {Res1, Expansion1, Matches1}
                                        end,
            Match1 = case Matches of
                         [] -> [];
                         _  -> Atoms = [#{title=>"atoms", elems=>Matches, options=>[{hide, title}]}],
                               [Section#{elems:=Atoms}]
                     end,
            {Res, Expansion, Match1}
    end.

extract_record_fields(Record, {attribute,_,record,{Record, Fields}})->
    [X || X <- [extract_record_field(F) || F <- Fields], X /= []];
extract_record_fields(_, _)-> error.
extract_record_field({typed_record_field, {_, _,{atom, _, Field}},_})->
    Field;
extract_record_field({typed_record_field, {_, _,{atom, _, Field}, _},_})->
    Field;
extract_record_field({record_field, _,{atom, _, Field},_})->
    Field;
extract_record_field({record_field, _,{atom, _, Field}})->
    Field;
extract_record_field(_) -> [].

fold_results([]) -> {no, [], []};
fold_results([R|Results]) ->
    lists:foldl(fun fold_completion_result/2, R, Results).

fold_completion_result({yes, Cmp1, Matches1}, {yes, Cmp2, Matches2}) ->
    {_, Cmp} = longest_common_head([Cmp1,Cmp2]),
    case Cmp of
        [] -> {no, [], ordsets:union([Matches1,Matches2])};
        _ -> {yes, Cmp, ordsets:union([Matches1,Matches2])}
    end;
fold_completion_result({yes, Cmp, Matches}, {no, [], []}) ->
    {yes, Cmp, Matches};
fold_completion_result({no, [], []},{yes, Cmp, Matches}) ->
    {yes, Cmp, Matches};
fold_completion_result({_, _, Matches1}, {_, [], Matches2}) ->
    {no, [], ordsets:union([Matches1,Matches2])};
fold_completion_result(A, B) ->
    fold_completion_result(B,A).

expand_function_type(ModStr, FunStr, Args, Unfinished, Nestings, FT) ->
    Mod = list_to_atom(ModStr),
    Fun = list_to_atom(FunStr),
    MinArity = if Unfinished =:= [], length(Args) =:= 0 -> 0;
        true -> length(Args)+1
    end,
    case [A || A <- get_arities(ModStr, FunStr, FT), A >= MinArity] of
        [] -> {no, [], []};
        Arities ->
            {Res, Expansion, Matches} = fold_results([begin
                              FunTypes = edlin_type_suggestion:get_function_type(Mod, Fun, Arity, FT),
                              case FunTypes of
                                  [] -> MFA = print_function_head(ModStr, FunStr, Arity),
                                        case Unfinished of
                                            [] -> {no, [], [#{title=>MFA, elems=>[], options=>[]}]};
                                            _ -> {no, [], []}
                                        end;
                                  _ ->
                                      fold_results([begin
                                                        MFA = print_function_head(ModStr, FunStr, FunType, FT),
                                                        expand_function_parameter_type(Mod, MFA, FunType, Args, Unfinished, Nestings, FT)
                                                    end || FunType <- FunTypes])
                              end
                          end || Arity <- Arities]),
            case Matches of
                [] -> {Res, Expansion, Matches};
                _ -> {Res, Expansion, [#{title=>"typespecs", elems=>Matches, options=>[highlight_all]}]}
            end
    end.

  Behaves like zsh
expand_filepath(PathPrefix, Word) ->
    Path = case PathPrefix of
               [$/|_] -> PathPrefix;
               _ ->
                   {ok, Cwd} = file:get_cwd(),
                   Cwd ++ "/" ++ PathPrefix
           end,
    ShowHidden = case Word of
                     "." ++ _ -> true;
                     _ -> false
                 end,
    Entries = case file:list_dir(Path) of
                  {ok, E} -> lists:map(
                               fun(X)->
                                       case filelib:is_dir(Path ++ "/" ++ X) of
                                           true -> X ++ "/";
                                           false -> X
                                       end
                               end, [".."|E]);
                  _ -> []
              end,
    EntriesFiltered = [File || File <- Entries,
                               case File of
                                   [$.|_] -> ShowHidden;
                                   _ -> true
                               end],
    case match(Word, EntriesFiltered, []) of
        {yes, Cmp, [Match]} ->
            case filelib:is_dir(Path ++ "/" ++ Word ++ Cmp) of
                true -> {yes, Cmp, [Match]};
                false -> {yes, Cmp ++ "\"", [Match]}
            end;
        X -> X
    end.

shell(Fun) ->
    case shell:local_func(list_to_atom(Fun)) of
        true -> "shell";
        false -> "user_defined"
    end.

shell_default_or_bif(Fun) ->
    case lists:member(list_to_atom(Fun), [E || {E,_}<-get_exports(shell_default)]) of
        true -> "shell_default";
        _ -> bif(Fun)
    end.
bif(Fun) ->
    case lists:member(list_to_atom(Fun), [E || {E,A}<-get_exports(erlang), erl_internal:bif(E,A)]) of
        true -> "erlang";
        _ -> shell(Fun)
    end.

expand_string(Bef0) ->
    case over_filepath(Bef0, []) of
        {_, Filepath} ->
            {Path, File} = split_at_last_slash(Filepath),
            expand_filepath(Path, File);
        _ -> {no, [], []}
    end.
over_filepath([],_) -> none;
over_filepath([$", $\\|Bef1], Filepath) -> over_filepath(Bef1, [$" | Filepath]);
over_filepath([$"|Bef1], Filepath) -> {Bef1, Filepath};
over_filepath([$\ ,$\\|Bef1], Filepath) -> over_filepath(Bef1, [$\ |Filepath]);
over_filepath([$\ |_], _) -> none;
over_filepath([C|Bef1], Filepath) ->
    over_filepath(Bef1, [C|Filepath]).
split_at_last_slash(Filepath) ->
    {File, Path} = lists:splitwith(fun(X)->X/=$/ end, lists:reverse(Filepath)),
    {lists:reverse(Path), lists:reverse(File)}.

print_function_head(ModStr, FunStr, Arity) ->
    lists:flatten(ModStr ++ ":" ++ FunStr ++ "/" ++ integer_to_list(Arity)).
print_function_head(ModStr, FunStr, FunType, FT) ->
    lists:flatten(print_function_head_from_type(ModStr, FunStr, FunType, FT)).

print_function_head1(Mod, Fun, Par, _Ret) ->
    Mod++":"++Fun++"("++lists:join(", ",
                                   [case P of
                                        Atom when is_atom(Atom) -> atom_to_list(Atom);
                                        {var, V} -> atom_to_list(V);
                                        {ann_type, {var, V}, _T} -> atom_to_list(V);
                                        T -> edlin_type_suggestion:print_type(T, [], [{first_only, true}])
                                    end || {_N,P} <- lists:enumerate(Par)])++")".
print_function_head_from_type(Mod, Fun, FunType, FT) ->
    case edlin_type_suggestion:type_tree(list_to_atom(Mod), FunType, [], FT) of
        {function, {{parameters, Parameters},{return, Return}}, _} ->
            print_function_head1(Mod, Fun, Parameters, Return);
        {{parameters, Parameters},{return, Return}} ->
            print_function_head1(Mod, Fun, Parameters, Return)
    end.

   possible expansions are printed . ´ ´ ´
expand_module_function(Bef0, FT) ->
    {Bef1,Word,_} = edlin:over_word(Bef0, [], 0),
    case over_white(Bef1, [], 0) of
        {[$,|Bef2],_White,_Nwh} ->
            {Bef3,_White1,_Nwh1} = over_white(Bef2, [], 0),
            {Bef4,Mod,_Nm} = edlin:over_word(Bef3, [], 0),
            case expand_function(Bef4) of
                help ->
                    expand_function_name(Mod, Word, ", ", FT);
                help_type ->
                    expand_type_name(Mod, Word, ", ");
                _ ->
                    fold_results(expand_helper(FT, module, Word, ":"))
            end;
        {[$:|Bef2],_White,_Nwh} ->
            {Bef3,_White1,_Nwh1} = over_white(Bef2, [], 0),
            {_,Mod,_Nm} = edlin:over_word(Bef3, [], 0),
            expand_function_name(Mod, Word, "(", FT);
        {[CC, N_Esc|_], _White, _Nwh} when (CC =:= $] orelse CC =:= $) orelse CC =:= $> orelse CC =:= $}
                                            orelse CC =:= $" orelse CC =:= $'),
                                           N_Esc =/= $$, N_Esc =/= $- ->
            {no, [], []};
        {[], _, _} ->
            case Word of
                _ -> fold_results(expand_helper(FT, all, Word, ":"))
            end;
        {_,_,_} ->
            case Word of
                [] -> {no, [], []};
                _ ->
            TypeOfExpand = expand_function(Bef1),
            CompleteChar
                = case TypeOfExpand of
                      help -> ", ";
                      help_type -> ", ";
                      _ -> ":"
                  end,
            fold_results(expand_helper(FT, TypeOfExpand, Word, CompleteChar))
        end
    end.
expand_keyword(Word) ->
    Keywords = ["begin", "case", "of", "receive", "after", "maybe", "try", "catch", "throw", "if", "fun", "when", "end"],
    {Res, Expansion, Matches} = match(Word, Keywords, ""),
    case Matches of
        [] -> {no, [], []};
        _ -> {Res,Expansion,[#{title=>"keywords", elems=>Matches, options=>[highlight_all]}]}
    end.
expand_helper(_, help, Word, CompleteChar) ->
    [expand_module_name(Word, CompleteChar)];
expand_helper(_, help_type, Word, CompleteChar) ->
    [expand_module_name(Word, CompleteChar)];
expand_helper(FT, all, Word, CompleteChar) ->
    [expand_module_name(Word, CompleteChar), expand_bifs(Word), expand_shell_default(Word),
     expand_user_defined_functions(FT, Word), expand_keyword(Word)];
expand_helper(FT, _, Word, CompleteChar) ->
    [expand_module_name(Word, CompleteChar), expand_bifs(Word),
     expand_user_defined_functions(FT, Word), expand_keyword(Word)].
expand_function("("++Str) ->
    case edlin:over_word(Str, [], 0) of
        {_,"h",_} ->
            help;
        {_,"ht",_} ->
            help_type;
        _ ->
            module
    end;
expand_function(_) ->
    module.

expand_bifs(Prefix) ->
    Alts = [EA || {E,A}=EA <- get_exports(erlang), erl_internal:bif(E,A)],
    CC = "(",
    case match(Prefix, Alts, CC) of
        {_Res,_Expansion,[]}=M -> M;
        {Res,Expansion, Matches} -> {Res,Expansion,[#{title=>"bifs", elems=>Matches, options=>[highlight_all]}]}
    end.

expand_shell_default(Prefix) ->
    Alts = get_exports(shell_default) ++ shell:local_func(),
    CC = "(",
    case match(Prefix, Alts, CC) of
        {_Res,_Expansion,[]}=M -> M;
        {Res,Expansion, Matches} -> {Res,Expansion,[#{title=>"commands",elems=>Matches, options=>[highlight_all]}]}
    end.

expand_user_defined_functions(FT, Prefix) ->
    Alts = [{Name, Arity}||{{function, {_, Name, Arity}}, _} <- FT],
    CC = "(",
    case match(Prefix, Alts, CC) of
        {_Res,_Expansion,[]}=M -> M;
        {Res,Expansion, Matches} -> {Res,Expansion,[#{title=>"user_defined", elems=>Matches, options=>[highlight_all]}]}
    end.

expand_module_name("",_) ->
    {no, [], []};
expand_module_name(Prefix,CC) ->
    Alts = [{list_to_atom(M),""} || {M,_,_} <- code:all_available()],
    case match(Prefix, Alts, CC) of
        {_Res,_Expansion,[]}=M -> M;
        {Res,Expansion, Matches} -> {Res,Expansion,[#{title=>"modules", elems=>Matches, options=>[highlight_all]}]}
    end.

get_arities("shell_default"=ModStr, FuncStr, FT) ->
    case [A || {{function, {_, Fun, A}}, _} <- FT, Fun =:= list_to_atom(FuncStr)] of
        [] -> get_arities(ModStr, FuncStr);
        Arities -> Arities
    end;
get_arities(ModStr, FuncStr, _) ->
    get_arities(ModStr, FuncStr).
get_arities(ModStr, FuncStr) ->
    case to_atom(ModStr) of
        {ok, Mod} ->
            Exports = get_exports(Mod),
            lists:sort(
              [A || {H, A} <- Exports, string:equal(FuncStr, flat_write(H))]);
        error ->
            {no, [], []}
    end.

get_exports(Mod) ->
    case erlang:module_loaded(Mod) of
        true ->
            Mod:module_info(exports);
        false ->
            case beam_lib:chunks(code:which(Mod), [exports]) of
                {ok, {Mod, [{exports,E}]}} ->
                    E;
                _ ->
                    []
            end
    end.

expand_function_name(ModStr, FuncPrefix, CompleteChar, FT) ->
    case to_atom(ModStr) of
        {ok, Mod} ->
            Extra = case Mod of
                        shell_default -> [{Name, Arity}||{{function, {_, Name, Arity}}, _} <- FT];
                        _ -> []
                    end,
            Exports = get_exports(Mod) ++ Extra,
            {Res, Expansion, Matches}=Result = match(FuncPrefix, Exports, CompleteChar),
            case Matches of
                [] -> Result;
                _ -> {Res, Expansion, [#{title=>"functions", elems=>Matches, options=>[highlight_all]}]}
            end;
        error ->
            {no, [], []}
    end.

get_module_types(Mod) ->
    case code:get_doc(Mod, #{sources => [debug_info]}) of
        {ok, #docs_v1{ docs = Docs } } ->
            [{T, A} || {{type, T, A},_Anno,_Sig,_Doc,_Meta} <- Docs];
        _ -> {no, [], []}
    end.

expand_type_name(ModStr, TypePrefix, CompleteChar) ->
    case to_atom(ModStr) of
        {ok, Mod} ->
            case get_module_types(Mod) of
                {no, [], []} ->
                    {no, [], []};
                Types ->
                    {Res, Expansion, Matches}=Result = match(TypePrefix, Types, CompleteChar),
                    case Matches of
                        [] -> Result;
                        _ -> {Res, Expansion, [#{title=>"types", elems=>Matches, options=>[highlight_all]}]}
                    end
            end;
        error ->
            {no, [], []}
    end.

to_atom(Str) ->
    case erl_scan:string(Str) of
        {ok, [{atom,_,A}], _} ->
            {ok, A};
        _ ->
            error
    end.

to_list(Atom) ->
    io_lib:write_atom(Atom).

strip_quotes(Atom) ->
    [C || C<-atom_to_list(Atom), C/=$'].

match_preprocess_alt({_,_}=Alt) -> Alt;
match_preprocess_alt(X) -> {X, ""}.

match(Prefix, Alts, Extra0) ->
    Alts2 = [match_preprocess_alt(A) || A <- Alts],
    Len = string:length(Prefix),
    Matches = lists:sort(
                [{S, A} || {H, A} <- Alts2,
                           lists:prefix(Prefix, S=flat_write(H))]),
    Matches2 = lists:usort(
                 case Extra0 of
                     [] -> [{S,[]} || {S,_} <- Matches];
                     _  -> [{S,[{ending, Extra0}]} || {S,_} <- Matches]
                 end),
    case longest_common_head([N || {N, _} <- Matches]) of
        {partial, []} ->
            {no, [], Matches2};
        {partial, Str} ->
            case string:slice(Str, Len) of
                [] ->
                    {yes, [], Matches2};
                Remain ->
                    {yes, Remain, Matches2}
            end;
        {complete, Str} ->
            Extra = case {Extra0,Matches} of
                        {"/",[{Str,N}]} when is_integer(N) -> "/"++integer_to_list(N);
                        {"(",[{Str,0}]} -> "()";
                        {_,_} -> Extra0
                    end,
            {yes, string:slice(Str, Len) ++ Extra, ordsets:from_list(Matches2)};
        no ->
            {no, [], []}
    end.

flat_write(T) when is_atom(T) ->
    lists:flatten(io_lib:fwrite("~tw",[T]));
flat_write(S) ->
    S.

special_sort1([C|A], B) when C == ${ ; C == $. ; C == $# ->
    special_sort1(A, B);
special_sort1(A, [C|B]) when C == ${ ; C == $. ; C == $# ->
    special_sort1(A,B);
special_sort1(A,B) ->
    string:lowercase(A) =< string:lowercase(B).
special_sort(#{title:=A}, #{title:=B}) ->
    special_sort1(A,B);
special_sort(#{}, {}) ->
    error;
special_sort({}, #{}) ->
    error;
special_sort({A,_},{B,_}) ->
    special_sort1(A,B);
special_sort(A,B) ->
    special_sort1(A,B).

to_legacy_format([]) -> [];
to_legacy_format([#{title:=Title}|Rest]) when Title =:= "commands"; Title =:= "bifs" ->
    to_legacy_format(Rest);
to_legacy_format([#{title:=Title, elems:=Elems}|Rest])
  when Title =:= "modules"; Title =:= "functions"; Title =:= "bindings";
       Title =:= "user_defined", Title =:= "records"; Title =:= "fields";
       Title =:= "types"; Title =:= "atoms"; Title =:= "matches";
       Title =:= "keywords"; Title =:= "typespecs" ->
    Elems1 = to_legacy_format(Elems),
    Elems1 ++ to_legacy_format(Rest);
to_legacy_format([#{title:=Title, elems:=_Elems}|Rest]) ->
    [Title] ++ to_legacy_format(Rest);
to_legacy_format([{Val, _}|Rest]) ->
    [{Val, ""}] ++ to_legacy_format(Rest).

format_matches([], _LineWidth) -> [];
format_matches([#{}|_]=FF, LineWidth) ->
    Groups = maps:groups_from_list(
                fun(#{title:=Title, elems:=T, options:=Opts}) ->
                    Separator = proplists:get_value(separator, Opts, "\n"),
                    case lists:last(string:split(Title++Separator, "\n", all)) of
                        [] -> format_section_matches(T, LineWidth);
  we have chars that compete with the results on the first line
                            Len = length(Chars),
                            format_section_matches(T, LineWidth, Len)
                    end
                end,
               fun(F) ->
                       format_title(F, LineWidth)
               end, FF),
    S = lists:flatten(
      [lists:join("", F)++Matches ||
          {Matches, F}<-lists:sort(fun({_,A},{_,B}) -> A =< B end, maps:to_list(Groups))]),
    lists:flatten(string:trim(S, trailing)++"\n");
format_matches(Elems, LineWidth) ->
    S = format_section_matches1(Elems, LineWidth, 0),
    lists:flatten(string:trim(S, trailing)++"\n").
format_title(#{title:=MFA, options:=Options}, _LineWidth) ->
    case proplists:get_value(hide, Options) of
        title -> "";
        _ ->
            Separator = proplists:get_value(separator, Options, "\n"),
            HighlightAll = proplists:is_defined(highlight_all, Options),
            case HighlightAll of
                true -> "\033[;1;4m"++MFA++"\033[0m"++Separator;
                _ ->
                    HighlightParam = proplists:get_value(highlight_param, Options, false),
                    
                    MFA2 = case HighlightParam of
                        false -> MFA;
                        _ -> 
                            PreviousParams = HighlightParam -1,
                            TuplePattern = "(?:\\{[^\\}]+\\})",
                            AtomVarPattern = "(?:\\w+)",
                            TypePattern="(?:(?:"++AtomVarPattern++":)?(?:"++AtomVarPattern++"\\(\\))(?:\\s[><=]+\\s\\d+)?)",
                            SimplePatterns = "(?:"++TuplePattern++"|"++TypePattern++"|"++AtomVarPattern++")",
                            UnionPattern = "(?:"++SimplePatterns++"(?:\\s\\|\\s"++SimplePatterns++")*)",
                            FunPattern="(?:fun\\(\\(" ++ UnionPattern ++ "\\)\\s*->\\s*" ++ UnionPattern ++ "\\))",
                            ArgPattern3 = "(?:"++FunPattern++"|"++UnionPattern++")",
                            PrevArgs="(?:"++ArgPattern3++",\\s){"++integer_to_list(PreviousParams) ++ "}",
  \\1
                            
\\2
                            NextArgs="(?:,\\s"++ArgPattern3++")*",
  \\3

                            re:replace(MFA, 
                        FunctionHeadStart ++ HighlightArg ++ FunctionHeadEnd,
                                "\\1\033[;1;4m\\2\033[0m\\3",
                            [global, {return, list}, unicode])
                    end,
                    Highlight = proplists:get_value(highlight, Options, false),
                    case Highlight of
                        false -> MFA2;
                        _ -> re:replace(MFA2, "(\\Q"++Highlight++"\\E)", "\033[;1;4m\\1\033[0m", [global, {return, list}, unicode])
                    end ++ Separator
            end
    end;
format_title(_Elems, _LineWidth) ->
    "".

format_section_matches(LS, LineWidth) -> format_section_matches(LS, LineWidth, 0).
format_section_matches([], _, _) -> "\n";
format_section_matches([#{}|_]=FF, LineWidth, Acc) ->
    Groups = maps:groups_from_list(
        fun(#{title:=Title, elems:=T, options:=Opts}) ->
                Separator = proplists:get_value(separator, Opts, "\n"),
                case lists:last(string:split(Title++Separator, "\n", trailing)) of
                    [] -> format_section_matches(T, LineWidth);
  we have chars that compete with the results on the first line
                        Len = string:length(Chars),
                        format_section_matches(T, LineWidth, Len+Acc)
                end
        end,
        fun(F) ->
                format_title(F, LineWidth)
        end, FF),
    lists:flatten(
        [lists:join("", F)++Matches ||
        {Matches, F}<-lists:sort(fun({_,A},{_,B}) -> A =< B end, maps:to_list(Groups))]);
format_section_matches(Elems, LineWidth, Acc) ->
    format_section_matches1(Elems, LineWidth, Acc).

format_section_matches1([], _, _) -> [];
format_section_matches1(LS, LineWidth, Len) ->
    L = lists:usort(fun special_sort/2, ordsets:to_list(LS)),
    Opt = case Len == 0 of
        true -> [];
        false -> [{title, Len}]
    end,    
    S1 = format_col(Opt ++ L, field_width(Opt ++ L, LineWidth), Len, [], LineWidth, Opt),
    S2 = lists:map(
           fun(Line) ->
                case string:length(Line) of
                    Len1 when Len1 > LineWidth ->
                        string:sub_string(Line, 1, LineWidth-4) ++ "...\n";
                    _ -> Line
                end
           end,S1),
    lists:flatten(string:trim(S2, trailing)++"\n").

format_col(X, Width, Len, Acc, LL, Opt) when Width + Len > LL ->
    format_col(X, Width, 0, ["\n" | Acc], LL, Opt);
format_col([{title,TitleLen}|T], Width, Len, Acc0, LL, Opt) ->
    Acc = [io_lib:format("~-*ts", [Width-TitleLen, ""])|Acc0],
    format_col(T, Width, Len+Width, Acc, LL, Opt);
format_col([A|T], Width, Len, Acc0, LL, _Opt) ->
    {H0, R} = format_val(A),
    Hmax = LL - string:length(R),
    {H, _} =
        case string:length(H0) > Hmax of
            true -> {io_lib:format("~-*ts", [Hmax - 3, H0]) ++ "...", true};
            false -> {H0, false}
        end,
    Acc = [io_lib:format("~-*ts", [Width, H ++ R]) | Acc0],
    format_col(T, Width, Len+Width, Acc, LL, []);
format_col([], _, _, Acc, _LL, _Opt) ->
    lists:reverse(Acc).

format_val({H, L}) when is_list(L) ->
    {H, proplists:get_value(ending, L, "")};
format_val({H, I}) when is_integer(I) ->
    {H, "/"++integer_to_list(I)};
format_val({H, _}) ->
    {H, ""};
format_val(H) ->
    {H, ""}.

field_width(L, LL) -> field_width(L, 0, LL).
field_width([{title, Len}|T], W, LL) ->
    case Len of
        L when L > W -> field_width(T, L, LL);
        _ -> field_width(T, W, LL)
    end;
field_width([H|T], W, LL) ->
    {H1, Ending} = format_val(H),
    case string:length(H1++Ending) of
        L when L > W -> field_width(T, L, LL);
        _ -> field_width(T, W, LL)
    end;
field_width([], W, LL) when W < LL ->
    W + 4;
field_width([], _, LL) ->
    LL.

number_matches([#{ elems := Matches }|T]) ->
    number_matches(Matches) + number_matches(T);
number_matches([_|T]) ->
    1 + number_matches(T);
number_matches([]) ->
    0.

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

longest_common_head([[]|_], L) ->
    {partial, lists: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, lists:reverse([H|L])}
            end;
        false ->
            {partial, lists: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.

  over_white(Chars , InitialStack , InitialCount ) - >
     { RemainingChars ,  , 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) when is_list(Cs) ->
    {Cs,Stack,N}.
