Code

Simplify format_argv to always replace
[tig.git] / tig.c
diff --git a/tig.c b/tig.c
index 2cf4360ebff7ec72d1bf66233441dedbe68c819b..7ad7fe741ce2b80ac74c58503b9529ed76ecb886 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -1,4 +1,4 @@
-/* Copyrsght (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
+/* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -365,6 +365,7 @@ static FILE *opt_tty                        = NULL;
 static const char **opt_diff_argv      = NULL;
 static const char **opt_rev_argv       = NULL;
 static const char **opt_file_argv      = NULL;
+static const char **opt_blame_argv     = NULL;
 
 #define is_initial_commit()    (!get_ref_head())
 #define is_head_commit(rev)    (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
@@ -933,6 +934,7 @@ add_builtin_run_requests(void)
        OPT_ERR_(NO_OPTION_VALUE, "No option value"), \
        OPT_ERR_(NO_VALUE_ASSIGNED, "No value assigned"), \
        OPT_ERR_(OBSOLETE_REQUEST_NAME, "Obsolete request name"), \
+       OPT_ERR_(OUT_OF_MEMORY, "Out of memory"), \
        OPT_ERR_(TOO_MANY_OPTION_ARGUMENTS, "Too many option arguments"), \
        OPT_ERR_(UNKNOWN_ATTRIBUTE, "Unknown attribute"), \
        OPT_ERR_(UNKNOWN_COLOR, "Unknown color"), \
@@ -1110,16 +1112,30 @@ parse_string(char *opt, const char *arg, size_t optsize)
        }
 }
 
+static enum option_code
+parse_args(const char ***args, const char *argv[])
+{
+       if (*args == NULL && !argv_copy(args, argv))
+               return OPT_ERR_OUT_OF_MEMORY;
+       return OPT_OK;
+}
+
 /* Wants: name = value */
 static enum option_code
 option_set_command(int argc, const char *argv[])
 {
-       if (argc != 3)
+       if (argc < 3)
                return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
 
        if (strcmp(argv[1], "="))
                return OPT_ERR_NO_VALUE_ASSIGNED;
 
+       if (!strcmp(argv[0], "blame-options"))
+               return parse_args(&opt_blame_argv, argv + 2);
+
+       if (argc != 3)
+               return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
+
        if (!strcmp(argv[0], "show-author"))
                return parse_enum(&opt_author, argv[2], author_map);
 
@@ -1362,7 +1378,6 @@ enum view_type {
 struct view {
        enum view_type type;    /* View type */
        const char *name;       /* View name */
-       const char *cmd_env;    /* Command line set via environment */
        const char *id;         /* Points to either of ref_{head,commit,blob} */
 
        struct view_ops *ops;   /* View operations */
@@ -1414,13 +1429,19 @@ struct view {
        time_t update_secs;
 };
 
+enum open_flags {
+       OPEN_DEFAULT = 0,       /* Use default view switching. */
+       OPEN_SPLIT = 1,         /* Split current view. */
+       OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
+       OPEN_REFRESH = 16,      /* Refresh view using previous command. */
+       OPEN_PREPARED = 32,     /* Open already prepared command. */
+};
+
 struct view_ops {
        /* What type of content being displayed. Used in the title bar. */
        const char *type;
-       /* Default command arguments. */
-       const char **argv;
        /* Open and reads in all view content. */
-       bool (*open)(struct view *view);
+       bool (*open)(struct view *view, enum open_flags flags);
        /* Read one line; updates view->line. */
        bool (*read)(struct view *view, char *data);
        /* Draw one line; @lineno must be < view->height. */
@@ -1431,8 +1452,6 @@ struct view_ops {
        bool (*grep)(struct view *view, struct line *line);
        /* Select line */
        void (*select)(struct view *view, struct line *line);
-       /* Prepare view for loading */
-       bool (*prepare)(struct view *view);
 };
 
 static struct view_ops blame_ops;
@@ -1447,11 +1466,11 @@ static struct view_ops status_ops;
 static struct view_ops tree_ops;
 static struct view_ops branch_ops;
 
-#define VIEW_STR(type, name, env, ref, ops, map, git) \
-       { type, name, #env, ref, ops, map, git }
+#define VIEW_STR(type, name, ref, ops, map, git) \
+       { type, name, ref, ops, map, git }
 
 #define VIEW_(id, name, ops, git, ref) \
-       VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
+       VIEW_STR(VIEW_##id, name, ref, ops, KEYMAP_##id, git)
 
 static struct view views[] = {
        VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
@@ -1498,7 +1517,9 @@ set_view_attr(struct view *view, enum line_type type)
        }
 }
 
-static int
+#define VIEW_MAX_LEN(view) ((view)->width + (view)->yoffset - (view)->col)
+
+static bool
 draw_chars(struct view *view, enum line_type type, const char *string,
           int max_len, bool use_tilde)
 {
@@ -1509,7 +1530,7 @@ draw_chars(struct view *view, enum line_type type, const char *string,
        size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
 
        if (max_len <= 0)
-               return 0;
+               return VIEW_MAX_LEN(view) <= 0;
 
        len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
 
@@ -1540,25 +1561,26 @@ draw_chars(struct view *view, enum line_type type, const char *string,
                }
        }
 
-       return col;
+       view->col += col;
+       return VIEW_MAX_LEN(view) <= 0;
 }
 
-static int
+static bool
 draw_space(struct view *view, enum line_type type, int max, int spaces)
 {
        static char space[] = "                    ";
-       int col = 0;
 
        spaces = MIN(max, spaces);
 
        while (spaces > 0) {
                int len = MIN(spaces, sizeof(space) - 1);
 
-               col += draw_chars(view, type, space, len, FALSE);
+               if (draw_chars(view, type, space, len, FALSE))
+                       return TRUE;
                spaces -= len;
        }
 
-       return col;
+       return VIEW_MAX_LEN(view) <= 0;
 }
 
 static bool
@@ -1569,18 +1591,19 @@ draw_text(struct view *view, enum line_type type, const char *string)
        do {
                size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
 
-               view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, TRUE);
+               if (draw_chars(view, type, text, VIEW_MAX_LEN(view), TRUE))
+                       return TRUE;
                string += pos;
-       } while (*string && view->width + view->yoffset > view->col);
+       } while (*string);
 
-       return view->width + view->yoffset <= view->col;
+       return VIEW_MAX_LEN(view) <= 0;
 }
 
 static bool
 draw_graphic(struct view *view, enum line_type type, const chtype graphic[], size_t size, bool separator)
 {
        size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
-       int max = view->width + view->yoffset - view->col;
+       int max = VIEW_MAX_LEN(view);
        int i;
 
        if (max < size)
@@ -1599,23 +1622,20 @@ draw_graphic(struct view *view, enum line_type type, const chtype graphic[], siz
                view->col++;
        }
 
-       return view->width + view->yoffset <= view->col;
+       return VIEW_MAX_LEN(view) <= 0;
 }
 
 static bool
 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
 {
-       int max = MIN(view->width + view->yoffset - view->col, len);
-       int col;
+       int max = MIN(VIEW_MAX_LEN(view), len);
+       int col = view->col;
 
-       if (text)
-               col = draw_chars(view, type, text, max - 1, trim);
-       else
-               col = draw_space(view, type, max - 1, max - 1);
+       if (!text) 
+               return draw_space(view, type, max, max);
 
-       view->col += col;
-       view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
-       return view->width + view->yoffset <= view->col;
+       return draw_chars(view, type, text, max - 1, trim)
+           || draw_space(view, LINE_DEFAULT, max - (view->col - col), max);
 }
 
 static bool
@@ -1624,6 +1644,9 @@ draw_date(struct view *view, struct time *time)
        const char *date = mkdate(time, opt_date);
        int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
 
+       if (opt_date == DATE_NO)
+               return FALSE;
+
        return draw_field(view, LINE_DATE, date, cols, FALSE);
 }
 
@@ -1633,6 +1656,9 @@ draw_author(struct view *view, const char *author)
        bool trim = opt_author_cols == 0 || opt_author_cols > 5;
        bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
 
+       if (opt_author == AUTHOR_NO)
+               return FALSE;
+
        if (abbreviate && author)
                author = get_author_initials(author);
 
@@ -1665,7 +1691,7 @@ draw_lineno(struct view *view, unsigned int lineno)
 {
        char number[10];
        int digits3 = view->digits < 3 ? 3 : view->digits;
-       int max = MIN(view->width + view->yoffset - view->col, digits3);
+       int max = MIN(VIEW_MAX_LEN(view), digits3);
        char *text = NULL;
        chtype separator = opt_line_graphics ? ACS_VLINE : '|';
 
@@ -1678,9 +1704,9 @@ draw_lineno(struct view *view, unsigned int lineno)
                        text = number;
        }
        if (text)
-               view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
+               draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
        else
-               view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
+               draw_space(view, LINE_LINE_NUMBER, max, digits3);
        return draw_graphic(view, LINE_DEFAULT, &separator, 1, TRUE);
 }
 
@@ -1951,14 +1977,16 @@ toggle_option(enum request request)
 }
 
 static void
-maximize_view(struct view *view)
+maximize_view(struct view *view, bool redraw)
 {
        memset(display, 0, sizeof(display));
        current_view = 0;
        display[current_view] = view;
        resize_display();
-       redraw_display(FALSE);
-       report("");
+       if (redraw) {
+               redraw_display(FALSE);
+               report("");
+       }
 }
 
 
@@ -2343,7 +2371,7 @@ format_arg(const char *name)
        } vars[] = {
 #define FORMAT_VAR(name, value, value_if_empty) \
        { name, STRING_SIZE(name), value, value_if_empty }
-               FORMAT_VAR("%(directory)",      opt_path,       ""),
+               FORMAT_VAR("%(directory)",      opt_path,       "."),
                FORMAT_VAR("%(file)",           opt_file,       ""),
                FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
                FORMAT_VAR("%(head)",           ref_head,       ""),
@@ -2362,7 +2390,7 @@ format_arg(const char *name)
 }
 
 static bool
-format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool first)
+format_argv(const char ***dst_argv, const char *src_argv[], bool first)
 {
        char buf[SIZEOF_STR];
        int argc;
@@ -2383,6 +2411,11 @@ format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool f
                                break;
                        continue;
 
+               } else if (!strcmp(arg, "%(blameargs)")) {
+                       if (!argv_append_array(dst_argv, opt_blame_argv))
+                               break;
+                       continue;
+
                } else if (!strcmp(arg, "%(revargs)") ||
                           (first && !strcmp(arg, "%(commit)"))) {
                        if (!argv_append_array(dst_argv, opt_rev_argv))
@@ -2395,7 +2428,7 @@ format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool f
                        int len = next - arg;
                        const char *value;
 
-                       if (!next || !replace) {
+                       if (!next) {
                                len = strlen(arg);
                                value = "";
 
@@ -2410,7 +2443,7 @@ format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool f
                        if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
                                return FALSE;
 
-                       arg = next && replace ? strchr(next, ')') + 1 : NULL;
+                       arg = next ? strchr(next, ')') + 1 : NULL;
                }
 
                if (!argv_append(dst_argv, buf))
@@ -2467,51 +2500,37 @@ setup_update(struct view *view, const char *vid)
 }
 
 static bool
-prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
-{
-       view->dir = dir;
-       return format_argv(&view->argv, argv, replace, !view->prev);
-}
-
-static bool
-prepare_update(struct view *view, const char *argv[], const char *dir)
+prepare_update(struct view *view, const char *dir, const char *argv[])
 {
        if (view->pipe)
-               end_update(view, TRUE);
-       return prepare_io(view, dir, argv, FALSE);
+               io_done(view->pipe);
+       view->dir = dir;
+       return argv_copy(&view->argv, argv);
 }
 
 static bool
 start_update(struct view *view, const char **argv, const char *dir)
 {
-       if (view->pipe)
-               io_done(view->pipe);
-       return prepare_io(view, dir, argv, FALSE) &&
+       return prepare_update(view, dir, argv) &&
               io_run(&view->io, IO_RD, dir, view->argv);
 }
 
 static bool
-prepare_update_file(struct view *view, const char *name)
+begin_update(struct view *view, const char *dir, const char **argv, enum open_flags flags)
 {
-       if (view->pipe)
-               end_update(view, TRUE);
-       argv_free(view->argv);
-       return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
-}
+       bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
+       bool refresh = flags & (OPEN_REFRESH | OPEN_PREPARED);
+
+       if (!reload && !strcmp(view->vid, view->id))
+               return TRUE;
 
-static bool
-begin_update(struct view *view, bool refresh)
-{
        if (view->pipe)
                end_update(view, TRUE);
 
        if (!refresh) {
-               if (view->ops->prepare) {
-                       if (!view->ops->prepare(view))
-                               return FALSE;
-               } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
+               view->dir = dir;
+               if (!format_argv(&view->argv, argv, !view->prev))
                        return FALSE;
-               }
 
                /* Put the current ref_* value to the view title ref
                 * member. This is needed by the blob view. Most other
@@ -2523,12 +2542,21 @@ begin_update(struct view *view, bool refresh)
        if (view->argv && view->argv[0] &&
            !io_run(&view->io, IO_RD, view->dir, view->argv))
                return FALSE;
+       else if (view->argv && !strcmp(view->argv[0], opt_cdup) &&
+                !io_open(&view->io, "%s%s", opt_cdup, view->argv[1]))
+               return FALSE;
 
        setup_update(view, view->id);
 
        return TRUE;
 }
 
+static bool
+view_open(struct view *view, enum open_flags flags)
+{
+       return begin_update(view, NULL, NULL, flags);
+}
+
 static bool
 update_view(struct view *view)
 {
@@ -2665,23 +2693,63 @@ add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
  * View opening
  */
 
-enum open_flags {
-       OPEN_DEFAULT = 0,       /* Use default view switching. */
-       OPEN_SPLIT = 1,         /* Split current view. */
-       OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
-       OPEN_REFRESH = 16,      /* Refresh view using previous command. */
-       OPEN_PREPARED = 32,     /* Open already prepared command. */
-};
+static void
+load_view(struct view *view, enum open_flags flags)
+{
+       if (view->pipe)
+               end_update(view, TRUE);
+       if (!view->ops->open(view, flags)) {
+               report("Failed to load %s view", view->name);
+               return;
+       }
+       restore_view_position(view);
+
+       if (view->pipe && view->lines == 0) {
+               /* Clear the old view and let the incremental updating refill
+                * the screen. */
+               werase(view->win);
+               view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
+               report("");
+       } else if (view_is_displayed(view)) {
+               redraw_view(view);
+               report("");
+       }
+}
+
+#define refresh_view(view) load_view(view, OPEN_REFRESH)
+
+static void
+split_view(struct view *prev, struct view *view)
+{
+       display[1] = view;
+       current_view = 1;
+       view->parent = prev;
+       resize_display();
+
+       if (prev->lineno - prev->offset >= prev->height) {
+               /* Take the title line into account. */
+               int lines = prev->lineno - prev->offset - prev->height + 1;
+
+               /* Scroll the view that was split if the current line is
+                * outside the new limited view. */
+               do_scroll_view(prev, lines);
+       }
+
+       if (view != prev && view_is_displayed(prev)) {
+               /* "Blur" the previous view. */
+               update_view_title(prev);
+       }
+}
 
 static void
 open_view(struct view *prev, enum request request, enum open_flags flags)
 {
        bool split = !!(flags & OPEN_SPLIT);
-       bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
-       bool nomaximize = !!(flags & OPEN_REFRESH);
+       bool reload = !!(flags & (OPEN_RELOAD | OPEN_PREPARED));
        struct view *view = VIEW(request);
        int nviews = displayed_views();
-       struct view *base_view = display[0];
+
+       assert(flags ^ OPEN_REFRESH);
 
        if (view == prev && nviews == 1 && !reload) {
                report("Already in %s view", view->name);
@@ -2694,14 +2762,9 @@ open_view(struct view *prev, enum request request, enum open_flags flags)
        }
 
        if (split) {
-               display[1] = view;
-               current_view = 1;
-               view->parent = prev;
-       } else if (!nomaximize) {
-               /* Maximize the current view. */
-               memset(display, 0, sizeof(display));
-               current_view = 0;
-               display[current_view] = view;
+               split_view(prev, view);
+       } else {
+               maximize_view(view, FALSE);
        }
 
        /* No prev signals that this is the first loaded view. */
@@ -2709,50 +2772,33 @@ open_view(struct view *prev, enum request request, enum open_flags flags)
                view->prev = prev;
        }
 
-       /* Resize the view when switching between split- and full-screen,
-        * or when switching between two different full-screen views. */
-       if (nviews != displayed_views() ||
-           (nviews == 1 && base_view != display[0]))
-               resize_display();
-
-       if (view->ops->open) {
-               if (view->pipe)
-                       end_update(view, TRUE);
-               if (!view->ops->open(view)) {
-                       report("Failed to load %s view", view->name);
-                       return;
-               }
-               restore_view_position(view);
-
-       } else if ((reload || strcmp(view->vid, view->id)) &&
-                  !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
-               report("Failed to load %s view", view->name);
-               return;
-       }
+       load_view(view, flags);
+}
 
-       if (split && prev->lineno - prev->offset >= prev->height) {
-               /* Take the title line into account. */
-               int lines = prev->lineno - prev->offset - prev->height + 1;
+static void
+open_argv(struct view *prev, struct view *view, const char *argv[], const char *dir, enum open_flags flags)
+{
+       enum request request = view - views + REQ_OFFSET + 1;
 
-               /* Scroll the view that was split if the current line is
-                * outside the new limited view. */
-               do_scroll_view(prev, lines);
+       if (!prepare_update(view, dir, argv)) {
+               report("Failed to open %s view: %s", view->name, io_strerror(&view->io));
+       } else {
+               open_view(prev, request, flags | OPEN_PREPARED);
        }
+}
 
-       if (prev && view != prev && split && view_is_displayed(prev)) {
-               /* "Blur" the previous view. */
-               update_view_title(prev);
-       }
+static void
+open_file(struct view *prev, struct view *view, const char *file, enum open_flags flags)
+{
+       enum request request = view - views + REQ_OFFSET + 1;
+       const char *file_argv[] = { opt_cdup, file , NULL };
 
-       if (view->pipe && view->lines == 0) {
-               /* Clear the old view and let the incremental updating refill
-                * the screen. */
-               werase(view->win);
-               view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
-               report("");
-       } else if (view_is_displayed(view)) {
-               redraw_view(view);
-               report("");
+       if (view->pipe)
+               end_update(view, TRUE);
+       if (!argv_copy(&view->argv, file_argv)) {
+               report("Failed to load %s: out of memory", file);
+       } else {
+               open_view(prev, request, flags | OPEN_PREPARED);
        }
 }
 
@@ -2807,7 +2853,7 @@ open_run_request(enum request request)
                return;
        }
 
-       if (format_argv(&argv, req->argv, TRUE, FALSE))
+       if (format_argv(&argv, req->argv, FALSE))
                open_external_viewer(argv, NULL);
        if (argv)
                argv_free(argv);
@@ -2957,7 +3003,7 @@ view_driver(struct view *view, enum request request)
 
        case REQ_MAXIMIZE:
                if (displayed_views() == 2)
-                       maximize_view(view);
+                       maximize_view(view, TRUE);
                break;
 
        case REQ_OPTIONS:
@@ -3014,7 +3060,7 @@ view_driver(struct view *view, enum request request)
                 * view itself. Parents to closed view should never be
                 * followed. */
                if (view->prev && view->prev != view) {
-                       maximize_view(view->prev);
+                       maximize_view(view->prev, TRUE);
                        view->prev = view;
                        break;
                }
@@ -3311,8 +3357,7 @@ pager_select(struct view *view, struct line *line)
 
 static struct view_ops pager_ops = {
        "line",
-       NULL,
-       NULL,
+       view_open,
        pager_read,
        pager_draw,
        pager_request,
@@ -3320,9 +3365,15 @@ static struct view_ops pager_ops = {
        pager_select,
 };
 
-static const char *log_argv[SIZEOF_ARG] = {
-       "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
-};
+static bool
+log_open(struct view *view, enum open_flags flags)
+{
+       static const char *log_argv[] = {
+               "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
+       };
+
+       return begin_update(view, NULL, log_argv, flags);
+}
 
 static enum request
 log_request(struct view *view, enum request request, struct line *line)
@@ -3330,7 +3381,7 @@ log_request(struct view *view, enum request request, struct line *line)
        switch (request) {
        case REQ_REFRESH:
                load_refs();
-               open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
+               refresh_view(view);
                return REQ_NONE;
        default:
                return pager_request(view, request, line);
@@ -3339,8 +3390,7 @@ log_request(struct view *view, enum request request, struct line *line)
 
 static struct view_ops log_ops = {
        "line",
-       log_argv,
-       NULL,
+       log_open,
        pager_read,
        pager_draw,
        log_request,
@@ -3348,11 +3398,17 @@ static struct view_ops log_ops = {
        pager_select,
 };
 
-static const char *diff_argv[SIZEOF_ARG] = {
-       "git", "show", "--pretty=fuller", "--no-color", "--root",
-               "--patch-with-stat", "--find-copies-harder", "-C",
-               "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
-};
+static bool
+diff_open(struct view *view, enum open_flags flags)
+{
+       static const char *diff_argv[] = {
+               "git", "show", "--pretty=fuller", "--no-color", "--root",
+                       "--patch-with-stat", "--find-copies-harder", "-C",
+                       "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
+       };
+
+       return begin_update(view, NULL, diff_argv, flags);
+}
 
 static bool
 diff_read(struct view *view, char *data)
@@ -3383,8 +3439,7 @@ diff_read(struct view *view, char *data)
 
 static struct view_ops diff_ops = {
        "line",
-       diff_argv,
-       NULL,
+       diff_open,
        diff_read,
        pager_draw,
        pager_request,
@@ -3480,7 +3535,7 @@ help_open_keymap(struct view *view, enum keymap keymap)
 }
 
 static bool
-help_open(struct view *view)
+help_open(struct view *view, enum open_flags flags)
 {
        enum keymap keymap;
 
@@ -3502,8 +3557,7 @@ help_request(struct view *view, enum request request, struct line *line)
                if (line->type == LINE_HELP_KEYMAP) {
                        help_keymap_hidden[line->other] =
                                !help_keymap_hidden[line->other];
-                       view->p_restore = TRUE;
-                       open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
+                       refresh_view(view);
                }
 
                return REQ_NONE;
@@ -3514,7 +3568,6 @@ help_request(struct view *view, enum request request, struct line *line)
 
 static struct view_ops help_ops = {
        "line",
-       NULL,
        help_open,
        NULL,
        pager_draw,
@@ -3806,10 +3859,10 @@ tree_draw(struct view *view, struct line *line, unsigned int lineno)
                if (draw_mode(view, entry->mode))
                        return TRUE;
 
-               if (opt_author && draw_author(view, entry->author))
+               if (draw_author(view, entry->author))
                        return TRUE;
 
-               if (opt_date && draw_date(view, &entry->time))
+               if (draw_date(view, &entry->time))
                        return TRUE;
        }
 
@@ -3951,8 +4004,12 @@ tree_select(struct view *view, struct line *line)
 }
 
 static bool
-tree_prepare(struct view *view)
+tree_open(struct view *view, enum open_flags flags)
 {
+       static const char *tree_argv[] = {
+               "git", "ls-tree", "%(commit)", "%(directory)", NULL
+       };
+
        if (view->lines == 0 && opt_prefix[0]) {
                char *pos = opt_prefix;
 
@@ -3973,25 +4030,29 @@ tree_prepare(struct view *view)
                opt_path[0] = 0;
        }
 
-       return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
+       return begin_update(view, opt_cdup, tree_argv, flags);
 }
 
-static const char *tree_argv[SIZEOF_ARG] = {
-       "git", "ls-tree", "%(commit)", "%(directory)", NULL
-};
-
 static struct view_ops tree_ops = {
        "file",
-       tree_argv,
-       NULL,
+       tree_open,
        tree_read,
        tree_draw,
        tree_request,
        tree_grep,
        tree_select,
-       tree_prepare,
 };
 
+static bool
+blob_open(struct view *view, enum open_flags flags)
+{
+       static const char *blob_argv[] = {
+               "git", "cat-file", "blob", "%(blob)", NULL
+       };
+
+       return begin_update(view, NULL, blob_argv, flags);
+}
+
 static bool
 blob_read(struct view *view, char *line)
 {
@@ -4012,14 +4073,9 @@ blob_request(struct view *view, enum request request, struct line *line)
        }
 }
 
-static const char *blob_argv[SIZEOF_ARG] = {
-       "git", "cat-file", "blob", "%(blob)", NULL
-};
-
 static struct view_ops blob_ops = {
        "line",
-       blob_argv,
-       NULL,
+       blob_open,
        blob_read,
        pager_draw,
        blob_request,
@@ -4055,7 +4111,7 @@ struct blame {
 };
 
 static bool
-blame_open(struct view *view)
+blame_open(struct view *view, enum open_flags flags)
 {
        char path[SIZEOF_STR];
        size_t i;
@@ -4181,7 +4237,7 @@ blame_read_file(struct view *view, const char *line, bool *read_file)
 {
        if (!line) {
                const char *blame_argv[] = {
-                       "git", "blame", "--incremental",
+                       "git", "blame", "%(blameargs)", "--incremental",
                                *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
                };
 
@@ -4290,10 +4346,10 @@ blame_draw(struct view *view, struct line *line, unsigned int lineno)
                time = &blame->commit->time;
        }
 
-       if (opt_date && draw_date(view, time))
+       if (draw_date(view, time))
                return TRUE;
 
-       if (opt_author && draw_author(view, author))
+       if (draw_author(view, author))
                return TRUE;
 
        if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
@@ -4371,7 +4427,7 @@ blame_request(struct view *view, enum request request, struct line *line)
                        string_copy(opt_file, blame->commit->filename);
                        if (blame->lineno)
                                view->lineno = blame->lineno;
-                       open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
+                       refresh_view(view);
                }
                break;
 
@@ -4384,7 +4440,7 @@ blame_request(struct view *view, enum request request, struct line *line)
                        string_copy_rev(opt_ref, blame->commit->parent_id);
                        string_copy(opt_file, blame->commit->parent_filename);
                        setup_blame_parent_line(view, blame);
-                       open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
+                       refresh_view(view);
                }
                break;
 
@@ -4410,14 +4466,10 @@ blame_request(struct view *view, enum request request, struct line *line)
                                diff_index_argv[7] = "/dev/null";
                        }
 
-                       if (!prepare_update(diff, diff_index_argv, NULL)) {
-                               report("Failed to allocate diff command");
-                               break;
-                       }
-                       flags |= OPEN_PREPARED;
+                       open_argv(view, diff, diff_index_argv, NULL, flags);
+               } else {
+                       open_view(view, REQ_VIEW_DIFF, flags);
                }
-
-               open_view(view, REQ_VIEW_DIFF, flags);
                if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
                        string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
                break;
@@ -4463,7 +4515,6 @@ blame_select(struct view *view, struct line *line)
 
 static struct view_ops blame_ops = {
        "line",
-       NULL,
        blame_open,
        blame_read,
        blame_draw,
@@ -4514,10 +4565,10 @@ branch_draw(struct view *view, struct line *line, unsigned int lineno)
        struct branch *branch = line->data;
        enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
 
-       if (opt_date && draw_date(view, &branch->time))
+       if (draw_date(view, &branch->time))
                return TRUE;
 
-       if (opt_author && draw_author(view, branch->author))
+       if (draw_author(view, branch->author))
                return TRUE;
 
        draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name);
@@ -4532,7 +4583,7 @@ branch_request(struct view *view, enum request request, struct line *line)
        switch (request) {
        case REQ_REFRESH:
                load_refs();
-               open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
+               refresh_view(view);
                return REQ_NONE;
 
        case REQ_TOGGLE_SORT_FIELD:
@@ -4550,10 +4601,7 @@ branch_request(struct view *view, enum request request, struct line *line)
                };
                struct view *main_view = VIEW(REQ_VIEW_MAIN);
 
-               if (!prepare_update(main_view, all_branches_argv, NULL))
-                       report("Failed to load view of all branches");
-               else
-                       open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
+               open_argv(view, main_view, all_branches_argv, NULL, OPEN_SPLIT);
                return REQ_NONE;
        }
        default:
@@ -4620,7 +4668,7 @@ branch_open_visitor(void *data, const struct ref *ref)
 }
 
 static bool
-branch_open(struct view *view)
+branch_open(struct view *view, enum open_flags flags)
 {
        const char *branch_log[] = {
                "git", "log", "--no-color", "--pretty=raw",
@@ -4666,7 +4714,6 @@ branch_select(struct view *view, struct line *line)
 
 static struct view_ops branch_ops = {
        "branch",
-       NULL,
        branch_open,
        branch_read,
        branch_draw,
@@ -4918,7 +4965,7 @@ status_update_onbranch(void)
  * info using git-diff-files(1), and finally untracked files using
  * git-ls-files(1). */
 static bool
-status_open(struct view *view)
+status_open(struct view *view, enum open_flags flags)
 {
        reset_view(view);
 
@@ -4999,15 +5046,6 @@ status_draw(struct view *view, struct line *line, unsigned int lineno)
        return TRUE;
 }
 
-static enum request
-status_load_error(struct view *view, struct view *stage, const char *path)
-{
-       if (displayed_views() == 2 || display[current_view] != view)
-               maximize_view(view);
-       report("Failed to load '%s': %s", path, io_strerror(&stage->io));
-       return REQ_NONE;
-}
-
 static enum request
 status_enter(struct view *view, struct line *line)
 {
@@ -5017,7 +5055,7 @@ status_enter(struct view *view, struct line *line)
         * path, so leave it empty. */
        const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
        const char *info;
-       enum open_flags split;
+       enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
        struct view *stage = VIEW(REQ_VIEW_STAGE);
 
        if (line->type == LINE_STAT_NONE ||
@@ -5034,8 +5072,7 @@ status_enter(struct view *view, struct line *line)
                                        "--", "/dev/null", newpath, NULL
                        };
 
-                       if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
-                               return status_load_error(view, stage, newpath);
+                       open_argv(view, stage, no_head_diff_argv, opt_cdup, flags); 
                } else {
                        const char *index_show_argv[] = {
                                "git", "diff-index", "--root", "--patch-with-stat",
@@ -5043,8 +5080,7 @@ status_enter(struct view *view, struct line *line)
                                        oldpath, newpath, NULL
                        };
 
-                       if (!prepare_update(stage, index_show_argv, opt_cdup))
-                               return status_load_error(view, stage, newpath);
+                       open_argv(view, stage, index_show_argv, opt_cdup, flags);
                }
 
                if (status)
@@ -5060,8 +5096,7 @@ status_enter(struct view *view, struct line *line)
                                "-C", "-M", "--", oldpath, newpath, NULL
                };
 
-               if (!prepare_update(stage, files_show_argv, opt_cdup))
-                       return status_load_error(view, stage, newpath);
+               open_argv(view, stage, files_show_argv, opt_cdup, flags);
                if (status)
                        info = "Unstaged changes to %s";
                else
@@ -5079,8 +5114,7 @@ status_enter(struct view *view, struct line *line)
                        return REQ_NONE;
                }
 
-               if (!prepare_update_file(stage, newpath))
-                       return status_load_error(view, stage, newpath);
+               open_file(view, stage, newpath, flags);
                info = "Untracked file %s";
                break;
 
@@ -5091,8 +5125,6 @@ status_enter(struct view *view, struct line *line)
                die("line type %d not handled in switch", line->type);
        }
 
-       split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
-       open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
        if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
                if (status) {
                        stage_status = *status;
@@ -5362,7 +5394,7 @@ status_request(struct view *view, enum request request, struct line *line)
                return request;
        }
 
-       open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
+       refresh_view(view);
 
        return REQ_NONE;
 }
@@ -5433,7 +5465,6 @@ status_grep(struct view *view, struct line *line)
 
 static struct view_ops status_ops = {
        "file",
-       NULL,
        status_open,
        NULL,
        status_draw,
@@ -5643,8 +5674,7 @@ stage_request(struct view *view, enum request request, struct line *line)
                return request;
        }
 
-       VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
-       open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
+       refresh_view(view->parent);
 
        /* Check whether the staged entry still exists, and close the
         * stage view if it doesn't. */
@@ -5653,26 +5683,14 @@ stage_request(struct view *view, enum request request, struct line *line)
                return REQ_VIEW_CLOSE;
        }
 
-       if (stage_line_type == LINE_STAT_UNTRACKED) {
-               if (!suffixcmp(stage_status.new.name, -1, "/")) {
-                       report("Cannot display a directory");
-                       return REQ_NONE;
-               }
-
-               if (!prepare_update_file(view, stage_status.new.name)) {
-                       report("Failed to open file: %s", strerror(errno));
-                       return REQ_NONE;
-               }
-       }
-       open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
+       refresh_view(view);
 
        return REQ_NONE;
 }
 
 static struct view_ops stage_ops = {
        "line",
-       NULL,
-       NULL,
+       view_open,
        pager_read,
        pager_draw,
        stage_request,
@@ -5763,11 +5781,17 @@ struct commit {
        struct graph_canvas graph;      /* Ancestry chain graphics. */
 };
 
-static const char *main_argv[SIZEOF_ARG] = {
-       "git", "log", "--no-color", "--pretty=raw", "--parents",
-               "--topo-order", "%(diffargs)", "%(revargs)",
-               "--", "%(fileargs)", NULL
-};
+static bool
+main_open(struct view *view, enum open_flags flags)
+{
+       static const char *main_argv[] = {
+               "git", "log", "--no-color", "--pretty=raw", "--parents",
+                       "--topo-order", "%(diffargs)", "%(revargs)",
+                       "--", "%(fileargs)", NULL
+       };
+
+       return begin_update(view, NULL, main_argv, flags);
+}
 
 static bool
 main_draw(struct view *view, struct line *line, unsigned int lineno)
@@ -5777,10 +5801,10 @@ main_draw(struct view *view, struct line *line, unsigned int lineno)
        if (!commit->author)
                return FALSE;
 
-       if (opt_date && draw_date(view, &commit->time))
+       if (draw_date(view, &commit->time))
                return TRUE;
 
-       if (opt_author && draw_author(view, commit->author))
+       if (draw_author(view, commit->author))
                return TRUE;
 
        if (opt_rev_graph && draw_graph(view, &commit->graph))
@@ -5914,12 +5938,12 @@ main_request(struct view *view, enum request request, struct line *line)
        switch (request) {
        case REQ_ENTER:
                if (view_is_displayed(view) && display[0] != view)
-                       maximize_view(view);
+                       maximize_view(view, TRUE);
                open_view(view, REQ_VIEW_DIFF, flags);
                break;
        case REQ_REFRESH:
                load_refs();
-               open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
+               refresh_view(view);
                break;
        default:
                return request;
@@ -5970,8 +5994,7 @@ main_select(struct view *view, struct line *line)
 
 static struct view_ops main_ops = {
        "commit",
-       main_argv,
-       NULL,
+       main_open,
        main_read,
        main_draw,
        main_request,
@@ -6695,7 +6718,7 @@ static const char usage[] =
 "\n"
 "Usage: tig        [options] [revs] [--] [paths]\n"
 "   or: tig show   [options] [revs] [--] [paths]\n"
-"   or: tig blame  [rev] path\n"
+"   or: tig blame  [options] [rev] [--] path\n"
 "   or: tig status\n"
 "   or: tig <      [git command output]\n"
 "\n"
@@ -6792,16 +6815,18 @@ parse_options(int argc, const char *argv[])
                return REQ_VIEW_STATUS;
 
        } else if (!strcmp(subcommand, "blame")) {
-               if (argc <= 2 || argc > 4)
+               filter_rev_parse(&opt_file_argv, "--no-revs", "--no-flags", argv + 2);
+               filter_rev_parse(&opt_blame_argv, "--no-revs", "--flags", argv + 2);
+               filter_rev_parse(&opt_rev_argv, "--symbolic", "--revs-only", argv + 2);
+
+               if (!opt_file_argv || opt_file_argv[1] || (opt_rev_argv && opt_rev_argv[1]))
                        die("invalid number of options to blame\n\n%s", usage);
 
-               i = 2;
-               if (argc == 4) {
-                       string_ncopy(opt_ref, argv[i], strlen(argv[i]));
-                       i++;
+               if (opt_rev_argv) {
+                       string_ncopy(opt_ref, opt_rev_argv[0], strlen(opt_rev_argv[0]));
                }
 
-               string_ncopy(opt_file, argv[i], strlen(argv[i]));
+               string_ncopy(opt_file, opt_file_argv[0], strlen(opt_file_argv[0]));
                return REQ_VIEW_BLAME;
 
        } else if (!strcmp(subcommand, "show")) {
@@ -6851,7 +6876,6 @@ main(int argc, const char *argv[])
        const char *codeset = "UTF-8";
        enum request request = parse_options(argc, argv);
        struct view *view;
-       size_t i;
 
        signal(SIGINT, quit);
        signal(SIGPIPE, SIG_IGN);
@@ -6888,16 +6912,6 @@ main(int argc, const char *argv[])
        if (load_refs() == ERR)
                die("Failed to load refs.");
 
-       foreach_view (view, i) {
-               if (getenv(view->cmd_env))
-                       warn("Use of the %s environment variable is deprecated,"
-                            " use options or TIG_DIFF_ARGS instead",
-                            view->cmd_env);
-               if (!argv_from_env(view->ops->argv, view->cmd_env))
-                       die("Too many arguments in the `%s` environment variable",
-                           view->cmd_env);
-       }
-
        init_display();
 
        while (view_driver(display[current_view], request)) {
@@ -6939,10 +6953,8 @@ main(int argc, const char *argv[])
 
                                if (!argv_from_string(argv, &argc, cmd)) {
                                        report("Too many arguments");
-                               } else if (!prepare_update(next, argv, NULL)) {
-                                       report("Failed to format command");
                                } else {
-                                       open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
+                                       open_argv(view, next, argv, NULL, OPEN_DEFAULT);
                                }
                        }