Skip to content

make syntax highlighting colors configurable - #37

Open
mvanhorn wants to merge 1 commit into
elixir-dbvisor:mainfrom
mvanhorn:fix/26-configurable-syntax-colors
Open

mvanhorn wants to merge 1 commit into
elixir-dbvisor:mainfrom
mvanhorn:fix/26-configurable-syntax-colors

Conversation

@mvanhorn

@mvanhorn mvanhorn commented Oct 4, 2026

Copy link
Copy Markdown

SQL.Format.syntax_color/1 reads :syntax_colors from the :sql application environment and turns a color name, or a list of them, into an ANSI fragment with IO.ANSI.format_fragment. Formatted SQL and the compile-time warnings that quote it always highlight keywords, literals, enclosed quotes, and errors with the same ANSI colors.

Tests for this live in test/syntax_colors_test.exs.

Closes #26

@Schultzer

Copy link
Copy Markdown
Member

@mvanhorn thanks for this PR, I think we can simplify this by using Application.get_compile_env in the module attributes instead of introduce new public functions.

Are you currently using this library?

@mvanhorn

mvanhorn commented Oct 5, 2026

Copy link
Copy Markdown
Author

Updated in 5fc9f84: syntax colors are now read with Application.compile_env in module attributes, and the public helper is gone. Defaults are unchanged, and the tests and README now describe the compile-time configuration. The full test suite passes locally.

To answer your question: no, I'm not using the library myself. I picked up #26 to contribute. If compile-time configuration isn't the direction you want, I'm happy to close this.

@Schultzer Schultzer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, we're almost there. I imagine this feature to be around 13 lines of additions.

Also @mvanhorn can you tell me how you work now a days, obvisouly there is an agent behind this, but I'm not sure if there is a human in this loop.

Comment thread lib/format.ex
Comment on lines -8 to -12
@error IO.ANSI.red()
@reset IO.ANSI.reset()
@keyword IO.ANSI.magenta()
@literal IO.ANSI.yellow()
@enclosed IO.ANSI.green()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to move the code around just change these to:

Suggested change
@error IO.ANSI.red()
@reset IO.ANSI.reset()
@keyword IO.ANSI.magenta()
@literal IO.ANSI.yellow()
@enclosed IO.ANSI.green()
@error Application.compile_env(:sql, [:syntax_colors, :error], IO.ANSI.red()))
@reset IO.ANSI.reset()
@keyword Application.compile_env(:sql, [:syntax_colors, :keyword], IO.ANSI.magenta()))
@literal Application.compile_env(:sql, [:syntax_colors, :literal], IO.ANSI.yellow()))
@enclosed Application.compile_env(:sql, [:syntax_colors, :enclosed], IO.ANSI.green()))

reset should not be configured.

Comment thread lib/format.ex Outdated
Comment on lines +76 to +78
newline(color(color, [keyword(case, unquote(atom)),?\s,keyword(case, atom)], [?\n|to_iodata(values, color, case, errors, indent+1, acc)]), indent)
else
newline([@keyword, keyword(case, unquote(atom)),?\s,keyword(case, atom),@reset|to_iodata(values, color, case, errors, indent, acc)], indent)
newline(color(color, [keyword(case, unquote(atom)),?\s,keyword(case, atom)], to_iodata(values, color, case, errors, indent, acc)), indent)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to change this.

Comment thread lib/inspect.ex
defmodule SQL.Inspect do
@moduledoc false

@error IO.ANSI.red()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@error IO.ANSI.red()
@error Application.compile_env(:sql, [:syntax_colors, :error], IO.ANSI.red()))
@reset IO.ANSI.reset()

reset should not be configured.

Comment thread test/syntax_colors_test.exs Outdated
Comment on lines +1 to +103
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2025 DBVisor

defmodule SQL.SyntaxColorsTest do
use ExUnit.Case, async: false

setup do
original = Application.fetch_env(:sql, :syntax_colors)

on_exit(fn ->
case original do
{:ok, colors} -> Application.put_env(:sql, :syntax_colors, colors)
:error -> Application.delete_env(:sql, :syntax_colors)
end

recompile()
end)

Application.delete_env(:sql, :syntax_colors)
recompile()
:ok
end

test "default palette" do
formatted = formatted("select +1000", true)
assert formatted =~ "\e[35mselect\e[0m"
assert formatted =~ "\e[33m1000\e[0m"
end

test "partial override keeps unspecified roles" do
configure(keyword: :cyan)
formatted = formatted("select +1000", true)
assert formatted =~ "\e[36mselect\e[0m"
assert formatted =~ "\e[33m1000\e[0m"
end

test "runtime changes do not affect compiled colors" do
configure(keyword: [:bright, :cyan])
Application.put_env(:sql, :syntax_colors, keyword: :blue)
assert formatted("select +1000", true) =~ "\e[1m\e[36mselect\e[0m"
end

test "enclosed quotes" do
assert formatted("select 'abc'", true) =~ "\e[32mabc\e[0m"

configure(enclosed: :cyan)
assert formatted("select 'abc'", true) =~ "\e[36mabc\e[0m"
end

