X-Git-Url: https://git.distorted.org.uk/~mdw/tig/blobdiff_plain/8fee6614be3b0fe4b1f65e5746be05b67737a22a..0001fc3426ae001a0bd465dcd19a20e2005cdfb6:/tig.c diff --git a/tig.c b/tig.c index b2e1a36..d3702c4 100644 --- a/tig.c +++ b/tig.c @@ -30,6 +30,13 @@ #include #include +#include +#include + +#include +#include +#include + #include #if __GNUC__ >= 3 @@ -50,13 +57,15 @@ 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. */ #define COLOR_DEFAULT (-1) +#define ICONV_NONE ((iconv_t) -1) + /* The format and size of the date column in the main view. */ #define DATE_FORMAT "%Y-%m-%d %H:%M" #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ") @@ -74,7 +83,7 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset, "git ls-remote . 2>/dev/null" #define TIG_DIFF_CMD \ - "git show --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null" + "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null" #define TIG_LOG_CMD \ "git log --cc --stat -n100 %s 2>/dev/null" @@ -82,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 "" @@ -130,7 +145,7 @@ set_from_int_map(struct int_map *map, size_t map_size, */ static inline void -string_ncopy(char *dst, const char *src, int dstlen) +string_ncopy(char *dst, const char *src, size_t dstlen) { strncpy(dst, src, dstlen - 1); dst[dstlen - 1] = 0; @@ -157,10 +172,10 @@ chomp_string(char *name) } static bool -string_nformat(char *buf, size_t bufsize, int *bufpos, const char *fmt, ...) +string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...) { va_list args; - int pos = bufpos ? *bufpos : 0; + size_t pos = bufpos ? *bufpos : 0; va_start(args, fmt); pos += vsnprintf(buf + pos, bufsize - pos, fmt, args); @@ -218,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++)) { @@ -251,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"), \ \ @@ -276,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"), \ @@ -355,15 +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 char opt_encoding[20] = ""; -static bool opt_utf8 = TRUE; -static FILE *opt_pipe = NULL; +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, @@ -543,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) \ @@ -645,9 +674,10 @@ static struct keybinding default_keybindings[] = { { 'm', REQ_VIEW_MAIN }, { 'd', REQ_VIEW_DIFF }, { 'l', REQ_VIEW_LOG }, + { 't', REQ_VIEW_TREE }, + { 'f', REQ_VIEW_BLOB }, { 'p', REQ_VIEW_PAGER }, { 'h', REQ_VIEW_HELP }, - { '?', REQ_VIEW_HELP }, /* View manipulation */ { 'q', REQ_VIEW_CLOSE }, @@ -673,19 +703,25 @@ 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 }, { 'v', REQ_SHOW_VERSION }, { 'r', REQ_SCREEN_REDRAW }, - { 'n', REQ_TOGGLE_LINENO }, - { 'g', REQ_TOGGLE_REV_GRAPH}, + { '.', REQ_TOGGLE_LINENO }, + { 'g', REQ_TOGGLE_REV_GRAPH }, { ':', 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 }, }; @@ -694,6 +730,8 @@ static struct keybinding default_keybindings[] = { KEYMAP_(MAIN), \ KEYMAP_(DIFF), \ KEYMAP_(LOG), \ + KEYMAP_(TREE), \ + KEYMAP_(BLOB), \ KEYMAP_(PAGER), \ KEYMAP_(HELP) \ @@ -809,7 +847,7 @@ get_key(enum request request) { static char buf[BUFSIZ]; static char key_char[] = "'X'"; - int pos = 0; + size_t pos = 0; char *sep = " "; int i; @@ -1081,7 +1119,7 @@ static int load_options(void) { char *home = getenv("HOME"); - char buf[1024]; + char buf[SIZEOF_STR]; FILE *file; config_lineno = 0; @@ -1114,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"; @@ -1127,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. */ @@ -1145,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; @@ -1169,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} @@ -1185,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; @@ -1226,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 @@ -1293,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) @@ -1321,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); } @@ -1350,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; @@ -1529,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 */ @@ -1559,6 +1724,16 @@ 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 (!string_format(view->cmd, format, id, opt_path)) + return FALSE; + } else { const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt; @@ -1616,7 +1791,8 @@ realloc_lines(struct view *view, size_t line_size) static bool update_view(struct view *view) { - char buffer[BUFSIZ]; + char in_buffer[BUFSIZ]; + char out_buffer[BUFSIZ * 2]; char *line; /* The number of lines to read. If too low it will cause too much * redrawing (and possible flickering), if too high responsiveness @@ -1631,15 +1807,32 @@ 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; - while ((line = fgets(buffer, sizeof(buffer), view->pipe))) { - int linelen = strlen(line); + while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) { + size_t linelen = strlen(line); if (linelen) line[linelen - 1] = 0; + if (opt_iconv != ICONV_NONE) { + char *inbuf = line; + size_t inlen = linelen; + + char *outbuf = out_buffer; + size_t outlen = sizeof(out_buffer); + + size_t ret; + + ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen); + if (ret != (size_t) -1) { + line = out_buffer; + linelen = strlen(out_buffer); + } + } + if (!view->ops->read(view, line)) goto alloc_error; @@ -1661,7 +1854,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. */ @@ -1676,6 +1877,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; @@ -1847,9 +2049,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); @@ -1859,8 +2068,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; @@ -1911,6 +2122,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]; @@ -1931,7 +2152,7 @@ view_driver(struct view *view, enum request request) redraw_display(); break; - case REQ_SCREEN_UPDATE: + case REQ_NONE: doupdate(); return TRUE; @@ -1981,6 +2202,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; @@ -2042,20 +2268,52 @@ pager_draw(struct view *view, struct line *line, unsigned int lineno) return TRUE; } +static bool +add_describe_ref(char *buf, size_t *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 *data = line->data; + char buf[SIZEOF_STR]; + char *commit_id = line->data + STRING_SIZE("commit "); struct ref **refs; - int bufpos = 0, refpos = 0; + size_t bufpos = 0, refpos = 0; const char *sep = "Refs: "; + bool is_tag = FALSE; assert(line->type == LINE_COMMIT); - refs = get_refs(data + STRING_SIZE("commit ")); - if (!refs) + refs = get_refs(commit_id); + if (!refs) { + if (view == VIEW(REQ_VIEW_DIFF)) + goto try_add_describe_ref; return; + } do { struct ref *ref = refs[refpos]; @@ -2064,8 +2322,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; @@ -2124,11 +2390,231 @@ 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; + bool first_read = view->lines == 0; + + if (textlen <= SIZEOF_TREE_ATTR) + return FALSE; + + type = text[STRING_SIZE("100644 ")] == 't' + ? LINE_TREE_DIR : LINE_TREE_FILE; + + if (first_read) { + /* Add path info line */ + if (string_format(buf, "Directory path /%s", opt_path) && + 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 && + string_format(buf, TREE_UP_FORMAT, view->ref) && + 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; + + /* Move the current line to the first tree entry. */ + if (first_read) + view->lineno++; + + 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 { + size_t pathlen = strlen(opt_path); + size_t origlen = pathlen; + char *basename = data + SIZEOF_TREE_ATTR; + + if (string_format_from(opt_path, &pathlen, "%s/", basename)) { + opt_path[origlen] = 0; + return TRUE; + } + } + + /* 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, }; @@ -2365,11 +2851,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, }; @@ -2621,11 +3139,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) { @@ -2635,7 +3153,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) { @@ -2662,7 +3180,7 @@ read_prompt(void) default: if (pos >= sizeof(buf)) { report("Input string too long"); - return ERR; + return NULL; } if (isprint(key)) @@ -2673,15 +3191,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; - opt_request = REQ_VIEW_PAGER; - return OK; + return buf; } /* @@ -2891,6 +3406,10 @@ main(int argc, char *argv[]) signal(SIGINT, quit); + if (setlocale(LC_ALL, "")) { + string_copy(opt_codeset, nl_langinfo(CODESET)); + } + if (load_options() == ERR) die("Failed to load user config."); @@ -2902,6 +3421,12 @@ main(int argc, char *argv[]) if (!parse_options(argc, argv)) return 0; + if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) { + opt_iconv = iconv_open(opt_codeset, opt_encoding); + if (opt_iconv == ICONV_NONE) + die("Failed to initialize character set conversion"); + } + if (load_refs() == ERR) die("Failed to load refs."); @@ -2932,10 +3457,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;