src/formatters/default_formatter.erl

%% @doc Rebar3 Pretty Printing of abstract Erlang syntax trees, following our own preferred style.
%% @reference Check
%%  <a target="_blank" href="https://github.com/AdRoll/rebar3_format#configuration">README.md</a>
%%  for more information on the available options.
-module(default_formatter).

-behaviour(rebar3_formatter).
-behaviour(rebar3_ast_formatter).

%% Allow erl_syntax:syntaxTree/0 type spec
-elvis([{elvis_style, atom_naming_convention, #{regex => "^([a-zA-Z][a-z0-9]*_?)*$"}}]).

%% 'maybe' and 'else', among others
-format #{unquote_atoms => false}.

%% erl_syntax functions that only appear in OTP25
-dialyzer([no_missing_calls]).

-export([init/2, format_file/3, format/3]).

-define(PADDING, 2).
-define(PAPER, 100).
-define(RIBBON, 90).
-define(BREAK_INDENT, 4).

-type clause_t() ::
    case_expr |
    simple_fun_expr |
    fun_expr |
    if_expr |
    maybe_expr |
    receive_expr |
    try_expr |
    {function, prettypr:document()} |
    spec.
-type inlining() :: all | none | {when_over, pos_integer()} | {when_under, pos_integer()}.

-record(ctxt,
        {prec = 0 :: integer(),
         sub_indent = ?BREAK_INDENT :: non_neg_integer(),
         break_indent = ?BREAK_INDENT :: non_neg_integer(),
         clause = undefined :: clause_t() | undefined,
         paper = ?PAPER :: integer(),
         ribbon = ?RIBBON :: integer(),
         inline_items = {when_over, 25} :: inlining(),
         inline_fields = {when_under, 3} :: inlining(),
         inline_attributes = all :: inlining(),
         within_disjunction = false :: boolean(),
         force_indentation = false :: boolean(),
         force_arity_qualifiers = false :: boolean(),
         inline_simple_funs = true :: boolean(),
         inline_clause_bodies = false :: boolean(),
         inline_qualified_function_composition = false :: boolean(),
         inline_expressions = false :: boolean(),
         spaces_around_arguments = false :: boolean(),
         spaces_around_fields = false :: boolean(),
         sort_arity_qualifiers = false :: boolean(),
         sort_arity_qualifiers_match = false :: boolean(),
         unquote_atoms = true :: boolean(),
         truncate_strings = false :: boolean(),
         parenthesize_infix_operations = false :: boolean(),
         empty_lines = [] :: [pos_integer()],
         encoding = epp:default_encoding() :: epp:source_encoding()}).

set_prec(Ctxt, Prec) ->
    Ctxt#ctxt{prec = Prec}.    % used internally

reset_prec(Ctxt) ->
    set_prec(Ctxt, 0).    % used internally

%% =====================================================================
%% @doc Pretty-prints/formats an abstract Erlang syntax tree as text in the style of NextRoll.
%%
%% @see erl_syntax
%% @see format/1
%% @see layout/2
-spec format(erl_syntax:syntaxTree(), [pos_integer()], rebar3_formatter:opts()) ->
                string().
format(Node, EmptyLines, Options) ->
    W = maps:get(paper, Options, ?PAPER),
    L = maps:get(ribbon, Options, ?RIBBON),
    E = maps:get(encoding, Options, utf8),
    FinalEmptyLines =
        case maps:get(preserve_empty_lines, Options, true) of
            true ->
                EmptyLines;
            false ->
                []
        end,
    PreFormatted = prettypr:format(layout(Node, FinalEmptyLines, Options), W, L),
    Formatted = remove_tabs(unicode:characters_to_binary(PreFormatted, E)),
    remove_trailing_spaces(Formatted).

%% @doc Initialize the formatter and generate a state that will be passed in when
%%      calling other callbacks.
-spec init(rebar3_formatter:opts(), undefined | rebar_state:t()) -> nostate.
init(_, _) ->
    nostate.

%% @doc Format a file.
%%      Apply formatting rules to a file containing erlang code.
%%      Use <code>Opts</code> to configure the formatter.
-spec format_file(file:filename_all(), nostate, rebar3_formatter:opts()) ->
                     rebar3_formatter:result().
format_file(File, nostate, Opts) ->
    rebar3_ast_formatter:format(File, ?MODULE, Opts).

remove_tabs(Formatted) ->
    case re:replace(Formatted, <<"(\n *)\t">>, <<"\\1        ">>, [global, {return, binary}])
    of
        Formatted ->
            Formatted;
        Replaced ->
            remove_tabs(Replaced)
    end.

remove_trailing_spaces(Formatted) ->
    re:replace(Formatted, <<" +\n">>, <<"\n">>, [global, {return, list}]).

%% =====================================================================
%% @doc Creates an abstract document layout for a syntax tree. The
%% result represents a set of possible layouts (cf. module `prettypr').
%% For information on the options, see {@link format/2}; note, however,
%% that the `paper' and `ribbon' options are ignored by this function.
%%
%% This function provides a low-level interface to the pretty printer,
%% returning a flexible representation of possible layouts, independent
%% of the paper width eventually to be used for formatting. This can be
%% included as part of another document and/or further processed
%% directly by the functions in the `prettypr' module (see `format/2'
%% for details).
%%
%% @see prettypr
%% @see format/2
-spec layout(erl_syntax:syntaxTree(), [pos_integer()], rebar3_formatter:opts()) ->
                prettypr:document().
layout(Node, EmptyLines, Options) ->
    BreakIndent = maps:get(break_indent, Options, ?BREAK_INDENT),
    lay(Node,
        #ctxt{paper = maps:get(paper, Options, ?PAPER),
              ribbon = maps:get(ribbon, Options, ?RIBBON),
              break_indent = BreakIndent,
              sub_indent = maps:get(sub_indent, Options, BreakIndent),
              inline_simple_funs = maps:get(inline_simple_funs, Options, true),
              inline_clause_bodies = maps:get(inline_clause_bodies, Options, false),
              inline_qualified_function_composition =
                  maps:get(inline_qualified_function_composition, Options, false),
              inline_expressions = maps:get(inline_expressions, Options, false),
              inline_items = maps:get(inline_items, Options, {when_over, 25}),
              inline_fields = maps:get(inline_fields, Options, {when_under, 3}),
              inline_attributes = maps:get(inline_attributes, Options, all),
              parenthesize_infix_operations =
                  maps:get(parenthesize_infix_operations, Options, false),
              unquote_atoms = maps:get(unquote_atoms, Options, true),
              truncate_strings = maps:get(truncate_strings, Options, false),
              spaces_around_arguments = maps:get(spaces_around_arguments, Options, false),
              spaces_around_fields = maps:get(spaces_around_fields, Options, false),
              sort_arity_qualifiers = maps:get(sort_arity_qualifiers, Options, false),
              empty_lines = EmptyLines,
              encoding = maps:get(encoding, Options, epp:default_encoding())}).

lay(Node, Ctxt) ->
    case erl_syntax:has_comments(Node) of
        true ->
            D1 = lay_no_comments(Node, Ctxt),
            D2 = lay_postcomments(erl_syntax:get_postcomments(Node), D1),
            lay_precomments(erl_syntax:get_precomments(Node), D2);
        false ->
            lay_no_comments(Node, Ctxt)
    end.

%% For pre-comments, all padding is ignored.
lay_precomments([], D) ->
    D;
lay_precomments(Cs, D) ->
    prettypr:above(
        prettypr:floating(
            prettypr:break(stack_comments(Cs, false)), -1, -1),
        D).

%% For postcomments, individual padding is added.
lay_postcomments([], D) ->
    D;
lay_postcomments(Cs, D) ->
    prettypr:beside(D,
                    prettypr:floating(
                        prettypr:break(stack_comments(Cs, true)), 1, 0)).

%% Format (including padding, if `Pad' is `true', otherwise not)
%% and stack the listed comments above each other.
stack_comments([C | Cs], Pad) ->
    D = stack_comment_lines(erl_syntax:comment_text(C)),
    D1 = case Pad of
             true ->
                 P = case erl_syntax:comment_padding(C) of
                         none ->
                             ?PADDING;
                         P1 ->
                             P1
                     end,
                 prettypr:beside(
                     prettypr:text(spaces(P)), D);
             false ->
                 D
         end,
    case Cs of
        [] ->
            D1; % done
        _ ->
            prettypr:above(D1, stack_comments(Cs, Pad))
    end.

%% Stack lines of text above each other and prefix each string in
%% the list with a single `%' character.
stack_comment_lines([S | Ss]) ->
    D = prettypr:text(add_comment_prefix(S)),
    case Ss of
        [] ->
            D;
        _ ->
            prettypr:above(D, stack_comment_lines(Ss))
    end;
stack_comment_lines([]) ->
    prettypr:empty().

add_comment_prefix(S) ->
    [$% | S].

%% This part ignores annotations and comments:
lay_no_comments(Node, Ctxt) ->
    case erl_syntax:type(Node) of
        %% We list literals and other common cases first.
        variable ->
            prettypr:text(
                erl_syntax:variable_literal(Node));
        atom ->
            prettypr:text(tidy_atom(Node, Ctxt));
        integer ->
            prettypr:text(tidy_integer(Node));
        float ->
            prettypr:text(tidy_float(Node));
        char ->
            prettypr:text(tidy_char(Node, Ctxt#ctxt.encoding));
        string ->
            lay_string(Node, Ctxt);
        nil ->
            prettypr:text("[]");
        tuple ->
            case maybe_convert_to_qualifier(Node, Ctxt) of
                Node -> % it didn't change
                    Es = lay_items(erl_syntax:tuple_elements(Node), reset_prec(Ctxt), fun lay/2),
                    prettypr:beside(lay_text_float("{"), prettypr:beside(Es, lay_text_float("}")));
                NewNode ->
                    lay_no_comments(NewNode, Ctxt)
            end;
        list ->
            Ctxt1 = reset_prec(Ctxt),
            Node1 = erl_syntax:compact_list(Node),
            D1 = lay_items(erl_syntax:list_prefix(Node1), Ctxt1, fun lay/2),
            D = case erl_syntax:list_suffix(Node1) of
                    none ->
                        prettypr:beside(D1, lay_text_float("]"));
                    S ->
                        prettypr:follow(D1,
                                        prettypr:beside(lay_text_float("| "),
                                                        prettypr:beside(lay(S, Ctxt1),
                                                                        lay_text_float("]"))))
                end,
            prettypr:beside(lay_text_float("["), D);
        operator ->
            lay_text_float(erl_syntax:operator_literal(Node));
        infix_expr ->
            {Prec, Docs} = infix_expr_docs(Node, Ctxt),
            D = prettypr:sep(adjust_infix_expr_pars(Docs, Ctxt)),
            maybe_parentheses(D, Prec, Ctxt);
        prefix_expr ->
            Operator = erl_syntax:prefix_expr_operator(Node),
            {{Prec, PrecR}, Name} =
                case erl_syntax:type(Operator) of
                    operator ->
                        N = erl_syntax:operator_name(Operator),
                        {erl_parse:preop_prec(N), N};
                    _ ->
                        {{0, 0}, any}
                end,
            D1 = lay(Operator, reset_prec(Ctxt)),
            D2 = lay(erl_syntax:prefix_expr_argument(Node), set_prec(Ctxt, PrecR)),
            D3 = case Name of
                     '+' ->
                         prettypr:beside(D1, D2);
                     '-' ->
                         prettypr:beside(D1, D2);
                     _ ->
                         prettypr:par([D1, D2], Ctxt#ctxt.break_indent)
                 end,
            maybe_parentheses(D3, Prec, Ctxt);
        application ->
            lay_application(erl_syntax:application_operator(Node),
                            erl_syntax:application_arguments(Node),
                            Ctxt#ctxt.spaces_around_arguments,
                            Ctxt);
        match_expr ->
            {PrecL, Prec, PrecR} = erl_parse:inop_prec('='),
            Pattern = erl_syntax:match_expr_pattern(Node),
            D1 = lay(Pattern, set_prec(Ctxt, PrecL)),
            D2 = lay(erl_syntax:match_expr_body(Node), set_prec(Ctxt, PrecR)),
            D3 = lay_match_expression(" =", Pattern, D1, D2, Ctxt),
            maybe_parentheses(D3, Prec, Ctxt);
        underscore ->
            prettypr:text("_");
        clause ->
            %% The style used for a clause depends on its context
            Ctxt1 = (reset_prec(Ctxt))#ctxt{clause = undefined},
            D1 = lay_items(erl_syntax:clause_patterns(Node), Ctxt1, fun lay/2),
            D2 = case erl_syntax:clause_guard(Node) of
                     none ->
                         none;
                     G ->
                         lay(G, Ctxt1)
                 end,
            D3 = lay_clause_expressions(erl_syntax:clause_body(Node), Ctxt1, fun lay/2),
            case Ctxt#ctxt.clause of
                fun_expr ->
                    make_fun_clause(D1, D2, D3, Ctxt);
                simple_fun_expr ->
                    make_simple_fun_clause(D1, D2, D3, Ctxt);
                {function, N} ->
                    make_fun_clause(N, D1, D2, D3, Ctxt);
                if_expr ->
                    make_if_clause(D2, D3, Ctxt);
                case_expr ->
                    make_case_clause(D1, D2, D3, Ctxt);
                maybe_expr ->
                    make_case_clause(D1, D2, D3, Ctxt);
                receive_expr ->
                    make_case_clause(D1, D2, D3, Ctxt);
                try_expr ->
                    make_case_clause(D1, D2, D3, Ctxt);
                undefined ->
                    %% If a clause is formatted out of context, we
                    %% use a "fun-expression" clause style.
                    make_fun_clause(D1, D2, D3, Ctxt)
            end;
        function ->
            %% Comments on the name itself will be repeated for each
            %% clause, but that seems to be the best way to handle it.
            Ctxt1 = reset_prec(Ctxt),
            D1 = lay(erl_syntax:function_name(Node), Ctxt1),
            D2 = lay_clauses(erl_syntax:function_clauses(Node), {function, D1}, Ctxt1),
            prettypr:beside(D2, lay_text_float("."));
        case_expr ->
            Ctxt1 = reset_prec(Ctxt),
            D1 = lay(erl_syntax:case_expr_argument(Node), Ctxt1),
            D2 = lay_clauses(erl_syntax:case_expr_clauses(Node), case_expr, Ctxt1),
            prettypr:sep([prettypr:par([prettypr:follow(
                                            prettypr:text("case"), D1, Ctxt1#ctxt.break_indent),
                                        prettypr:text("of")]),
                          prettypr:nest(Ctxt1#ctxt.break_indent, D2),
                          prettypr:text("end")]);
        if_expr ->
            Ctxt1 = reset_prec(Ctxt),
            D = lay_clauses(erl_syntax:if_expr_clauses(Node), if_expr, Ctxt1),
            prettypr:sep([prettypr:follow(
                              prettypr:text("if"), D, Ctxt1#ctxt.break_indent),
                          prettypr:text("end")]);
        fun_expr ->
            Ctxt1 = reset_prec(Ctxt),
            case erl_syntax:fun_expr_clauses(Node) of
                [Clause] -> % Just one clause
                    % We force inlining here, to prevent fun() -> x end to use 3 lines
                    % if inline_simple_funs is true. Otherwise treat them as the rest
                    % of the code
                    DClause = lay(Clause, Ctxt1#ctxt{clause = simple_fun_expr}),
                    prettypr:sep([prettypr:beside(
                                      prettypr:text("fun"), DClause),
                                  prettypr:text("end")]);
                Clauses ->
                    lay_fun_sep(lay_clauses(Clauses, fun_expr, Ctxt1), Ctxt1)
            end;
        named_fun_expr ->
            Ctxt1 = reset_prec(Ctxt),
            D1 = lay(erl_syntax:named_fun_expr_name(Node), Ctxt1),
            Clauses = lay_clauses(erl_syntax:named_fun_expr_clauses(Node), {function, D1}, Ctxt1),
            lay_fun_sep(Clauses, Ctxt1);
        module_qualifier ->
            {PrecL, _Prec, PrecR} = erl_parse:inop_prec(':'),
            D1 = lay(erl_syntax:module_qualifier_argument(Node), set_prec(Ctxt, PrecL)),
            D2 = lay(erl_syntax:module_qualifier_body(Node), set_prec(Ctxt, PrecR)),
            prettypr:beside(D1,
                            prettypr:beside(
                                prettypr:text(":"), D2));
        %%
        %% The rest is in alphabetical order (except map and types)
        %%
        arity_qualifier ->
            Ctxt1 = reset_prec(Ctxt),
            D1 = lay(erl_syntax:arity_qualifier_body(Node), Ctxt1),
            D2 = lay(erl_syntax:arity_qualifier_argument(Node), Ctxt1),
            prettypr:beside(D1,
                            prettypr:beside(
                                prettypr:text("/"), D2));
        attribute ->
            %% The attribute name and arguments are formatted similar to
            %% a function call, but prefixed with a "-" and followed by
            %% a period. If the arguments is `none', we only output the
            %% attribute name, without following parentheses.
            Ctxt1 = reset_prec(Ctxt),
            Tag = attribute_name(Node),

            %% NOTE: The preceding $- must be part of the name of the attribute.
            %%       That's because we want indentation to start counting from
            %%       that character on, and not from the first character on the
            %%       attribute name.
            N = erl_syntax:variable([$- | atom_to_list(Tag)]),
            D = case {Tag, erl_syntax:attribute_arguments(Node)} of
                    {Tag, [SpecTuple]} when Tag =:= spec; Tag =:= callback ->
                        [FuncName, FuncTypes] = erl_syntax:tuple_elements(SpecTuple),
                        Name = get_func_node(FuncName),
                        Types = concrete_dodging_macros(FuncTypes),
                        case Types of
                            [Type] ->
                                lay_simple_spec(prettypr:follow(lay(N, Ctxt1),
                                                                lay(Name, Ctxt1),
                                                                Ctxt1#ctxt.break_indent),
                                                Type,
                                                Ctxt1);
                            Types ->
                                D1 = lay_clauses(Types, spec, Ctxt1),
                                prettypr:beside(
                                    prettypr:follow(lay(N, Ctxt1),
                                                    lay(Name, Ctxt1),
                                                    Ctxt1#ctxt.break_indent),
                                    D1)
                        end;
                    {Tag, [TypeTuple]} when Tag =:= type; Tag =:= opaque ->
                        [Name, Type, Elements] = erl_syntax:tuple_elements(TypeTuple),
                        As = concrete_dodging_macros(Elements),
                        D1 = prettypr:follow(lay(N, Ctxt1), lay_application(Name, As, Ctxt1)),
                        D2 = lay(concrete_dodging_macros(Type), Ctxt1),
                        lay_double_colon(D1, D2, Ctxt1);
                    {Tag, [FuncNames]} when Tag =:= export_type; Tag =:= optional_callbacks ->
                        As0 = unfold_function_names(FuncNames),
                        Ctxt2 = Ctxt1#ctxt{sort_arity_qualifiers_match = true},
                        As = maybe_sort_arity_qualifiers(As0, Ctxt2),

                        %% We force inlining of list items and use inline_attributes to
                        %% format the list of functions
                        Ctxt3 =
                            Ctxt2#ctxt{force_indentation = true,
                                       inline_items = Ctxt1#ctxt.inline_attributes},
                        prettypr:beside(lay(N, Ctxt2),
                                        prettypr:beside(
                                            prettypr:text("("),
                                            prettypr:beside(lay(As, Ctxt3), lay_text_float(")"))));
                    {on_load, [FuncName]} ->
                        As = unfold_function_name(FuncName),
                        prettypr:beside(lay(N, Ctxt1),
                                        prettypr:beside(lay_text_float(" "), lay(As, Ctxt1)));
                    {format, [Opts]} -> % Always a single map
                        D1 = lay(N, Ctxt),
                        As = lay(Opts, Ctxt),
                        prettypr:beside(D1, prettypr:beside(lay_text_float(" "), As));
                    {export, Args} ->
                        %% We force inlining of list items and use inline_attributes to
                        %% format the lists within these attributes
                        Ctxt2 =
                            Ctxt1#ctxt{force_indentation = true,
                                       sort_arity_qualifiers_match = true,
                                       inline_items = Ctxt1#ctxt.inline_attributes},
                        lay_application(N, Args, Ctxt2);
                    {Tag, Args}
                        when Tag =:= dialyzer;
                             Tag =:= mixin;
                             Tag =:= ignore_xref;
                             Tag =:= compile ->
                        %% We need to convert tuples with 2 elements to arity qualifiers here
                        %% because the parser doesn't recognize them as such.
                        Ctxt2 = Ctxt1#ctxt{force_arity_qualifiers = true},
                        lay_application(N, Args, Ctxt2);
                    {_, none} ->
                        lay(N, Ctxt1);
                    {Tag, [Arg]} ->
                        case Tag /= module
                             andalso erl_syntax:type(Arg) == atom
                             andalso erl_syntax:concrete(Arg) == ignore
                        of
                            true ->
                                % handle stuff like -elvis ignore.
                                D1 = lay(N, Ctxt),
                                As = lay(Arg, Ctxt),
                                prettypr:beside(D1, prettypr:beside(lay_text_float(" "), As));
                            false ->
                                lay_application(N, [Arg], Ctxt1)
                        end;
                    {_, Args} ->
                        lay_application(N, Args, Ctxt1)
                end,
            prettypr:beside(D, lay_text_float("."));
        binary ->
            Ctxt1 = reset_prec(Ctxt),
            Es = lay_items(erl_syntax:binary_fields(Node), Ctxt1, fun lay/2),
            prettypr:beside(lay_text_float("<<"), prettypr:beside(Es, lay_text_float(">>")));
        binary_field ->
            Ctxt1 = set_prec(Ctxt, erl_parse:max_prec()),
            D1 = lay(erl_syntax:binary_field_body(Node), Ctxt1),
            D2 = case erl_syntax:binary_field_types(Node) of
                     [] ->
                         prettypr:empty();
                     Ts ->
                         prettypr:beside(lay_text_float("/"), lay_bit_types(Ts, Ctxt1))
                 end,
            prettypr:beside(D1, D2);
        block_expr ->
            Ctxt1 = reset_prec(Ctxt),
            Es = lay_clause_expressions(erl_syntax:block_expr_body(Node), Ctxt1, fun lay/2),
            prettypr:sep([prettypr:text("begin"),
                          prettypr:nest(Ctxt1#ctxt.break_indent, Es),
                          prettypr:text("end")]);
        catch_expr ->
            {_, PrecR} = erl_parse:preop_prec('catch'),
            D = lay(erl_syntax:catch_expr_body(Node), set_prec(Ctxt, PrecR)),
            D1 = prettypr:follow(
                     prettypr:text("catch"), D, Ctxt#ctxt.break_indent),
            maybe_parentheses(D1, 0, Ctxt);
        class_qualifier ->
            Ctxt1 = set_prec(Ctxt, erl_parse:max_prec()),
            D1 = lay(erl_syntax:class_qualifier_argument(Node), Ctxt1),
            D2 = lay(erl_syntax:class_qualifier_body(Node), Ctxt1),
            Stacktrace = erl_syntax:class_qualifier_stacktrace(Node),
            case erl_syntax:variable_name(Stacktrace) of
                '_' ->
                    prettypr:beside(D1,
                                    prettypr:beside(
                                        prettypr:text(":"), D2));
                _ ->
                    D3 = lay(Stacktrace, Ctxt1),
                    prettypr:beside(D1,
                                    prettypr:beside(
                                        prettypr:beside(
                                            prettypr:text(":"), D2),
                                        prettypr:beside(
                                            prettypr:text(":"), D3)))
            end;
        comment ->
            D = stack_comment_lines(erl_syntax:comment_text(Node)),

            %% Default padding for standalone comments is empty.
            case erl_syntax:comment_padding(Node) of
                none ->
                    prettypr:floating(
                        prettypr:break(D));
                P ->
                    prettypr:floating(
                        prettypr:break(
                            prettypr:beside(
                                prettypr:text(spaces(P)), D)))
            end;
        conjunction ->
            ExprDocs =
                seq(erl_syntax:conjunction_body(Node),
                    lay_text_float(","),
                    reset_prec(Ctxt),
                    fun lay/2),
            case Ctxt#ctxt.within_disjunction of
                true ->
                    %% We're in a clause guard, since all clause guards are lists
                    %% of (at least one) disjunction items.
                    prettypr:par(ExprDocs);
                false ->
                    %% We're in a "fake" conjunction, i.e. the 'when' piece of a
                    %% constrained_function_type and we want those types laid out
                    %% one in each row.
                    vertical(ExprDocs)
            end;
        disjunction ->
            %% For clarity, we don't paragraph-format disjunctions;
            %% only conjunctions within disjunctions (see above).
            prettypr:sep(seq(erl_syntax:disjunction_body(Node),
                             lay_text_float(";"),
                             reset_prec(Ctxt#ctxt{within_disjunction = true}),
                             fun lay/2));
        error_marker ->
            E = erl_syntax:error_marker_info(Node),
            prettypr:beside(
                prettypr:text("** "),
                prettypr:beside(lay_error_info(E, reset_prec(Ctxt)), prettypr:text(" **")));
        eof_marker ->
            prettypr:empty();
        form_list ->
            Es = seq(erl_syntax:form_list_elements(Node), none, reset_prec(Ctxt), fun lay/2),
            AddEmptyLines = empty_lines_to_add(erl_syntax:form_list_elements(Node), Ctxt),
            vertical_sep(lists:zip(Es, AddEmptyLines));
        generator ->
            Ctxt1 = reset_prec(Ctxt),
            D1 = lay(erl_syntax:generator_pattern(Node), Ctxt1),
            D2 = lay(erl_syntax:generator_body(Node), Ctxt1),
            prettypr:par([D1,
                          prettypr:beside(
                              prettypr:text("<- "), D2)],
                         Ctxt1#ctxt.break_indent);
        binary_generator ->
            Ctxt1 = reset_prec(Ctxt),
            D1 = lay(erl_syntax:binary_generator_pattern(Node), Ctxt1),
            D2 = lay(erl_syntax:binary_generator_body(Node), Ctxt1),
            prettypr:par([D1,
                          prettypr:beside(
                              prettypr:text("<= "), D2)],
                         Ctxt1#ctxt.break_indent);
        implicit_fun ->
            D = lay(erl_syntax:implicit_fun_name(Node), reset_prec(Ctxt)),
            prettypr:beside(lay_text_float("fun "), D);
        list_comp ->
            Ctxt1 = reset_prec(Ctxt),
            D1 = lay(erl_syntax:list_comp_template(Node), Ctxt1),
            D2 = lay_items(erl_syntax:list_comp_body(Node), Ctxt1, fun lay/2),
            D3 = prettypr:beside(lay_text_float("|| "), prettypr:beside(D2, lay_text_float("]"))),
            prettypr:beside(lay_text_float("["), prettypr:par([D1, D3]));
        binary_comp ->
            Ctxt1 = reset_prec(Ctxt),
            D1 = lay(erl_syntax:binary_comp_template(Node), Ctxt1),
            D2 = lay_items(erl_syntax:binary_comp_body(Node), Ctxt1, fun lay/2),
            D3 = prettypr:beside(lay_text_float("|| "), prettypr:beside(D2, lay_text_float(" >>"))),
            prettypr:beside(lay_text_float("<< "), prettypr:par([D1, D3]));
        macro ->
            %% This is formatted similar to a normal function call or a variable
            N = macro_name(Node, variable),
            case erl_syntax:macro_arguments(Node) of
                none ->
                    lay(N, Ctxt);
                Args ->
                    lay_application(N, Args, Ctxt)
            end;
        parentheses ->
            D = lay(erl_syntax:parentheses_body(Node), reset_prec(Ctxt)),
            lay_parentheses(D);
        maybe_expr ->
            Ctxt1 = reset_prec(Ctxt),
            D0 = lay_clause_expressions(erl_syntax:maybe_expr_body(Node), Ctxt1, fun lay/2),
            D1 = vertical([prettypr:text("maybe"), prettypr:nest(Ctxt1#ctxt.break_indent, D0)]),
            case erl_syntax:maybe_expr_else(Node) of
                none ->
                    prettypr:par([D1, prettypr:text("end")]);
                ElseNode ->
                    ElseCs = erl_syntax:else_expr_clauses(ElseNode),
                    D3 = lay_clauses(ElseCs, maybe_expr, Ctxt1),
                    prettypr:sep([prettypr:par([D1, prettypr:text("else")]),
                                  prettypr:nest(Ctxt1#ctxt.break_indent, D3),
                                  prettypr:text("end")])
            end;
        maybe_match_expr ->
            {PrecL, Prec, PrecR} = erl_parse:inop_prec('='),
            Pattern = erl_syntax:maybe_match_expr_pattern(Node),
            D1 = lay(Pattern, set_prec(Ctxt, PrecL)),
            D2 = lay(erl_syntax:maybe_match_expr_body(Node), set_prec(Ctxt, PrecR)),
            D3 = lay_match_expression(" ?=", Pattern, D1, D2, Ctxt),
            maybe_parentheses(D3, Prec, Ctxt);
        receive_expr ->
            Ctxt1 = reset_prec(Ctxt),
            case {erl_syntax:receive_expr_clauses(Node), erl_syntax:receive_expr_timeout(Node)} of
                {Clauses, none} ->
                    D1 = lay_clauses(Clauses, receive_expr, Ctxt1),
                    prettypr:sep([prettypr:text("receive"),
                                  prettypr:nest(Ctxt1#ctxt.break_indent, D1),
                                  prettypr:text("end")]);
                {[], T} ->
                    D1 = prettypr:beside(lay_text_float("receive after "), lay(T, Ctxt1)),
                    D2 = lay_clause_expressions(erl_syntax:receive_expr_action(Node),
                                                Ctxt1,
                                                fun lay/2),
                    D3 = append_clause_body(D2, D1, Ctxt1),
                    prettypr:sep([D3, prettypr:text("end")]);
                {Clauses, T} ->
                    D1 = lay_clauses(Clauses, receive_expr, Ctxt1),
                    D2 = prettypr:beside(lay_text_float("after "), lay(T, Ctxt1)),
                    D3 = lay_clause_expressions(erl_syntax:receive_expr_action(Node),
                                                Ctxt1,
                                                fun lay/2),
                    D4 = append_clause_body(D3, D2, Ctxt1),
                    prettypr:sep([prettypr:text("receive"),
                                  prettypr:nest(Ctxt1#ctxt.break_indent, D1),
                                  D4,
                                  prettypr:text("end")])
            end;
        record_access ->
            {PrecL, Prec, PrecR} = erl_parse:inop_prec('#'),
            Argument = erl_syntax:record_access_argument(Node),
            D1 = case erl_syntax:type(Argument) of
                     record_access ->
                         lay(Argument, set_prec(Ctxt, Prec)); % A#b.c#d.e#f.g is valid Erlang
                     _ ->
                         lay(Argument, set_prec(Ctxt, PrecL))
                 end,
            D2 = prettypr:beside(lay_text_float("."),
                                 lay(erl_syntax:record_access_field(Node), set_prec(Ctxt, PrecR))),
            T = erl_syntax:record_access_type(Node),
            D3 = prettypr:beside(
                     prettypr:beside(lay_text_float("#"), lay(T, reset_prec(Ctxt))), D2),
            maybe_parentheses(prettypr:beside(D1, D3), Prec, Ctxt);
        record_expr ->
            Ctxt1 = reset_prec(Ctxt),
            D1 = prettypr:beside(
                     prettypr:beside(lay_text_float("#"),
                                     lay(erl_syntax:record_expr_type(Node), Ctxt1)),
                     prettypr:text("{")),
            D2 = lay_fields(D1, erl_syntax:record_expr_fields(Node), Ctxt1, fun lay/2),
            Arg = erl_syntax:record_expr_argument(Node),
            lay_expr_argument(Arg, D2, Ctxt);
        record_field ->
            Ctxt1 = reset_prec(Ctxt),
            D1 = lay(erl_syntax:record_field_name(Node), Ctxt1),
            case erl_syntax:record_field_value(Node) of
                none ->
                    D1;
                V ->
                    prettypr:par([D1, lay_text_float("="), lay(V, Ctxt1)], Ctxt1#ctxt.break_indent)
            end;
        record_index_expr ->
            {Prec, PrecR} = erl_parse:preop_prec('#'),
            D1 = lay(erl_syntax:record_index_expr_type(Node), reset_prec(Ctxt)),
            D2 = lay(erl_syntax:record_index_expr_field(Node), set_prec(Ctxt, PrecR)),
            D3 = prettypr:beside(
                     prettypr:beside(lay_text_float("#"), D1),
                     prettypr:beside(lay_text_float("."), D2)),
            maybe_parentheses(D3, Prec, Ctxt);
        map_expr ->
            Ctxt1 = reset_prec(Ctxt),
            D1 = lay_fields(prettypr:text("#{"),
                            erl_syntax:map_expr_fields(Node),
                            Ctxt1,
                            fun lay/2),
            Arg = erl_syntax:map_expr_argument(Node),
            lay_expr_argument(Arg, D1, Ctxt);
        map_field_assoc ->
            Name = erl_syntax:map_field_assoc_name(Node),
            Value = erl_syntax:map_field_assoc_value(Node),
            lay_type_assoc(Name, Value, Ctxt);
        map_field_exact ->
            Name = erl_syntax:map_field_exact_name(Node),
            Value = erl_syntax:map_field_exact_value(Node),
            lay_type_exact(Name, Value, Ctxt);
        size_qualifier ->
            Ctxt1 = set_prec(Ctxt, erl_parse:max_prec()),
            D1 = lay(erl_syntax:size_qualifier_body(Node), Ctxt1),
            Arg = erl_syntax:size_qualifier_argument(Node),
            D2 = lay(erl_syntax:size_qualifier_argument(Node), Ctxt1),
            D3 = case erl_syntax:type(Arg) of
                     macro ->
                         case erl_syntax:macro_arguments(Arg) of
                             none ->
                                 %% Something:(?MACRO) gets converted into
                                 %% Something:?MACRO since this formatter treats
                                 %% ?MACRO as a "literal" and sometimes it actually
                                 %% is not. We'll add parentheses here, just in case.
                                 lay_parentheses(D2);
                             _ ->
                                 D2
                         end;
                     _ ->
                         D2
                 end,

            prettypr:beside(D1,
                            prettypr:beside(
                                prettypr:text(":"), D3));
        text ->
            case erl_syntax:get_ann(Node) of
                [expression_dot] -> % see maybe_append/3
                    prettypr:empty();
                _ ->
                    prettypr:text(
                        erl_syntax:text_string(Node))
            end;
        typed_record_field ->
            {_, Prec, _} = erl_parse:type_inop_prec('::'),
            Ctxt1 = reset_prec(Ctxt),
            D1 = lay(erl_syntax:typed_record_field_body(Node), Ctxt1),
            D2 = lay(erl_syntax:typed_record_field_type(Node), set_prec(Ctxt, Prec)),
            D3 = lay_double_colon(D1, D2, Ctxt1),
            maybe_parentheses(D3, Prec, Ctxt);
        try_expr ->
            Ctxt1 = reset_prec(Ctxt),
            D0 = lay_clause_expressions(erl_syntax:try_expr_body(Node), Ctxt1, fun lay/2),
            D1 = case erl_syntax:try_expr_clauses(Node) of
                     [] ->
                         vertical([prettypr:text("try"),
                                   prettypr:nest(Ctxt1#ctxt.break_indent, D0)]);
                     _ ->
                         prettypr:follow(
                             prettypr:text("try"), D0, Ctxt1#ctxt.break_indent)
                 end,
            Es0 = [prettypr:text("end")],
            Es1 = case erl_syntax:try_expr_after(Node) of
                      [] ->
                          Es0;
                      As ->
                          D2 = lay_clause_expressions(As, Ctxt1, fun lay/2),
                          [prettypr:text("after"), prettypr:nest(Ctxt1#ctxt.break_indent, D2) | Es0]
                  end,
            Es2 = case erl_syntax:try_expr_handlers(Node) of
                      [] ->
                          Es1;
                      Hs ->
                          D3 = lay_clauses(Hs, try_expr, Ctxt1),
                          [prettypr:text("catch"), prettypr:nest(Ctxt1#ctxt.break_indent, D3) | Es1]
                  end,
            Es3 = case erl_syntax:try_expr_clauses(Node) of
                      [] ->
                          Es2;
                      Cs ->
                          D4 = lay_clauses(Cs, try_expr, Ctxt1),
                          [prettypr:text("of"), prettypr:nest(Ctxt1#ctxt.break_indent, D4) | Es2]
                  end,
            prettypr:sep([prettypr:par([D1, hd(Es3)]) | tl(Es3)]);
        warning_marker ->
            E = erl_syntax:warning_marker_info(Node),
            prettypr:beside(
                prettypr:text("%% WARNING: "), lay_error_info(E, reset_prec(Ctxt)));
        %%
        %% Types
        %%
        annotated_type ->
            {_, Prec, _} = erl_parse:type_inop_prec('::'),
            D1 = lay(erl_syntax:annotated_type_name(Node), reset_prec(Ctxt)),
            D2 = lay(erl_syntax:annotated_type_body(Node), set_prec(Ctxt, Prec)),
            D3 = lay_double_colon(D1, D2, Ctxt),
            maybe_parentheses(D3, Prec, Ctxt);
        type_application ->
            Name = erl_syntax:type_application_name(Node),
            Arguments = erl_syntax:type_application_arguments(Node),

            %% Prefer shorthand notation.
            try erl_syntax_lib:analyze_type_application(Node) of
                {nil, 0} ->
                    prettypr:text("[]");
                {list, 1} ->
                    [A] = Arguments,
                    D1 = lay(A, reset_prec(Ctxt)),
                    prettypr:beside(
                        prettypr:text("["), prettypr:beside(D1, prettypr:text("]")));
                {nonempty_list, 1} ->
                    [A] = Arguments,
                    D1 = lay(A, reset_prec(Ctxt)),
                    prettypr:beside(
                        prettypr:text("["), prettypr:beside(D1, prettypr:text(", ...]")));
                _ ->
                    lay_application(Name, Arguments, Ctxt)
            catch
                syntax_error -> % Damn macros!!
                    lay_application(Name, Arguments, Ctxt)
            end;
        bitstring_type ->
            Ctxt1 = set_prec(Ctxt, erl_parse:max_prec()),
            M = erl_syntax:bitstring_type_m(Node),
            N = erl_syntax:bitstring_type_n(Node),
            D1 = [prettypr:beside(
                      prettypr:text("_:"), lay(M, Ctxt1))
                  || erl_syntax:type(M) =/= integer orelse erl_syntax:integer_value(M) =/= 0],
            D2 = [prettypr:beside(
                      prettypr:text("_:_*"), lay(N, Ctxt1))
                  || erl_syntax:type(N) =/= integer orelse erl_syntax:integer_value(N) =/= 0],
            F = fun(D, _) -> D end,
            D = lay_items(D1 ++ D2, Ctxt1, F),
            prettypr:beside(lay_text_float("<<"), prettypr:beside(D, lay_text_float(">>")));
        fun_type ->
            prettypr:text("fun()");
        constrained_function_type ->
            Ctxt1 = reset_prec(Ctxt),
            D1 = lay(erl_syntax:constrained_function_type_body(Node), Ctxt1),
            Ctxt2 = Ctxt1#ctxt{clause = undefined},
            D2 = lay(erl_syntax:constrained_function_type_argument(Node), Ctxt2),
            prettypr:par([D1, prettypr:beside(lay_text_float("when "), D2)],
                         Ctxt#ctxt.break_indent);
        function_type ->
            {Before, After} =
                case Ctxt#ctxt.clause of
                    spec ->
                        {"", ""};
                    _ ->
                        {"fun(", ")"}
                end,
            Ctxt1 = (reset_prec(Ctxt))#ctxt{clause = undefined},
            D1 = case erl_syntax:function_type_arguments(Node) of
                     any_arity ->
                         prettypr:text("(...)");
                     Arguments ->
                         As = lay_items(Arguments, Ctxt1, fun lay/2),
                         prettypr:beside(
                             prettypr:text("("), prettypr:beside(As, lay_text_float(")")))
                 end,
            D2 = lay(erl_syntax:function_type_return(Node), Ctxt1),
            prettypr:beside(lay_text_float(Before),
                            prettypr:sep([prettypr:beside(D1, lay_text_float(" ->")),
                                          prettypr:nest(Ctxt#ctxt.break_indent,
                                                        prettypr:beside(D2,
                                                                        lay_text_float(After)))]));
        constraint ->
            Name = erl_syntax:constraint_argument(Node),
            Args = erl_syntax:constraint_body(Node),
            case is_subtype(Name, Args) of
                true ->
                    [Var, Type] = Args,
                    {PrecL, Prec, PrecR} = erl_parse:type_inop_prec('::'),
                    D1 = lay(Var, set_prec(Ctxt, PrecL)),
                    D2 = lay(Type, set_prec(Ctxt, PrecR)),
                    D3 = lay_double_colon(D1, D2, Ctxt),
                    maybe_parentheses(D3, Prec, Ctxt);
                false ->
                    lay_application(Name, Args, Ctxt)
            end;
        map_type ->
            case erl_syntax:map_type_fields(Node) of
                any_size ->
                    prettypr:text("map()");
                Fs ->
                    Ctxt1 = reset_prec(Ctxt),
                    D = lay_fields(lay_text_float("#{"), Fs, Ctxt1, fun lay/2),
                    {Prec, _PrecR} = erl_parse:type_preop_prec('#'),
                    maybe_parentheses(D, Prec, Ctxt)
            end;
        map_type_assoc ->
            Name = erl_syntax:map_type_assoc_name(Node),
            Value = erl_syntax:map_type_assoc_value(Node),
            lay_type_assoc(Name, Value, Ctxt);
        map_type_exact ->
            Name = erl_syntax:map_type_exact_name(Node),
            Value = erl_syntax:map_type_exact_value(Node),
            lay_type_exact(Name, Value, Ctxt);
        integer_range_type ->
            {PrecL, Prec, PrecR} = erl_parse:type_inop_prec('..'),
            D1 = lay(erl_syntax:integer_range_type_low(Node), set_prec(Ctxt, PrecL)),
            D2 = lay(erl_syntax:integer_range_type_high(Node), set_prec(Ctxt, PrecR)),
            D3 = prettypr:beside(D1,
                                 prettypr:beside(
                                     prettypr:text(".."), D2)),
            maybe_parentheses(D3, Prec, Ctxt);
        record_type ->
            {Prec, _PrecR} = erl_parse:type_preop_prec('#'),
            D1 = prettypr:beside(
                     prettypr:beside(
                         prettypr:text("#"),
                         lay(erl_syntax:record_type_name(Node), reset_prec(Ctxt))),
                     prettypr:text("{")),
            D2 = lay_fields(D1, erl_syntax:record_type_fields(Node), reset_prec(Ctxt), fun lay/2),
            maybe_parentheses(D2, Prec, Ctxt);
        record_type_field ->
            Ctxt1 = reset_prec(Ctxt),
            D1 = lay(erl_syntax:record_type_field_name(Node), Ctxt1),
            D2 = lay(erl_syntax:record_type_field_type(Node), Ctxt1),
            lay_double_colon(D1, D2, Ctxt1);
        tuple_type ->
            case erl_syntax:tuple_type_elements(Node) of
                any_size ->
                    prettypr:text("tuple()");
                Elements ->
                    Es = lay_items(Elements, reset_prec(Ctxt), fun lay/2),
                    prettypr:beside(lay_text_float("{"), prettypr:beside(Es, lay_text_float("}")))
            end;
        type_union ->
            {_, Prec, PrecR} = erl_parse:type_inop_prec('|'),
            Es = lay_items(erl_syntax:type_union_types(Node),
                           lay_text_float(" |"),
                           set_prec(Ctxt, PrecR),
                           fun lay/2),
            maybe_parentheses(Es, Prec, Ctxt);
        user_type_application ->
            lay_application(erl_syntax:user_type_application_name(Node),
                            erl_syntax:user_type_application_arguments(Node),
                            Ctxt)
    end.

attribute_name(Node) ->
    N = erl_syntax:attribute_name(Node),
    Name =
        case erl_syntax:type(N) of
            macro ->
                macro_name(N, atom);
            _ ->
                N
        end,
    erl_syntax:concrete(Name).

is_subtype(Name, [Var, _]) ->
    erl_syntax:is_atom(Name, is_subtype) andalso erl_syntax:type(Var) =:= variable;
is_subtype(_, _) ->
    false.

get_func_node(Node) ->
    case erl_syntax:type(Node) of
        tuple ->
            case erl_syntax:tuple_elements(Node) of
                [F0, _] ->
                    F0;
                [M0, F0, _] ->
                    erl_syntax:module_qualifier(M0, F0);
                _ ->
                    Node
            end;
        _ ->
            Node
    end.

unfold_function_names(Ns) ->
    erl_syntax_lib:map_subtrees(fun unfold_function_name/1, Ns).

unfold_function_name(Tuple) ->
    [Name, Arity] = erl_syntax:tuple_elements(Tuple),
    case erl_syntax:type(Name) of
        atom ->
            erl_syntax:arity_qualifier(Name, Arity);
        macro ->
            erl_syntax:arity_qualifier(macro_name(Name, variable), Arity)
    end.

macro_name(Macro) ->
    MacroName = erl_syntax:macro_name(Macro),
    case erl_syntax:type(MacroName) of
        atom ->
            erl_syntax:atom_name(MacroName);
        variable ->
            erl_syntax:variable_literal(MacroName)
    end.

macro_name(Node, Type) ->
    Source = erl_syntax:macro_name(Node),
    FullName = [$? | macro_name(Node)],
    Target =
        case Type of
            variable ->
                erl_syntax:variable(FullName);
            atom ->
                erl_syntax:atom(FullName)
        end,
    erl_syntax:copy_pos(Source, erl_syntax:copy_comments(Source, Target)).

concrete_dodging_macros(Nodes) ->
    undodge_macros(erl_syntax:concrete(dodge_macros(Nodes))).

%% Macros are not handled well.
dodge_macros(Type) ->
    erl_syntax_lib:map(fun dodge_macro/1, Type).

dodge_macro(T) ->
    case erl_syntax:type(T) of
        macro ->
            erl_syntax:atom(macro_name(T));
        _ ->
            T
    end.

undodge_macros(Type) when is_list(Type) ->
    lists:map(fun undodge_macros/1, Type);
undodge_macros(Type) ->
    erl_syntax_lib:map(fun undodge_macro/1, Type).

undodge_macro(T) ->
    case erl_syntax:type(T) of
        atom ->
            case get_node_text(T) of
                "?" ->
                    erl_syntax:macro(
                        erl_syntax:variable(
                            erl_syntax:atom_name(T)));
                _ ->
                    T
            end;
        _ ->
            T
    end.

%% @doc This is a particular edge case for those places where the parser
%%      treats func/1 as {func, 1}... particularly -dialyzer(...)
maybe_convert_to_qualifier(Node, #ctxt{force_arity_qualifiers = false}) ->
    Node;
maybe_convert_to_qualifier(Node, #ctxt{force_arity_qualifiers = true}) ->
    case erl_syntax:tuple_elements(Node) of
        [FuncName, Arity] ->
            case {erl_syntax:type(FuncName), erl_syntax:type(Arity)} of
                {atom, integer} ->
                    erl_syntax:arity_qualifier(FuncName, Arity);
                _ ->
                    Node
            end;
        _ ->
            Node
    end.

lay_nested_infix_expr(Node, #ctxt{parenthesize_infix_operations = false} = Ctxt) ->
    lay(Node, Ctxt);
lay_nested_infix_expr(Node, #ctxt{parenthesize_infix_operations = true} = Ctxt) ->
    D1 = lay(Node, Ctxt),
    case erl_syntax:type(Node) of
        infix_expr ->
            Operator = erl_syntax:infix_expr_operator(Node),
            Prec =
                case erl_syntax:type(Operator) of
                    operator ->
                        {_, P, _} =
                            erl_parse:inop_prec(
                                erl_syntax:operator_name(Operator)),
                        P;
                    _ ->
                        0
                end,

            % If we *should* add parentheses semantic-wise, lay/2 will take care
            % of that, thanks to maybe_parentheses/3
            case needs_parentheses(Prec, Ctxt) of
                true ->
                    D1;
                false ->
                    lay_parentheses(D1)
            end;
        _ ->
            D1
    end.

lay_text_float(Str) ->
    prettypr:floating(
        prettypr:text(Str)).

lay_fun_sep(Clauses, Ctxt) ->
    prettypr:sep([prettypr:follow(
                      prettypr:text("fun"), Clauses, Ctxt#ctxt.break_indent),
                  prettypr:text("end")]).

lay_expr_argument(none, D, Ctxt) ->
    {_, Prec, _} = erl_parse:inop_prec('#'),
    maybe_parentheses(D, Prec, Ctxt);
lay_expr_argument(Arg, D, Ctxt) ->
    {PrecL, Prec, _} = erl_parse:inop_prec('#'),
    D1 = prettypr:beside(lay(Arg, set_prec(Ctxt, PrecL)), D),
    maybe_parentheses(D1, Prec, Ctxt).

lay_parentheses(D) ->
    prettypr:beside(lay_text_float("("), prettypr:beside(D, lay_text_float(")"))).

maybe_parentheses(D, Prec, Ctxt) ->
    case needs_parentheses(Prec, Ctxt) of
        true ->
            lay_parentheses(D);
        false ->
            D
    end.

needs_parentheses(Prec, Ctxt) ->
    Ctxt#ctxt.prec > Prec.

lay_string(Node, Ctxt) ->
    S0 = erl_syntax:string_literal(Node, Ctxt#ctxt.encoding),
    Txt = get_node_text(Node),
    S = case {interpret_string(S0), interpret_string(Txt)} of
            {Same, Same} ->
                %% They're 'semantically' the same, but syntactically different
                Txt;
            {_, _} ->
                %% They're 'semantically' different. We couldn't parse the text
                %% correctly.
                S0
        end,
    lay_string_lines(string_lines(S), Ctxt).

interpret_string(undefined) ->
    undefined;
interpret_string(S) ->
    {ok, Tokens, _} = erl_scan:string(S),
    erl_parse:parse_exprs(Tokens ++ [{dot, 0}]).

string_lines([$\" | S0]) ->
    [$\" | S1] = lists:reverse(S0),
    Ls = string:split(
             lists:reverse(S1), "\"\n\"", all),
    [[$\" | L] ++ [$\"] || L <- Ls].

lay_string_lines([S], Ctxt) ->
    lay_string_line(S, Ctxt);
lay_string_lines([S | Ss], Ctxt) ->
    prettypr:above(lay_string_line(S, Ctxt), lay_string_lines(Ss, Ctxt)).

lay_string_line(S, #ctxt{truncate_strings = true, ribbon = Ribbon}) ->
    %% S includes leading/trailing double-quote characters. The segment
    %% width is 2/3 of the ribbon width - this seems to work well.
    lay_string(S, length(S), Ribbon * 2 div 3);
lay_string_line(S, _) ->
    prettypr:text(prepare_string_line(S)).

%% @doc We need to replace \n\t here as a work around for how remove_tabs/1 works
%%      It's a hack, but it makes the formatter consistent.
%%      And it only affects strings that start with tab right after a newline.
%%      We truly hope that there are not too many of those.
%%      We also need to convert the rightmost space to \s to prevent
%%      remove_trailing_spaces/1 from removing it. Again another workaround.
prepare_string_line(S) ->
    switch_tabs_after_newline(switch_trailing_spaces(S)).

switch_trailing_spaces(String) ->
    re:replace(String, "\s\n", "\\\\s\n", [global, {return, list}, unicode]).

switch_tabs_after_newline(String) ->
    case re:replace(String,
                    <<$\n, $\t>>,
                    <<$\n, $\\, $\\, $t>>,
                    [global, {return, list}, unicode])
    of
        String ->
            String;
        Replaced ->
            switch_tabs_after_newline(Replaced)
    end.

lay_string(S, L, W) when L > W, W > 0 ->
    %% Note that L is the minimum, not the exact, printed length.
    case split_string(S, W - 1, L) of
        {_S1, ""} ->
            prettypr:text(S);
        {S1, S2} ->
            prettypr:above(
                prettypr:text(S1 ++ "\""), lay_string([$" | S2], L - W + 1, W))
    end;
lay_string(S, _L, _W) ->
    prettypr:text(S).

split_string(Xs, N, L) ->
    split_string_first(Xs, N, L, []).

%% We only split strings at whitespace, if possible. We must make sure
%% we do not split an escape sequence.
split_string_first([$\s | Xs], N, L, As) when N =< 0, L >= 5 ->
    {lists:reverse([$\s | As]), Xs};
split_string_first([$\t | Xs], N, L, As) when N =< 0, L >= 5 ->
    {lists:reverse([$t, $\\ | As]), Xs};
split_string_first([$\n | Xs], N, L, As) when N =< 0, L >= 5 ->
    {lists:reverse([$n, $\\ | As]), Xs};
split_string_first([$\\ | Xs], N, L, As) ->
    split_string_second(Xs, N - 1, L - 1, [$\\ | As]);
split_string_first(Xs, N, L, As) when N =< -10, L >= 5 ->
    {lists:reverse(As), Xs};
split_string_first([_ | _] = S, N, L, As) ->
    split_string_next(S, N, L, As);
split_string_first([], _N, _L, As) ->
    {lists:reverse(As), ""}.

split_string_second([$^, X | Xs], N, L, As) ->
    split_string_first(Xs, N - 2, L - 2, [X, $^ | As]);
split_string_second([$x, ${ | Xs], N, L, As) ->
    split_string_third(Xs, N - 2, L - 2, [${, $x | As]);
split_string_second([X1, X2, X3 | Xs], N, L, As)
    when X1 >= $0, X1 =< $7, X2 >= $0, X2 =< $7, X3 >= $0, X3 =< $7 ->
    split_string_first(Xs, N - 3, L - 3, [X3, X2, X1 | As]);
split_string_second([X1, X2 | Xs], N, L, As)
    when X1 >= $0, X1 =< $7, X2 >= $0, X2 =< $7 ->
    split_string_first(Xs, N - 2, L - 2, [X2, X1 | As]);
split_string_second(S, N, L, As) ->
    split_string_next(S, N, L, As).

split_string_third([$} | Xs], N, L, As) ->
    split_string_first(Xs, N - 1, L - 1, [$} | As]);
split_string_third([X | Xs], N, L, As)
    when X >= $0, X =< $9; X >= $a, X =< $z; X >= $A, X =< $Z ->
    split_string_third(Xs, N - 1, L - 1, [X | As]);
split_string_third([X | _Xs] = S, N, L, As) when X >= $0, X =< $9 ->
    split_string_next(S, N, L, As).

split_string_next([X | Xs], N, L, As) ->
    split_string_first(Xs, N - 1, L - 1, [X | As]);
split_string_next([], N, L, As) ->
    split_string_first([], N, L, As).

%% @doc Produces the layout for a spec/callback with a single clause that has a
%%      when... Which is a scenario that's common enough to deserve its own
%%      implementation. That allows us to place the when, if indented, closer to
%%      the margin and not floating below the parameters of the function in question
lay_simple_spec(NameDoc, Node, Ctxt) ->
    case erl_syntax:type(Node) of
        constrained_function_type ->
            Ctxt1 = reset_prec(Ctxt),
            D1 = lay(erl_syntax:constrained_function_type_body(Node), Ctxt1#ctxt{clause = spec}),
            D2 = lay(erl_syntax:constrained_function_type_argument(Node), Ctxt1),
            prettypr:par([prettypr:beside(NameDoc, D1),
                          prettypr:beside(lay_text_float("when "), D2)],
                         Ctxt1#ctxt.break_indent);
        function_type ->
            prettypr:beside(NameDoc, lay(Node, Ctxt#ctxt{clause = spec}))
    end.

%% Note that there is nothing in `lay_clauses' that actually requires
%% that the elements have type `clause'; it just sets up the proper
%% context and arranges the elements suitably for clauses.
lay_clauses(Cs, Type, Ctxt) ->
    vertical(seq(Cs, lay_text_float(";"), Ctxt#ctxt{clause = Type}, fun lay/2)).

%% Note that for the clause-making functions, the guard argument
%% can be `none', which has different interpretations in different
%% contexts.
make_simple_fun_clause(P, G, B, Ctxt) ->
    D = make_fun_clause_head(none, P),

    % Since this anonymous fun has a single clause, we don't need to indent its
    % body that much
    append_clause_body(B,
                       append_guard(G, D, Ctxt),
                       Ctxt#ctxt{inline_clause_bodies =
                                     Ctxt#ctxt.inline_simple_funs
                                     orelse Ctxt#ctxt.inline_clause_bodies,
                                 break_indent = 0}).

make_fun_clause(P, G, B, Ctxt) ->
    make_fun_clause(none, P, G, B, Ctxt).

make_fun_clause(N, P, G, B, Ctxt) ->
    D = make_fun_clause_head(N, P),
    make_case_clause(D, G, B, Ctxt).

make_fun_clause_head(none, P) ->
    lay_parentheses(P);
make_fun_clause_head(N, P) ->
    prettypr:beside(N, lay_parentheses(P)).

make_case_clause(P, G, B, Ctxt) ->
    append_clause_body(B, append_guard(G, P, Ctxt), Ctxt).

make_if_clause(G, B, Ctxt) ->
    G1 = case G of
             none ->
                 prettypr:text("true");
             _ ->
                 G
         end,
    append_clause_body(B, G1, Ctxt).

append_clause_body(B, D, Ctxt) ->
    D1 = [prettypr:beside(D, lay_text_float(" ->")),
          prettypr:nest(Ctxt#ctxt.break_indent, B)],
    case Ctxt#ctxt.inline_clause_bodies of
        false ->
            vertical(D1);
        true ->
            prettypr:sep(D1)
    end.

append_guard(none, D, _) ->
    D;
append_guard(G, D, Ctxt) ->
    prettypr:par([D,
                  prettypr:follow(
                      prettypr:text("when"), G, Ctxt#ctxt.sub_indent)],
                 Ctxt#ctxt.break_indent).

lay_bit_types([T], Ctxt) ->
    lay(T, Ctxt);
lay_bit_types([T | Ts], Ctxt) ->
    prettypr:beside(lay(T, Ctxt),
                    prettypr:beside(lay_text_float("-"), lay_bit_types(Ts, Ctxt))).

lay_error_info({L, M, T} = T0, Ctxt) when is_integer(L), is_atom(M) ->
    try apply(M, format_error, [T]) of
        S when is_list(S) ->
            case L > 0 of
                true ->
                    prettypr:beside(
                        prettypr:text(
                            io_lib:format("~w: ", [L])),
                        prettypr:text(S));
                _ ->
                    prettypr:text(S)
            end;
        _ ->
            lay_concrete(T0, Ctxt)
    catch
        _:_ ->
            lay_concrete(T0, Ctxt)
    end;
lay_error_info(T, Ctxt) ->
    lay_concrete(T, Ctxt).

lay_concrete(T, Ctxt) ->
    lay(erl_syntax:abstract(T), Ctxt).

lay_type_assoc(Name, Value, Ctxt) ->
    lay_type_par_text(Name, Value, "=>", Ctxt).

lay_type_exact(Name, Value, Ctxt) ->
    lay_type_par_text(Name, Value, ":=", Ctxt).

lay_type_par_text(Name, Value, Text, Ctxt) ->
    Ctxt1 = reset_prec(Ctxt),
    D1 = lay(Name, Ctxt1),
    D2 = lay(Value, Ctxt1),
    prettypr:par([D1, lay_text_float(Text), D2], Ctxt1#ctxt.break_indent).

lay_application(Name, Arguments, Ctxt) ->
    lay_application(Name, Arguments, false, Ctxt).

lay_application(Name, Arguments, SpacesWithinParentheses, Ctxt) ->
    case erl_syntax:type(Name) of
        macro ->
            [Arg | Args] = Arguments,
            MacroVar = erl_syntax:variable([$? | atom_to_list(erl_syntax:variable_name(Arg))]),
            lay_application(MacroVar, Args, SpacesWithinParentheses, Ctxt);
        _ ->
            {PrecL, Prec} = erl_parse:func_prec(),
            MaybeSortedArgs = maybe_sort_arity_qualifiers(Arguments, Ctxt),
            {CommentedName, CommentedArgs} = move_comments(Name, MaybeSortedArgs),
            DName = prettypr:beside(lay(CommentedName, set_prec(Ctxt, PrecL)), prettypr:text("(")),
            DArgs = lay_items(CommentedArgs, reset_prec(Ctxt), fun lay/2),
            DClosingParen = lay_text_float(")"),
            D = case not Ctxt#ctxt.inline_qualified_function_composition
                     andalso is_qualified_function_composition(Name, Arguments)
                of
                    true ->
                        vertical([DName,
                                  prettypr:nest(Ctxt#ctxt.break_indent,
                                                prettypr:beside(DArgs, DClosingParen))]);
                    _ ->
                        case SpacesWithinParentheses andalso CommentedArgs /= [] of
                            false ->
                                prettypr:beside(DName, prettypr:beside(DArgs, DClosingParen));
                            true ->
                                prettypr:par([prettypr:par([DName, DArgs], Ctxt#ctxt.break_indent),
                                              DClosingParen])
                        end
                end,
            maybe_parentheses(D, Prec, Ctxt)
    end.

%% @doc Might produce a new AST node if the following criteria is met:
%%        1. we know that the AST node is ONLY a list of arity qualifiers
%%           (e.g.: [name/0]), and nothing else (no other kind of AST nodes
%%           in the list)
%%        2. the 'sort_arity_qualifiers' option was set to true
%%      In short, we only will sort arity qualifiers present in the '-export',
%%      '-export_type', and '-optional_callbacks' attributes.
maybe_sort_arity_qualifiers(OriginalAST, Ctxt) ->
    %% The 'OriginalAST' variable is a #list{} AST node for export lists,
    %% and a list of #tree{} AST nodes for both export_type and optional_callbacks
    %% lists. In order to unify the code handling the sorting for any of the cases,
    %% we have to wrap the list of #tree{} AST nodes in a #list{} AST node, sort it,
    %% and then unwrap it.
    case Ctxt#ctxt.sort_arity_qualifiers_match andalso Ctxt#ctxt.sort_arity_qualifiers of
        false ->
            OriginalAST;
        true when is_list(OriginalAST) ->
            [UnwrappedAST] = OriginalAST,
            do_sort_arity_qualifiers(UnwrappedAST);
        true ->
            ArityQualifiers = erl_syntax:list_elements(OriginalAST),
            SortedArityQualifiers = do_sort_arity_qualifiers(ArityQualifiers),
            erl_syntax:update_tree(OriginalAST, [SortedArityQualifiers])
    end.

%% @doc Might produce a new AST on which the arity qualifiers are sorted
%%      alphabetically if 'sort_arity_qualifiers' was set to 'true'.
%%      These arity qualifiers are the items in the export lists, export_type lists,
%%      and other module attributes that contain function references in the form of
%%      '[fun1/0, fun2/1]'.
do_sort_arity_qualifiers(Arguments0) when is_list(Arguments0) ->
    [Sorted] = do_sort_arity_qualifiers(erl_syntax:list(Arguments0)),
    erl_syntax:list_elements(Sorted);
do_sort_arity_qualifiers(Arguments0) ->
    case erl_syntax:subtrees(Arguments0) of
        [] ->
            %% node was a leaf node, skip
            [Arguments0];
        [SubTrees0] ->
            SubTrees1 = lists:sort(fun sort_arity_qualifiers_alphabetically/2, SubTrees0),
            [erl_syntax:update_tree(Arguments0, [SubTrees1])]
    end.

%% @doc Returns an altered AST with the arity qualifiers list
%%      sorted first by name and then by arity.
sort_arity_qualifiers_alphabetically(FuncInfoA, FuncInfoB) ->
    %% We get the relevant function info from the AST, namely its name and arity
    {FuncNameA, FuncArityA} = func_name_and_arity_from_ast(FuncInfoA),
    {FuncNameB, FuncArityB} = func_name_and_arity_from_ast(FuncInfoB),

    %% If we are comparing two functions with the same name,
    %% they should be ordered by their arity instead.
    {FuncNameA, FuncArityA} < {FuncNameB, FuncArityB}.

func_name_and_arity_from_ast(FuncSubTree) ->
    FuncName =
        erl_syntax:data(
            erl_syntax:arity_qualifier_body(FuncSubTree)),
    FuncArity =
        erl_syntax:data(
            erl_syntax:arity_qualifier_argument(FuncSubTree)),
    {FuncName, FuncArity}.

%% @doc Recursive function that groups nested applications of the same infix
%%      expression as a single list of docs.
infix_expr_docs(Node, Ctxt) ->
    Operator = erl_syntax:infix_expr_operator(Node),
    {OperatorName, {PrecL, Prec, PrecR}} =
        case erl_syntax:type(Operator) of
            operator ->
                ON = erl_syntax:operator_name(Operator),
                {ON, erl_parse:inop_prec(ON)};
            _ ->
                {undefined, {0, 0, 0}}
        end,
    OpDoc = lay(Operator, reset_prec(Ctxt)),
    LeftDocs =
        infix_expr_docs(OperatorName, erl_syntax:infix_expr_left(Node), set_prec(Ctxt, PrecL)),
    RightDocs =
        infix_expr_docs(OperatorName, erl_syntax:infix_expr_right(Node), set_prec(Ctxt, PrecR)),
    Ds = LeftDocs ++ [OpDoc | RightDocs],
    {Prec, Ds}.

infix_expr_docs(_, Node, #ctxt{parenthesize_infix_operations = true} = Ctxt) ->
    [lay_nested_infix_expr(Node, Ctxt)];
infix_expr_docs(OperatorName, Node, Ctxt) ->
    case infix_expr_operator_name(Node) of
        OperatorName ->
            {InnerPrecL, DsL} = infix_expr_docs(Node, Ctxt),
            case needs_parentheses(InnerPrecL, Ctxt) of
                false ->
                    DsL;
                true ->
                    [lay_nested_infix_expr(Node, Ctxt)]
            end;
        _ ->
            [lay_nested_infix_expr(Node, Ctxt)]
    end.

infix_expr_operator_name(Node) ->
    case erl_syntax:type(Node) of
        infix_expr ->
            Operator = erl_syntax:infix_expr_operator(Node),
            case erl_syntax:type(Operator) of
                operator ->
                    erl_syntax:operator_name(Operator);
                _ ->
                    not_an_operator
            end;
        _ ->
            not_an_operator
    end.

adjust_infix_expr_pars([Doc | Docs], Ctxt) ->
    [Doc | adjust_infix_expr_pars(Docs, Ctxt, [])].

adjust_infix_expr_pars([], _, Acc) ->
    lists:reverse(Acc);
adjust_infix_expr_pars([OpDoc, ExprDoc | Docs], Ctxt, Acc) ->
    adjust_infix_expr_pars(Docs,
                           Ctxt,
                           [prettypr:beside(
                                prettypr:beside(OpDoc, prettypr:text(" ")), ExprDoc)
                            | Acc]).

%% @doc If the name has postcomments and/or the first argument has precomments
%%      they get moved *too much*. So we convert them all into precomments, since
%%      that's how the look better.
move_comments(Name, []) ->
    {Name, []};
move_comments(Name, [Arg0 | Args]) ->
    case {erl_syntax:get_postcomments(Name), erl_syntax:get_precomments(Arg0)} of
        {[], _} ->
            {Name, [Arg0 | Args]};
        {PostComments, PreComments} ->
            {erl_syntax:set_postcomments(Name, []),
             [erl_syntax:set_precomments(Arg0, PostComments ++ PreComments) | Args]}
    end.

%% @doc Is this a function composition of two fully-qualified names.
%%      i.e. something like a_module:a_fun(another_module:another_func(...))
%%      The idea in this scenario is to indent that with the heuristic thinking that such
%%      a function composition will result in a very long line.
is_qualified_function_composition(_, []) ->
    false;
is_qualified_function_composition(Outside, [FirstArg | _]) ->
    erl_syntax:type(Outside) == module_qualifier
    andalso erl_syntax:type(FirstArg) == application
    andalso erl_syntax:type(
                erl_syntax:application_operator(FirstArg))
            == module_qualifier.

seq([H], _Separator, Ctxt, Fun) ->
    [Fun(H, Ctxt)];
seq([H | T], Separator, Ctxt, Fun) ->
    [maybe_append(hd(T), Separator, Fun(H, Ctxt)) | seq(T, Separator, Ctxt, Fun)];
seq([], _, _, _) ->
    [prettypr:empty()].

maybe_append(Next, Suffix, D) ->
    try erl_syntax:type(Next) of
        text ->
            case erl_syntax:get_ann(Next) of
                % This is introduced by ktn_dodger when parsing non-module files (e.g. app.src)
                [expression_dot] ->
                    prettypr:beside(D, lay_text_float("."));
                _ ->
                    maybe_append(Suffix, D)
            end;
        _ ->
            maybe_append(Suffix, D)
    catch
        error:{badarg, Next} -> % Sometimes lay_items is called with documents, not nodes.
            maybe_append(Suffix, D)
    end.

maybe_append(none, D) ->
    D;
maybe_append(Suffix, D) ->
    prettypr:beside(D, Suffix).

vertical([D]) ->
    D;
vertical([D | Ds]) ->
    prettypr:above(D, vertical(Ds));
vertical([]) ->
    [].

vertical_sep([{D, _}]) ->
    D;
vertical_sep([{D, empty_line} | Ds]) ->
    prettypr:above(
        prettypr:above(D, prettypr:text("")), vertical_sep(Ds));
vertical_sep([{D, no_empty_line} | Ds]) ->
    prettypr:above(D, vertical_sep(Ds));
vertical_sep([]) ->
    [].

empty_lines_to_add([], _Ctxt) ->
    [];
empty_lines_to_add([Node | Nodes], Ctxt) ->
    AfterThisNode =
        case erl_syntax:type(Node) of
            attribute ->
                AttrName = attribute_name(Node),
                case is_last_in_list(AttrName, Nodes) of
                    true ->
                        empty_line;
                    false ->
                        no_empty_line
                end;
            text -> % To handle the initial rows of escripts
                no_empty_line;
            _ ->
                empty_line
        end,
    [AfterThisNode | empty_lines_to_add(Nodes, Ctxt)].

is_last_in_list(_AttrName, []) ->
    true;
is_last_in_list(spec, _) ->
    false; % we never want to add an empty line after spec
is_last_in_list(AttrName, [Node | _]) ->
    erl_syntax:type(Node) /= attribute orelse attribute_name(Node) /= AttrName.

spaces(N) when N > 0 ->
    [$\s | spaces(N - 1)];
spaces(_) ->
    [].

tidy_integer(Node) ->
    tidy_number(Node, erl_syntax:integer_literal(Node)).

tidy_float(Node) ->
    tidy_number(Node, io_lib:format("~p", [erl_syntax:float_value(Node)])).

tidy_char(Node, Encoding) ->
    case get_node_text(Node) of
        undefined ->
            erl_syntax:char_literal(Node, Encoding);
        Text ->
            Text
    end.

tidy_atom(Node, #ctxt{encoding = Encoding, unquote_atoms = true}) ->
    erl_syntax:atom_literal(Node, Encoding);
tidy_atom(Node, #ctxt{encoding = Encoding}) ->
    case erl_syntax:is_tree(Node) of
        true -> %% It's not exactly an atom (e.g. module, export, spec)
            erl_syntax:atom_literal(Node, Encoding);
        false ->
            case get_node_text(Node) of
                undefined ->
                    erl_syntax:atom_literal(Node, Encoding);
                Text ->
                    Text
            end
    end.

%% @doc If we captured the original text for the number, then we use it.
%%      Otherwise, we use the value returned by the parser.
%%      The goal is to preserve things like 16#FADE or -1e-1 instead of turning
%%      them into integers or "pretty printed" floats.
tidy_number(Node, Default) ->
    case get_node_text(Node) of
        undefined ->
            Default;
        Text ->
            number_from_text(Text, Default)
    end.

%% @doc This function covers the corner case when erl_parse:parse_form/1
%%      (used by ktn_dodger) screws up the text for things like fun x/1 or
%%      -vsn(1) and therefore that text, that was actually captured,
%%      can not be used.
%% NOTE: floats work as "integers" according to string:to_integer/1
number_from_text(Text, Default) ->
    case string:to_integer(Text) of
        {error, no_integer} ->
            Default;
        {_, _} ->
            Text
    end.

lay_fields(Opening, Exprs, #ctxt{spaces_around_fields = false} = Ctxt, Fun) ->
    prettypr:beside(Opening,
                    prettypr:beside(lay_fields(Exprs, Ctxt, Fun), lay_text_float("}")));
lay_fields(Opening, [] = Exprs, #ctxt{spaces_around_fields = true} = Ctxt, Fun) ->
    lay_fields(Opening, Exprs, Ctxt#ctxt{spaces_around_fields = false}, Fun);
lay_fields(Opening, Exprs, #ctxt{spaces_around_fields = true} = Ctxt, Fun) ->
    prettypr:par([prettypr:par([Opening, lay_fields(Exprs, Ctxt, Fun)],
                               Ctxt#ctxt.break_indent),
                  lay_text_float("}")]).

lay_fields(Exprs, #ctxt{inline_fields = {when_over, N}} = Ctxt, Fun)
    when length(Exprs) > N ->
    prettypr:par(seq(Exprs, lay_text_float(","), Ctxt, Fun));
lay_fields(Exprs, #ctxt{inline_fields = {when_over, N}} = Ctxt, Fun)
    when length(Exprs) =< N ->
    vertical(seq(Exprs, lay_text_float(","), Ctxt, Fun));
lay_fields(Exprs, #ctxt{inline_fields = {when_under, N}} = Ctxt, Fun)
    when length(Exprs) < N ->
    prettypr:par(seq(Exprs, lay_text_float(","), Ctxt, Fun));
lay_fields(Exprs, #ctxt{inline_fields = {when_under, N}} = Ctxt, Fun)
    when length(Exprs) >= N ->
    vertical(seq(Exprs, lay_text_float(","), Ctxt, Fun));
lay_fields(Exprs, #ctxt{inline_fields = all} = Ctxt, Fun) ->
    prettypr:par(seq(Exprs, lay_text_float(","), Ctxt, Fun));
lay_fields(Exprs, #ctxt{inline_fields = none} = Ctxt, Fun) ->
    vertical(seq(Exprs, lay_text_float(","), Ctxt, Fun)).

lay_items(Exprs, Ctxt, Fun) ->
    lay_items(Exprs, lay_text_float(","), Ctxt, Fun).

lay_items(Exprs, Separator, #ctxt{inline_items = {when_over, N}} = Ctxt, Fun)
    when length(Exprs) > N ->
    prettypr:par(seq(Exprs, Separator, Ctxt, Fun));
lay_items(Exprs,
          Separator,
          #ctxt{force_indentation = true, inline_items = {when_over, N}} = Ctxt,
          Fun)
    when length(Exprs) =< N ->
    vertical(seq(Exprs, Separator, Ctxt, Fun));
lay_items(Exprs, Separator, #ctxt{inline_items = {when_over, N}} = Ctxt, Fun)
    when length(Exprs) =< N ->
    prettypr:sep(seq(Exprs, Separator, Ctxt, Fun));
lay_items(Exprs, Separator, #ctxt{inline_items = {when_under, N}} = Ctxt, Fun)
    when length(Exprs) < N ->
    prettypr:par(seq(Exprs, Separator, Ctxt, Fun));
lay_items(Exprs,
          Separator,
          #ctxt{force_indentation = true, inline_items = {when_under, N}} = Ctxt,
          Fun)
    when length(Exprs) >= N ->
    vertical(seq(Exprs, Separator, Ctxt, Fun));
lay_items(Exprs, Separator, #ctxt{inline_items = {when_under, N}} = Ctxt, Fun)
    when length(Exprs) >= N ->
    prettypr:sep(seq(Exprs, Separator, Ctxt, Fun));
lay_items(Exprs, Separator, #ctxt{inline_items = all} = Ctxt, Fun) ->
    prettypr:par(seq(Exprs, Separator, Ctxt, Fun));
lay_items(Exprs,
          Separator,
          #ctxt{force_indentation = true, inline_items = none} = Ctxt,
          Fun) ->
    vertical(seq(Exprs, Separator, Ctxt, Fun));
lay_items(Exprs, Separator, #ctxt{inline_items = none} = Ctxt, Fun) ->
    prettypr:sep(seq(Exprs, Separator, Ctxt, Fun)).

lay_clause_expressions(Exprs, #ctxt{inline_expressions = true} = Ctxt, Fun) ->
    prettypr:sep(seq(Exprs, lay_text_float(","), Ctxt, Fun));
lay_clause_expressions([H], Ctxt, Fun) ->
    Fun(H, Ctxt);
lay_clause_expressions([H | T], Ctxt, Fun) ->
    Clause = prettypr:beside(Fun(H, Ctxt), lay_text_float(",")),
    Next = lay_clause_expressions(T, Ctxt, Fun),
    case is_last_and_before_empty_line(H, T, Ctxt) of
        true ->
            prettypr:above(
                prettypr:above(Clause, prettypr:text("")), Next);
        false ->
            prettypr:above(Clause, Next)
    end;
lay_clause_expressions([], _, _) ->
    prettypr:empty().

is_last_and_before_empty_line(H, [H2 | _], #ctxt{empty_lines = EmptyLines}) ->
    H2Pos =
        case erl_syntax:get_precomments(H2) of
            [] ->
                get_pos(H2);
            [Comment | _] ->
                get_pos(Comment)
        end,
    H2Pos - get_pos(H) >= 2 andalso lists:member(H2Pos - 1, EmptyLines).

get_pos(Node) ->
    erl_anno:line(
        erl_syntax:get_pos(Node)).

get_node_text(Node) ->
    erl_anno:text(
        erl_syntax:get_pos(Node)).

lay_double_colon(D1, D2, Ctxt) ->
    prettypr:par([prettypr:beside(D1, lay_text_float(" ::")), D2], Ctxt#ctxt.break_indent).

lay_match_expression(Op, Pattern, D1, D2, Ctxt) ->
    case erl_syntax:type(Pattern) == underscore
         orelse erl_syntax:type(Pattern) == variable
                andalso length(erl_syntax:variable_literal(Pattern)) < Ctxt#ctxt.break_indent
    of
        true -> %% Single short variable on the left, don't nest
            prettypr:follow(
                prettypr:beside(D1, lay_text_float(Op)), D2, Ctxt#ctxt.break_indent);
        false -> %% Large pattern, nesting makes sense
            prettypr:sep([prettypr:beside(D1, lay_text_float(Op)),
                          prettypr:nest(Ctxt#ctxt.break_indent, D2)])
    end.