Skip to content

Commit f678b91

Browse files
committed
Make ElixirFmt ANSI colours configurable
1 parent e1fbf75 commit f678b91

File tree

4 files changed

+95
-34
lines changed

4 files changed

+95
-34
lines changed

lib/gradient/elixir_expr.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ defmodule Gradient.ElixirExpr do
1111
@doc """
1212
Convert abstract expressions to Elixir code and format output with formatter.
1313
"""
14-
@spec pp_expr_format([expr()], keyword()) :: iodata()
14+
@spec pp_expr_format(expr() | [expr()], keyword()) :: iodata()
1515
def pp_expr_format(exprs, fmt_opts \\ []) do
1616
exprs
1717
|> pp_expr()

lib/gradient/elixir_fmt.ex

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,40 @@
11
defmodule Gradient.ElixirFmt do
22
@moduledoc """
3-
Module that handles formatting and printing error messages produced by Gradient in Elixir.
3+
Module that handles formatting and printing error messages produced by Gradualizer in Elixir.
4+
5+
Options:
6+
- `ex_colors`: list of color options:
7+
- {`use_colors`, boolean()}: - wheather to use the colors, default: true
8+
- {`expression`, ansicode()}: color of the expressions, default: :yellow
9+
- {`type`, ansicode()}: color of the types, default: :cyan
10+
- {`underscored_line`, ansicode()}: color of the underscored line pointed the error in code, default: :red
11+
12+
- `ex_fmt_expr_fun`: function to pretty print an expression AST in Elixir `(abstract_expr()) -> iodata()`.
13+
14+
- `ex_fmt_type_fun`: function to pretty print an type AST in Elixir `(abstract_type() -> iodata())`.
15+
16+
- Gradualizer options, but some of them are overwritten by Gradient.
417
"""
518
@behaviour Gradient.Fmt
619

720
alias :gradualizer_fmt, as: FmtLib
821
alias Gradient.ElixirType
922
alias Gradient.ElixirExpr
23+
alias Gradient.Types
24+
25+
@type colors_opts() :: [
26+
use_colors: boolean(),
27+
expression: IO.ANSI.ansicode(),
28+
type: IO.ANSI.ansicode(),
29+
underscored_line: IO.ANSI.ansicode()
30+
]
31+
@type options() :: [
32+
ex_colors: colors_opts(),
33+
ex_fmt_type_fun: (Types.abstract_type() -> iodata()),
34+
ex_fmt_expr_fun: (Types.abstract_expr() -> iodata())
35+
]
36+
37+
@default_colors [use_colors: true, expression: :yellow, type: :cyan, underscored_line: :red]
1038

1139
def print_errors(errors, opts) do
1240
for {file, e} <- errors do
@@ -29,8 +57,9 @@ defmodule Gradient.ElixirFmt do
2957
end
3058

3159
def format_error(error, opts) do
32-
opts = Keyword.put_new(opts, :fmt_type_fun, &ElixirType.pretty_print/1)
33-
opts = Keyword.put_new(opts, :fmt_expr_fun, &ElixirExpr.pp_expr/1)
60+
opts = Keyword.put_new(opts, :color, false)
61+
opts = Keyword.put_new(opts, :fmt_type_fun, pp_type_fun(opts))
62+
opts = Keyword.put_new(opts, :fmt_expr_fun, pp_expr_fun(opts))
3463
format_type_error(error, opts)
3564
end
3665

@@ -120,7 +149,7 @@ defmodule Gradient.ElixirFmt do
120149
def format_expr_type_error(expression, actual_type, expected_type, opts) do
121150
{inline_expr, fancy_expr} =
122151
case try_highlight_in_context(expression, opts) do
123-
{:error, _e} -> {" " <> pp_expr(expression, opts), ""}
152+
{:error, _e} -> {[" " | pp_expr(expression, opts)], ""}
124153
{:ok, fancy} -> {"", fancy}
125154
end
126155

@@ -145,34 +174,56 @@ defmodule Gradient.ElixirFmt do
145174
end
146175
end
147176

148-
def pp_expr(expression, opts) do
149-
fmt = Keyword.get(opts, :fmt_expr_fun, &ElixirExpr.pp_expr/1)
177+
def pp_expr_fun(opts) do
178+
fmt = Keyword.get(opts, :ex_fmt_expr_fun, &ElixirExpr.pp_expr_format/1)
179+
colors = get_colors_with_default(opts)
180+
{:ok, use_colors} = Keyword.fetch(colors, :use_colors)
181+
{:ok, expr_color} = Keyword.fetch(colors, :expression)
150182

151-
if Keyword.get(opts, :colors, true) do
152-
IO.ANSI.blue() <> fmt.(expression) <> IO.ANSI.reset()
153-
else
154-
fmt.(expression)
183+
fn expression ->
184+
IO.ANSI.format([expr_color, fmt.(expression)], use_colors)
155185
end
156186
end
157187

158-
def pp_type(type, opts) do
159-
fmt = Keyword.get(opts, :fmt_type_fun, &ElixirType.pretty_print/1)
188+
def pp_type_fun(opts) do
189+
fmt = Keyword.get(opts, :ex_fmt_type_fun, &ElixirType.pp_type_format/1)
190+
colors = get_colors_with_default(opts)
191+
{:ok, use_colors} = Keyword.fetch(colors, :use_colors)
192+
{:ok, type_color} = Keyword.fetch(colors, :type)
160193

