DrRacket's auto-indentation outside DrRacket?

497 views
Skip to first unread message

David Christiansen

unread,
Aug 25, 2016, 4:52:50 PM8/25/16
to racket-users@googlegroups.com List
Hi all,

As far as I know, the standard for indentation in Racket is "Do like
DrRacket", after DrRacket has been suitably configured for new syntax
introduced by the application in question.

I'd like to arrange for this to be enforced by Travis. As far as I can
see, my building blocks for this are the following:

1. A way to represent indentation specifications for new syntactic forms

2. A way to invoke automatic indentation on a file without running a
GUI, using these specifications in addition to the default rules

Is there a canonical way to achieve these things?

Thanks!

/David

Robby Findler

unread,
Aug 25, 2016, 5:24:23 PM8/25/16
to David Christiansen, racket-users@googlegroups.com List
I'm not sure about the suitable configuration: that should probably
happen via the #lang line and shouldn't be configured "from the
outside" (we're not quite there yet, but that's where we should be
heading, IMO).

But for point 2, here's a script. It depends on the GUI library.
Removing that dependency is possible, but probably a lot of work.

Robby


#lang racket/base
(require racket/gui/base
racket/port
racket/class
framework)

(define (indented-properly? port)
(define-values (out1 in1) (make-pipe))
(define-values (out2 in2) (make-pipe))
(thread (λ ()
(copy-port port in1 in2)
(close-output-port in1)
(close-output-port in2)))
(define (insert-it port chan)
(thread
(λ ()
(define t (new racket:text%))
(send t insert-port port)
(channel-put chan t))))
(define c1 (make-channel))
(define c2 (make-channel))
(insert-it out1 c1)
(insert-it out2 c2)
(define t1 (channel-get c1))
(define t2 (channel-get c2))

;; this is the important line
(send t1 tabify-all)

;; this should really compare snips, not characters
(and (= (send t1 last-position)
(send t2 last-position))
(for/and ([x (in-range (send t1 last-position))])
(equal? (send t1 get-character x)
(send t2 get-character x)))))

(define illindented-candidate
(string-append
"#lang racket\n"
"(define (f x)\n"
"x)"))
(define well-indented-candidate
(string-append
"#lang racket\n"
"(define (f x)\n"
" x)"))
(indented-properly? (open-input-string illindented-candidate))
(indented-properly? (open-input-string well-indented-candidate))
> --
> You received this message because you are subscribed to the Google Groups "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Robby Findler

unread,
Aug 25, 2016, 5:35:30 PM8/25/16
to David Christiansen, racket-users@googlegroups.com List
Oh, actually comparing characters is just fine, since tabify changes
only characters, not snips.

Robby

Dupéron Georges

unread,
Aug 25, 2016, 5:41:04 PM8/25/16
to Racket Users
I'll add a few relevant and less relevant references:

This discussion [1] is about extending drracket's built-in indenter, using `drracket:indentation` [2]. The #lang's get-info function should accept that symbol as a key, and return an indentation function.

To access the default indentation by drracket, it seems you could call `compute-racket-amount-to-indent` [3]. It accepts an optional argument `get-head-sexp-type` which should be a function mapping identifiers to one of #f 'lambda 'define 'begin 'for/fold 'other, where #f means that it will use the built-in list.

There was some discussion in [1] and in this mailing-list discussion [4] about the future possibility of using syntax properties or a similar mechanism to allow macros and other identifiers to specify their indentation.

[1] https://github.com/racket/drracket/issues/60
[2] http://docs.racket-lang.org/tools/adding-languages.html#%28idx._%28gentag._17._%28lib._scribblings%2Ftools%2Ftools..scrbl%29%29%29
[3] http://docs.racket-lang.org/framework/Racket.html#%28meth._%28%28%28lib._framework%2Fmain..rkt%29._racket~3atext~3c~25~3e%29._compute-racket-amount-to-indent%29%29
[4] https://groups.google.com/forum/#!msg/racket-users/mTXshH6eal0/u-OySmuuCwAJ

