canon 0.1.21 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +43 -43
  3. data/README.adoc +8 -3
  4. data/docs/advanced/diff-pipeline.adoc +36 -9
  5. data/docs/features/diff-formatting/colors-and-symbols.adoc +82 -0
  6. data/docs/features/diff-formatting/index.adoc +12 -0
  7. data/docs/features/diff-formatting/themes.adoc +353 -0
  8. data/docs/features/environment-configuration/index.adoc +23 -0
  9. data/docs/internals/diff-char-range-pipeline.adoc +249 -0
  10. data/docs/internals/diffnode-enrichment.adoc +1 -0
  11. data/docs/internals/index.adoc +52 -4
  12. data/docs/reference/environment-variables.adoc +6 -0
  13. data/docs/understanding/architecture.adoc +5 -0
  14. data/examples/show_themes.rb +217 -0
  15. data/lib/canon/comparison/comparison_result.rb +9 -4
  16. data/lib/canon/config/env_schema.rb +3 -1
  17. data/lib/canon/config.rb +11 -0
  18. data/lib/canon/diff/diff_block.rb +7 -0
  19. data/lib/canon/diff/diff_block_builder.rb +2 -2
  20. data/lib/canon/diff/diff_char_range.rb +140 -0
  21. data/lib/canon/diff/diff_line.rb +42 -4
  22. data/lib/canon/diff/diff_line_builder.rb +907 -0
  23. data/lib/canon/diff/diff_node.rb +5 -1
  24. data/lib/canon/diff/diff_node_enricher.rb +1418 -0
  25. data/lib/canon/diff/diff_node_mapper.rb +54 -0
  26. data/lib/canon/diff/source_locator.rb +105 -0
  27. data/lib/canon/diff/text_decomposer.rb +103 -0
  28. data/lib/canon/diff_formatter/by_line/base_formatter.rb +264 -24
  29. data/lib/canon/diff_formatter/by_line/html_formatter.rb +35 -20
  30. data/lib/canon/diff_formatter/by_line/json_formatter.rb +36 -19
  31. data/lib/canon/diff_formatter/by_line/simple_formatter.rb +33 -19
  32. data/lib/canon/diff_formatter/by_line/xml_formatter.rb +583 -98
  33. data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +36 -19
  34. data/lib/canon/diff_formatter/by_object/base_formatter.rb +62 -13
  35. data/lib/canon/diff_formatter/by_object/json_formatter.rb +59 -24
  36. data/lib/canon/diff_formatter/by_object/xml_formatter.rb +74 -34
  37. data/lib/canon/diff_formatter/diff_detail_formatter/color_helper.rb +4 -5
  38. data/lib/canon/diff_formatter/diff_detail_formatter.rb +1 -1
  39. data/lib/canon/diff_formatter/legend.rb +4 -2
  40. data/lib/canon/diff_formatter/theme.rb +857 -0
  41. data/lib/canon/diff_formatter.rb +11 -6
  42. data/lib/canon/tree_diff/matchers/hash_matcher.rb +15 -15
  43. data/lib/canon/tree_diff/matchers/similarity_matcher.rb +10 -0
  44. data/lib/canon/tree_diff/operations/operation_detector.rb +5 -1
  45. data/lib/canon/tree_diff/tree_diff_integrator.rb +1 -1
  46. data/lib/canon/version.rb +1 -1
  47. metadata +11 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c93345884c8bf0f6d5c4507415bb04fa991b8361eb9e38f10fddcbebb92b2370
4
- data.tar.gz: d48eb18c21f562d7e0ff533507c56c6d264600a775f04b2adf2b267b9ce4de2a
3
+ metadata.gz: ba5fabd7a7d0057f365952f8aecc01381ef88f2f1dd28679e9e8947945b54104
4
+ data.tar.gz: 3e4119f7fc69a7c2534957c7654b6a567a868208179262fc921ea1266b5a1a6c
5
5
  SHA512:
