Ralph
Ralph

Reputation: 32304

Clojure indenter that works

I am looking for an editor/command line program that properly (IMHO) indents Clojure code, aligning the third and subsequent elements with the second element:

(let [foo (fun arg1
               arg2)

      bar (doto (Baz.)
                (.setProp abc123))

      [v1 v2] (fun2)

      [v3 v4] (fun3)]
  (xyzzy 42))

I have tried La Clojure for IntelliJ with the "Align Clojure forms by the second element" (does not align by the second element), VimClojure, emacs (messed up the indentation of the [v3 v4] part), Enclojure in Netbeans (does not align by the second element), and Counterclockwise in Eclipse (does not align by the second element). In each case, I am using the latest version of the software.

I can run a beautifier outside of the editor (on the command line) if necessary.

Here is the output of Aquamacs. Note the "cascading" (make-day-panel) calls:

(defn make-mainframe [build-number project-version]
  "Create the mainframe.
  @param build-number the current build number
  @param project-version the Maven project version
  @returns the reified JFrame"
  (let [[^JToggleButton day-sheets-button
         ^JToggleButton summaries-button]
        (let [day-sheets-button
              (doto (JToggleButton. "Day Sheets")
                (.setToolTipText
                 "Switch to the Day Sheets view"))

              summaries-button
              (doto (JToggleButton. "Summaries")
                (.setToolTipText "Switch to the Summaries view"))]
          (doto (ButtonGroup.)
            (.add day-sheets-button)
            (.add summaries-button))
          (.setSelected day-sheets-button true)
          [day-sheets-button summaries-button])

        [^JPanel monday-panel
         monday-button
         monday-vacation-check
         monday-arrive-text-field
         monday-depart-text-field] (make-day-panel "Monday")

         [^JPanel tuesday-panel
          tuesday-button
          tuesday-vacation-check
          tuesday-arrive-text-field
          tuesday-depart-text-field] (make-day-panel "Tuesday")

          [^JPanel wednesday-panel
           wednesday-button
           wednesday-vacation-check
           wednesday-arrive-text-field
           wednesday-depart-text-field] (make-day-panel "Wednesday")

           [^JPanel thursday-panel
            thursday-button
            thursday-vacation-check
            thursday-arrive-text-field
            thursday-depart-text-field] (make-day-panel "Thursday")

            [^JPanel friday-panel
             friday-button
             friday-vacation-check
             friday-arrive-text-field
             friday-depart-text-field] (make-day-panel "Friday")

             [^JPanel saturday-panel
              saturday-button
              saturday-vacation-check
              saturday-arrive-text-field
              saturday-depart-text-field] (make-day-panel "Saturday")

              [^JPanel sunday-panel
               sunday-button
               sunday-vacation-check
               sunday-arrive-text-field
               sunday-depart-text-field] (make-day-panel "Sunday")

        #_ [week-selector-panel week-selector-combobox week-selector-today-button]
        #_ (make-week-selector)

        north-panel
        (let [^JPanel panel (JPanel.)]
          (doto panel
            (.setLayout (BoxLayout. panel BoxLayout/X_AXIS))
            (.add day-sheets-button)
            (.add (Box/createHorizontalStrut 5))
            (.add summaries-button)
            (.add (Box/createHorizontalGlue))
            #_ (.add week-selector-panel)))

        ^JToggleButton mail-button
        (make-button "Mail Report"
                     (make-icon "email-32x32-plain.png")
                     "Generate and send a weekly report for the currently selected week")

        ^JToggleButton report-button
        (make-button "Report"
                     (make-icon "history2-32x32-plain.png")
                     "Generate a weekly report for the currently selected week")

        day-sheet-panel
        (let [panel (JPanel. (BorderLayout.))

              north-panel (grid-bag-container (JPanel. (GridBagLayout.))
                                              {:insets (Insets. 2 2 2 2)}
                                              [[(doto (JPanel. (GridLayout. 1 0))
                                                  (.add report-button)
                                                  (.add mail-button))]
                                               [(Box/createHorizontalGlue)
                                                :weightx 1.0 :fill :HORIZONTAL ]
                                               [(doto (JPanel. (GridLayout. 1 0))
                                                  (.add monday-panel)
                                                  (.add tuesday-panel)
                                                  (.add wednesday-panel)
                                                  (.add thursday-panel)
                                                  (.add friday-panel)
                                                  (.add saturday-panel)
                                                  (.add sunday-panel))]])]
          (doto panel
            (.add north-panel BorderLayout/NORTH)
            (.setBorder (BorderFactory/createRaisedBevelBorder))))

        summaries-panel (let [panel (JPanel.)]
                          (doto panel))

        card-panel (let [panel (JPanel. (CardLayout.))]
                     (doto panel
                       (.add day-sheet-panel day-sheet-panel-key)
                       (.add summaries-panel summaries-panel-key)))

        main-panel (doto (JPanel. (BorderLayout.))
                     (.setBorder
                      (BorderFactory/createEmptyBorder default-border default-border
                                                       default-border default-border))
                     (.add north-panel BorderLayout/NORTH)
                     (.add card-panel))

        frame (doto (JFrame. (str "SpareTime (version " project-version ", build "
                                  build-number ")"))
                (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
                (.add main-panel))]
    (reify mainview-methods
      (frame-add-component-listener [_ listener] (.addComponentListener frame listener))
      (frame-center [_] (.setLocationRelativeTo frame nil))
      (frame-get-location [_] (.getLocation frame))
      (frame-get-size [_] (.getSize frame))
      (frame-set-location [_ x y] (.setLocation frame x y))
      (frame-set-size [_ width height] (.setSize frame (Dimension. width height)))
      (frame-set-visible [_ visible] (.setVisible frame visible)))))

Upvotes: 2

Views: 674

Answers (4)

holographic-principle
holographic-principle

Reputation: 19738

LightTable indents it the same way as Emacs in the example from Joost's post:

(let [foo (fun arg1
               arg2)

      bar (doto (Baz.)
            (.setProp abc123))

      [v1 v2] (fun2)

      [v3 v4] (fun3)]
  (xyzzy 42))

Pretty good if you ask me. From my experience, it works better than IDEA, Sublime Text or Netbeans. Worth checking out if you're not a Vim / Emacs user. It's still in an early phase, but shows a lot of promise. And it was designed with Clojure in mind.

Upvotes: 0

kotarak
kotarak

Reputation: 17299

VimClojure will indent the way you want (if I understand your example correctly), when you remove doto from lispwords.

setlocal lispwords-=doto

Upvotes: 1

amalloy
amalloy

Reputation: 92147

As far as I can tell the Emacs indenter doesn't understand what's going on if you start an entirely-new sexp on the same line as an indented continuation, which is what your let above does. You see a similar problem with

(if (< 1 (- 3
            1) 3
            4))

Of course, writing that to begin with is not very good style, so perhaps Emacs can be excused. Code shaped like this is more likely to appear in let or cond in Clojure, as you noted, and it's not bad style - but the Emacs indenter was designed for other lisps, which require each pair to be wrapped in parentheses:

(let [([x
        y
        z] (foo))
      ([a
        b
        c] (bar))]
  ...)

And as you can see, that indents fine. So I think at some point someone will have to figure out how to update the Emacs indenter for this case, and that this will be way easier than getting some other editor up to Emacs's level. Perhaps that person should be you, if it's bothering you - the magic and the burden of open-source, I suppose.

Upvotes: 1

Joost Diepenmaat
Joost Diepenmaat

Reputation: 17761

As far as I can tell, current clojure-mode indents like you want except that it handles doto as special (that is, it indents from the third form onwards to make the "target" stand out more).

Output for indent-sexp on my emacs 24 with clojure-mode from marmalade:

(let [foo (fun arg1
               arg2)

      bar (doto (Baz.)
            (.setProp abc123))

      [v1 v2] (fun2)

      [v3 v4] (fun3)]
  (xyzzy 42))

I think you've either got a broken clojure-mode, or aquamacs is being different and difficult again. I really recommend using standard Gnu emacs on OSX - as long as you map meta to something other than command, you can use most of the simple standard osx key combos (command-c, command-v, command-s etc) in standard emacs.

Addendum: output for that large code block (note correct placement of make-day-panel calls):

(defn make-mainframe [build-number project-version]
  "Create the mainframe.
  @param build-number the current build number
  @param project-version the Maven project version
  @returns the reified JFrame"
  (let [[^JToggleButton day-sheets-button
         ^JToggleButton summaries-button]
        (let [day-sheets-button
              (doto (JToggleButton. "Day Sheets")
                (.setToolTipText
                 "Switch to the Day Sheets view"))

              summaries-button
              (doto (JToggleButton. "Summaries")
                (.setToolTipText "Switch to the Summaries view"))]
          (doto (ButtonGroup.)
            (.add day-sheets-button)
            (.add summaries-button))
          (.setSelected day-sheets-button true)
          [day-sheets-button summaries-button])

        [^JPanel monday-panel
         monday-button
         monday-vacation-check
         monday-arrive-text-field
         monday-depart-text-field] (make-day-panel "Monday")

        [^JPanel tuesday-panel
         tuesday-button
         tuesday-vacation-check
         tuesday-arrive-text-field
         tuesday-depart-text-field] (make-day-panel "Tuesday")

        [^JPanel wednesday-panel
         wednesday-button
         wednesday-vacation-check
         wednesday-arrive-text-field
         wednesday-depart-text-field] (make-day-panel "Wednesday")

        [^JPanel thursday-panel
         thursday-button
         thursday-vacation-check
         thursday-arrive-text-field
         thursday-depart-text-field] (make-day-panel "Thursday")

        [^JPanel friday-panel
         friday-button
         friday-vacation-check
         friday-arrive-text-field
         friday-depart-text-field] (make-day-panel "Friday")

        [^JPanel saturday-panel
         saturday-button
         saturday-vacation-check
         saturday-arrive-text-field
         saturday-depart-text-field] (make-day-panel "Saturday")

        [^JPanel sunday-panel
         sunday-button
         sunday-vacation-check
         sunday-arrive-text-field
         sunday-depart-text-field] (make-day-panel "Sunday")

        #_ [week-selector-panel week-selector-combobox week-selector-today-button]
        #_ (make-week-selector)

        north-panel
        (let [^JPanel panel (JPanel.)]
          (doto panel
            (.setLayout (BoxLayout. panel BoxLayout/X_AXIS))
            (.add day-sheets-button)
            (.add (Box/createHorizontalStrut 5))
            (.add summaries-button)
            (.add (Box/createHorizontalGlue))
            #_ (.add week-selector-panel)))

        ^JToggleButton mail-button
        (make-button "Mail Report"
                     (make-icon "email-32x32-plain.png")
                     "Generate and send a weekly report for the currently selected week")

        ^JToggleButton report-button
        (make-button "Report"
                     (make-icon "history2-32x32-plain.png")
                     "Generate a weekly report for the currently selected week")

        day-sheet-panel
        (let [panel (JPanel. (BorderLayout.))

              north-panel (grid-bag-container (JPanel. (GridBagLayout.))
                                              {:insets (Insets. 2 2 2 2)}
                                              [[(doto (JPanel. (GridLayout. 1 0))
                                                  (.add report-button)
                                                  (.add mail-button))]
                                               [(Box/createHorizontalGlue)
                                                :weightx 1.0 :fill :HORIZONTAL ]
                                               [(doto (JPanel. (GridLayout. 1 0))
                                                  (.add monday-panel)
                                                  (.add tuesday-panel)
                                                  (.add wednesday-panel)
                                                  (.add thursday-panel)
                                                  (.add friday-panel)
                                                  (.add saturday-panel)
                                                  (.add sunday-panel))]])]
          (doto panel
            (.add north-panel BorderLayout/NORTH)
            (.setBorder (BorderFactory/createRaisedBevelBorder))))

        summaries-panel (let [panel (JPanel.)]
                          (doto panel))

        card-panel (let [panel (JPanel. (CardLayout.))]
                     (doto panel
                       (.add day-sheet-panel day-sheet-panel-key)
                       (.add summaries-panel summaries-panel-key)))

        main-panel (doto (JPanel. (BorderLayout.))
                     (.setBorder
                      (BorderFactory/createEmptyBorder default-border default-border
                                                       default-border default-border))
                     (.add north-panel BorderLayout/NORTH)
                     (.add card-panel))

        frame (doto (JFrame. (str "SpareTime (version " project-version ", build "
                                  build-number ")"))
                (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
                (.add main-panel))]
    (reify mainview-methods
      (frame-add-component-listener [_ listener] (.addComponentListener frame listener))
      (frame-center [_] (.setLocationRelativeTo frame nil))
      (frame-get-location [_] (.getLocation frame))
      (frame-get-size [_] (.getSize frame))
      (frame-set-location [_ x y] (.setLocation frame x y))
      (frame-set-size [_ width height] (.setSize frame (Dimension. width height)))
      (frame-set-visible [_ visible] (.setVisible frame visible)))))

Upvotes: 2

Related Questions