test "color false emits no ansi" do
configure(keyword: :cyan, literal: :blue)
formatted = formatted("select +1000", false)
refute formatted =~ "\e["
end

test "error nodes" do
{context, tokens} = parse("select users")
[{:select, _, [ident]}] = tokens
context = %{context | errors: [ident]}
formatted = IO.iodata_to_binary(SQL.Format.to_iodata(tokens, context, 0, true))
assert formatted =~ "\e[31musers\e[0m"

configure(error: :blue)
formatted = IO.iodata_to_binary(SQL.Format.to_iodata(tokens, context, 0, true))
assert formatted =~ "\e[34musers\e[0m"

warning = IO.iodata_to_binary(SQL.Inspect.format_error([{:ident, [], "users"}]))
assert warning =~ "the relation \e[34musers\e[0m does not exist"
end

test "unknown color raises" do
assert_raise ArgumentError, fn ->
configure(keyword: :not_a_color)
end
end

defp configure(colors) do
Application.put_env(:sql, :syntax_colors, colors)
recompile()
end

defp recompile do
options = Code.compiler_options(ignore_module_conflict: true)

try do
Code.compile_file("lib/format.ex")
Code.compile_file("lib/inspect.ex")
after
Code.compiler_options(options)
end
end

defp parse(sql) do
{:ok, context, tokens} = SQL.Lexer.lex(sql)
{:ok, context, tokens} = SQL.Parser.parse(tokens, context)
{context, tokens}
end

defp formatted(sql, color) do
{context, tokens} = parse(sql)
IO.iodata_to_binary(SQL.Format.to_iodata(tokens, context, 0, color))
end
end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have tests for the inspect, so these test are not nedded, also they are testing Elixir more then our implementation.

Comment thread README.md Outdated
Comment on lines +122 to +131
## Syntax highlighting

Inspect output and compile-time warnings color keywords, literals, enclosed quotes, and errors. Override any of those roles from `config.exs`:

```elixir
config :sql, :syntax_colors, [keyword: :cyan, literal: :yellow, enclosed: :green, error: :red]
```

Each value is an `IO.ANSI` color name or a list of them. Roles you omit keep the defaults: magenta keywords, yellow literals, green enclosed quotes, and red errors. Colors are read when the SQL library compiles, so this setting belongs in `config.exs`; recompile the library after changing it (for a dependency, run `mix deps.compile sql --force`). Recompile callers with static `~SQL` sigils as well, since they store their inspect text at compilation.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to adjust these to the suggestions I gave above, we can also make this more consice, no need for the last paragraph.

@Schultzer

Schultzer commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

@mvanhorn please do me a favor and just amend the first commit in this PR with all the changes, no need for multiple commits for this feature.

@mvanhorn
mvanhorn force-pushed the fix/26-configurable-syntax-colors branch from e1e54f3 to e260f74 Compare October 6, 2026 17:19
@mvanhorn

mvanhorn commented Oct 6, 2026

Copy link
Copy Markdown
Author

@Schultzer Updated and squashed into the first commit, so the PR is now a single commit.

  • lib/format.ex and lib/inspect.ex use your compile_env suggestions in place (balanced parens), and reset is no longer configurable
  • the hunk at format.ex:78 is back to what it was
  • removed test/syntax_colors_test.exs
  • trimmed the README section to the config example and dropped the last paragraph

The code change is 5 lines now; with the README and CHANGELOG it's 22 additions. mix compile --warnings-as-errors and mix test are clean (397 tests).

On how I work: I use AI coding agents to draft the changes and the replies, and I review and approve every commit and comment before it goes out, including this one. If you'd rather work with me differently on this PR, happy to adjust.

@Schultzer

Copy link
Copy Markdown
Member

@Schultzer Updated and squashed into the first commit, so the PR is now a single commit.

  • lib/format.ex and lib/inspect.ex use your compile_env suggestions in place (balanced parens), and reset is no longer configurable
  • the hunk at format.ex:78 is back to what it was
  • removed test/syntax_colors_test.exs
  • trimmed the README section to the config example and dropped the last paragraph

The code change is 5 lines now; with the README and CHANGELOG it's 22 additions. mix compile --warnings-as-errors and mix test are clean (397 tests).

On how I work: I use AI coding agents to draft the changes and the replies, and I review and approve every commit and comment before it goes out, including this one. If you'd rather work with me differently on this PR, happy to adjust.

Thanks, just a rebase and we should be good to merge this.

Read the keyword, literal, enclosed, and error colors from
config :sql, :syntax_colors with Application.compile_env, falling back
to the current IO.ANSI defaults. The reset sequence stays fixed.

Fixes elixir-dbvisor#26

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019wSaUMZrtm6XSzwEcxavkv
@mvanhorn
mvanhorn force-pushed the fix/26-configurable-syntax-colors branch from e260f74 to 3c23785 Compare October 7, 2026 23:31
@mvanhorn

mvanhorn commented Oct 7, 2026

Copy link
Copy Markdown
Author

Rebased on main (3c23785). The only conflict was CHANGELOG.md, so the entry now sits under a new Unreleased section above v0.6.0. mix test passes locally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make syntax highlighting configurable

2 participants