6
- metadata.gz: 0cd5ed6a025b439ef76e347a8e223abcdf70c7524bc3e4e883297bfff35c68a30b0ca543c00a657c05714da9273e740c89e7cb34b3d4d0f5b9a3ea496f237530
7
- data.tar.gz: a80515c239c74a58835ad6486e2fc02379166831e5555d4426607bf9b7e612e4047b50135e59caea25ed9d4c6809624a2f2a3b305f54c9c70899280f85d317dd
6
+ metadata.gz: 44a74c7bb2010a2aa22855258affa6889003585edba717538bb579c63f6694757b21b74bc929b07bd0f87660f0bb3bd02f158b0a91be18017e938100f1fee491
7
+ data.tar.gz: be61d26774ae22a5547b2ba0ec1d578b11d37be0899add26d7f14ac63c197637568b74a9d0cad76535e5ab6a82d297af7b917f225f972361f09549964917576b
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-03-25 12:14:38 UTC using RuboCop version 1.86.0.
3
+ # on 2026-03-31 12:44:31 UTC using RuboCop version 1.86.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -13,34 +13,25 @@ Gemspec/RequiredRubyVersion:
13
13
 
14
14
  # Offense count: 1
15
15
  # This cop supports safe autocorrection (--autocorrect).
16
- # Configuration parameters: EnforcedStyleAlignWith.
17
- # SupportedStylesAlignWith: either, start_of_block, start_of_line
18
- Layout/BlockAlignment:
16
+ # Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, DefLikeMacros, AllowAdjacentOneLineDefs, NumberOfEmptyLines.
17
+ Layout/EmptyLineBetweenDefs:
19
18
  Exclude:
20
- - 'spec/canon/diff/diff_node_mapper_comments_spec.rb'
19
+ - 'lib/canon/diff_formatter/by_line/xml_formatter.rb'
21
20
 
22
21
  # Offense count: 1
23
22
  # This cop supports safe autocorrection (--autocorrect).
24
- Layout/BlockEndNewline:
25
- Exclude:
26
- - 'spec/canon/diff/diff_node_mapper_comments_spec.rb'
27
-
28
- # Offense count: 2
29
- # This cop supports safe autocorrection (--autocorrect).
30
- # Configuration parameters: Width, EnforcedStyleAlignWith, AllowedPatterns.
31
- # SupportedStylesAlignWith: start_of_line, relative_to_receiver
32
- Layout/IndentationWidth:
23
+ Layout/EmptyLines:
33
24
  Exclude:
34
- - 'spec/canon/diff/diff_node_mapper_comments_spec.rb'
25
+ - 'lib/canon/diff_formatter/by_line/xml_formatter.rb'
35
26
 
36
- # Offense count: 858
27
+ # Offense count: 1094
37
28
  # This cop supports safe autocorrection (--autocorrect).
38
29
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
39
30
  # URISchemes: http, https
40
31
  Layout/LineLength:
41
32
  Enabled: false
42
33
 
43
- # Offense count: 48
34
+ # Offense count: 55
44
35
  # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
45
36
  Lint/DuplicateBranch:
46
37
  Enabled: false
@@ -80,33 +71,43 @@ Lint/UnusedMethodArgument:
80
71
  - 'lib/canon/diff_formatter/by_line/xml_formatter.rb'
81
72
  - 'lib/canon/diff_formatter/by_object/base_formatter.rb'
82
73
 
83
- # Offense count: 238
74
+ # Offense count: 1
75
+ Lint/UselessConstantScoping:
76
+ Exclude:
77
+ - 'lib/canon/diff_formatter/theme.rb'
78
+
79
+ # Offense count: 297
84
80
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
85
81
  Metrics/AbcSize:
86
82
  Enabled: false
87
83
 
88
- # Offense count: 22
84
+ # Offense count: 29
89
85
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
90
86
  # AllowedMethods: refine
91
87
  Metrics/BlockLength:
92
- Max: 85
88
+ Max: 92
89
+
90
+ # Offense count: 1
91
+ # Configuration parameters: CountBlocks, CountModifierForms.
92
+ Metrics/BlockNesting:
93
+ Max: 4
93
94
 
94
- # Offense count: 198
95
+ # Offense count: 261
95
96
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
96
97
  Metrics/CyclomaticComplexity:
97
98
  Enabled: false
98
99
 
