Skip to content

Discussion: formatting options #374

@gilramir

Description

@gilramir

Question 1 - Number of spaces per indentation

4?

By the way, you will see in some examples that even if we pick a specific
indentation, we might also have a smaller indentation, or so it seems (nested brackets,
Question 4)

Question 2 - Page Width

I currently have a 100-column page width. Do we want this? 100? 90? 80?

The reason I'm using PrettyExpressive is that other formatters also use similar
pretty-printing algorithms, notably the Wadler-Leijen Algorithm and its
functional variant, Strictly Pretty. In this family of pretty-printing
algorithms, there is a page width, and the algorithm can make the code look
pretty within the page width, but if needed, let it go beyond the page
width and care less about the prettiness.

Some tools, like Golang's "go fmt" don't do this. They honor the line breaks
of the author. If the author wants very long lines, so be it.
"go fmt" isn't producing canonical output, it's just adjusting
the author's formatting to make it nicer.

Since we do want canonical output, we have to enforce a canonical way of
where to break lines. And thus, we need a page width.

The simplest example of this is is Gren's "module" line. If the "exposing" list is short
and can fit in the page width, we can render it on one line.

module Geometry exposing ( Point, area, perimeter )

But if it's long, we can render it in the multi-line format.

module Geometry exposing                                                         
    ( Point                          
    , Shape
    , Circle
    , Rectangle
    , computeArea
    , computePerimeter
    , scaleShape
    , translateShape
    , rotateShape
    )

But also, as an example, a function call that is short stays on one line:

result = renderWidget header content footer

while one that is long enough to reach the page boundary wraps, and its
overflow arguments indent one tab stop:

result =
    renderWidget mainContainerElement headerSectionElement navigationBarElement contentAreaElement
        footerSectionElement

Question 3 - Multi-line "if" predicates

If we are going to have a page width boundary, and thus long expressions
do wrap, how shall we render if/then expression with long "if" predicates?

Example of a short predicate:

    if callSomeFunction then
        runTrue
    else
        runFalse

Example of a long predicate, indented to the same level as the body:

    if (callSomeFunction with lots of arguments that need to
        wrap the line) then
        runTrue
    else
        runFalse

Example of a long predicate, indented to an extra tab-stop, to be visually
different from the body:

    if (callSomeFunction with lots of arguments that need to
            wrap the line) then
        runTrue
    else
        runFalse

This last example is what the formatter is currently doing.

It turns out the wrapping can happen such that the "then" is by itself on a
line. The then is pushed to a line of its own, indented one
extra step (+8) past the branch body (+4):

ifLong x =
    if conditionOne x && conditionTwo x && conditionThree x && conditionFour x && conditionFive x
            then
        trueBranchResult
    else
        falseBranchResult

Is a dangling then the layout we want, or should the then kept attached to the last predicate line?

Question 4 - Indentation of nested brackets

It can appear that nested brackets have 2-space tab stops instead of 4. The
outer [ and its item separators , sit at one tab stop (column 5 here), but
because each item begins with [ / , (two characters), an item that is
itself a bracketed block lines its {, , and } up two columns further in
(column 7) — so the inner brackets read as a 2-space indent rather than 4.

shapes =
    [ { name = "circle"
      , numberOfSides = 0
      , isFilled = True
      , colorValue = "crimson"
      , strokeWidth = 2
      }
    , { name = "triangle"
      , numberOfSides = 3
      , isFilled = False
      , colorValue = "cornflowerblue"
      , strokeWidth = 1
      }
    ]

Is this okay? Or is another rendering better?

By the way, each item also decides independently whether it fits, so a short item can stay
inline while a longer sibling breaks

shapes =
    [ { name = "circle", numberOfSides = 0, isFilled = True, colorValue = "red", strokeWidth = 2 }
    , { name = "triangle"
      , numberOfSides = 3
      , isFilled = False
      , colorValue = "blue"
      , strokeWidth = 1
      }
    ]

Question 5 - Multi-line block comments are re-indented

The body of a multi-line block comment is re-indented from the comment's own
structure: the shallowest body line is aligned three columns past the {-
(under the first character after {- ), and any deeper line keeps its extra
indentation relative to that.

This is straight-forward. However the body was written:

config =
        {- the body of this block comment
              spans several lines
           and is re-indented -}
        42

it comes out anchored to the {-, independent of the original columns:

config =
    {- the body of this block comment
          spans several lines
       and is re-indented -}
    42

However, there are cases where the 2nd line (and beyond) of the multi-line
comment start at a column before the "{-".
Here the opener is indented but the body lines start flush at the left margin:

config =
        {- this comment opener is indented
but the body lines are written flush at the left margin
   and this one is a little deeper -}
        42

The body is still pulled in and anchored three columns past the {-, with the
deeper line kept deeper:

config =
    {- this comment opener is indented
       but the body lines are written flush at the left margin
          and this one is a little deeper -}
    42

Is this what we want? or, do we keep the comments at the absolute starting
column position at which the author wrote them?

Question 6 - Binop chains break strangely

Here is something I just noticed.
A binop chain that overflows breaks the first operand(s) onto their own lines but
then leaves the remaining operands crammed onto one line, with operators at the
seed column (not indented):

binopChain =
    operandOne
    + operandTwo
    + operandThree + operandFour + operandFive + operandSix + operandSeven + operandEight

I think this should change to look more like how a long function call wraps:

binopChain =
    operandOne + operandTwo operandThree + operandFour + operandFive + operandSix
        + operandSeven + operandEight

or

binopChain =
    operandOne + operandTwo operandThree + operandFour + operandFive + operandSix +
        operandSeven + operandEight

Where does the binop go?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions