Make tig handle GIT_DIR better
[tig] / tig.c
diff --git a/tig.c b/tig.c
index 11eb06d..ebd7abe 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -12,7 +12,7 @@
  */
 
 #ifndef        VERSION
-#define VERSION        "tig-0.4.git"
+#define VERSION        "tig-0.5.git"
 #endif
 
 #ifndef DEBUG
@@ -59,6 +59,7 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset,
 
 #define SIZEOF_STR     1024    /* Default string size. */
 #define SIZEOF_REF     256     /* Size of symbolic or SHA1 ID. */
+#define SIZEOF_REV     41      /* Holds a SHA-1 and an ending NUL */
 #define SIZEOF_REVGRAPH        19      /* Size of revision ancestry graphics. */
 
 /* This color name can be used to refer to the default term colors. */
@@ -80,7 +81,7 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset,
 #define        SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
 
 #define TIG_LS_REMOTE \
-       "git ls-remote . 2>/dev/null"
+       "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
 
 #define TIG_DIFF_CMD \
        "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
@@ -109,7 +110,7 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset,
 
 struct ref {
        char *name;             /* Ref name; tag or head names are shortened. */
-       char id[41];            /* Commit SHA1 ID */
+       char id[SIZEOF_REV];    /* Commit SHA1 ID */
        unsigned int tag:1;     /* Is it a tag? */
        unsigned int next:1;    /* For ref lists: are there more refs? */
 };
@@ -145,16 +146,22 @@ set_from_int_map(struct int_map *map, size_t map_size,
  */
 
 static inline void
-string_ncopy(char *dst, const char *src, size_t dstlen)
+string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
 {
-       strncpy(dst, src, dstlen - 1);
-       dst[dstlen - 1] = 0;
+       if (srclen > dstlen - 1)
+               srclen = dstlen - 1;
 
+       strncpy(dst, src, srclen);
+       dst[srclen] = 0;
 }
 
-/* Shorthand for safely copying into a fixed buffer. */
+/* Shorthands for safely copying into a fixed buffer. */
+
 #define string_copy(dst, src) \
-       string_ncopy(dst, src, sizeof(dst))
+       string_ncopy_do(dst, sizeof(dst), src, sizeof(dst))
+
+#define string_ncopy(dst, src, srclen) \
+       string_ncopy_do(dst, sizeof(dst), src, srclen)
 
 static char *
 chomp_string(char *name)
@@ -444,6 +451,17 @@ parse_options(int argc, char *argv[])
        for (i = 1; i < argc; i++) {
                char *opt = argv[i];
 
+               if (!strcmp(opt, "log") ||
+                   !strcmp(opt, "diff") ||
+                   !strcmp(opt, "show")) {
+                       opt_request = opt[0] == 'l'
+                                   ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
+                       break;
+               }
+
+               if (opt[0] && opt[0] != '-')
+                       break;
+
                if (!strcmp(opt, "-l")) {
                        opt_request = REQ_VIEW_LOG;
                        continue;
@@ -479,17 +497,6 @@ parse_options(int argc, char *argv[])
                        break;
                }
 
-               if (!strcmp(opt, "log") ||
-                   !strcmp(opt, "diff") ||
-                   !strcmp(opt, "show")) {
-                       opt_request = opt[0] == 'l'
-                                   ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
-                       break;
-               }
-
-               if (opt[0] && opt[0] != '-')
-                       break;
-
                die("unknown option '%s'\n\n%s", opt, usage);
        }
 
@@ -503,7 +510,7 @@ parse_options(int argc, char *argv[])
                if (opt_request == REQ_VIEW_MAIN)
                        /* XXX: This is vulnerable to the user overriding
                         * options required for the main view parser. */
-                       string_copy(opt_cmd, "git log --stat --pretty=raw");
+                       string_copy(opt_cmd, "git log --pretty=raw");
                else
                        string_copy(opt_cmd, "git");
                buf_size = strlen(opt_cmd);
@@ -517,7 +524,6 @@ parse_options(int argc, char *argv[])
                        die("command too long");
 
                opt_cmd[buf_size] = 0;
-
        }
 
        if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