99
- # Offense count: 413
100
+ # Offense count: 485
100
101
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
101
102
  Metrics/MethodLength:
102
- Max: 104
103
+ Max: 146
103
104
 
104
- # Offense count: 46
105
+ # Offense count: 56
105
106
  # Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
106
107
  Metrics/ParameterLists:
107
- Max: 9
108
+ Max: 10
108
109
 
109
- # Offense count: 162
110
+ # Offense count: 212
110
111
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
111
112
  Metrics/PerceivedComplexity:
112
113
  Enabled: false
@@ -130,11 +131,12 @@ Naming/VariableNumber:
130
131
  - 'lib/canon/comparison/markup_comparator.rb'
131
132
  - 'lib/canon/comparison/xml_comparator/diff_node_builder.rb'
132
133
 
133
- # Offense count: 3
134
+ # Offense count: 4
134
135
  # Configuration parameters: MinSize.
135
136
  Performance/CollectionLiteralInLoop:
136
137
  Exclude:
137
138
  - 'lib/canon/comparison/html_comparator.rb'
139
+ - 'lib/canon/diff_formatter/theme.rb'
138
140
  - 'lib/canon/xml/xml_base_handler.rb'
139
141
  - 'spec/canon/diff/diff_node_mapper_comments_spec.rb'
140
142
 
@@ -144,7 +146,7 @@ Performance/CollectionLiteralInLoop:
144
146
  RSpec/ContextWording:
145
147
  Enabled: false
146
148
 
147
- # Offense count: 30
149
+ # Offense count: 33
148
150
  # Configuration parameters: IgnoredMetadata.
149
151
  RSpec/DescribeClass:
150
152
  Enabled: false
@@ -155,10 +157,10 @@ RSpec/DescribeMethod:
155
157
  - 'spec/canon/comparison/multiple_differences_spec.rb'
156
158
  - 'spec/canon/diff_formatter/character_map_customization_spec.rb'
157
159
 
158
- # Offense count: 707
160
+ # Offense count: 736
159
161
  # Configuration parameters: CountAsOne.
160
162
  RSpec/ExampleLength:
161
- Max: 43
163
+ Max: 44
162
164
 
163
165
  # Offense count: 8
164
166
  # This cop supports safe autocorrection (--autocorrect).
@@ -207,7 +209,7 @@ RSpec/MultipleDescribes:
207
209
  Exclude:
208
210
  - 'spec/canon/comparison/match_options_spec.rb'
209
211
 
210
- # Offense count: 546
212
+ # Offense count: 590
211
213
  RSpec/MultipleExpectations:
212
214
  Max: 15
213
215
 
@@ -240,10 +242,11 @@ RSpec/NoExpectationExample:
240
242
  - 'spec/canon/isodoc_blockquotes_spec.rb'
241
243
  - 'spec/canon/match_scenarios_spec.rb'
242
244
 
243
- # Offense count: 2
245
+ # Offense count: 4
244
246
  RSpec/RepeatedExample:
245
247
  Exclude:
246
248
  - 'spec/canon/comparison/encoding_normalization_spec.rb'
249
+ - 'spec/canon/diff_display_spec.rb'
247
250
 
248
251
  # Offense count: 7
249
252
  # Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata, InflectorPath, EnforcedInflector.
@@ -270,17 +273,6 @@ RSpec/VerifiedDoubles:
270
273
  - 'spec/canon/diff_formatter/diff_detail_formatter_spec.rb'
271
274
  - 'spec/canon/tree_diff/operation_converter_spec.rb'
272
275
 
273
- # Offense count: 1
274
- # This cop supports safe autocorrection (--autocorrect).
275
- # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods.
276
- # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
277
- # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
278
- # FunctionalMethods: let, let!, subject, watch
279
- # AllowedMethods: lambda, proc, it
280
- Style/BlockDelimiters:
281
- Exclude:
282
- - 'spec/canon/diff/diff_node_mapper_comments_spec.rb'
283
-
284
276
  # Offense count: 1
285
277
  # This cop supports safe autocorrection (--autocorrect).
286
278
  # Configuration parameters: EnforcedStyle, AllowComments.
@@ -309,3 +301,11 @@ Style/IdenticalConditionalBranches:
309
301
  Style/OptionalBooleanParameter:
