From 3a2492d584e3c7d4cada1386921d80ace33c1fd2 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Thu, 23 Sep 2021 20:51:04 -0700 Subject: WIP: working line breaks, formatting, etc. All seems functional, but a bit messy --- Lua/lbp.c | 4 +- bp.c | 99 +++++++++++++++++++---- match.c | 69 ++++++++-------- match.h | 11 ++- print.c | 268 -------------------------------------------------------------- print.h | 29 ------- utils.c | 2 +- 7 files changed, 133 insertions(+), 349 deletions(-) diff --git a/Lua/lbp.c b/Lua/lbp.c index 0c81188..6ce03a6 100644 --- a/Lua/lbp.c +++ b/Lua/lbp.c @@ -36,7 +36,7 @@ static void push_matchstring(lua_State *L, match_t *m) char *buf = NULL; size_t size = 0; FILE *out = open_memstream(&buf, &size); - fprint_match(out, m->start, m); + fprint_match(out, m->start, m, NULL); fflush(out); lua_pushlstring(L, buf, size); fclose(out); @@ -184,7 +184,7 @@ static int Lreplace(lua_State *L) const char *prev = text; for (match_t *m = NULL; next_match(&m, NULL, text, &text[textlen], maybe_pat.value.pat, NULL, false); ) { fwrite(prev, sizeof(char), (size_t)(m->start - prev), out); - fprint_match(out, text, m); + fprint_match(out, text, m, NULL); prev = m->end; ++replacements; } diff --git a/bp.c b/bp.c index 3acafe4..003d062 100644 --- a/bp.c +++ b/bp.c @@ -59,6 +59,10 @@ static const char *usage = ( // Used as a heuristic to check if a file is binary or text: #define CHECK_FIRST_N_BYTES 256 +#define USE_DEFAULT_CONTEXT -3 +#define ALL_CONTEXT -2 +#define NO_CONTEXT -1 + // Flag-configurable options: static struct { int context_before, context_after; @@ -234,33 +238,85 @@ static void sig_handler(int sig) if (kill(0, sig)) _exit(EXIT_FAILURE); } -void fprint_between(FILE *out, file_t *f, const char *prev, const char *next) +void fprint_linenum(FILE *out, file_t *f, int linenum, const char *normal_color) +{ + switch (options.format) { + case FORMAT_FANCY: case FORMAT_PLAIN: { + int space = 0; + for (int i = (int)f->nlines; i > 0; i /= 10) ++space; + if (options.format == FORMAT_FANCY) + fprintf(out, "\033[0;2m%*d\033(0\x78\033(B%s", space, linenum, normal_color ? normal_color : ""); + else fprintf(out, "%*d|", space, linenum); + break; + } + case FORMAT_FILE_LINE: { + fprintf(out, "%s:%d:", f->filename, linenum); + break; + } + default: break; + } +} + +static file_t *printing_file = NULL; +static void _fprint_between(FILE *out, const char *start, const char *end, const char *normal_color) { + if (!end) end = printing_file->end; + while (start < end) { + if (!start) start = printing_file->start; + if (start == printing_file->start || start[-1] == '\n') { + int linenum = (int)get_line_number(printing_file, start); + fprint_linenum(out, printing_file, linenum, normal_color); + } + const char *line_end = memchr(start, '\n', (size_t)(end - start)); + if (line_end && line_end != end) { + fwrite(start, sizeof(char), (size_t)(line_end - start + 1), out); + start = line_end + 1; + } else { + fwrite(start, sizeof(char), (size_t)(end - start), out); + break; + } + } +} + +static void on_nl(FILE *out) +{ + switch (options.format) { + case FORMAT_FANCY: case FORMAT_PLAIN: + for (int i = (int)printing_file->nlines; i > 0; i /= 10) fputc('.', out); + fprintf(out, "%s", options.format == FORMAT_FANCY ? "\033[0;2m\033(0\x78\033(B\033[m" : "|"); + break; + default: break; + } +} + +static void fprint_context_between(FILE *out, const char *prev, const char *next) +{ + if (!prev && !next) return; // if (options.context_before == NO_CONTEXT && options.context_after == NO_CONTEXT) // return; if (options.context_before == ALL_CONTEXT || options.context_after == ALL_CONTEXT) { - fwrite(prev ? prev : f->start, sizeof(char), (size_t)((next ? next : f->end) - (prev ? prev : f->start)), out); + _fprint_between(out, prev, next, "\033[m"); return; } const char *after_prev = prev, *before_next = next; if (prev && options.context_after >= 0) { - size_t after_prev_line = get_line_number(f, prev) + (size_t)options.context_after + 1; - after_prev = get_line(f, after_prev_line > f->nlines ? f->nlines : after_prev_line); + size_t after_prev_line = get_line_number(printing_file, prev) + (size_t)options.context_after + 1; + after_prev = after_prev_line > printing_file->nlines ? printing_file->end : get_line(printing_file, after_prev_line > printing_file->nlines ? printing_file->nlines : after_prev_line); } if (next && options.context_before >= 0) { - size_t before_next_line = get_line_number(f, next); + size_t before_next_line = get_line_number(printing_file, next); before_next_line = options.context_before >= (int)before_next_line ? 1 : before_next_line - (size_t)options.context_before; - before_next = get_line(f, before_next_line); + before_next = get_line(printing_file, before_next_line); } if (!prev) { - fwrite(before_next, sizeof(char), (size_t)(next - before_next), out); + _fprint_between(out, before_next, next, "\033[m"); } else if (!next) { - fwrite(prev, sizeof(char), (size_t)(after_prev - prev), out); + _fprint_between(out, prev, after_prev, "\033[m"); } else if (after_prev >= before_next) { - fwrite(prev, sizeof(char), (size_t)(next - prev), out); + _fprint_between(out, prev, next, "\033[m"); } else { - fwrite(prev, sizeof(char), (size_t)(after_prev - prev), out); - fwrite(before_next, sizeof(char), (size_t)(next - before_next), out); + _fprint_between(out, prev, after_prev, "\033[m"); + _fprint_between(out, before_next, next, "\033[m"); } } @@ -272,17 +328,32 @@ static int print_matches(FILE *out, def_t *defs, file_t *f, pat_t *pattern) static int printed_filenames = 0; int matches = 0; const char *prev = NULL; + + printing_file = f; + + print_options_t print_opts = {.fprint_between = _fprint_between, .on_nl = on_nl}; + if (options.format == FORMAT_FANCY) { + print_opts.match_color = "\033[0;31;1m"; + print_opts.replace_color = "\033[0;34;1m"; + print_opts.normal_color = "\033[m"; + } for (match_t *m = NULL; next_match(&m, defs, f->start, f->end, pattern, options.skip, options.ignorecase); ) { if (++matches == 1 && options.print_filenames) { if (printed_filenames++ > 0) printf("\n"); fprint_filename(out, f->filename); } - fprint_between(out, f, prev, m->start); - fprint_match(out, f->start, m); + fprint_context_between(out, prev, m->start); + if (print_opts.normal_color) fprintf(out, "%s", print_opts.normal_color); + if (m->start == f->start || m->start[-1] == '\n') + fprint_linenum(out, f, (int)get_line_number(f, m->start), print_opts.normal_color); + fprint_match(out, f->start, m, &print_opts); + if (print_opts.normal_color) fprintf(out, "%s", print_opts.normal_color); prev = m->end; } if (matches > 0) - fprint_between(out, f, prev, NULL); + fprint_context_between(out, prev, NULL); + + printing_file = NULL; return matches; } diff --git a/match.c b/match.c index 29db8c6..870b4d0 100644 --- a/match.c +++ b/match.c @@ -844,37 +844,46 @@ match_t *get_named_capture(match_t *m, const char *name, size_t namelen) return NULL; } -void fprint_match(FILE *out, const char *file_start, match_t *m) +static inline void fputc_safe(FILE *out, char c, print_options_t *opts) +{ + (void)fputc(c, out); + if (c == '\n' && opts && opts->on_nl) { + opts->on_nl(out); + if (opts->replace_color) fprintf(out, "%s", opts->replace_color); + } +} + +void fprint_match(FILE *out, const char *file_start, match_t *m, print_options_t *opts) { if (m->pat->type == BP_REPLACE) { const char *text = m->pat->args.replace.text; const char *end = &text[m->pat->args.replace.len]; + if (opts && opts->replace_color) fprintf(out, "%s", opts->replace_color); // TODO: clean up the line numbering code for (const char *r = text; r < end; ) { // Capture substitution if (*r == '@' && r+1 < end && r[1] != '@') { - ++r; - + const char *next = r+1; // Retrieve the capture value: match_t *cap = NULL; - if (isdigit(*r)) { - int n = (int)strtol(r, (char**)&r, 10); + if (isdigit(*next)) { + int n = (int)strtol(next, (char**)&next, 10); cap = get_numbered_capture(m->children[0], n); } else { - const char *name = r, *end = after_name(r); + const char *name = next, *end = after_name(next); if (end > name) { cap = get_named_capture(m->children[0], name, (size_t)(end - name)); - r = end; - if (r < m->end && *r == ';') ++r; + next = end; + if (next < m->end && *next == ';') ++next; } } if (cap != NULL) { - fprint_match(out, file_start, cap); + fprint_match(out, file_start, cap, opts); + if (opts && opts->replace_color) fprintf(out, "%s", opts->replace_color); + r = next; continue; - } else { - --r; } } @@ -887,30 +896,19 @@ void fprint_match(FILE *out, const char *file_start, match_t *m) // the replacement text contains newlines, this may get weird. const char *line_start = m->start; while (line_start > file_start && line_start[-1] != '\n') --line_start; - char denter = *line_start == '\t' ? '\t' : ' '; - fputc('\n', out); - if (denter == ' ' || denter == '\t') { - for (const char *p = line_start; p && *p == denter && p < m->start; ++p) - fputc(denter, out); - } + fputc_safe(out, '\n', opts); + for (const char *p = line_start; p < m->start && (*p == ' ' || *p == '\t'); ++p) + fputc(*p, out); continue; } - const char *start = r; - char c = unescapechar(r, &r); - if (r > start) (void)fputc(c, out); - else (void)fputc('\\', out); - continue; - } else if (*r == '\n') { - (void)fputc('\n', out); - ++r; - continue; + fputc_safe(out, unescapechar(r, &r), opts); } else { - (void)fputc(*r, out); + fputc_safe(out, *r, opts); ++r; - continue; } } } else { + if (opts && opts->match_color) fprintf(out, "%s", opts->match_color); const char *prev = m->start; for (int i = 0; m->children && m->children[i]; i++) { match_t *child = m->children[i]; @@ -918,13 +916,18 @@ void fprint_match(FILE *out, const char *file_start, match_t *m) if (!(prev <= child->start && child->start <= m->end && prev <= child->end && child->end <= m->end)) continue; - if (child->start > prev) - fwrite(prev, sizeof(char), (size_t)(child->start - prev), out); - fprint_match(out, file_start, child); + if (child->start > prev) { + if (opts && opts->fprint_between) opts->fprint_between(out, prev, child->start, opts->match_color); + else fwrite(prev, sizeof(char), (size_t)(child->start - prev), out); + } + fprint_match(out, file_start, child, opts); + if (opts && opts->match_color) fprintf(out, "%s", opts->match_color); prev = child->end; } - if (m->end > prev) - fwrite(prev, sizeof(char), (size_t)(m->end - prev), out); + if (m->end > prev) { + if (opts && opts->fprint_between) opts->fprint_between(out, prev, m->end, opts->match_color); + else fwrite(prev, sizeof(char), (size_t)(m->end - prev), out); + } } } diff --git a/match.h b/match.h index 15578e0..7cb1c59 100644 --- a/match.h +++ b/match.h @@ -33,6 +33,12 @@ typedef struct match_s { struct match_s *_children[3]; } match_t; +typedef struct { + const char *normal_color, *match_color, *replace_color; + void (*fprint_between)(FILE *out, const char *start, const char *end, const char *normal_color); + void (*on_nl)(FILE *out); +} print_options_t; + __attribute__((returns_nonnull)) match_t *new_match(def_t *defs, pat_t *pat, const char *start, const char *end, match_t *children[]); __attribute__((nonnull)) @@ -45,8 +51,9 @@ __attribute__((nonnull)) match_t *get_numbered_capture(match_t *m, int n); __attribute__((nonnull, pure)) match_t *get_named_capture(match_t *m, const char *name, size_t namelen); -__attribute__((nonnull)) -void fprint_match(FILE *out, const char *file_start, match_t *m); +__attribute__((nonnull(1,2,3))) +//void fprint_match(FILE *out, const char *file_start, match_t *m, const char *colors[3]); +void fprint_match(FILE *out, const char *file_start, match_t *m, print_options_t *opts); #endif // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/print.c b/print.c index a5c27af..e69de29 100644 --- a/print.c +++ b/print.c @@ -1,268 +0,0 @@ -// -// print.c - Code for printing and visualizing matches. -// - -#include -#include -#include -#include -#include - -#include "files.h" -#include "match.h" -#include "print.h" -#include "utils.h" - -static const char *color_match = "\033[0;31;1m"; -static const char *color_replace = "\033[0;34;1m"; -static const char *color_normal = "\033[m"; -static const char *current_color = NULL; - -// -// Print a line number, if it needs to be printed. -// In the lineformat string, replace "@" with the filename, and "#" with the line number. -// -__attribute__((nonnull(1,2))) -static inline void print_line_number(FILE *out, printer_t *pr, size_t line_number, const char *color, int is_line_continued) -{ - if (!pr->needs_line_number) return; - for (const char *c = pr->lineformat; c && *c; c++) { - if (*c == '@') { // Print filename - fprintf(out, "%s", pr->file->filename); - } else if (*c == '#') { // Print line number - const char *after; - int space = (int)strtol(c+1, (char**)&after, 10); - if (after > c+1) c = after-1; // Width was specified - else // Otherwise default to "wide enough for every line number in this file" - for (int i = (int)pr->file->nlines; i > 0; i /= 10) ++space; - - if (is_line_continued) fprintf(out, "%*s", abs(space), ""); - else fprintf(out, "%*lu", space, line_number); - } else fputc(*c, out); - } - if (color) { - fprintf(out, "%s", color); - current_color = color; - } - pr->needs_line_number = 0; -} - -// -// Print a range of text from a file, adding line numbers if necessary. -// -__attribute__((nonnull(1,2,3,4))) -static void print_between(FILE *out, printer_t *pr, const char *start, const char *end, const char *color) -{ - file_t *f = pr->file; - while (start < end) { - size_t line_num = f ? get_line_number(f, start) : 0; - print_line_number(out, pr, line_num, color, 0); - const char *eol = f ? get_line(f, line_num + 1) : end; - if (!eol || eol > end) eol = end; - if (color && color != current_color) { - fprintf(out, "%s", color); - current_color = color; - } - fwrite(start, sizeof(char), (size_t)(eol-start), out); - if (eol[-1] == '\n') - pr->needs_line_number = 1; - start = eol; - } - pr->pos = end; -} - -// -// Return a pointer to the first character of context information before `pos`, -// according to the context settings in `pr` -// -static const char *context_before(printer_t *pr, const char *pos) -{ - if (pr->context_before == ALL_CONTEXT) { - return pr->pos; - } else if (pr->context_before >= 0) { - size_t n = get_line_number(pr->file, pos); - if (n >= (size_t)(pr->context_before + 1)) - n -= (size_t)pr->context_before; - else - n = 1; - const char *sol = get_line(pr->file, n); - if (sol == NULL || sol < pr->pos) sol = pr->pos; - return sol; - } else { - return pos; - } -} - -// -// Return a pointer to the last character of context information after `pos`, -// according to the context settings in `pr` -// -static const char *context_after(printer_t *pr, const char *pos) -{ - if (pr->context_after == ALL_CONTEXT) { - return pr->file->end; - } else if (pr->context_after >= 0) { - size_t n = get_line_number(pr->file, pos) + (size_t)pr->context_after; - const char *eol = get_line(pr->file, n+1); - return eol ? eol : pr->file->end; - } else { - return pos; - } -} - -// -// Print the text of a match (no context). -// -static void _print_match(FILE *out, printer_t *pr, match_t *m) -{ - pr->pos = m->start; - if (m->pat->type == BP_REPLACE) { - size_t line = pr->file ? get_line_number(pr->file, m->start) : 0; - size_t line_end = pr->file ? get_line_number(pr->file, m->end) : 0; - - if (pr->use_color && current_color != color_replace) { - fprintf(out, "%s", color_replace); - current_color = color_replace; - } - const char *text = m->pat->args.replace.text; - const char *end = &text[m->pat->args.replace.len]; - - // TODO: clean up the line numbering code - for (const char *r = text; r < end; ) { - print_line_number(out, pr, line, pr->use_color ? color_replace : NULL, line > line_end); - - // Capture substitution - if (*r == '@' && r+1 < end && r[1] != '@') { - ++r; - - // Retrieve the capture value: - match_t *cap = NULL; - if (isdigit(*r)) { - int n = (int)strtol(r, (char**)&r, 10); - cap = get_numbered_capture(m->children[0], n); - } else { - const char *name = r, *end = after_name(r); - if (end > name) { - cap = get_named_capture(m->children[0], name, (size_t)(end - name)); - r = end; - if (r < m->end && *r == ';') ++r; - } - } - - if (cap != NULL) { - _print_match(out, pr, cap); - if (pr->use_color && current_color != color_replace) { - fprintf(out, "%s", color_replace); - current_color = color_replace; - } - continue; - } else { - --r; - } - } - - if (*r == '\\') { - ++r; - if (*r == 'N') { // \N (nodent) - ++r; - // Mildly hacky: nodents here are based on the *first line* - // of the match. If the match spans multiple lines, or if - // the replacement text contains newlines, this may get weird. - const char *line_start = get_line( - pr->file, get_line_number(pr->file, m->start)); - char denter = line_start ? *line_start : '\t'; - fputc('\n', out); - pr->needs_line_number = 1; - print_line_number(out, pr, line, pr->use_color ? color_replace : NULL, 1); - ++line; - if (denter == ' ' || denter == '\t') { - for (const char *p = line_start; p && *p == denter && p < m->start; ++p) - fputc(denter, out); - } - continue; - } - const char *start = r; - char c = unescapechar(r, &r); - if (r > start) { - (void)fputc(c, out); - if (c == '\n') { - ++line; - pr->needs_line_number = 1; - } - } else (void)fputc('\\', out); - continue; - } else if (*r == '\n') { - (void)fputc('\n', out); - ++line; - pr->needs_line_number = 1; - ++r; - continue; - } else { - (void)fputc(*r, out); - ++r; - continue; - } - } - print_line_number(out, pr, line, pr->use_color ? color_normal : NULL, line > line_end); - } else { - const char *prev = m->start; - for (int i = 0; m->children && m->children[i]; i++) { - match_t *child = m->children[i]; - // Skip children from e.g. zero-width matches like >@foo - if (!(prev <= child->start && child->start <= m->end && - prev <= child->end && child->end <= m->end)) - continue; - if (child->start > prev) - print_between(out, pr, prev, child->start, pr->use_color ? color_match : NULL); - _print_match(out, pr, child); - prev = child->end; - } - if (m->end > prev) - print_between(out, pr, prev, m->end, pr->use_color ? color_match : NULL); - } - pr->pos = m->end; -} - -// -// Print the text of a match and any context. -// -void print_match(FILE *out, printer_t *pr, match_t *m) -{ - current_color = color_normal; - bool first = (pr->pos == NULL); - if (first) { // First match printed: - pr->pos = pr->file ? pr->file->start : m->start; - pr->needs_line_number = 1; - } - if (m) { - const char *before_m = context_before(pr, m->start); - if (!first) { - // When not printing context lines, print each match on its own - // line instead of jamming them all together: - if (pr->context_before == NO_CONTEXT && pr->context_after == NO_CONTEXT && (!pr->needs_line_number || !pr->lineformat)) { - fprintf(out, "\n"); - pr->needs_line_number = 1; - } - - const char *after_last = context_after(pr, pr->pos); - if (after_last >= before_m) { - // Overlapping ranges: - before_m = pr->pos; - } else { - // Non-overlapping ranges: - print_between(out, pr, pr->pos, after_last, pr->use_color ? color_normal : NULL); - if (pr->context_before > 0 || pr->context_after > 0) - fprintf(out, "\n"); // Gap between chunks - } - } - print_between(out, pr, before_m, m->start, pr->use_color ? color_normal : NULL); - _print_match(out, pr, m); - } else { - // After the last match is printed, print the trailing context: - const char *after_last = context_after(pr, pr->pos); - print_between(out, pr, pr->pos, after_last, pr->use_color ? color_normal : NULL); - } - if (pr->use_color) fprintf(out, "%s", color_normal); -} - -// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/print.h b/print.h index b690d69..e69de29 100644 --- a/print.h +++ b/print.h @@ -1,29 +0,0 @@ -// -// Header file for print.c (printing/visualizing matches) -// -#ifndef PRINT__H -#define PRINT__H - -#include - -#include "files.h" -#include "match.h" - -#define USE_DEFAULT_CONTEXT -3 -#define ALL_CONTEXT -2 -#define NO_CONTEXT -1 - -typedef struct { - file_t *file; - const char *pos; - int context_before, context_after; - bool needs_line_number:1; - bool use_color:1; - const char *lineformat; -} printer_t; - -__attribute__((nonnull(1,2))) -void print_match(FILE *out, printer_t *pr, match_t *m); - -#endif -// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/utils.c b/utils.c index 9057df6..38d7f30 100644 --- a/utils.c +++ b/utils.c @@ -121,7 +121,7 @@ char unescapechar(const char *escaped, const char **end) } default: if (end) *end = escaped; - return (char)0; + return '\\'; } if (end) *end = &escaped[len]; return (char)ret; -- cgit v1.2.3