Module: Canon::DiffFormatter::Theme
- Defined in:
- lib/canon/diff_formatter/theme.rb
Overview
Theme definitions for diff display.
Theme is a nested hash structure:
-
diff: removed/added/changed/unchanged/formatting/informative
-
xml: tag/attribute_name/attribute_value/text/comment/cdata
-
html: same as xml
-
structure: line_number/pipe/context
-
visualization: space/tab/newline/nbsp
-
display_mode: :separate/:inline/:mixed
Each styled element has: color, bg, bold, underline, strikethrough, italic
Defined Under Namespace
Classes: Resolver, ThemeInheritance, ValidationResult
Constant Summary collapse
- VALID_COLORS =
Valid ANSI color values (standard 16 + common extended colors) Standard: 8 colors + 8 bright variants Extended: light_ variants (for backgrounds), amber (retro terminal)
%i[ default black red green yellow blue magenta cyan white bright_black bright_red bright_green bright_yellow bright_blue bright_magenta bright_cyan bright_white light_red light_green light_blue light_cyan light_magenta light_yellow light_black light_white amber ].freeze
- VALID_DISPLAY_MODES =
Valid display modes
%i[separate inline mixed].freeze
- STYLING_PROPERTIES =
Base properties for any styled element
%i[color bg bold underline strikethrough italic].freeze
- LIGHT =
LIGHT THEME - Light terminal backgrounds, professional use
{ name: "Light", description: "Light terminal backgrounds - professional, high contrast", diff: { removed: { marker: { color: :red, bg: :light_red, bold: false }, content: { color: :red, bg: nil, bold: false, underline: false, strikethrough: true }, }, added: { marker: { color: :green, bg: :light_green, bold: false }, content: { color: :green, bg: nil, bold: false, underline: false, strikethrough: false }, }, changed: { marker: { color: :bright_red, bg: nil, bold: true }, content_old: { color: :bright_red, bg: nil, bold: true, underline: false, strikethrough: true }, content_new: { color: :bright_green, bg: nil, bold: true, underline: true, strikethrough: false }, }, unchanged: { content: { color: :default, bg: nil, bold: false, underline: false, strikethrough: false }, }, formatting: { marker: { color: :bright_blue, bg: nil, bold: false }, content: { color: :bright_blue, bg: nil, bold: false, underline: false, strikethrough: false }, }, informative: { marker: { color: :bright_magenta, bg: nil, bold: false }, content: { color: :bright_magenta, bg: nil, bold: false, underline: false, strikethrough: false }, }, }, xml: { tag: { color: :bright_blue, bg: nil, bold: true, italic: false }, attribute_name: { color: :magenta, bg: nil, bold: false, italic: false }, attribute_value: { color: :green, bg: nil, bold: false, italic: false }, text: { color: :default, bg: nil, bold: false, italic: false }, comment: { color: :magenta, bg: nil, bold: false, italic: true }, cdata: { color: :yellow, bg: nil, bold: false, italic: false }, }, html: { tag: { color: :bright_blue, bg: nil, bold: true, italic: false }, attribute_name: { color: :magenta, bg: nil, bold: false, italic: false }, attribute_value: { color: :green, bg: nil, bold: false, italic: false }, text: { color: :default, bg: nil, bold: false, italic: false }, comment: { color: :magenta, bg: nil, bold: false, italic: true }, cdata: { color: :yellow, bg: nil, bold: false, italic: false }, }, structure: { line_number: { color: :black }, pipe: { color: :black }, context: { color: :default }, }, visualization: { space: "░", tab: "→", newline: "¶", nbsp: "␣", }, display_mode: :separate, }.freeze
- DARK =
DARK THEME - Dark terminal backgrounds, developer favorite
{ name: "Dark", description: "Dark terminal backgrounds - saturated colors, no backgrounds", diff: { removed: { marker: { color: :red, bg: nil, bold: false }, content: { color: :red, bg: nil, bold: false, underline: false, strikethrough: true }, }, added: { marker: { color: :green, bg: nil, bold: false }, content: { color: :green, bg: nil, bold: false, underline: false, strikethrough: false }, }, changed: { marker: { color: :yellow, bg: nil, bold: true }, content_old: { color: :bright_red, bg: nil, bold: false, underline: false, strikethrough: true }, content_new: { color: :bright_green, bg: nil, bold: false, underline: true, strikethrough: false }, }, unchanged: { content: { color: :default, bg: nil, bold: false, underline: false, strikethrough: false }, }, formatting: { marker: { color: :bright_blue, bg: nil, bold: false }, content: { color: :bright_blue, bg: nil, bold: false, underline: false, strikethrough: false }, }, informative: { marker: { color: :cyan, bg: nil, bold: false }, content: { color: :cyan, bg: nil, bold: false, underline: false, strikethrough: false }, }, }, xml: { tag: { color: :bright_blue, bg: nil, bold: true, italic: false }, attribute_name: { color: :magenta, bg: nil, bold: false, italic: false }, attribute_value: { color: :green, bg: nil, bold: false, italic: false }, text: { color: :default, bg: nil, bold: false, italic: false }, comment: { color: :cyan, bg: nil, bold: false, italic: true }, cdata: { color: :yellow, bg: nil, bold: false, italic: false }, }, html: { tag: { color: :bright_blue, bg: nil, bold: true, italic: false }, attribute_name: { color: :magenta, bg: nil, bold: false, italic: false }, attribute_value: { color: :green, bg: nil, bold: false, italic: false }, text: { color: :default, bg: nil, bold: false, italic: false }, comment: { color: :cyan, bg: nil, bold: false, italic: true }, cdata: { color: :yellow, bg: nil, bold: false, italic: false }, }, structure: { line_number: { color: :white }, pipe: { color: :white }, context: { color: :default }, }, visualization: { space: "░", tab: "→", newline: "¶", nbsp: "␣", }, display_mode: :separate, }.freeze
- RETRO =
RETRO THEME - Amber CRT, low blue light, accessibility
{ name: "Retro", description: "Amber CRT phosphor - monochromatic amber, low blue light, high accessibility", diff: { removed: { # Bright amber on amber background = inverse video, highest emphasis marker: { color: :bright_yellow, bg: :yellow, bold: true }, content: { color: :bright_yellow, bg: :yellow, bold: true, underline: false, strikethrough: false }, }, added: { # Bright white = less emphasis than removed, but distinct from normal text marker: { color: :bright_white, bg: nil, bold: true }, content: { color: :bright_white, bg: nil, bold: false, underline: false, strikethrough: false }, }, changed: { marker: { color: :bright_yellow, bg: :yellow, bold: true }, content_old: { color: :bright_yellow, bg: :yellow, bold: true, underline: false, strikethrough: true }, content_new: { color: :bright_white, bg: nil, bold: false, underline: true, strikethrough: false }, }, unchanged: { content: { color: :yellow, bg: nil, bold: false, underline: false, strikethrough: false }, }, formatting: { # Dimmer amber + strikethrough = clearly different from normal text marker: { color: :yellow, bg: nil, bold: false, strikethrough: true }, content: { color: :yellow, bg: nil, bold: false, underline: false, strikethrough: true }, }, informative: { # Bright amber + underline = distinct from formatting and normal marker: { color: :bright_yellow, bg: nil, bold: true, underline: true }, content: { color: :bright_yellow, bg: nil, bold: true, underline: true, strikethrough: false }, }, }, xml: { # Amber monochrome for all XML elements tag: { color: :bright_yellow, bg: nil, bold: true, italic: false }, attribute_name: { color: :bright_yellow, bg: nil, bold: false, italic: false }, attribute_value: { color: :bright_yellow, bg: nil, bold: false, italic: false }, text: { color: :yellow, bg: nil, bold: false, italic: false }, comment: { color: :yellow, bg: nil, bold: false, italic: true }, cdata: { color: :bright_yellow, bg: nil, bold: false, italic: false }, }, html: { tag: { color: :bright_yellow, bg: nil, bold: true, italic: false }, attribute_name: { color: :bright_yellow, bg: nil, bold: false, italic: false }, attribute_value: { color: :bright_yellow, bg: nil, bold: false, italic: false }, text: { color: :yellow, bg: nil, bold: false, italic: false }, comment: { color: :yellow, bg: nil, bold: false, italic: true }, cdata: { color: :bright_yellow, bg: nil, bold: false, italic: false }, }, structure: { line_number: { color: :yellow }, pipe: { color: :yellow }, context: { color: :yellow }, }, visualization: { space: "░", tab: "→", newline: "¶", nbsp: "␣", }, display_mode: :separate, }.freeze
- CLAUDE =
CLAUDE THEME - Claude Code diff style, high contrast HUD
{ name: "Claude", description: "Claude Code diff style - red/green backgrounds, maximum visual pop", diff: { removed: { # Red background + white text = immediate visual pop marker: { color: :white, bg: :red, bold: true }, content: { color: :white, bg: :red, bold: false, underline: false, strikethrough: false }, }, added: { # Green background + white text (black invisible on dark terminals) marker: { color: :white, bg: :green, bold: true }, content: { color: :white, bg: :green, bold: false, underline: false, strikethrough: false }, }, changed: { marker: { color: :white, bg: :magenta, bold: true }, content_old: { color: :bright_red, bg: nil, bold: false, underline: false, strikethrough: true }, content_new: { color: :bright_green, bg: nil, bold: false, underline: true, strikethrough: false }, }, unchanged: { content: { color: :default, bg: nil, bold: false, underline: false, strikethrough: false }, }, formatting: { marker: { color: :yellow, bg: nil, bold: false }, content: { color: :yellow, bg: nil, bold: false, underline: false, strikethrough: false }, }, informative: { marker: { color: :bright_cyan, bg: nil, bold: false }, content: { color: :bright_cyan, bg: nil, bold: false, underline: false, strikethrough: false }, }, }, xml: { tag: { color: :bright_blue, bg: nil, bold: true, italic: false }, attribute_name: { color: :magenta, bg: nil, bold: false, italic: false }, attribute_value: { color: :green, bg: nil, bold: false, italic: false }, text: { color: :default, bg: nil, bold: false, italic: false }, comment: { color: :cyan, bg: nil, bold: false, italic: true }, cdata: { color: :yellow, bg: nil, bold: false, italic: false }, }, html: { tag: { color: :bright_blue, bg: nil, bold: true, italic: false }, attribute_name: { color: :magenta, bg: nil, bold: false, italic: false }, attribute_value: { color: :green, bg: nil, bold: false, italic: false }, text: { color: :default, bg: nil, bold: false, italic: false }, comment: { color: :cyan, bg: nil, bold: false, italic: true }, cdata: { color: :yellow, bg: nil, bold: false, italic: false }, }, structure: { line_number: { color: :yellow }, pipe: { color: :yellow }, context: { color: :default }, }, visualization: { space: "░", tab: "→", newline: "¶", nbsp: "␣", }, display_mode: :separate, }.freeze
- CYBERPUNK =
CYBERPUNK THEME - Neon on black, high contrast, futuristic
{ name: "Cyberpunk", description: "Neon on black - high contrast, futuristic, electric", diff: { removed: { # Hot pink/magenta neon for deletions marker: { color: :bright_magenta, bg: nil, bold: true }, content: { color: :bright_magenta, bg: nil, bold: true, underline: false, strikethrough: true }, }, added: { # Electric cyan neon for additions marker: { color: :bright_cyan, bg: nil, bold: true }, content: { color: :bright_cyan, bg: nil, bold: true, underline: false, strikethrough: false }, }, changed: { # Yellow warning neon for change markers marker: { color: :bright_yellow, bg: nil, bold: true }, content_old: { color: :bright_magenta, bg: nil, bold: false, underline: false, strikethrough: true }, content_new: { color: :bright_cyan, bg: nil, bold: false, underline: true, strikethrough: false }, }, unchanged: { content: { color: :default, bg: nil, bold: false, underline: false, strikethrough: false }, }, formatting: { # Dim green for low-priority formatting marker: { color: :green, bg: nil, bold: false }, content: { color: :green, bg: nil, bold: false, underline: false, strikethrough: false }, }, informative: { # Bright yellow neon for informative marker: { color: :bright_yellow, bg: nil, bold: true }, content: { color: :bright_yellow, bg: nil, bold: false, underline: false, strikethrough: false }, }, }, xml: { # Tags in bright cyan, attributes in hot magenta tag: { color: :bright_cyan, bg: nil, bold: true, italic: false }, attribute_name: { color: :bright_magenta, bg: nil, bold: false, italic: false }, attribute_value: { color: :bright_green, bg: nil, bold: false, italic: false }, text: { color: :default, bg: nil, bold: false, italic: false }, comment: { color: :green, bg: nil, bold: false, italic: true }, cdata: { color: :bright_yellow, bg: nil, bold: false, italic: false }, }, html: { tag: { color: :bright_cyan, bg: nil, bold: true, italic: false }, attribute_name: { color: :bright_magenta, bg: nil, bold: false, italic: false }, attribute_value: { color: :bright_green, bg: nil, bold: false, italic: false }, text: { color: :default, bg: nil, bold: false, italic: false }, comment: { color: :green, bg: nil, bold: false, italic: true }, cdata: { color: :bright_yellow, bg: nil, bold: false, italic: false }, }, structure: { line_number: { color: :bright_cyan }, pipe: { color: :bright_cyan }, context: { color: :default }, }, visualization: { space: "░", tab: "→", newline: "¶", nbsp: "␣", }, display_mode: :separate, }.freeze
- THEMES =
Registry of all themes
{ light: LIGHT, dark: DARK, retro: RETRO, claude: CLAUDE, cyberpunk: CYBERPUNK, }.freeze
Class Method Summary collapse
- .[](name) ⇒ Object
-
.deep_dup(obj) ⇒ Hash
Get a theme by name Deep copy a value, handling nested hashes and arrays.
-
.include?(name) ⇒ Boolean
Check if theme name exists.
-
.inherit_from(base_name) ⇒ ThemeInheritance
Create a new theme by inheriting from a base theme and merging overrides.
-
.names ⇒ Array<Symbol>
List available theme names.
-
.resolver(config = nil) ⇒ Object
Singleton instance for convenience.
-
.validate(theme) ⇒ ValidationResult
Validate a theme hash has all required keys and valid values.
-
.validate_all ⇒ Hash{Symbol => ValidationResult}
Validate all predefined themes.
Class Method Details
.[](name) ⇒ Object
614 615 616 617 618 619 |
# File 'lib/canon/diff_formatter/theme.rb', line 614 def self.[](name) theme = THEMES[name] || raise(ArgumentError, "Unknown theme: #{name}. Valid: #{THEMES.keys}") # Return a deep copy to prevent mutation of theme constants deep_dup(theme) end |
.deep_dup(obj) ⇒ Hash
Get a theme by name Deep copy a value, handling nested hashes and arrays
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 |
# File 'lib/canon/diff_formatter/theme.rb', line 597 def self.deep_dup(obj) case obj when Hash obj.transform_values { |v| deep_dup(v) } when Array obj.map { |v| deep_dup(v) } when String, Symbol, Numeric, TrueClass, FalseClass, NilClass obj else begin obj.dup rescue StandardError obj end end end |
.include?(name) ⇒ Boolean
Check if theme name exists
630 631 632 |
# File 'lib/canon/diff_formatter/theme.rb', line 630 def self.include?(name) THEMES.key?(name) end |
.inherit_from(base_name) ⇒ ThemeInheritance
Create a new theme by inheriting from a base theme and merging overrides
460 461 462 |
# File 'lib/canon/diff_formatter/theme.rb', line 460 def self.inherit_from(base_name) ThemeInheritance.new(base_name) end |
.names ⇒ Array<Symbol>
List available theme names
623 624 625 |
# File 'lib/canon/diff_formatter/theme.rb', line 623 def self.names THEMES.keys end |
.resolver(config = nil) ⇒ Object
Singleton instance for convenience
859 860 861 |
# File 'lib/canon/diff_formatter/theme.rb', line 859 def self.resolver(config = nil) Resolver.new(config) end |
.validate(theme) ⇒ ValidationResult
Validate a theme hash has all required keys and valid values
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 |
# File 'lib/canon/diff_formatter/theme.rb', line 527 def self.validate(theme) missing_keys = [] extra_keys = [] invalid_values = [] # Check top-level keys required_toplevel = %i[name description diff xml html structure visualization display_mode] required_toplevel.each do |key| missing_keys << "top-level.#{key}" unless theme.key?(key) end # Validate diff section if theme[:diff] validate_diff_section(theme[:diff], missing_keys, extra_keys, invalid_values) end # Validate xml section if theme[:xml] validate_xml_section(theme[:xml], missing_keys, extra_keys, invalid_values) end # Validate html section if theme[:html] validate_xml_section(theme[:html], missing_keys, extra_keys, invalid_values) end # Validate structure if theme[:structure] validate_structure_section(theme[:structure], missing_keys, extra_keys, invalid_values) end # Validate visualization if theme[:visualization] validate_visualization_section(theme[:visualization], missing_keys, extra_keys, invalid_values) end # Validate display_mode if theme[:display_mode] unless VALID_DISPLAY_MODES.include?(theme[:display_mode]) invalid_values << "display_mode must be one of #{VALID_DISPLAY_MODES}, got #{theme[:display_mode]}" end else missing_keys << "display_mode" end ValidationResult.new( valid: missing_keys.empty? && extra_keys.empty? && invalid_values.empty?, missing_keys: missing_keys, extra_keys: extra_keys, invalid_values: invalid_values, ) end |
.validate_all ⇒ Hash{Symbol => ValidationResult}
Validate all predefined themes
588 589 590 |
# File 'lib/canon/diff_formatter/theme.rb', line 588 def self.validate_all THEMES.transform_values { |theme| validate(theme) } end |