Skip to content

Add StyleOverride trait and rainbow brackets for terminal formatter#730

Open
dannote wants to merge 3 commits into
leandrocp:mainfrom
dannote:feat/style-override-rainbow-brackets
Open

Add StyleOverride trait and rainbow brackets for terminal formatter#730
dannote wants to merge 3 commits into
leandrocp:mainfrom
dannote:feat/style-override-rainbow-brackets

Conversation

@dannote
Copy link
Copy Markdown

@dannote dannote commented Apr 26, 2026

Motivation

Rainbow brackets — color-coding nested delimiters by depth — is a standard feature in editors (Helix, Neovim's rainbow-delimiters.nvim, VS Code). Adding it to Lumis makes terminal output more readable for deeply nested code.

Rather than a one-off feature, this introduces a generic StyleOverride trait in lumis-core that any formatter consumer can implement. Rainbow brackets are the first concrete implementation.

What changed

Core: StyleOverride trait (lumis-core)

New pub trait StyleOverride: Send + Sync + Debug in formatter/mod.rs:

fn override_style(&self, text: &str, scope: &str, base: &Style, state: &mut usize) -> Style;
  • text / scope — the token and its highlight scope name
  • base — the style resolved from the theme
  • state — mutable usize that persists across tokens (e.g. nesting depth)

The terminal formatter calls the override after resolving the theme style. Zero cost when None — same code path as before.

The override also fires when the theme has no style for a scope but a scope exists, so rainbow brackets work without a theme.

Built-in: RainbowBrackets (lumis-core)

Colors punctuation.bracket tokens with a cycling palette based on nesting depth. Lives in lumis-core so all runtimes (CLI, Rust API, Elixir NIF) can use it.

CLI: --rainbow-brackets flag

lumis highlight code.ex --theme dracula --rainbow-brackets

Elixir: rainbow_brackets option

# Default 6-color palette
Lumis.highlight!(code, formatter: {:terminal, language: "elixir", rainbow_brackets: true})

# Custom palette
Lumis.highlight!(code,
  formatter: {:terminal, language: "elixir",
    rainbow_brackets: %Lumis.RainbowBrackets{colors: ["#ff0000", "#00ff00", "#0000ff"]}}
)

New Lumis.RainbowBrackets module with new/0 (defaults) and new/1 (custom colors). Empty color list is rejected on both sides.

Rust API

use lumis::formatters::RainbowBrackets;

let colors = vec!["#e06c75".into(), "#61afef".into(), "#98c379".into()];
let formatter = TerminalBuilder::new()
    .language(Language::Elixir)
    .style_override(Some(Arc::new(RainbowBrackets::new(colors))))
    .build()?;

Docs

  • Terminal formatter page: new "Rainbow brackets" section with Elixir/Rust/CLI examples
  • CLI commands page: --rainbow-brackets flag and example
  • Options tables updated to show current runtime availability

Not in scope (future work)

  • JavaScript / Java runtime support
  • Per-language bracket config or tree-sitter query customization (these are editor-level concerns, not a highlighter library's job)

Files changed

Area Files Δ
Core trait lumis-core/formatter/mod.rs +45
Core terminal lumis-core/formatter/terminal.rs +38 / −16
Rainbow impl lumis-core/formatter/rainbow_brackets.rs (new) ~80
Rust wrapper lumis/formatter/terminal.rs +109
Rust re-export lumis/formatter/mod.rs +2
CLI lumis-cli/main.rs +25
NIF lumis_nif/src/elixir.rs +26
Elixir API lumis.ex +34
Elixir module lumis/rainbow_brackets.ex (new) ~50
Elixir tests lumis_test.exs +8
Docs terminal.mdx, commands.mdx +70

Test coverage

  • 286 Rust tests pass (212 lumis + 43 lumis-core + 31 lumis-cli)
  • 5 new Rust tests: rainbow with theme, custom colors, no override by default, works without theme, doctest
  • 204 Elixir tests pass
  • cargo fmt --check clean
  • cargo clippy -- -D warnings clean
  • mix format --check-formatted clean

dannote added 2 commits April 26, 2026 09:01
Clippy nightly flags derivable_impls lint. Replace manual Default impl
with #[derive(Default)].
@dannote
Copy link
Copy Markdown
Author

dannote commented May 4, 2026

@leandrocp Pushed a fix for the clippy nightly lint (derivable_impls) — the CI workflows need approval to re-run. Could you approve them?

@leandrocp
Copy link
Copy Markdown
Owner

Hey @dannote thanks for the contribution! I like the ideia of opening formatters for extension, I'll review it today to check if it does fit other use cases that we need to cover. At first glance I can tell:

  1. Detecting brackets using @punctuation.bracket might be enough but I need to check existing queries, otherwise we can introduce brackets.scm that allows per-lang brackets querying. Similar to https://docs.helix-editor.com/master/guides/rainbow_bracket_queries.html and https://zed.dev/blog/rainbow-brackets

  2. Coloring will need some changes in the theme exporter to include the rainbow colors that themes already use for eg https://github.com/search?q=repo%3Acatppuccin%2Fnvim%20rainbow&type=code and https://github.com/search?q=repo%3Afolke%2Ftokyonight.nvim%20rainbow&type=code this is important otherwise many themes would not work with a default set of rainbow colors. I think the most accepted standard nowadays is this list of group:

RainbowDelimiterRed
RainbowDelimiterYellow
RainbowDelimiterBlue
RainbowDelimiterOrange
RainbowDelimiterGreen
RainbowDelimiterViolet
RainbowDelimiterCyan

@dannote
Copy link
Copy Markdown
Author

dannote commented May 5, 2026

Thanks for the pointers — I added support for the standard rainbow delimiter theme groups and pushed it.

What changed:

  • the theme extractor now reads the full standard set: RainbowDelimiterRed, RainbowDelimiterYellow, RainbowDelimiterBlue, RainbowDelimiterOrange, RainbowDelimiterGreen, RainbowDelimiterViolet, and RainbowDelimiterCyan
  • those are stored as rainbow.delimiter.* scopes in generated theme JSON
  • terminal rainbow brackets now use the selected theme's delimiter colors when available, with a fallback only for missing colors
  • Elixir now uses the same theme-derived path for rainbow_brackets: true; explicit %Lumis.RainbowBrackets{colors: ...} remains the custom palette path

I also kept the generated theme diff scoped to only adding rainbow.delimiter.* entries, so unrelated theme revision/style changes from regeneration are not included.

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.

2 participants