310
302
  Exclude:
311
303
  - 'lib/canon/diff_formatter/debug_output.rb'
304
+
305
+ # Offense count: 1
306
+ # This cop supports safe autocorrection (--autocorrect).
307
+ # Configuration parameters: AllowedMethods.
308
+ # AllowedMethods: infinite?, nonzero?
309
+ Style/RedundantCondition:
310
+ Exclude:
311
+ - 'lib/canon/diff/diff_node_enricher.rb'
data/README.adoc CHANGED
@@ -586,10 +586,15 @@ See link:docs/ENV_CONFIG#size-limits[ENV_CONFIG] for details on size limit confi
586
586
 
587
587
  **By-line mode**: Traditional line-by-line diff with:
588
588
 
589
+ * **DiffCharRange pipeline**: DiffNodes are enriched with character-level positions (Phase 1: DiffNodeEnricher using SourceLocator + TextDecomposer), then assembled into display lines (Phase 2: DiffLineBuilder). The formatter only reads pre-computed positions — no tokenization or LCS during rendering.
590
+ * **Character-level highlighting**: Within changed lines, only the specific changed characters are highlighted, not entire lines. A text change like "Hello World" → "Hello Universe" shows "World" in red and "Universe" in green, with "Hello " in default color.
591
+ * **Inline and separate-line display modes**: Changed lines can be displayed inline (old→new on same line with effects) or as separate lines (traditional `-`/`+` format).
592
+ * **Mixed change detection**: The `*` marker identifies lines with multiple separate changed regions, distinguishing them from simple word replacements.
593
+ * **Legacy terminal support**: For terminals without ANSI support, forces separate-line mode with no color codes.
589
594
  * DOM-guided semantic matching for XML
590
- * Syntax-aware token highlighting
591
- * Context lines around changes
592
- * Whitespace visualization
595
+ * Context lines around changes with configurable grouping
596
+ * Whitespace visualization (`░` for spaces, `⇥` for tabs)
597
+ * Three-tier classification with directional colors
593
598
 
594
599
  **By-object mode**: Tree-based semantic diff with:
595
600
 
@@ -84,17 +84,21 @@ end
84
84
 
85
85
  == Layer 3: Mapping
86
86
 
87
- **Responsibility**: Map semantic diffs to text line positions
87
+ **Responsibility**: Enrich semantic diffs with character-level positions and map to text lines
88
88
 
89
89
  **Input**: DiffNode array + original text documents
90
90
 
91
91
  **Process**:
92
- 1. Run text diff (Diff::LCS) on original strings
93
- 2. For each changed line, find corresponding DiffNode
94
- 3. Create DiffLine linking line DiffNode
95
- 4. Inherit normative/informative from DiffNode
92
+ 1. **Enrichment** (DiffNodeEnricher): For each DiffNode, locate serialized content in source text and decompose into character ranges
93
+ - SourceLocator: Find serialized content position (char offset, line, column)
94
+ - TextDecomposer: Split into common prefix / changed / common suffix
95
+ - Create DiffCharRange objects with exact character positions
96
+ 2. **Assembly** (DiffLineBuilder): Build DiffLine sequence from enriched DiffNodes
97
+ - Map DiffCharRanges to source lines
98
+ - Fill in unchanged lines between changes
99
+ - Detect reflow (lines that moved but content unchanged)
96
100
 
97
- **Output**: Array of DiffLine objects
101
+ **Output**: Array of DiffLine objects, each carrying DiffCharRange arrays
98
102
 
99
103
  [source,ruby]
100
104
  ----
@@ -102,13 +106,27 @@ end
102
106
  {
103
107
  line_number: 5,
104
108
  content: "<p>Changed text</p>",
109
+ new_content: "<p>New text</p>",
105
110
  type: :changed, # :added, :removed, :unchanged
106
111
  diff_node: DiffNode reference,
112
+ char_ranges: [DiffCharRange, ...], # text1 side
113
+ new_char_ranges: [DiffCharRange, ...], # text2 side
107
114
  normative: true # from diff_node
108
115
  }
