diff: Fix --expand-tabs and --side-by-side.

* Overhaul column width and padding calculation.
* Rewrite print_space() so it is now a) correct and b) understandable.
* Rewrite tab expansion in fetch() for the same reason.

This brings us in line with GNU diff for all cases I could think of.

Sponsored by:	Klara, Inc.
Reviewed by:	imp
Differential Revision:	https://reviews.freebsd.org/D44014
This commit is contained in:
Dag-Erling Smørgrav 2024-02-26 19:08:06 +01:00
parent 6b9e6f12e5
commit 53de23f4d1
3 changed files with 78 additions and 56 deletions

View file

@ -427,6 +427,15 @@ Output at most
.Ar number
columns when using side by side format.
The default value is 130.
Note that unless
.It Fl t
was specified,
.Nm
will always align the second column to a tab stop, so values of
.Fl -width
smaller than approximately five times the value of
.Fl -tabsize
may yield surprising results.
.It Fl -changed-group-format Ar GFMT
Format input groups in the provided
.Pp

View file

@ -276,10 +276,8 @@ main(int argc, char **argv)
break;
case 'W':
width = (int) strtonum(optarg, 1, INT_MAX, &errstr);
if (errstr) {
warnx("Invalid argument for width");
usage();
}
if (errstr)
errx(1, "width is %s: %s", errstr, optarg);
break;
case 'X':
read_excludes_file(optarg);
@ -317,10 +315,8 @@ main(int argc, char **argv)
break;
case OPT_TSIZE:
tabsize = (int) strtonum(optarg, 1, INT_MAX, &errstr);
if (errstr) {
warnx("Invalid argument for tabsize");
usage();
}
if (errstr)
errx(1, "tabsize is %s: %s", errstr, optarg);
break;
case OPT_STRIPCR:
dflags |= D_STRIPCR;

View file

