Jim Meyering <j...@meyering.net> writes: > On Sat, Nov 28, 2015 at 12:36 PM, Giuseppe Scrivano <gscriv...@gnu.org> wrote: > .... >> As it is hopefully getting closer to be accepted, I am attaching the >> full series with the amended changes. I've verified that each patch >> passes "make check" and "make syntax-check". > > Thanks, I see no problem. > I can push as-is, but wondered if you wanted to merge > your 0005 patch into the others? Probably not worth the work. > Either way is fine.
it was not much work as there were no conflicts to solve, so I splitted 0005 into 0001 and 0004. I verified again that each step passes all the tests. Regards, Giuseppe
>From 4b944653c3c27d99312f77813394a4c965c5c787 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano <gscriv...@gnu.org> Date: Sun, 8 Mar 2015 22:45:11 +0100 Subject: [PATCH 1/4] diff: add support for --color * doc/diffutils.texi (diff Options): Add documentation for --color. Copied from coreutils ls --color. * src/context.c (pr_unidiff_hunk): Set the color context. (print_context_header): Likewise. (pr_context_hunk): Likewise. * src/diff.h (enum colors_style): New enum to record when to use colors. (colors_style): New variable to memorize the argument value. (set_color_context): Add function definition. * src/diff.c: : Define COLOR_OPTION. (specify_colors_style): New function. (longopts): Add --color. (main): Handle --color argument. (option_help_msgid): Add usage string for --color. * src/normal.c (print_normal_hunk): Set the color context. * src/side.c (print_1sdiff_line): Likewise. * src/util.c (print_1_line_nl): New function. (print_1_line): Make it a wrapper of 'print_1_line_nl'. (colors_enabled): New boolean variable. (begin_output): Call check_color_output once the output file is configured. (output_1_line): Periodically call `process_signals'. (caught_signals): New sigset_t. (colors_enabled): New boolean variable. (interrupt_signal): New sig_atomic_t. (stop_signal_count): New sig_atomic_t. (check_color_output): New function. (install_signal_handlers): Likewise. Copied from coreutils ls. (process_signals): Likewise. Copied from coreutils ls. (set_color_context): New function. (sighandler): Likewise. Copied from coreutils ls. (stophandler): Likewise. Copied from coreutils ls. --- doc/diffutils.texi | 21 ++++ src/context.c | 81 ++++++++++--- src/diff.c | 27 ++++- src/diff.h | 28 +++++ src/normal.c | 30 ++++- src/side.c | 15 +++ src/util.c | 327 +++++++++++++++++++++++++++++++++++++++++++++++------ 7 files changed, 471 insertions(+), 58 deletions(-) diff --git a/doc/diffutils.texi b/doc/diffutils.texi index 091257f..b2c39da 100644 --- a/doc/diffutils.texi +++ b/doc/diffutils.texi @@ -3742,6 +3742,27 @@ Read and write data in binary mode. @xref{Binary}. Use the context output format, showing three lines of context. @xref{Context Format}. +@item --color [=@var{when}] +@cindex color, distinguishing different context +Specify whether to use color for distinguishing different contexts, +like header, added or removed lines. @var{when} may be omitted, or +one of: +@itemize @bullet +@item none +@vindex none @r{color option} +Do not use color at all. This is the default when no --color option +is specified. +@item auto +@vindex auto @r{color option} +@cindex terminal, using color iff +Use color only if standard output is a terminal. +@item always +@vindex always @r{color option} +Always use color. +@end itemize +Specifying @option{--color} and no @var{when} is equivalent to +@option{--color=auto}. + @item -C @var{lines} @itemx --context@r{[}=@var{lines}@r{]} Use the context output format, showing @var{lines} (an integer) lines of diff --git a/src/context.c b/src/context.c index e0f21c4..46b5b1f 100644 --- a/src/context.c +++ b/src/context.c @@ -80,6 +80,7 @@ print_context_label (char const *mark, void print_context_header (struct file_data inf[], char const *const *names, bool unidiff) { + set_color_context (HEADER_CONTEXT); if (unidiff) { print_context_label ("---", &inf[0], names[0], file_label[0]); @@ -90,6 +91,7 @@ print_context_header (struct file_data inf[], char const *const *names, bool uni print_context_label ("***", &inf[0], names[0], file_label[0]); print_context_label ("---", &inf[1], names[1], file_label[1]); } + set_color_context (RESET_CONTEXT); } /* Print an edit script in context format. */ @@ -205,14 +207,21 @@ pr_context_hunk (struct change *hunk) if (function) print_context_function (out, function); - fputs ("\n*** ", out); + putc ('\n', out); + set_color_context (LINE_NUMBER_CONTEXT); + fputs ("*** ", out); print_context_number_range (&files[0], first0, last0); - fputs (" ****\n", out); + fputs (" ****", out); + set_color_context (RESET_CONTEXT); + putc ('\n', out); if (changes & OLD) { struct change *next = hunk; + if (first0 <= last0) + set_color_context (DELETE_CONTEXT); + for (i = first0; i <= last0; i++) { /* Skip past changes that apply (in file 0) @@ -225,23 +234,34 @@ pr_context_hunk (struct change *hunk) prefix = " "; if (next && next->line0 <= i) - /* The change NEXT covers this line. - If lines were inserted here in file 1, this is "changed". - Otherwise it is "deleted". */ - prefix = (next->inserted > 0 ? "!" : "-"); - - print_1_line (prefix, &files[0].linbuf[i]); + { + /* The change NEXT covers this line. + If lines were inserted here in file 1, this is "changed". + Otherwise it is "deleted". */ + prefix = (next->inserted > 0 ? "!" : "-"); + } + print_1_line_nl (prefix, &files[0].linbuf[i], true); + if (i == last0) + set_color_context (RESET_CONTEXT); + if (files[0].linbuf[i + 1][-1] == '\n') + putc ('\n', out); } } + set_color_context (LINE_NUMBER_CONTEXT); fputs ("--- ", out); print_context_number_range (&files[1], first1, last1); - fputs (" ----\n", out); + fputs (" ----", out); + set_color_context (RESET_CONTEXT); + putc ('\n', out); if (changes & NEW) { struct change *next = hunk; + if (first1 <= last1) + set_color_context (ADD_CONTEXT); + for (i = first1; i <= last1; i++) { /* Skip past changes that apply (in file 1) @@ -254,12 +274,17 @@ pr_context_hunk (struct change *hunk) prefix = " "; if (next && next->line1 <= i) - /* The change NEXT covers this line. - If lines were deleted here in file 0, this is "changed". - Otherwise it is "inserted". */ - prefix = (next->deleted > 0 ? "!" : "+"); - - print_1_line (prefix, &files[1].linbuf[i]); + { + /* The change NEXT covers this line. + If lines were deleted here in file 0, this is "changed". + Otherwise it is "inserted". */ + prefix = (next->deleted > 0 ? "!" : "+"); + } + print_1_line_nl (prefix, &files[1].linbuf[i], true); + if (i == last1) + set_color_context (RESET_CONTEXT); + if (files[1].linbuf[i + 1][-1] == '\n') + putc ('\n', out); } } } @@ -330,11 +355,13 @@ pr_unidiff_hunk (struct change *hunk) begin_output (); out = outfile; + set_color_context (LINE_NUMBER_CONTEXT); fputs ("@@ -", out); print_unidiff_number_range (&files[0], first0, last0); fputs (" +", out); print_unidiff_number_range (&files[1], first1, last1); fputs (" @@", out); + set_color_context (RESET_CONTEXT); if (function) print_context_function (out, function); @@ -363,25 +390,43 @@ pr_unidiff_hunk (struct change *hunk) /* For each difference, first output the deleted part. */ k = next->deleted; + if (k) + set_color_context (DELETE_CONTEXT); + while (k--) { char const * const *line = &files[0].linbuf[i++]; putc ('-', out); if (initial_tab && ! (suppress_blank_empty && **line == '\n')) putc ('\t', out); - print_1_line (NULL, line); + print_1_line_nl (NULL, line, true); + + if (!k) + set_color_context (RESET_CONTEXT); + + if (line[1][-1] == '\n') + putc ('\n', out); } /* Then output the inserted part. */ k = next->inserted; - while (k--) + if (k) + set_color_context (ADD_CONTEXT); + + while (k--) { char const * const *line = &files[1].linbuf[j++]; putc ('+', out); if (initial_tab && ! (suppress_blank_empty && **line == '\n')) putc ('\t', out); - print_1_line (NULL, line); + print_1_line_nl (NULL, line, true); + + if (!k) + set_color_context (RESET_CONTEXT); + + if (line[1][-1] == '\n') + putc ('\n', out); } /* We're done with this hunk, so on to the next! */ diff --git a/src/diff.c b/src/diff.c index efd7e47..536f545 100644 --- a/src/diff.c +++ b/src/diff.c @@ -70,6 +70,7 @@ static void add_regexp (struct regexp_list *, char const *); static void summarize_regexp_list (struct regexp_list *); static void specify_style (enum output_style); static void specify_value (char const **, char const *, char const *); +static void specify_colors_style (char const *); static void try_help (char const *, char const *) __attribute__((noreturn)); static void check_stdout (void); static void usage (void); @@ -136,7 +137,9 @@ enum UNCHANGED_GROUP_FORMAT_OPTION, OLD_GROUP_FORMAT_OPTION, NEW_GROUP_FORMAT_OPTION, - CHANGED_GROUP_FORMAT_OPTION + CHANGED_GROUP_FORMAT_OPTION, + + COLOR_OPTION, }; static char const group_format_option[][sizeof "--unchanged-group-format"] = @@ -159,6 +162,7 @@ static struct option const longopts[] = {"binary", 0, 0, BINARY_OPTION}, {"brief", 0, 0, 'q'}, {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION}, + {"color", 2, 0, COLOR_OPTION}, {"context", 2, 0, 'C'}, {"ed", 0, 0, 'e'}, {"exclude", 1, 0, 'x'}, @@ -627,6 +631,10 @@ main (int argc, char **argv) specify_value (&group_format[c], optarg, group_format_option[c]); break; + case COLOR_OPTION: + specify_colors_style (optarg); + break; + default: try_help (NULL, NULL); } @@ -940,6 +948,8 @@ static char const * const option_help_msgid[] = { N_("-d, --minimal try hard to find a smaller set of changes"), N_(" --horizon-lines=NUM keep NUM lines of the common prefix and suffix"), N_(" --speed-large-files assume large files and many scattered small changes"), + N_(" --color[=WHEN] colorize the output; WHEN can be 'never', 'always',"), + N_(" or 'auto' (the default)"), "", N_(" --help display this help and exit"), N_("-v, --version output version information and exit"), @@ -1008,6 +1018,21 @@ specify_style (enum output_style style) output_style = style; } } + +/* Set the color mode. */ +static void +specify_colors_style (char const *value) +{ + if (value == NULL || STREQ (value, "auto")) + colors_style = AUTO; + else if (STREQ (value, "always")) + colors_style = ALWAYS; + else if (STREQ (value, "never")) + colors_style = NEVER; + else + try_help ("invalid color '%s'", value); +} + /* Set the last-modified time of *ST to be the current time. */ diff --git a/src/diff.h b/src/diff.h index 465e4bc..7bf7344 100644 --- a/src/diff.h +++ b/src/diff.h @@ -38,6 +38,19 @@ enum changes /* Both deletes and inserts: a hunk containing both old and new lines. */ CHANGED }; + +/* When colors should be used in the output. */ +enum colors_style +{ + /* Never output colors. */ + NEVER, + + /* Output colors if the output is a terminal. */ + AUTO, + + /* Always output colors. */ + ALWAYS, +}; /* Variables for command line options */ @@ -83,6 +96,9 @@ enum output_style XTERN enum output_style output_style; +/* Define the current color context used to print a line. */ +XTERN enum colors_style colors_style; + /* Nonzero if output cannot be generated for identical files. */ XTERN bool no_diff_means_no_output; @@ -383,6 +399,7 @@ extern void output_1_line (char const *, char const *, char const *, extern void perror_with_name (char const *); extern void pfatal_with_name (char const *) __attribute__((noreturn)); extern void print_1_line (char const *, char const * const *); +extern void print_1_line_nl (char const *, char const * const *, bool); extern void print_message_queue (void); extern void print_number_range (char, struct file_data *, lin, lin); extern void print_script (struct change *, struct change * (*) (struct change *), @@ -390,3 +407,14 @@ extern void print_script (struct change *, struct change * (*) (struct change *) extern void setup_output (char const *, char const *, bool); extern void translate_range (struct file_data const *, lin, lin, long int *, long int *); + +enum color_context +{ + HEADER_CONTEXT, + ADD_CONTEXT, + DELETE_CONTEXT, + RESET_CONTEXT, + LINE_NUMBER_CONTEXT, +}; + +extern void set_color_context (enum color_context color_context); diff --git a/src/normal.c b/src/normal.c index 721fd1a..e78e8ba 100644 --- a/src/normal.c +++ b/src/normal.c @@ -49,21 +49,43 @@ print_normal_hunk (struct change *hunk) begin_output (); /* Print out the line number header for this hunk */ + set_color_context (LINE_NUMBER_CONTEXT); print_number_range (',', &files[0], first0, last0); fputc (change_letter[changes], outfile); print_number_range (',', &files[1], first1, last1); + set_color_context (RESET_CONTEXT); fputc ('\n', outfile); /* Print the lines that the first file has. */ if (changes & OLD) - for (i = first0; i <= last0; i++) - print_1_line ("<", &files[0].linbuf[i]); + { + if (first0 <= last0) + set_color_context (DELETE_CONTEXT); + for (i = first0; i <= last0; i++) + { + print_1_line_nl ("<", &files[0].linbuf[i], true); + if (i == last0) + set_color_context (RESET_CONTEXT); + if (files[0].linbuf[i + 1][-1] == '\n') + putc ('\n', outfile); + } + } if (changes == CHANGED) fputs ("---\n", outfile); /* Print the lines that the second file has. */ if (changes & NEW) - for (i = first1; i <= last1; i++) - print_1_line (">", &files[1].linbuf[i]); + { + if (first1 <= last1) + set_color_context (ADD_CONTEXT); + for (i = first1; i <= last1; i++) + { + print_1_line_nl (">", &files[1].linbuf[i], true); + if (i == last1) + set_color_context (RESET_CONTEXT); + if (files[1].linbuf[i + 1][-1] == '\n') + putc ('\n', outfile); + } + } } diff --git a/src/side.c b/src/side.c index 155512c..a0213c4 100644 --- a/src/side.c +++ b/src/side.c @@ -206,6 +206,18 @@ print_1sdiff_line (char const *const *left, char sep, size_t c2o = sdiff_column2_offset; size_t col = 0; bool put_newline = false; + bool color_to_reset = false; + + if (sep == '<') + { + set_color_context (DELETE_CONTEXT); + color_to_reset = true; + } + else if (sep == '>') + { + set_color_context (ADD_CONTEXT); + color_to_reset = true; + } if (left) { @@ -233,6 +245,9 @@ print_1sdiff_line (char const *const *left, char sep, if (put_newline) putc ('\n', out); + + if (color_to_reset) + set_color_context (RESET_CONTEXT); } /* Print lines common to both files in side-by-side format. */ diff --git a/src/util.c b/src/util.c index 2d6d3fc..78b1a2d 100644 --- a/src/util.c +++ b/src/util.c @@ -24,6 +24,22 @@ #include <system-quote.h> #include <xalloc.h> #include "xvasprintf.h" +#include <signal.h> + +/* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is + present. */ +#ifndef SA_NOCLDSTOP +# define SA_NOCLDSTOP 0 +# define sigprocmask(How, Set, Oset) /* empty */ +# define sigset_t int +# if ! HAVE_SIGINTERRUPT +# define siginterrupt(sig, flag) /* empty */ +# endif +#endif + +#ifndef SA_RESTART +# define SA_RESTART 0 +#endif char const pr_program[] = PR_PROGRAM; @@ -143,6 +159,174 @@ print_message_queue (void) } } +/* The set of signals that are caught. */ + +static sigset_t caught_signals; + +/* If nonzero, the value of the pending fatal signal. */ + +static sig_atomic_t volatile interrupt_signal; + +/* A count of the number of pending stop signals that have been received. */ + +static sig_atomic_t volatile stop_signal_count; + +/* An ordinary signal was received; arrange for the program to exit. */ + +static void +sighandler (int sig) +{ + if (! SA_NOCLDSTOP) + signal (sig, SIG_IGN); + if (! interrupt_signal) + interrupt_signal = sig; +} + +/* A SIGTSTP was received; arrange for the program to suspend itself. */ + +static void +stophandler (int sig) +{ + if (! SA_NOCLDSTOP) + signal (sig, stophandler); + if (! interrupt_signal) + stop_signal_count++; +} +/* Process any pending signals. If signals are caught, this function + should be called periodically. Ideally there should never be an + unbounded amount of time when signals are not being processed. + Signal handling can restore the default colors, so callers must + immediately change colors after invoking this function. */ + +static void +process_signals (void) +{ + while (interrupt_signal || stop_signal_count) + { + int sig; + int stops; + sigset_t oldset; + + set_color_context (RESET_CONTEXT); + fflush (stdout); + + sigprocmask (SIG_BLOCK, &caught_signals, &oldset); + + /* Reload interrupt_signal and stop_signal_count, in case a new + signal was handled before sigprocmask took effect. */ + sig = interrupt_signal; + stops = stop_signal_count; + + /* SIGTSTP is special, since the application can receive that signal + more than once. In this case, don't set the signal handler to the + default. Instead, just raise the uncatchable SIGSTOP. */ + if (stops) + { + stop_signal_count = stops - 1; + sig = SIGSTOP; + } + else + signal (sig, SIG_DFL); + + /* Exit or suspend the program. */ + raise (sig); + sigprocmask (SIG_SETMASK, &oldset, NULL); + + /* If execution reaches here, then the program has been + continued (after being suspended). */ + } +} + +static void +install_signal_handlers (void) +{ + /* The signals that are trapped, and the number of such signals. */ + static int const sig[] = + { + /* This one is handled specially. */ + SIGTSTP, + + /* The usual suspects. */ + SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM, +#ifdef SIGPOLL + SIGPOLL, +#endif +#ifdef SIGPROF + SIGPROF, +#endif +#ifdef SIGVTALRM + SIGVTALRM, +#endif +#ifdef SIGXCPU + SIGXCPU, +#endif +#ifdef SIGXFSZ + SIGXFSZ, +#endif + }; + enum { nsigs = sizeof (sig) / sizeof *(sig) }; + +#if ! SA_NOCLDSTOP + bool caught_sig[nsigs]; +#endif + { + int j; +#if SA_NOCLDSTOP + struct sigaction act; + + sigemptyset (&caught_signals); + for (j = 0; j < nsigs; j++) + { + sigaction (sig[j], NULL, &act); + if (act.sa_handler != SIG_IGN) + sigaddset (&caught_signals, sig[j]); + } + + act.sa_mask = caught_signals; + act.sa_flags = SA_RESTART; + + for (j = 0; j < nsigs; j++) + if (sigismember (&caught_signals, sig[j])) + { + act.sa_handler = sig[j] == SIGTSTP ? stophandler : sighandler; + sigaction (sig[j], &act, NULL); + } +#else + for (j = 0; j < nsigs; j++) + { + caught_sig[j] = (signal (sig[j], SIG_IGN) != SIG_IGN); + if (caught_sig[j]) + { + signal (sig[j], sig[j] == SIGTSTP ? stophandler : sighandler); + siginterrupt (sig[j], 0); + } + } +#endif + } +} + +static char const *current_name0; +static char const *current_name1; +static bool currently_recursive; +static bool colors_enabled; + +static void +check_color_output (bool is_pipe) +{ + bool output_is_tty; + + if (! outfile || colors_style == NEVER) + return; + + output_is_tty = !is_pipe && isatty (fileno (outfile)); + + colors_enabled = (colors_style == ALWAYS + || (colors_style == AUTO && output_is_tty)); + + if (output_is_tty) + install_signal_handlers (); +} + /* Call before outputting the results of comparing files NAME0 and NAME1 to set up OUTFILE, the stdio stream for the output to go to. @@ -150,10 +334,6 @@ print_message_queue (void) we fork off a 'pr' and make OUTFILE a pipe to it. 'pr' then outputs to our stdout. */ -static char const *current_name0; -static char const *current_name1; -static bool currently_recursive; - void setup_output (char const *name0, char const *name1, bool recursive) { @@ -313,6 +493,7 @@ begin_output (void) outfile = fdopen (pipes[1], "w"); if (!outfile) pfatal_with_name ("fdopen"); + check_color_output (true); } #else char *command = system_quote_argv (SCI_SYSTEM, (char **) argv); @@ -320,6 +501,7 @@ begin_output (void) outfile = popen (command, "w"); if (!outfile) pfatal_with_name (command); + check_color_output (true); free (command); #endif } @@ -330,6 +512,7 @@ begin_output (void) /* If -l was not specified, output the diff straight to 'stdout'. */ outfile = stdout; + check_color_output (false); /* If handling multiple files (because scanning a directory), print which files the following output is about. */ @@ -630,6 +813,18 @@ print_script (struct change *script, void print_1_line (char const *line_flag, char const *const *line) { + print_1_line_nl (line_flag, line, false); +} + +/* Print the text of a single line LINE, + flagging it with the characters in LINE_FLAG (which say whether + the line is inserted, deleted, changed, etc.). LINE_FLAG must not + end in a blank, unless it is a single blank. If SKIP_NL is set, then + the final '\n' is not printed. */ + +void +print_1_line_nl (char const *line_flag, char const *const *line, bool skip_nl) +{ char const *base = line[0], *limit = line[1]; /* Help the compiler. */ FILE *out = outfile; /* Help the compiler some more. */ char const *flag_format = 0; @@ -657,10 +852,13 @@ print_1_line (char const *line_flag, char const *const *line) fprintf (out, flag_format_1, line_flag_1); } - output_1_line (base, limit, flag_format, line_flag); + output_1_line (base, limit - (skip_nl && limit[-1] == '\n'), flag_format, line_flag); if ((!line_flag || line_flag[0]) && limit[-1] != '\n') - fprintf (out, "\n\\ %s\n", _("No newline at end of file")); + { + set_color_context (RESET_CONTEXT); + fprintf (out, "\n\\ %s\n", _("No newline at end of file")); + } } /* Output a line from BASE up to LIMIT. @@ -672,8 +870,21 @@ void output_1_line (char const *base, char const *limit, char const *flag_format, char const *line_flag) { + const size_t MAX_CHUNK = 1024; if (!expand_tabs) - fwrite (base, sizeof (char), limit - base, outfile); + { + size_t left = limit - base; + while (left) + { + size_t to_write = MIN (left, MAX_CHUNK); + size_t written = fwrite (base, sizeof (char), to_write, outfile); + if (written < to_write) + return; + base += written; + left -= written; + process_signals (); + } + } else { register FILE *out = outfile; @@ -681,39 +892,85 @@ output_1_line (char const *base, char const *limit, char const *flag_format, register char const *t = base; register size_t column = 0; size_t tab_size = tabsize; + size_t counter_proc_signals = 0; while (t < limit) - switch ((c = *t++)) - { - case '\t': - { - size_t spaces = tab_size - column % tab_size; - column += spaces; - do - putc (' ', out); - while (--spaces); - } - break; + { + counter_proc_signals++; + if (counter_proc_signals == MAX_CHUNK) + { + process_signals (); + counter_proc_signals = 0; + } + + switch ((c = *t++)) + { + case '\t': + { + size_t spaces = tab_size - column % tab_size; + column += spaces; + do + putc (' ', out); + while (--spaces); + } + break; + + case '\r': + putc (c, out); + if (flag_format && t < limit && *t != '\n') + fprintf (out, flag_format, line_flag); + column = 0; + break; + + case '\b': + if (column == 0) + continue; + column--; + putc (c, out); + break; + + default: + column += isprint (c) != 0; + putc (c, out); + break; + } + } + } +} - case '\r': - putc (c, out); - if (flag_format && t < limit && *t != '\n') - fprintf (out, flag_format, line_flag); - column = 0; - break; - case '\b': - if (column == 0) - continue; - column--; - putc (c, out); - break; +void +set_color_context (enum color_context color_context) +{ + process_signals (); + if (colors_enabled) + { + switch (color_context) + { + case HEADER_CONTEXT: + fputs ("\x1B[1m", outfile); + break; - default: - column += isprint (c) != 0; - putc (c, out); - break; - } + case LINE_NUMBER_CONTEXT: + fputs ("\x1B[36m", outfile); + + break; + + case ADD_CONTEXT: + fputs ("\x1B[32m", outfile); + break; + + case DELETE_CONTEXT: + fputs ("\x1B[31m", outfile); + break; + + case RESET_CONTEXT: + fputs ("\x1b[0m", outfile); + break; + + default: + abort (); + } } } -- 2.5.0
>From 8ab5aeabc752dfc746ebd385d1ce569d96d051b5 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano <gscriv...@gnu.org> Date: Mon, 19 Oct 2015 10:29:41 +0200 Subject: [PATCH 2/4] diff: add --palette * bootstrap (gnulib_modules): Add 'argmatch'. * doc/diffutils.texi: Add documentation for --palette * src/diff.h (set_color_palette): New prototype. * src/diff.c (set_color_palette): New function. (color_palette): New variable. * src/utils.c: Include "argmatch.h". (struct bin_str): New struct. (struct color_ext_type): New struct. (color_indicator): New array. (indicator_name): New array. (indicator_no): New enum. (parse_state): New enum. (put_indicator): New function. (get_funky_string): New function. Copied from coreutils ls. (parse_diff_color): New function. Copied from coreutils ls "parse_ls_color" function. (set_color_context): Use put_indicator instead of directly outputting the sequence. * po/POTFILES.in: Add 'lib/argmatch.c' --- bootstrap.conf | 1 + doc/diffutils.texi | 34 +++++ po/POTFILES.in | 1 + src/diff.c | 8 + src/diff.h | 1 + src/util.c | 424 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 462 insertions(+), 7 deletions(-) diff --git a/bootstrap.conf b/bootstrap.conf index 9b2de22..3ab2c8b 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -19,6 +19,7 @@ # gnulib modules used by this package. gnulib_modules=' announce-gen +argmatch binary-io c-stack config-h diff --git a/doc/diffutils.texi b/doc/diffutils.texi index b2c39da..4ec9a0b 100644 --- a/doc/diffutils.texi +++ b/doc/diffutils.texi @@ -3890,6 +3890,40 @@ if-then-else format. @xref{Line Formats}. @itemx --show-c-function Show which C function each change is in. @xref{C Function Headings}. +@item --palette=@var{palette} +Specify what color palette to use when colored output is enabled. It +defaults to @samp{rs=0:hd=1:ad=32:de=31:ln=36} for red deleted lines, +green added lines, cyan line numbers, bold header. + +Supported capabilities are as follows. + +@table @code +@item ad=32 +@vindex ad @r{capability} + +SGR substring for added lines. +The default is green foreground. + +@item de=31 +@vindex de @r{capability} + +SGR substring for deleted lines. +The default is red foreground. + +@item hd=1 +@vindex hd @r{capability} + +SGR substring for chunk header. +The default is bold foreground. + +@item ln=36 +@vindex ln @r{capability} + +SGR substring for line numbers. +The default is cyan foreground. +@end table + + @item -q @itemx --brief Report only whether the files differ, not the details of the diff --git a/po/POTFILES.in b/po/POTFILES.in index 74fb756..af39427 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -15,6 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +lib/argmatch.c lib/c-stack.c lib/error.c lib/file-type.c diff --git a/src/diff.c b/src/diff.c index 536f545..46ac99d 100644 --- a/src/diff.c +++ b/src/diff.c @@ -140,6 +140,7 @@ enum CHANGED_GROUP_FORMAT_OPTION, COLOR_OPTION, + COLOR_PALETTE_OPTION, }; static char const group_format_option[][sizeof "--unchanged-group-format"] = @@ -196,6 +197,7 @@ static struct option const longopts[] = {"old-group-format", 1, 0, OLD_GROUP_FORMAT_OPTION}, {"old-line-format", 1, 0, OLD_LINE_FORMAT_OPTION}, {"paginate", 0, 0, 'l'}, + {"palette", 1, 0, COLOR_PALETTE_OPTION}, {"rcs", 0, 0, 'n'}, {"recursive", 0, 0, 'r'}, {"report-identical-files", 0, 0, 's'}, @@ -635,6 +637,10 @@ main (int argc, char **argv) specify_colors_style (optarg); break; + case COLOR_PALETTE_OPTION: + set_color_palette (optarg); + break; + default: try_help (NULL, NULL); } @@ -950,6 +956,8 @@ static char const * const option_help_msgid[] = { N_(" --speed-large-files assume large files and many scattered small changes"), N_(" --color[=WHEN] colorize the output; WHEN can be 'never', 'always',"), N_(" or 'auto' (the default)"), + N_(" --palette=PALETTE specify the colors to use when --color is active"), + N_(" PALETTE is a colon-separated list terminfo capabilities"), "", N_(" --help display this help and exit"), N_("-v, --version output version information and exit"), diff --git a/src/diff.h b/src/diff.h index 7bf7344..6f1bb34 100644 --- a/src/diff.h +++ b/src/diff.h @@ -418,3 +418,4 @@ enum color_context }; extern void set_color_context (enum color_context color_context); +extern void set_color_palette (char const *palette); diff --git a/src/util.c b/src/util.c index 78b1a2d..1fa61fa 100644 --- a/src/util.c +++ b/src/util.c @@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "diff.h" +#include "argmatch.h" #include <dirname.h> #include <error.h> #include <system-quote.h> @@ -310,6 +311,397 @@ static char const *current_name1; static bool currently_recursive; static bool colors_enabled; +static struct color_ext_type *color_ext_list = NULL; + +struct bin_str + { + size_t len; /* Number of bytes */ + const char *string; /* Pointer to the same */ + }; + +struct color_ext_type + { + struct bin_str ext; /* The extension we're looking for */ + struct bin_str seq; /* The sequence to output when we do */ + struct color_ext_type *next; /* Next in list */ + }; + +/* Parse a string as part of the --palette argument; this may involve + decoding all kinds of escape characters. If equals_end is set an + unescaped equal sign ends the string, otherwise only a : or \0 + does. Set *OUTPUT_COUNT to the number of bytes output. Return + true if successful. + + The resulting string is *not* null-terminated, but may contain + embedded nulls. + + Note that both dest and src are char **; on return they point to + the first free byte after the array and the character that ended + the input string, respectively. */ + +static bool +get_funky_string (char **dest, const char **src, bool equals_end, + size_t *output_count) +{ + char num; /* For numerical codes */ + size_t count; /* Something to count with */ + enum { + ST_GND, ST_BACKSLASH, ST_OCTAL, ST_HEX, ST_CARET, ST_END, ST_ERROR + } state; + const char *p; + char *q; + + p = *src; /* We don't want to double-indirect */ + q = *dest; /* the whole darn time. */ + + count = 0; /* No characters counted in yet. */ + num = 0; + + state = ST_GND; /* Start in ground state. */ + while (state < ST_END) + { + switch (state) + { + case ST_GND: /* Ground state (no escapes) */ + switch (*p) + { + case ':': + case '\0': + state = ST_END; /* End of string */ + break; + case '\\': + state = ST_BACKSLASH; /* Backslash scape sequence */ + ++p; + break; + case '^': + state = ST_CARET; /* Caret escape */ + ++p; + break; + case '=': + if (equals_end) + { + state = ST_END; /* End */ + break; + } + /* else fall through */ + default: + *(q++) = *(p++); + ++count; + break; + } + break; + + case ST_BACKSLASH: /* Backslash escaped character */ + switch (*p) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + state = ST_OCTAL; /* Octal sequence */ + num = *p - '0'; + break; + case 'x': + case 'X': + state = ST_HEX; /* Hex sequence */ + num = 0; + break; + case 'a': /* Bell */ + num = '\a'; + break; + case 'b': /* Backspace */ + num = '\b'; + break; + case 'e': /* Escape */ + num = 27; + break; + case 'f': /* Form feed */ + num = '\f'; + break; + case 'n': /* Newline */ + num = '\n'; + break; + case 'r': /* Carriage return */ + num = '\r'; + break; + case 't': /* Tab */ + num = '\t'; + break; + case 'v': /* Vtab */ + num = '\v'; + break; + case '?': /* Delete */ + num = 127; + break; + case '_': /* Space */ + num = ' '; + break; + case '\0': /* End of string */ + state = ST_ERROR; /* Error! */ + break; + default: /* Escaped character like \ ^ : = */ + num = *p; + break; + } + if (state == ST_BACKSLASH) + { + *(q++) = num; + ++count; + state = ST_GND; + } + ++p; + break; + + case ST_OCTAL: /* Octal sequence */ + if (*p < '0' || *p > '7') + { + *(q++) = num; + ++count; + state = ST_GND; + } + else + num = (num << 3) + (*(p++) - '0'); + break; + + case ST_HEX: /* Hex sequence */ + switch (*p) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + num = (num << 4) + (*(p++) - '0'); + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + num = (num << 4) + (*(p++) - 'a') + 10; + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + num = (num << 4) + (*(p++) - 'A') + 10; + break; + default: + *(q++) = num; + ++count; + state = ST_GND; + break; + } + break; + + case ST_CARET: /* Caret escape */ + state = ST_GND; /* Should be the next state... */ + if (*p >= '@' && *p <= '~') + { + *(q++) = *(p++) & 037; + ++count; + } + else if (*p == '?') + { + *(q++) = 127; + ++count; + } + else + state = ST_ERROR; + break; + + default: + abort (); + } + } + + *dest = q; + *src = p; + *output_count = count; + + return state != ST_ERROR; +} + +enum parse_state + { + PS_START = 1, + PS_2, + PS_3, + PS_4, + PS_DONE, + PS_FAIL + }; + +#define LEN_STR_PAIR(s) sizeof (s) - 1, s + +static struct bin_str color_indicator[] = + { + { LEN_STR_PAIR ("\033[") }, /* lc: Left of color sequence */ + { LEN_STR_PAIR ("m") }, /* rc: Right of color sequence */ + { 0, NULL }, /* ec: End color (replaces lc+rs+rc) */ + { LEN_STR_PAIR ("0") }, /* rs: Reset to ordinary colors */ + { LEN_STR_PAIR ("1") }, /* hd: Header */ + { LEN_STR_PAIR ("32") }, /* ad: Add line */ + { LEN_STR_PAIR ("31") }, /* de: Delete line */ + { LEN_STR_PAIR ("36") }, /* ln: Line number */ + }; + +static const char *const indicator_name[] = + { + "lc", "rc", "ec", "rs", "hd", "ad", "de", "ln", NULL + }; +ARGMATCH_VERIFY (indicator_name, color_indicator); + +static char const *color_palette; + +void +set_color_palette (char const *palette) +{ + color_palette = palette; +} + +static void +parse_diff_color (void) +{ + char *color_buf; + const char *p; /* Pointer to character being parsed */ + char *buf; /* color_buf buffer pointer */ + int ind_no; /* Indicator number */ + char label[3]; /* Indicator label */ + struct color_ext_type *ext; /* Extension we are working on */ + + if ((p = color_palette) == NULL || *p == '\0') + return; + + ext = NULL; + strcpy (label, "??"); + + /* This is an overly conservative estimate, but any possible + --palette string will *not* generate a color_buf longer than + itself, so it is a safe way of allocating a buffer in + advance. */ + buf = color_buf = xstrdup (p); + + enum parse_state state = PS_START; + while (true) + { + switch (state) + { + case PS_START: /* First label character */ + switch (*p) + { + case ':': + ++p; + break; + + case '*': + /* Allocate new extension block and add to head of + linked list (this way a later definition will + override an earlier one, which can be useful for + having terminal-specific defs override global). */ + + ext = xmalloc (sizeof *ext); + ext->next = color_ext_list; + color_ext_list = ext; + + ++p; + ext->ext.string = buf; + + state = (get_funky_string (&buf, &p, true, &ext->ext.len) + ? PS_4 : PS_FAIL); + break; + + case '\0': + state = PS_DONE; /* Done! */ + goto done; + + default: /* Assume it is file type label */ + label[0] = *(p++); + state = PS_2; + break; + } + break; + + case PS_2: /* Second label character */ + if (*p) + { + label[1] = *(p++); + state = PS_3; + } + else + state = PS_FAIL; /* Error */ + break; + + case PS_3: /* Equal sign after indicator label */ + state = PS_FAIL; /* Assume failure... */ + if (*(p++) == '=')/* It *should* be... */ + { + for (ind_no = 0; indicator_name[ind_no] != NULL; ++ind_no) + { + if (STREQ (label, indicator_name[ind_no])) + { + color_indicator[ind_no].string = buf; + state = (get_funky_string (&buf, &p, false, + &color_indicator[ind_no].len) + ? PS_START : PS_FAIL); + break; + } + } + if (state == PS_FAIL) + error (0, 0, _("unrecognized prefix: %s"), label); + } + break; + + case PS_4: /* Equal sign after *.ext */ + if (*(p++) == '=') + { + ext->seq.string = buf; + state = (get_funky_string (&buf, &p, false, &ext->seq.len) + ? PS_START : PS_FAIL); + } + else + state = PS_FAIL; + break; + + case PS_FAIL: + goto done; + + default: + abort (); + } + } + done: + + if (state == PS_FAIL) + { + struct color_ext_type *e; + struct color_ext_type *e2; + + error (0, 0, + _("unparsable value for --palette")); + free (color_buf); + for (e = color_ext_list; e != NULL; /* empty */) + { + e2 = e; + e = e->next; + free (e2); + } + colors_enabled = false; + } +} + static void check_color_output (bool is_pipe) { @@ -323,6 +715,9 @@ check_color_output (bool is_pipe) colors_enabled = (colors_style == ALWAYS || (colors_style == AUTO && output_is_tty)); + if (colors_enabled) + parse_diff_color (); + if (output_is_tty) install_signal_handlers (); } @@ -938,42 +1333,57 @@ output_1_line (char const *base, char const *limit, char const *flag_format, } } +enum indicator_no + { + C_LEFT, C_RIGHT, C_END, C_RESET, C_HEADER, C_ADD, C_DELETE, C_LINE + }; + +static void +put_indicator (const struct bin_str *ind) +{ + fwrite (ind->string, ind->len, 1, outfile); +} + +static enum color_context last_context = RESET_CONTEXT; void set_color_context (enum color_context color_context) { process_signals (); - if (colors_enabled) + if (colors_enabled && last_context != color_context) { + put_indicator (&color_indicator[C_LEFT]); switch (color_context) { case HEADER_CONTEXT: - fputs ("\x1B[1m", outfile); + put_indicator (&color_indicator[C_HEADER]); break; case LINE_NUMBER_CONTEXT: - fputs ("\x1B[36m", outfile); - + put_indicator (&color_indicator[C_LINE]); break; case ADD_CONTEXT: - fputs ("\x1B[32m", outfile); + put_indicator (&color_indicator[C_ADD]); break; case DELETE_CONTEXT: - fputs ("\x1B[31m", outfile); + put_indicator (&color_indicator[C_DELETE]); break; case RESET_CONTEXT: - fputs ("\x1b[0m", outfile); + put_indicator (&color_indicator[C_RESET]); break; default: abort (); } + put_indicator (&color_indicator[C_RIGHT]); + last_context = color_context; } } + char const change_letter[] = { 0, 'd', 'a', 'c' }; /* Translate an internal line number (an index into diff's table of lines) -- 2.5.0
>From 773498ed3956af73ae3caff69c62f3b84a632cca Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano <gscriv...@gnu.org> Date: Mon, 2 Nov 2015 19:03:32 +0000 Subject: [PATCH 3/4] doc: mention --color and --palette in NEWS --- NEWS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS b/NEWS index 7cdfedd..088f13b 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,13 @@ GNU diffutils NEWS -*- outline -*- * Noteworthy changes in release ?.? (????-??-??) [?] +** New features + + diff accepts two new options --color and --palette to generate + and configure colored output. --color takes an optional argument + specifying when to colorize a line: --color=always, --color=auto, + --color=never. --palette is used to configure which colors are used. + ** Bug fixes When binary files differ, diff now exits with status 1 as POSIX requires. -- 2.5.0
>From 4a69b46c3d71dd7299cf5d3b1dde3ce6902d3afe Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano <gscriv...@gnu.org> Date: Mon, 2 Nov 2015 19:05:10 +0000 Subject: [PATCH 4/4] tests: Add tests for --color and --palette * tests/colors: New file. * tests/Makefile.am (TESTS): Add colors. --- tests/Makefile.am | 3 +- tests/colors | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100755 tests/colors diff --git a/tests/Makefile.am b/tests/Makefile.am index 438fbdf..805ccc2 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -15,7 +15,8 @@ TESTS = \ no-newline-at-eof \ stdin \ strcoll-0-names \ - filename-quoting + filename-quoting \ + colors EXTRA_DIST = \ $(TESTS) init.sh t-local.sh diff --git a/tests/colors b/tests/colors new file mode 100755 index 0000000..facfd8d --- /dev/null +++ b/tests/colors @@ -0,0 +1,119 @@ +#!/bin/sh + +. "${srcdir=.}/init.sh"; path_prepend_ ../src + +TZ=UTC0 +export TZ + +fail=0 + +echo a > a +echo b > b + +epoch='1970-01-01 00:00:00' +touch --date="$epoch" a b + +gen_exp_u() +{ + local tab=$(printf '\t') + local epoch_plus="$epoch.000000000 +0000" + local rs=$(printf "\e[${rs}m") + local hd=$(printf "\e[${hd}m") + local ad=$(printf "\e[${ad}m") + local de=$(printf "\e[${de}m") + local ln=$(printf "\e[${ln}m") + printf '%s' \ +"$hd--- a$tab$epoch_plus ++++ b$tab$epoch_plus +$rs${ln}@@ -1 +1 @@$rs +$de-a$rs +$ad+b$rs +" +} + +gen_exp_c() +{ + local tab=$(printf '\t') + local epoch_posix_1003_1_2001="Thu Jan 1 00:00:00 1970" + local rs=$(printf "\e[${rs}m") + local hd=$(printf "\e[${hd}m") + local ad=$(printf "\e[${ad}m") + local de=$(printf "\e[${de}m") + local ln=$(printf "\e[${ln}m") + printf '%s' \ +"$hd*** a$tab$epoch_posix_1003_1_2001 +--- b$tab$epoch_posix_1003_1_2001 +$rs*************** +$ln*** 1 ****$rs +$de! a$rs +$ln--- 1 ----$rs +$ad! b$rs +" +} + +gen_exp_default() +{ + printf '%s' \ +"1c1 +< a +--- +> b +" +} + +gen_exp_default_colors() +{ + local rs=$(printf "\e[${rs}m") + local hd=$(printf "\e[${hd}m") + local ad=$(printf "\e[${ad}m") + local de=$(printf "\e[${de}m") + local ln=$(printf "\e[${ln}m") + printf '%s' \ +"${ln}1c1$rs +$de< a$rs +--- +$ad> b$rs +" +} + +# Compare with some known outputs + +rs=0 hd=1 ad=32 de=31 ln=36 + +diff --color=auto a b > out +test $? = 1 || fail=1 +gen_exp_default > exp || framework_failure_ +compare exp out || fail=1 + +diff --color=never a b > out +test $? = 1 || fail=1 +gen_exp_default > exp || framework_failure_ +compare exp out || fail=1 + +diff a b > out +test $? = 1 || fail=1 +gen_exp_default > exp || framework_failure_ +compare exp out || fail=1 + +diff --color=always a b > out +test $? = 1 || fail=1 +gen_exp_default_colors > exp || framework_failure_ +compare exp out || fail=1 + +diff -u --color=always a b > out +test $? = 1 || fail=1 +gen_exp_u > exp || framework_failure_ +compare exp out || fail=1 + +diff -c --color=always a b > out +test $? = 1 || fail=1 +gen_exp_c > exp || framework_failure_ +compare exp out || fail=1 + +rs=0 hd=33 ad=34 de=35 ln=36 +diff -u --color=always --palette="rs=0:hd=33:ad=34:de=35:ln=36" a b > out +test $? = 1 || fail=1 +gen_exp_u > exp || framework_failure_ +compare exp out || fail=1 + +Exit $fail -- 2.5.0