diff options
author | Giuseppe Scrivano <gscrivano@gnu.org> | 2015-03-08 22:45:11 +0100 |
---|---|---|
committer | Jim Meyering <meyering@fb.com> | 2015-11-29 08:20:53 -0800 |
commit | c0fa19fe92da71404f809aafb5f51cfd99b1bee2 (patch) | |
tree | 20f573743b7d02ce463eaa548f25f15b2a3e3213 | |
parent | b4efca9de418c0166f0b106fef068a594b4ab483 (diff) | |
download | diffutils-c0fa19fe92da71404f809aafb5f51cfd99b1bee2.tar.gz |
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.
-rw-r--r-- | doc/diffutils.texi | 21 | ||||
-rw-r--r-- | src/context.c | 81 | ||||
-rw-r--r-- | src/diff.c | 27 | ||||
-rw-r--r-- | src/diff.h | 28 | ||||
-rw-r--r-- | src/normal.c | 30 | ||||
-rw-r--r-- | src/side.c | 15 | ||||
-rw-r--r-- | 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! */ @@ -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. */ @@ -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); + } + } } @@ -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. */ @@ -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 (); + } } } |