#!/usr/bin/env perl

##
## sdif: sdiff clone
##
## Copyright (c) 1992- Kazumasa Utashiro
##
## Original version on Jul 24 1991
##

=pod

=head1 NAME

sdif - side-by-side diff viewer for ANSI terminal

=head1 VERSION

Version 4.49

=head1 SYNOPSIS

    sdif file_1 file_2

    diff ... | sdif

  OPTIONS:

    -i, --ignore-case
    -b, --ignore-space-change
    -w, --ignore-all-space
    -B, --ignore-blank-lines

    --[no]number, -n    print line number
    --digit=#           set the line number digits (default 4)
    --truncate, -t      truncate long line
    --boundary=#        line folding boundary (default word)
    --context, -c, -C#  context diff
    --unified, -u, -U#  unified diff

    --width=#, -W#      width of output (default 80)
    --margin=#          margin column number (default 0)
    --runin=#           run-in column number (default --margin)
    --runout=#          run-out column number (default --margin)
    --mark=position     mark position (right, left, center, side) or no
    --column=order      column order (default ONM)
    --view, -v          viewer mode
    --parallel[=#], -V  treat unknown text as common part (default 2)
    --ambiguous=s       ambiguous character width (detect, wide, narrow)
    --[no-]command      print diff control command (default on)
    --[no-]filename     print diff filename (default on)
    --[no-]prefix       process git --graph output (default on)
    --prefix-pattern    prefix pattern

    --color=when        'always' (default), 'never' or 'auto'
    --nocolor           --color=never
    --colormap, --cm    specify color map
    --colortable[=#]    show color table (optional #: 6, 12, 24)
    --[no-]256          on/off ANSI 256 color mode (default on)
    --[no-]cc           color command line  (default true)
    --[no-]fc                 file name     (default true)
    --[no-]lc                 line number   (default true)
    --[no-]mc                 diff mark     (default true)
    --[no-]tc                 normal text   (default true)
    --[no-]uc                 unknown text  (default true)

    --man               display manual page
    --version           show version
    --diff=s            set diff command
    --diffopts=s        set diff command options

    --[no-]lenience     suppress unexpected input warning (default on)
    --limit key=val     set limit (line, length)
    --visible xx=1      set visible chars
    --tabhead=char      set tabhead char
    --tabspace=char     set tabspace char
    --tabstyle=style    set tabstyle (dot, symbol, shade, bar, dash...)
    --tabstop=#         set tabstop width (default 8)

    --[no-]cdif         use ``cdif'' as word context diff backend
    --unit=s            pass through to cdif (word, letter, char, mecab)
    --cdifopts=s        set cdif command options

=cut

use v5.14;
use warnings;
use utf8;
use Encode;
use open IO => ':utf8';
use Carp;
use charnames ':full';
use List::Util qw(min max reduce sum pairmap first);
use Pod::Usage;
use Text::ParseWords qw(shellwords);
use Data::Dumper; {
    $Data::Dumper::Terse = 1;
}

use App::sdif;
my $version = $App::sdif::VERSION;

use App::sdif::Util;
use IO::Divert;

my $default_cdif = 'cdif';
my @default_cdifopts = qw(--sdif);
my $default_lenience = 1;
my $default_256 = 1;
my $default_prefix = 1;
my $default_prefix_pattern = q/(?:\\| )*(?:  )?/;
my @cdifopts;
my $read_stdin;

our $screen_width;

if (my $env = $ENV{'SDIFOPTS'}) {
    unshift @ARGV, shellwords($env);
}

use open IO => ':utf8', ':std';
map { $_ = decode 'utf8', $_ unless utf8::is_utf8($_) } @ARGV;

my $app;

use Getopt::EX::Hashed; {

    Getopt::EX::Hashed->configure( DEFAULT => [ is => 'rw' ] );

    has help           => ' h   ' ;
    has version        => '     ' ;
    has man            => '     ' ;
    has debug          => 'd +  ' ;
    has number         => 'n !  ' ;
    has digit          => '  =i ' , default => 4 ;
    has column         => '  =s ' ;
    has truncate       => 't !  ' ;
    has boundary       => '  =s ' , default => 'word' ;
    has onword         => '  !  ' ;
    has mark           => '  =s ' , default => 'center' ;
    has prefix         => '  !  ' , default => $default_prefix ;
    has prefix_pattern => '  =s ' , default => $default_prefix_pattern ;
    has width          => 'W =i ' ;
    has margin         => '  =i ' , default => 0 ;
    has runin          => '  =i ' ;
    has runout         => '  =i ' ;
    has view           => 'v !  ' ;
    has parallel       => 'V :2 ' , default => 0 ;
    has ambiguous      => '  =s ' , default => 'narrow' ;
    has filename       => '  !  ' , default => 1 ;
    has command        => '  !  ' , default => 1 ;
    has diff           => '  =s ' , default => 'diff' ;
    has diffopts       => '  =s@' , default => [] ;
    has color          => '  =s ' , default => 'always' ;
    has colormap       => 'cm=s@' , default => [] ;
    has colordump      => '     ' ;
    has 256            => '  !  ' , default => $default_256 ;
    has commandcolor   => 'cc!  ' , default => 1 ;
    has filecolor      => 'fc!  ' , default => 1 ;
    has linecolor      => 'lc!  ' , default => 1 ;
    has markcolor      => 'mc!  ' , default => 1 ;
    has textcolor      => 'tc!  ' , default => 1 ;
    has unknowncolor   => 'uc!  ' , default => 1 ;
    has cdif           => '  :s ' , default => '' ;
    has cdifopts       => '  =s ' ;
    has colortable     => '  :s ' , any => qr/^(|6|12|24)$/ ;
    has lenience       => '  !  ' , default => $default_lenience ;
    has limit          => '  =s%' , default => { length => 10000 } ;
    has visible        => '  =i%' , default => {} ;
    has tabstop        => '  =i ' , default => 8 ;
    has tabhead        => '  =s ' ;
    has tabstyle       => 'ts=s ' ;
    has tabspace       => '  =s ' ;
    has unit           => 'by:s ' ;

    has ignore_case         => 'i    ' ;
    has ignore_space_change => 'b    ' ;
    has ignore_all_space    => 'w    ' ;
    has ignore_blank_lines  => 'B    ' ;
    has context             => 'C  =i' ;
    has unified             => 'U  =i' ;
    has c                   => '     ' ;
    has u                   => '     ' ;

    has '+onword'
	=> sub { $_->boundary = $_[1] ? 'word' : '' } ;

    has '+cdifopts'
	=> sub { push @cdifopts, shellwords $_[1] } ;

    has '+ignore_case'
	=> sub { push @{$app->diffopts}, '-i'; push @cdifopts, '-i' } ;
    has '+ignore_space_change'
	=> sub { push @{$app->diffopts}, '-b'; push @cdifopts, '-w' } ;
    has '+ignore_all_space'
	=> sub { push @{$app->diffopts}, '-w'; push @cdifopts, '-w' } ;
    has '+ignore_blank_lines'
	=> sub { push @{$app->diffopts}, '-B' } ;

    has '+c'       => sub { push @{$app->diffopts}, '-c' } ;
    has '+u'       => sub { push @{$app->diffopts}, '-u' } ;
    has '+context' => sub { push @{$app->diffopts}, '-C' . $_[1] } ;
    has '+unified' => sub { push @{$app->diffopts}, '-U' . $_[1] } ;

    has nocolor => 'no-color' , action => sub { $app->color = 'never' } ;
    has nocdif  => 'no-cdif'  , action => sub { $app->cdif = undef } ;

    has mecab   => '!' , action => sub { $app->unit = $_[1] ? 'mecab' : undef } ;

    has '+ambiguous' => sub {
	if ($_[1] =~ /^(?:wide|full)/) {
	    $Text::VisualWidth::PP::EastAsian = 1;
	    Text::ANSI::Fold->configure(ambiguous => 'wide');
	}
    } ;

    has '+help'    => sub {
	pod2usage
	    -verbose  => 99,
	    -sections => [ qw(SYNOPSIS VERSION) ];
    } ;
    has '+version' => sub { print "$version\n"; exit 0 } ;
    has '+man'     => sub { pod2usage {-verbose => 2} } ;

} no Getopt::EX::Hashed;

$app = Getopt::EX::Hashed->new() or die;

my @SAVEDARGV = @ARGV;
use Getopt::EX::Long qw(:DEFAULT Configure ExConfigure);
ExConfigure BASECLASS => [ "App::sdif", "Getopt::EX" ];
Configure "bundling";
$app->getopt or usage({status => 1});

warn "\@ARGV = (@SAVEDARGV)\n" if $app->debug;

$App::sdif::Util::NO_WARNINGS = $app->lenience;

use Text::VisualWidth::PP qw(vwidth);
use Text::ANSI::Fold qw(ansi_fold :constants); {
    Text::ANSI::Fold->configure(padding => 1,
				expand  => 1,
				tabstop => $app->tabstop);
}

$app->visible->{ht} //= 1 if $app->tabstyle;
if ($app->visible->{ht}) {
    Text::ANSI::Fold->configure(
	tabstyle => $app->tabstyle,
	map  { $_->[0] => unicode($_->[1]) }
	grep { $_->[1] }
	[ tabhead  => $app->tabhead  ],
	[ tabspace => $app->tabspace ],
	);
}

sub unicode {
    my $char = shift or return undef;
    if ($char =~ /^\X$/) {
	$char;
    } else {
	eval qq["\\N{$char}"] or die "$!";
    }
}

if ($app->margin > 0) {
    if ($app->runin and $app->margin < $app->runin) {
	die "margin must be >= runin\n";
    }
    Text::ANSI::Fold->configure(
	linebreak => LINEBREAK_ALL,
	margin    => $app->margin,
	runin     => $app->runin // $app->margin,
	runout    => $app->runout // $app->margin,
	);
}

my %colormap = do {
    my $col = $app->{256} ? 0 : 1;
    pairmap { $a => (ref $b eq 'ARRAY') ? $b->[$col] : $b } (
	UNKNOWN  =>    ""         ,
	OCOMMAND =>  [ "555/010"  , "GS"  ],
	NCOMMAND =>  [ "555/010"  , "GS"  ],
	MCOMMAND =>  [ "555/010"  , "GS"  ],
	OFILE    =>  [ "551/010D" , "GDS" ],
	NFILE    =>  [ "551/010D" , "GDS" ],
	MFILE    =>  [ "551/010D" , "GDS" ],
	OMARK    =>  [ "010/444"  , "G/W" ],
	NMARK    =>  [ "010/444"  , "G/W" ],
	MMARK    =>  [ "010/444"  , "G/W" ],
	UMARK    =>    ""         ,
	OLINE    =>  [ "220"      , "Y"   ],
	NLINE    =>  [ "220"      , "Y"   ],
	MLINE    =>  [ "220"      , "Y"   ],
	ULINE    =>    ""         ,
	OTEXT    =>  [ "K/454"    , "G"   ],
	NTEXT    =>  [ "K/454"    , "G"   ],
	MTEXT    =>  [ "K/454"    , "G"   ],
	UTEXT    =>    ""         ,
	NOTICE   =>    "R"        ,
    );
};

use Getopt::EX::Colormap;
$Getopt::EX::Colormap::NO_RESET_EL = 1;
use constant SGR_RESET => "\e[m";
my $color_handler = Getopt::EX::Colormap
    ->new(HASH => \%colormap)
    ->load_params(@{$app->colormap});

$colormap{OUMARK} ||= $colormap{UMARK} || $colormap{OMARK};
$colormap{NUMARK} ||= $colormap{UMARK} || $colormap{NMARK};
$colormap{OULINE} ||= $colormap{ULINE} || $colormap{OLINE};
$colormap{NULINE} ||= $colormap{ULINE} || $colormap{NLINE};

for (
    [ $app->unknowncolor => q/UNKNOWN/ ],
    [ $app->commandcolor => q/COMMAND/ ],
    [ $app->filecolor    => q/FILE/ ],
    [ $app->linecolor    => q/LINE/ ],
    [ $app->markcolor    => q/MARK/ ],
    [ $app->textcolor    => q/TEXT/ ],
) {
    my($color, $label) = @$_;
    $color and next;
    for (grep /$label/, keys %colormap) {
	$colormap{$_} = '';
    }
}

if ($app->colordump) {
    print $color_handler->colormap(
	name => '--changeme', option => '--colormap');
    exit;
}

my $painter = do {
    if (($app->color eq 'always')
	or (($app->color eq 'auto') and (-t STDOUT))) {
	sub { $color_handler->color(@_) };
    } else {
	sub { $_[1] } ;
    }
};

##
## setup cdif command and option
##
if (defined $app->cdif and $app->cdif eq '') {
    $app->cdif = $default_cdif;
}

for (
    [ "unit"     , "=" , $app->unit     , undef             ] ,
    [ "256"      , "!" , $app->{256}    , $default_256      ] ,
    [ "prefix"   , "!" , $app->prefix   , $default_prefix   ] ,
    [ "lenience" , "!" , $app->lenience , $default_lenience ] ,
    )
{
    my($name, $type, $var, $default) = @$_;
    if ($type eq "!") {
	next if not defined $var;
	next if $var == $default;
	unshift @cdifopts, sprintf("--%s%s", $var ? '' : 'no-', $name);
    } elsif ($type eq "=") {
	next if not defined $var;
	unshift @cdifopts, sprintf("--%s=%s", $name, $var);
    } else {
	die;
    }
}

unshift @cdifopts, @default_cdifopts;

my($OLD, $NEW, $DIFF);
if (@ARGV == 2) {
    ($OLD, $NEW) = @ARGV;
    $DIFF = "$app->{diff} @{$app->{diffopts}} $OLD $NEW |";
} elsif (@ARGV < 2) {
    $DIFF = shift || '-';
    $read_stdin++;
} else {
    usage({status => 1}, "Unexpected arguments.\n\n");
}
my $readfile =
    ($OLD and $NEW) && !$read_stdin && !(grep { /^-[cuCU]/ } @{$app->diffopts});

use constant {
    RIGHT => 'right',
    LEFT  => 'left',
    NO    => 'no',
};
my %markpos = (
    center => [ RIGHT , LEFT  , LEFT  ],
    side   => [ LEFT  , RIGHT , LEFT  ],
    right  => [ RIGHT , RIGHT , RIGHT ],
    left   => [ LEFT  , LEFT  , LEFT  ],
    no     => [ NO    , NO    , NO    ],
    none   => [ NO    , NO    , NO    ],
    );
unless ($markpos{$app->mark}) {
    my @keys = sort keys %markpos;
    usage "Use one from (@keys) for option --mark\n\n";
}
my @markpos = @{$markpos{$app->mark}};
my($omarkpos, $nmarkpos, $mmarkpos) = @markpos;

my $num_format = sprintf '%%%dd', $app->digit;

$screen_width = $app->width || &terminal_width;

sub column_width {
    my $column = shift;
    state %column_width;
    $column_width{$screen_width * 1000 + $column} //= do {
	use integer;
	my $w = $screen_width;
	$w -= $column if $app->mark;
	max 1, $w / $column;
    };
}

##
## --colortable
##
if (defined(my $n = $app->colortable)) {
    no strict 'refs';
    &{"Getopt::EX::Colormap::colortable$n"}($screen_width);
    exit;
}

##
## Column order
##
my @column = !$app->column ? () : do {
    map { $_ - 1 }
    map { { O=>1, N=>2, M=>3 }->{$_} // $_ }
    $app->column =~ /[0-9ONM]/g;
};

##
## Git --graph prefix pattern
##
my $prefix_re = do {
    if ($app->prefix) {
	qr/$app->{prefix_pattern}/;
    } else {
	"";
    }
};

if ($app->debug) {
    printf STDERR "\$OLD = %s\n", $OLD // "undef";
    printf STDERR "\$NEW = %s\n", $NEW // "undef";
    printf STDERR "\$DIFF = %s\n", $DIFF // "undef";
}

if ($app->cdif) {
    my $pid = open DIFF, '-|';
    if (not defined $pid) {
	die "$!" if not defined $pid;
    }
    ## child
    elsif ($pid == 0) {
	if ($DIFF ne '-') {
	    open(STDIN, $DIFF) || die "cannot open diff: $!\n";
	}
	do { exec shellwords($app->cdif), @cdifopts } ;
	warn "exec failed: $!";
	print while <>;
	exit;
    }
    ## parent
    else {
	## nothing to do
    }
} else {
    open(DIFF, $DIFF) || die "cannot open diff: $!\n";
}

if ($readfile) {

    binmode DIFF, ':raw';
    my $DIFFOUT = do { local $/; <DIFF> };
    close DIFF;
    open  DIFF, '<', \$DIFFOUT or die;

    open OLD, $OLD or die "$OLD: $!\n";
    open NEW, $NEW or die "$NEW: $!\n";

    # For reading /dev/fd/*
    seek OLD, 0, 0 or die unless -p OLD;
    seek NEW, 0, 0 or die unless -p NEW;
}

my @boundary = (boundary => $app->boundary);
my $color_re = qr{ \e \[ [\d;]* [mK] }x;

our $oline = 1;
our $nline = 1;
our $mline = 1;

while (<DIFF>) {
    #
    # normal diff
    #
    if (/^([\d,]+)([adc])([\d,]+)$/) {
	my(@old, @new);
	my($left, $ctrl, $right) = ($1, $2, $3);
	my($l1, $l2) = range($left);
	my($r1, $r2) = range($right);
	if ($readfile) {
	    my $identical_line = $l1 - $oline + 1 - ($ctrl ne 'a');
	    print_identical($identical_line);
	}
	if ($app->debug || $read_stdin) {
	    print_command_n($_, $_);
	}
	if ($ctrl eq 'd' || $ctrl eq 'c') {
	    ($oline) = $left =~ /^(\d+)/;
	    my $n = $l2 - $l1 + 1;
	    @old = read_line(*DIFF, $n);
	    $readfile and read_line(*OLD, $n);
	}
	read_line(*DIFF, 1) if $ctrl eq 'c';
	if ($ctrl eq 'a' || $ctrl eq 'c') {
	    ($nline) = $right =~ /^(\d+)/;
	    my $n = $r2 - $r1 + 1;
	    @new = read_line(*DIFF, $n);
	    $readfile and read_line(*NEW, $n);
	}
	map {
	    s/^([<>])\s?/{'<' => '-', '>' => '+'}->{$1}/e
	} @old, @new;
	flush_buffer([], \@old, \@new);
    }
    #
    # context diff
    #
    elsif (/^\*\*\* /) {
	my $next = <DIFF>;
	print_command_n({ type => 'FILE' }, $_, $next);
    }
    elsif ($_ eq "***************\n") {
	my(@old, @new);
	my $ohead = $_ = <DIFF>;
	my($left, $right);
	unless (($left) = /^\*\*\* ([\d,]+) \*\*\*\*$/) {
	    print;
	    next;
	}
	my $oline = range($left);
	my $dline = 0;
	my $cline = 0;
	my $nhead = $_ = <DIFF>;
	unless (($right) = /^--- ([\d,]+) ----$/) {
	    @old = read_line(*DIFF, $oline - 1, $nhead);
	    $nhead = $_ = <DIFF>;
	    unless (($right) = /^--- ([\d,]+) ----$/) {
		print $ohead, @old, $_;
		next;
	    }
	    for (@old) {
		/^-/ and ++$dline;
		/^!/ and ++$cline;
	    }
	}
	my $nline = range($right);
	if (@old == 0 or $cline != 0 or ($oline - $dline != $nline)) {
	    @new = read_line(*DIFF, $nline);
	}
	print_command_n($ohead, $nhead);
	($oline) = $left =~ /^(\d+)/;
	($nline) = $right =~ /^(\d+)/;

	my @buf = merge_diffc(\@old, \@new);
	flush_buffer(@buf);
    }
    #
    # unified diff
    #
    elsif (/^($prefix_re)(--- (?s:.*))/) {
	my($prefix, $left) = ($1, $2);
	my $right = <DIFF>;
	local $screen_width = $screen_width;
	if ($prefix) {
	    $right =~ s/^\Q$prefix//;
	    print $prefix;
	    $screen_width -= length $prefix;
	}
	print_command_n({ type => 'FILE' }, $left, $right);
    }
    elsif (m{^
	   (?<prefix>$prefix_re)
	   (?<command>
	     \@\@ [ ]
	     \-(?<oline>\d+) (?:,(?<o>\d+))? [ ]
	     \+(?<nline>\d+) (?:,(?<n>\d+))? [ ]
	     \@\@
	     (?s:.*)
	   )
	   }x) {
	($oline, $nline) = @+{qw(oline nline)};
	my($o, $n) = ($+{o}//1, $+{n}//1);
	my($prefix, $command) = @+{qw(prefix command)};

	local $screen_width = $screen_width;
	my($divert, %read_opt);
	if ($prefix) {
	    $screen_width -= length $prefix;
	    $read_opt{prefix} = $prefix;
	    $divert = IO::Divert->new(FINAL => sub { s/^/$prefix/mg });
	}

	print_command_n({ type => 'COMMAND' }, $command, $command);
	my @buf = read_unified_2 \%read_opt, *DIFF, $o, $n;

	flush_buffer(@buf);
    }
    #
    # diff --combined (only support 3 files)
    #
    elsif (/^diff --(?:cc|combined)/) {
	my @lines = ($_);
	push @lines, read_until { /^\+\+\+/ } *DIFF;
	if (not defined $lines[-1]) {
	    pop @lines;
	    print @lines;
	    next;
	}
	print @lines;
    }
    elsif (/^\@{3} -(\d+)(?:,(\d+))? -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? \@{3}/)  {
	print_command_n({ type => 'COMMAND' }, $_, $_, $_);

	($oline, $nline, $mline) = ($1, $3, $5);
	state $read_unified_3 = read_unified_sub(3);
	my @buf = $read_unified_3->(*DIFF, $2 // 1, $4 // 1, $6 // 1);
	flush_buffer_3(@buf);
    }
    #
    # conflict marker
    #
    elsif (/^<<<<<<<\h*+(.*)/) {
      CONFLICT:
	{
	    my %name = (o => $_);
	    my($c1, $c2, $c3, $c4);
	    $c1 = $_;

	    my @old = read_until { /^=======$/ } *DIFF;
	    $c2 = pop @old // do {
		flush_unknown($c1, @old);
		last;
	    };

	    my @new = read_until { /^>>>>>>>\h*+(.*)/ } *DIFF;
	    $c4 = pop @new // do {
		flush_unknown($c1, @old, $c2, @new);
		last;
	    };
	    $name{n} = $c4;

	    my @mrg;
	    my $mrg = first { $old[$_] =~ /^\Q|||||||\E\h*+(.*)/ } keys @old;
	    if (defined $mrg or $app->parallel > 2) {
		if (defined $mrg) {
		    $name{m} = $old[$mrg];
		    ($c3, @mrg) = splice @old, $mrg;
		} else {
		    $name{m} = $name{o};
		    $name{o} = $name{n};
		    @mrg = @old;
		    @old = @new;
		}
		s/^/--/ for @old, @mrg;
		s/^/++/ for @new;
		print_command_n({ type => 'FILE' }, @name{qw(o m n)});
		flush_buffer_3([], \@old, \@mrg, \@new);
	    } else {
		s/^/-/ for @old;
		s/^/+/ for @new;
		print_command_n({ type => 'FILE' }, @name{qw(o n)});
		flush_buffer([], \@old, \@new);
	    }
	}
    }
    #
    # #ifdef            custom container
    # 
    #   #ifdef JA         ::::::: ja
    #   japanese text     japanese text
    #   #endif            :::::::
    #   #ifdef EN         ::::::: en
    #   english text      english text
    #   #endif            :::::::
    #
    elsif (/^(\#ifdef|:{7,})\h++(.*)/) {
      COLON:
	{
	    my($c1, $c2, $c3, $c4) = ($_);
	    my $start = $1;
	    my $end = $start eq '#ifdef' ? qr/^#endif$/ : qr/^$start$/;
	    my($m1, $m2) = ($2);

	    my @old = read_until { /$end/ } *DIFF;
	    $c2 = pop @old // do {
		flush_unknown($c1, @old);
		last;
	    };
	    $c3 = <DIFF>;
	    if ($c3 !~ /^${start}\h++(.*)/) {
		flush_unknown($c1, @old, $c2, $c3);
		last;
	    }
	    $m2 = $1;
	    my @new = read_until { /$end/ } *DIFF;
	    $c4 = pop @new // do {
		flush_unknown($c1, @old, $c2, $c3, @new);
		last;
	    };

	    @old = (" $c1", map(s/^/-/r, @old), " $c2");
	    @new = (" $c3", map(s/^/+/r, @new), " $c4");
	    flush_buffer([], \@old, \@new);
	}
    }
    else {
	flush_unknown($_);
    }
}
continue {
    STDOUT->flush;
}

close DIFF;
my $exit = $DIFF =~ /\|$/ ? $? >> 8 : 0;

if ($readfile) {
    if ($exit < 2) {
	print_identical(-1);
    }
    close OLD;
    close NEW;
}

exit($exit > 1);

######################################################################

##
## Convert diff -c output to -u compatible format.
##
sub merge_diffc {
    my @o = @{+shift};
    my @n = @{+shift};
    for (@o, @n) {
	s/(?<= ^[ \-\+\!] ) [\t ]//x or die "Format error (-c).\n";
    }

    my @buf;
    while (@o or @n) {

	push @buf, \( my( @common, @old, @new ) );

	while (@o and $o[0] =~ /^ /) {
	    push @common, shift @o;
	    shift @n if @n;
	}
	while (@n and $n[0] =~ /^ /) {
	    push @common, shift @n;
	}

	push @old, shift @o while @o and $o[0] =~ /^\-/;
	next if @old;

	push @new, shift @n while @n and $n[0] =~ /^\+/;
	next if @new;

	push @old, shift @o while @o and $o[0] =~ s/^!/-/;
	push @new, shift @n while @n and $n[0] =~ s/^!/+/;
    }

    @buf;
}

sub flush_unknown {
    if ($app->parallel > 2) {
	flush_buffer_3( [ map s/^/  /r, @_ ] );
    }
    elsif ($app->parallel > 1) {
	flush_buffer( [ map s/^/ /r, @_ ] );
    }
    else {
	print $painter->('UNKNOWN', $_) for @_;
    }
}

sub flush_buffer {

    push @_, [] while @_ % 3;

    if ($app->view) {
	@_ = do {
	    map { @$_ }
	    reduce {
		[ [] ,
		  [ map { @$_ } $a->[1], $b->[0], $b->[1] ] ,
		  [ map { @$_ } $a->[2], $b->[0], $b->[2] ] ] }
	    map { $_ ? [ ( splice @_, 0, 3 ) ] : [ [], [], [] ] }
	    0 .. @_ / 3 ;
	};
    }

    while (my($s, $o, $n) = splice @_, 0, 3) {
	for (@$s) {
	    s/^(.)// or die;
	    print_column_23($1, $_, $1, $_);
	    $oline++;
	    $nline++;
	}

	my $max = $app->limit->{line};
	my $one_side = !@$o || !@$n;
	my $count = 0;
	while (@$o or @$n) {
	    my $old = shift @$o;
	    my $new = shift @$n;
	    my $omark = $old ? $old =~ s/^(.)// && $1 : ' ';
	    my $nmark = $new ? $new =~ s/^(.)// && $1 : ' ';

	    print_column_23($omark, $old, $nmark, $new);

	    $oline++ if defined $old;
	    $nline++ if defined $new;

	    # truncate one-sided (add/delete) section exceeding max lines
	    if ($max and $one_side and ++$count >= $max and @$o + @$n > 0) {
		my $rest = @$o + @$n;
		my $msg = $painter->(
		    'NOTICE',
		    sprintf "... %d lines omitted ...\n", $rest,
		);
		# show message on the side that has content
		# suppress line number by localizing $oline/$nline
		{
		    local($oline, $nline) = (undef, undef);
		    if (@$n) {
			print_column_23(' ', undef, ' ', $msg);
		    } else {
			print_column_23(' ', $msg, ' ', undef);
		    }
		}
		# advance line counter past omitted lines
		$oline += $rest if !@$n;
		$nline += $rest if !@$o;
		last;
	    }
	}
    }
}

sub flush_buffer_3 {

    push @_, [] while @_ % 4;

    if ($app->view) {
	@_ = do {
	    map { @$_ }
	    reduce {
		[ [] ,
		  [ map { @$_ } $a->[1], $b->[0], $b->[1] ] ,
		  [ map { @$_ } $a->[2], $b->[0], $b->[2] ] ,
		  [ map { @$_ } $a->[3], $b->[0], $b->[3] ] ] }
	    map { $_ ? [ splice @_, 0, 4 ] : [ [], [], [], [] ] }
	    0 .. @_ / 4;
	};
    }

    while (@_) {
	my @d = splice @_, 0, 4;

	for my $common (@{shift @d}) {
	    $common =~ s/^  //;
	    print_column_23(' ', $common, ' ', $common, ' ', $common);
	    $oline++;
	    $nline++;
	    $mline++;
	}

	while (first { @$_ > 0 } @d) {
	    my $old = shift @{$d[0]};
	    my $new = shift @{$d[1]};
	    my $mrg = shift @{$d[2]};
	    my $om = $old ? $old =~ s/^(?|(\-).| (\+)|( ) )// && $1 : ' ';
	    my $nm = $new ? $new =~ s/^(?|.(\-)|(\+) |( ) )// && $1 : ' ';
	    my $mm = $mrg ? $mrg =~ s/^(?|(\+).|.(\+)|( ) )// && $1 : ' ';

	    print_column_23($om, $old, $nm, $new, $mm, $mrg);

	    $oline++ if defined $old;
	    $nline++ if defined $new;
	    $mline++ if defined $mrg;
	}
    }
}

sub print_identical {
    my $n = shift;
    while ($n--) {
	my $old = <OLD>;
	my $new = <NEW>;
	defined $old or defined $new or last;
	print_column_23(' ', $old, ' ', $new);
	$oline++;
	$nline++;
	$mline++;
    }
}

sub linenum {
    my $n = shift;
    defined $n ? (sprintf $num_format, $n) : (' ' x $app->digit);
}

sub print_column_23 {
    my $column = @_ / 2;
    my $width = column_width $column;
    my($omark, $old, $nmark, $new, $mmark, $mrg) = @_;
    my $print_number = $app->number;

    my($onum, $nnum, $mnum) = ('', '', '');
    my $nspace = $print_number ? ' ' : '';
    if (defined $old) {
	$old =~ s/\R\z//;
	$onum = linenum($oline) if $print_number;
    }
    if (defined $new) {
	$new =~ s/\R\z//;
	$nnum = linenum($nline) if $print_number;
    }
    if (defined $mrg) {
	$mrg =~ s/\R\z//;
	$mnum = linenum($mline) if $print_number;
    }

    # truncate long lines before processing
    if (my $max = $app->limit->{length}) {
	for ($old, $new, $mrg) {
	    defined and length > $max or next;
	    state $fold = Text::ANSI::Fold->new;
	    my $orig_len = length;
	    ($_, undef) = $fold->fold($_, width => $max);
	    my $truncated = $orig_len - length;
	    $_ .= $painter->('NOTICE',
			      sprintf " ... %d characters omitted ...", $truncated);
	}
    }

    my($OTEXT, $OLINE, $OMARK) =
	$omark =~ /\S/ ? qw(OTEXT OLINE OMARK) : qw(UTEXT OULINE OUMARK);
    my($NTEXT, $NLINE, $NMARK) =
	$nmark =~ /\S/ ? qw(NTEXT NLINE NMARK) : qw(UTEXT NULINE NUMARK);
    my($MTEXT, $MLINE, $MMARK) =
	$mmark =~ /\S/ ? qw(MTEXT MLINE MMARK) : qw(UTEXT NULINE NUMARK)
	if $column >= 3;

    while (1) {
	(my $o, $old) = ansi_fold($old,
				  max(1, $width - length($onum . $nspace)),
				  @boundary);
	(my $n, $new) = ansi_fold($new,
				  max(1, $width - length($nnum . $nspace)),
				  @boundary);
	(my $m, $mrg) = ansi_fold($mrg,
				  max(1, $width - length($mnum . $nspace)),
				  @boundary)
	    if $column >= 3;

	my @f;
	$f[0]{MARK} = $painter->($OMARK, $omark);
	$f[0]{LINE} = $painter->($OLINE, $onum) . $nspace if $print_number;
	$f[0]{TEXT} = $painter->($OTEXT, $o) if $o ne "";
	$f[1]{MARK} = $painter->($NMARK, $nmark);
	$f[1]{LINE} = $painter->($NLINE, $nnum) . $nspace if $print_number;
	$f[1]{TEXT} = $painter->($NTEXT, $n) if $n ne "";
	if ($column >= 3) {
	    $f[2]{MARK} = $painter->($MMARK, $mmark);
	    $f[2]{LINE} = $painter->($MLINE, $mnum) . $nspace if $print_number;
	    $f[2]{TEXT} = $painter->($MTEXT, $m) if $m ne "";
	}
	print_field_n(@f);

	last if $app->truncate;
	last unless $old ne '' or $new ne '' or ($mrg and $mrg ne '');

	if ($print_number) {
	    $onum =~ s/./ /g;
	    $nnum =~ s/./ /g;
	    $mnum =~ s/./ /g if $column >= 3;
	}
	$omark = $old ne '' ? '.' : ' ';
	$nmark = $new ne '' ? '.' : ' ';
	$mmark = $mrg ne '' ? '.' : ' ' if $column >= 3;
    }
}

sub print_command_n {
    my $opt = ref $_[0] ? shift : {};
    my $column = @_;
    my $width = column_width $column;
    my @f;

    $opt->{type} //= 'COMMAND';

    $app->command  or return if $opt->{type} eq 'COMMAND';
    $app->filename or return if $opt->{type} eq 'FILE';

    my @color = map { $_ . $opt->{type} } "O", "N", "M";

    for my $i (keys @_) {
	local $_ = $_[$i];
	chomp if defined;
	($_) = ansi_fold($_, $width);
	my %f;
	my $color = $i < @color ? $color[$i] : $color[-1];
	$f{TEXT} = $painter->($color, $_);
	$f{MARK} = ' ';
	push @f, \%f;
    }

    print_field_n(@f);
}

sub print_field_n {
    if (@column >= @_) {
	@_ = @_[ @column[ keys @_ ] ];
    }
    while (my($i, $f) = each @_) {
	my $markpos = $i < @markpos ? $markpos[$i] : $markpos[-1];
	local $_;
	$_ = $f->{"MARK"} and print if $markpos eq LEFT;
	$_ = $f->{"LINE"} and print;
	$_ = $f->{"TEXT"} and print;
	$_ = $f->{"MARK"} and print if $markpos eq RIGHT;
    }
    print "\n";
}

__END__

=pod

=head1 DESCRIPTION

B<sdif> is inspired by the System V L<sdiff(1)> command.  Its basic
job is to make a side-by-side listing of two different files.  The
whole of both files is listed, one on the left and one on the right.
The center column shows how far the lines differ.  No mark means no
difference; added, deleted and modified lines are marked with a minus
C<-> or a plus C<+>; a wrapped line is marked with a period C<.>.

    1 deleted  -
    2 same          1 same
    3 changed  -+   2 modified
      wrapped  ..     folded
    4 same          3 same
                +   4 added

It also reads and formats the output of the B<diff> command from
standard input.  Besides normal diff output, context diff B<-c> and
unified diff B<-u> output are handled properly.  Combined diff and
conflict marker styles are supported too, though currently for no more
than three files.

The current implementation also supports C<#ifdef> and markdown custom
container (using seven colons) formats on an experimental basis.  This
is to support the multilingual format generated by the
L<App::Greple::xlate> module.

To simply show several files side by side, without caring how they
differ, use the L<App::ansicolumn> command.

=head2 STARTUP and MODULE

B<sdif> uses the Perl L<Getopt::EX> module, and reads the F<~/.sdifrc>
file at startup if there is one.  You can define your own options and
defaults there.  To always show the line number, define it like this:

    option default -n

Modules under B<App::sdif> can be loaded by the B<-M> option without
the prefix.  The next command loads the B<App::sdif::colors> module.

    $ sdif -Mcolors

Options can be defined in a module file as well.  See
L<Getopt::EX::Module> for details.

=head2 COLOR

Each line is displayed in a different color by default; use the
B<--no-color> option to turn that off.  Every text segment has its own
label, and the color for each label can be set by the B<--colormap>
option.  See L<Getopt::EX::Colormap> for details.

The standard module B<-Mcolors> is loaded by default, and defines
several color maps for light and dark screens.  To use CMY colors on a
dark screen, place the next line in your F<~/.sdifrc>.

    option default --dark-cmy

Option B<--autocolor> is defined in the B<default> module to call the
L<Getopt::EX::termcolor> module.  It sets the B<--light> or B<--dark>
option according to the brightness of the terminal screen.  You can
set your preferred colors in F<~/.sdifrc> like this:

    option --light --cmy
    option --dark  --dark-cmy

The detection is done by the L<Getopt::EX::termcolor> module, and
works with macOS Terminal.app and iTerm.app, and other XTerm
compatible terminals.  That module reads the environment variable
C<TERM_BGCOLOR> as the terminal background color, in the form
C<#FFFFFF>.

Option B<--autocolor> is set by default; to disable it, override it
with something that does nothing.

    option --autocolor --nop

=head2 WORD DIFFERENCE

B<sdif> itself does not care what is inside a modified line, but it
can read the output of the B<cdif> command, which shows the word
context differences within each line.  Invoke B<cdif> with the
B<--sdif> option to set everything appropriately for B<sdif>.  When
invoking B<cdif> by hand, set B<--no-cc> and B<--no-mc> at the very
least; B<--no-tc> is preferable as well, since B<sdif> can handle the
text color itself.

Since version 4.1.0 option B<--cdif> is set by default, so use
B<--no-cdif> to disable it.  Option B<--unit> (default word) is passed
through to B<cdif>.  Other B<cdif> options can be given with
B<--cdifopts>.

=head2 EXIT STATUS

B<sdif> always exits with status zero unless an error occurs.

=head1 OPTIONS

=over 7

=item B<--width>=I<width>, B<-W> I<width>

Set the width of the output listing.  The default is 80.  If standard
error is connected to a terminal, the width is taken from it when
possible.

=item B<--margin>=I<column>

=item B<--runin>=I<column>

=item B<--runout>=I<column>

Set the number of margin columns, left blank at the end of each line.
This option implicitly turns on line break control, which lets
prohibited characters run in and run out at the beginning and the end
of a line.  The margin is used for the run-in and run-out columns
unless those are given explicitly.  See L<Text::ANSI::Fold> for
details.

=item B<-n>, B<-->[B<no->]B<number>

Print line number on each line.
Default false.

=item B<-->[B<no->]B<command>

Print diff command control lines.
Default true.

=item B<-->[B<no->]B<filename>

Print filename lines.
Default true.

=item B<--digit>=I<n>

Line number is displayed in 4 digits by default.  Use this option to
change it.

=item B<-i>, B<--ignore-case>

=item B<-b>, B<--ignore-space-change>

=item B<-w>, B<--ignore-all-space>

=item B<-B>, B<--ignore-blank-lines>

=item B<-c>, B<--context>=I<n>, B<-C>I<n>

=item B<-u>, B<--unified>=I<n>, B<-U>I<n>

Passed through to the back-end diff command.  B<sdif> can interpret
the output of normal, context (B<diff -c>) and unified (B<diff -u>)
diff.

=item B<-t>, B<-->[B<no->]B<truncate>

Truncate lines if they are longer than printing width.
Default false.

=item B<--boundary>=[C<none>,C<word>,C<space>]

Set the text wrap boundary.  Set to C<word> or C<space>, text is not
wrapped in the middle of an alphanumeric word or a non-space sequence.
See L<Text::ANSI::Fold> for details.
Default is C<word>.

=item B<--onword>

Shortcut for B<--boundary=word>.  Its use is no longer recommended.
Default true.

=item B<-->[B<no->]B<cdif>[=I<command>]

Use the B<cdif> command instead of the normal diff command.  Enabled
by default; use B<--no-cdif> to turn it off explicitly.  The option
takes an optional parameter naming the B<cdif> command to run.

=item B<--cdifopts>=I<option>

Specify options for back-end B<cdif> command.

=item B<--unit>=[C<word>,C<letter>,C<char>,C<mecab>]

=item B<--by>=[C<word>,C<letter>,C<char>,C<mecab>]

=item B<--mecab>

These options are simply passed on to the back-end B<cdif> command.
Choose a value from C<word> (default), C<letter>, C<char> or C<mecab>.
Option B<--by> is an alias for B<--unit>, and B<--mecab> is a shortcut
for B<--unit=mecab>.  See the L<cdif> manual for details.

Use B<--cdifopts> to set other options.

=item B<--diff>=I<command>

Any command can be named as the diff command to use.  Unless you need
the whole text, piping the output into B<sdif> is easier.

=item B<--diffopts>=I<option>

Specify options for back-end B<diff> command.

=item B<--mark>=I<position>

Specify the position for a mark.  Choose from C<left>, C<right>,
C<center>, C<side> or C<no>.  Default is C<center>.

=item B<--column>=I<order>

Specify the order of the columns with B<O> (1: old), B<N> (2: new) and
B<M> (3: merged).  The default order is C<ONM>, or C<123>.  To show
the new file on the left and the old file on the right:

    $ sdif --column NO

The next example puts the merged file in the leftmost column for diff3
data.

    $ sdif --column MON

The next two commands produce the same output.

    $ git diff v1 v2 v3 | sdif --column 312

    $ git diff v3 v1 v2 | sdif

=item B<-->[B<no->]B<color>

Use ANSI color escape sequences in the output.  Default is true.

=item B<-->[B<no->]B<256>

Use ANSI 256 color mode.  Default is true.

=item B<--colortable>[=6,12,24]

Without a parameter, show a table of the 216 ANSI colors.

Given a parameter, display a 6x6, 12x12 or 24x24 color matrix
respectively.

=item B<-v>, B<--view>

Viewer mode.  Display each file in straightforward order.  Without
this option, unchanged lines are placed at the same position.

=item B<-V>, B<--parallel>

=item B<-V3>, B<--parallel=3>

B<sdif> processes only what looks like diff output, and prints
everything else to standard output as is.  Option C<-V> or
C<--parallel> makes it treat unknown text as common to the old and the
new data.  This is useful for reading a file that contains
L<git(1)>-compatible conflict markers.

The option takes an optional number, and 2 is assumed when it is
omitted; the only other effective value is 3.  The L<diff3(1)> command
can produce conflict marker style output with its C<-m> option, so you
can use it like this:

    $ diff3 -m A B C | sdif -V3

=item B<--ambiguous>=I<width_spec>

Specify how to treat Unicode ambiguous width characters.  The default
is C<narrow>.

=over 4

=item B<detect> or B<auto>

Detect from the user's locale, choosing C<wide> in a CJK environment.

=item B<wide> or B<full>

Treat ambiguous characters as wide.

=item B<narrow> or B<half>

Treat ambiguous characters as narrow.

=back

=item B<-->[B<no->]B<prefix>

Understand a prefix in front of the diff output, such as the one
B<git> B<--graph> produces.  True by default.

=item B<--prefix-pattern>=I<pattern>

Specify the prefix pattern as a regular expression.  Default pattern is:

    (?:\| )*(?:  )?

This pattern matches B<git> graph style and whitespace indented diff
output.

=item B<-->[B<no->]B<lenience>

Suppress the warning message for unexpected input from the diff
command.  True
by default.

=item B<--limit> I<key>=I<value>

Set resource limits.  Available keys are:

=over 4

=item B<line>=I<#>

Limit the number of lines displayed for add/delete sections.  When a
diff section contains only additions or only deletions and the number
of lines exceeds this value, only the first I<#> lines are shown and
the rest are omitted with a message indicating the number of omitted
lines.  This is useful for suppressing long diffs caused by large file
additions or deletions, for example when browsing C<git log -p>
output.

=item B<length>=I<#>

Truncate lines longer than I<#> characters before display processing.
Default is 10000.  Extremely long lines, such as SVG data with
embedded base64 images, can make display processing very slow.  Set to
0 to disable this limit.

=back

=item B<--visible> I<charname>=[0,1]

=item B<--tabhead>=I<char>

=item B<--tabspace>=I<char>

Visualize characters.  Currently only C<ht> (horizontal tab) is
supported.  Each horizontal tab is converted to a B<tabhead> character
followed by B<tabspace> characters, both of which can be set by the
B<--tabhead> and B<--tabspace> options.

    $ sdif --visible ht=1 --tabhead=T --tabspace=.

An option value longer than a single character is taken as a Unicode
character name.

    $ sdif --visible ht=1 \
           --tabhead="MEDIUM SHADE" \
           --tabspace="LIGHT SHADE"

See L<https://www.unicode.org/charts/charindex.html> for Unicode
names.

B<cdif> makes non-space control characters visible by default.  See
L<cdif/--visible>.

=item B<--tabstyle>=[C<space>,C<dot>,C<symbol>,C<shade>,C<bar>,C<dash>...]

=item B<--ts>=...

Option B<--tabstyle> (or B<--ts>) sets the B<--tabhead> and
B<--tabspace> characters at once, according to the given style name.
Select from C<space>, C<dot>, C<symbol>, C<shade>, C<bar>, C<dash> and
others.  See L<Text::ANSI::Fold/tabstyle> for the available styles.

Two styles can be combined, as in C<symbol,space>.  In that case
tabhead is taken from the C<symbol> style and tabspace from C<space>.

Setting a tabstyle implies making C<ht> visible.  To set a tabstyle by
default without making tabs visible all the time, disable it
explicitly.

    option default --tabstyle=symbol,space --visible ht=0

You can then enable it when you run the command.

    $ sdif --visible ht=1

=item B<--tabstop>=I<n>

Specify tab stop.  Default is 8.

=item B<--colormap>=I<colormap>, B<--cm>=I<colormap>

Basic I<colormap> format is :

    FIELD=COLOR

where the FIELD is one from these :

    OLD       NEW       MERGED    UNCHANGED
    --------- --------- --------- ---------
    OCOMMAND  NCOMMAND  MCOMMAND           : Command line
    OFILE     NFILE     MFILE              : File name
    OMARK     NMARK     MMARK     UMARK    : Mark
    OLINE     NLINE     MLINE     ULINE    : Line number
    OTEXT     NTEXT     MTEXT     UTEXT    : Text

If UMARK and/or ULINE is empty, OMARK/NMARK and/or OLINE/NLINE are
used instead.

You can give several fields the same color by joining them with = :

    FIELD1=FIELD2=...=COLOR

A wildcard can also be used for the field name :

    *CHANGE=BDw

Multiple fields can be specified by repeating the option :

    --cm FIELD1=COLOR1 --cm FIELD2=COLOR2 ...

or combined with comma (,) :

    --cm FIELD1=COLOR1,FIELD2=COLOR2, ...

Color specification is a combination of single uppercase character
representing 8 colors :

    R  Red
    G  Green
    B  Blue
    C  Cyan
    M  Magenta
    Y  Yellow
    K  Black
    W  White

and alternative (usually brighter) colors in lowercase :

    r, g, b, c, m, y, k, w

or RGB values and 24 grey levels on an ANSI 256-color or full-color
terminal :

    (255,255,255)      : 24bit decimal RGB colors
    #000000 .. #FFFFFF : 24bit hex RGB colors
    #000    .. #FFF    : 12bit hex RGB 4096 colors
    000 .. 555         : 6x6x6 RGB 216 colors
    L00 .. L25         : Black (L00), 24 grey levels, White (L25)

or color names enclosed in angle brackets :

    <red> <blue> <green> <cyan> <magenta> <yellow>
    <aliceblue> <honeydew> <hotpink> <moccasin>
    <medium_aqua_marine>

with other special effects :

    D  Double-struck (boldface)
    I  Italic
    U  Underline
    S  Stand-out (reverse video)

The above is a simplified summary; see L<Getopt::EX::Colormap> for the
complete specification.

Defaults are :

    OCOMMAND => "555/010"  or "GS"
    NCOMMAND => "555/010"  or "GS"
    MCOMMAND => "555/010"  or "GS"
    OFILE    => "551/010D" or "GDS"
    NFILE    => "551/010D" or "GDS"
    MFILE    => "551/010D" or "GDS"
    OMARK    => "010/444"  or "G/W"
    NMARK    => "010/444"  or "G/W"
    MMARK    => "010/444"  or "G/W"
    UMARK    => ""
    OLINE    => "220"      or "Y"
    NLINE    => "220"      or "Y"
    MLINE    => "220"      or "Y"
    ULINE    => ""
    OTEXT    => "K/454"    or "G"
    NTEXT    => "K/454"    or "G"
    MTEXT    => "K/454"    or "G"
    UTEXT    => ""

This is equivalent to :

    sdif --cm '?COMMAND=555/010,?FILE=555/010D' \
         --cm '?MARK=010/444,UMARK=' \
         --cm '?LINE=220,ULINE=' \
         --cm '?TEXT=K/454,UTEXT='

=item B<--colormap>=C<&func>

=item B<--colormap>=C<sub{...}>

You can also give the name of a Perl subroutine, or its definition, to
be called for the matched words.  The target word is passed in the
variable C<$_>, and the return value of the subroutine is displayed.

See L<Getopt::EX::Colormap/FUNCTION SPEC> for details.

=item B<-->[B<no->]B<cc>, B<-->[B<no->]B<commandcolor>

=item B<-->[B<no->]B<fc>, B<-->[B<no->]B<filecolor>

=item B<-->[B<no->]B<lc>, B<-->[B<no->]B<linecolor>

=item B<-->[B<no->]B<mc>, B<-->[B<no->]B<markcolor>

=item B<-->[B<no->]B<tc>, B<-->[B<no->]B<textcolor>

=item B<-->[B<no->]B<uc>, B<-->[B<no->]B<unknowncolor>

Enable or disable color for the corresponding field.

=back


=head1 MODULE OPTIONS

=head2 default

  default      --autocolor
  --nop        do nothing

=head2 -Mcolors

The following options are available by default.  Run C<perldoc -m
App::sdif::colors> to see the actual settings.

  --light
  --green
  --cmy
  --mono

  --dark
  --dark-green
  --dark-cmy
  --dark-mono


=head1 GIT

See L<App::sdif/GIT> for how to use the B<sdif> family under GIT.


=head1 ENVIRONMENT

=over 7

=item B<SDIFOPTS>

Environment variable B<SDIFOPTS> is used to set default options.

=item B<LESS>

=item B<LESSANSIENDCHARS>

Since B<sdif> emits the ANSI Erase Line terminal sequence, it helps to
let the B<less> command know about it.

    LESS=-cR
    LESSANSIENDCHARS=mK

=back


=head1 AUTHOR

=over

=item Kazumasa Utashiro

=item L<https://github.com/kaz-utashiro/sdif-tools>

=back


=head1 LICENSE

Copyright 1992-2026 Kazumasa Utashiro

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.


=head1 SEE ALSO

L<cdif(1)>, L<watchdiff(1)>

L<Getopt::EX::Colormap>

L<Getopt::EX::termcolor>

L<App::sdif::colors>

L<https://taku910.github.io/mecab/>

L<App::ansicolumn>

L<App::Greple::xlate>

=cut

#  LocalWords:  perldoc colormap autocolor termcolor onword cdifopts
#  LocalWords:  mecab CJK diffopts colortable Unicode OCOMMAND cdif
#  LocalWords:  NCOMMAND OFILE MCOMMAND NFILE MFILE OMARK NMARK MMARK
#  LocalWords:  UMARK OLINE NLINE MLINE ULINE OTEXT NTEXT MTEXT UTEXT
#  LocalWords:  Cyan RGB SDIFOPTS Kazumasa Utashiro watchdiff sdif
#  LocalWords:  sdiff diff sdifrc CMY cmy macOS iTerm XTerm runin
#  LocalWords:  runout regex lenience tabhead tabspace unicode cyan
#  LocalWords:  tabstyle tabstop perl