@ -166,7 +166,6 @@ struct context_vec {
enum readhash { RH_BINARY, RH_OK, RH_EOF };
#define MIN_PAD 1
static FILE *opentemp(const char *);
static void output(char *, FILE *, char *, FILE *, int);
static void check(FILE *, FILE *, int);
@ -206,7 +205,7 @@ static int len[2];
static int pref, suff; /* length of prefix and suffix */
static int slen[2];
static int anychange;
static int hw, padding; /* half width and padding */
static int hw, lpad, rpad; /* half width and padding */
static int edoffset;
static long *ixnew; /* will be overlaid on file[1] */
static long *ixold; /* will be overlaid on klist */
@ -251,21 +250,44 @@ diffreg(char *file1, char *file2, int flags, int capsicum)
lastline = 0;
lastmatchline = 0;
/*
* hw excludes padding and make sure when -t is not used,
* the second column always starts from the closest tab stop
*/
/*
* In side-by-side mode, we need to print the left column, a
* change marker surrounded by padding, and the right column.
*
* If expanding tabs, we don't care about alignment, so we simply
* subtract 3 from the width and divide by two.
*
* If not expanding tabs, we need to ensure that the right column
* is aligned to a tab stop. We start with the same formula, then
* decrement until we reach a size that lets us tab-align the
* right column. We then adjust the width down if necessary for
* the padding calculation to work.
*
* Left padding is half the space left over, rounded down; right
* padding is whatever is needed to match the width.
*/
if (diff_format == D_SIDEBYSIDE) {
hw = width >> 1;
padding = tabsize - (hw % tabsize);
if ((flags & D_EXPANDTABS) != 0 || (padding % tabsize == 0))
padding = MIN_PAD;
hw = (width >> 1) -
((padding == MIN_PAD) ? (padding << 1) : padding) - 1;
if (flags & D_EXPANDTABS) {
if (width > 3) {
hw = (width - 3) / 2;
} else {
/* not enough space */
hw = 0;
}
} else if (width <= 3 || width <= tabsize) {
/* not enough space */
hw = 0;
} else {
hw = (width - 3) / 2;
while (hw > 0 && roundup(hw + 3, tabsize) + hw > width)
hw--;
if (width - (roundup(hw + 3, tabsize) + hw) < tabsize)
width = roundup(hw + 3, tabsize) + hw;
}
lpad = (width - hw * 2 - 1) / 2;
rpad = (width - hw * 2 - 1) - lpad;
}
if (flags & D_IGNORECASE)
chrtran = cup2low;
else
@ -866,7 +888,7 @@ output(char *file1, FILE *f1, char *file2, FILE *f2, int flags)
while (i0 <= m && J[i0] == J[i0 - 1] + 1) {
if (diff_format == D_SIDEBYSIDE && suppress_common != 1) {
nc = fetch(ixold, i0, i0, f1, '\0', 1, flags);
print_space(nc, (hw - nc) + (padding << 1) + 1, flags);
print_space(nc, hw - nc + lpad + 1 + rpad, flags);
fetch(ixnew, J[i0], J[i0], f2, '\0', 0, flags);
printf("\n");
}
@ -1144,10 +1166,10 @@ change(char *file1, FILE *f1, char *file2, FILE *f2, int a, int b, int c, int d,
else if (color && c > d)
printf("\033[%sm", del_code);
if (a > b) {
print_space(0, hw + padding , *pflags);
print_space(0, hw + lpad, *pflags);
} else {
nc = fetch(ixold, a, b, f1, '\0', 1, *pflags);
print_space(nc, hw - nc + padding, *pflags);
print_space(nc, hw - nc + lpad, *pflags);
}
if (color && a > b)
printf("\033[%sm", add_code);
@ -1156,7 +1178,7 @@ change(char *file1, FILE *f1, char *file2, FILE *f2, int a, int b, int c, int d,
printf("%c", (a > b) ? '>' : ((c > d) ? '<' : '|'));
if (color && c > d)
printf("\033[m");
print_space(hw + padding + 1 , padding, *pflags);
print_space(hw + lpad + 1, rpad, *pflags);
fetch(ixnew, c, d, f2, '\0', 0, *pflags);
printf("\n");
}
@ -1262,30 +1284,24 @@ fetch(long *f, int a, int b, FILE *lb, int ch, int oldfile, int flags)
printf("\n\\ No newline at end of file\n");
return (col);
}
/*
* when using --side-by-side, col needs to be increased
* in any case to keep the columns aligned
*/
if (c == '\t') {
if (flags & D_EXPANDTABS) {
newcol = ((col / tabsize) + 1) * tabsize;
do {
printf(" ");
} while (++col < newcol && col < hw);
/*
* Calculate where the tab would bring us.
* If it would take us to the end of the
* column, either clip it (if expanding
* tabs) or return right away (if not).
*/
newcol = roundup(col + 1, tabsize);
if ((flags & D_EXPANDTABS) == 0) {
if (hw > 0 && newcol >= hw)
return (col);
printf("\t");
} else {
if (diff_format == D_SIDEBYSIDE) {
if ((col + tabsize) > hw) {
printf("%*s", hw - col, "");
col = hw;
} else {
printf("\t");
col += tabsize - 1;
}
} else {
printf("\t");
col++;
}
if (hw > 0 && newcol > hw)
newcol = hw;
printf("%*s", newcol - col, "");
}
col = newcol;
} else {
if (diff_format == D_EDIT && j == 1 && c == '\n' &&
lastc == '.') {
@ -1665,18 +1681,19 @@ print_header(const char *file1, const char *file2)
* nc is the preceding number of characters
*/
static void
print_space(int nc, int n, int flags) {
int i, col;
print_space(int nc, int n, int flags)
{
int col, newcol, tabstop;
col = n;
col = nc;
newcol = nc + n;
/* first, use tabs if allowed */
if ((flags & D_EXPANDTABS) == 0) {
/* first tabstop may be closer than tabsize */
i = tabsize - (nc % tabsize);
while (col >= tabsize) {
while ((tabstop = roundup(col + 1, tabsize)) <= newcol) {
printf("\t");
col -= i;
i = tabsize;
col = tabstop;
}
}
printf("%*s", col, "");
/* finish with spaces */
printf("%*s", newcol - col, "");
}