116
+
117
+ # DiffCharRange structure
118
+ {
119
+ line_number: 5,
120
+ start_col: 3,
121
+ end_col: 14,
122
+ side: :old,
123
+ status: :changed_old, # :unchanged, :changed_new, :removed, :added
124
+ role: :changed, # :before, :changed, :after
125
+ diff_node: DiffNode reference
126
+ }
109
127
  ----
110
128
 
111
- **Key Point**: This layer bridges semantic differences to their textual representation.
129
+ **Key Point**: This layer bridges semantic differences to their textual representation with precise character-level positions. The two-phase approach (enrich then assemble) ensures the formatter receives pre-computed data requiring no further computation.
112
130
 
113
131
  == Layer 4: Blocking
114
132
 
@@ -272,7 +290,15 @@ Result: Files differ, diff shown in red/green
272
290
 
273
291
  === Processing Layers
274
292
 
275
- [`DiffNodeMapper`](../../lib/canon/diff/diff_node_mapper.rb):: Maps semantic diffs to text line positions
293
+ [`DiffNodeEnricher`](../../lib/canon/diff/diff_node_enricher.rb):: Phase 1: Enriches DiffNodes with character-level positions (SourceLocator + TextDecomposer)
294
+
295
+ [`DiffLineBuilder`](../../lib/canon/diff/diff_line_builder.rb):: Phase 2: Assembles DiffLines from enriched DiffNodes (replaces DiffNodeMapper)
296
+
297
+ [`DiffCharRange`](../../lib/canon/diff/diff_char_range.rb):: Value object: a character range within a source line linked to a DiffNode
298
+
299
+ [`TextDecomposer`](../../lib/canon/diff/text_decomposer.rb):: Decomposes two strings into common prefix / changed / common suffix
300
+
301
+ [`SourceLocator`](../../lib/canon/diff/source_locator.rb):: Locates serialized content within source text, returns char offset + line/col
276
302
 
277
303
  [`DiffBlockBuilder`](../../lib/canon/diff/diff_block_builder.rb):: Groups contiguous lines into blocks, filters by show_diffs
278
304
 
@@ -296,7 +322,8 @@ Each class does ONE thing:
296
322
 
297
323
  * **Comparator**: Compares → DiffNodes
298
324
  * **Classifier**: Classifies → normative flags
299
- * **Mapper**: Maps nodes lines
325
+ * **Enricher**: Enricheschar positions on DiffNodes
326
+ * **LineBuilder**: Assembles → DiffLines with char ranges
300
327
  * **BlockBuilder**: Groups lines → blocks
301
328
  * **ContextBuilder**: Adds context → contexts
302
329
  * **Formatter**: Renders → string
@@ -49,6 +49,13 @@ Green (added)
49
49
  | Semantic differences that affect match results. +
50
50
  Requires developer attention. +
51
51
  Represents actual content changes.
52
+
53
+ h| Mixed
54
+ | `*`
55
+ | Yellow
56
+ | Line contains multiple separate changed regions. +
57
+ Both old and new lines are modified. +
58
+ Appears on both OLD and NEW lines.
52
59
  |===
53
60
 
54
61
  === Classification hierarchy
@@ -141,6 +148,13 @@ Normative changes use red/green for high visual priority:
141
148
  * `-` - Line removed (normative difference, red)
142
149
  * `+` - Line added (normative difference, green)
143
150
 
151
+ ==== Mixed Marker (Yellow)
152
+
153
+ * `*` - Line with multiple separate changed regions (yellow)
154
+ ** Appears on BOTH old and new lines when changes are interleaved.
155
+ ** A simple word replacement (e.g., "John" → "Jane") uses `-`/`+`, not `*`.
156
+ ** Only used when there are 2+ separate changed regions in the same line.
157
+
144
158
  ==== Context Marker
145
159
 
146
160
  * ` ` (space) - Unchanged line (context)
