X-Git-Url: https://git.distorted.org.uk/~mdw/tig/blobdiff_plain/c9ca1ec34b48072886183a80442b1e5f2614a46d..a389e9442c5877daf3570df75d8b188cf23679ee:/tig.c diff --git a/tig.c b/tig.c index 08cdd41..f9a9a1a 100644 --- a/tig.c +++ b/tig.c @@ -30,6 +30,9 @@ #include #include +#include +#include + #include #include #include @@ -54,8 +57,8 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset, #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) #define STRING_SIZE(x) (sizeof(x) - 1) +#define SIZEOF_STR 1024 /* Default string size. */ #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */ -#define SIZEOF_CMD 1024 /* Size of command buffer. */ #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */ /* This color name can be used to refer to the default term colors. */ @@ -224,11 +227,11 @@ string_enum_compare(const char *str1, const char *str2, int len) */ static size_t -sq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src) +sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src) { char c; -#define BUFPUT(x) do { if (bufsize < SIZEOF_CMD) buf[bufsize++] = (x); } while (0) +#define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0) BUFPUT('\''); while ((c = *src++)) { @@ -282,9 +285,15 @@ sq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src) REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \ REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \ \ + REQ_GROUP("Searching") \ + REQ_(SEARCH, "Search the view"), \ + REQ_(SEARCH_BACK, "Search backwards in the view"), \ + REQ_(FIND_NEXT, "Find next search match"), \ + REQ_(FIND_PREV, "Find previous search match"), \ + \ REQ_GROUP("Misc") \ + REQ_(NONE, "Do nothing"), \ REQ_(PROMPT, "Bring up the prompt"), \ - REQ_(SCREEN_UPDATE, "Update the screen"), \ REQ_(SCREEN_REDRAW, "Redraw the screen"), \ REQ_(SCREEN_RESIZE, "Resize the screen"), \ REQ_(SHOW_VERSION, "Show version information"), \ @@ -361,17 +370,18 @@ VERSION " (" __DATE__ ")\n" " -h, --help Show help message and exit\n"; /* Option and state variables. */ -static bool opt_line_number = FALSE; -static bool opt_rev_graph = TRUE; -static int opt_num_interval = NUMBER_INTERVAL; -static int opt_tab_size = TABSIZE; -static enum request opt_request = REQ_VIEW_MAIN; -static char opt_cmd[SIZEOF_CMD] = ""; -static FILE *opt_pipe = NULL; -static char opt_encoding[20] = "UTF-8"; -static bool opt_utf8 = TRUE; -static char opt_codeset[20] = "UTF-8"; -static iconv_t opt_iconv = ICONV_NONE; +static bool opt_line_number = FALSE; +static bool opt_rev_graph = TRUE; +static int opt_num_interval = NUMBER_INTERVAL; +static int opt_tab_size = TABSIZE; +static enum request opt_request = REQ_VIEW_MAIN; +static char opt_cmd[SIZEOF_STR] = ""; +static FILE *opt_pipe = NULL; +static char opt_encoding[20] = "UTF-8"; +static bool opt_utf8 = TRUE; +static char opt_codeset[20] = "UTF-8"; +static iconv_t opt_iconv = ICONV_NONE; +static char opt_search[SIZEOF_STR] = ""; enum option_type { OPT_NONE, @@ -655,7 +665,6 @@ static struct keybinding default_keybindings[] = { { 'l', REQ_VIEW_LOG }, { 'p', REQ_VIEW_PAGER }, { 'h', REQ_VIEW_HELP }, - { '?', REQ_VIEW_HELP }, /* View manipulation */ { 'q', REQ_VIEW_CLOSE }, @@ -681,6 +690,12 @@ static struct keybinding default_keybindings[] = { { 'w', REQ_SCROLL_PAGE_UP }, { 's', REQ_SCROLL_PAGE_DOWN }, + /* Searching */ + { '/', REQ_SEARCH }, + { '?', REQ_SEARCH_BACK }, + { 'n', REQ_FIND_NEXT }, + { 'N', REQ_FIND_PREV }, + /* Misc */ { 'Q', REQ_QUIT }, { 'z', REQ_STOP_LOADING }, @@ -691,9 +706,9 @@ static struct keybinding default_keybindings[] = { { ':', REQ_PROMPT }, /* wgetch() with nodelay() enabled returns ERR when there's no input. */ - { ERR, REQ_SCREEN_UPDATE }, + { ERR, REQ_NONE }, - /* Use the ncurses SIGWINCH handler. */ + /* Using the ncurses SIGWINCH handler. */ { KEY_RESIZE, REQ_SCREEN_RESIZE }, }; @@ -1089,7 +1104,7 @@ static int load_options(void) { char *home = getenv("HOME"); - char buf[1024]; + char buf[SIZEOF_STR]; FILE *file; config_lineno = 0; @@ -1141,7 +1156,7 @@ struct view { enum keymap keymap; /* What keymap does this view have */ - char cmd[SIZEOF_CMD]; /* Command buffer */ + char cmd[SIZEOF_STR]; /* Command buffer */ char ref[SIZEOF_REF]; /* Hovered commit reference */ char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */ @@ -1153,6 +1168,10 @@ struct view { unsigned long offset; /* Offset of the window top */ unsigned long lineno; /* Current line number */ + /* Searching */ + char grep[SIZEOF_STR]; /* Search string */ + regex_t regex; /* Pre-compiled regex */ + /* If non-NULL, points to the view that opened this view. If this view * is closed tig will switch back to the parent view. */ struct view *parent; @@ -1177,6 +1196,8 @@ struct view_ops { bool (*read)(struct view *view, char *data); /* Depending on view, change display based on current line. */ bool (*enter)(struct view *view, struct line *line); + /* Search for regex in a line. */ + bool (*grep)(struct view *view, struct line *line); }; static struct view_ops pager_ops; @@ -1537,6 +1558,109 @@ move_view(struct view *view, enum request request, bool redraw) /* + * Searching + */ + +static void search_view(struct view *view, enum request request, const char *search); + +static bool +find_next_line(struct view *view, unsigned long lineno, struct line *line) +{ + if (!view->ops->grep(view, line)) + return FALSE; + + if (lineno - view->offset >= view->height) { + view->offset = lineno; + view->lineno = lineno; + redraw_view(view); + + } else { + unsigned long old_lineno = view->lineno - view->offset; + + view->lineno = lineno; + + wmove(view->win, old_lineno, 0); + wclrtoeol(view->win); + draw_view_line(view, old_lineno); + + draw_view_line(view, view->lineno - view->offset); + redrawwin(view->win); + wrefresh(view->win); + } + + report("Line %ld matches '%s'", lineno + 1, view->grep); + return TRUE; +} + +static void +find_next(struct view *view, enum request request) +{ + unsigned long lineno = view->lineno; + int direction; + + if (!*view->grep) { + if (!*opt_search) + report("No previous search"); + else + search_view(view, request, opt_search); + return; + } + + switch (request) { + case REQ_SEARCH: + case REQ_FIND_NEXT: + direction = 1; + break; + + case REQ_SEARCH_BACK: + case REQ_FIND_PREV: + direction = -1; + break; + + default: + return; + } + + if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV) + lineno += direction; + + /* Note, lineno is unsigned long so will wrap around in which case it + * will become bigger than view->lines. */ + for (; lineno < view->lines; lineno += direction) { + struct line *line = &view->line[lineno]; + + if (find_next_line(view, lineno, line)) + return; + } + + report("No match found for '%s'", view->grep); +} + +static void +search_view(struct view *view, enum request request, const char *search) +{ + int regex_err; + + if (*view->grep) { + regfree(&view->regex); + *view->grep = 0; + } + + regex_err = regcomp(&view->regex, search, REG_EXTENDED); + if (regex_err != 0) { + char buf[SIZEOF_STR] = "unknown error"; + + regerror(regex_err, &view->regex, buf, sizeof(buf)); + report("Search failed: %s", buf);; + return; + } + + string_copy(view->grep, search); + + find_next(view, request); +} + +/* * Incremental updating */ @@ -1936,6 +2060,16 @@ view_driver(struct view *view, enum request request) open_view(view, opt_request, OPEN_RELOAD); break; + case REQ_SEARCH: + case REQ_SEARCH_BACK: + search_view(view, request, opt_search); + break; + + case REQ_FIND_NEXT: + case REQ_FIND_PREV: + find_next(view, request); + break; + case REQ_STOP_LOADING: for (i = 0; i < ARRAY_SIZE(views); i++) { view = &views[i]; @@ -1956,7 +2090,7 @@ view_driver(struct view *view, enum request request) redraw_display(); break; - case REQ_SCREEN_UPDATE: + case REQ_NONE: doupdate(); return TRUE; @@ -2067,20 +2201,52 @@ pager_draw(struct view *view, struct line *line, unsigned int lineno) return TRUE; } +static bool +add_describe_ref(char *buf, int *bufpos, char *commit_id, const char *sep) +{ + char refbuf[SIZEOF_STR]; + char *ref = NULL; + FILE *pipe; + + if (!string_format(refbuf, "git describe %s", commit_id)) + return TRUE; + + pipe = popen(refbuf, "r"); + if (!pipe) + return TRUE; + + if ((ref = fgets(refbuf, sizeof(refbuf), pipe))) + ref = chomp_string(ref); + pclose(pipe); + + if (!ref || !*ref) + return TRUE; + + /* This is the only fatal call, since it can "corrupt" the buffer. */ + if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref)) + return FALSE; + + return TRUE; +} + static void add_pager_refs(struct view *view, struct line *line) { - char buf[1024]; + char buf[SIZEOF_STR]; char *commit_id = line->data + STRING_SIZE("commit "); struct ref **refs; int bufpos = 0, refpos = 0; const char *sep = "Refs: "; + bool is_tag = FALSE; assert(line->type == LINE_COMMIT); refs = get_refs(commit_id); - if (!refs) + if (!refs) { + if (view == VIEW(REQ_VIEW_DIFF)) + goto try_add_describe_ref; return; + } do { struct ref *ref = refs[refpos]; @@ -2089,8 +2255,16 @@ add_pager_refs(struct view *view, struct line *line) if (!string_format_from(buf, &bufpos, fmt, sep, ref->name)) return; sep = ", "; + if (ref->tag) + is_tag = TRUE; } while (refs[refpos++]->next); + if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) { +try_add_describe_ref: + if (!add_describe_ref(buf, &bufpos, commit_id, sep)) + return; + } + if (!realloc_lines(view, view->line_size + 1)) return; @@ -2149,11 +2323,27 @@ pager_enter(struct view *view, struct line *line) return TRUE; } +static bool +pager_grep(struct view *view, struct line *line) +{ + regmatch_t pmatch; + char *text = line->data; + + if (!*text) + return FALSE; + + if (regexec(&view->regex, text, 1, &pmatch, 0) == REG_NOMATCH) + return FALSE; + + return TRUE; +} + static struct view_ops pager_ops = { "line", pager_draw, pager_read, pager_enter, + pager_grep, }; @@ -2390,11 +2580,43 @@ main_enter(struct view *view, struct line *line) return TRUE; } +static bool +main_grep(struct view *view, struct line *line) +{ + struct commit *commit = line->data; + enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state; + char buf[DATE_COLS + 1]; + regmatch_t pmatch; + + for (state = S_TITLE; state < S_END; state++) { + char *text; + + switch (state) { + case S_TITLE: text = commit->title; break; + case S_AUTHOR: text = commit->author; break; + case S_DATE: + if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time)) + continue; + text = buf; + break; + + default: + return FALSE; + } + + if (regexec(&view->regex, text, 1, &pmatch, 0) != REG_NOMATCH) + return TRUE; + } + + return FALSE; +} + static struct view_ops main_ops = { "commit", main_draw, main_read, main_enter, + main_grep, }; @@ -2646,11 +2868,11 @@ init_display(void) wbkgdset(status_win, get_line_attr(LINE_STATUS)); } -static int -read_prompt(void) +static char * +read_prompt(const char *prompt) { enum { READING, STOP, CANCEL } status = READING; - char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")]; + static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")]; int pos = 0; while (status == READING) { @@ -2660,7 +2882,7 @@ read_prompt(void) foreach_view (view, i) update_view(view); - report(":%.*s", pos, buf); + report("%s%.*s", prompt, pos, buf); /* Refresh, accept single keystroke of input */ key = wgetch(status_win); switch (key) { @@ -2687,7 +2909,7 @@ read_prompt(void) default: if (pos >= sizeof(buf)) { report("Input string too long"); - return ERR; + return NULL; } if (isprint(key)) @@ -2698,18 +2920,12 @@ read_prompt(void) if (status == CANCEL) { /* Clear the status window */ report(""); - return ERR; + return NULL; } buf[pos++] = 0; - if (!string_format(opt_cmd, "git %s", buf)) - return ERR; - if (strncmp(buf, "show", 4) && isspace(buf[4])) - opt_request = REQ_VIEW_DIFF; - else - opt_request = REQ_VIEW_PAGER; - return OK; + return buf; } /* @@ -2970,10 +3186,34 @@ main(int argc, char *argv[]) * status_win restricted. */ switch (request) { case REQ_PROMPT: - if (read_prompt() == ERR) - request = REQ_SCREEN_UPDATE; + { + char *cmd = read_prompt(":"); + + if (cmd && string_format(opt_cmd, "git %s", cmd)) { + if (strncmp(cmd, "show", 4) && isspace(cmd[4])) { + opt_request = REQ_VIEW_DIFF; + } else { + opt_request = REQ_VIEW_PAGER; + } + break; + } + + request = REQ_NONE; break; + } + case REQ_SEARCH: + case REQ_SEARCH_BACK: + { + const char *prompt = request == REQ_SEARCH + ? "/" : "?"; + char *search = read_prompt(prompt); + if (search) + string_copy(opt_search, search); + else + request = REQ_NONE; + break; + } case REQ_SCREEN_RESIZE: { int height, width;