Matthias Felleisen

unread,
Aug 25, 2016, 5:47:21 PM8/25/16
to Dupéron Georges, Racket Users

I am skeptical of this and I would like us to not jump the gun.

Just today I adjusted my personal indentation style for universe. (It had somehow gotten lost.) It now indents properly the way its creator intended it to:

(universe the-small-thing
[on-new . . . ]
[on-msg . . . ])

before it indented it wrong.

If Travis re-indented my benchmarks/samples every time I commit one, I would be rather unhappy.

I would like us to wait until languages can choose an indentation style per construct.

Dupéron Georges

unread,
Aug 25, 2016, 6:00:04 PM8/25/16
to Racket Users, jahvascr...@gmail.com, matt...@ccs.neu.edu
Le jeudi 25 août 2016 23:47:21 UTC+2, Matthias Felleisen a écrit :
> If Travis re-indented my benchmarks/samples every time I commit one, I would be rather unhappy.

I think that's what David meant by "A way to represent indentation specifications for new syntactic forms": the idea would be to include in the repository a list of define-style, lambda-style, begin-style and for/fold-style keywords, so that the build knows how to indent project-specific and library-specific identifiers.

> I would like us to wait until languages can choose an indentation style per construct.

If I understood well, I think this can already be done with 'drracket:indentation to override drracket's indentation function (and with a bit of luck, Robby's script already takes that into account).

I didn't know this had been used in practice, but surprisingly (for me) github's search for `drracket:indentation` turned up several repositories:
* https://github.com/mbutterick/beautiful-racket
* scribble seems to use it
* a few other random repos

Matthias Felleisen

unread,
Aug 25, 2016, 6:02:13 PM8/25/16
to Dupéron Georges, Racket Users

The specification has to come with feature and/or the language, not the tool. How would Emacs know about it? Or Notepad? Every editor — and every tool in the tool chain — must know what indentation means if it may touch it.

Dupéron Georges

unread,
Aug 25, 2016, 6:17:41 PM8/25/16
to Racket Users, jahvascr...@gmail.com, matt...@ccs.neu.edu
Le vendredi 26 août 2016 00:02:13 UTC+2, Matthias Felleisen a écrit :
> The specification has to come with feature and/or the language, not the tool. How would Emacs know about it? Or Notepad? Every editor — and every tool in the tool chain — must know what indentation means if it may touch it.

I agree, it is unfortunate that for a language's get-info to respond to the `'drracket:indentation` it has to manipulate racket:text<%> among other things.

This could however be achieved (without popping a GUI) by an emacs mode communicating with Racket. If macros were allowed to specify how they are indented, the editor would need to run some racket code at some point, anyway (to expand the macro). It is already necessary to run some racket code to parse the surface syntax of the file, and know what parts are strings, what parts are identifiers etc. (the same file with #lang scribble and #lang racket won't indent in the same way, obviously).

I'm not sure if using racket:text<%> is that much of a concern in the end, though: any editor plugin could spawn a racket process, create an instance of racket:text<%> and communicate with it to get the indentation of any line it wants. The overhead does not seem that high, and it does not require a full-blown gui (the GTK libraries might be needed, but not a suitable display).

Alex Knauth

unread,
Aug 25, 2016, 6:32:33 PM8/25/16
to Dupéron Georges, Matthias Felleisen, Racket Users

> On Aug 25, 2016, at 6:17 PM, Dupéron Georges <jahvascr...@gmail.com> wrote:
>
> Le vendredi 26 août 2016 00:02:13 UTC+2, Matthias Felleisen a écrit :
>> The specification has to come with feature and/or the language, not the tool. How would Emacs know about it? Or Notepad? Every editor — and every tool in the tool chain — must know what indentation means if it may touch it.
>
> I agree, it is unfortunate that for a language's get-info to respond to the `'drracket:indentation` it has to manipulate racket:text<%> among other things.
>
> This could however be achieved ...

That wouldn't be enough, because even DrRacket's indentation working properly with a `racket:text<%>` and a language's get-info function isn't enough.

One thing we would still need would be a general way for libraries to specify indentation for functions and macros, so that when this library is required, any language and editor can look at the library's specification, even if neither the language nor the editor have any rules about this library.

How would a library express indentation rules? Would indentation rules meant for s-expression languages be useful in at-exp or sweet-exp notations?


David Christiansen

unread,
Aug 25, 2016, 10:51:02 PM8/25/16
to Robby Findler, racket-users@googlegroups.com List
Hi Robby,

Thank you very much for a fast and useful answer!

> I'm not sure about the suitable configuration: that should probably
> happen via the #lang line and shouldn't be configured "from the
> outside" (we're not quite there yet, but that's where we should be
> heading, IMO).

