canon 0.1.21 → 0.1.23

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 +50 -26
  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 +864 -0
  41. data/lib/canon/diff_formatter.rb +11 -6
  42. data/lib/canon/tree_diff/matchers/hash_matcher.rb +16 -1
  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: '08086e588946bb0f5f39a8235984058b5a5e8aea956b9652e0e366ff84500232'
4
+ data.tar.gz: bb078d67c1a68da1ca88b3d29fde336e291b0ff95186ade125ee1ccfb1a82de4
5
5
  SHA512:
6
- metadata.gz: 0cd5ed6a025b439ef76e347a8e223abcdf70c7524bc3e4e883297bfff35c68a30b0ca543c00a657c05714da9273e740c89e7cb34b3d4d0f5b9a3ea496f237530
7
- data.tar.gz: a80515c239c74a58835ad6486e2fc02379166831e5555d4426607bf9b7e612e4047b50135e59caea25ed9d4c6809624a2f2a3b305f54c9c70899280f85d317dd
6
+ metadata.gz: 35f7888d948f86fa4b504b060ee6d9976dc38b32232e8c86b9b8986ee08448cdad265fe4d091e810e5f63f0b0702e125317e07d15761b5cd6404905190546234
7
+ data.tar.gz: 4f57564289fdff8c73020f3ed9f9184b363688062cf1b89baea79f0d25c57905bcbc225b5e946afeb30305b91402b557ae65ce61b99a0d63b8bb1c569ec2d57f
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-04-01 00:03:16 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
@@ -11,36 +11,39 @@ Gemspec/RequiredRubyVersion:
11
11
  Exclude:
12
12
  - 'canon.gemspec'
13
13
 
14
- # Offense count: 1
14
+ # Offense count: 2
15
15
  # This cop supports safe autocorrection (--autocorrect).
16
16
  # Configuration parameters: EnforcedStyleAlignWith.
17
17
  # SupportedStylesAlignWith: either, start_of_block, start_of_line
18
18
  Layout/BlockAlignment:
19
19
  Exclude:
20
- - 'spec/canon/diff/diff_node_mapper_comments_spec.rb'
20
+ - 'lib/canon/tree_diff/matchers/hash_matcher.rb'
21
+ - 'spec/canon/hash_matcher_regression_spec.rb'
21
22
 
22
- # Offense count: 1
23
+ # Offense count: 2
23
24
  # This cop supports safe autocorrection (--autocorrect).
24
25
  Layout/BlockEndNewline:
25
26
  Exclude:
26
- - 'spec/canon/diff/diff_node_mapper_comments_spec.rb'
27
+ - 'lib/canon/tree_diff/matchers/hash_matcher.rb'
28
+ - 'spec/canon/hash_matcher_regression_spec.rb'
27
29
 
28
- # Offense count: 2
30
+ # Offense count: 4
29
31
  # This cop supports safe autocorrection (--autocorrect).
30
32
  # Configuration parameters: Width, EnforcedStyleAlignWith, AllowedPatterns.
31
33
  # SupportedStylesAlignWith: start_of_line, relative_to_receiver
32
34
  Layout/IndentationWidth:
33
35
  Exclude:
34
- - 'spec/canon/diff/diff_node_mapper_comments_spec.rb'
36
+ - 'lib/canon/tree_diff/matchers/hash_matcher.rb'
37
+ - 'spec/canon/hash_matcher_regression_spec.rb'
35
38
 
36
- # Offense count: 858
39
+ # Offense count: 1103
37
40
  # This cop supports safe autocorrection (--autocorrect).
38
41
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
39
42
  # URISchemes: http, https
40
43
  Layout/LineLength:
41
44
  Enabled: false
42
45
 
43
- # Offense count: 48
46
+ # Offense count: 55
44
47
  # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
45
48
  Lint/DuplicateBranch:
46
49
  Enabled: false
@@ -80,33 +83,43 @@ Lint/UnusedMethodArgument:
80
83
  - 'lib/canon/diff_formatter/by_line/xml_formatter.rb'
81
84
  - 'lib/canon/diff_formatter/by_object/base_formatter.rb'
82
85
 
83
- # Offense count: 238
86
+ # Offense count: 1
87
+ Lint/UselessConstantScoping:
88
+ Exclude:
89
+ - 'lib/canon/diff_formatter/theme.rb'
90
+
91
+ # Offense count: 298
84
92
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
85
93
  Metrics/AbcSize:
86
94
  Enabled: false
87
95
 
88
- # Offense count: 22
96
+ # Offense count: 29
89
97
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
90
98
  # AllowedMethods: refine
91
99
  Metrics/BlockLength:
92
- Max: 85
100
+ Max: 92
93
101
 
94
- # Offense count: 198
102
+ # Offense count: 1
103
+ # Configuration parameters: CountBlocks, CountModifierForms.
104
+ Metrics/BlockNesting:
105
+ Max: 4
106
+
107
+ # Offense count: 262
95
108
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
96
109
  Metrics/CyclomaticComplexity:
97
110
  Enabled: false
98
111
 
99
- # Offense count: 413
112
+ # Offense count: 485
100
113
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
101
114
  Metrics/MethodLength:
102
- Max: 104
115
+ Max: 146
103
116
 
104
- # Offense count: 46
117
+ # Offense count: 56
105
118
  # Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
106
119
  Metrics/ParameterLists:
107
- Max: 9
120
+ Max: 10
108
121
 
109
- # Offense count: 162
122
+ # Offense count: 213
110
123
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
111
124
  Metrics/PerceivedComplexity:
112
125
  Enabled: false
@@ -130,11 +143,12 @@ Naming/VariableNumber:
130
143
  - 'lib/canon/comparison/markup_comparator.rb'
131
144
  - 'lib/canon/comparison/xml_comparator/diff_node_builder.rb'
132
145
 
133
- # Offense count: 3
146
+ # Offense count: 4
134
147
  # Configuration parameters: MinSize.
135
148
  Performance/CollectionLiteralInLoop:
136
149
  Exclude:
137
150
  - 'lib/canon/comparison/html_comparator.rb'
151
+ - 'lib/canon/diff_formatter/theme.rb'
138
152
  - 'lib/canon/xml/xml_base_handler.rb'
139
153
  - 'spec/canon/diff/diff_node_mapper_comments_spec.rb'
140
154
 
@@ -144,7 +158,7 @@ Performance/CollectionLiteralInLoop:
144
158
  RSpec/ContextWording:
145
159
  Enabled: false
146
160
 
147
- # Offense count: 30
161
+ # Offense count: 34
148
162
  # Configuration parameters: IgnoredMetadata.
149
163
  RSpec/DescribeClass:
150
164
  Enabled: false
@@ -155,10 +169,10 @@ RSpec/DescribeMethod:
155
169
  - 'spec/canon/comparison/multiple_differences_spec.rb'
156
170
  - 'spec/canon/diff_formatter/character_map_customization_spec.rb'
157
171
 
158
- # Offense count: 707
172
+ # Offense count: 741
159
173
  # Configuration parameters: CountAsOne.
160
174
  RSpec/ExampleLength:
161
- Max: 43
175
+ Max: 44
162
176
 
163
177
  # Offense count: 8
164
178
  # This cop supports safe autocorrection (--autocorrect).
@@ -207,7 +221,7 @@ RSpec/MultipleDescribes:
207
221
  Exclude:
208
222
  - 'spec/canon/comparison/match_options_spec.rb'
209
223
 
210
- # Offense count: 546
224
+ # Offense count: 595
211
225
  RSpec/MultipleExpectations:
212
226
  Max: 15
213
227
 
@@ -240,10 +254,11 @@ RSpec/NoExpectationExample:
240
254
  - 'spec/canon/isodoc_blockquotes_spec.rb'
241
255
  - 'spec/canon/match_scenarios_spec.rb'
242
256
 
243
- # Offense count: 2
257
+ # Offense count: 4
244
258
  RSpec/RepeatedExample:
245
259
  Exclude:
246
260
  - 'spec/canon/comparison/encoding_normalization_spec.rb'
261
+ - 'spec/canon/diff_display_spec.rb'
247
262
 
248
263
  # Offense count: 7
249
264
  # Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata, InflectorPath, EnforcedInflector.
@@ -270,7 +285,7 @@ RSpec/VerifiedDoubles:
270
285
  - 'spec/canon/diff_formatter/diff_detail_formatter_spec.rb'
271
286
  - 'spec/canon/tree_diff/operation_converter_spec.rb'
272
287
 
273
- # Offense count: 1
288
+ # Offense count: 2
274
289
  # This cop supports safe autocorrection (--autocorrect).
275
290
  # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods.
276
291
  # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
@@ -279,7 +294,8 @@ RSpec/VerifiedDoubles:
279
294
  # AllowedMethods: lambda, proc, it
280
295
  Style/BlockDelimiters:
281
296
  Exclude:
282
- - 'spec/canon/diff/diff_node_mapper_comments_spec.rb'
297
+ - 'lib/canon/tree_diff/matchers/hash_matcher.rb'
298
+ - 'spec/canon/hash_matcher_regression_spec.rb'
283
299
 
284
300
  # Offense count: 1
285
301
  # This cop supports safe autocorrection (--autocorrect).
@@ -309,3 +325,11 @@ Style/IdenticalConditionalBranches:
309
325
  Style/OptionalBooleanParameter:
310
326
  Exclude:
311
327
  - 'lib/canon/diff_formatter/debug_output.rb'
328
+
329
+ # Offense count: 1
330
+ # This cop supports safe autocorrection (--autocorrect).
331
+ # Configuration parameters: AllowedMethods.
332
+ # AllowedMethods: infinite?, nonzero?
333
+ Style/RedundantCondition:
334
+ Exclude:
335
+ - '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: