Only split the tree view when the tree view is visible
[tig] / tig.c
diff --git a/tig.c b/tig.c
index 6518f0d..384fe21 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -145,16 +145,22 @@ 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_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)
@@ -172,10 +178,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);
@@ -655,6 +661,10 @@ init_colors(void)
 
 struct line {
        enum line_type type;
+
+       /* State flags */
+       unsigned int selected:1;
+
        void *data;             /* User data */
 };
 
@@ -675,7 +685,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 },
 
@@ -714,7 +724,7 @@ static struct keybinding default_keybindings[] = {
        { 'z',          REQ_STOP_LOADING },
        { 'v',          REQ_SHOW_VERSION },
        { 'r',          REQ_SCREEN_REDRAW },
-       { 'n',          REQ_TOGGLE_LINENO },
+       { '.',          REQ_TOGGLE_LINENO },
        { 'g',          REQ_TOGGLE_REV_GRAPH },
        { ':',          REQ_PROMPT },
 
@@ -847,7 +857,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;
 
@@ -1186,7 +1196,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 +1217,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;
@@ -1240,14 +1252,35 @@ static struct view views[] = {
 
 #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)
 {
+       struct line *line;
+       bool selected = (view->offset + lineno == view->lineno);
+
+       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);
+       }
+
+       return view->ops->draw(view, line, lineno, selected);
 }
 
 static void
@@ -1275,6 +1308,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
@@ -1397,14 +1432,27 @@ 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)
 {
+       bool redraw_current_line = FALSE;
+
        /* The rendering expects the new offset. */
        view->offset += lines;
 
        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);
@@ -1419,29 +1467,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("");
@@ -1453,6 +1483,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;
@@ -1484,13 +1516,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)
 {
+       bool scroll = FALSE;
        int steps;
 
        switch (request) {
@@ -1537,15 +1570,6 @@ 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) {
@@ -1561,15 +1585,26 @@ move_view(struct view *view, enum request request, bool redraw)
                        }
                }
 
-               do_scroll_view(view, steps, redraw);
+               scroll = TRUE;
+       }
+
+       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) {
+               do_scroll_view(view, steps);
                return;
+       }
+
+       /* Draw the current line */
+       draw_view_line(view, view->lineno - view->offset);
 
        redrawwin(view->win);
        wrefresh(view->win);
@@ -1581,11 +1616,13 @@ 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)
 {
+       assert(view_is_displayed(view));
+
        if (!view->ops->grep(view, line))
                return FALSE;
 
@@ -1598,9 +1635,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);
@@ -1622,7 +1656,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;
        }
 
@@ -1657,25 +1691,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));
-               report("Search failed: %s", 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);
 }
@@ -1718,8 +1756,7 @@ begin_update(struct view *view)
                if (strcmp(view->vid, view->id))
                        opt_path[0] = 0;
 
-               if (snprintf(view->cmd, sizeof(view->cmd), format, id, opt_path)
-                   >= sizeof(view->cmd))
+               if (!string_format(view->cmd, format, id, opt_path))
                        return FALSE;
 
        } else {
@@ -1795,6 +1832,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;
 
@@ -1841,6 +1879,9 @@ update_view(struct view *view)
                }
        }
 
+       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. */
@@ -1861,6 +1902,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;
@@ -1977,7 +2019,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) {
@@ -2022,7 +2064,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:
@@ -2055,14 +2097,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 */
@@ -2107,7 +2147,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:
@@ -2172,7 +2212,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;
@@ -2181,17 +2221,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);
        }
@@ -2252,7 +2282,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)
+add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
 {
        char refbuf[SIZEOF_STR];
        char *ref = NULL;
@@ -2285,7 +2315,7 @@ add_pager_refs(struct view *view, struct line *line)
        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;
 
@@ -2311,10 +2341,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;
 
@@ -2382,18 +2416,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,
 };
 
 
@@ -2434,6 +2480,7 @@ tree_read(struct view *view, char *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;
@@ -2441,10 +2488,9 @@ tree_read(struct view *view, char *text)
        type = text[STRING_SIZE("100644 ")] == 't'
             ? LINE_TREE_DIR : LINE_TREE_FILE;
 
-       /* The first time around ... */
-       if (!view->lines) {
+       if (first_read) {
                /* Add path info line */
-               if (snprintf(buf, sizeof(buf), "Directory path /%s", opt_path) < sizeof(buf) &&
+               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;
@@ -2453,7 +2499,7 @@ tree_read(struct view *view, char *text)
 
                /* Insert "link" to parent directory. */
                if (*opt_path &&
-                   snprintf(buf, sizeof(buf), TREE_UP_FORMAT, view->ref) < sizeof(buf) &&
+                   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;
@@ -2500,6 +2546,10 @@ tree_read(struct view *view, char *text)
        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;
 }
@@ -2507,7 +2557,7 @@ tree_read(struct view *view, char *text)
 static bool
 tree_enter(struct view *view, struct line *line)
 {
-       enum open_flags flags = OPEN_DEFAULT;
+       enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
        char *data = line->data;
        enum request request;
 
@@ -2525,10 +2575,14 @@ tree_enter(struct view *view, struct line *line)
                        dirsep[0] = 0;
 
                } else {
-                       int pathlen = strlen(opt_path);
+                       size_t pathlen = strlen(opt_path);
+                       size_t origlen = pathlen;
                        char *basename = data + SIZEOF_TREE_ATTR;
 
-                       string_format_from(opt_path, &pathlen, "%s/", basename);
+                       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
@@ -2538,10 +2592,6 @@ tree_enter(struct view *view, struct line *line)
                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;
 
@@ -2565,12 +2615,24 @@ tree_enter(struct view *view, struct line *line)
        return TRUE;
 }
 
+static void
+tree_select(struct view *view, struct line *line)
+{
+       if (line->type == LINE_TREE_DIR || line->type == LINE_TREE_FILE) {
+               char *text = line->data;
+
+               string_ncopy(view->ref, text + STRING_SIZE("100644 blob "), 40);
+               string_copy(ref_blob, view->ref);
+       }
+}
+
 static struct view_ops tree_ops = {
        "file",
        pager_draw,
        tree_read,
        tree_enter,
        pager_grep,
+       tree_select,
 };
 
 static bool
@@ -2590,6 +2652,7 @@ static struct view_ops blob_ops = {
        blob_read,
        pager_enter,
        pager_grep,
+       pager_select,
 };
 
 
@@ -2608,7 +2671,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;
@@ -2623,9 +2686,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);
@@ -2850,19 +2911,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,
 };
 
 
@@ -3125,7 +3196,7 @@ read_prompt(const char *prompt)
                struct view *view;
                int i, key;
 
-               foreach_displayed_view (view, i)
+               foreach_view (view, i)
                        update_view(view);
 
                report("%s%.*s", prompt, pos, buf);
@@ -3398,7 +3469,7 @@ main(int argc, char *argv[])
 
        if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
                opt_iconv = iconv_open(opt_codeset, opt_encoding);
-               if (opt_iconv == (iconv_t) -1)
+               if (opt_iconv == ICONV_NONE)
                        die("Failed to initialize character set conversion");
        }
 
@@ -3420,7 +3491,7 @@ main(int argc, char *argv[])
                int key;
                int i;
 
-               foreach_displayed_view (view, i)
+               foreach_view (view, i)
                        update_view(view);
 
                /* Refresh, accept single keystroke of input */