Pretty Arrows

A mix format plugin that lines up the parts of your code that are easiest to read in columns: the arrows in with clauses, the options in Ecto field declarations, and the arrows in one-line case clauses.

Installation

Add pretty_arrows to your dependencies in mix.exs:

def deps do
  [
    {:pretty_arrows, "~> 0.4.0", only: [:dev, :test], runtime: false}
  ]
end

Then fetch it:

mix deps.get

Setup

Add the plugin to the :plugins list in .formatter.exs:

[
  plugins: [PrettyArrows],
  inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

mix format now aligns with clauses and Ecto field declarations.

Plugins

Module Aligns Options
PrettyArrows with clauses, Ecto fields, case clauses :with, :ecto, :case
WithFormatter with clauses only :with
EctoFieldFormatter Ecto fields only :ecto
CaseFormatter case clauses only :case

Use PrettyArrows for all three alignments. Use one of the others if you only want that feature. Each plugin reads only its own section of :pretty_arrows.

Examples

with clauses

Input:

with a_result <- a_func(),
     {:ok, b_result_with_a_longer_name} <- b_func(),
     c <- c_func() do
  {:ok, c}
end

Output, with the default padding: :left:

with                           a_result <- a_func(),
     {:ok, b_result_with_a_longer_name} <- b_func(),
                                      c <- c_func() do
  {:ok, c}
end

Set padding: :right to pad after each pattern instead:

with a_result                           <- a_func(),
     {:ok, b_result_with_a_longer_name} <- b_func(),
     c                                  <- c_func() do
  {:ok, c}
end
[
  plugins: [PrettyArrows],
  pretty_arrows: [with: [padding: :right]]
]

Ecto fields

Input:

schema "users" do
  field :id, :uuid
  field :name, :string
  field :nowish, :datetime
  field :c, :enum
end

Output, with the defaults (parens: false, columns: 2):

schema "users" do
  field :id,     :uuid
  field :name,   :string
  field :nowish, :datetime
  field :c,      :enum
end

Keep the parentheses around the arguments:

[
  plugins: [PrettyArrows],
  pretty_arrows: [ecto: [parens: true]]
]

Align more columns. With columns: 3, the options after the type line up too:

schema "users" do
  field :id,         :uuid,                    primary_key: true
  field :MinCreated, MyType.Util.Type.Integer, primary_key: true
  field :name,       :string,                  null: false
end
[
  plugins: [PrettyArrows],
  pretty_arrows: [ecto: [columns: 3]]
]

Trailing # comments on a field line stay on that line, and comment lines between fields don't break the alignment.

case clauses

Two or more one-line clauses in a case block get their -> arrows lined up. Clauses whose body is on the following lines are left alone.

Input:

case my_fun() do
  {:ok, result_a} -> MyMod.a_fun(result_a)
  {:ok, results_for_b} -> MyMod.b_other_fun(results_for_b)
  {:error, reason} -> MyMod.error_handler(reason)
end

Output:

case my_fun() do
  {:ok, result_a}      -> MyMod.a_fun(result_a)
  {:ok, results_for_b} -> MyMod.b_other_fun(results_for_b)
  {:error, reason}     -> MyMod.error_handler(reason)
end
[
  plugins: [PrettyArrows]
]

Use CaseFormatter as the plugin to get only this alignment. To keep PrettyArrows but turn off case alignment, set pretty_arrows: [case: [enabled: false]].

Only code is aligned. Lines inside heredocs, arrows inside string literals, and -> in fn bodies inside a clause don't count as clauses.

Configuration

PrettyArrows reads all of its options from a single :pretty_arrows key. Every option is shown with its default:

[
  plugins: [PrettyArrows],
  pretty_arrows: [
    with: [
      enabled: true,      # align `with` clauses
      padding: :left      # :left or :right
    ],
    ecto: [
      enabled: true,      # align Ecto `field`s
      parens: false,      # keep parentheses around field arguments
      columns: 2          # how many argument columns to align
    ],
    case: [
      enabled: true       # align one-line `case` clause arrows
    ]
  ]
]
Option Default Accepted values
with: [enabled:] true true, false
with: [padding:] :left :left, :right
ecto: [enabled:] true true, false
ecto: [parens:] false true, false
ecto: [columns:] 2 a positive integer
case: [enabled:] true true, false

Unknown options and invalid values raise an ArgumentError that names the offending key, so a typo fails the format run instead of being ignored:

** (ArgumentError) unknown option(s) [:padding_typo] in pretty_arrows.with, expected one of [:enabled, :padding]

How it works

The plugin first runs the standard Code.format_string!/2 formatter, then works on the formatted text one line at a time:

Heredoc contents (""" blocks) are never aligned, so with, field, and case text inside test fixtures is left as written.

Because it works on the text line by line, each clause or field must start on its own line for it to align. Layouts that put several clauses on one line are formatted by mix format but not aligned.

Migrating from 0.2.x

Version 0.3.0 moves every option under :pretty_arrows and removes the old spellings. Old top-level keys raise an error that points here.

0.2.x 0.3.0
with_padding: :right pretty_arrows: [with: [padding: :right]]
with: false pretty_arrows: [with: [enabled: false]]
ecto: false pretty_arrows: [ecto: [enabled: false]]
ecto: [parens: true] pretty_arrows: [ecto: [parens: true]]
ecto: [columns: 3] pretty_arrows: [ecto: [columns: 3]]
prettyarrows: [with: [padding: :right], ecto: [parens: true]] pretty_arrows: [with: [padding: :right], ecto: [parens: true]]
ecto: [fields: [parens: true]] pretty_arrows: [ecto: [parens: true]]

String values such as padding: "right" and enabled: "false" are no longer accepted. Use atoms and booleans.

License

MIT