@@ -457,6 +471,17 @@ The whitespace difference is informative because `structural_whitespace: :ignore
457
471
 
458
472
  == Color Control
459
473
 
474
+ === Theme System
475
+
476
+ Canon supports multiple color themes that adapt the diff display to your terminal background and personal preference. Four built-in themes are available:
477
+
478
+ * `:light` -- Light terminal backgrounds
479
+ * `:dark` -- Dark terminals (default)
480
+ * `:retro` -- Amber CRT, low blue light, accessibility
481
+ * `:claude` -- Claude Code diff style with red/green backgrounds
482
+
483
+ See link:themes.adoc[Diff display themes] for full theme documentation.
484
+
460
485
  === Automatic Color Detection
461
486
 
462
487
  Canon automatically detects whether the terminal supports color output:
@@ -522,6 +547,63 @@ canon diff file1.xml file2.xml
522
547
 
523
548
  The `NO_COLOR` variable always takes precedence over other color settings.
524
549
 
550
+ == Diff Display Modes
551
+
552
+ Canon supports two display modes for changed lines, controlled by the `diff_mode` option:
553
+
554
+ === Separate-Line Mode (default)
555
+
556
+ Each changed line is shown as two lines — old on top, new on bottom:
557
+
558
+ [source]
559
+ ----
560
+ 2| - | <p>John░Doe</p>
561
+ | 2+ | <p>Jane░Doe</p>
562
+ ----
563
+
564
+ * Old line: line number on left, `-` marker, full content
565
+ * New line: line number on right, `+` marker, full content
566
+ * Changes are highlighted with colors (red for old, green for new)
567
+
568
+ === Inline Mode
569
+
570
+ Old and new content shown on the same line, with inline highlighting:
571
+
572
+ * When color is ON: removed text appears in red, added text in green
573
+ * When color is OFF: removed text has strikethrough (`\e[9m`), added text has underline (`\e[4m`)
574
+ * Both sides of the change are visible simultaneously
575
+
576
+ [source,ruby]
577
+ ----
578
+ result = Canon::Comparison.equivalent?(xml1, xml2,
579
+ verbose: true,
580
+ diff_mode: :inline
581
+ )
582
+ ----
583
+
584
+ === Legacy Terminal Mode
585
+
586
+ For terminals without ANSI support, Canon forces separate-line mode with no color codes:
587
+
588
+ [source,ruby]
589
+ ----
590
+ result = Canon::Comparison.equivalent?(xml1, xml2,
591
+ verbose: true,
592
+ legacy_terminal: true
593
+ )
594
+ ----
595
+
596
+ Legacy terminal mode automatically sets `diff_mode: :separate` and disables all color output.
597
+
598
+ === Color-Off Effects (Separate-Line Mode)
599
+
600
+ When colors are disabled but ANSI is supported, Canon uses text effects to distinguish changes:
601
+
602
+ * **Strikethrough** (`\e[9m`): Removed text
603
+ * **Underline** (`\e[4m`): Added text
604
+
605
+ These provide visual distinction even without color support.
606
+
525
607
  === Environment Variables
526
608
 
527
609
  In addition to `NO_COLOR`, Canon supports these environment variables:
@@ -23,6 +23,7 @@ Canon's diff formatting includes:
23
23
  * **Display filtering**: Control which differences appear in output based on
24
24
  whether they affect equivalence
25
25
  * **Colors and symbols**: Visual indicators for different types of changes
26
+ * **Diff display themes**: Choose from light, dark, retro, or claude color schemes
26
27
  * **Character visualization**: Make invisible characters visible
27
28
  * **Context and grouping**: Control how much surrounding context to show
28
29
  * **Algorithm-specific output**: Different output styles for different diff
@@ -52,6 +53,17 @@ Canon uses color-coded output to distinguish different types of changes:
52
53
 
53
54
  See link:colors-and-symbols.adoc[Colors and symbols] for details.
54
55
 
56
+ === Diff display themes
57
+
58
+ Choose from 4 predefined color themes (light, dark, retro, claude) or create custom themes:
59
+
60
+ * Light theme: Light terminal backgrounds
61
+ * Dark theme: Dark terminals (default)
62
+ * Retro theme: Amber CRT, low blue light
63
+ * Claude theme: Maximum contrast with colored backgrounds
64
+
65
+ See link:themes.adoc[Themes] for complete theme documentation.
66
+
55
67
  === Character visualization
56
68
 
57
69
  Make invisible characters visible in diff output: