X-Git-Url: https://git.distorted.org.uk/~mdw/tig/blobdiff_plain/dc23c0e307351a6e48025026ad9545936e90507c..699ae55b3872f063678d534707da052741ac8385:/tig.c diff --git a/tig.c b/tig.c index 97e3d0d..8664732 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. */ @@ -88,6 +91,12 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset, #define TIG_MAIN_CMD \ "git log --topo-order --pretty=raw %s 2>/dev/null" +#define TIG_TREE_CMD \ + "git ls-tree %s %s" + +#define TIG_BLOB_CMD \ + "git cat-file blob %s" + /* XXX: Needs to be defined to the empty string. */ #define TIG_HELP_CMD "" #define TIG_PAGER_CMD "" @@ -224,11 +233,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++)) { @@ -257,6 +266,8 @@ sq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src) REQ_(VIEW_MAIN, "Show main view"), \ REQ_(VIEW_DIFF, "Show diff view"), \ REQ_(VIEW_LOG, "Show log view"), \ + REQ_(VIEW_TREE, "Show tree view"), \ + REQ_(VIEW_BLOB, "Show blob view"), \ REQ_(VIEW_HELP, "Show help page"), \ REQ_(VIEW_PAGER, "Show pager view"), \ \ @@ -282,9 +293,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 +378,19 @@ 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 char opt_path[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, @@ -551,6 +570,8 @@ LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \ LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \ LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \ LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \ +LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \ +LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL) enum line_type { #define LINE(type, line, fg, bg, attr) \ @@ -653,9 +674,10 @@ static struct keybinding default_keybindings[] = { { 'm', REQ_VIEW_MAIN }, { 'd', REQ_VIEW_DIFF }, { 'l', REQ_VIEW_LOG }, + { 't', REQ_VIEW_TREE }, + { 'b', REQ_VIEW_BLOB }, { 'p', REQ_VIEW_PAGER }, { 'h', REQ_VIEW_HELP }, - { '?', REQ_VIEW_HELP }, /* View manipulation */ { 'q', REQ_VIEW_CLOSE }, @@ -681,6 +703,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 +719,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 }, }; @@ -702,6 +730,8 @@ static struct keybinding default_keybindings[] = { KEYMAP_(MAIN), \ KEYMAP_(DIFF), \ KEYMAP_(LOG), \ + KEYMAP_(TREE), \ + KEYMAP_(BLOB), \ KEYMAP_(PAGER), \ KEYMAP_(HELP) \ @@ -1089,7 +1119,7 @@ static int load_options(void) { char *home = getenv("HOME"); - char buf[1024]; + char buf[SIZEOF_STR]; FILE *file; config_lineno = 0; @@ -1122,12 +1152,13 @@ struct view_ops; static struct view *display[2]; static unsigned int current_view; -#define foreach_view(view, i) \ +#define foreach_displayed_view(view, i) \ for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++) #define displayed_views() (display[1] != NULL ? 2 : 1) /* Current head and commit ID */ +static char ref_blob[SIZEOF_REF] = ""; static char ref_commit[SIZEOF_REF] = "HEAD"; static char ref_head[SIZEOF_REF] = "HEAD"; @@ -1135,13 +1166,13 @@ struct view { const char *name; /* View name */ const char *cmd_fmt; /* Default command line format */ const char *cmd_env; /* Command line set via environment */ - const char *id; /* Points to either of ref_{head,commit} */ + const char *id; /* Points to either of ref_{head,commit,blob} */ struct view_ops *ops; /* View operations */ 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 +1184,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,10 +1212,14 @@ 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; static struct view_ops main_ops; +static struct view_ops tree_ops; +static struct view_ops blob_ops; #define VIEW_STR(name, cmd, env, ref, ops, map) \ { name, cmd, #env, ref, ops, map} @@ -1193,16 +1232,25 @@ static struct view views[] = { VIEW_(MAIN, "main", &main_ops, ref_head), VIEW_(DIFF, "diff", &pager_ops, ref_commit), VIEW_(LOG, "log", &pager_ops, ref_head), + VIEW_(TREE, "tree", &tree_ops, ref_commit), + VIEW_(BLOB, "blob", &blob_ops, ref_blob), VIEW_(HELP, "help", &pager_ops, "static"), VIEW_(PAGER, "pager", &pager_ops, "static"), }; #define VIEW(req) (&views[(req) - REQ_OFFSET - 1]) +#define foreach_view(view, i) \ + for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++) + +#define view_is_displayed(view) \ + (view == display[0] || view == display[1]) static bool draw_view_line(struct view *view, unsigned int lineno) { + assert(view_is_displayed(view)); + if (view->offset + lineno >= view->lines) return FALSE; @@ -1234,6 +1282,8 @@ redraw_view(struct view *view) static void update_view_title(struct view *view) { + assert(view_is_displayed(view)); + if (view == display[current_view]) wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS)); else @@ -1301,7 +1351,7 @@ resize_display(void) offset = 0; - foreach_view (view, i) { + foreach_displayed_view (view, i) { if (!view->win) { view->win = newwin(view->height, 0, offset, 0); if (!view->win) @@ -1329,7 +1379,7 @@ redraw_display(void) struct view *view; int i; - foreach_view (view, i) { + foreach_displayed_view (view, i) { redraw_view(view); update_view_title(view); } @@ -1358,6 +1408,8 @@ update_display_cursor(void) static void do_scroll_view(struct view *view, int lines, bool redraw) { + assert(view_is_displayed(view)); + /* The rendering expects the new offset. */ view->offset += lines; @@ -1537,6 +1589,111 @@ 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) +{ + assert(view_is_displayed(view)); + + 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 */ @@ -1567,6 +1724,17 @@ begin_update(struct view *view) /* When running random commands, the view ref could have become * invalid so clear it. */ view->ref[0] = 0; + + } else if (view == VIEW(REQ_VIEW_TREE)) { + const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt; + + if (strcmp(view->vid, view->id)) + opt_path[0] = 0; + + if (snprintf(view->cmd, sizeof(view->cmd), format, id, opt_path) + >= sizeof(view->cmd)) + return FALSE; + } else { const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt; @@ -1640,6 +1808,7 @@ update_view(struct view *view) if (view->offset + view->height >= view->lines) redraw_from = view->lines - view->offset; + /* FIXME: This is probably not perfect for backgrounded views. */ if (!realloc_lines(view, view->lines + lines)) goto alloc_error; @@ -1686,7 +1855,15 @@ update_view(struct view *view) } } - if (redraw_from >= 0) { + if (!view_is_displayed(view)) + goto check_pipe; + + if (view == VIEW(REQ_VIEW_TREE)) { + /* Clear the view and redraw everything since the tree sorting + * might have rearranged things. */ + redraw_view(view); + + } else if (redraw_from >= 0) { /* If this is an incremental update, redraw the previous line * since for commits some members could have changed when * loading the main view. */ @@ -1701,6 +1878,7 @@ update_view(struct view *view) * commit reference in view->ref it'll be available here. */ update_view_title(view); +check_pipe: if (ferror(view->pipe)) { report("Failed to read: %s", strerror(errno)); goto end; @@ -1872,9 +2050,16 @@ view_driver(struct view *view, enum request request) scroll_view(view, request); break; + case REQ_VIEW_BLOB: + if (!ref_blob[0]) { + report("No file chosen, press 't' to open tree view"); + break; + } + /* Fall-through */ case REQ_VIEW_MAIN: case REQ_VIEW_DIFF: case REQ_VIEW_LOG: + case REQ_VIEW_TREE: case REQ_VIEW_HELP: case REQ_VIEW_PAGER: open_view(view, request, OPEN_DEFAULT); @@ -1884,8 +2069,10 @@ view_driver(struct view *view, enum request request) case REQ_PREVIOUS: request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP; - if (view == VIEW(REQ_VIEW_DIFF) && - view->parent == VIEW(REQ_VIEW_MAIN)) { + if ((view == VIEW(REQ_VIEW_DIFF) && + view->parent == VIEW(REQ_VIEW_MAIN)) || + (view == VIEW(REQ_VIEW_BLOB) && + view->parent == VIEW(REQ_VIEW_TREE))) { bool redraw = display[1] == view; view = view->parent; @@ -1936,6 +2123,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 +2153,7 @@ view_driver(struct view *view, enum request request) redraw_display(); break; - case REQ_SCREEN_UPDATE: + case REQ_NONE: doupdate(); return TRUE; @@ -2006,6 +2203,11 @@ pager_draw(struct view *view, struct line *line, unsigned int lineno) if (type == LINE_COMMIT) { string_copy(view->ref, text + 7); string_copy(ref_commit, view->ref); + + } else if (type == LINE_TREE_DIR || type == LINE_TREE_FILE) { + strncpy(view->ref, text + STRING_SIZE("100644 blob "), 41); + view->ref[40] = 0; + string_copy(ref_blob, view->ref); } type = LINE_CURSOR; @@ -2070,7 +2272,7 @@ pager_draw(struct view *view, struct line *line, unsigned int lineno) static bool add_describe_ref(char *buf, int *bufpos, char *commit_id, const char *sep) { - char refbuf[1024]; + char refbuf[SIZEOF_STR]; char *ref = NULL; FILE *pipe; @@ -2089,7 +2291,7 @@ add_describe_ref(char *buf, int *bufpos, char *commit_id, const char *sep) return TRUE; /* This is the only fatal call, since it can "corrupt" the buffer. */ - if (!string_nformat(buf, 1024, bufpos, "%s%s", sep, ref)) + if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref)) return FALSE; return TRUE; @@ -2098,7 +2300,7 @@ add_describe_ref(char *buf, int *bufpos, char *commit_id, const char *sep) 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; @@ -2189,11 +2391,223 @@ 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, +}; + + +/* + * Tree backend + */ + +/* Parse output from git ls-tree: + * + * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile + * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README + * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c + * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf + */ + +#define SIZEOF_TREE_ATTR \ + STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t") + +#define TREE_UP_FORMAT "040000 tree %s\t.." + +static int +tree_compare_entry(enum line_type type1, char *name1, + enum line_type type2, char *name2) +{ + if (type1 != type2) { + if (type1 == LINE_TREE_DIR) + return -1; + return 1; + } + + return strcmp(name1, name2); +} + +static bool +tree_read(struct view *view, char *text) +{ + size_t textlen = strlen(text); + char buf[SIZEOF_STR]; + unsigned long pos; + enum line_type type; + + if (textlen <= SIZEOF_TREE_ATTR) + return FALSE; + + type = text[STRING_SIZE("100644 ")] == 't' + ? LINE_TREE_DIR : LINE_TREE_FILE; + + /* The first time around ... */ + if (!view->lines) { + /* Add path info line */ + if (snprintf(buf, sizeof(buf), "Directory path /%s", opt_path) < sizeof(buf) && + realloc_lines(view, view->line_size + 1) && + pager_read(view, buf)) + view->line[view->lines - 1].type = LINE_DEFAULT; + else + return FALSE; + + /* Insert "link" to parent directory. */ + if (*opt_path && + snprintf(buf, sizeof(buf), TREE_UP_FORMAT, view->ref) < sizeof(buf) && + realloc_lines(view, view->line_size + 1) && + pager_read(view, buf)) + view->line[view->lines - 1].type = LINE_TREE_DIR; + else if (*opt_path) + return FALSE; + } + + /* Strip the path part ... */ + if (*opt_path) { + size_t pathlen = textlen - SIZEOF_TREE_ATTR; + size_t striplen = strlen(opt_path); + char *path = text + SIZEOF_TREE_ATTR; + + if (pathlen > striplen) + memmove(path, path + striplen, + pathlen - striplen + 1); + } + + /* Skip "Directory ..." and ".." line. */ + for (pos = 1 + !!*opt_path; pos < view->lines; pos++) { + struct line *line = &view->line[pos]; + char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR; + char *path2 = text + SIZEOF_TREE_ATTR; + int cmp = tree_compare_entry(line->type, path1, type, path2); + + if (cmp <= 0) + continue; + + text = strdup(text); + if (!text) + return FALSE; + + if (view->lines > pos) + memmove(&view->line[pos + 1], &view->line[pos], + (view->lines - pos) * sizeof(*line)); + + line = &view->line[pos]; + line->data = text; + line->type = type; + view->lines++; + return TRUE; + } + + if (!pager_read(view, text)) + return FALSE; + + view->line[view->lines - 1].type = type; + return TRUE; +} + +static bool +tree_enter(struct view *view, struct line *line) +{ + enum open_flags flags = OPEN_DEFAULT; + char *data = line->data; + enum request request; + + switch (line->type) { + case LINE_TREE_DIR: + /* Depending on whether it is a subdir or parent (updir?) link + * mangle the path buffer. */ + if (line == &view->line[1] && *opt_path) { + size_t path_len = strlen(opt_path); + char *dirsep = opt_path + path_len - 1; + + while (dirsep > opt_path && dirsep[-1] != '/') + dirsep--; + + dirsep[0] = 0; + + } else { + int pathlen = strlen(opt_path); + char *basename = data + SIZEOF_TREE_ATTR; + + string_format_from(opt_path, &pathlen, "%s/", basename); + } + + /* Trees and subtrees share the same ID, so they are not not + * unique like blobs. */ + flags |= OPEN_RELOAD; + request = REQ_VIEW_TREE; + break; + + case LINE_TREE_FILE: + /* This causes the blob view to become split, and not having it + * in the tree dir case will make the blob view automatically + * disappear when moving to a different directory. */ + flags |= OPEN_SPLIT; + request = REQ_VIEW_BLOB; + break; + + default: + return TRUE; + } + + open_view(view, request, flags); + + if (!VIEW(request)->pipe) + return TRUE; + + /* For tree views insert the path to the parent as the first line. */ + if (request == REQ_VIEW_BLOB) { + /* Mirror what is showed in the title bar. */ + string_ncopy(ref_blob, data + STRING_SIZE("100644 blob "), 40); + string_copy(VIEW(REQ_VIEW_BLOB)->ref, ref_blob); + return TRUE; + } + + return TRUE; +} + +static struct view_ops tree_ops = { + "file", + pager_draw, + tree_read, + tree_enter, + pager_grep, +}; + +static bool +blob_read(struct view *view, char *line) +{ + bool state = pager_read(view, line); + + if (state == TRUE) + view->line[view->lines - 1].type = LINE_DEFAULT; + + return state; +} + +static struct view_ops blob_ops = { + "line", + pager_draw, + blob_read, + pager_enter, + pager_grep, }; @@ -2430,11 +2844,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, }; @@ -2686,11 +3132,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) { @@ -2700,7 +3146,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) { @@ -2727,7 +3173,7 @@ read_prompt(void) default: if (pos >= sizeof(buf)) { report("Input string too long"); - return ERR; + return NULL; } if (isprint(key)) @@ -2738,18 +3184,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; } /* @@ -3010,10 +3450,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;