Reputation: 32304
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
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
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
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
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