Today, this is done by setting individual preferences in DrRacket. Is
there a reasonably easy way to set these preferences for the
racket:text% ? It's clearly not a general solution, and I agree that
either the #lang line or the macro definition should be able to affect
these things, but in my case I've got a project that defines a couple
of macros, and I am happy to manually configure them in my CI setup. I
already have Emacs directory variables to set up their indentation for
racket-mode in Emacs, for instance.

> But for point 2, here's a script. It depends on the GUI library.
> Removing that dependency is possible, but probably a lot of work.

My test suite already depends on the GUI library, so that's no problem
at all for me.

Thanks again!

/David

David Christiansen

unread,
Aug 25, 2016, 11:02:04 PM8/25/16
to Alex Knauth, Dupéron Georges, Matthias Felleisen, Racket Users
>>> The specification has to come with feature and/or the language, not the tool. How would Emacs know about it? Or Notepad? Every editor — and every tool in the tool chain — must know what indentation means if it may touch it.

My goal here is not for Emacs to know how to indent things perfectly,
it is merely to ensure that Emacs or my merge tool have not made a
hash of it. Also, perhaps more importantly, it should ensure that
other contributors' editors haven't made a hash of it! This is a much
easier problem!

> How would a library express indentation rules? Would indentation rules meant for s-expression languages be useful in at-exp or sweet-exp notations?

At least in Elisp, indentation rules are specified by using a
"declare" form in a macro definition [1]. racket-mode for Emacs also
allows a similar customization of indentation by setting properties on
symbol plists, which provides something about as expressive as the
DrRacket configuration dialog. A good indenter for Racket would
probably need to macroexpand the source, but I imagine that something
like a designated submodule could contain indentation specifications
in a similar format.

[1]: https://www.gnu.org/software/emacs/manual/html_node/elisp/Indenting-Macros.html#Indenting-Macros

I don't know how to properly indent things from other readers, though.

/David

Robby Findler

unread,
Aug 25, 2016, 11:05:39 PM8/25/16
to David Christiansen, racket-users@googlegroups.com List
The code I sent would be influenced by the preferences I believe. But you could test that?

Robby

David Christiansen

unread,
Aug 25, 2016, 11:32:28 PM8/25/16
to Robby Findler, racket-users@googlegroups.com List
> The code I sent would be influenced by the preferences I believe. But you
> could test that?

I will, when I'm next on a machine with Racket. Is there a way to set
these preferences programmatically, though? I intend to run the code
in a VM that is created from scratch each time the tests are run (on
Travis), so scripting the setting of the preferences is pretty
important.

Thanks again!

/David

Robby Findler

unread,
Aug 26, 2016, 8:30:03 AM8/26/16
to David Christiansen, racket-users@googlegroups.com List
Here are the docs for the preferences system:

http://docs.racket-lang.org/framework/Preferences__Textual.html

You will want to look at the code in the framework for the specifics
of the preference you're after. It's name is 'framework:tabify and
these are the two files you want, I believe:

https://github.com/racket/gui/blob/master/gui-lib/framework/private/main.rkt

https://github.com/racket/gui/blob/master/gui-lib/framework/private/racket.rkt

Robby
Reply all
Reply to author
Forward
0 new messages