@@ -559,6 +565,7 @@ LINE(TREE,     "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
 LINE(AUTHOR,      "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
 LINE(COMMITTER,           "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
 LINE(SIGNOFF,     "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
+LINE(ACKED,       "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
 LINE(DEFAULT,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
 LINE(CURSOR,      "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
 LINE(STATUS,      "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
@@ -655,6 +662,10 @@ init_colors(void)
 
 struct line {
        enum line_type type;
+
+       /* State flags */
+       unsigned int selected:1;
+
        void *data;             /* User data */
 };
 
@@ -675,7 +686,7 @@ static struct keybinding default_keybindings[] = {
        { 'd',          REQ_VIEW_DIFF },
        { 'l',          REQ_VIEW_LOG },
        { 't',          REQ_VIEW_TREE },
-       { 'b',          REQ_VIEW_BLOB },
+       { 'f',          REQ_VIEW_BLOB },
        { 'p',          REQ_VIEW_PAGER },
        { 'h',          REQ_VIEW_HELP },
 
@@ -718,9 +729,6 @@ static struct keybinding default_keybindings[] = {
        { 'g',          REQ_TOGGLE_REV_GRAPH },
        { ':',          REQ_PROMPT },
 
-       /* wgetch() with nodelay() enabled returns ERR when there's no input. */
-       { ERR,          REQ_NONE },
-
        /* Using the ncurses SIGWINCH handler. */
        { KEY_RESIZE,   REQ_SCREEN_RESIZE },
 };
@@ -1152,6 +1160,9 @@ struct view_ops;
 static struct view *display[2];
 static unsigned int current_view;
 
+/* Reading from the prompt? */
+static bool input_mode = FALSE;
+
 #define foreach_displayed_view(view, i) \
        for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
 
@@ -1186,7 +1197,7 @@ struct view {
 
        /* Searching */
        char grep[SIZEOF_STR];  /* Search string */
-       regex_t regex;          /* Pre-compiled regex */
+       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. */
@@ -1207,13 +1218,15 @@ struct view_ops {
        /* What type of content being displayed. Used in the title bar. */
        const char *type;
        /* Draw one line; @lineno must be < view->height. */
-       bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
+       bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
        /* Read one line; updates view->line. */
        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);
+       /* Select line */
+       void (*select)(struct view *view, struct line *line);
 };
 
 static struct view_ops pager_ops;
@@ -1249,12 +1262,31 @@ static struct view views[] = {
 static bool
 draw_view_line(struct view *view, unsigned int lineno)
 {
+       struct line *line;
+       bool selected = (view->offset + lineno == view->lineno);
+       bool draw_ok;
+
        assert(view_is_displayed(view));
 
        if (view->offset + lineno >= view->lines)
                return FALSE;
 
-       return view->ops->draw(view, &view->line[view->offset + lineno], lineno);
+       line = &view->line[view->offset + lineno];
+
+       if (selected) {
+               line->selected = TRUE;
+               view->ops->select(view, line);
+       } else if (line->selected) {
+               line->selected = FALSE;
+               wmove(view->win, lineno, 0);
+               wclrtoeol(view->win);
+       }
+
+       scrollok(view->win, FALSE);
+       draw_ok = view->ops->draw(view, line, lineno, selected);
+       scrollok(view->win, TRUE);
+
+       return draw_ok;
 }
 
 static void
@@ -1268,7 +1300,10 @@ redraw_view_from(struct view *view, int lineno)
        }
 
        redrawwin(view->win);
-       wrefresh(view->win);
+       if (input_mode)
+               wnoutrefresh(view->win);
+       else
+               wrefresh(view->win);
 }
 
 static void
@@ -1282,20 +1317,11 @@ 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
-               wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
-
-       werase(view->title);
-       wmove(view->title, 0, 0);
+       char buf[SIZEOF_STR];
+       char state[SIZEOF_STR];
+       size_t bufpos = 0, statelen = 0;
 
-       if (*view->ref)
-               wprintw(view->title, "[%s] %s", view->name, view->ref);
-       else
-               wprintw(view->title, "[%s]", view->name);
+       assert(view_is_displayed(view));
 
        if (view->lines || view->pipe) {
                unsigned int view_lines = view->offset + view->height;
@@ -1303,23 +1329,48 @@ update_view_title(struct view *view)
                                   ? MIN(view_lines, view->lines) * 100 / view->lines
                                   : 0;
 
-               wprintw(view->title, " - %s %d of %d (%d%%)",
-                       view->ops->type,
-                       view->lineno + 1,
-                       view->lines,
-                       lines);
+               string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
+                                  view->ops->type,
+                                  view->lineno + 1,
+                                  view->lines,
+                                  lines);
+
+               if (view->pipe) {
+                       time_t secs = time(NULL) - view->start_time;
+
+                       /* Three git seconds are a long time ... */
+                       if (secs > 2)
+                               string_format_from(state, &statelen, " %lds", secs);
+               }
        }
 
-       if (view->pipe) {
-               time_t secs = time(NULL) - view->start_time;
+       string_format_from(buf, &bufpos, "[%s]", view->name);
+       if (*view->ref && bufpos < view->width) {
+               size_t refsize = strlen(view->ref);
+               size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
 
-               /* Three git seconds are a long time ... */
-               if (secs > 2)
-                       wprintw(view->title, " %lds", secs);
+               if (minsize < view->width)
+                       refsize = view->width - minsize + 7;
+               string_format_from(buf, &bufpos, " %.*s", refsize, view->ref);
        }
 
+       if (statelen && bufpos < view->width) {
+               string_format_from(buf, &bufpos, " %s", state);
+       }
+
+       if (view == display[current_view])
+               wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
+       else
+               wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
+
+       mvwaddnstr(view->title, 0, 0, buf, bufpos);
+       wclrtoeol(view->title);
        wmove(view->title, 0, view->width - 1);
-       wrefresh(view->title);
+
+       if (input_mode)
+               wnoutrefresh(view->title);
+       else
+               wrefresh(view->title);
 }
 
 static void
@@ -1386,10 +1437,8 @@ redraw_display(void)
 }
 
 static void
-update_display_cursor(void)
+update_display_cursor(struct view *view)
 {
-       struct view *view = display[current_view];
-
        /* Move the cursor to the right-most column of the cursor line.
         *
         * XXX: This could turn out to be a bit expensive, but it ensures that
@@ -1406,9 +1455,9 @@ update_display_cursor(void)
 
 /* Scrolling backend */
 static void
-do_scroll_view(struct view *view, int lines, bool redraw)
+do_scroll_view(struct view *view, int lines)
 {
-       assert(view_is_displayed(view));
+       bool redraw_current_line = FALSE;
 
        /* The rendering expects the new offset. */
        view->offset += lines;
@@ -1416,6 +1465,17 @@ do_scroll_view(struct view *view, int lines, bool redraw)
        assert(0 <= view->offset && view->offset < view->lines);
        assert(lines);
 
+       /* Move current line into the view. */
+       if (view->lineno < view->offset) {
+               view->lineno = view->offset;
+               redraw_current_line = TRUE;
+       } else if (view->lineno >= view->offset + view->height) {
+               view->lineno = view->offset + view->height - 1;
+               redraw_current_line = TRUE;
+       }
+
+       assert(view->offset <= view->lineno && view->lineno < view->lines);
+
        /* Redraw the whole screen if scrolling is pointless. */
        if (view->height < ABS(lines)) {
                redraw_view(view);
@@ -1430,29 +1490,11 @@ do_scroll_view(struct view *view, int lines, bool redraw)
                        if (!draw_view_line(view, line))
                                break;
                }
-       }
-
-       /* Move current line into the view. */
-       if (view->lineno < view->offset) {
-               view->lineno = view->offset;
-               draw_view_line(view, 0);
 
-       } else if (view->lineno >= view->offset + view->height) {
-               if (view->lineno == view->offset + view->height) {
-                       /* Clear the hidden line so it doesn't show if the view
-                        * is scrolled up. */
-                       wmove(view->win, view->height, 0);
-                       wclrtoeol(view->win);
-               }
-               view->lineno = view->offset + view->height - 1;
-               draw_view_line(view, view->lineno - view->offset);
+               if (redraw_current_line)
+                       draw_view_line(view, view->lineno - view->offset);
        }
 
-       assert(view->offset <= view->lineno && view->lineno < view->lines);
-
-       if (!redraw)
-               return;
-
        redrawwin(view->win);
        wrefresh(view->win);
        report("");
@@ -1464,6 +1506,8 @@ scroll_view(struct view *view, enum request request)
 {
        int lines = 1;
 
+       assert(view_is_displayed(view));
+
        switch (request) {
        case REQ_SCROLL_PAGE_DOWN:
                lines = view->height;
@@ -1495,13 +1539,14 @@ scroll_view(struct view *view, enum request request)
                die("request %d not handled in switch", request);
        }
 
-       do_scroll_view(view, lines, TRUE);
+       do_scroll_view(view, lines);
 }
 
 /* Cursor moving */
 static void
-move_view(struct view *view, enum request request, bool redraw)
+move_view(struct view *view, enum request request)
 {
+       int scroll_steps = 0;
        int steps;
 
        switch (request) {
@@ -1548,39 +1593,40 @@ move_view(struct view *view, enum request request, bool redraw)
        view->lineno += steps;
        assert(0 <= view->lineno && view->lineno < view->lines);
 
-       /* Repaint the old "current" line if we be scrolling */
-       if (ABS(steps) < view->height) {
-               int prev_lineno = view->lineno - steps - view->offset;
-
-               wmove(view->win, prev_lineno, 0);
-               wclrtoeol(view->win);
-               draw_view_line(view,  prev_lineno);
-       }
-
        /* Check whether the view needs to be scrolled */
        if (view->lineno < view->offset ||
            view->lineno >= view->offset + view->height) {
+               scroll_steps = steps;
                if (steps < 0 && -steps > view->offset) {
-                       steps = -view->offset;
+                       scroll_steps = -view->offset;
 
                } else if (steps > 0) {
                        if (view->lineno == view->lines - 1 &&
                            view->lines > view->height) {
-                               steps = view->lines - view->offset - 1;
-                               if (steps >= view->height)
-                                       steps -= view->height - 1;
+                               scroll_steps = view->lines - view->offset - 1;
+                               if (scroll_steps >= view->height)
+                                       scroll_steps -= view->height - 1;
                        }
                }
+       }
 
-               do_scroll_view(view, steps, redraw);
+       if (!view_is_displayed(view)) {
+               view->offset += steps;
+               view->ops->select(view, &view->line[view->lineno]);
                return;
        }
 
-       /* Draw the current line */
-       draw_view_line(view, view->lineno - view->offset);
+       /* Repaint the old "current" line if we be scrolling */
+       if (ABS(steps) < view->height)
+               draw_view_line(view, view->lineno - steps - view->offset);
 
-       if (!redraw)
+       if (scroll_steps) {
+               do_scroll_view(view, scroll_steps);
                return;
+       }
+
+       /* Draw the current line */
+       draw_view_line(view, view->lineno - view->offset);
 
        redrawwin(view->win);
        wrefresh(view->win);
@@ -1592,7 +1638,7 @@ 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 void search_view(struct view *view, enum request request);
 
 static bool
 find_next_line(struct view *view, unsigned long lineno, struct line *line)
@@ -1611,9 +1657,6 @@ find_next_line(struct view *view, unsigned long lineno, struct line *line)
                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);
@@ -1635,7 +1678,7 @@ find_next(struct view *view, enum request request)
                if (!*opt_search)
                        report("No previous search");
                else
-                       search_view(view, request, opt_search);
+                       search_view(view, request);
                return;
        }
 
@@ -1670,25 +1713,29 @@ find_next(struct view *view, enum request request)
 }
 
 static void
-search_view(struct view *view, enum request request, const char *search)
+search_view(struct view *view, enum request request)
 {
        int regex_err;
 
-       if (*view->grep) {
-               regfree(&view->regex);
+       if (view->regex) {
+               regfree(view->regex);
                *view->grep = 0;
+       } else {
+               view->regex = calloc(1, sizeof(*view->regex));
+               if (!view->regex)
+                       return;
        }
 
-       regex_err = regcomp(&view->regex, search, REG_EXTENDED);
+       regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
        if (regex_err != 0) {
                char buf[SIZEOF_STR] = "unknown error";
 
-               regerror(regex_err, &view->regex, buf, sizeof(buf));
+               regerror(regex_err, view->regex, buf, sizeof(buf));
                report("Search failed: %s", buf);
                return;
        }
 
-       string_copy(view->grep, search);
+       string_copy(view->grep, opt_search);
 
        find_next(view, request);
 }
@@ -1994,7 +2041,7 @@ open_view(struct view *prev, enum request request, enum open_flags flags)
 
                /* Scroll the view that was split if the current line is
                 * outside the new limited view. */
-               do_scroll_view(prev, lines, TRUE);
+               do_scroll_view(prev, lines);
        }
 
        if (prev && view != prev) {
@@ -2039,7 +2086,7 @@ view_driver(struct view *view, enum request request)
        case REQ_MOVE_PAGE_DOWN:
        case REQ_MOVE_FIRST_LINE:
        case REQ_MOVE_LAST_LINE:
-               move_view(view, request, TRUE);
+               move_view(view, request);
                break;
 
        case REQ_SCROLL_LINE_DOWN:
@@ -2072,14 +2119,12 @@ view_driver(struct view *view, enum request request)
                     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;
-                       move_view(view, request, redraw);
-                       if (redraw)
+                       move_view(view, request);
+                       if (view_is_displayed(view))
                                update_view_title(view);
                } else {
-                       move_view(view, request, TRUE);
+                       move_view(view, request);
                        break;
                }
                /* Fall-through */
@@ -2124,7 +2169,7 @@ view_driver(struct view *view, enum request request)
 
        case REQ_SEARCH:
        case REQ_SEARCH_BACK:
-               search_view(view, request, opt_search);
+               search_view(view, request);
                break;
 
        case REQ_FIND_NEXT:
@@ -2189,7 +2234,7 @@ view_driver(struct view *view, enum request request)
  */
 
 static bool
-pager_draw(struct view *view, struct line *line, unsigned int lineno)
+pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
 {
        char *text = line->data;
        enum line_type type = line->type;
@@ -2198,17 +2243,7 @@ pager_draw(struct view *view, struct line *line, unsigned int lineno)
 
        wmove(view->win, lineno, 0);
 
-       if (view->offset + lineno == view->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);
-               }
-
+       if (selected) {
                type = LINE_CURSOR;
                wchgat(view->win, -1, 0, type, NULL);
        }
@@ -2275,7 +2310,7 @@ add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
        char *ref = NULL;
        FILE *pipe;
 
-       if (!string_format(refbuf, "git describe %s", commit_id))
+       if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
                return TRUE;
 
        pipe = popen(refbuf, "r");
@@ -2328,10 +2363,14 @@ add_pager_refs(struct view *view, struct line *line)
 
        if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
 try_add_describe_ref:
+               /* Add <tag>-g<commit_id> "fake" reference. */
                if (!add_describe_ref(buf, &bufpos, commit_id, sep))
                        return;
        }
 
+       if (bufpos == 0)
+               return;
+
        if (!realloc_lines(view, view->line_size + 1))
                return;
 
@@ -2399,18 +2438,30 @@ pager_grep(struct view *view, struct line *line)
        if (!*text)
                return FALSE;
 
-       if (regexec(&view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
+       if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
                return FALSE;
 
        return TRUE;
 }
 
+static void
+pager_select(struct view *view, struct line *line)
+{
+       if (line->type == LINE_COMMIT) {
+               char *text = line->data;
+
+               string_copy(view->ref, text + STRING_SIZE("commit "));
+               string_copy(ref_commit, view->ref);
+       }
+}
+
 static struct view_ops pager_ops = {
        "line",
        pager_draw,
        pager_read,
        pager_enter,
        pager_grep,
+       pager_select,
 };
 
 
@@ -2418,7 +2469,7 @@ static struct view_ops pager_ops = {
  * Tree backend
  */
 
-/* Parse output from git ls-tree:
+/* Parse output from git-ls-tree(1):
  *
  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26        Makefile
  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6        README
@@ -2528,8 +2579,7 @@ tree_read(struct view *view, char *text)
 static bool
 tree_enter(struct view *view, struct line *line)
 {
-       enum open_flags flags = OPEN_DEFAULT;
-       char *data = line->data;
+       enum open_flags flags;
        enum request request;
 
        switch (line->type) {
@@ -2548,9 +2598,10 @@ tree_enter(struct view *view, struct line *line)
                } else {
                        size_t pathlen = strlen(opt_path);
                        size_t origlen = pathlen;
+                       char *data = line->data;
                        char *basename = data + SIZEOF_TREE_ATTR;
 
-                       if (string_format_from(opt_path, &pathlen, "%s/", basename)) {
+                       if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
                                opt_path[origlen] = 0;
                                return TRUE;
                        }
@@ -2558,15 +2609,12 @@ tree_enter(struct view *view, struct line *line)
 
                /* Trees and subtrees share the same ID, so they are not not
                 * unique like blobs. */
-               flags |= OPEN_RELOAD;
+               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;
+               flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
                request = REQ_VIEW_BLOB;
                break;
 
@@ -2576,18 +2624,27 @@ tree_enter(struct view *view, struct line *line)
 
        open_view(view, request, flags);
 
-       if (!VIEW(request)->pipe)
-               return TRUE;
+       return TRUE;
+}
+
+static void
+tree_select(struct view *view, struct line *line)
+{
+       char *text = line->data;
 
-       /* 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);
+       text += STRING_SIZE("100644 blob ");
+
+       if (line->type == LINE_TREE_FILE) {
+               string_ncopy(ref_blob, text, 40);
+               /* Also update the blob view's ref, since all there must always
+                * be in sync. */
                string_copy(VIEW(REQ_VIEW_BLOB)->ref, ref_blob);
-               return TRUE;
+
+       } else if (line->type != LINE_TREE_DIR) {
+               return;
        }
 
-       return TRUE;
+       string_ncopy(view->ref, text, 40);
 }
 
 static struct view_ops tree_ops = {
@@ -2596,6 +2653,7 @@ static struct view_ops tree_ops = {
        tree_read,
        tree_enter,
        pager_grep,
+       tree_select,
 };
 
 static bool
@@ -2615,6 +2673,7 @@ static struct view_ops blob_ops = {
        blob_read,
        pager_enter,
        pager_grep,
+       pager_select,
 };
 
 
@@ -2623,8 +2682,8 @@ static struct view_ops blob_ops = {
  */
 
 struct commit {
-       char id[41];                    /* SHA1 ID. */
-       char title[75];                 /* First line of the commit message. */
+       char id[SIZEOF_REV];            /* SHA1 ID. */
+       char title[128];                /* First line of the commit message. */
        char author[75];                /* Author of the commit. */
        struct tm time;                 /* Date from the author ident. */
        struct ref **refs;              /* Repository references. */
@@ -2633,7 +2692,7 @@ struct commit {
 };
 
 static bool
-main_draw(struct view *view, struct line *line, unsigned int lineno)
+main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
 {
        char buf[DATE_COLS + 1];
        struct commit *commit = line->data;
@@ -2648,9 +2707,7 @@ main_draw(struct view *view, struct line *line, unsigned int lineno)
 
        wmove(view->win, lineno, col);
 
-       if (view->offset + lineno == view->lineno) {
-               string_copy(view->ref, commit->id);
-               string_copy(ref_commit, view->ref);
+       if (selected) {
                type = LINE_CURSOR;
                wattrset(view->win, get_line_attr(type));
                wchgat(view->win, -1, 0, type, NULL);
@@ -2765,43 +2822,32 @@ main_read(struct view *view, char *line)
 
        case LINE_AUTHOR:
        {
+               /* Parse author lines where the name may be empty:
+                *      author  <email@address.tld> 1138474660 +0100
+                */
                char *ident = line + STRING_SIZE("author ");
-               char *end = strchr(ident, '<');
+               char *nameend = strchr(ident, '<');
+               char *emailend = strchr(ident, '>');
 
-               if (!commit)
+               if (!commit || !nameend || !emailend)
                        break;
 
-               if (end) {
-                       char *email = end + 1;
-
-                       for (; end > ident && isspace(end[-1]); end--) ;
-
-                       if (end == ident && *email) {
-                               ident = email;
-                               end = strchr(ident, '>');
-                               for (; end > ident && isspace(end[-1]); end--) ;
-                       }
-                       *end = 0;
+               *nameend = *emailend = 0;
+               ident = chomp_string(ident);
+               if (!*ident) {
+                       ident = chomp_string(nameend + 1);
+                       if (!*ident)
+                               ident = "Unknown";
                }
 
-               /* End is NULL or ident meaning there's no author. */
-               if (end <= ident)
-                       ident = "Unknown";
-
                string_copy(commit->author, ident);
 
                /* Parse epoch and timezone */
-               if (end) {
-                       char *secs = strchr(end + 1, '>');
-                       char *zone;
-                       time_t time;
-
-                       if (!secs || secs[1] != ' ')
-                               break;
+               if (emailend[1] == ' ') {
+                       char *secs = emailend + 2;
+                       char *zone = strchr(secs, ' ');
+                       time_t time = (time_t) atol(secs);
 
-                       secs += 2;
-                       time = (time_t) atol(secs);
-                       zone = strchr(secs, ' ');
                        if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
                                long tz;
 
@@ -2816,6 +2862,7 @@ main_read(struct view *view, char *line)
 
                                time -= tz;
                        }
+
                        gmtime_r(&time, &commit->time);
                }
                break;
@@ -2830,13 +2877,19 @@ main_read(struct view *view, char *line)
 
                /* Require titles to start with a non-space character at the
                 * offset used by git log. */
-               /* FIXME: More gracefull handling of titles; append "..." to
-                * shortened titles, etc. */
-               if (strncmp(line, "    ", 4) ||
-                   isspace(line[4]))
+               if (strncmp(line, "    ", 4))
                        break;
+               line += 4;
+               /* Well, if the title starts with a whitespace character,
+                * try to be forgiving.  Otherwise we end up with no title. */
+               while (isspace(*line))
+                       line++;
+               if (*line == '\0')
+                       break;
+               /* FIXME: More graceful handling of titles; append "..." to
+                * shortened titles, etc. */
 
-               string_copy(commit->title, line + 4);
+               string_copy(commit->title, line);
        }
 
        return TRUE;
@@ -2875,19 +2928,29 @@ main_grep(struct view *view, struct line *line)
                        return FALSE;
                }
 
-               if (regexec(&view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
+               if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
                        return TRUE;
        }
 
        return FALSE;
 }
 
+static void
+main_select(struct view *view, struct line *line)
+{
+       struct commit *commit = line->data;
+
+       string_copy(view->ref, commit->id);
+       string_copy(ref_commit, view->ref);
+}
+
 static struct view_ops main_ops = {
        "commit",
        main_draw,
        main_read,
        main_enter,
        main_grep,
+       main_select,
 };
 
 
@@ -3061,33 +3124,37 @@ static bool cursed = FALSE;
 /* The status window is used for polling keystrokes. */
 static WINDOW *status_win;
 
+static bool status_empty = TRUE;
+
 /* Update status and title window. */
 static void
 report(const char *msg, ...)
 {
-       static bool empty = TRUE;
        struct view *view = display[current_view];
 
-       if (!empty || *msg) {
+       if (input_mode)
+               return;
+
+       if (!status_empty || *msg) {
                va_list args;
 
                va_start(args, msg);
 
-               werase(status_win);
                wmove(status_win, 0, 0);
                if (*msg) {
                        vwprintw(status_win, msg, args);
-                       empty = FALSE;
+                       status_empty = FALSE;
                } else {
-                       empty = TRUE;
+                       status_empty = TRUE;
                }
+               wclrtoeol(status_win);
                wrefresh(status_win);
 
                va_end(args);
        }
 
        update_view_title(view);
-       update_display_cursor();
+       update_display_cursor(view);
 }
 
 /* Controls when nodelay should be in effect when polling user input. */
@@ -3150,10 +3217,16 @@ read_prompt(const char *prompt)
                struct view *view;
                int i, key;
 
+               input_mode = TRUE;
+
                foreach_view (view, i)
                        update_view(view);
 
-               report("%s%.*s", prompt, pos, buf);
+               input_mode = FALSE;
+
+               mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
+               wclrtoeol(status_win);
+
                /* Refresh, accept single keystroke of input */
                key = wgetch(status_win);
                switch (key) {
@@ -3188,11 +3261,12 @@ read_prompt(const char *prompt)
                }
        }
 
-       if (status == CANCEL) {
-               /* Clear the status window */
-               report("");
+       /* Clear the status window */
+       status_empty = FALSE;
+       report("");
+
+       if (status == CANCEL)
                return NULL;
-       }
 
        buf[pos++] = 0;
 
@@ -3451,6 +3525,13 @@ main(int argc, char *argv[])
                /* Refresh, accept single keystroke of input */
                key = wgetch(status_win);
 
+               /* wgetch() with nodelay() enabled returns ERR when there's no
+                * input. */
+               if (key == ERR) {
+                       request = REQ_NONE;
+                       continue;
+               }
+
                request = get_keybinding(display[current_view]->keymap, key);
 
                /* Some low-level request handling. This keeps access to