Paul Eggert <egg...@cs.ucla.edu> writes: > There's no limit on line length though, right? Other than available > memory. So this could be a problem. ('ls' doesn't have a similar > problem, if I understand it correctly, as OSes typically have > reasonably short limits on file name length.) > >>> >Also, 'diff --color' needn't mess with signal handling unless isatty >>> >(fileno (outfile)). >> Should this also be done if --color=always is used? > > Yes. diff needs to mess with signal handling only if both (1) diff is > outputting colors and (2) the output is a terminal. This is > independent of why diff is doing (1).
ok, thanks for the explanation. I've addressed your comments in the version below. Now I make sure that we don't write more than "max_chunk" bytes in output_1_line and unblock/block the signals. Regards, Giuseppe >From 41b868be5adb8c3de2c57c235693d1f2ec426b12 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano <gscri...@redhat.com> Date: Sun, 8 Mar 2015 22:45:11 +0100 Subject: [PATCH] diff: add support for --color * bootstrap.conf (gnulib_modules): Add "sigprocmask". * doc/diffutils.texi (diff Options): Add documentation for --color. Copied from coreutils ls --color. * src/context.c (pr_unidiff_hunk): Add calls to set_color_context. * src/diff.h (enum colors): New enum to register the current color to use. (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): Add calls to set_color_context * src/side.c (print_1sdiff_line): Add calls to set_color_context. * src/util.c (colors_enabled): New boolean variable. (colors_enabled): New boolean variable. (check_color_output): New function. (begin_output): Call check_color_output every time the output file is changed. (set_color_context): New function. If colors are enabled, print the right command for the terminal to change the color. --- bootstrap.conf | 1 + doc/diffutils.texi | 20 +++++++++ src/context.c | 4 ++ src/diff.c | 30 +++++++++++++- src/diff.h | 34 +++++++++++++++ src/normal.c | 20 +++++++-- src/side.c | 15 +++++++ src/util.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 8 files changed, 234 insertions(+), 10 deletions(-) diff --git a/bootstrap.conf b/bootstrap.conf index 9b2de22..63be732 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -61,6 +61,7 @@ readme-release regex sh-quote signal +sigprocmask stat stat-macros stat-time diff --git a/doc/diffutils.texi b/doc/diffutils.texi index 3e25807..92a7243 100644 --- a/doc/diffutils.texi +++ b/doc/diffutils.texi @@ -3745,6 +3745,26 @@ 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 file types with +Specify whether to use color for distinguishing file types. @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 present. +@item auto +@vindex auto @r{color option} +@cindex terminal, using color iff +- Only use color 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..88ddfde 100644 --- a/src/context.c +++ b/src/context.c @@ -366,10 +366,12 @@ pr_unidiff_hunk (struct change *hunk) while (k--) { char const * const *line = &files[0].linbuf[i++]; + set_color_context (DELETE); putc ('-', out); if (initial_tab && ! (suppress_blank_empty && **line == '\n')) putc ('\t', out); print_1_line (NULL, line); + set_color_context (RESET); } /* Then output the inserted part. */ @@ -378,10 +380,12 @@ pr_unidiff_hunk (struct change *hunk) while (k--) { char const * const *line = &files[1].linbuf[j++]; + set_color_context (ADD); putc ('+', out); if (initial_tab && ! (suppress_blank_empty && **line == '\n')) putc ('\t', out); print_1_line (NULL, line); + set_color_context (RESET); } /* We're done with this hunk, so on to the next! */ diff --git a/src/diff.c b/src/diff.c index ff28377..07fcb57 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); } @@ -645,6 +653,9 @@ main (int argc, char **argv) specify_style (OUTPUT_NORMAL); } + if (colors_style != NEVER && paginate) + error (EXIT_TROUBLE, 0, _("Cannot specify both --color and --paginate.")); + if (output_style != OUTPUT_CONTEXT || hard_locale (LC_TIME)) { #if (defined STAT_TIMESPEC || defined STAT_TIMESPEC_NS \ @@ -940,6 +951,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 +1021,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..0291fdd 100644 --- a/src/diff.h +++ b/src/diff.h @@ -38,6 +38,36 @@ enum changes /* Both deletes and inserts: a hunk containing both old and new lines. */ CHANGED }; + +/* What kind of changes a hunk contains. */ +enum colors +{ + /* Reset to the default color. */ + RESET, + + /* Delete lines. Show output in red. */ + DELETE, + + /* Added lines. Show them in green. */ + ADD, + + /* Does not modify the context. Use it to periodically process pending + signals. */ + SAME, +}; + +/* What kind of changes a hunk contains. */ +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 +113,9 @@ enum output_style XTERN enum output_style output_style; +/* True if colors are printed. */ +XTERN enum colors_style colors_style; + /* Nonzero if output cannot be generated for identical files. */ XTERN bool no_diff_means_no_output; @@ -390,3 +423,4 @@ 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 *); +extern void set_color_context (enum colors); diff --git a/src/normal.c b/src/normal.c index 721fd1a..0d53033 100644 --- a/src/normal.c +++ b/src/normal.c @@ -56,14 +56,26 @@ print_normal_hunk (struct change *hunk) /* Print the lines that the first file has. */ if (changes & OLD) - for (i = first0; i <= last0; i++) - print_1_line ("<", &files[0].linbuf[i]); + { + for (i = first0; i <= last0; i++) + { + set_color_context (DELETE); + print_1_line ("<", &files[0].linbuf[i]); + set_color_context (RESET); + } + } 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]); + { + for (i = first1; i <= last1; i++) + { + set_color_context (ADD); + print_1_line (">", &files[1].linbuf[i]); + set_color_context (RESET); + } + } } diff --git a/src/side.c b/src/side.c index 155512c..e52454e 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); + color_to_reset = true; + } + else if (sep == '>') + { + set_color_context (ADD); + 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); } /* Print lines common to both files in side-by-side format. */ diff --git a/src/util.c b/src/util.c index 2d6d3fc..dcb3a59 100644 --- a/src/util.c +++ b/src/util.c @@ -24,6 +24,7 @@ #include <system-quote.h> #include <xalloc.h> #include "xvasprintf.h" +#include <signal.h> char const pr_program[] = PR_PROGRAM; @@ -153,6 +154,20 @@ print_message_queue (void) static char const *current_name0; static char const *current_name1; static bool currently_recursive; +static bool colors_enabled; +static bool output_is_tty; + +static void +check_color_output (void) +{ + if (! outfile) + return; + + output_is_tty = isatty (fileno (outfile)); + + colors_enabled = (colors_style == ALWAYS) + || (colors_style == AUTO && output_is_tty); +} void setup_output (char const *name0, char const *name1, bool recursive) @@ -313,6 +328,7 @@ begin_output (void) outfile = fdopen (pipes[1], "w"); if (!outfile) pfatal_with_name ("fdopen"); + check_color_output (); } #else char *command = system_quote_argv (SCI_SYSTEM, (char **) argv); @@ -320,6 +336,7 @@ begin_output (void) outfile = popen (command, "w"); if (!outfile) pfatal_with_name (command); + check_color_output (); free (command); #endif } @@ -330,6 +347,7 @@ begin_output (void) /* If -l was not specified, output the diff straight to 'stdout'. */ outfile = stdout; + check_color_output (); /* If handling multiple files (because scanning a directory), print which files the following output is about. */ @@ -672,8 +690,20 @@ void output_1_line (char const *base, char const *limit, char const *flag_format, char const *line_flag) { + /* Try to avoid to block for too long and write more than MAX_CHUNK bytes before + checking for pending signals. */ + const size_t max_chunk = 4096; if (!expand_tabs) - fwrite (base, sizeof (char), limit - base, outfile); + { + size_t left = limit - base; + while (left) + { + size_t len = MIN (left, max_chunk); + fwrite (base, sizeof (char), len, outfile); + set_color_context (SAME); + left -= len; + } + } else { register FILE *out = outfile; @@ -681,6 +711,7 @@ 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 written = 0; while (t < limit) switch ((c = *t++)) @@ -690,7 +721,9 @@ output_1_line (char const *base, char const *limit, char const *flag_format, size_t spaces = tab_size - column % tab_size; column += spaces; do - putc (' ', out); + { + written += putc (' ', out); + } while (--spaces); } break; @@ -698,7 +731,7 @@ output_1_line (char const *base, char const *limit, char const *flag_format, case '\r': putc (c, out); if (flag_format && t < limit && *t != '\n') - fprintf (out, flag_format, line_flag); + written += fprintf (out, flag_format, line_flag); column = 0; break; @@ -706,17 +739,94 @@ output_1_line (char const *base, char const *limit, char const *flag_format, if (column == 0) continue; column--; - putc (c, out); + written += putc (c, out); break; default: column += isprint (c) != 0; - putc (c, out); + written += putc (c, out); + if (written >= max_chunk) + { + set_color_context (SAME); + written = 0; + } break; } } } +static sigset_t old_sigproc_set; +void +set_color_context (enum colors con) +{ + int j; + static sigset_t set; + static bool set_initialized; + static enum colors last_color_context = RESET; + static int const sig[] = + { + SIGTSTP, + 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 + }; + + if (!colors_enabled || (con == SAME && !output_is_tty)) + return; + + if (output_is_tty && !set_initialized) + { + sigemptyset (&set); + for (j = 0; j < sizeof (sig) / sizeof (*sig); j++) + sigaddset (&set, sig[j]); + set_initialized = true; + } + +repeat: + switch (con) + { + case DELETE: + if (output_is_tty) + sigprocmask (SIG_BLOCK, &set, &old_sigproc_set); + fprintf (outfile, "\x1B[31m"); + break; + + case ADD: + if (output_is_tty) + sigprocmask (SIG_BLOCK, &set, &old_sigproc_set); + fprintf (outfile, "\x1B[32m"); + break; + + case SAME: + case RESET: + fprintf (outfile, "\x1b[0m"); + fflush (outfile); + if (output_is_tty) + sigprocmask (SIG_SETMASK, &old_sigproc_set, NULL); + if (con == SAME) + { + con = last_color_context; + goto repeat; + } + break; + } + + last_color_context = con; +} + char const change_letter[] = { 0, 'd', 'a', 'c' }; /* Translate an internal line number (an index into diff's table of lines) -- 2.1.0