161-
if Keyword.get(opts, :colors, true) do
162-
IO.ANSI.cyan() <> fmt.(type) <> IO.ANSI.reset()
163-
else
164-
fmt.(type)
194+
fn type ->
195+
IO.ANSI.format([type_color, fmt.(type)], use_colors)
165196
end
166197
end
167198

199+
def get_colors_with_default(opts) do
200+
case Keyword.fetch(opts, :ex_colors) do
201+
{:ok, colors} ->
202+
colors ++ @default_colors
203+
204+
_ ->
205+
@default_colors
206+
end
207+
end
208+
209+
def pp_expr(expression, opts) do
210+
pp_expr_fun(opts).(expression)
211+
end
212+
213+
def pp_type(type, opts) do
214+
pp_type_fun(opts).(type)
215+
end
216+
217+
@spec try_highlight_in_context(Types.abstract_expr(), options()) ::
218+
{:ok, iodata()} | {:error, term()}
168219
def try_highlight_in_context(expression, opts) do
169220
forms = Keyword.get(opts, :forms)
170221

171222
with :ok <- has_location?(expression),
172223
{:ok, path} <- get_ex_file_path(forms),
173224
{:ok, code} <- File.read(path) do
174225
code_lines = String.split(code, ~r/\R/)
175-
{:ok, highlight_in_context(expression, code_lines)}
226+
{:ok, highlight_in_context(expression, code_lines, opts)}
176227
end
177228
end
178229

@@ -184,14 +235,14 @@ defmodule Gradient.ElixirFmt do
184235
end
185236
end
186237

187-
@spec highlight_in_context(tuple(), [String.t()]) :: String.t()
188-
def highlight_in_context(expression, context) do
238+
@spec highlight_in_context(tuple(), [String.t()], options()) :: iodata()
239+
def highlight_in_context(expression, context, opts) do
189240
line = elem(expression, 1)
190241

191242
context
192243
|> Enum.with_index(1)
193244
|> filter_context(line, 2)
194-
|> underscore_line(line)
245+
|> underscore_line(line, opts)
195246
|> Enum.join("\n")
196247
end
197248

@@ -202,10 +253,19 @@ defmodule Gradient.ElixirFmt do
202253
Enum.filter(lines, fn {_, number} -> number in range end)
203254
end
204255

205-
def underscore_line(lines, line) do
256+
def underscore_line(lines, line, opts) do
206257
Enum.map(lines, fn {str, n} ->
207258
if(n == line) do
208-
IO.ANSI.underline() <> IO.ANSI.red() <> to_string(n) <> " " <> str <> IO.ANSI.reset()
259+
colors = get_colors_with_default(opts)
260+
{:ok, use_colors} = Keyword.fetch(colors, :use_colors)
261+
{:ok, color} = Keyword.fetch(colors, :underscored_line)
262+
line_str = to_string(n) <> " " <> str
263+
264+
[
265+
IO.ANSI.underline(),
266+
IO.ANSI.format_fragment([color, line_str], use_colors),
267+
IO.ANSI.reset()
268+
]
209269
else
210270
to_string(n) <> " " <> str
211271
end
@@ -225,11 +285,6 @@ defmodule Gradient.ElixirFmt do
225285
end
226286
end
227287

228-
# defp warning_error_not_handled(error) do
229-
# msg = "\nElixir formatter not exist for #{inspect(error, pretty: true)} using default \n"
230-
# String.to_charlist(IO.ANSI.light_yellow() <> msg <> IO.ANSI.reset())
231-
# end
232-
233288
@spec describe_expr(:gradualizer_type.abstract_expr()) :: binary()
234289
def describe_expr({:atom, _, _}), do: "atom"
235290
def describe_expr({:bc, _, _, _}), do: "binary comprehension"

lib/gradient/elixir_type.ex

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
defmodule Gradient.ElixirType do
22
@moduledoc """
3-
Module to format types.
4-
5-
Seems that:
6-
- record type
7-
- constrained function type
8-
are not used by Elixir so the pp support has not been added.
3+
Convert the Erlang abstract types to the Elixir code.
94
"""
105

116
alias Gradient.ElixirFmt
127

138
@type abstract_type() :: Gradient.Types.abstract_type()
149

10+
@doc """
11+
Convert abstract type to Elixir code and format output with formatter.
12+
"""
13+
@spec pp_type_format(abstract_type(), keyword()) :: iodata()
14+
def pp_type_format(type, fmt_opts \\ []) do
15+
type
16+
|> pretty_print()
17+
|> Code.format_string!(fmt_opts)
18+
end
19+
1520
@doc """
1621
Take type and prepare a pretty string representation.
1722
"""

lib/gradient/types.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ defmodule Gradient.Types do
22
@type token :: tuple()
33
@type tokens :: [tuple()]
44
@type abstract_type :: :erl_parse.abstract_type()
5+
@type abstract_expr :: :erl_parse.abstract_expr()
56
@type form ::
67
:erl_parse.abstract_clause()
78
| :erl_parse.abstract_expr()

0 commit comments

Comments
 (0)