sorawee / fmt

A code formatter for Racket
Other
72 stars 6 forks source link

Permit multiple formats for the same form #20

Closed default-kramer closed 2 years ago

default-kramer commented 2 years ago

First of all, thanks for doing this project. For me, DrRacket's formatting is what I want 99% of the time, but every now and then I wish I could customize it. I think this project might be able to grow into my preferred solution.

=========

Consider the following example file

#lang racket

(define x (if #t 'foo 'bar))
(define y (if #f
              'foo
              'bar))

When I run DrRacket's indenter on this, nothing changes. This is the behavior I want. The rationale is that I added those newlines deliberately and unless I am breaking some rule I would like to keep them where they are. (Where "breaking some rule" means exceeding the width, incorrect indentation, or something like that.) But when I run fmt, the output is

#lang racket

(define x (if #t 'foo 'bar))
(define y (if #f 'foo 'bar))

I read the docs about formatters and I'm not sure if it is possible/easy to customize fmt to work like DrRacket for this example. Would this be implemented as a single formatter for the if form? Or maybe the formatter map should map if to a list of formatters. (But then how would it decide which one to use when the input doesn't match any of them?)

Is what I want to do even philosophically compatible with your project goals? Perhaps an "indenter" and a "formatter" are two different things, and I want an indenter.

PS - I'm sure I can find a non-contrived example from real code if you're wondering "why do you want to do this?"

sorawee commented 2 years ago

Yes, the indenter and formatter are different. An indenter will never insert or remove a newline. A formatter on the other hand aims to produce code with minimal height, subjected to the constraint that the code fit the page width limit. So it could insert a newline if your original code is too wide, or remove a newline if doing so will reduce the number of lines.

If you want a customizable indenter, both DrRacket and Racket Mode are customizable in different aspect. DrRacket allows you to use regexp to match forms to indent "begin-like keyword", "define-like keyword", "lambda-like keyword", and "for/fold-like keyword" (Preferences > Editing > Indenting). On the other hand, Racket Mode allows you to create a new keyword "category".

Currently it's not possible for fmt to do what you want, and you intuited correctly that it's somewhat not compatible with the project goal.

But theoretically, fmt is also flexible enough to do what you want (at least for this specific case). As you mentioned, you can create your own if-formatter. The code fragment that is the input to formatters ideally should contain the source locations (right now I haven't implemented this feature -- see #14). With the source locations, you can check if the "conditional" and the "branches" are on the same line. Depending on the answer, you would format differently.

Also note that while one newline is insignificant in fmt, two or more newlines are not (this is the what most code formatters for most languages do). fmt will try to preserve these significant newlines. For example, currently fmt formats

#lang racket

(
if test

hello

world
)

to:

#lang racket

(if test

    hello

    world)
default-kramer commented 2 years ago

Thank you for the detailed explanation. It seems what I currently want is an indenter with maybe some very light formatting capabilities. Still, the idea of a standard formatter is intriguing (the Go community seems to universally love gofmt).